8 changed files with 308 additions and 141 deletions
@ -0,0 +1,51 @@ |
|||||||
|
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/base_data_source.dart'; |
||||||
|
|
||||||
|
class ComponentSource extends SelectableDataSource<UftComponent> { |
||||||
|
ComponentSource( |
||||||
|
List<UftComponent> uftComponents, { |
||||||
|
required Null Function(dynamic index, dynamic isSelected) onSelectionChanged, |
||||||
|
}) : super(uftComponents, onSelectionChanged: onSelectionChanged); |
||||||
|
|
||||||
|
@override |
||||||
|
List<DataGridRow> get rows => |
||||||
|
items |
||||||
|
.map( |
||||||
|
(component) => DataGridRow( |
||||||
|
cells: [ |
||||||
|
DataGridCell<bool>(columnName: 'select', value: component.isSelected), |
||||||
|
DataGridCell<int>(columnName: 'id', value: component.id), |
||||||
|
DataGridCell<String>(columnName: 'name', value: component.name), |
||||||
|
DataGridCell<String>(columnName: 'chineseName', value: component.chineseName), |
||||||
|
], |
||||||
|
), |
||||||
|
) |
||||||
|
.toList(); |
||||||
|
|
||||||
|
get data => items; |
||||||
|
|
||||||
|
@override |
||||||
|
DataGridRowAdapter buildRow(DataGridRow row) { |
||||||
|
final rowIndex = effectiveRows.indexOf(row); |
||||||
|
return DataGridRowAdapter( |
||||||
|
cells: |
||||||
|
row.getCells().map<Widget>((cell) { |
||||||
|
if (cell.columnName == 'select') { |
||||||
|
return Center( |
||||||
|
child: Checkbox( |
||||||
|
value: items[rowIndex].isSelected, |
||||||
|
onChanged: (value) => toggleRowSelection(rowIndex, value), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
return Container( |
||||||
|
alignment: Alignment.centerLeft, |
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8), |
||||||
|
child: Text(cell.value.toString(), overflow: TextOverflow.ellipsis), |
||||||
|
); |
||||||
|
}).toList(), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -1,19 +1,85 @@ |
|||||||
// memory_table_service.dart |
// memory_table_service.dart |
||||||
|
|
||||||
|
import 'dart:io'; |
||||||
|
|
||||||
|
import 'package:win_text_editor/modules/uft_component/models/uft_component.dart'; |
||||||
|
import 'package:win_text_editor/shared/data/std_fields_cache.dart'; |
||||||
import 'package:win_text_editor/shared/models/std_filed.dart'; |
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||||
|
|
||||||
import 'package:win_text_editor/framework/controllers/logger.dart'; |
import 'package:win_text_editor/framework/controllers/logger.dart'; |
||||||
|
import 'package:win_text_editor/shared/uft_std_fields/field_data_service.dart'; |
||||||
|
import 'package:xml/xml.dart' as xml; |
||||||
|
|
||||||
class UftComponentService { |
class UftComponentService { |
||||||
final Logger _logger; |
final Logger _logger; |
||||||
|
|
||||||
UftComponentService(this._logger); |
UftComponentService(this._logger); |
||||||
} |
|
||||||
|
|
||||||
class ComponentData { |
Future<List<UftComponent>> parseComponentFile(String filePath) async { |
||||||
final String name; |
try { |
||||||
final String chineseName; |
// 1. Check file extension |
||||||
final List<Field> fields; |
if (!filePath.toLowerCase().endsWith('component.xml')) { |
||||||
|
throw const FormatException("文件名必须是component.xml"); |
||||||
|
} |
||||||
|
|
||||||
|
// 2. 查找 metadata 目录和 stdfield.stfield 文件 |
||||||
|
if (await StdFieldsCache.getLength() == 0) { |
||||||
|
_logger.info("加载标准字段缓存"); |
||||||
|
final metadataFile = await FieldDataService.findMetadataFile(filePath); |
||||||
|
if (metadataFile != null) { |
||||||
|
await FieldDataService.processStdFieldFile(metadataFile); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 3. Read and parse structure file content |
||||||
|
final file = File(filePath); |
||||||
|
final content = await file.readAsString(); |
||||||
|
|
||||||
|
final document = xml.XmlDocument.parse(content); |
||||||
|
final componentNodes = document.findAllElements('items'); |
||||||
|
|
||||||
|
if (componentNodes.isEmpty) { |
||||||
|
throw const FormatException("文件格式错误:缺少items节点"); |
||||||
|
} |
||||||
|
|
||||||
|
// 4. 解析组件列表 |
||||||
|
final components = <UftComponent>[]; |
||||||
|
int id = 0; |
||||||
|
for (var node in componentNodes) { |
||||||
|
if (node.findElements("items").isEmpty) continue; |
||||||
|
id++; |
||||||
|
final name = node.getAttribute('name') ?? ''; |
||||||
|
final chineseName = node.getAttribute('chineseName') ?? ''; |
||||||
|
|
||||||
|
// 5. Process properties (fields) |
||||||
|
final childNodes = node.children; |
||||||
|
final fields = <Field>[]; |
||||||
|
int index = 1; |
||||||
|
|
||||||
|
for (final childNode in childNodes) { |
||||||
|
final name = childNode.getAttribute('name') ?? ''; |
||||||
|
// 尝试从缓存获取标准字段信息 |
||||||
|
final stdField = StdFieldsCache.getData(name); |
||||||
|
fields.add( |
||||||
|
Field( |
||||||
|
(index++).toString(), // 序号 |
||||||
|
name, // 名称 |
||||||
|
stdField?.chineseName ?? '', // 中文名 |
||||||
|
stdField?.dateType ?? '', // 类型 |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
components.add(UftComponent(id: id, name: name, chineseName: chineseName, fields: fields)); |
||||||
|
} |
||||||
|
|
||||||
ComponentData({required this.name, required this.chineseName, required this.fields}); |
return components; |
||||||
|
} on xml.XmlParserException catch (e) { |
||||||
|
_logger.error("XML解析错误: ${e.message}"); |
||||||
|
rethrow; |
||||||
|
} catch (e) { |
||||||
|
_logger.error("处理文件时发生错误: $e"); |
||||||
|
rethrow; |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,106 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:syncfusion_flutter_datagrid/datagrid.dart'; |
||||||
|
import 'package:win_text_editor/modules/uft_component/controllers/component_source.dart'; |
||||||
|
import 'package:win_text_editor/shared/base/base_data_source.dart'; |
||||||
|
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||||
|
|
||||||
|
class ComponentGrid extends StatelessWidget { |
||||||
|
final ComponentSource componentsSource; |
||||||
|
final Function(int index, bool isSelected)? onSelectionChanged; |
||||||
|
|
||||||
|
const ComponentGrid({super.key, required this.componentsSource, this.onSelectionChanged}); |
||||||
|
|
||||||
|
Container _buildGridHeader(String text) { |
||||||
|
return Container( |
||||||
|
alignment: Alignment.center, |
||||||
|
color: Colors.grey[200], |
||||||
|
child: Text(text, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildCheckboxHeader<T extends SelectableItem>( |
||||||
|
BuildContext context, |
||||||
|
SelectableDataSource<T> dataSource, |
||||||
|
) { |
||||||
|
final allSelected = |
||||||
|
dataSource.items.isNotEmpty && dataSource.items.every((item) => item.isSelected); |
||||||
|
final someSelected = |
||||||
|
dataSource.items.isNotEmpty && |
||||||
|
dataSource.items.any((item) => item.isSelected) && |
||||||
|
!allSelected; |
||||||
|
|
||||||
|
return Container( |
||||||
|
alignment: Alignment.center, |
||||||
|
color: Colors.grey[200], |
||||||
|
child: Checkbox( |
||||||
|
value: allSelected, |
||||||
|
tristate: someSelected, |
||||||
|
onChanged: (value) => dataSource.toggleAllSelection(value ?? false), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Card( |
||||||
|
child: Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
Expanded( |
||||||
|
child: LayoutBuilder( |
||||||
|
builder: (context, constraints) { |
||||||
|
return SizedBox( |
||||||
|
width: constraints.maxWidth, |
||||||
|
child: SfDataGrid( |
||||||
|
source: componentsSource, |
||||||
|
gridLinesVisibility: GridLinesVisibility.both, |
||||||
|
headerGridLinesVisibility: GridLinesVisibility.both, |
||||||
|
columnWidthMode: ColumnWidthMode.fitByCellValue, |
||||||
|
selectionMode: SelectionMode.none, |
||||||
|
columns: [ |
||||||
|
GridColumn( |
||||||
|
columnName: 'select', |
||||||
|
label: ValueListenableBuilder<bool>( |
||||||
|
valueListenable: componentsSource.selectionNotifier, |
||||||
|
builder: |
||||||
|
(context, _, __) => _buildCheckboxHeader(context, componentsSource), |
||||||
|
), |
||||||
|
width: 60, |
||||||
|
), |
||||||
|
GridColumn(columnName: 'id', label: _buildGridHeader('序号'), minimumWidth: 80), |
||||||
|
GridColumn( |
||||||
|
columnName: 'name', |
||||||
|
label: _buildGridHeader('名称'), |
||||||
|
minimumWidth: 120, |
||||||
|
), |
||||||
|
GridColumn( |
||||||
|
columnName: 'chineseName', |
||||||
|
label: _buildGridHeader('中文名'), |
||||||
|
minimumWidth: 120, |
||||||
|
), |
||||||
|
], |
||||||
|
onCellTap: (details) { |
||||||
|
if (details.column.columnName == 'select') { |
||||||
|
final rowIndex = details.rowColumnIndex.rowIndex - 1; |
||||||
|
if (rowIndex >= 0 && rowIndex < componentsSource.items.length) { |
||||||
|
componentsSource.toggleRowSelection( |
||||||
|
rowIndex, |
||||||
|
!componentsSource.items[rowIndex].isSelected, |
||||||
|
); |
||||||
|
onSelectionChanged?.call( |
||||||
|
rowIndex, |
||||||
|
componentsSource.items[rowIndex].isSelected, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue