@ -1,199 +1,177 @@
@@ -1,199 +1,177 @@
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/grid_view_controller.dart ' ;
import ' package:win_text_editor/modules/template_parser/models/template_node.dart ' ;
import ' package:file_picker/file_picker.dart ' ;
import ' dart:io ' ;
import ' package:csv/csv.dart ' ;
class DataGridView extends Stateless Widget {
class DataGridView extends Statefu lWidget {
const DataGridView ( { super . key } ) ;
@ override
State < DataGridView > createState ( ) = > _DataGridViewState ( ) ;
}
class _DataGridViewState extends State < DataGridView > {
final TextEditingController _filePathController = TextEditingController ( ) ;
List < List < dynamic > > _csvData = [ ] ;
String ? _delimiter ;
final ScrollController _horizontalScrollController = ScrollController ( ) ;
@ override
void dispose ( ) {
_filePathController . dispose ( ) ;
_horizontalScrollController . dispose ( ) ;
super . dispose ( ) ;
}
@ override
Widget build ( BuildContext context ) {
return Consumer < GridViewController > (
builder: ( context , controller , _ ) {
return GestureDetector (
onSecondaryTapDown: ( details ) {
_showContextMenu ( context , details . globalPosition , controller ) ;
} ,
child: _buildGridView ( controller ) ,
) ;
} ,
return Column (
children: [
/ / 文 件 选 择 区 域
Padding (
padding: const EdgeInsets . all ( 8.0 ) ,
child: Row (
children: [
Expanded (
child: TextField (
controller: _filePathController ,
decoration: const InputDecoration (
labelText: ' CSV文件路径 ' ,
border: OutlineInputBorder ( ) ,
) ,
readOnly: true ,
) ,
) ,
const SizedBox ( width: 8 ) ,
ElevatedButton ( onPressed: _pickAndLoadCsvFile , child: const Text ( ' 选择文件 ' ) ) ,
] ,
) ,
) ,
/ / 数 据 表 格 区 域
Expanded (
child:
_csvData . isEmpty ? const Center ( child: Text ( ' 请选择CSV文件 ' ) ) : _buildScrollableDataGrid ( ) ,
) ,
] ,
) ;
}
Future < void > _showContextMenu (
BuildContext context ,
Offset position ,
GridViewController 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 ,
Widget _buildScrollableDataGrid ( ) {
return Align (
/ / 添 加 Align 组 件 使 整 个 表 格 左 对 齐
alignment: Alignment . topLeft , / / 设 置 为 左 上 对 齐
child: Scrollbar (
controller: _horizontalScrollController ,
thumbVisibility: true ,
trackVisibility: true ,
child: SingleChildScrollView (
controller: _horizontalScrollController ,
scrollDirection: Axis . horizontal ,
child: SizedBox ( width: _calculateTableWidth ( ) , child: _buildCsvDataGrid ( ) ) ,
) ,
) ,
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 ( GridViewController controller ) async {
final selectedNodes = controller . getSelectedNodes ( ) ;
if ( selectedNodes . isEmpty ) return ;
double _calculateTableWidth ( ) {
if ( _csvData . isEmpty | | _csvData . length < 2 ) return 0 ;
final columnCount = _csvData . first . length ;
return columnCount * 150.0 ; / / 每 列 宽 度 150
}
/ / 直 接 从 数 据 源 获 取 数 据
final dataSource = _TemplateItemDataSource (
rows: _buildDataRows ( selectedNodes , controller . displayedItems ) ,
selectedNodes: selectedNodes ,
) ;
Future < void > _pickAndLoadCsvFile ( ) async {
try {
final result = await FilePicker . platform . pickFiles (
type: FileType . custom ,
allowedExtensions: [ ' csv ' ] ,
) ;
/ / 构 建 表 头
String csvData = ' 序号 \t ' ;
csvData + = selectedNodes
. map ( ( node ) = > node . isAttribute ? node . name . substring ( 1 ) : node . name )
. join ( ' \t ' ) ;
csvData + = ' \n ' ;
/ / 填 充 数 据
for ( final row in dataSource . rows ) {
csvData + = ' ${ row . getCells ( ) [ 0 ] . value } \t ' ; / / 序 号
for ( int i = 1 ; i < row . getCells ( ) . length ; i + + ) {
csvData + = row . getCells ( ) [ i ] . value . toString ( ) ;
if ( i < row . getCells ( ) . length - 1 ) csvData + = ' \t ' ;
if ( result ! = null ) {
final file = File ( result . files . single . path ! ) ;
_filePathController . text = file . path ;
final content = await file . readAsString ( ) ;
/ / 检 测 分 隔 符 ( 第 一 行 是 标 题 行 )
final firstLine = content . split ( ' \n ' ) . first . trim ( ) ;
_delimiter = firstLine . contains ( ' \t ' ) ? ' \t ' : ' , ' ;
/ / 解 析 CSV - 使 用 更 健 壮 的 解 析 方 式
final csvTable = const CsvToListConverter (
shouldParseNumbers: false ,
allowInvalid: false ,
eol: ' \n ' ,
) . convert ( content , fieldDelimiter: _delimiter ) ;
/ / 清 理 数 据
final cleanedData =
csvTable
. where (
( row ) = > row . isNotEmpty & & row . any ( ( cell ) = > cell . toString ( ) . trim ( ) . isNotEmpty ) ,
)
. map ( ( row ) = > row . map ( ( cell ) = > cell . toString ( ) . trim ( ) ) . toList ( ) )
. toList ( ) ;
setState ( ( ) {
_csvData = cleanedData ;
} ) ;
}
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 ) ;
} catch ( e ) {
ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( ' 加载CSV文件失败: $ e ' ) ) ) ;
}
}
Widget _buildGridView ( GridViewController controller ) {
final selectedNodes = controller . getSelectedNodes ( ) ;
if ( selectedNodes . isEmpty ) {
return const Center ( child: Text ( ' 请在左侧树中选择要显示的节点(勾选复选框) ' ) ) ;
Widget _buildCsvDataGrid ( ) {
if ( _csvData . isEmpty | | _csvData . length < 2 ) {
return const Center ( child: Text ( ' 没有有效数据或数据格式不正确 ' ) ) ;
}
/ / 获 取 所 有 需 要 显 示 的 数 据 项
final allItems = controller . displayedItems ;
final headers = _csvData . first ;
final dataRows = _csvData . sublist ( 1 ) ;
/ / 构 建 数 据 行 - 每 个 父 节 点 实 例 为 一 行
final rows = _buildDataRows ( selectedNodes , allItems ) ;
final dataSource = _TemplateItemDataSource ( rows: rows , selectedNodes: selectedNodes ) ;
final columns =
headers . map < GridColumn > ( ( header ) {
return GridColumn (
columnName: header . toString ( ) ,
width: 150 , / / 固 定 列 宽
label: Container (
padding: const EdgeInsets . all ( 8.0 ) ,
color: Colors . grey [ 200 ] ,
alignment: Alignment . center ,
child: Text ( header . toString ( ) , overflow: TextOverflow . ellipsis , maxLines: 1 ) ,
) ,
) ;
} ) . toList ( ) ;
/ / 构 建 列
final columns = < GridColumn > [
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 . isAttribute ? node . name . substring ( 1 ) : node . name ) ,
) ,
) ;
} ) . toList ( ) ,
] ;
final dataSource = _CsvDataSource ( headers: headers , rows: dataRows ) ;
return SfDataGrid (
source : dataSource ,
columns: columns ,
gridLinesVisibility: GridLinesVisibility . both ,
headerGridLinesVisibility: GridLinesVisibility . both ,
columnWidthMode: ColumnWidthMode . fill ,
columnWidthMode: ColumnWidthMode . fitByCellValue , / / 使 用 固 定 列 宽 模 式
) ;
}
List < Map < String , dynamic > > _buildDataRows (
List < TemplateNode > selectedNodes ,
List < TemplateItem > allItems ,
) {
final instanceMap = < String , Map < String , dynamic > > { } ;
/ / 1. 先 按 实 例 分 组
for ( final item in allItems ) {
/ / 2. 只 填 充 选 中 的 列
if ( selectedNodes . any ( ( n ) = > n . path = = item . xPath ) ) {
final instanceId = item . rowId ; / / 或 使 用 其 他 分 组 逻 辑
instanceMap . putIfAbsent ( instanceId , ( ) = > { ' _index ' : instanceMap . length + 1 } ) ;
instanceMap [ instanceId ] ! [ item . xPath ] = item . value ;
}
}
/ / 3. 确 保 所 有 选 中 列 都 存 在
return instanceMap . values . map ( ( row ) {
for ( final node in selectedNodes ) {
row . putIfAbsent ( node . path , ( ) = > row [ node . path ] ? ? ' ' ) ;
}
return row ;
} ) . toList ( ) ;
}
}
class _TemplateItem DataSource extends DataGridSource {
final List < Map < String , dynamic > > _row s;
final List < TemplateNode > selectedNode s;
class _CsvDataSource extends DataGridSource {
final List < dynamic > headers ;
final List < List < dynamic > > _rows ;
_TemplateItemDataSource ( { required List < Map < String , dynamic > > rows , required this . selectedNodes } )
: _rows = rows ;
_CsvDataSource ( { required this . headers , required List < List < dynamic > > rows } ) : _rows = rows ;
@ override
List < DataGridRow > get rows {
/ / print ( " [DEBUG] 原始可加载记录数: ${ _rows . length } " ) ;
return _rows . asMap ( ) . entries . map ( ( entry ) {
final index = entry . key ;
final rowData = entry . value ;
return _rows . map ( ( row ) {
return DataGridRow (
cells: [
DataGridCell < int > ( columnName: ' index ' , value: index + 1 ) ,
. . . selectedNodes . map ( ( node ) {
return DataGridCell < String > (
columnName: node . path ,
value: rowData [ node . path ] ? . toString ( ) ? ? ' ' ,
) ;
} ) . toList ( ) ,
] ,
cells:
headers . asMap ( ) . entries . map ( ( entry ) {
final columnIndex = entry . key ;
final columnName = entry . value . toString ( ) ;
final cellValue = columnIndex < row . length ? row [ columnIndex ] . toString ( ) : ' ' ;
return DataGridCell ( columnName: columnName , value: cellValue ) ;
} ) . toList ( ) ,
) ;
} ) . toList ( ) ;
}
@ -205,9 +183,12 @@ class _TemplateItemDataSource extends DataGridSource {
@@ -205,9 +183,12 @@ class _TemplateItemDataSource extends DataGridSource {
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 ( ) ) ,
alignment: Alignment . centerLeft ,
child: Text (
dataGridCell . value . toString ( ) ,
overflow: TextOverflow . ellipsis ,
maxLines: 1 ,
) ,
) ;
} ) . toList ( ) ,
) ;