|
|
|
@ -4,6 +4,8 @@ import 'package:syncfusion_flutter_datagrid/datagrid.dart';
@@ -4,6 +4,8 @@ import 'package:syncfusion_flutter_datagrid/datagrid.dart';
|
|
|
|
|
import 'package:path/path.dart' as path; |
|
|
|
|
import 'package:win_text_editor/app/modules/content_search/content_search_controller.dart'; |
|
|
|
|
import 'package:win_text_editor/app/modules/content_search/content_search_service.dart'; |
|
|
|
|
import 'package:file_picker/file_picker.dart'; |
|
|
|
|
import 'dart:io'; |
|
|
|
|
|
|
|
|
|
class ResultsView extends StatelessWidget { |
|
|
|
|
const ResultsView({super.key}); |
|
|
|
@ -20,7 +22,77 @@ class ResultsView extends StatelessWidget {
@@ -20,7 +22,77 @@ class ResultsView extends StatelessWidget {
|
|
|
|
|
controller.clearResults(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return Card(child: _buildResultsGrid(controller)); |
|
|
|
|
return Card( |
|
|
|
|
child: GestureDetector( |
|
|
|
|
onSecondaryTapDown: (details) { |
|
|
|
|
_showContextMenu(context, details.globalPosition, controller); |
|
|
|
|
}, |
|
|
|
|
child: _buildResultsGrid(controller), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Future<void> _showContextMenu( |
|
|
|
|
BuildContext context, |
|
|
|
|
Offset position, |
|
|
|
|
ContentSearchController controller, |
|
|
|
|
) async { |
|
|
|
|
// 获取渲染对象以正确定位菜单 |
|
|
|
|
final renderBox = context.findRenderObject() as RenderBox; |
|
|
|
|
final localPosition = renderBox.globalToLocal(position); |
|
|
|
|
|
|
|
|
|
// 显示菜单并等待选择结果 |
|
|
|
|
final result = await showMenu<String>( |
|
|
|
|
context: context, |
|
|
|
|
position: RelativeRect.fromLTRB( |
|
|
|
|
position.dx, |
|
|
|
|
position.dy, |
|
|
|
|
position.dx + renderBox.size.width - localPosition.dx, |
|
|
|
|
position.dy + renderBox.size.height - localPosition.dy, |
|
|
|
|
), |
|
|
|
|
items: [const PopupMenuItem<String>(value: 'export', child: Text('导出(csv)'))], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 处理菜单选择结果 |
|
|
|
|
if (result == 'export' && context.mounted) { |
|
|
|
|
try { |
|
|
|
|
await _exportToCsv(controller); |
|
|
|
|
} catch (e) { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
context, |
|
|
|
|
).showSnackBar(SnackBar(content: Text('导出失败: ${e.toString()}'))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Future<void> _exportToCsv(ContentSearchController controller) async { |
|
|
|
|
String csvData = ''; |
|
|
|
|
if (controller.searchMode == SearchMode.locate) { |
|
|
|
|
csvData = '文件(行号),内容\n'; |
|
|
|
|
for (var result in controller.results) { |
|
|
|
|
csvData += |
|
|
|
|
'${path.basename(result.filePath)}(${result.lineNumber}),${result.lineContent.replaceAll(',', ',')}\n'; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
csvData = '关键词,匹配数量\n'; |
|
|
|
|
for (var result in controller.results) { |
|
|
|
|
csvData += '${result.filePath},${result.lineNumber}\n'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
final filePath = await FilePicker.platform.saveFile( |
|
|
|
|
dialogTitle: '保存导出结果', |
|
|
|
|
fileName: 'search_results.csv', |
|
|
|
|
type: FileType.custom, |
|
|
|
|
allowedExtensions: ['csv'], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (filePath != null) { |
|
|
|
|
final file = File(filePath); |
|
|
|
|
await file.writeAsString(csvData); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Widget _buildResultsGrid(ContentSearchController controller) { |
|
|
|
|