From 4ffb5f39f958f2a7ed37b942638dabd517f33d08 Mon Sep 17 00:00:00 2001 From: hejl Date: Tue, 20 May 2025 18:19:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E8=A7=A3=E6=9E=90OK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/template_grid_view.dart | 252 +++++++++++++----- 1 file changed, 185 insertions(+), 67 deletions(-) diff --git a/win_text_editor/lib/modules/template_parser/widgets/template_grid_view.dart b/win_text_editor/lib/modules/template_parser/widgets/template_grid_view.dart index c0685d0..bc11746 100644 --- a/win_text_editor/lib/modules/template_parser/widgets/template_grid_view.dart +++ b/win_text_editor/lib/modules/template_parser/widgets/template_grid_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_datagrid/datagrid.dart'; +import 'package:win_text_editor/framework/controllers/logger.dart'; import 'package:win_text_editor/modules/template_parser/controllers/template_parser_controller.dart'; import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; import 'package:file_picker/file_picker.dart'; @@ -18,9 +19,9 @@ class TemplateGridView extends StatelessWidget { return GestureDetector( onSecondaryTapDown: (details) { _showContextMenu(context, details.globalPosition, controller); - }, - child: _buildGridView(controller.templateItems, controller), - ); + }, + child: _buildGridView(controller), + ); }, ); } @@ -30,11 +31,9 @@ class TemplateGridView extends StatelessWidget { Offset position, TemplateParserController controller, ) async { - // 获取渲染对象以正确定位菜单 final renderBox = context.findRenderObject() as RenderBox; final localPosition = renderBox.globalToLocal(position); - // 显示菜单并等待选择结果 final result = await showMenu( context: context, position: RelativeRect.fromLTRB( @@ -46,7 +45,6 @@ class TemplateGridView extends StatelessWidget { items: [const PopupMenuItem(value: 'export', child: Text('导出(csv)'))], ); - // 处理菜单选择结果 if (result == 'export' && context.mounted) { try { await _exportToCsv(controller); @@ -56,29 +54,30 @@ class TemplateGridView extends StatelessWidget { context, ).showSnackBar(SnackBar(content: Text('导出失败: ${e.toString()}'))); } - } + } } } Future _exportToCsv(TemplateParserController controller) async { - String csvData = '序号\t'; final selectedNodes = controller.getSelectedNodes(); - for (var node in selectedNodes) { - csvData += '${node.name}\t'; - } + if (selectedNodes.isEmpty) return; + + // 构建表头 + String csvData = '序号\t'; + csvData += selectedNodes.map((node) => node.name).join('\t'); csvData += '\n'; - final filteredItems = - controller.selectedNode != null - ? controller.templateItems.where((item) => item.matchesPath(controller.selectedNode!.path)).toList() - : controller.templateItems; + // 获取所有行数据 + final rows = _getGroupedData(controller); - for (var item in filteredItems) { - csvData += '${filteredItems.indexOf(item) + 1}\t'; - for (var node in selectedNodes) { - // 这里需要根据实际情况填充列内容 - csvData += '\t'; - } + // 填充数据 + for (var i = 0; i < rows.length; i++) { + csvData += '${i + 1}\t'; + csvData += selectedNodes + .map((node) { + return rows[i][node.path] ?? ''; + }) + .join('\t'); csvData += '\n'; } @@ -90,50 +89,49 @@ class TemplateGridView extends StatelessWidget { ); if (filePath != null) { - final file = File(filePath); - await file.writeAsString(csvData); + await File(filePath).writeAsString(csvData); } } - Widget _buildGridView(List items, TemplateParserController controller) { - final filteredItems = - controller.selectedNode != null - ? items.where((item) => item.matchesPath(controller.selectedNode!.path)).toList() - : items; - + Widget _buildGridView(TemplateParserController controller) { final selectedNodes = controller.getSelectedNodes(); - final dataSource = _TemplateItemDataSource( - items: filteredItems, - selectedNodes: selectedNodes, - ); + if (selectedNodes.isEmpty) { + return const Center(child: Text('请在左侧树中选择要显示的节点(勾选复选框)')); + } - List columns = [ - GridColumn( - columnName: 'index', - width: 60, - label: Container( - padding: const EdgeInsets.all(8.0), - color: Colors.grey[200], - alignment: Alignment.center, - child: const Text('序号'), - ), - ), - ]; + // 获取所有需要显示的数据项 + final allItems = controller.templateItems; + + // 构建数据行 - 每个父节点实例为一行 + final rows = _buildDataRows(selectedNodes, allItems); + + final dataSource = _TemplateItemDataSource(rows: rows, selectedNodes: selectedNodes); - for (var node in selectedNodes) { - columns.add( - GridColumn( - columnName: node.id.toString(), + // 构建列 + final columns = [ + GridColumn( + columnName: 'index', + width: 60, + label: Container( + padding: const EdgeInsets.all(8.0), + color: Colors.grey[200], + alignment: Alignment.center, + child: const Text('序号'), + ), + ), + ...selectedNodes.map((node) { + return GridColumn( + columnName: node.path, label: Container( padding: const EdgeInsets.all(8.0), alignment: Alignment.center, color: Colors.grey[200], - child: Text(node.name), + child: Text(node.isAttribute ? node.name.substring(1) : node.name), ), - ), - ); - } + ); + }).toList(), + ]; return SfDataGrid( source: dataSource, @@ -143,28 +141,148 @@ class TemplateGridView extends StatelessWidget { columnWidthMode: ColumnWidthMode.fill, ); } + + List> _buildDataRows( + List selectedNodes, + List allItems, + ) { + // 1. 为每个选中的节点路径收集所有匹配项,并记录它们的原始顺序 + final nodeValueGroups = >>{}; + + for (final node in selectedNodes) { + // 获取该节点路径对应的所有值,并保留它们的原始索引 + final valuesWithIndex = + allItems + .asMap() + .entries + .where((entry) => entry.value.xPath == node.path) + .map((entry) => MapEntry(entry.key, entry.value.value)) + .toList(); + + nodeValueGroups[node.path] = valuesWithIndex; + } + + // 2. 确定最大行数(以最多数据的列为准) + final maxRows = nodeValueGroups.values.fold( + 0, + (max, group) => group.length > max ? group.length : max, + ); + + // 3. 构建结果行列表 + final rows = >[]; + + for (var rowIndex = 0; rowIndex < maxRows; rowIndex++) { + final row = {'_index': rowIndex + 1}; + + // 为每个选中的节点添加当前行的值 + for (final node in selectedNodes) { + final values = nodeValueGroups[node.path]!; + row[node.path] = rowIndex < values.length ? values[rowIndex].value : ''; + } + + rows.add(row); + } + + return rows; + } + + List _getInstancesForParent(String parentPath, List items) { + // 获取该父路径下的所有唯一实例路径 + return items + .where((item) => item.xPath.startsWith(parentPath)) + .map((item) { + // 对于重复节点,提取实例标识部分 + if (parentPath.contains('[')) { + return item.xPath.substring(0, item.xPath.indexOf('/', parentPath.length)); + } + return parentPath; + }) + .toSet() + .toList(); + } + + // 辅助方法:根据父路径分组数据 + List> _getGroupedData(TemplateParserController controller) { + final selectedNodes = controller.getSelectedNodes(); + if (selectedNodes.isEmpty) return []; + + // 获取所有相关数据项 + final allItems = + controller.templateItems.where((item) { + return selectedNodes.any((node) => item.xPath == node.path); + }).toList(); + + // 按父路径分组 + final parentPaths = + selectedNodes.map((node) { + return node.path.substring(0, node.path.lastIndexOf('/')); + }).toSet(); + + final groupedData = >[]; + + for (final parentPath in parentPaths) { + // 获取该父路径下的所有项 + final items = + allItems.where((item) { + return item.xPath.startsWith(parentPath) || + item.xPath == parentPath || + (item.xPath.contains('@') && + item.xPath.substring(0, item.xPath.lastIndexOf('@')) == parentPath); + }).toList(); + + // 按实例分组(对于重复节点) + final instanceGroups = >{}; + + for (final item in items) { + // 提取实例标识(如对于重复节点) + final instanceId = _getInstanceId(item.xPath, parentPath); + + if (!instanceGroups.containsKey(instanceId)) { + instanceGroups[instanceId] = {'_parent': parentPath}; + } + + instanceGroups[instanceId]![item.xPath] = item.value; + } + + groupedData.addAll(instanceGroups.values); + } + + return groupedData; + } + + String _getInstanceId(String fullPath, String parentPath) { + // 简单实现:使用父路径后的部分作为实例ID + return fullPath.substring(parentPath.length); + } } class _TemplateItemDataSource extends DataGridSource { - final List items; + final List> _rows; final List selectedNodes; - _TemplateItemDataSource({required this.items, required this.selectedNodes}); + _TemplateItemDataSource({required List> rows, required this.selectedNodes}) + : _rows = rows; @override - List get rows => - items.map((item) { - List cells = [ - DataGridCell(columnName: 'index', value: items.indexOf(item) + 1), - ]; - - for (var node in selectedNodes) { - // 这里需要根据实际情况填充列内容 - cells.add(DataGridCell(columnName: node.id.toString(), value: '')); - } + List get rows { + // print("[DEBUG] 原始可加载记录数:${_rows.length}"); + return _rows.asMap().entries.map((entry) { + final index = entry.key; + final rowData = entry.value; - return DataGridRow(cells: cells); - }).toList(); + return DataGridRow( + cells: [ + DataGridCell(columnName: 'index', value: index + 1), + ...selectedNodes.map((node) { + return DataGridCell( + columnName: node.path, + value: rowData[node.path]?.toString() ?? '', + ); + }).toList(), + ], + ); + }).toList(); + } @override DataGridRowAdapter? buildRow(DataGridRow row) {