From 70dae0124aa3dee1d0d6b481d3d74fa4f8420e09 Mon Sep 17 00:00:00 2001 From: hejl Date: Sun, 25 May 2025 21:48:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CppServerProject | 1 + cpp_server/.vscode/c_cpp_properties.json | 22 --- cpp_server/.vscode/settings.json | 9 -- cpp_server/CMakeLists.txt | 35 ----- cpp_server/RuntimeCompiledCPlusPlus | 1 - cpp_server/cmake/FindRCCPP.cmake | 7 - cpp_server/src/controllers/UserController.cpp | 21 --- cpp_server/src/controllers/UserController.h | 22 --- cpp_server/src/main.cpp | 48 ------- .../controllers/filter_controller.dart | 2 +- .../controllers/grid_view_controller.dart | 2 +- .../template_parser_controller.dart | 2 +- .../controllers/tree_view_controller.dart | 2 +- .../template_parser/widgets/grid_view.dart | 2 +- .../template_parser/widgets/tree_view.dart | 2 +- .../controllers/tree_view_controller.dart | 68 +++++++++ .../controllers/uft_file_controller.dart | 136 +++++++++++++++++- .../modules/uft_file/widgets/tree_view.dart | 89 ++++++++++++ .../uft_file/widgets/uft_file_view.dart | 67 ++++++++- .../models/template_node.dart | 0 20 files changed, 364 insertions(+), 174 deletions(-) create mode 160000 CppServerProject delete mode 100644 cpp_server/.vscode/c_cpp_properties.json delete mode 100644 cpp_server/.vscode/settings.json delete mode 100644 cpp_server/CMakeLists.txt delete mode 160000 cpp_server/RuntimeCompiledCPlusPlus delete mode 100644 cpp_server/cmake/FindRCCPP.cmake delete mode 100644 cpp_server/src/controllers/UserController.cpp delete mode 100644 cpp_server/src/controllers/UserController.h delete mode 100644 cpp_server/src/main.cpp create mode 100644 win_text_editor/lib/modules/uft_file/controllers/tree_view_controller.dart create mode 100644 win_text_editor/lib/modules/uft_file/widgets/tree_view.dart rename win_text_editor/lib/{modules/template_parser => shared}/models/template_node.dart (100%) diff --git a/CppServerProject b/CppServerProject new file mode 160000 index 0000000..73cb769 --- /dev/null +++ b/CppServerProject @@ -0,0 +1 @@ +Subproject commit 73cb76986ce1748eaee5ee6aee9c34a835d52fd5 diff --git a/cpp_server/.vscode/c_cpp_properties.json b/cpp_server/.vscode/c_cpp_properties.json deleted file mode 100644 index e0aa4cb..0000000 --- a/cpp_server/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "configurations": [ - { - "name": "Win32", - "includePath": [ - "${workspaceFolder}/**", - "D:/aigc/vcpkg/installed/x64-windows/include/**" - ], - "defines": [ - "_DEBUG", - "UNICODE", - "_UNICODE" - ], - "windowsSdkVersion": "10.0.18362.0", - "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe", - "cStandard": "c11", - "cppStandard": "c++11", - "intelliSenseMode": "windows-msvc-x64" - } - ], - "version": 4 -} \ No newline at end of file diff --git a/cpp_server/.vscode/settings.json b/cpp_server/.vscode/settings.json deleted file mode 100644 index 435248e..0000000 --- a/cpp_server/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cmake.configureOnOpen": true, - "cmake.buildDirectory": "${workspaceFolder}/build", - "cmake.generator": "Visual Studio 16 2019 Win64", - "C_Cpp.default.cppStandard": "c++11", - "cmake.configureSettings": { - "CMAKE_TOOLCHAIN_FILE": "D:/aigc/vcpkg/scripts/buildsystems/vcpkg.cmake" - } -} \ No newline at end of file diff --git a/cpp_server/CMakeLists.txt b/cpp_server/CMakeLists.txt deleted file mode 100644 index c61ddd7..0000000 --- a/cpp_server/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -cmake_minimum_required(VERSION 3.15) -project(MyDrogonProject) - -# 设置C++11标准 -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# 查找依赖 -find_package(Drogon CONFIG REQUIRED) - -# 添加RCC++子模块 -add_subdirectory(RuntimeCompiledCPlusPlus) - -# 定义热重载源文件 -set(RUNTIME_COMPILED_SOURCES - src/controllers/UserController.cpp -) - -# 主可执行文件 -add_executable(${PROJECT_NAME} - src/main.cpp - ${RUNTIME_COMPILED_SOURCES} -) - -# 配置RCC++ -runtime_compile_setup( - TARGET ${PROJECT_NAME} - SOURCES ${RUNTIME_COMPILED_SOURCES} -) - -# 链接库 -target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon) - -# 复制Swagger UI -file(COPY swagger DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) \ No newline at end of file diff --git a/cpp_server/RuntimeCompiledCPlusPlus b/cpp_server/RuntimeCompiledCPlusPlus deleted file mode 160000 index a93b46d..0000000 --- a/cpp_server/RuntimeCompiledCPlusPlus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a93b46d32052f16f2f0e647ef180ac92afa88764 diff --git a/cpp_server/cmake/FindRCCPP.cmake b/cpp_server/cmake/FindRCCPP.cmake deleted file mode 100644 index 4b58066..0000000 --- a/cpp_server/cmake/FindRCCPP.cmake +++ /dev/null @@ -1,7 +0,0 @@ -# 用于查找RCC++的头文件路径 -find_path(RCCPP_INCLUDE_DIR RuntimeObjectSystem.h - PATHS ${CMAKE_SOURCE_DIR}/RuntimeCompiledCPlusPlus/RuntimeObjectSystem -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(RCCPP DEFAULT_MSG RCCPP_INCLUDE_DIR) \ No newline at end of file diff --git a/cpp_server/src/controllers/UserController.cpp b/cpp_server/src/controllers/UserController.cpp deleted file mode 100644 index ac3d1fe..0000000 --- a/cpp_server/src/controllers/UserController.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "UserController.h" - -RCCPP_RUNTIME_TYPE_REGISTRATION(UserController) - -/** - * @brief 获取用户列表 - * @route GET /api/v1/users - * @response 200 用户列表JSON - */ -void UserController::asyncHandleHttpRequest( - const drogon::HttpRequestPtr &req, - std::function &&callback) -{ - Json::Value ret; - ret["status"] = "ok"; - ret["message"] = "Hello from RCC++!"; - ret["data"] = Json::arrayValue; - - auto resp = drogon::HttpResponse::newHttpJsonResponse(ret); - callback(resp); -} \ No newline at end of file diff --git a/cpp_server/src/controllers/UserController.h b/cpp_server/src/controllers/UserController.h deleted file mode 100644 index 55647f1..0000000 --- a/cpp_server/src/controllers/UserController.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "RuntimeCompiledCPlusPlus/RuntimeObjectSystem/RuntimeObjectSystem.h" -#include - -class UserController : public RCCpp::IObject, - public drogon::HttpSimpleController -{ -public: - UserController() { RCCpp::Construct(); } - virtual ~UserController() { RCCpp::Destruct(); } - - RCCPP_RUNTIME_TYPE_DECLARATION(UserController) - - void asyncHandleHttpRequest( - const drogon::HttpRequestPtr &req, - std::function &&callback) override; - - PATH_LIST_BEGIN - PATH_ADD("/api/v1/users", drogon::Get); - PATH_LIST_END -}; \ No newline at end of file diff --git a/cpp_server/src/main.cpp b/cpp_server/src/main.cpp deleted file mode 100644 index 9b2b180..0000000 --- a/cpp_server/src/main.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include "RuntimeCompiledCPlusPlus/RuntimeObjectSystem/RuntimeObjectSystem.h" -#include "controllers/UserController.h" - -class SystemTable : public RCCpp::ISystemTable -{ -public: - virtual void Log(RCCpp::LogSystem::LogType type, const char *pText) override - { - if (type == RCCpp::LogSystem::ERROR) - LOG_ERROR << pText; - else - LOG_INFO << pText; - } -}; - -int main() -{ - // 初始化RCC++ - SystemTable systemTable; - RCCpp::RuntimeObjectSystem runtimeObjectSystem; - runtimeObjectSystem.Initialise(&systemTable, nullptr); - - // 设置Swagger - drogon::app().registerController(std::make_shared()); - - // 加载热重载控制器 - runtimeObjectSystem.GetObjectFactorySystem()->LoadObjectFactory( - "UserController", true); - - // 设置文档路径 - drogon::app().setDocumentRoot("./swagger"); - drogon::app().setDocumentRoot("/api/v1/docs"); - - // 启动日志 - LOG_INFO << "Server running on http://127.0.0.1:8848"; - LOG_INFO << "Swagger UI: http://127.0.0.1:8848/api/v1/docs"; - LOG_INFO << "Hot reload enabled - modify controller files to see changes"; - - // 主循环 - drogon::app() - .addListener("0.0.0.0", 8848) - .setThreadNum(4) - .run(); - - runtimeObjectSystem.CleanObjectFiles(); - return 0; -} \ No newline at end of file diff --git a/win_text_editor/lib/modules/template_parser/controllers/filter_controller.dart b/win_text_editor/lib/modules/template_parser/controllers/filter_controller.dart index f4dc860..094d38c 100644 --- a/win_text_editor/lib/modules/template_parser/controllers/filter_controller.dart +++ b/win_text_editor/lib/modules/template_parser/controllers/filter_controller.dart @@ -1,7 +1,7 @@ // filter_controller.dart import 'package:flutter/foundation.dart'; import 'package:win_text_editor/framework/controllers/logger.dart'; -import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; +import 'package:win_text_editor/shared/models/template_node.dart'; import '../../../shared/base/safe_notifier.dart'; class FilterController extends SafeNotifier { diff --git a/win_text_editor/lib/modules/template_parser/controllers/grid_view_controller.dart b/win_text_editor/lib/modules/template_parser/controllers/grid_view_controller.dart index 2c1beef..f25b778 100644 --- a/win_text_editor/lib/modules/template_parser/controllers/grid_view_controller.dart +++ b/win_text_editor/lib/modules/template_parser/controllers/grid_view_controller.dart @@ -1,5 +1,5 @@ // grid_view_controller.dart -import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; +import 'package:win_text_editor/shared/models/template_node.dart'; import '../../../shared/base/safe_notifier.dart'; class GridViewController extends SafeNotifier { diff --git a/win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart b/win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart index e62b74d..af53e62 100644 --- a/win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart +++ b/win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart @@ -1,6 +1,6 @@ import 'package:file_picker/file_picker.dart'; import 'package:win_text_editor/framework/controllers/logger.dart'; -import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; +import 'package:win_text_editor/shared/models/template_node.dart'; import 'package:win_text_editor/shared/base/base_content_controller.dart'; import 'package:xml/xml.dart' as xml; import 'dart:io'; diff --git a/win_text_editor/lib/modules/template_parser/controllers/tree_view_controller.dart b/win_text_editor/lib/modules/template_parser/controllers/tree_view_controller.dart index e18ca83..0bb7e4c 100644 --- a/win_text_editor/lib/modules/template_parser/controllers/tree_view_controller.dart +++ b/win_text_editor/lib/modules/template_parser/controllers/tree_view_controller.dart @@ -1,6 +1,6 @@ // tree_view_controller.dart -import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; +import 'package:win_text_editor/shared/models/template_node.dart'; import '../../../shared/base/safe_notifier.dart'; class TreeViewController extends SafeNotifier { diff --git a/win_text_editor/lib/modules/template_parser/widgets/grid_view.dart b/win_text_editor/lib/modules/template_parser/widgets/grid_view.dart index b0a9b38..4960f9e 100644 --- a/win_text_editor/lib/modules/template_parser/widgets/grid_view.dart +++ b/win_text_editor/lib/modules/template_parser/widgets/grid_view.dart @@ -2,7 +2,7 @@ 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:win_text_editor/shared/models/template_node.dart'; import 'package:file_picker/file_picker.dart'; import 'dart:io'; diff --git a/win_text_editor/lib/modules/template_parser/widgets/tree_view.dart b/win_text_editor/lib/modules/template_parser/widgets/tree_view.dart index 302f26e..87a1f4d 100644 --- a/win_text_editor/lib/modules/template_parser/widgets/tree_view.dart +++ b/win_text_editor/lib/modules/template_parser/widgets/tree_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:win_text_editor/modules/template_parser/controllers/tree_view_controller.dart'; -import 'package:win_text_editor/modules/template_parser/models/template_node.dart'; +import 'package:win_text_editor/shared/models/template_node.dart'; import 'package:win_text_editor/shared/components/tree_view.dart'; class TemplateTreeView extends StatelessWidget { diff --git a/win_text_editor/lib/modules/uft_file/controllers/tree_view_controller.dart b/win_text_editor/lib/modules/uft_file/controllers/tree_view_controller.dart new file mode 100644 index 0000000..0bb7e4c --- /dev/null +++ b/win_text_editor/lib/modules/uft_file/controllers/tree_view_controller.dart @@ -0,0 +1,68 @@ +// tree_view_controller.dart + +import 'package:win_text_editor/shared/models/template_node.dart'; +import '../../../shared/base/safe_notifier.dart'; + +class TreeViewController extends SafeNotifier { + //根节点 + List _treeNodes = []; + TemplateNode? _selectedNode; + String? _currentParentPath; + + List get treeNodes => _treeNodes; + TemplateNode? get selectedNode => _selectedNode; + + // 加载树视图,当文件路径改变时调用 + void updateTreeNodes(List nodes) { + _treeNodes = nodes; + safeNotify(); + } + + void selectTreeNode(TemplateNode node) { + _selectedNode = node; + safeNotify(); + } + + // 选择节点,多选时仅可选中同一层级的节点 + void toggleNodeCheck(TemplateNode node) { + final parentPath = node.path.substring(0, node.path.lastIndexOf('/')); + if (_currentParentPath != null && _currentParentPath != parentPath) { + clearAllChecked(); + } + node.isChecked = !node.isChecked; + _currentParentPath = parentPath; + safeNotify(); + } + + void clearAllChecked() { + void traverse(TemplateNode node) { + node.isChecked = false; + for (var child in node.children) { + traverse(child); + } + } + + for (var node in _treeNodes) { + traverse(node); + } + } + + List get selectedNodeNames { + List selectedNodeNames = []; + + void traverse(TemplateNode node) { + if (node.isChecked) { + selectedNodeNames.add(node.name); + } + for (var child in node.children) { + traverse(child); + } + } + + for (var node in _treeNodes) { + traverse(node); + } + + return selectedNodeNames; + } +} diff --git a/win_text_editor/lib/modules/uft_file/controllers/uft_file_controller.dart b/win_text_editor/lib/modules/uft_file/controllers/uft_file_controller.dart index 45bd1ec..c87cf77 100644 --- a/win_text_editor/lib/modules/uft_file/controllers/uft_file_controller.dart +++ b/win_text_editor/lib/modules/uft_file/controllers/uft_file_controller.dart @@ -1,13 +1,145 @@ +import 'package:file_picker/file_picker.dart'; +import 'package:win_text_editor/framework/controllers/logger.dart'; +import 'package:win_text_editor/shared/models/template_node.dart'; import 'package:win_text_editor/shared/base/base_content_controller.dart'; +import 'package:xml/xml.dart' as xml; +import 'dart:io'; +import 'tree_view_controller.dart'; class UftFileController extends BaseContentController { + final TreeViewController treeController; + + String _filePath = ''; + String? _errorMessage; + + String get filePath => _filePath; + String? get errorMessage => _errorMessage; + + //---------------初始化方法---- + + UftFileController() : treeController = TreeViewController() { + _setupCrossControllerCommunication(); + } + + //设置跨控制器状态协同 + void _setupCrossControllerCommunication() {} + + //----------------业务入口方法----- + //widget调用入口:打开文件 + Future pickFile() async { + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['xml', '*'], + ); + if (result != null) { + _filePath = result.files.single.path!; + notifyListeners(); // 通知 Consumer 刷新 + await _loadTemplateData(); + } + } + + //执行框架回调入口:双击左侧资源管理文件 + Future setFilePath(String path) async { + _filePath = path; + notifyListeners(); // 通知 Consumer 刷新 + await _loadTemplateData(); + } + + //加载xml文件 + Future _loadTemplateData() async { + try { + _errorMessage = null; + final file = File(_filePath); + final content = await file.readAsString(); + final document = xml.XmlDocument.parse(content); + + // 更新各控制器 + //树视图展示文件结构 + treeController.updateTreeNodes( + _buildTreeNodes(document.rootElement, document.rootElement.localName, depth: 0), + ); + //列表展示选中节点的内容 + } catch (e) { + _errorMessage = 'Failed to load XML: ${e.toString()}'; + Logger().error('XML加载错误$_errorMessage'); + } + } + + //--------------------私有方法--------- + // 构建树节点 + List _buildTreeNodes( + xml.XmlElement element, + String path, { + required int depth, + int repreatCount = 1, + }) { + final node = TemplateNode( + path: path, + name: element.qualifiedName, + children: [], + depth: depth, + isExpanded: depth < 5, // 默认展开前两层 + isRepeated: repreatCount > 1, + repreatCount: repreatCount, + ); + + // 添加当前元素的所有属性节点 + if (element.attributes.isNotEmpty) { + node.children.addAll( + element.attributes.map( + (attr) => TemplateNode( + path: '$path/@${attr.name.local}', + name: '@${attr.qualifiedName}', + children: [], + depth: depth + 1, + isAttribute: true, + ), + ), + ); + } + + // 处理子元素节点(忽略文本节点) + final childElements = element.children.whereType(); + final groupedChildren = >{}; + + // 按元素名分组 + for (var child in childElements) { + groupedChildren.putIfAbsent(child.name.local, () => []).add(child); + } + + // 为每个唯一子元素创建节点 + groupedChildren.forEach((name, elements) { + String path0 = '$path/${elements.first.name.local}'; + if (elements.length == 1) { + // 单一节点直接添加(包含其所有属性) + node.children.addAll(_buildTreeNodes(elements.first, path0, depth: depth + 1)); + } else { + // 多个相同节点需要合并 + node.children.addAll( + _buildTreeNodes(elements.first, path0, depth: depth + 1, repreatCount: elements.length), + ); + } + }); + + return [node]; + } + + //解析全量数据 + + //-----------框架回调-- @override void onOpenFile(String filePath) { - // TODO: implement onOpenFile + setFilePath(filePath); } @override void onOpenFolder(String folderPath) { - // TODO: implement onOpenFolder + // 不支持打开文件夹 + } + + @override + void dispose() { + treeController.dispose(); + super.dispose(); } } diff --git a/win_text_editor/lib/modules/uft_file/widgets/tree_view.dart b/win_text_editor/lib/modules/uft_file/widgets/tree_view.dart new file mode 100644 index 0000000..9ab60e5 --- /dev/null +++ b/win_text_editor/lib/modules/uft_file/widgets/tree_view.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:win_text_editor/modules/uft_file/controllers/tree_view_controller.dart'; +import 'package:win_text_editor/shared/models/template_node.dart'; +import 'package:win_text_editor/shared/components/tree_view.dart'; + +class FileTreeView extends StatelessWidget { + const FileTreeView({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, controller, _) { + if (controller.treeNodes.isEmpty) { + return const Center(child: Text('No XML data available')); + } + + return TreeView( + nodes: controller.treeNodes, + config: const TreeViewConfig( + showIcons: true, + singleSelect: true, + selectedColor: Colors.lightBlueAccent, + icons: {'element': Icons.label_outline, 'attribute': Icons.code}, + ), + onNodeTap: (node) { + controller.selectTreeNode; + }, + nodeBuilder: (context, node, isSelected, onTap) { + return _buildTreeNode(node, isSelected, onTap, controller); + }, + ); + }, + ); + } + + Widget _buildTreeNode( + TreeNode node, + bool isSelected, + VoidCallback onTap, + TreeViewController 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: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (templateNode.children.isEmpty) // 仅在叶子节点显示复选框 + Checkbox( + value: templateNode.isChecked, + onChanged: (value) { + if (value != null) { + controller.toggleNodeCheck(templateNode); + } + }, + ), + 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, + ), + ), + ); + } +} diff --git a/win_text_editor/lib/modules/uft_file/widgets/uft_file_view.dart b/win_text_editor/lib/modules/uft_file/widgets/uft_file_view.dart index d313959..09fa046 100644 --- a/win_text_editor/lib/modules/uft_file/widgets/uft_file_view.dart +++ b/win_text_editor/lib/modules/uft_file/widgets/uft_file_view.dart @@ -2,6 +2,7 @@ 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_file/controllers/uft_file_controller.dart'; +import 'package:win_text_editor/modules/uft_file/widgets/tree_view.dart'; class UftFileView extends StatefulWidget { final String tabId; @@ -13,6 +14,7 @@ class UftFileView extends StatefulWidget { class _UftFileViewState extends State { late final UftFileController _controller; + bool _isControllerFromTabManager = false; get tabManager => Provider.of(context, listen: false); @@ -42,6 +44,69 @@ class _UftFileViewState extends State { @override Widget build(BuildContext context) { - return const Center(child: Text('demo')); + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: _controller), + ChangeNotifierProvider.value(value: _controller.treeController), + ], + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + _buildFilePathInput(), + const SizedBox(height: 8), + Expanded(child: _buildMainContent()), + ], + ), + ), + ); + } + + Widget _buildFilePathInput() { + return Consumer( + builder: (context, controller, _) { + return TextField( + decoration: InputDecoration( + labelText: 'UFT File', + hintText: 'UFT File (包括服务、原子层代码文件)', + suffixIcon: IconButton( + icon: const Icon(Icons.folder_open), + onPressed: controller.pickFile, + ), + border: const OutlineInputBorder(), + errorText: controller.errorMessage, + ), + controller: TextEditingController(text: controller.filePath), + readOnly: true, + ); + }, + ); + } + + Widget _buildMainContent() { + return Consumer( + builder: (context, controller, _) { + if (controller.errorMessage != null) { + return Center(child: Text(controller.errorMessage!)); + } + + return Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.3, + child: const Column( + children: [ + Expanded(flex: 2, child: Card(child: FileTreeView())), + SizedBox(height: 8), + Expanded(flex: 4, child: Card(child: Text("UFT File Content1"))), + ], + ), + ), + const SizedBox(width: 8), + const Expanded(child: Card(child: Text("UFT File Content2"))), + ], + ); + }, + ); } } diff --git a/win_text_editor/lib/modules/template_parser/models/template_node.dart b/win_text_editor/lib/shared/models/template_node.dart similarity index 100% rename from win_text_editor/lib/modules/template_parser/models/template_node.dart rename to win_text_editor/lib/shared/models/template_node.dart