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'; @@ -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/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/services/base_search_service.dart';
import 'package:win_text_editor/shared/utils/file_utils.dart';
typedef ProgressCallback = void Function(double progress);

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

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

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

@ -1,9 +1,8 @@ @@ -1,9 +1,8 @@
// search_result.dart
class SearchResult {
final int rowNum;
final String attributeValue;
int index = 0;
bool isSelected = false; //
bool isSelected;
SearchResult({
required this.rowNum,
@ -11,4 +10,19 @@ class SearchResult { @@ -11,4 +10,19 @@ class SearchResult {
this.index = 0,
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 { @@ -63,8 +63,9 @@ class XmlSearchService {
final document = xml.XmlDocument.parse(content);
String newContent = content;
// 2.
for (SearchResult result in resultList) {
// 2. ,
for (int i = resultList.length - 1; i >= 0; i--) {
SearchResult result = resultList[i];
final nodes =
document.findAllElements(nodeName).where((node) {
final attributeValue = node.getAttribute(attributeName);

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

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:pluto_grid/pluto_grid.dart';
import 'package:path/path.dart' as path;
@ -11,6 +10,7 @@ import 'dart:io'; @@ -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/shared/components/my_pluto_column.dart';
import 'package:win_text_editor/shared/components/my_pluto_configuration.dart';
class ResultsView extends StatefulWidget {
const ResultsView({super.key});
@ -20,19 +20,29 @@ class ResultsView extends StatefulWidget { @@ -20,19 +20,29 @@ class ResultsView extends StatefulWidget {
}
class _ResultsViewState extends State<ResultsView> {
late final PlutoGridStateManager stateManager;
late final XmlSearchController controller;
PlutoGridStateManager? stateManager;
@override
Widget build(BuildContext context) {
final controller = context.watch<XmlSearchController>();
void initState() {
super.initState();
controller = context.read<XmlSearchController>();
}
return Card(
child: GestureDetector(
onSecondaryTapDown: (details) {
_showContextMenu(context, details.globalPosition, controller);
},
child: _buildLocateGrid(controller, context),
),
@override
Widget build(BuildContext context) {
// 使Consumer而不是ValueListenableBuilder
return Consumer<XmlSearchController>(
builder: (context, controller, child) {
return Card(
child: GestureDetector(
onSecondaryTapDown: (details) {
_showContextMenu(context, details.globalPosition, controller);
},
child: _buildResultGrid(controller),
),
);
},
);
}
@ -87,68 +97,40 @@ class _ResultsViewState extends State<ResultsView> { @@ -87,68 +97,40 @@ class _ResultsViewState extends State<ResultsView> {
}
}
Widget _buildLocateGrid(XmlSearchController controller, BuildContext context) {
Widget _buildResultGrid(XmlSearchController controller) {
return PlutoGrid(
configuration: const PlutoGridConfiguration(
style: PlutoGridStyleConfig(
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,
),
),
key: ValueKey(controller.results.hashCode),
configuration: MyPlutoGridConfiguration(),
columns: _buildColumns(),
mode: PlutoGridMode.normal,
rows: _buildRows(controller),
onLoaded: (PlutoGridOnLoadedEvent event) {
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;
}
}
rows: controller.results.isEmpty ? [] : _buildRows(controller),
noRowsWidget: const Center(child: Text('没有搜索结果')),
//
for (var row in stateManager.refRows.where((r) => !checkedRows.contains(r))) {
final result = row.cells['action']?.value as SearchResult?;
if (result != null) {
result.isSelected = false;
}
}
onLoaded: (PlutoGridOnLoadedEvent event) {
stateManager ??= event.stateManager;
},
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) {
return controller.results.map((result) {
return controller.results.asMap().entries.map((entry) {
final index = entry.key;
final result = entry.value;
return PlutoRow(
checked: result.isSelected, //
key: ValueKey('${result.hashCode}_$index'), // key确保唯一性
cells: {
'rowNum': PlutoCell(value: result.rowNum),
'rowNum': PlutoCell(value: index + 1),
'content': PlutoCell(value: result.attributeValue),
'index': PlutoCell(value: result.index ?? 0),
'index': PlutoCell(value: result.index),
'action': PlutoCell(value: result),
},
);
@ -157,7 +139,7 @@ class _ResultsViewState extends State<ResultsView> { @@ -157,7 +139,7 @@ class _ResultsViewState extends State<ResultsView> {
List<PlutoColumn> _buildColumns() {
return [
MyPlutoColumn(title: '序号', field: 'rowNum', width: 90, checkable: true),
MyPlutoColumn(title: '#', field: 'rowNum', width: 60, checkable: true),
MyPlutoColumn(
title: '搜索内容',
field: 'content',
@ -190,69 +172,47 @@ class _ResultsViewState extends State<ResultsView> { @@ -190,69 +172,47 @@ class _ResultsViewState extends State<ResultsView> {
PlutoRow row,
SearchResult result,
) async {
bool confirmed =
final confirmed =
await showDialog<bool>(
context: context,
builder: (context) {
return Shortcuts(
shortcuts: const {SingleActivator(LogicalKeyboardKey.enter): _ConfirmAction()},
child: Actions(
actions: {
_ConfirmAction: CallbackAction<_ConfirmAction>(
onInvoke: (_) {
Navigator.pop(context, true);
return null;
},
builder:
(context) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除 ${result.attributeValue} 吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
},
child: Focus(
autofocus: true,
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('确认'),
),
],
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('删除'),
),
),
],
),
);
},
) ??
false;
if (confirmed && context.mounted) {
try {
final controller = context.read<XmlSearchController>();
// 1.
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) {
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 @@ @@ -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