20 changed files with 577 additions and 27 deletions
@ -1,17 +1,4 @@
@@ -1,17 +1,4 @@
|
||||
abstract class SelectableItem { |
||||
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; |
||||
} |
||||
import 'package:win_text_editor/shared/models/std_filed.dart'; |
||||
|
||||
class Index implements SelectableItem { |
||||
Index(this.indexName, this.isPrimary, this.indexFields, this.rule, [this.isSelected = false]); |
@ -0,0 +1,113 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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