3 changed files with 180 additions and 212 deletions
@ -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<TemplateParserController>( |
||||||
|
builder: (context, controller, _) { |
||||||
|
return _buildGridView(controller.templateItems, controller); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildGridView(List<TemplateItem> 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<TemplateItem> items; |
||||||
|
final TemplateNode? selectedNode; |
||||||
|
|
||||||
|
_TemplateItemDataSource({required this.items, required this.selectedNode}); |
||||||
|
|
||||||
|
@override |
||||||
|
List<DataGridRow> get rows => |
||||||
|
items.map((item) { |
||||||
|
return DataGridRow( |
||||||
|
cells: [ |
||||||
|
DataGridCell<int>(columnName: 'index', value: items.indexOf(item) + 1), |
||||||
|
DataGridCell<String>(columnName: 'content', value: item.value), |
||||||
|
], |
||||||
|
); |
||||||
|
}).toList(); |
||||||
|
|
||||||
|
@override |
||||||
|
DataGridRowAdapter? buildRow(DataGridRow row) { |
||||||
|
return DataGridRowAdapter( |
||||||
|
cells: |
||||||
|
row.getCells().map<Widget>((dataGridCell) { |
||||||
|
return Container( |
||||||
|
padding: const EdgeInsets.all(8.0), |
||||||
|
alignment: |
||||||
|
dataGridCell.columnName == 'index' ? Alignment.center : Alignment.centerLeft, |
||||||
|
child: Text(dataGridCell.value.toString()), |
||||||
|
); |
||||||
|
}).toList(), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -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<TemplateParserController>( |
||||||
|
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<TreeNode> _processXmlNodes(List<TemplateNode> nodes) { |
||||||
|
return nodes.cast<TreeNode>(); |
||||||
|
} |
||||||
|
|
||||||
|
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, |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue