20 changed files with 364 additions and 174 deletions
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
Subproject commit 73cb76986ce1748eaee5ee6aee9c34a835d52fd5 |
@ -1,22 +0,0 @@
@@ -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 |
||||
} |
@ -1,9 +0,0 @@
@@ -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" |
||||
} |
||||
} |
@ -1,35 +0,0 @@
@@ -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}) |
@ -1 +0,0 @@
@@ -1 +0,0 @@
|
||||
Subproject commit a93b46d32052f16f2f0e647ef180ac92afa88764 |
@ -1,7 +0,0 @@
@@ -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) |
@ -1,21 +0,0 @@
@@ -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<void(const drogon::HttpResponsePtr &)> &&callback) |
||||
{ |
||||
Json::Value ret; |
||||
ret["status"] = "ok"; |
||||
ret["message"] = "Hello from RCC++!"; |
||||
ret["data"] = Json::arrayValue; |
||||
|
||||
auto resp = drogon::HttpResponse::newHttpJsonResponse(ret); |
||||
callback(resp); |
||||
} |
@ -1,22 +0,0 @@
@@ -1,22 +0,0 @@
|
||||
#pragma once |
||||
|
||||
#include "RuntimeCompiledCPlusPlus/RuntimeObjectSystem/RuntimeObjectSystem.h" |
||||
#include <drogon/HttpSimpleController.h> |
||||
|
||||
class UserController : public RCCpp::IObject, |
||||
public drogon::HttpSimpleController<UserController> |
||||
{ |
||||
public: |
||||
UserController() { RCCpp::Construct(); } |
||||
virtual ~UserController() { RCCpp::Destruct(); } |
||||
|
||||
RCCPP_RUNTIME_TYPE_DECLARATION(UserController) |
||||
|
||||
void asyncHandleHttpRequest( |
||||
const drogon::HttpRequestPtr &req, |
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback) override; |
||||
|
||||
PATH_LIST_BEGIN |
||||
PATH_ADD("/api/v1/users", drogon::Get); |
||||
PATH_LIST_END |
||||
}; |
@ -1,48 +0,0 @@
@@ -1,48 +0,0 @@
|
||||
#include <drogon/drogon.h> |
||||
#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<drogon::SwaggerController>()); |
||||
|
||||
// 加载热重载控制器
|
||||
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; |
||||
} |
@ -0,0 +1,68 @@
@@ -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<TemplateNode> _treeNodes = []; |
||||
TemplateNode? _selectedNode; |
||||
String? _currentParentPath; |
||||
|
||||
List<TemplateNode> get treeNodes => _treeNodes; |
||||
TemplateNode? get selectedNode => _selectedNode; |
||||
|
||||
// 加载树视图,当文件路径改变时调用 |
||||
void updateTreeNodes(List<TemplateNode> 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<String> get selectedNodeNames { |
||||
List<String> 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; |
||||
} |
||||
} |
@ -1,13 +1,145 @@
@@ -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<void> 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<void> setFilePath(String path) async { |
||||
_filePath = path; |
||||
notifyListeners(); // 通知 Consumer 刷新 |
||||
await _loadTemplateData(); |
||||
} |
||||
|
||||
//加载xml文件 |
||||
Future<void> _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<TemplateNode> _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<xml.XmlElement>(); |
||||
final groupedChildren = <String, List<xml.XmlElement>>{}; |
||||
|
||||
// 按元素名分组 |
||||
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(); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,89 @@
@@ -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<TreeViewController>( |
||||
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, |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue