Browse Source

引入pluto_grid成功

master
hejl 1 month ago
parent
commit
ea72f1dc0f
  1. 1
      win_text_editor/lib/modules/content_search/services/custom_search_service.dart
  2. 52
      win_text_editor/lib/modules/xml_search/controllers/xml_search_controller.dart
  3. 18
      win_text_editor/lib/modules/xml_search/models/search_result.dart
  4. 5
      win_text_editor/lib/modules/xml_search/services/xml_search_service.dart
  5. 178
      win_text_editor/lib/modules/xml_search/widgets/results_view.dart
  6. 25
      win_text_editor/lib/shared/components/my_pluto_configuration.dart

1
win_text_editor/lib/modules/content_search/services/custom_search_service.dart

@ -6,7 +6,6 @@ import 'package:flutter_js/flutter_js.dart';
import 'package:win_text_editor/framework/controllers/logger.dart'; import 'package:win_text_editor/framework/controllers/logger.dart';
import 'package:win_text_editor/modules/content_search/models/search_mode.dart'; import 'package:win_text_editor/modules/content_search/models/search_mode.dart';
import 'package:win_text_editor/modules/content_search/models/search_result.dart'; import 'package:win_text_editor/modules/content_search/models/search_result.dart';
import 'package:win_text_editor/modules/content_search/services/base_search_service.dart';
import 'package:win_text_editor/shared/utils/file_utils.dart'; import 'package:win_text_editor/shared/utils/file_utils.dart';
typedef ProgressCallback = void Function(double progress); typedef ProgressCallback = void Function(double progress);

52
win_text_editor/lib/modules/xml_search/controllers/xml_search_controller.dart

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:win_text_editor/framework/controllers/logger.dart'; import 'package:win_text_editor/framework/controllers/logger.dart';
import 'package:win_text_editor/modules/xml_search/models/search_result.dart'; import 'package:win_text_editor/modules/xml_search/models/search_result.dart';
import 'package:win_text_editor/modules/xml_search/services/xml_search_service.dart'; import 'package:win_text_editor/modules/xml_search/services/xml_search_service.dart';
@ -13,16 +14,18 @@ class XmlSearchController extends BaseContentController {
String attributeName = ''; String attributeName = '';
bool _isSearching = false; bool _isSearching = false;
final List<SearchResult> _results = [SearchResult(rowNum: 1, attributeValue: '模拟数据')]; final List<SearchResult> _results = [];
final XmlSearchService _searchService = XmlSearchService(); final XmlSearchService _searchService = XmlSearchService();
List<SearchResult> get results => _results; List<SearchResult> get results => List.unmodifiable(_results); //
String get searchDirectory => _searchDirectory; String get searchDirectory => _searchDirectory;
String get searchQuery => _searchQuery; String get searchQuery => _searchQuery;
bool get isSearching => _isSearching; bool get isSearching => _isSearching;
Timer? _searchDebounce; Timer? _searchDebounce;
final ValueNotifier<bool> refreshNotifier = ValueNotifier(false);
set errorMessage(String value) { set errorMessage(String value) {
Logger().error('打开文件出错:$value'); Logger().error('打开文件出错:$value');
} }
@ -35,6 +38,20 @@ class XmlSearchController extends BaseContentController {
}); });
} }
void removeResult(SearchResult result) async {
try {
Logger().debug('删除前结果数: ${_results.length}');
await _searchService.removeNodes(searchDirectory, nodePath, attributeName, [result]);
_results.removeWhere((r) => r.hashCode == result.hashCode); // 使hashCode确保正确删除
Logger().debug('删除后结果数: ${_results.length}');
notifyListeners();
Future.delayed(Duration.zero, notifyListeners);
} catch (e) {
Logger().error('删除失败: $e');
rethrow;
}
}
Future<void> pickFile() async { Future<void> pickFile() async {
final result = await FilePicker.platform.pickFiles( final result = await FilePicker.platform.pickFiles(
type: FileType.custom, type: FileType.custom,
@ -59,17 +76,19 @@ class XmlSearchController extends BaseContentController {
_isSearching = true; _isSearching = true;
notifyListeners(); notifyListeners();
_results.clear();
notifyListeners();
try { try {
final newResults = await _searchService.searchFromDirectory( _results.clear();
directory: _searchDirectory, _results.addAll(
nodeName: nodePath, await _searchService.searchFromDirectory(
attributeName: attributeName, directory: _searchDirectory,
queryContent: searchQuery, nodeName: nodePath,
attributeName: attributeName,
queryContent: searchQuery,
),
); );
_results.addAll(newResults); notifyListeners();
//
Future.delayed(Duration.zero, notifyListeners);
} catch (e) { } catch (e) {
Logger().error("搜索文件出错:$e"); Logger().error("搜索文件出错:$e");
} finally { } finally {
@ -78,6 +97,10 @@ class XmlSearchController extends BaseContentController {
} }
} }
void refresh() {
notifyListeners();
}
set searchDirectory(String value) { set searchDirectory(String value) {
_searchDirectory = value; _searchDirectory = value;
notifyListeners(); notifyListeners();
@ -102,17 +125,10 @@ class XmlSearchController extends BaseContentController {
void cancelSearching() {} void cancelSearching() {}
void removeResult(SearchResult result) async {
await _searchService.removeNodes(searchDirectory, nodePath, attributeName, [result]);
results.remove(result);
notifyListeners();
}
void toggleSelectAll(bool select) { void toggleSelectAll(bool select) {
for (var result in results) { for (var result in results) {
result.isSelected = select; result.isSelected = select;
} }
notifyListeners();
} }
List<SearchResult> getSelectedResults() { List<SearchResult> getSelectedResults() {

18
win_text_editor/lib/modules/xml_search/models/search_result.dart

@ -1,9 +1,8 @@
// search_result.dart
class SearchResult { class SearchResult {
final int rowNum; final int rowNum;
final String attributeValue; final String attributeValue;
int index = 0; int index = 0;
bool isSelected = false; // bool isSelected;
SearchResult({ SearchResult({
required this.rowNum, required this.rowNum,
@ -11,4 +10,19 @@ class SearchResult {
this.index = 0, this.index = 0,
this.isSelected = false, this.isSelected = false,
}); });
// equals和hashCode
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SearchResult &&
runtimeType == other.runtimeType &&
rowNum == other.rowNum &&
attributeValue == other.attributeValue &&
index == other.index &&
isSelected == other.isSelected;
@override
int get hashCode =>
rowNum.hashCode ^ attributeValue.hashCode ^ index.hashCode ^ isSelected.hashCode;
} }

5
win_text_editor/lib/modules/xml_search/services/xml_search_service.dart

@ -63,8 +63,9 @@ class XmlSearchService {
final document = xml.XmlDocument.parse(content); final document = xml.XmlDocument.parse(content);
String newContent = content; String newContent = content;
// 2. // 2. ,
for (SearchResult result in resultList) { for (int i = resultList.length - 1; i >= 0; i--) {
SearchResult result = resultList[i];
final nodes = final nodes =
document.findAllElements(nodeName).where((node) { document.findAllElements(nodeName).where((node) {
final attributeValue = node.getAttribute(attributeName); final attributeValue = node.getAttribute(attributeName);

178
win_text_editor/lib/modules/xml_search/widgets/results_view.dart

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pluto_grid/pluto_grid.dart'; import 'package:pluto_grid/pluto_grid.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -11,6 +10,7 @@ import 'dart:io';
import 'package:win_text_editor/modules/xml_search/controllers/xml_search_controller.dart'; import 'package:win_text_editor/modules/xml_search/controllers/xml_search_controller.dart';
import 'package:win_text_editor/shared/components/my_pluto_column.dart'; import 'package:win_text_editor/shared/components/my_pluto_column.dart';
import 'package:win_text_editor/shared/components/my_pluto_configuration.dart';
class ResultsView extends StatefulWidget { class ResultsView extends StatefulWidget {
const ResultsView({super.key}); const ResultsView({super.key});
@ -20,19 +20,29 @@ class ResultsView extends StatefulWidget {
} }
class _ResultsViewState extends State<ResultsView> { class _ResultsViewState extends State<ResultsView> {
late final PlutoGridStateManager stateManager; late final XmlSearchController controller;
PlutoGridStateManager? stateManager;
@override @override
Widget build(BuildContext context) { void initState() {
final controller = context.watch<XmlSearchController>(); super.initState();
controller = context.read<XmlSearchController>();
}
return Card( @override
child: GestureDetector( Widget build(BuildContext context) {
onSecondaryTapDown: (details) { // 使Consumer而不是ValueListenableBuilder
_showContextMenu(context, details.globalPosition, controller); return Consumer<XmlSearchController>(
}, builder: (context, controller, child) {
child: _buildLocateGrid(controller, context), return Card(
), child: GestureDetector(
onSecondaryTapDown: (details) {
_showContextMenu(context, details.globalPosition, controller);
},
child: _buildResultGrid(controller),
),
);
},
); );
} }
@ -87,68 +97,40 @@ class _ResultsViewState extends State<ResultsView> {
} }
} }
Widget _buildLocateGrid(XmlSearchController controller, BuildContext context) { Widget _buildResultGrid(XmlSearchController controller) {
return PlutoGrid( return PlutoGrid(
configuration: const PlutoGridConfiguration( key: ValueKey(controller.results.hashCode),
style: PlutoGridStyleConfig( configuration: MyPlutoGridConfiguration(),
rowHeight: 32,
columnHeight: 32,
iconColor: Colors.transparent,
gridBorderRadius: BorderRadius.zero,
columnTextStyle: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
columnSize: PlutoGridColumnSizeConfig(
autoSizeMode: PlutoAutoSizeMode.scale,
resizeMode: PlutoResizeMode.pushAndPull,
),
),
columns: _buildColumns(), columns: _buildColumns(),
mode: PlutoGridMode.normal, mode: PlutoGridMode.normal,
rows: _buildRows(controller), rows: controller.results.isEmpty ? [] : _buildRows(controller),
onLoaded: (PlutoGridOnLoadedEvent event) { noRowsWidget: const Center(child: Text('没有搜索结果')),
stateManager = event.stateManager;
stateManager.setSelectingMode(PlutoGridSelectingMode.row);
//
stateManager.addListener(() {
if (stateManager.hasFocus) {
final checkedRows = stateManager.checkedRows;
for (var row in checkedRows) {
final result = row.cells['action']?.value as SearchResult?;
if (result != null) {
result.isSelected = true;
}
}
// onLoaded: (PlutoGridOnLoadedEvent event) {
for (var row in stateManager.refRows.where((r) => !checkedRows.contains(r))) { stateManager ??= event.stateManager;
final result = row.cells['action']?.value as SearchResult?; },
if (result != null) {
result.isSelected = false;
}
}
controller.notifyListeners(); onRowChecked: (PlutoGridOnRowCheckedEvent e) {
} if (e.isAll) {
}); controller.toggleSelectAll(e.isChecked!);
} else {
final result = e.row?.cells['action']!.value as SearchResult;
result.isSelected = e.isChecked!;
}
}, },
noRowsWidget: const Center(child: Text('没有搜索结果')),
); );
} }
List<PlutoRow> _buildRows(XmlSearchController controller) { List<PlutoRow> _buildRows(XmlSearchController controller) {
return controller.results.map((result) { return controller.results.asMap().entries.map((entry) {
final index = entry.key;
final result = entry.value;
return PlutoRow( return PlutoRow(
checked: result.isSelected, // key: ValueKey('${result.hashCode}_$index'), // key确保唯一性
cells: { cells: {
'rowNum': PlutoCell(value: result.rowNum), 'rowNum': PlutoCell(value: index + 1),
'content': PlutoCell(value: result.attributeValue), 'content': PlutoCell(value: result.attributeValue),
'index': PlutoCell(value: result.index ?? 0), 'index': PlutoCell(value: result.index),
'action': PlutoCell(value: result), 'action': PlutoCell(value: result),
}, },
); );
@ -157,7 +139,7 @@ class _ResultsViewState extends State<ResultsView> {
List<PlutoColumn> _buildColumns() { List<PlutoColumn> _buildColumns() {
return [ return [
MyPlutoColumn(title: '序号', field: 'rowNum', width: 90, checkable: true), MyPlutoColumn(title: '#', field: 'rowNum', width: 60, checkable: true),
MyPlutoColumn( MyPlutoColumn(
title: '搜索内容', title: '搜索内容',
field: 'content', field: 'content',
@ -190,69 +172,47 @@ class _ResultsViewState extends State<ResultsView> {
PlutoRow row, PlutoRow row,
SearchResult result, SearchResult result,
) async { ) async {
bool confirmed = final confirmed =
await showDialog<bool>( await showDialog<bool>(
context: context, context: context,
builder: (context) { builder:
return Shortcuts( (context) => AlertDialog(
shortcuts: const {SingleActivator(LogicalKeyboardKey.enter): _ConfirmAction()}, title: const Text('确认删除'),
child: Actions( content: Text('确定要删除 ${result.attributeValue} 吗?'),
actions: { actions: [
_ConfirmAction: CallbackAction<_ConfirmAction>( TextButton(
onInvoke: (_) { onPressed: () => Navigator.pop(context, false),
Navigator.pop(context, true); child: const Text('取消'),
return null;
},
), ),
}, TextButton(
child: Focus( onPressed: () => Navigator.pop(context, true),
autofocus: true, child: const Text('删除'),
child: AlertDialog(
title: const Text('确认删除'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('将所选xml节点删除吗?'),
const SizedBox(height: 8),
Text(
'${result.attributeValue}[${result.index}]',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确认'),
),
],
), ),
), ],
), ),
);
},
) ?? ) ??
false; false;
if (confirmed && context.mounted) { if (confirmed && context.mounted) {
try { try {
final controller = context.read<XmlSearchController>(); final controller = context.read<XmlSearchController>();
// 1.
controller.removeResult(result); controller.removeResult(result);
stateManager.removeRows([row]);
Logger().info('已删除节点文件: ${result.attributeValue}[${result.index}]'); Logger().info('已删除节点: ${result.attributeValue}');
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('已删除: ${result.attributeValue}')));
} catch (e) { } catch (e) {
Logger().error('删除失败: ${e.toString()}'); Logger().error('删除失败: $e');
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('删除失败: ${e.toString()}')));
}
} }
} }
} }
} }
class _ConfirmAction extends Intent {
const _ConfirmAction();
}

25
win_text_editor/lib/shared/components/my_pluto_configuration.dart

@ -0,0 +1,25 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:pluto_grid/pluto_grid.dart';
class MyPlutoGridConfiguration extends PlutoGridConfiguration {
MyPlutoGridConfiguration()
: super(
style: const PlutoGridStyleConfig(
rowHeight: 32,
columnHeight: 32,
iconColor: Colors.transparent,
gridBorderRadius: BorderRadius.zero,
columnTextStyle: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
columnSize: const PlutoGridColumnSizeConfig(
autoSizeMode: PlutoAutoSizeMode.scale,
resizeMode: PlutoResizeMode.pushAndPull,
),
);
}
Loading…
Cancel
Save