Browse Source

表格优化完成

master
hejl 1 month ago
parent
commit
8841bf294a
  1. 10
      win_text_editor/lib/modules/content_search/models/count_result.dart
  2. 6
      win_text_editor/lib/modules/content_search/models/search_result.dart
  3. 131
      win_text_editor/lib/modules/content_search/widgets/results_view.dart
  4. 50
      win_text_editor/lib/modules/template_parser/widgets/grid_view.dart
  5. 4
      win_text_editor/lib/modules/uft_component/controllers/component_source.dart
  6. 79
      win_text_editor/lib/shared/base/my_sf_data_grid.dart
  7. 37
      win_text_editor/lib/shared/base/my_sf_data_source.dart
  8. 2
      win_text_editor/lib/shared/uft_std_fields/field_data_source.dart
  9. 3
      win_text_editor/lib/shared/uft_std_fields/fields_data_grid.dart

10
win_text_editor/lib/modules/content_search/models/count_result.dart

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
import 'package:win_text_editor/shared/base/selectable_item.dart';
class CountResult implements SelectableItem {
final String keyword;
final int matchCount;
@override
bool isSelected;
CountResult({required this.keyword, required this.matchCount, this.isSelected = false});
}

6
win_text_editor/lib/modules/content_search/models/search_result.dart

@ -1,11 +1,14 @@ @@ -1,11 +1,14 @@
import 'package:win_text_editor/modules/content_search/models/match_result.dart';
import 'package:win_text_editor/shared/base/selectable_item.dart';
class SearchResult {
class SearchResult implements SelectableItem {
final String filePath;
final int lineNumber;
final String lineContent;
final List<MatchResult> matches;
final String queryTerm; //
@override
bool isSelected;
SearchResult({
required this.filePath,
@ -13,5 +16,6 @@ class SearchResult { @@ -13,5 +16,6 @@ class SearchResult {
required this.lineContent,
required this.matches,
required this.queryTerm,
this.isSelected = false,
});
}

131
win_text_editor/lib/modules/content_search/widgets/results_view.dart

@ -6,10 +6,13 @@ import 'package:path/path.dart' as path; @@ -6,10 +6,13 @@ import 'package:path/path.dart' as path;
import 'package:win_text_editor/framework/controllers/logger.dart';
import 'package:win_text_editor/modules/content_search/controllers/content_search_controller.dart';
import 'package:file_picker/file_picker.dart';
import 'package:win_text_editor/modules/content_search/models/count_result.dart';
import 'dart:io';
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/shared/base/my_sf_data_grid.dart';
import 'package:win_text_editor/shared/base/my_sf_data_source.dart';
import 'package:win_text_editor/shared/components/my_grid_column.dart';
// import 'package:recycle_bin/recycle_bin.dart';
@ -29,88 +32,15 @@ class ResultsView extends StatelessWidget { @@ -29,88 +32,15 @@ class ResultsView extends StatelessWidget {
}
return Card(
child: GestureDetector(
onSecondaryTapDown: (details) {
_showContextMenu(context, details.globalPosition, controller);
},
child: _buildResultsGrid(controller, context),
),
child:
controller.searchMode == SearchMode.locate
? _buildLocateGrid(controller, context)
: _buildCountGrid(controller, context),
);
}
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 = '文件\t行号\t内容\n';
for (var result in controller.results) {
csvData +=
'${path.basename(result.filePath)}\t${result.lineNumber}\t${result.lineContent}\n';
}
} else {
csvData = '关键词\t匹配数量\n';
for (var result in controller.results) {
csvData += '${result.filePath}\t${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, BuildContext context) {
return controller.searchMode == SearchMode.locate
? _buildLocateGrid(controller, context)
: _buildCountGrid(controller);
}
Widget _buildLocateGrid(ContentSearchController controller, BuildContext context) {
return SfDataGrid(
rowHeight: 32,
headerRowHeight: 32,
return MySfDataGrid<SearchResult>(
source: LocateDataSource(controller, context),
columns: [
ShortGridColumn(columnName: 'index', label: '序号'),
@ -118,45 +48,29 @@ class ResultsView extends StatelessWidget { @@ -118,45 +48,29 @@ class ResultsView extends StatelessWidget {
MyGridColumn(columnName: 'content', label: '内容'),
ShortGridColumn(columnName: 'action', label: '操作', width: 90),
],
selectionMode: SelectionMode.multiple,
navigationMode: GridNavigationMode.cell,
gridLinesVisibility: GridLinesVisibility.both,
headerGridLinesVisibility: GridLinesVisibility.both,
allowSorting: false,
allowFiltering: false,
columnWidthMode: ColumnWidthMode.fill,
isScrollbarAlwaysShown: true,
allowColumnsResizing: true, //
columnResizeMode: ColumnResizeMode.onResizeEnd,
selectable: false,
);
}
Widget _buildCountGrid(ContentSearchController controller) {
return SfDataGrid(
rowHeight: 32,
headerRowHeight: 32,
source: CountDataSource(controller),
Widget _buildCountGrid(ContentSearchController controller, BuildContext context) {
return MySfDataGrid<CountResult>(
source: CountDataSource(controller, context),
columns: [
ShortGridColumn(columnName: 'index', label: '序号'),
MyGridColumn(columnName: 'keyword', label: '关键词', minimumWidth: 300),
MyGridColumn(columnName: 'count', label: '匹配数量'),
MyGridColumn(columnName: 'matchCount', label: '匹配数量'),
],
selectionMode: SelectionMode.multiple,
navigationMode: GridNavigationMode.cell,
allowColumnsResizing: true,
gridLinesVisibility: GridLinesVisibility.both,
headerGridLinesVisibility: GridLinesVisibility.both,
columnWidthMode: ColumnWidthMode.fill,
isScrollbarAlwaysShown: true,
selectable: false,
);
}
}
class LocateDataSource extends DataGridSource {
class LocateDataSource extends MySfDataSource<SearchResult> {
final ContentSearchController controller;
final BuildContext context;
LocateDataSource(this.controller, this.context);
LocateDataSource(this.controller, this.context)
: super(controller.results, onSelectionChanged: (index, isSelected) {});
@override
List<DataGridRow> get rows =>
@ -182,7 +96,7 @@ class LocateDataSource extends DataGridSource { @@ -182,7 +96,7 @@ class LocateDataSource extends DataGridSource {
}).toList();
@override
DataGridRowAdapter? buildRow(DataGridRow row) {
DataGridRowAdapter buildRow(DataGridRow row) {
final cells = row.getCells();
final result = cells[2].value as SearchResult;
@ -357,11 +271,13 @@ class LocateDataSource extends DataGridSource { @@ -357,11 +271,13 @@ class LocateDataSource extends DataGridSource {
}
}
class CountDataSource extends DataGridSource {
class CountDataSource extends MySfDataSource<CountResult> {
final ContentSearchController controller;
final BuildContext context;
late final List<MapEntry<String, int>> _counts;
CountDataSource(this.controller) {
CountDataSource(this.controller, this.context)
: super([], onSelectionChanged: (index, isSelected) {}) {
final counts = <String, int>{};
for (var result in controller.results) {
counts[result.filePath] = (counts[result.filePath] ?? 0) + result.lineNumber;
@ -383,7 +299,7 @@ class CountDataSource extends DataGridSource { @@ -383,7 +299,7 @@ class CountDataSource extends DataGridSource {
}).toList();
@override
DataGridRowAdapter? buildRow(DataGridRow row) {
DataGridRowAdapter buildRow(DataGridRow row) {
return DataGridRowAdapter(
cells:
row.getCells().map((cell) {
@ -397,7 +313,6 @@ class CountDataSource extends DataGridSource { @@ -397,7 +313,6 @@ class CountDataSource extends DataGridSource {
}
}
// Action标识类
class _ConfirmAction extends Intent {
const _ConfirmAction();
}

50
win_text_editor/lib/modules/template_parser/widgets/grid_view.dart

@ -214,54 +214,4 @@ class _TemplateItemDataSource extends DataGridSource { @@ -214,54 +214,4 @@ class _TemplateItemDataSource extends DataGridSource {
}).toList(),
);
}
// 1:
@override
Future<void> onCellSubmit(
DataGridRow dataGridRow,
RowColumnIndex rowColumnIndex,
GridColumn column,
) async {
final dynamic newValue = dataGridRow.getCells()[rowColumnIndex.columnIndex].value;
final int dataRowIndex = _rows.indexWhere(
(row) => row['_index'] == dataGridRow.getCells()[0].value,
);
if (dataRowIndex >= 0) {
final node = selectedNodes[rowColumnIndex.columnIndex - 1]; //
_rows[dataRowIndex][node.path] = newValue;
if (onCellValueChanged != null) {
onCellValueChanged!(node, column.columnName, newValue);
}
notifyListeners();
}
}
// 2:
@override
Widget? buildEditWidget(
DataGridRow dataGridRow,
RowColumnIndex rowColumnIndex,
GridColumn column,
CellSubmit submitCell,
) {
//
if (column.columnName == 'index') return null;
final String value = dataGridRow.getCells()[rowColumnIndex.columnIndex].value.toString();
return Container(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
controller: TextEditingController(text: value),
onSubmitted: (String newValue) {
// submitCell调用方式
submitCell();
},
),
);
}
}

4
win_text_editor/lib/modules/uft_component/controllers/component_source.dart

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/modules/uft_component/models/uft_component.dart';
import 'package:win_text_editor/shared/base/selectable_data_source.dart';
import 'package:win_text_editor/shared/base/my_sf_data_source.dart';
class ComponentSource extends SelectableDataSource<UftComponent> {
class ComponentSource extends MySfDataSource<UftComponent> {
ComponentSource(
List<UftComponent> uftComponents, {
required Null Function(dynamic index, dynamic isSelected) onSelectionChanged,

79
win_text_editor/lib/shared/base/my_sf_data_grid.dart

@ -2,18 +2,23 @@ import 'package:flutter/material.dart'; @@ -2,18 +2,23 @@ import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/base/selectable_data_source.dart';
import 'package:win_text_editor/shared/base/selectable_item.dart';
import 'package:win_text_editor/shared/components/my_sf_data_source.dart';
import 'package:win_text_editor/shared/base/my_sf_data_source.dart';
// ignore: must_be_immutable
class MySfDataGrid<T extends SelectableItem> extends StatelessWidget {
final MySfDataSource<T> dataSource;
final MySfDataSource<T> source;
final Function(int index, bool isSelected)? onSelectionChanged;
final List<GridColumn> columns;
final DataGridController? controller;
bool selectable = false;
const MySfDataGrid({
MySfDataGrid({
super.key,
required this.dataSource,
required this.source,
required this.columns,
this.onSelectionChanged,
this.controller,
this.selectable = false,
});
Widget _buildCheckboxHeader(BuildContext context, SelectableDataSource<T> dataSource) {
@ -52,32 +57,30 @@ class MySfDataGrid<T extends SelectableItem> extends StatelessWidget { @@ -52,32 +57,30 @@ class MySfDataGrid<T extends SelectableItem> extends StatelessWidget {
child: SfDataGrid(
rowHeight: 32,
headerRowHeight: 32,
source: dataSource,
source: source,
gridLinesVisibility: GridLinesVisibility.both,
headerGridLinesVisibility: GridLinesVisibility.both,
columnWidthMode: ColumnWidthMode.fitByCellValue,
selectionMode: SelectionMode.none,
allowEditing: true,
controller: controller,
columns: [
GridColumn(
columnName: 'select',
label: ValueListenableBuilder<bool>(
valueListenable: dataSource.selectionNotifier,
builder: (context, _, __) => _buildCheckboxHeader(context, dataSource),
if (selectable)
GridColumn(
columnName: 'select',
label: ValueListenableBuilder<bool>(
valueListenable: source.selectionNotifier,
builder: (context, _, __) => _buildCheckboxHeader(context, source),
),
width: 60,
),
width: 60,
),
...columns,
],
onCellTap: (details) {
if (details.column.columnName == 'select') {
final rowIndex = details.rowColumnIndex.rowIndex - 1;
if (rowIndex >= 0 && rowIndex < dataSource.items.length) {
dataSource.toggleRowSelection(
rowIndex,
!dataSource.items[rowIndex].isSelected,
);
onSelectionChanged?.call(rowIndex, dataSource.items[rowIndex].isSelected);
if (rowIndex >= 0 && rowIndex < source.items.length) {
source.toggleRowSelection(rowIndex, !source.items[rowIndex].isSelected);
onSelectionChanged?.call(rowIndex, source.items[rowIndex].isSelected);
}
}
},
@ -98,33 +101,43 @@ class MySfDataGrid<T extends SelectableItem> extends StatelessWidget { @@ -98,33 +101,43 @@ class MySfDataGrid<T extends SelectableItem> extends StatelessWidget {
position.dy + renderBox.size.height - position.dy,
),
items: [
const PopupMenuItem<String>(value: 'export', child: Text('导出表格(csv)')),
if (!isHeader)
PopupMenuItem(value: 'copyCell', child: Text('复制单元格 ($columnName)')),
PopupMenuItem<String>(
value: 'copyCell',
child: Text('复制单元格 ($columnName)'),
),
if (!isHeader)
const PopupMenuItem(value: 'copyRow', child: Text('复制当前行')),
PopupMenuItem(value: 'copyColumn', child: Text('复制整列 ($columnName)')),
const PopupMenuItem<String>(value: 'copyRow', child: Text('复制当前行')),
PopupMenuItem<String>(
value: 'copyColumn',
child: Text('复制整列 ($columnName)'),
),
],
).then((value) async {
if (value != null) {
switch (value) {
case 'copyCell':
final row = dataSource.effectiveRows[rowIndex];
await dataSource.copyCellValue(row, columnName);
final row = source.effectiveRows[rowIndex];
await source.copyCellValue(row, columnName);
break;
case 'copyRow':
final row = dataSource.effectiveRows[rowIndex];
await dataSource.copyRowValues(row);
final row = source.effectiveRows[rowIndex];
await source.copyRowValues(row);
break;
case 'copyColumn':
await dataSource.copyColumnValues(
dataSource.effectiveRows,
columnName,
);
await source.copyColumnValues(source.effectiveRows, columnName);
break;
case 'export':
await source.exportToCsv(source.effectiveRows);
break;
}
if (value != 'export') {
ScaffoldMessenger.of(
// ignore: use_build_context_synchronously
context,
).showSnackBar(const SnackBar(content: Text('已复制到剪贴板')));
}
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('已复制到剪贴板')));
}
});
},

37
win_text_editor/lib/shared/components/my_sf_data_source.dart → win_text_editor/lib/shared/base/my_sf_data_source.dart

@ -1,3 +1,6 @@ @@ -1,3 +1,6 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/services.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/base/selectable_data_source.dart';
@ -40,4 +43,38 @@ abstract class MySfDataSource<T extends SelectableItem> extends SelectableDataSo @@ -40,4 +43,38 @@ abstract class MySfDataSource<T extends SelectableItem> extends SelectableDataSo
.join('\n');
await Clipboard.setData(ClipboardData(text: values));
}
Future<void> exportToCsv(List<DataGridRow> rows) async {
if (rows.length <= 1) return;
//
String csvData = "";
for (int i = 1; i < rows[0].getCells().length; i++) {
csvData += rows[0].getCells()[i].columnName.toString();
if (i < rows[0].getCells().length - 1) csvData += '\t';
}
csvData += '\n';
//
for (final row in rows) {
// 10
for (int i = 1; i < row.getCells().length; i++) {
csvData += row.getCells()[i].value.toString();
if (i < row.getCells().length - 1) csvData += '\t';
}
csvData += '\n';
}
//
final filePath = await FilePicker.platform.saveFile(
dialogTitle: '保存导出结果',
fileName: 'template_results.csv',
type: FileType.custom,
allowedExtensions: ['csv'],
);
if (filePath != null) {
await File(filePath).writeAsString(csvData);
}
}
}

2
win_text_editor/lib/shared/uft_std_fields/field_data_source.dart

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/components/my_sf_data_source.dart';
import 'package:win_text_editor/shared/base/my_sf_data_source.dart';
import 'package:win_text_editor/shared/models/std_filed.dart';
class FieldsDataSource extends MySfDataSource<Field> {

3
win_text_editor/lib/shared/uft_std_fields/fields_data_grid.dart

@ -13,8 +13,9 @@ class FieldsDataGrid extends StatelessWidget { @@ -13,8 +13,9 @@ class FieldsDataGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MySfDataGrid<Field>(
dataSource: fieldsSource,
source: fieldsSource,
onSelectionChanged: onSelectionChanged,
selectable: true,
columns: [
MyGridColumn(columnName: 'id', label: '序号', minimumWidth: 80),
MyGridColumn(columnName: 'name', label: '名称', minimumWidth: 120),

Loading…
Cancel
Save