20 changed files with 577 additions and 27 deletions
@ -1,17 +1,4 @@ |
|||||||
abstract class SelectableItem { |
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||||
bool isSelected = false; |
|
||||||
} |
|
||||||
|
|
||||||
class Field implements SelectableItem { |
|
||||||
Field(this.id, this.name, this.chineseName, this.type, [this.isSelected = false]); |
|
||||||
|
|
||||||
final String id; |
|
||||||
final String name; |
|
||||||
final String chineseName; |
|
||||||
final String type; |
|
||||||
@override |
|
||||||
bool isSelected; |
|
||||||
} |
|
||||||
|
|
||||||
class Index implements SelectableItem { |
class Index implements SelectableItem { |
||||||
Index(this.indexName, this.isPrimary, this.indexFields, this.rule, [this.isSelected = false]); |
Index(this.indexName, this.isPrimary, this.indexFields, this.rule, [this.isSelected = false]); |
@ -0,0 +1,113 @@ |
|||||||
|
import 'package:syncfusion_flutter_datagrid/datagrid.dart'; |
||||||
|
import 'package:win_text_editor/framework/controllers/logger.dart'; |
||||||
|
import 'package:win_text_editor/framework/services/macro_template_service.dart'; |
||||||
|
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||||
|
import 'package:win_text_editor/shared/uft_std_fields/field_data_source.dart'; |
||||||
|
import 'package:win_text_editor/modules/memory_table/models/memory_table.dart'; |
||||||
|
import 'package:win_text_editor/modules/memory_table/services/memory_table_service.dart'; |
||||||
|
import 'package:win_text_editor/shared/base/base_content_controller.dart'; |
||||||
|
|
||||||
|
class UftComponentController extends BaseContentController { |
||||||
|
String? _errorMessage; |
||||||
|
String tableName = ""; |
||||||
|
String objectId = ""; |
||||||
|
String chineseName = ""; |
||||||
|
|
||||||
|
late DataGridSource fieldsSource; |
||||||
|
final MemoryTableService _service; |
||||||
|
final MacroTemplateService templateService = MacroTemplateService(); |
||||||
|
|
||||||
|
// 新增:维护MemoryTable对象 |
||||||
|
late MemoryTable _memoryTable; |
||||||
|
|
||||||
|
UftComponentController() : _service = MemoryTableService(Logger()) { |
||||||
|
// 初始化空数据 |
||||||
|
final initialFields = [Field('1', '', '', '', false), Field('2', '', '', '', false)]; |
||||||
|
|
||||||
|
final initialIndexes = [Index('', false, '', '', false)]; |
||||||
|
|
||||||
|
fieldsSource = FieldsDataSource( |
||||||
|
initialFields, |
||||||
|
onSelectionChanged: (index, isSelected) { |
||||||
|
updateFieldSelection(index, isSelected); |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
// 初始化MemoryTable |
||||||
|
_memoryTable = MemoryTable(tableName: '', columns: initialFields, indexes: initialIndexes); |
||||||
|
} |
||||||
|
|
||||||
|
String? get errorMessage => _errorMessage; |
||||||
|
|
||||||
|
// 新增:获取当前MemoryTable |
||||||
|
MemoryTable get memoryTable => _memoryTable; |
||||||
|
|
||||||
|
void initTemplateService() { |
||||||
|
if (!templateService.inited) { |
||||||
|
templateService.init(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
String? genCodeString(List<String> macroList) { |
||||||
|
initTemplateService(); |
||||||
|
return templateService.renderTemplate(macroList, _memoryTable.toMap()); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> onOpenFile(String filePath) async { |
||||||
|
Logger().info("Opening file: $filePath"); |
||||||
|
try { |
||||||
|
final tableData = await _service.parseStructureFile(filePath); |
||||||
|
|
||||||
|
// Update controller state |
||||||
|
tableName = tableData.tableName; |
||||||
|
chineseName = tableData.chineseName; |
||||||
|
objectId = tableData.objectId; |
||||||
|
|
||||||
|
// Update data sources |
||||||
|
(fieldsSource as FieldsDataSource).updateData(tableData.fields); |
||||||
|
|
||||||
|
// 更新MemoryTable对象 |
||||||
|
_memoryTable = MemoryTable( |
||||||
|
tableName: tableName, |
||||||
|
columns: tableData.fields, |
||||||
|
indexes: tableData.indexes, |
||||||
|
); |
||||||
|
|
||||||
|
// Clear any previous error |
||||||
|
_errorMessage = null; |
||||||
|
|
||||||
|
// Notify UI to update |
||||||
|
notifyListeners(); |
||||||
|
} catch (e) { |
||||||
|
_errorMessage = e.toString(); |
||||||
|
notifyListeners(); |
||||||
|
Logger().error("Error opening file: $e"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 新增:更新字段选择状态 |
||||||
|
void updateFieldSelection(int index, bool isSelected) { |
||||||
|
final fields = (fieldsSource as FieldsDataSource).data; |
||||||
|
if (index >= 0 && index < fields.length) { |
||||||
|
fields[index].isSelected = isSelected; |
||||||
|
fieldsSource.notifyListeners(); |
||||||
|
|
||||||
|
// 同步更新MemoryTable |
||||||
|
_memoryTable.columns[index].isSelected = isSelected; |
||||||
|
notifyListeners(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 新增:更新索引选择状态 |
||||||
|
|
||||||
|
@override |
||||||
|
void onOpenFolder(String folderPath) { |
||||||
|
// 不支持打开文件夹 |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||||
|
|
||||||
|
class UftComponent { |
||||||
|
final String componentName; |
||||||
|
final List<Field> columns; |
||||||
|
|
||||||
|
UftComponent({required this.componentName, required this.columns}); |
||||||
|
|
||||||
|
List<Field> get selectFields => columns.where((field) => field.isSelected).toList(); |
||||||
|
|
||||||
|
Map<String, dynamic> toMap() { |
||||||
|
return { |
||||||
|
'componentName': componentName, |
||||||
|
'fields': |
||||||
|
columns |
||||||
|
.map( |
||||||
|
(field) => { |
||||||
|
'id': field.id, |
||||||
|
'name': field.name, |
||||||
|
'chineseName': field.chineseName, |
||||||
|
'type': field.type, |
||||||
|
'isLast': columns.indexOf(field) == columns.length - 1, |
||||||
|
}, |
||||||
|
) |
||||||
|
.toList(), |
||||||
|
'selectedFields': |
||||||
|
selectFields |
||||||
|
.map( |
||||||
|
(field) => { |
||||||
|
'id': field.id, |
||||||
|
'name': field.name, |
||||||
|
'chineseName': field.chineseName, |
||||||
|
'type': field.type, |
||||||
|
'isLast': selectFields.indexOf(field) == selectFields.length - 1, |
||||||
|
}, |
||||||
|
) |
||||||
|
.toList(), |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
// memory_table_service.dart |
||||||
|
|
||||||
|
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||||
|
|
||||||
|
import 'package:win_text_editor/framework/controllers/logger.dart'; |
||||||
|
|
||||||
|
class UftComponentService { |
||||||
|
final Logger _logger; |
||||||
|
|
||||||
|
UftComponentService(this._logger); |
||||||
|
} |
||||||
|
|
||||||
|
class ComponentData { |
||||||
|
final String name; |
||||||
|
final String chineseName; |
||||||
|
final List<Field> fields; |
||||||
|
|
||||||
|
ComponentData({required this.name, required this.chineseName, required this.fields}); |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:win_text_editor/modules/uft_component/controllers/uft_component_controller.dart'; |
||||||
|
import 'package:win_text_editor/shared/uft_std_fields/field_data_source.dart'; |
||||||
|
import 'package:win_text_editor/shared/uft_std_fields/fields_data_grid.dart'; |
||||||
|
|
||||||
|
class UftComponentLeftSide extends StatelessWidget { |
||||||
|
final UftComponentController controller; |
||||||
|
const UftComponentLeftSide({super.key, required this.controller}); |
||||||
|
|
||||||
|
Widget _buildTextFieldRow(String label, String value) { |
||||||
|
return Row( |
||||||
|
mainAxisSize: MainAxisSize.min, |
||||||
|
children: [ |
||||||
|
Text('$label:'), |
||||||
|
SizedBox( |
||||||
|
width: 200, |
||||||
|
child: TextField( |
||||||
|
controller: TextEditingController(text: value), |
||||||
|
readOnly: true, |
||||||
|
decoration: const InputDecoration(isDense: true, contentPadding: EdgeInsets.all(8)), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
SizedBox( |
||||||
|
width: double.infinity, |
||||||
|
child: Card( |
||||||
|
child: Padding( |
||||||
|
padding: const EdgeInsets.all(8.0), |
||||||
|
child: Wrap( |
||||||
|
spacing: 16, |
||||||
|
runSpacing: 8, |
||||||
|
children: [ |
||||||
|
_buildTextFieldRow('名称', controller.tableName), |
||||||
|
_buildTextFieldRow('中文名', controller.chineseName), |
||||||
|
_buildTextFieldRow('对象编号', controller.objectId), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(height: 8), |
||||||
|
const Padding( |
||||||
|
padding: EdgeInsets.all(8.0), |
||||||
|
child: Text('字段列表', style: TextStyle(fontWeight: FontWeight.bold)), |
||||||
|
), |
||||||
|
Expanded( |
||||||
|
flex: 6, |
||||||
|
child: FieldsDataGrid( |
||||||
|
fieldsSource: controller.fieldsSource as FieldsDataSource, |
||||||
|
onSelectionChanged: (index, isSelected) { |
||||||
|
controller.updateFieldSelection(index, isSelected); |
||||||
|
}, |
||||||
|
), |
||||||
|
), |
||||||
|
const Padding( |
||||||
|
padding: EdgeInsets.all(8.0), |
||||||
|
child: Text('索引列表', style: TextStyle(fontWeight: FontWeight.bold)), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/services.dart'; |
||||||
|
import 'package:win_text_editor/modules/uft_component/controllers/uft_component_controller.dart'; |
||||||
|
|
||||||
|
class UftComponentRightSide extends StatefulWidget { |
||||||
|
final UftComponentController controller; |
||||||
|
final TextEditingController codeController; |
||||||
|
|
||||||
|
const UftComponentRightSide({super.key, required this.controller, required this.codeController}); |
||||||
|
|
||||||
|
@override |
||||||
|
State<UftComponentRightSide> createState() => _UftComponentRightSideState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _UftComponentRightSideState extends State<UftComponentRightSide> { |
||||||
|
final List<String> _selectedOperations = []; |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
widget.controller.initTemplateService(); |
||||||
|
widget.controller.addListener(_updateDisplay); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
widget.controller.removeListener(_updateDisplay); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
void _updateDisplay() { |
||||||
|
widget.codeController.text = widget.controller.genCodeString(_selectedOperations)!; |
||||||
|
} |
||||||
|
|
||||||
|
void _toggleOperation(String operation, bool? value) { |
||||||
|
setState(() { |
||||||
|
if (value == true) { |
||||||
|
_selectedOperations.add(operation); |
||||||
|
} else { |
||||||
|
_selectedOperations.remove(operation); |
||||||
|
} |
||||||
|
_updateDisplay(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Column( |
||||||
|
children: [ |
||||||
|
_buildCheckboxSection(), |
||||||
|
Padding( |
||||||
|
padding: const EdgeInsets.only(left: 8.0, right: 8.0), |
||||||
|
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||||
|
children: [ |
||||||
|
const Text('生成代码:', style: TextStyle(fontWeight: FontWeight.bold)), |
||||||
|
IconButton( |
||||||
|
icon: const Icon(Icons.content_copy, size: 20), |
||||||
|
tooltip: '复制代码', |
||||||
|
onPressed: () { |
||||||
|
if (widget.codeController.text.isNotEmpty) { |
||||||
|
Clipboard.setData(ClipboardData(text: widget.codeController.text)); |
||||||
|
ScaffoldMessenger.of( |
||||||
|
context, |
||||||
|
).showSnackBar(const SnackBar(content: Text('已复制到剪贴板'))); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
Flexible(child: _buildCodeEditor()), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildCheckboxSection() { |
||||||
|
final operations = ['获取记录', '获取记录数', '插入记录', '修改记录', '删除记录', '遍历记录']; |
||||||
|
|
||||||
|
return SizedBox( |
||||||
|
width: double.infinity, |
||||||
|
child: Card( |
||||||
|
child: Padding( |
||||||
|
padding: const EdgeInsets.all(8.0), |
||||||
|
child: Column( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
Wrap( |
||||||
|
spacing: 16, |
||||||
|
runSpacing: 8, |
||||||
|
children: operations.map((op) => _buildCheckbox(op)).toList(), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildCheckbox(String label) { |
||||||
|
return Row( |
||||||
|
mainAxisSize: MainAxisSize.min, |
||||||
|
children: [ |
||||||
|
Checkbox( |
||||||
|
value: _selectedOperations.contains(label), |
||||||
|
onChanged: (bool? value) => _toggleOperation(label, value), |
||||||
|
), |
||||||
|
Text(label), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildCodeEditor() { |
||||||
|
return Card( |
||||||
|
child: Padding( |
||||||
|
padding: const EdgeInsets.all(8), |
||||||
|
child: Container( |
||||||
|
decoration: BoxDecoration( |
||||||
|
border: Border.all(color: Colors.grey), |
||||||
|
borderRadius: BorderRadius.circular(4), |
||||||
|
), |
||||||
|
child: TextField( |
||||||
|
controller: widget.codeController, |
||||||
|
maxLines: null, |
||||||
|
expands: true, |
||||||
|
decoration: const InputDecoration( |
||||||
|
border: InputBorder.none, |
||||||
|
contentPadding: EdgeInsets.all(8), |
||||||
|
), |
||||||
|
style: const TextStyle(fontFamily: 'monospace', color: Colors.blueAccent), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:provider/provider.dart'; |
||||||
|
import 'package:win_text_editor/framework/controllers/tab_items_controller.dart'; |
||||||
|
import 'package:win_text_editor/modules/uft_component/controllers/uft_component_controller.dart'; |
||||||
|
|
||||||
|
import 'uft_component_left_side.dart'; |
||||||
|
import 'uft_component_right_side.dart'; |
||||||
|
|
||||||
|
class UftComponentView extends StatefulWidget { |
||||||
|
final String tabId; |
||||||
|
const UftComponentView({super.key, required this.tabId}); |
||||||
|
|
||||||
|
@override |
||||||
|
State<UftComponentView> createState() => _UftComponentViewState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _UftComponentViewState extends State<UftComponentView> { |
||||||
|
late final UftComponentController _controller; |
||||||
|
final TextEditingController _codeController = TextEditingController(); |
||||||
|
bool _isControllerFromTabManager = false; |
||||||
|
|
||||||
|
get tabManager => Provider.of<TabItemsController>(context, listen: false); |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
|
||||||
|
final controllerFromManager = tabManager.getController(widget.tabId); |
||||||
|
if (controllerFromManager != null) { |
||||||
|
_controller = controllerFromManager; |
||||||
|
_isControllerFromTabManager = true; |
||||||
|
} else { |
||||||
|
_controller = UftComponentController(); |
||||||
|
_isControllerFromTabManager = false; |
||||||
|
tabManager.registerController(widget.tabId, _controller); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
if (!_isControllerFromTabManager) { |
||||||
|
_controller.dispose(); |
||||||
|
} |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return ChangeNotifierProvider.value( |
||||||
|
value: _controller, |
||||||
|
child: Consumer<UftComponentController>( |
||||||
|
builder: (context, controller, child) { |
||||||
|
return Padding( |
||||||
|
padding: const EdgeInsets.all(8.0), |
||||||
|
child: Row( |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: [ |
||||||
|
// 左侧部分 (50%) |
||||||
|
Expanded(flex: 5, child: UftComponentLeftSide(controller: controller)), |
||||||
|
const SizedBox(width: 8), |
||||||
|
// 右侧部分 (50%) |
||||||
|
Expanded( |
||||||
|
flex: 5, |
||||||
|
child: UftComponentRightSide( |
||||||
|
codeController: _codeController, |
||||||
|
controller: controller, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,71 @@ |
|||||||
|
import 'dart:io'; |
||||||
|
|
||||||
|
import 'package:win_text_editor/framework/controllers/logger.dart'; |
||||||
|
import 'package:win_text_editor/modules/memory_table/models/memory_table.dart'; |
||||||
|
import 'package:win_text_editor/shared/data/std_fields_cache.dart'; |
||||||
|
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||||
|
import 'package:xml/xml.dart' as xml; |
||||||
|
|
||||||
|
class FieldDataService { |
||||||
|
final Logger _logger; |
||||||
|
|
||||||
|
FieldDataService(this._logger); |
||||||
|
|
||||||
|
Future<File?> findMetadataFile(String filePath) async { |
||||||
|
Directory currentDir = File(filePath).parent; |
||||||
|
const targetDirName = 'metadata'; |
||||||
|
const targetFileName = 'stdfield.stdfield'; |
||||||
|
|
||||||
|
// 向上查找 metadata 目录 |
||||||
|
while (true) { |
||||||
|
final metadataDir = Directory('${currentDir.path}/$targetDirName'); |
||||||
|
if (await metadataDir.exists()) { |
||||||
|
final stdFieldFile = File('${metadataDir.path}/$targetFileName'); |
||||||
|
if (await stdFieldFile.exists()) { |
||||||
|
return stdFieldFile; |
||||||
|
} else { |
||||||
|
_logger.error("没找到标准字段文件 $targetFileName"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 到达根目录时停止 |
||||||
|
if (currentDir.path == currentDir.parent.path) { |
||||||
|
_logger.error("没有找到元数据目录 $targetDirName"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
currentDir = currentDir.parent; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 处理标准字段文件并缓存 |
||||||
|
Future<void> processStdFieldFile(File stdFieldFile) async { |
||||||
|
try { |
||||||
|
final content = await stdFieldFile.readAsString(); |
||||||
|
final document = xml.XmlDocument.parse(content); |
||||||
|
final items = document.findAllElements('items'); |
||||||
|
|
||||||
|
for (final item in items) { |
||||||
|
final name = item.getAttribute('name') ?? ''; |
||||||
|
final chineseName = item.getAttribute('chineseName') ?? ''; |
||||||
|
final dataType = item.getAttribute('dataType') ?? ''; |
||||||
|
|
||||||
|
if (name.isNotEmpty) { |
||||||
|
final stdField = StdField(name: name, chineseName: chineseName, dateType: dataType); |
||||||
|
await StdFieldsCache.setData(name, stdField); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
_logger.error("处理标准字段文件时出错: $e"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
List<Field> getDefaultFields() { |
||||||
|
return [Field('1', '', '', ''), Field('2', '', '', ''), Field('3', '', '', '')]; |
||||||
|
} |
||||||
|
|
||||||
|
List<Index> getDefaultIndexes() { |
||||||
|
return [Index('', false, '', ''), Index('', false, '', '')]; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue