20 changed files with 364 additions and 174 deletions
@ -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 @@ |
|||||||
{ |
|
||||||
"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 @@ |
|||||||
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 @@ |
|||||||
Subproject commit a93b46d32052f16f2f0e647ef180ac92afa88764 |
|
@ -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 @@ |
|||||||
#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 @@ |
|||||||
#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 @@ |
|||||||
#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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
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: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 { |
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 |
@override |
||||||
void onOpenFile(String filePath) { |
void onOpenFile(String filePath) { |
||||||
// TODO: implement onOpenFile |
setFilePath(filePath); |
||||||
} |
} |
||||||
|
|
||||||
@override |
@override |
||||||
void onOpenFolder(String folderPath) { |
void onOpenFolder(String folderPath) { |
||||||
// TODO: implement onOpenFolder |
// 不支持打开文件夹 |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
treeController.dispose(); |
||||||
|
super.dispose(); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -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