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 new file mode 100644 index 0000000..e5d902c --- /dev/null +++ b/win_text_editor/lib/modules/template_parser/widgets/template_grid_view.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:syncfusion_flutter_datagrid/datagrid.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'; + +class TemplateGridView extends StatelessWidget { + final TemplateParserController controller; + + const TemplateGridView({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, controller, _) { + return _buildGridView(controller.templateItems, controller); + }, + ); + } + + Widget _buildGridView(List items, TemplateParserController controller) { + final filteredItems = + controller.selectedNode != null + ? items.where((item) => item.matchesPath(controller.selectedNode!.path)).toList() + : items; + + final dataSource = _TemplateItemDataSource( + items: filteredItems, + selectedNode: controller.selectedNode, + ); + + return SfDataGrid( + source: dataSource, + columns: [ + GridColumn( + columnName: 'index', + width: 60, + label: Container( + padding: const EdgeInsets.all(8.0), + color: Colors.grey[200], + alignment: Alignment.center, + child: const Text('序号'), + ), + ), + GridColumn( + columnName: 'content', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + color: Colors.grey[200], + child: const Text('内容'), + ), + ), + ], + gridLinesVisibility: GridLinesVisibility.both, + headerGridLinesVisibility: GridLinesVisibility.both, + columnWidthMode: ColumnWidthMode.fill, + ); + } +} + +class _TemplateItemDataSource extends DataGridSource { + final List items; + final TemplateNode? selectedNode; + + _TemplateItemDataSource({required this.items, required this.selectedNode}); + + @override + List get rows => + items.map((item) { + return DataGridRow( + cells: [ + DataGridCell(columnName: 'index', value: items.indexOf(item) + 1), + DataGridCell(columnName: 'content', value: item.value), + ], + ); + }).toList(); + + @override + DataGridRowAdapter? buildRow(DataGridRow row) { + return DataGridRowAdapter( + cells: + row.getCells().map((dataGridCell) { + return Container( + padding: const EdgeInsets.all(8.0), + alignment: + dataGridCell.columnName == 'index' ? Alignment.center : Alignment.centerLeft, + child: Text(dataGridCell.value.toString()), + ); + }).toList(), + ); + } +} diff --git a/win_text_editor/lib/modules/template_parser/widgets/template_parser_view.dart b/win_text_editor/lib/modules/template_parser/widgets/template_parser_view.dart index a2af407..1ed5d28 100644 --- a/win_text_editor/lib/modules/template_parser/widgets/template_parser_view.dart +++ b/win_text_editor/lib/modules/template_parser/widgets/template_parser_view.dart @@ -1,13 +1,9 @@ -import 'dart:io'; - -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:syncfusion_flutter_datagrid/datagrid.dart'; import 'package:win_text_editor/framework/controllers/tab_items_controller.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:win_text_editor/shared/components/tree_view.dart'; +import 'package:win_text_editor/modules/template_parser/widgets/template_grid_view.dart'; +import 'package:win_text_editor/modules/template_parser/widgets/template_tree_view.dart'; class TemplateParserView extends StatefulWidget { final String tabId; @@ -83,217 +79,13 @@ class _TemplateParserViewState extends State { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.3, - child: Card(child: _buildTreeView(controller.treeNodes)), + child: Card(child: TemplateTreeView(controller: controller)), ), const SizedBox(width: 8), - Expanded( - child: Card( - child: GestureDetector( - onSecondaryTapDown: (details) { - _showContextMenu(context, details.globalPosition); - }, - child: _buildGridView(controller.templateItems), - ), - ), - ), - ], - ); - }, - ); - } - - Future _showContextMenu(BuildContext context, Offset position) async { - // 获取渲染对象以正确定位菜单 - final renderBox = context.findRenderObject() as RenderBox; - final localPosition = renderBox.globalToLocal(position); - - // 显示菜单并等待选择结果 - final result = await showMenu( - 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(value: 'export', child: Text('导出(csv)'))], - ); - - // 处理菜单选择结果 - if (result == 'export' && context.mounted) { - try { - await _exportToCsv(); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('导出失败: ${e.toString()}'))); - } - } - } - } - - Future _exportToCsv() async { - String csvData = '序号\t内容\n'; - final items = _controller.templateItems; - for (var i = 0; i < items.length; i++) { - final item = items[i]; - csvData += '${i + 1}\t${item.value}\n'; - } - - final filePath = await FilePicker.platform.saveFile( - dialogTitle: '保存导出结果', - fileName: 'template_results.csv', - type: FileType.custom, - allowedExtensions: ['csv'], - ); - - if (filePath != null) { - final file = File(filePath); - await file.writeAsString(csvData); - } - } - - Widget _buildTreeView(List nodes) { - if (nodes.isEmpty) { - return const Center(child: Text('No XML data available')); - } - - return Consumer( - builder: (context, controller, _) { - return TreeView( - nodes: _processXmlNodes(nodes), - config: const TreeViewConfig( - showIcons: true, - singleSelect: true, - selectedColor: Colors.lightBlueAccent, - icons: {'element': Icons.label_outline, 'attribute': Icons.code}, - ), - onNodeTap: (node) { - final templateNode = node as TemplateNode; - controller.selectTreeNode(templateNode); - }, - nodeBuilder: (context, node, isSelected, onTap) { - final templateNode = node as TemplateNode; - final isAttribute = node.isAttribute; - // 使用控制器中的 selectedNode 来判断是否选中当前节点 - final isActuallySelected = controller.selectedNode?.id == templateNode.id; - - return Container( - color: - isActuallySelected ? Colors.lightBlueAccent.withOpacity(0.2) : Colors.transparent, - child: Padding( - padding: EdgeInsets.only(left: 12.0 * node.depth), - child: ListTile( - dense: true, - leading: - isAttribute - ? const Icon(Icons.code, size: 16, color: Colors.grey) - : const Icon(Icons.label_outline, size: 18, color: Colors.blue), - title: Text( - isAttribute ? templateNode.name.substring(1) : templateNode.name, - style: TextStyle( - color: isAttribute ? Colors.grey[600] : Colors.black, - fontWeight: isAttribute ? FontWeight.normal : FontWeight.w500, - ), - ), - trailing: - templateNode.isRepeated - ? Text( - "(${templateNode.repreatCount.toString()})", - style: const TextStyle(color: Colors.grey), - ) - : null, - onTap: onTap, - ), - ), - ); - }, - ); - }, - ); - } - - List _processXmlNodes(List nodes) { - return nodes.cast(); - } - - Widget _buildGridView(List items) { - return Consumer( - builder: (context, controller, _) { - // 根据当前选中的节点的path过滤数据 - final filteredItems = - controller.selectedNode != null - ? items.where((item) => item.matchesPath(controller.selectedNode!.path)).toList() - : items; - - // 创建 DataGridSource - final dataSource = _TemplateItemDataSource( - items: filteredItems, - selectedNode: controller.selectedNode, - ); - - return SfDataGrid( - source: dataSource, - columns: [ - GridColumn( - columnName: 'index', - width: 60, - label: Container( - padding: const EdgeInsets.all(8.0), - color: Colors.grey[200], - alignment: Alignment.center, - child: const Text('序号'), - ), - ), - GridColumn( - columnName: 'content', - label: Container( - padding: const EdgeInsets.all(8.0), - alignment: Alignment.center, - color: Colors.grey[200], - child: const Text('内容'), - ), - ), + Expanded(child: Card(child: TemplateGridView(controller: controller))), ], - gridLinesVisibility: GridLinesVisibility.both, - headerGridLinesVisibility: GridLinesVisibility.both, - columnWidthMode: ColumnWidthMode.fill, ); }, ); } } - -class _TemplateItemDataSource extends DataGridSource { - final List items; - final TemplateNode? selectedNode; - - _TemplateItemDataSource({required this.items, required this.selectedNode}); - - @override - List get rows => - items.map((item) { - return DataGridRow( - cells: [ - DataGridCell(columnName: 'index', value: items.indexOf(item) + 1), - DataGridCell(columnName: 'content', value: item.value), - ], - ); - }).toList(); - - @override - DataGridRowAdapter? buildRow(DataGridRow row) { - return DataGridRowAdapter( - cells: - row.getCells().map((dataGridCell) { - return Container( - padding: const EdgeInsets.all(8.0), - alignment: - dataGridCell.columnName == 'index' ? Alignment.center : Alignment.centerLeft, - child: Text(dataGridCell.value.toString()), - ); - }).toList(), - ); - } -} diff --git a/win_text_editor/lib/modules/template_parser/widgets/template_tree_view.dart b/win_text_editor/lib/modules/template_parser/widgets/template_tree_view.dart new file mode 100644 index 0000000..84cd1ce --- /dev/null +++ b/win_text_editor/lib/modules/template_parser/widgets/template_tree_view.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.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:win_text_editor/shared/components/tree_view.dart'; + +class TemplateTreeView extends StatelessWidget { + final TemplateParserController controller; + + const TemplateTreeView({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, controller, _) { + if (controller.treeNodes.isEmpty) { + return const Center(child: Text('No XML data available')); + } + + return TreeView( + nodes: _processXmlNodes(controller.treeNodes), + config: const TreeViewConfig( + showIcons: true, + singleSelect: true, + selectedColor: Colors.lightBlueAccent, + icons: {'element': Icons.label_outline, 'attribute': Icons.code}, + ), + onNodeTap: (node) { + final templateNode = node as TemplateNode; + controller.selectTreeNode(templateNode); + }, + nodeBuilder: (context, node, isSelected, onTap) { + return _buildTreeNode(node, isSelected, onTap, controller); + }, + ); + }, + ); + } + + List _processXmlNodes(List nodes) { + return nodes.cast(); + } + + Widget _buildTreeNode( + TreeNode node, + bool isSelected, + VoidCallback onTap, + TemplateParserController controller, + ) { + final templateNode = node as TemplateNode; + final isAttribute = node.isAttribute; + final isActuallySelected = controller.selectedNode?.id == templateNode.id; + + return Container( + color: isActuallySelected ? Colors.lightBlueAccent.withOpacity(0.2) : Colors.transparent, + child: Padding( + padding: EdgeInsets.only(left: 12.0 * node.depth), + child: ListTile( + dense: true, + leading: + isAttribute + ? const Icon(Icons.code, size: 16, color: Colors.grey) + : const Icon(Icons.label_outline, size: 18, color: Colors.blue), + title: Text( + isAttribute ? templateNode.name.substring(1) : templateNode.name, + style: TextStyle( + color: isAttribute ? Colors.grey[600] : Colors.black, + fontWeight: isAttribute ? FontWeight.normal : FontWeight.w500, + ), + ), + trailing: + templateNode.isRepeated + ? Text( + "(${templateNode.repreatCount.toString()})", + style: const TextStyle(color: Colors.grey), + ) + : null, + onTap: onTap, + ), + ), + ); + } +}