diff --git a/README.md b/README.md
index a499c77b..5069b361 100644
--- a/README.md
+++ b/README.md
@@ -152,6 +152,7 @@ youlai-boot/
│ │ ├── form/ # 动态表单
│ │ └── workflow/ # 工作流
│ ├── support/ # 能力服务
+│ │ ├── ai/ # AI 对话(代码生成/动态表单/工作流辅助,默认关闭)
│ │ ├── file/ # 文件存储(RustFS/阿里云OSS/本地)
│ │ ├── mail/ # 邮件服务
│ │ ├── sms/ # 短信服务
@@ -179,10 +180,10 @@ youlai-boot/
| [youlai-gin](https://gitee.com/youlaiorg/youlai-gin) | Go + Gorm | Go | ✅️ |
| [youlai-django](https://gitee.com/youlaiorg/youlai-django) | Django + DRF | Python | ✅️ |
| [youlai-fastapi](https://gitee.com/youlaiorg/youlai-fastapi) | FastAPI + SQLAlchemy | Python | ✅️ |
-| [youlai-laravel](https://gitee.com/youlaiorg/youlai-laravel) | Laravel + Eloquent | PHP | ✅️ |
| [youlai-think](https://gitee.com/youlaiorg/youlai-think) | ThinkPHP + ThinkORM | PHP | ✅️ |
| [youlai-aspnet](https://gitee.com/youlaiorg/youlai-aspnet) | ASP.NET Core + EF Core | C# | ✅️ |
| [youlai-axum](https://gitee.com/youlaiorg/youlai-axum) | Axum + SeaORM | Rust | ✅️ |
+| [youlai-laravel](https://gitee.com/youlaiorg/youlai-laravel) | Laravel + Eloquent | PHP | ❌️ |
> 九种后端共享同一套 **RESTful API 规范** 和 **数据库结构**,前端可无缝切换。
@@ -194,8 +195,8 @@ youlai-boot/
| [youlai-boot-flex](https://gitee.com/youlaiorg/youlai-boot-flex) | youlai-boot | 独立仓库 | 改用 MyBatis-Flex | ✅️ |
| [youlai-boot (db-pg)](https://gitee.com/youlaiorg/youlai-boot/tree/db-pg) | youlai-boot | 分支 | PostgreSQL 数据库分支 | ✅️ |
| [youlai-boot (multi-module)](https://gitee.com/youlaiorg/youlai-boot/tree/multi-module) | youlai-boot | 分支 | 多模块工程拆分 | ✅️ |
-| [youlai-boot (spring-boot-3)](https://gitee.com/youlaiorg/youlai-boot/tree/spring-boot-3) | youlai-boot | 分支 | Spring Boot 3 兼容分支 | ✅️ |
| [youlai-nest (multi-tenant)](https://gitee.com/youlaiorg/youlai-nest/tree/multi-tenant) | youlai-nest | 分支 | 多租户 SaaS,租户隔离与租户配置 | ✅️ |
+| [youlai-boot (spring-boot-3)](https://gitee.com/youlaiorg/youlai-boot/tree/spring-boot-3) | youlai-boot | 分支 | Spring Boot 3 兼容分支 | ❌️ |
## 可选扩展
@@ -218,7 +219,7 @@ youlai-boot/
enabled: true
```
- Flowable 引擎开关(process / idm / eventregistry)在同一 `application-{profile}.yml` 中以占位符跟随 `workflow.enabled`,无需单独维护。
+ Flowable 引擎开关(process / idm / eventregistry)在 `application.yml` 中以占位符跟随 `workflow.enabled`,无需单独维护;只改上面一处即可。
**多租户**
diff --git a/pom.xml b/pom.xml
index d2dde7f6..2ff23159 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.youlai
youlai-boot
- 4.9.0
+ 4.10.0
基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。
@@ -36,7 +36,7 @@
1.6.3
0.2.0
- 3.2.0
+ 3.4.2
1.3.0
@@ -70,8 +70,23 @@
4.8.1.B
+
+ 2.0.0
+
+
+
+
+ org.springframework.ai
+ spring-ai-bom
+ ${spring-ai.version}
+ pom
+ import
+
+
+
+
org.projectlombok
@@ -152,6 +167,17 @@
spring-boot-starter-validation
+
+
+ org.springframework.ai
+ spring-ai-starter-model-openai
+
+
+
+ org.springframework.ai
+ spring-ai-client-chat
+
+
org.springframework.boot
spring-boot-starter-mail
diff --git a/sql/extensions/dynamic-form.sql b/sql/extensions/dynamic-form.sql
index 658402ae..75a9d31b 100644
--- a/sql/extensions/dynamic-form.sql
+++ b/sql/extensions/dynamic-form.sql
@@ -1,8 +1,8 @@
# 动态表单模块(可选扩展)
-# Copyright (c) 2021-present, youlai.tech
+# Copyright (c) 2026-present, youlai.tech
-- ----------------------------------------------------------------------------
--- 动态表单模块增量脚本
+-- 动态表单模块扩展脚本(全量执行,可重复运行)
-- 前置:请先执行 ../youlai-admin.sql(本脚本向 sys_menu/sys_role_menu 插入数据)
-- ----------------------------------------------------------------------------
USE youlai_admin;
@@ -75,50 +75,50 @@ CREATE TABLE `form_snapshot` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='表单版本快照表';
-- ----------------------------
--- 菜单(扩展段顶级 ID=7,基础段 1-6 见 youlai-admin.sql;目录设 always_show=1:仅一个可见子菜单时父级不被吞,LayoutSidebarItem 折叠逻辑)
+-- 菜单(扩展段顶级 ID=8,基础段 1-7 见 youlai-admin.sql)
-- 目录与视图目录/URL 全链路对齐:views/dynamic-form + /dynamic-form(route_path/component 字段)
-- ----------------------------
-- params 携 {"badge":"NEW"}:挂顶级"动态表单"一级可见(二级折叠态角标不可见),宣传期过后清空 params 即消失
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (7, 0, '0', '动态表单', 'C', '', '/dynamic-form', 'Layout', NULL, 1, NULL, 1, 7, 'el-icon-Document', '/dynamic-form/definition', now(), now(), '{\"badge\":\"NEW\"}');
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (701, 7, '0,7', '表单列表', 'M', 'FormDefinition', 'definition', 'dynamic-form/index', NULL, NULL, 1, 1, 1, 'el-icon-List', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70101, 701, '0,7,701', '表单查询', 'B', NULL, '', NULL, 'form:definition:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70102, 701, '0,7,701', '表单新增', 'B', NULL, '', NULL, 'form:definition:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70103, 701, '0,7,701', '表单修改', 'B', NULL, '', NULL, 'form:definition:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70104, 701, '0,7,701', '表单删除', 'B', NULL, '', NULL, 'form:definition:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
--- 表单设计器(隐藏菜单,visible=0,列表"设计"按钮携 id 跳转,复刻 dict-item 模式)
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (703, 7, '0,7', '表单设计器', 'M', 'FormDesigner', 'designer', 'dynamic-form/designer', NULL, NULL, 1, 0, 3, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (8, 0, '0', '动态表单', 'C', '', '/dynamic-form', 'Layout', NULL, NULL, 1, 8, 'el-icon-Document', '/dynamic-form/definition', now(), now(), '{\"badge\":\"NEW\"}');
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (801, 8, '0,8', '表单列表', 'M', 'FormDefinition', 'definition', 'dynamic-form/index', NULL, 1, 1, 1, 'el-icon-List', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80101, 801, '0,8,801', '表单查询', 'B', NULL, '', NULL, 'form:definition:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80102, 801, '0,8,801', '表单新增', 'B', NULL, '', NULL, 'form:definition:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80103, 801, '0,8,801', '表单修改', 'B', NULL, '', NULL, 'form:definition:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80104, 801, '0,8,801', '表单删除', 'B', NULL, '', NULL, 'form:definition:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
+-- 表单设计器(隐藏菜单,visible=0,列表"设计"按钮携 id 跳转)
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (803, 8, '0,8', '表单设计器', 'M', 'FormDesigner', 'designer', 'dynamic-form/designer', NULL, 1, 0, 3, '', NULL, now(), now(), NULL);
-- 表单预览(隐藏菜单,visible=0,设计器"预览"按钮携 id 跳转,运行态真预览)
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (704, 7, '0,7', '表单预览', 'M', 'FormPreview', 'preview', 'dynamic-form/preview', NULL, NULL, 1, 0, 4, '', NULL, now(), now(), NULL);
--- 表单数据(隐藏菜单,visible=0,列表"数据"按钮携 formKey 跳转,复刻 dict-item 模式)
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (702, 7, '0,7', '表单数据', 'M', 'FormData', 'data', 'dynamic-form/data', NULL, NULL, 1, 0, 2, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70201, 702, '0,7,702', '数据查询', 'B', NULL, '', NULL, 'form:data:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70202, 702, '0,7,702', '数据删除', 'B', NULL, '', NULL, 'form:data:delete', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (804, 8, '0,8', '表单预览', 'M', 'FormPreview', 'preview', 'dynamic-form/preview', NULL, 1, 0, 4, '', NULL, now(), now(), NULL);
+-- 表单数据(隐藏菜单,visible=0,列表"数据"按钮携 formKey 跳转)
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (802, 8, '0,8', '表单数据', 'M', 'FormData', 'data', 'dynamic-form/data', NULL, 1, 0, 2, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80201, 802, '0,8,802', '数据查询', 'B', NULL, '', NULL, 'form:data:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80202, 802, '0,8,802', '数据删除', 'B', NULL, '', NULL, 'form:data:delete', NULL, 1, 2, '', NULL, now(), now(), NULL);
-- ----------------------------
-- 示例表单入口(预置双入口:内嵌菜单 + 公开外链;与 saveFormMenu 生成的结构完全一致——
-- type=M、component=dynamic-form/render、route_path=formKey、params 携 formKey、
-- route_name 遵循 FormRender+驼峰(formKey) 全局唯一约束,防发布向导重复生成)
-- ----------------------------
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (705, 7, '0,7', '表单示例', 'C', '', 'sample', 'Layout', NULL, 1, NULL, 1, 5, 'el-icon-Files', '/dynamic-form/sample/open_source_survey', now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70501, 705, '0,7,705', '开源项目问卷收集', 'M', 'FormRenderOpenSourceSurvey', 'open_source_survey', 'dynamic-form/render', NULL, NULL, NULL, 1, 1, 'el-icon-Tickets', NULL, now(), now(), '{"formKey":"open_source_survey"}');
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70502, 705, '0,7,705', '活动报名表', 'M', 'FormRenderActivityRegistration', 'activity_registration', 'dynamic-form/render', NULL, NULL, NULL, 1, 2, 'el-icon-Calendar', NULL, now(), now(), '{"formKey":"activity_registration"}');
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (70503, 705, '0,7,705', '意见反馈', 'M', 'FormRenderFeedback', 'feedback', 'dynamic-form/render', NULL, NULL, NULL, 1, 3, 'el-icon-ChatLineRound', NULL, now(), now(), '{"formKey":"feedback"}');
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (805, 8, '0,8', '表单示例', 'C', '', 'sample', 'Layout', NULL, NULL, 1, 5, 'el-icon-Files', '/dynamic-form/sample/open_source_survey', now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80501, 805, '0,8,805', '开源项目问卷收集', 'M', 'FormRenderOpenSourceSurvey', 'open_source_survey', 'dynamic-form/render', NULL, NULL, 1, 1, 'el-icon-Tickets', NULL, now(), now(), '{"formKey":"open_source_survey"}');
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80502, 805, '0,8,805', '活动报名表', 'M', 'FormRenderActivityRegistration', 'activity_registration', 'dynamic-form/render', NULL, NULL, 1, 2, 'el-icon-Calendar', NULL, now(), now(), '{"formKey":"activity_registration"}');
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80503, 805, '0,8,805', '意见反馈', 'M', 'FormRenderFeedback', 'feedback', 'dynamic-form/render', NULL, NULL, 1, 3, 'el-icon-ChatLineRound', NULL, now(), now(), '{"formKey":"feedback"}');
-- ----------------------------
-- 系统管理员角色授权(role_id=2)
-- ----------------------------
-INSERT IGNORE INTO `sys_role_menu` VALUES (2, 7), (2, 701), (2, 70101), (2, 70102), (2, 70103), (2, 70104), (2, 702), (2, 70201), (2, 70202), (2, 703), (2, 704), (2, 705), (2, 70501), (2, 70502), (2, 70503);
+INSERT IGNORE INTO `sys_role_menu` VALUES (2, 8), (2, 801), (2, 80101), (2, 80102), (2, 80103), (2, 80104), (2, 802), (2, 80201), (2, 80202), (2, 803), (2, 804), (2, 805), (2, 80501), (2, 80502), (2, 80503);
-- ----------------------------
-- 预置示例表单(开箱即用,覆盖常用组件类型;ID 段 1001-1003,避开用户自建的自增 ID)
-- 已发布(status=1)+ 双入口预置:
--- 内嵌:菜单 70501/70502/70503(动态表单 > 表单示例),menu_id 已回填,发布向导"入口管理"可直接改挂载/角色
+-- 内嵌:菜单 80501/80502/80503(动态表单 > 表单示例),menu_id 已回填,发布向导"入口管理"可直接改挂载/角色
-- 外链:is_public=1,匿名访问 /f/{form_key}(问卷/报名/反馈天然适合对外收集)
-- ----------------------------
INSERT IGNORE INTO `form_definition` (`id`, `form_key`, `form_name`, `description`, `form_json`, `options_json`, `status`, `is_public`, `menu_id`, `version`, `create_time`, `update_time`, `create_by`, `update_by`, `is_deleted`) VALUES
-(1001, 'open_source_survey', '开源项目问卷收集', '面向社区的开源项目推荐与信息收集问卷', '[{"type":"input","field":"project_name","title":"项目名称","info":"","props":{"placeholder":"如 vue3-element-admin"},"validate":[{"required":true,"message":"请输入项目名称"}]},{"type":"input","field":"repo_url","title":"项目地址","info":"","props":{"placeholder":"https://github.com/xxx/xxx"},"validate":[{"required":true,"message":"请输入项目地址"}]},{"type":"select","field":"category","title":"项目领域","info":"","props":{},"options":[{"label":"前端","value":"frontend"},{"label":"后端","value":"backend"},{"label":"移动端","value":"mobile"},{"label":"AI 与大模型","value":"ai"},{"label":"开发工具","value":"tools"},{"label":"其他","value":"other"}],"validate":[{"required":true,"message":"请选择项目领域"}]},{"type":"radio","field":"license","title":"开源协议","info":"","options":[{"label":"MIT","value":"MIT"},{"label":"Apache-2.0","value":"Apache-2.0"},{"label":"GPLv3","value":"GPLv3"},{"label":"其他","value":"other"}]},{"type":"select","field":"stars","title":"Star 数量级","info":"","props":{},"options":[{"label":"100 以内","value":"lt100"},{"label":"100 ~ 1k","value":"100to1k"},{"label":"1k ~ 10k","value":"1kto10k"},{"label":"10k 以上","value":"gt10k"}]},{"type":"textarea","field":"recommend","title":"推荐理由","info":"","props":{"placeholder":"一句话说说它解决了什么问题"},"validate":[{"required":true,"message":"请填写推荐理由"}]},{"type":"input","field":"contact","title":"联系邮箱","info":"","props":{"placeholder":"选填,便于回访"}}]', '{"labelPosition":"right","size":"default"}', 1, 1, 70501, 1, now(), now(), NULL, NULL, 0),
-(1002, 'activity_registration', '活动报名表', '线下活动/聚会报名登记,含场次选择与用餐需求', '[{"type":"input","field":"name","title":"姓名","info":"","props":{},"validate":[{"required":true,"message":"请输入姓名"}]},{"type":"input","field":"phone","title":"手机号码","info":"","props":{"placeholder":"用于接收活动通知"},"validate":[{"required":true,"message":"请输入手机号码"}]},{"type":"radio","field":"gender","title":"性别","info":"","options":[{"label":"男","value":"male"},{"label":"女","value":"female"}]},{"type":"checkbox","field":"sessions","title":"参加场次","info":"","options":[{"label":"主论坛","value":"main"},{"label":"工作坊 A","value":"workshop_a"},{"label":"工作坊 B","value":"workshop_b"},{"label":"晚间交流","value":"evening"}]},{"type":"inputNumber","field":"attendees","title":"同行人数","info":"","props":{"min":0,"max":10}},{"type":"radio","field":"dinner","title":"用餐需求","info":"","options":[{"label":"正常","value":"normal"},{"label":"素食","value":"vegetarian"},{"label":"无需用餐","value":"none"}]},{"type":"textarea","field":"remark","title":"备注","info":"","props":{"placeholder":"选填"}}]', '{"labelPosition":"right","size":"default"}', 1, 1, 70502, 1, now(), now(), NULL, NULL, 0),
-(1003, 'feedback', '意见反馈', '产品/系统通用意见反馈表,含满意度评分', '[{"type":"select","field":"type","title":"反馈类型","info":"","props":{},"options":[{"label":"功能建议","value":"feature"},{"label":"问题报告","value":"bug"},{"label":"体验吐槽","value":"ux"},{"label":"其他","value":"other"}],"validate":[{"required":true,"message":"请选择反馈类型"}]},{"type":"rate","field":"rating","title":"整体满意度","info":"","props":{}},{"type":"textarea","field":"content","title":"反馈内容","info":"","props":{"placeholder":"请具体描述你的建议或遇到的问题"},"validate":[{"required":true,"message":"请填写反馈内容"}]},{"type":"input","field":"contact","title":"联系方式","info":"","props":{"placeholder":"选填,便于跟进回复"}}]', '{"labelPosition":"right","size":"default"}', 1, 1, 70503, 1, now(), now(), NULL, NULL, 0);
+(1001, 'open_source_survey', '开源项目问卷收集', '面向社区的开源项目推荐与信息收集问卷', '[{"type":"input","field":"project_name","title":"项目名称","info":"","props":{"placeholder":"如 vue3-element-admin"},"validate":[{"required":true,"message":"请输入项目名称"}]},{"type":"input","field":"repo_url","title":"项目地址","info":"","props":{"placeholder":"https://github.com/xxx/xxx"},"validate":[{"required":true,"message":"请输入项目地址"}]},{"type":"select","field":"category","title":"项目领域","info":"","props":{},"options":[{"label":"前端","value":"frontend"},{"label":"后端","value":"backend"},{"label":"移动端","value":"mobile"},{"label":"AI 与大模型","value":"ai"},{"label":"开发工具","value":"tools"},{"label":"其他","value":"other"}],"validate":[{"required":true,"message":"请选择项目领域"}]},{"type":"radio","field":"license","title":"开源协议","info":"","options":[{"label":"MIT","value":"MIT"},{"label":"Apache-2.0","value":"Apache-2.0"},{"label":"GPLv3","value":"GPLv3"},{"label":"其他","value":"other"}]},{"type":"select","field":"stars","title":"Star 数量级","info":"","props":{},"options":[{"label":"100 以内","value":"lt100"},{"label":"100 ~ 1k","value":"100to1k"},{"label":"1k ~ 10k","value":"1kto10k"},{"label":"10k 以上","value":"gt10k"}]},{"type":"textarea","field":"recommend","title":"推荐理由","info":"","props":{"placeholder":"一句话说说它解决了什么问题"},"validate":[{"required":true,"message":"请填写推荐理由"}]},{"type":"input","field":"contact","title":"联系邮箱","info":"","props":{"placeholder":"选填,便于回访"}}]', '{"labelPosition":"right","size":"default"}', 1, 1, 80501, 1, now(), now(), NULL, NULL, 0),
+(1002, 'activity_registration', '活动报名表', '线下活动/聚会报名登记,含场次选择与用餐需求', '[{"type":"input","field":"name","title":"姓名","info":"","props":{},"validate":[{"required":true,"message":"请输入姓名"}]},{"type":"input","field":"phone","title":"手机号码","info":"","props":{"placeholder":"用于接收活动通知"},"validate":[{"required":true,"message":"请输入手机号码"}]},{"type":"radio","field":"gender","title":"性别","info":"","options":[{"label":"男","value":"male"},{"label":"女","value":"female"}]},{"type":"checkbox","field":"sessions","title":"参加场次","info":"","options":[{"label":"主论坛","value":"main"},{"label":"工作坊 A","value":"workshop_a"},{"label":"工作坊 B","value":"workshop_b"},{"label":"晚间交流","value":"evening"}]},{"type":"inputNumber","field":"attendees","title":"同行人数","info":"","props":{"min":0,"max":10}},{"type":"radio","field":"dinner","title":"用餐需求","info":"","options":[{"label":"正常","value":"normal"},{"label":"素食","value":"vegetarian"},{"label":"无需用餐","value":"none"}]},{"type":"textarea","field":"remark","title":"备注","info":"","props":{"placeholder":"选填"}}]', '{"labelPosition":"right","size":"default"}', 1, 1, 80502, 1, now(), now(), NULL, NULL, 0),
+(1003, 'feedback', '意见反馈', '产品/系统通用意见反馈表,含满意度评分', '[{"type":"select","field":"type","title":"反馈类型","info":"","props":{},"options":[{"label":"功能建议","value":"feature"},{"label":"问题报告","value":"bug"},{"label":"体验吐槽","value":"ux"},{"label":"其他","value":"other"}],"validate":[{"required":true,"message":"请选择反馈类型"}]},{"type":"rate","field":"rating","title":"整体满意度","info":"","props":{}},{"type":"textarea","field":"content","title":"反馈内容","info":"","props":{"placeholder":"请具体描述你的建议或遇到的问题"},"validate":[{"required":true,"message":"请填写反馈内容"}]},{"type":"input","field":"contact","title":"联系方式","info":"","props":{"placeholder":"选填,便于跟进回复"}}]', '{"labelPosition":"right","size":"default"}', 1, 1, 80503, 1, now(), now(), NULL, NULL, 0);
-- 预置表单版本快照(已发布语义 = 固化 v1 规则,防止后续编辑定义导致历史数据回显漂移)
INSERT IGNORE INTO `form_snapshot` (`id`, `form_id`, `version`, `form_json`, `options_json`, `create_time`, `update_time`, `is_deleted`) VALUES
diff --git a/sql/extensions/workflow.sql b/sql/extensions/workflow.sql
index dd4f80ad..279e223d 100644
--- a/sql/extensions/workflow.sql
+++ b/sql/extensions/workflow.sql
@@ -1,8 +1,8 @@
# 工作流模块(可选扩展)
-# Copyright (c) 2021-present, youlai.tech
+# Copyright (c) 2026-present, youlai.tech
-- ----------------------------------------------------------------------------
--- 工作流模块增量脚本
+-- 工作流模块扩展脚本(全量执行,可重复运行)
-- 前置:先执行 ../youlai-admin.sql 与 dynamic-form.sql
-- 引擎表(ACT_*)由 Flowable 按 flowable.database-schema-update 策略自动创建,本脚本不含建表
-- ----------------------------------------------------------------------------
@@ -13,54 +13,31 @@ SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
--- 菜单(顶级 ID=8,顺延动态表单 ID=7)
+-- 菜单(顶级 ID=9,顺延动态表单 ID=8)
-- ----------------------------
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (8, 0, '0', '工作流', 'C', '', '/workflow', 'Layout', NULL, 1, NULL, 1, 8, 'el-icon-Connection', '/workflow/launch', now(), now(), '{\"badge\":\"NEW\"}');
-UPDATE `sys_menu` SET redirect = '/workflow/launch', update_time = now() WHERE id = 8 AND redirect = '/workflow/model';
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (801, 8, '0,8', '流程设计', 'M', 'WorkflowModel', 'model', 'workflow/model', NULL, NULL, NULL, 1, 1, 'el-icon-Coin', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80101, 801, '0,8,801', '流程查询', 'B', NULL, '', NULL, 'workflow:model:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80102, 801, '0,8,801', '流程新增', 'B', NULL, '', NULL, 'workflow:model:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80103, 801, '0,8,801', '流程修改', 'B', NULL, '', NULL, 'workflow:model:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80104, 801, '0,8,801', '流程删除', 'B', NULL, '', NULL, 'workflow:model:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80105, 801, '0,8,801', '流程发布', 'B', NULL, '', NULL, 'workflow:model:deploy', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80106, 801, '0,8,801', '流程重置', 'B', NULL, '', NULL, 'workflow:model:reset', NULL, NULL, 1, 6, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (9, 0, '0', '工作流', 'C', '', '/workflow', 'Layout', NULL, NULL, 1, 9, 'el-icon-Connection', '/workflow/launch', now(), now(), '{\"badge\":\"NEW\"}');
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (901, 9, '0,9', '流程设计', 'M', 'WorkflowModel', 'model', 'workflow/model', NULL, NULL, 1, 1, 'el-icon-Coin', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90101, 901, '0,9,901', '流程查询', 'B', NULL, '', NULL, 'workflow:model:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90102, 901, '0,9,901', '流程新增', 'B', NULL, '', NULL, 'workflow:model:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90103, 901, '0,9,901', '流程修改', 'B', NULL, '', NULL, 'workflow:model:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90104, 901, '0,9,901', '流程删除', 'B', NULL, '', NULL, 'workflow:model:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90105, 901, '0,9,901', '流程发布', 'B', NULL, '', NULL, 'workflow:model:deploy', NULL, 1, 5, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90106, 901, '0,9,901', '流程重置', 'B', NULL, '', NULL, 'workflow:model:reset', NULL, 1, 6, '', NULL, now(), now(), NULL);
-- 流程启停:启用开关走 definition:update,删除复用 model:delete
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80107, 801, '0,8,801', '流程启停', 'B', NULL, '', NULL, 'workflow:definition:update', NULL, NULL, 1, 7, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90107, 901, '0,9,901', '流程启停', 'B', NULL, '', NULL, 'workflow:definition:update', NULL, 1, 7, '', NULL, now(), now(), NULL);
-- 流程设计器(隐藏菜单,列表"设计"按钮跳转)
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (802, 8, '0,8', '流程设计器', 'M', 'WorkflowDesigner', 'designer', 'workflow/designer', NULL, NULL, 1, 0, 2, '', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (902, 9, '0,9', '流程设计器', 'M', 'WorkflowDesigner', 'designer', 'workflow/designer', NULL, 1, 0, 2, '', NULL, now(), now(), NULL);
--- ----------------------------
--- 存量环境迁移(幂等可重复执行)
--- 1) 菜单重命名:INSERT IGNORE 不覆盖已有行,改名需显式 UPDATE
--- 2) 「流程发布」并入「流程设计」,删除 803 系菜单及角色关联
--- ----------------------------
-UPDATE `sys_menu` SET name = '流程设计', update_time = now() WHERE id = 801 AND name = '流程模型';
-UPDATE `sys_menu` SET name = '流程查询', update_time = now() WHERE id = 80101 AND name = '模型查询';
-UPDATE `sys_menu` SET name = '流程新增', update_time = now() WHERE id = 80102 AND name = '模型新增';
-UPDATE `sys_menu` SET name = '流程修改', update_time = now() WHERE id = 80103 AND name = '模型修改';
-UPDATE `sys_menu` SET name = '流程删除', update_time = now() WHERE id = 80104 AND name = '模型删除';
-UPDATE `sys_menu` SET name = '流程发布', update_time = now() WHERE id = 80105 AND name IN ('模型部署', '流程部署');
-UPDATE `sys_menu` SET name = '流程重置', update_time = now() WHERE id = 80106 AND name = '模型重置';
-UPDATE `sys_menu` SET name = '流程启停', update_time = now() WHERE id = 80107 AND name IN ('发布停用', '流程停用');
--- 先迁移 803 授权到 80107 再删菜单,避免角色丢失启停能力
-INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`)
-SELECT rm.role_id, 80107 FROM `sys_role_menu` rm WHERE rm.menu_id = 80302;
-DELETE FROM `sys_role_menu` WHERE menu_id IN (803, 80301, 80302, 80303, 80108);
-DELETE FROM `sys_menu` WHERE id IN (803, 80301, 80302, 80303, 80108);
-- 发起/审批中心仅登录校验,不设按钮权限
-- 审批中心 = 待办/已办/我发起 三个页签
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (804, 8, '0,8', '发起流程', 'M', 'WorkflowLaunch', 'launch', 'workflow/launch', NULL, NULL, NULL, 1, 4, 'el-icon-Promotion', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (805, 8, '0,8', '审批中心', 'M', 'WorkflowTask', 'task', 'workflow/task/index', NULL, NULL, NULL, 1, 5, 'el-icon-Stamp', NULL, now(), now(), NULL);
-INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (80501, 805, '0,8,805', '终止流程', 'B', NULL, '', NULL, 'workflow:instance:terminate', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
--- 存量环境迁移:805 升级为「审批中心」,806/807 并入 805
-UPDATE `sys_menu` SET name = '审批中心', route_name = 'WorkflowTask', route_path = 'task', component = 'workflow/task/index', icon = 'el-icon-Stamp', update_time = now() WHERE id = 805 AND component = 'workflow/instance';
-DELETE FROM `sys_role_menu` WHERE menu_id IN (806, 807);
-DELETE FROM `sys_menu` WHERE id IN (806, 807);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (904, 9, '0,9', '发起流程', 'M', 'WorkflowLaunch', 'launch', 'workflow/launch', NULL, NULL, 1, 4, 'el-icon-Promotion', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (905, 9, '0,9', '审批中心', 'M', 'WorkflowTask', 'task', 'workflow/task/index', NULL, NULL, 1, 5, 'el-icon-Stamp', NULL, now(), now(), NULL);
+INSERT IGNORE INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (90501, 905, '0,9,905', '终止流程', 'B', NULL, '', NULL, 'workflow:instance:terminate', NULL, 1, 1, '', NULL, now(), now(), NULL);
-- ----------------------------
-- 系统管理员角色授权(role_id=2)
-- ----------------------------
-INSERT IGNORE INTO `sys_role_menu` VALUES (2, 8), (2, 801), (2, 80101), (2, 80102), (2, 80103), (2, 80104), (2, 80105), (2, 80106), (2, 80107), (2, 802), (2, 804), (2, 805), (2, 80501);
+INSERT IGNORE INTO `sys_role_menu` VALUES (2, 9), (2, 901), (2, 90101), (2, 90102), (2, 90103), (2, 90104), (2, 90105), (2, 90106), (2, 90107), (2, 902), (2, 904), (2, 905), (2, 90501);
-- ----------------------------
-- 演示角色与用户(ID 段 8-10,顺延 youlai-admin.sql 预置 1-7;密码 123456)
@@ -79,12 +56,12 @@ INSERT IGNORE INTO `sys_user_role` VALUES (8, 8), (9, 9), (10, 10);
-- ----------------------------
-- 演示角色的工作流菜单授权(不含流程设计等管理菜单)
-- ----------------------------
-INSERT IGNORE INTO `sys_role_menu` VALUES (4, 8), (4, 804), (4, 805);
-INSERT IGNORE INTO `sys_role_menu` VALUES (5, 8), (5, 804), (5, 805);
-INSERT IGNORE INTO `sys_role_menu` VALUES (6, 8), (6, 804), (6, 805);
-INSERT IGNORE INTO `sys_role_menu` VALUES (8, 8), (8, 804), (8, 805);
-INSERT IGNORE INTO `sys_role_menu` VALUES (9, 8), (9, 804), (9, 805);
-INSERT IGNORE INTO `sys_role_menu` VALUES (10, 8), (10, 804), (10, 805);
+INSERT IGNORE INTO `sys_role_menu` VALUES (4, 9), (4, 904), (4, 905);
+INSERT IGNORE INTO `sys_role_menu` VALUES (5, 9), (5, 904), (5, 905);
+INSERT IGNORE INTO `sys_role_menu` VALUES (6, 9), (6, 904), (6, 905);
+INSERT IGNORE INTO `sys_role_menu` VALUES (8, 9), (8, 904), (8, 905);
+INSERT IGNORE INTO `sys_role_menu` VALUES (9, 9), (9, 904), (9, 905);
+INSERT IGNORE INTO `sys_role_menu` VALUES (10, 9), (10, 904), (10, 905);
-- ----------------------------
-- 演示流程的发起表单(依赖动态表单扩展)
@@ -97,11 +74,6 @@ INSERT IGNORE INTO `form_definition` (`id`, `form_key`, `form_name`, `descriptio
(1006, 'purchase_apply', '采购申请', '演示流程"采购申请"的发起表单,金额超过 1 万元流转总经理审批,通过后回退发起人执行采购', '[{"type":"input","field":"item_name","title":"采购物品","info":"","props":{"placeholder":"请输入采购物品名称"},"validate":[{"required":true,"message":"请填写采购物品"}]},{"type":"inputNumber","field":"quantity","title":"采购数量","info":"","props":{"min":1,"precision":0},"validate":[{"required":true,"message":"请输入采购数量"}]},{"type":"inputNumber","field":"amount","title":"预估金额(元)","info":"","props":{"min":0,"precision":2},"validate":[{"required":true,"message":"请输入预估金额"}]},{"type":"textarea","field":"reason","title":"采购事由","info":"","props":{"placeholder":"请简要说明采购背景与用途"},"validate":[{"required":true,"message":"请填写采购事由"}]}]', '{"labelPosition":"right","size":"default"}', 1, 0, 'workflow', NULL, 1, now(), now(), NULL, NULL, 0),
(1007, 'seal_apply', '用章申请', '演示流程"用章申请"的发起表单,部门领导与行政两级审批', '[{"type":"select","field":"seal_type","title":"印章类型","info":"","props":{},"options":[{"label":"公章","value":"official"},{"label":"合同章","value":"contract"},{"label":"财务章","value":"finance"},{"label":"法人章","value":"legal"}],"validate":[{"required":true,"message":"请选择印章类型"}]},{"type":"datePicker","field":"usage_date","title":"用章日期","info":"","props":{"valueFormat":"YYYY-MM-DD"},"validate":[{"required":true,"message":"请选择用章日期"}]},{"type":"textarea","field":"usage","title":"用章事由","info":"","props":{"placeholder":"请说明用章文件及事由"},"validate":[{"required":true,"message":"请填写用章事由"}]}]', '{"labelPosition":"right","size":"default"}', 1, 0, 'workflow', NULL, 1, now(), now(), NULL, NULL, 0);
--- 按 form_key 幂等纠正 category,确保设计器能检索到全部演示表单
-UPDATE `form_definition` SET category = 'workflow'
-WHERE form_key IN ('leave_apply', 'expense_apply', 'purchase_apply', 'seal_apply')
- AND category <> 'workflow';
-
-- 预置表单版本快照(固化 v1 规则,防止编辑定义后历史数据回显漂移)
INSERT IGNORE INTO `form_snapshot` (`id`, `form_id`, `version`, `form_json`, `options_json`, `create_time`, `update_time`, `is_deleted`) VALUES
(1004, 1004, 1, (SELECT `form_json` FROM `form_definition` WHERE `id` = 1004), (SELECT `options_json` FROM `form_definition` WHERE `id` = 1004), now(), now(), 0),
diff --git a/sql/youlai-admin.sql b/sql/youlai-admin.sql
index 1295cafb..9e58450d 100644
--- a/sql/youlai-admin.sql
+++ b/sql/youlai-admin.sql
@@ -50,25 +50,25 @@ INSERT INTO `sys_dept` VALUES (3, '测试部门', 'QA001', 1, '0,1', 1, 1, 2, NU
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict` (
- `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键 ',
- `dict_code` varchar(50) COMMENT '类型编码',
- `name` varchar(50) COMMENT '类型名称',
- `status` tinyint(1) DEFAULT '0' COMMENT '状态(0:正常;1:禁用)',
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `dict_code` varchar(50) COMMENT '字典编码',
+ `name` varchar(50) COMMENT '字典名称',
+ `status` tinyint(1) DEFAULT '0' COMMENT '状态(1-启用 0-停用)',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
- `is_deleted` tinyint DEFAULT '0' COMMENT '是否删除(1-删除,0-未删除)',
+ `is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_dict_code` (`dict_code`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典类型表';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='字典表';
-- ----------------------------
-- Records of sys_dict
-- ----------------------------
-INSERT INTO `sys_dict` VALUES (1, 'gender', '性别', 1, NULL, now() , 1,now(), 1,0);
-INSERT INTO `sys_dict` VALUES (2, 'notice_type', '通知类型', 1, NULL, now(), 1,now(), 1,0);
-INSERT INTO `sys_dict` VALUES (3, 'notice_level', '通知级别', 1, NULL, now(), 1,now(), 1,0);
+INSERT INTO `sys_dict` VALUES (1, 'gender', '性别', 1, NULL, now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict` VALUES (2, 'notice_type', '通知类型', 1, NULL, now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict` VALUES (3, 'notice_level', '通知级别', 1, NULL, now(), 1, now(), 1, 0);
-- ----------------------------
@@ -77,35 +77,37 @@ INSERT INTO `sys_dict` VALUES (3, 'notice_level', '通知级别', 1, NULL, now()
DROP TABLE IF EXISTS `sys_dict_item`;
CREATE TABLE `sys_dict_item` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
- `dict_code` varchar(50) COMMENT '关联字典编码,与sys_dict表中的dict_code对应',
+ `dict_code` varchar(50) COMMENT '字典编码,与 sys_dict.dict_code 对应',
`value` varchar(50) COMMENT '字典项值',
`label` varchar(100) COMMENT '字典项标签',
- `tag_type` varchar(50) COMMENT '标签类型,用于前端样式展示(如success、warning等)',
- `status` tinyint DEFAULT '0' COMMENT '状态(1-正常,0-禁用)',
+ `tag_type` varchar(50) COMMENT '标签样式(primary-主要 success-成功 warning-警告 danger-危险 info-信息,空为无样式)',
+ `status` tinyint DEFAULT '0' COMMENT '状态(1-启用 0-停用)',
`sort` int DEFAULT '0' COMMENT '排序',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
- PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典项表';
+ `is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除(1-已删除 0-未删除)',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `idx_dict_code` (`dict_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='字典项表';
-- ----------------------------
-- Records of sys_dict_item
-- ----------------------------
-INSERT INTO `sys_dict_item` VALUES (1, 'gender', '1', '男', 'primary', 1, 1, NULL, now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (2, 'gender', '2', '女', 'danger', 1, 2, NULL, now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (3, 'gender', '0', '保密', 'info', 1, 3, NULL, now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (4, 'notice_type', '1', '系统升级', 'success', 1, 1, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (5, 'notice_type', '2', '系统维护', 'primary', 1, 2, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (6, 'notice_type', '3', '安全警告', 'danger', 1, 3, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (7, 'notice_type', '4', '假期通知', 'success', 1, 4, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (8, 'notice_type', '5', '公司新闻', 'primary', 1, 5, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (9, 'notice_type', '99', '其他', 'info', 1, 99, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (10, 'notice_level', 'L', '低', 'info', 1, 1, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (11, 'notice_level', 'M', '中', 'warning', 1, 2, '', now(), 1,now(),1);
-INSERT INTO `sys_dict_item` VALUES (12, 'notice_level', 'H', '高', 'danger', 1, 3, '', now(), 1,now(),1);
+INSERT INTO `sys_dict_item` VALUES (1, 'gender', '1', '男', 'primary', 1, 1, NULL, now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (2, 'gender', '2', '女', 'danger', 1, 2, NULL, now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (3, 'gender', '0', '保密', 'info', 1, 3, NULL, now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (4, 'notice_type', '1', '系统升级', 'success', 1, 1, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (5, 'notice_type', '2', '系统维护', 'primary', 1, 2, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (6, 'notice_type', '3', '安全警告', 'danger', 1, 3, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (7, 'notice_type', '4', '假期通知', 'success', 1, 4, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (8, 'notice_type', '5', '公司新闻', 'primary', 1, 5, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (9, 'notice_type', '99', '其他', 'info', 1, 99, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (10, 'notice_level', 'L', '低', 'info', 1, 1, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (11, 'notice_level', 'M', '中', 'warning', 1, 2, '', now(), 1, now(), 1, 0);
+INSERT INTO `sys_dict_item` VALUES (12, 'notice_level', 'H', '高', 'danger', 1, 3, '', now(), 1, now(), 1, 0);
-- ----------------------------
-- Table structure for sys_menu
@@ -122,7 +124,6 @@ CREATE TABLE `sys_menu` (
`component` varchar(128) COMMENT '组件路径(组件页面完整路径,相对于 src/views/,缺省后缀 .vue)',
`external_url` varchar(512) COMMENT '外链地址',
`perm` varchar(128) COMMENT '【按钮】权限标识',
- `always_show` tinyint DEFAULT 0 COMMENT '【目录】只有一个子路由是否始终显示(1-是 0-否)',
`keep_alive` tinyint DEFAULT 0 COMMENT '【菜单】是否开启页面缓存(1-是 0-否)',
`visible` tinyint(1) DEFAULT 1 COMMENT '显示状态(1-显示 0-隐藏)',
`sort` int DEFAULT 0 COMMENT '排序',
@@ -137,127 +138,130 @@ CREATE TABLE `sys_menu` (
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
--- 顶级菜单(基础段 ID=sort 1-6):系统管理(1)/代码生成(2)/通用组件(3)/多级菜单(4)/路由参数(5)/项目资源(6)
--- 扩展段 7-99 由 sql/extensions/ 脚本占用:动态表单(7,dynamic-form.sql)/工作流(8,workflow.sql),新扩展模块从 9 领号顺延
+-- 顶级菜单(基础段 ID=sort 1-7):系统管理(1)/数据大屏(2)/代码生成(3)/通用组件(4)/多级菜单(5)/路由示例(6)/项目资源(7)
+-- 扩展段 8-99 由 sql/extensions/ 脚本占用:动态表单(8,dynamic-form.sql)/工作流(9,workflow.sql),新扩展模块从 10 领号顺延
-- ID 规则:子 ID = 父 ID × 100 + 两位序号(如 10101 即用户管理 101 的第 1 个按钮),tree_path 与 ID 同构可机器校验
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (1, 0, '0', '系统管理', 'C', '', '/system', 'Layout', NULL, NULL, NULL, 1, 1, 'system', '/system/user', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (2, 0, '0', '代码生成', 'C', '', '/codegen', 'Layout', NULL, NULL, NULL, 1, 2, 'code', '/codegen/index', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (6, 0, '0', '项目资源', 'C', '', '/resource', 'Layout', NULL, NULL, NULL, 1, 6, 'document', '/resource/practice-doc', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (1, 0, '0', '系统管理', 'C', '', '/system', 'Layout', NULL, NULL, 1, 1, 'system', '/system/user', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (2, 0, '0', '数据大屏', 'C', '', '/screen', 'Layout', NULL, NULL, 1, 2, 'el-icon-Monitor', '/screen/system', now(), now(), NULL);
+-- 数据大屏示例(keep_alive 关,每次进入重新加载)
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (201, 2, '0,2', '系统运营大屏', 'M', 'ScreenSystem', 'system', 'demo/screen/system/index', NULL, 0, 1, 1, 'el-icon-DataLine', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (202, 2, '0,2', '设备监控大屏', 'M', 'ScreenDevice', 'device', 'demo/screen/device/index', NULL, 0, 1, 2, 'el-icon-Cpu', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (203, 2, '0,2', '电商销售大屏', 'M', 'ScreenEcommerce', 'ecommerce', 'demo/screen/ecommerce/index', NULL, 0, 1, 3, 'el-icon-ShoppingCart', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (3, 0, '0', '代码生成', 'M', 'Codegen', '/codegen', 'codegen/index', NULL, 1, 1, 3, 'code', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (7, 0, '0', '项目资源', 'C', '', '/resource', 'Layout', NULL, NULL, 1, 7, 'document', '/resource/practice-doc', now(), now(), NULL);
-- 系统管理
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (101, 1, '0,1', '用户管理', 'M', 'User', 'user', 'system/user/index', NULL, NULL, 1, 1, 1, 'el-icon-User', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10101, 101, '0,1,101', '用户查询', 'B', NULL, '', NULL, 'sys:user:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10102, 101, '0,1,101', '用户新增', 'B', NULL, '', NULL, 'sys:user:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10103, 101, '0,1,101', '用户编辑', 'B', NULL, '', NULL, 'sys:user:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10104, 101, '0,1,101', '用户删除', 'B', NULL, '', NULL, 'sys:user:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10105, 101, '0,1,101', '重置密码', 'B', NULL, '', NULL, 'sys:user:reset-password', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10106, 101, '0,1,101', '用户导入', 'B', NULL, '', NULL, 'sys:user:import', NULL, NULL, 1, 6, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10107, 101, '0,1,101', '用户导出', 'B', NULL, '', NULL, 'sys:user:export', NULL, NULL, 1, 7, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (101, 1, '0,1', '用户管理', 'M', 'User', 'user', 'system/user/index', NULL, 1, 1, 1, 'el-icon-User', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10101, 101, '0,1,101', '用户查询', 'B', NULL, '', NULL, 'sys:user:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10102, 101, '0,1,101', '用户新增', 'B', NULL, '', NULL, 'sys:user:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10103, 101, '0,1,101', '用户编辑', 'B', NULL, '', NULL, 'sys:user:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10104, 101, '0,1,101', '用户删除', 'B', NULL, '', NULL, 'sys:user:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10105, 101, '0,1,101', '重置密码', 'B', NULL, '', NULL, 'sys:user:reset-password', NULL, 1, 5, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10106, 101, '0,1,101', '用户导入', 'B', NULL, '', NULL, 'sys:user:import', NULL, 1, 6, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10107, 101, '0,1,101', '用户导出', 'B', NULL, '', NULL, 'sys:user:export', NULL, 1, 7, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (102, 1, '0,1', '角色管理', 'M', 'Role', 'role', 'system/role/index', NULL, NULL, 1, 1, 2, 'role', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10201, 102, '0,1,102', '角色查询', 'B', NULL, '', NULL, 'sys:role:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10202, 102, '0,1,102', '角色新增', 'B', NULL, '', NULL, 'sys:role:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10203, 102, '0,1,102', '角色编辑', 'B', NULL, '', NULL, 'sys:role:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10204, 102, '0,1,102', '角色删除', 'B', NULL, '', NULL, 'sys:role:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10205, 102, '0,1,102', '角色分配权限', 'B', NULL, '', NULL, 'sys:role:assign', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (102, 1, '0,1', '角色管理', 'M', 'Role', 'role', 'system/role/index', NULL, 1, 1, 2, 'role', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10201, 102, '0,1,102', '角色查询', 'B', NULL, '', NULL, 'sys:role:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10202, 102, '0,1,102', '角色新增', 'B', NULL, '', NULL, 'sys:role:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10203, 102, '0,1,102', '角色编辑', 'B', NULL, '', NULL, 'sys:role:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10204, 102, '0,1,102', '角色删除', 'B', NULL, '', NULL, 'sys:role:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10205, 102, '0,1,102', '角色分配权限', 'B', NULL, '', NULL, 'sys:role:assign', NULL, 1, 5, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (103, 1, '0,1', '菜单管理', 'M', 'SysMenu', 'menu', 'system/menu/index', NULL, NULL, 1, 1, 3, 'menu', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10301, 103, '0,1,103', '菜单查询', 'B', NULL, '', NULL, 'sys:menu:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10302, 103, '0,1,103', '菜单新增', 'B', NULL, '', NULL, 'sys:menu:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10303, 103, '0,1,103', '菜单编辑', 'B', NULL, '', NULL, 'sys:menu:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10304, 103, '0,1,103', '菜单删除', 'B', NULL, '', NULL, 'sys:menu:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (103, 1, '0,1', '菜单管理', 'M', 'SysMenu', 'menu', 'system/menu/index', NULL, 1, 1, 3, 'menu', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10301, 103, '0,1,103', '菜单查询', 'B', NULL, '', NULL, 'sys:menu:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10302, 103, '0,1,103', '菜单新增', 'B', NULL, '', NULL, 'sys:menu:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10303, 103, '0,1,103', '菜单编辑', 'B', NULL, '', NULL, 'sys:menu:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10304, 103, '0,1,103', '菜单删除', 'B', NULL, '', NULL, 'sys:menu:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (104, 1, '0,1', '部门管理', 'M', 'Dept', 'dept', 'system/dept/index', NULL, NULL, 1, 1, 4, 'tree', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10401, 104, '0,1,104', '部门查询', 'B', NULL, '', NULL, 'sys:dept:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10402, 104, '0,1,104', '部门新增', 'B', NULL, '', NULL, 'sys:dept:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10403, 104, '0,1,104', '部门编辑', 'B', NULL, '', NULL, 'sys:dept:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10404, 104, '0,1,104', '部门删除', 'B', NULL, '', NULL, 'sys:dept:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (104, 1, '0,1', '部门管理', 'M', 'Dept', 'dept', 'system/dept/index', NULL, 1, 1, 4, 'tree', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10401, 104, '0,1,104', '部门查询', 'B', NULL, '', NULL, 'sys:dept:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10402, 104, '0,1,104', '部门新增', 'B', NULL, '', NULL, 'sys:dept:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10403, 104, '0,1,104', '部门编辑', 'B', NULL, '', NULL, 'sys:dept:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10404, 104, '0,1,104', '部门删除', 'B', NULL, '', NULL, 'sys:dept:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (105, 1, '0,1', '字典管理', 'M', 'Dict', 'dict', 'system/dict/index', NULL, NULL, 1, 1, 5, 'dict', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10501, 105, '0,1,105', '字典查询', 'B', NULL, '', NULL, 'sys:dict:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10502, 105, '0,1,105', '字典新增', 'B', NULL, '', NULL, 'sys:dict:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10503, 105, '0,1,105', '字典编辑', 'B', NULL, '', NULL, 'sys:dict:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10504, 105, '0,1,105', '字典删除', 'B', NULL, '', NULL, 'sys:dict:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (105, 1, '0,1', '字典管理', 'M', 'Dict', 'dict', 'system/dict/index', NULL, 1, 1, 5, 'dict', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10501, 105, '0,1,105', '字典查询', 'B', NULL, '', NULL, 'sys:dict:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10502, 105, '0,1,105', '字典新增', 'B', NULL, '', NULL, 'sys:dict:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10503, 105, '0,1,105', '字典编辑', 'B', NULL, '', NULL, 'sys:dict:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10504, 105, '0,1,105', '字典删除', 'B', NULL, '', NULL, 'sys:dict:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
+-- 字典项已并入字典管理页(左右分栏同屏),不设独立菜单,仅保留按钮权限
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10505, 105, '0,1,105', '字典项查询', 'B', NULL, '', NULL, 'sys:dict-item:list', NULL, 1, 5, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10506, 105, '0,1,105', '字典项新增', 'B', NULL, '', NULL, 'sys:dict-item:create', NULL, 1, 6, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10507, 105, '0,1,105', '字典项编辑', 'B', NULL, '', NULL, 'sys:dict-item:update', NULL, 1, 7, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10508, 105, '0,1,105', '字典项删除', 'B', NULL, '', NULL, 'sys:dict-item:delete', NULL, 1, 8, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (106, 1, '0,1', '字典项', 'M', 'DictItem', 'dict-item', 'system/dict/dict-item', NULL, 0, 1, 0, 6, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10601, 106, '0,1,106', '字典项查询', 'B', NULL, '', NULL, 'sys:dict-item:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10602, 106, '0,1,106', '字典项新增', 'B', NULL, '', NULL, 'sys:dict-item:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10603, 106, '0,1,106', '字典项编辑', 'B', NULL, '', NULL, 'sys:dict-item:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10604, 106, '0,1,106', '字典项删除', 'B', NULL, '', NULL, 'sys:dict-item:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (107, 1, '0,1', '系统日志', 'M', 'Log', 'log', 'system/log/index', NULL, 1, 1, 7, 'document', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10701, 107, '0,1,107', '日志查询', 'B', NULL, '', NULL, 'sys:log:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (107, 1, '0,1', '系统日志', 'M', 'Log', 'log', 'system/log/index', NULL, 0, 1, 1, 7, 'document', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10701, 107, '0,1,107', '日志查询', 'B', NULL, '', NULL, 'sys:log:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (108, 1, '0,1', '系统配置', 'M', 'Config', 'config', 'system/config/index', NULL, 1, 1, 8, 'setting', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10801, 108, '0,1,108', '系统配置查询', 'B', NULL, '', NULL, 'sys:config:list', 1, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10802, 108, '0,1,108', '系统配置新增', 'B', NULL, '', NULL, 'sys:config:create', 1, 1, 2, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10803, 108, '0,1,108', '系统配置修改', 'B', NULL, '', NULL, 'sys:config:update', 1, 1, 3, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10804, 108, '0,1,108', '系统配置删除', 'B', NULL, '', NULL, 'sys:config:delete', 1, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10805, 108, '0,1,108', '系统配置刷新', 'B', NULL, '', NULL, 'sys:config:refresh', 1, 1, 5, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (108, 1, '0,1', '系统配置', 'M', 'Config', 'config', 'system/config/index', NULL, 0, 1, 1, 8, 'setting', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10801, 108, '0,1,108', '系统配置查询', 'B', NULL, '', NULL, 'sys:config:list', 0, 1, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10802, 108, '0,1,108', '系统配置新增', 'B', NULL, '', NULL, 'sys:config:create', 0, 1, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10803, 108, '0,1,108', '系统配置修改', 'B', NULL, '', NULL, 'sys:config:update', 0, 1, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10804, 108, '0,1,108', '系统配置删除', 'B', NULL, '', NULL, 'sys:config:delete', 0, 1, 1, 4, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10805, 108, '0,1,108', '系统配置刷新', 'B', NULL, '', NULL, 'sys:config:refresh', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
-
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (109, 1, '0,1', '通知公告', 'M', 'Notice', 'notice', 'system/notice/index', NULL, NULL, NULL, 1, 9, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10901, 109, '0,1,109', '通知查询', 'B', NULL, '', NULL, 'sys:notice:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10902, 109, '0,1,109', '通知新增', 'B', NULL, '', NULL, 'sys:notice:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10903, 109, '0,1,109', '通知编辑', 'B', NULL, '', NULL, 'sys:notice:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10904, 109, '0,1,109', '通知删除', 'B', NULL, '', NULL, 'sys:notice:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10905, 109, '0,1,109', '通知发布', 'B', NULL, '', NULL, 'sys:notice:publish', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10906, 109, '0,1,109', '通知撤回', 'B', NULL, '', NULL, 'sys:notice:revoke', 0, 1, 1, 6, '', NULL, now(), now(), NULL);
-
--- 代码生成
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (201, 2, '0,2', '代码生成', 'M', 'Codegen', 'index', 'codegen/index', NULL, NULL, 1, 1, 1, 'code', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (109, 1, '0,1', '通知公告', 'M', 'Notice', 'notice', 'system/notice/index', NULL, NULL, 1, 9, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10901, 109, '0,1,109', '通知查询', 'B', NULL, '', NULL, 'sys:notice:list', NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10902, 109, '0,1,109', '通知新增', 'B', NULL, '', NULL, 'sys:notice:create', NULL, 1, 2, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10903, 109, '0,1,109', '通知编辑', 'B', NULL, '', NULL, 'sys:notice:update', NULL, 1, 3, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10904, 109, '0,1,109', '通知删除', 'B', NULL, '', NULL, 'sys:notice:delete', NULL, 1, 4, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10905, 109, '0,1,109', '通知发布', 'B', NULL, '', NULL, 'sys:notice:publish', 1, 1, 5, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (10906, 109, '0,1,109', '通知撤回', 'B', NULL, '', NULL, 'sys:notice:revoke', 1, 1, 6, '', NULL, now(), now(), NULL);
-- 项目资源(原"项目文档";范围扩展为文档 + 接口 + 源码仓库的资源导航)
-- 实战文档双形态置顶(内嵌 iframe + 外链新标签),直观演示系统对两种外链打开方式的支持
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (601, 6, '0,6', '实战文档(内嵌)', 'E', 'PracticeDoc', 'practice-doc', 'iframe', 'https://juejin.cn/post/7228990409909108793', NULL, NULL, 1, 1, 1, 'el-icon-Document', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (602, 6, '0,6', '实战文档(外链)', 'E', NULL, NULL, NULL, 'https://juejin.cn/post/7228990409909108793', NULL, NULL, NULL, 1, 2, 'el-icon-Link', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (603, 6, '0,6', '接口文档', 'E', 'Apifox', 'apifox', 'iframe', 'https://www.apifox.cn/apidoc/shared-195e783f-4d85-4235-a038-eec696de4ea5', NULL, NULL, 1, 1, 3, 'api', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (604, 6, '0,6', '后端文档', 'E', NULL, NULL, NULL, 'https://youlai.blog.csdn.net/article/details/145178880', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (605, 6, '0,6', '移动端文档', 'E', NULL, NULL, NULL, 'https://youlai.blog.csdn.net/article/details/143222890', NULL, NULL, NULL, 1, 5, 'document', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (606, 6, '0,6', '前端仓库', 'E', NULL, NULL, NULL, 'https://gitee.com/youlaiorg/vue3-element-admin', NULL, NULL, NULL, 1, 6, 'gitee', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (607, 6, '0,6', '后端仓库', 'E', NULL, NULL, NULL, 'https://gitee.com/youlaiorg/youlai-boot', NULL, NULL, NULL, 1, 7, 'gitee', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (608, 6, '0,6', '移动端仓库', 'E', NULL, NULL, NULL, 'https://gitee.com/youlaiorg/vue-uniapp-template', NULL, NULL, NULL, 1, 8, 'gitee', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (701, 7, '0,7', '实战文档(内嵌)', 'E', 'PracticeDoc', 'practice-doc', 'iframe', 'https://juejin.cn/post/7228990409909108793', NULL, 1, 1, 1, 'el-icon-Document', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (702, 7, '0,7', '实战文档(外链)', 'E', NULL, NULL, NULL, 'https://juejin.cn/post/7228990409909108793', NULL, NULL, 1, 2, 'el-icon-Link', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (703, 7, '0,7', '接口文档', 'E', 'Apifox', 'apifox', 'iframe', 'https://www.apifox.cn/apidoc/shared-195e783f-4d85-4235-a038-eec696de4ea5', NULL, 1, 1, 3, 'api', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (704, 7, '0,7', '后端文档', 'E', NULL, NULL, NULL, 'https://youlai.blog.csdn.net/article/details/145178880', NULL, NULL, 1, 4, 'document', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (705, 7, '0,7', '移动端文档', 'E', NULL, NULL, NULL, 'https://youlai.blog.csdn.net/article/details/143222890', NULL, NULL, 1, 5, 'document', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (706, 7, '0,7', '前端仓库', 'E', NULL, NULL, NULL, 'https://gitee.com/youlaiorg/vue3-element-admin', NULL, NULL, 1, 6, 'gitee', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (707, 7, '0,7', '后端仓库', 'E', NULL, NULL, NULL, 'https://gitee.com/youlaiorg/youlai-boot', NULL, NULL, 1, 7, 'gitee', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `external_url`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (708, 7, '0,7', '移动端仓库', 'E', NULL, NULL, NULL, 'https://gitee.com/youlaiorg/vue-uniapp-template', NULL, NULL, 1, 8, 'gitee', '', now(), now(), NULL);
--- ============================ 通用组件(顶级 ID=3) ============================
+-- ============================ 通用组件(顶级 ID=4) ============================
-- 前端模板招牌能力聚合,独立顶级便于访客沉淀组件库;按用途三分组:基础/表单/表格
-- 物理目录 views/demo/component(基础与表单)与 views/demo/table(表格)一一对应
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (3, 0, '0', '通用组件', 'C', '', '/component', 'Layout', NULL, NULL, NULL, 1, 3, 'el-icon-SetUp', '/component/basic/icon-demo', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (4, 0, '0', '通用组件', 'C', '', '/component', 'Layout', NULL, NULL, 1, 4, 'el-icon-SetUp', '/component/basic/icon-demo', now(), now(), NULL);
-- 基础组件(不依附表单/表格场景的通用 UI 能力)
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (301, 3, '0,3', '基础组件', 'C', '', 'basic', 'Layout', NULL, 1, NULL, 1, 1, 'el-icon-Brush', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30101, 301, '0,3,301', 'Icons', 'M', 'IconDemo', 'icon-demo', 'demo/component/icons', NULL, NULL, 1, 1, 1, 'el-icon-Notification', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30102, 301, '0,3,301', '拖拽组件', 'M', 'Drag', 'drag', 'demo/component/drag', NULL, NULL, NULL, 1, 2, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30103, 301, '0,3,301', '滚动文本', 'M', 'TextScroll', 'text-scroll', 'demo/component/text-scroll', NULL, NULL, NULL, 1, 3, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30104, 301, '0,3,301', '字典实时同步', 'M', 'DictSync', 'dict-sync', 'demo/component/dict-sync', NULL, NULL, NULL, 1, 4, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (401, 4, '0,4', '基础组件', 'C', '', 'basic', 'Layout', NULL, NULL, 1, 1, 'el-icon-Brush', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40101, 401, '0,4,401', 'Icons', 'M', 'IconDemo', 'icon-demo', 'demo/component/icons', NULL, 1, 1, 1, 'el-icon-Notification', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40102, 401, '0,4,401', '拖拽组件', 'M', 'Drag', 'drag', 'demo/component/drag', NULL, NULL, 1, 2, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40103, 401, '0,4,401', '滚动文本', 'M', 'TextScroll', 'text-scroll', 'demo/component/text-scroll', NULL, NULL, 1, 3, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40104, 401, '0,4,401', '字典实时同步', 'M', 'DictSync', 'dict-sync', 'demo/component/dict-sync', NULL, NULL, 1, 4, '', '', now(), now(), NULL);
-- 表单组件(数据录入场景)
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (302, 3, '0,3', '表单组件', 'C', '', 'form', 'Layout', NULL, 1, NULL, 1, 2, 'el-icon-EditPen', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30201, 302, '0,3,302', '富文本编辑器', 'M', 'WangEditor', 'wang-editor', 'demo/component/wang-editor', NULL, NULL, 1, 1, 1, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30202, 302, '0,3,302', '图片上传', 'M', 'Upload', 'upload', 'demo/component/upload', NULL, NULL, 1, 1, 2, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30203, 302, '0,3,302', '图标选择器', 'M', 'DemoIconSelect', 'icon-select', 'demo/component/icon-select', NULL, NULL, 1, 1, 3, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30204, 302, '0,3,302', '字典组件', 'M', 'DictDemo', 'dict-demo', 'demo/component/dictionary', NULL, NULL, 1, 1, 4, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30205, 302, '0,3,302', '列表选择器', 'M', 'TableSelect', 'table-select', 'demo/component/table-select/index', NULL, NULL, 1, 1, 5, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (402, 4, '0,4', '表单组件', 'C', '', 'form', 'Layout', NULL, NULL, 1, 2, 'el-icon-EditPen', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40201, 402, '0,4,402', '富文本编辑器', 'M', 'WangEditor', 'wang-editor', 'demo/component/wang-editor', NULL, 1, 1, 1, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40202, 402, '0,4,402', '图片上传', 'M', 'Upload', 'upload', 'demo/component/upload', NULL, 1, 1, 2, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40203, 402, '0,4,402', '图标选择器', 'M', 'DemoIconSelect', 'icon-select', 'demo/component/icon-select', NULL, 1, 1, 3, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40204, 402, '0,4,402', '字典组件', 'M', 'DictDemo', 'dict-demo', 'demo/component/dictionary', NULL, 1, 1, 4, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40205, 402, '0,4,402', '列表选择器', 'M', 'TableSelect', 'table-select', 'demo/component/table-select/index', NULL, 1, 1, 5, '', '', now(), now(), NULL);
-- 表格组件(数据展示场景,物理目录 views/demo/table 与之对应)
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (303, 3, '0,3', '表格组件', 'C', '', 'table', 'Layout', NULL, 1, NULL, 1, 3, 'el-icon-Grid', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30301, 303, '0,3,303', '增删改查', 'M', 'Curd', 'curd', 'demo/table/curd/index', NULL, NULL, 1, 1, 1, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30302, 303, '0,3,303', 'CURD单文件', 'M', 'CurdSingle', 'curd-single', 'demo/table/curd-single', NULL, NULL, 1, 1, 2, 'el-icon-Reading', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30303, 303, '0,3,303', 'VxeTable', 'M', 'VxeTable', 'vxe-table', 'demo/table/vxe-table/index', NULL, NULL, 1, 1, 3, 'el-icon-MagicStick', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (30304, 303, '0,3,303', '自适应表格操作列', 'M', 'AutoOperationColumn', 'operation-column', 'demo/table/auto-operation-column', NULL, NULL, 1, 1, 4, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (403, 4, '0,4', '表格组件', 'C', '', 'table', 'Layout', NULL, NULL, 1, 3, 'el-icon-Grid', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40301, 403, '0,4,403', '增删改查', 'M', 'Crud', 'crud', 'demo/table/crud/index', NULL, 1, 1, 1, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40302, 403, '0,4,403', 'CRUD单文件', 'M', 'CrudSingle', 'crud-single', 'demo/table/crud-single', NULL, 1, 1, 2, 'el-icon-Reading', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40303, 403, '0,4,403', 'VxeTable', 'M', 'VxeTable', 'vxe-table', 'demo/table/vxe-table/index', NULL, 1, 1, 3, 'el-icon-MagicStick', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40304, 403, '0,4,403', '自适应表格操作列', 'M', 'AutoOperationColumn', 'operation-column', 'demo/table/auto-operation-column', NULL, 1, 1, 4, '', '', now(), now(), NULL);
--- ============================ 多级菜单(顶级 ID=4) ============================
+-- ============================ 多级菜单(顶级 ID=5) ============================
-- 独立顶级,直观演示菜单驱动动态路由的嵌套能力,避免折叠导致顶级菜单不可见
-- 物理目录 views/demo/route/multi-level 与之对应
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (4, 0, '0', '多级菜单', 'C', '', '/multi-level', 'Layout', NULL, 1, NULL, 1, 4, 'el-icon-Guide', '/multi-level/level-one/level-two/level-three-a', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (401, 4, '0,4', '一级菜单', 'C', '', 'level-one', 'Layout', NULL, 1, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (40101, 401, '0,4,401', '二级菜单', 'C', '', 'level-two', 'Layout', NULL, 0, NULL, 1, 1, '', NULL, now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (4010101, 40101, '0,4,401,40101', '三级菜单 A', 'M', 'MultiLevelLevelThreeA', 'level-three-a', 'demo/route/multi-level/level-one/level-two/level-three-a/index', NULL, 0, 1, 1, 1, '', '', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (4010102, 40101, '0,4,401,40101', '三级菜单 B', 'M', 'MultiLevelLevelThreeB', 'level-three-b', 'demo/route/multi-level/level-one/level-two/level-three-b/index', NULL, 0, 1, 1, 2, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (5, 0, '0', '多级菜单', 'C', '', '/multi-level', 'Layout', NULL, NULL, 1, 5, 'el-icon-Guide', '/multi-level/level-one/level-two/level-three-a', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (501, 5, '0,5', '一级菜单', 'C', '', 'level-one', 'Layout', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (50101, 501, '0,5,501', '二级菜单', 'C', '', 'level-two', 'Layout', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (5010101, 50101, '0,5,501,50101', '三级菜单 A', 'M', 'MultiLevelLevelThreeA', 'level-three-a', 'demo/route/multi-level/level-one/level-two/level-three-a/index', NULL, 1, 1, 1, '', '', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (5010102, 50101, '0,5,501,50101', '三级菜单 B', 'M', 'MultiLevelLevelThreeB', 'level-three-b', 'demo/route/multi-level/level-one/level-two/level-three-b/index', NULL, 1, 1, 2, '', '', now(), now(), NULL);
--- ============================ 路由参数(顶级 ID=5) ============================
--- 同一页面按 params 区分菜单入口的传参演示;详情页缓存经增删改查详情 /detail/:id 进入,无独立菜单
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (5, 0, '0', '路由参数', 'C', '', '/route-param', 'Layout', NULL, 1, NULL, 1, 5, 'el-icon-Share', '/route-param/route-param-type1', now(), now(), NULL);
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (501, 5, '0,5', '路由参数(type=1)', 'M', 'RouteParamType1', 'route-param-type1', 'demo/route/route-param', NULL, 0, 1, 1, 1, 'el-icon-Star', NULL, now(), now(), '{\"type\": \"1\"}');
-INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (502, 5, '0,5', '路由参数(type=2)', 'M', 'RouteParamType2', 'route-param-type2', 'demo/route/route-param', NULL, 0, 1, 1, 2, 'el-icon-StarFilled', NULL, now(), now(), '{\"type\": \"2\"}');
+-- ============================ 路由示例(顶级 ID=6) ============================
+-- 同一页面按 params 区分菜单入口的传参演示;缓存与跳转为独立页面,跳转目标 /detail/:id 走常量路由不占菜单
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (6, 0, '0', '路由示例', 'C', '', '/route-example', 'Layout', NULL, NULL, 1, 6, 'el-icon-Share', '/route-example/params-type-1', now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (601, 6, '0,6', '路由参数(type=1)', 'M', 'RouteParamType1', 'params-type-1', 'demo/route/route-param', NULL, 1, 1, 1, 'el-icon-Star', NULL, now(), now(), '{\"type\": \"1\"}');
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (602, 6, '0,6', '路由参数(type=2)', 'M', 'RouteParamType2', 'params-type-2', 'demo/route/route-param', NULL, 1, 1, 2, 'el-icon-StarFilled', NULL, now(), now(), '{\"type\": \"2\"}');
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (603, 6, '0,6', '页面缓存', 'M', 'RouteCache', 'cache', 'demo/route/cache/index', NULL, 1, 1, 3, 'el-icon-Stopwatch', NULL, now(), now(), NULL);
+INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) VALUES (604, 6, '0,6', '页面跳转', 'M', 'RouteNavigate', 'navigate', 'demo/route/navigate/index', NULL, 1, 1, 4, 'el-icon-Position', NULL, now(), now(), NULL);
-- ----------------------------
-- Table structure for sys_role
@@ -318,15 +322,14 @@ INSERT IGNORE INTO `sys_role_dept` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_dept` VALUES (7, 2);
-- ============================================
--- 系统管理员角色菜单权限(role_id=2,基础段顶级 1-6 全量)
-INSERT INTO `sys_role_menu` VALUES (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6);
+-- 系统管理员角色菜单权限(role_id=2,基础段顶级 1-7 全量)
+INSERT INTO `sys_role_menu` VALUES (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7);
-- 系统管理
INSERT INTO `sys_role_menu` VALUES (2, 101), (2, 10101), (2, 10102), (2, 10103), (2, 10104), (2, 10105), (2, 10106), (2, 10107);
INSERT INTO `sys_role_menu` VALUES (2, 102), (2, 10201), (2, 10202), (2, 10203), (2, 10204), (2, 10205);
INSERT INTO `sys_role_menu` VALUES (2, 103), (2, 10301), (2, 10302), (2, 10303), (2, 10304);
INSERT INTO `sys_role_menu` VALUES (2, 104), (2, 10401), (2, 10402), (2, 10403), (2, 10404);
-INSERT INTO `sys_role_menu` VALUES (2, 105), (2, 10501), (2, 10502), (2, 10503), (2, 10504);
-INSERT INTO `sys_role_menu` VALUES (2, 106), (2, 10601), (2, 10602), (2, 10603), (2, 10604);
+INSERT INTO `sys_role_menu` VALUES (2, 105), (2, 10501), (2, 10502), (2, 10503), (2, 10504), (2, 10505), (2, 10506), (2, 10507), (2, 10508);
INSERT INTO `sys_role_menu` VALUES (2, 107), (2, 10701);
INSERT INTO `sys_role_menu` VALUES (2, 108), (2, 10801), (2, 10802), (2, 10803), (2, 10804), (2, 10805);
INSERT INTO `sys_role_menu` VALUES (2, 109), (2, 10901), (2, 10902), (2, 10903), (2, 10904), (2, 10905), (2, 10906);
@@ -346,17 +349,17 @@ INSERT IGNORE INTO `sys_role_menu` VALUES (6, 102), (6, 10201), (6, 10202), (6,
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 101), (7, 10101), (7, 10102), (7, 10103), (7, 10104), (7, 10105), (7, 10106), (7, 10107);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 102), (7, 10201), (7, 10202), (7, 10203), (7, 10204), (7, 10205);
--- 代码生成
-INSERT INTO `sys_role_menu` VALUES (2, 201);
-- 项目资源
-INSERT INTO `sys_role_menu` VALUES (2, 601), (2, 602), (2, 603), (2, 604), (2, 605), (2, 606), (2, 607), (2, 608);
--- 通用组件(301 基础 / 302 表单 / 303 表格)
-INSERT INTO `sys_role_menu` VALUES (2, 301), (2, 30101), (2, 30102), (2, 30103), (2, 30104);
-INSERT INTO `sys_role_menu` VALUES (2, 302), (2, 30201), (2, 30202), (2, 30203), (2, 30204), (2, 30205);
-INSERT INTO `sys_role_menu` VALUES (2, 303), (2, 30301), (2, 30302), (2, 30303), (2, 30304);
--- 多级菜单 / 路由参数
-INSERT INTO `sys_role_menu` VALUES (2, 401), (2, 40101), (2, 4010101), (2, 4010102);
-INSERT INTO `sys_role_menu` VALUES (2, 501), (2, 502);
+INSERT INTO `sys_role_menu` VALUES (2, 701), (2, 702), (2, 703), (2, 704), (2, 705), (2, 706), (2, 707), (2, 708);
+-- 通用组件(401 基础 / 402 表单 / 403 表格)
+INSERT INTO `sys_role_menu` VALUES (2, 401), (2, 40101), (2, 40102), (2, 40103), (2, 40104);
+INSERT INTO `sys_role_menu` VALUES (2, 402), (2, 40201), (2, 40202), (2, 40203), (2, 40204), (2, 40205);
+INSERT INTO `sys_role_menu` VALUES (2, 403), (2, 40301), (2, 40302), (2, 40303), (2, 40304);
+-- 多级菜单 / 路由示例
+INSERT INTO `sys_role_menu` VALUES (2, 501), (2, 50101), (2, 5010101), (2, 5010102);
+INSERT INTO `sys_role_menu` VALUES (2, 601), (2, 602), (2, 603), (2, 604);
+-- 数据大屏(三个示例子菜单)
+INSERT INTO `sys_role_menu` VALUES (2, 201), (2, 202), (2, 203);
-- ----------------------------
-- Table structure for sys_user
@@ -458,7 +461,7 @@ CREATE TABLE `gen_table` (
`author` varchar(50) NOT NULL COMMENT '作者',
`parent_menu_id` bigint COMMENT '上级菜单ID,对应sys_menu的id ',
`remove_table_prefix` varchar(20) COMMENT '要移除的表前缀,如: sys_',
- `page_type` varchar(20) COMMENT '页面类型(classic|curd)',
+ `page_type` varchar(20) COMMENT '页面类型(classic|crud)',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint(4) DEFAULT 0 COMMENT '是否删除',
diff --git a/src/main/java/com/youlai/boot/common/constant/RedisConstants.java b/src/main/java/com/youlai/boot/common/constant/RedisConstants.java
index 3950c39a..7ddd87b6 100644
--- a/src/main/java/com/youlai/boot/common/constant/RedisConstants.java
+++ b/src/main/java/com/youlai/boot/common/constant/RedisConstants.java
@@ -13,7 +13,7 @@ public interface RedisConstants {
*/
interface RateLimit {
/** 接口级限流 Key(示例:rate_limit:api:{user}:{uri}) */
- String API = "rate_limit:{}:{}:{}";
+ String API = "rate_limit:api:{}:{}";
/** IP 全局限流 Key(示例:rate_limit:ip:192.168.1.100) */
String IP = "rate_limit:ip:{}";
diff --git a/src/main/java/com/youlai/boot/module/form/controller/FormController.java b/src/main/java/com/youlai/boot/module/form/controller/FormController.java
index fb65cff1..552fd6aa 100644
--- a/src/main/java/com/youlai/boot/module/form/controller/FormController.java
+++ b/src/main/java/com/youlai/boot/module/form/controller/FormController.java
@@ -10,6 +10,7 @@ import com.youlai.boot.common.result.Result;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.model.Option;
import com.youlai.boot.framework.web.util.IPUtils;
+import com.youlai.boot.module.form.model.form.FormAiGenerateForm;
import com.youlai.boot.module.form.model.form.FormDefinitionForm;
import com.youlai.boot.module.form.model.form.FormMenuForm;
import com.youlai.boot.module.form.model.query.FormDataPageQuery;
@@ -240,4 +241,14 @@ public class FormController {
return Result.success();
}
+ //---------------------------------------------------
+ // AI 辅助接口(未开启 AI 时调用返回业务异常)
+ //---------------------------------------------------
+
+ @Operation(summary = "AI 生成表单规则")
+ @PostMapping("/ai-generate")
+ public Result generateRule(@RequestBody @Valid FormAiGenerateForm form) {
+ return Result.success(formDefinitionService.aiGenerateRule(form.getDescription()));
+ }
+
}
diff --git a/src/main/java/com/youlai/boot/module/form/model/form/FormAiGenerateForm.java b/src/main/java/com/youlai/boot/module/form/model/form/FormAiGenerateForm.java
new file mode 100644
index 00000000..bc63a4d7
--- /dev/null
+++ b/src/main/java/com/youlai/boot/module/form/model/form/FormAiGenerateForm.java
@@ -0,0 +1,20 @@
+package com.youlai.boot.module.form.model.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * AI 生成表单入参
+ *
+ * @author Ray.Hao
+ * @since 0.1.0
+ */
+@Schema(description = "AI 生成表单入参")
+@Data
+public class FormAiGenerateForm {
+
+ @Schema(description = "表单需求描述", example = "请假申请:姓名、请假类型、起止日期、请假事由(必填)、附件")
+ @NotBlank(message = "需求描述不能为空")
+ private String description;
+}
diff --git a/src/main/java/com/youlai/boot/module/form/service/FormDefinitionService.java b/src/main/java/com/youlai/boot/module/form/service/FormDefinitionService.java
index d33af54d..65124da9 100644
--- a/src/main/java/com/youlai/boot/module/form/service/FormDefinitionService.java
+++ b/src/main/java/com/youlai/boot/module/form/service/FormDefinitionService.java
@@ -137,4 +137,12 @@ public interface FormDefinitionService {
*/
List
+ *
+ * @param forms 待删除表单
+ */
+ private void checkProcessReferences(List forms) {
+ // 工作流未启用时无流程服务,跳过校验
+ WorkflowModelService workflowModelService = workflowModelServiceProvider.getIfAvailable();
+ if (workflowModelService == null || forms.isEmpty()) {
+ return;
+ }
+ Map> references = workflowModelService.getFormReferences(
+ forms.stream().map(FormDefinition::getFormKey).collect(Collectors.toSet()));
+ List blocked = forms.stream()
+ .filter(form -> references.containsKey(form.getFormKey()))
+ .map(form -> StrUtil.format("表单【{}】被流程【{}】引用", form.getFormName(),
+ String.join("、", references.get(form.getFormKey()))))
+ .toList();
+ if (!blocked.isEmpty()) {
+ throw new BusinessException(String.join(";", blocked) + ",请先解除流程绑定后删除");
+ }
+ }
+
/**
* 发布表单
*
@@ -452,8 +491,6 @@ public class FormDefinitionServiceImpl extends ServiceImpl> rules = aiChatService.chatJsonList(
+ promptLoader.get(SYSTEM_PROMPT_PATH), buildAiUserPrompt(description));
+ return FormRuleValidator.validateRule(JSONUtil.toJsonStr(rules));
+ }
+
+ /**
+ * 构造用户提示词
+ *
+ * @param description 需求描述
+ * @return 用户提示词
+ */
+ private String buildAiUserPrompt(String description) {
+ return "需求描述:\n" + description;
+ }
+
}
diff --git a/src/main/java/com/youlai/boot/module/form/util/FormRuleValidator.java b/src/main/java/com/youlai/boot/module/form/util/FormRuleValidator.java
index 60834c36..9e27442b 100644
--- a/src/main/java/com/youlai/boot/module/form/util/FormRuleValidator.java
+++ b/src/main/java/com/youlai/boot/module/form/util/FormRuleValidator.java
@@ -1,5 +1,6 @@
package com.youlai.boot.module.form.util;
+import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.youlai.boot.common.exception.BusinessException;
@@ -59,6 +60,90 @@ public final class FormRuleValidator {
return filtered;
}
+ /**
+ * 校验表单规则结构(AI 生成或外部导入的规则先过这一层)
+ *
+ * @param formJson 表单规则(form-create rule 数组 JSON 字符串)
+ * @return 规则原文,便于链式使用
+ */
+ public static String validateRule(String formJson) {
+ JsonNode rules;
+ try {
+ rules = OBJECT_MAPPER.readTree(formJson);
+ } catch (Exception e) {
+ throw new BusinessException("表单规则解析失败");
+ }
+ if (!rules.isArray() || rules.isEmpty()) {
+ throw new BusinessException("表单规则格式非法:应为非空数组");
+ }
+ checkNodes(rules);
+ return formJson;
+ }
+
+ /**
+ * 递归校验规则节点:每个节点必须有 type,非容器节点必须有 field
+ *
+ * @param nodes 规则节点数组
+ */
+ private static void checkNodes(JsonNode nodes) {
+ for (JsonNode node : nodes) {
+ if (node == null || !node.isObject()) {
+ throw new BusinessException("表单规则格式非法:节点必须为对象");
+ }
+ if (!node.path("type").isTextual()) {
+ throw new BusinessException("表单规则格式非法:节点缺少 type");
+ }
+ JsonNode children = node.get("children");
+ if (children != null && children.isArray() && !children.isEmpty()) {
+ checkNodes(children);
+ } else if (!node.path("field").isTextual()) {
+ throw new BusinessException("表单规则格式非法:字段节点缺少 field");
+ }
+ }
+ }
+
+ /**
+ * 解析规则里 field -> title 的映射(摘要、导出等场景把字段名换成中文标题)
+ *
+ * @param formJson 表单规则
+ * @return field -> title 映射,规则为空或解析失败时返回空 Map
+ */
+ public static Map resolveTitles(String formJson) {
+ Map titles = new LinkedHashMap<>();
+ if (StrUtil.isBlank(formJson)) {
+ return titles;
+ }
+ try {
+ collectTitles(OBJECT_MAPPER.readTree(formJson), titles);
+ } catch (Exception ignored) {
+ return titles;
+ }
+ return titles;
+ }
+
+ /**
+ * 递归收集 field 与 title
+ *
+ * @param nodes 规则节点数组
+ * @param titles 映射结果(累积写入)
+ */
+ private static void collectTitles(JsonNode nodes, Map titles) {
+ for (JsonNode node : nodes) {
+ if (node == null || !node.isObject()) {
+ continue;
+ }
+ JsonNode field = node.get("field");
+ JsonNode title = node.get("title");
+ if (field != null && field.isTextual() && title != null && title.isTextual()) {
+ titles.putIfAbsent(field.asText(), title.asText());
+ }
+ JsonNode children = node.get("children");
+ if (children != null && children.isArray()) {
+ collectTitles(children, titles);
+ }
+ }
+ }
+
/**
* 递归遍历规则节点:布局容器(row/col)自身无 field,其 children 递归收集
*
diff --git a/src/main/java/com/youlai/boot/module/workflow/controller/WorkflowTaskController.java b/src/main/java/com/youlai/boot/module/workflow/controller/WorkflowTaskController.java
index 68b3d893..f572b839 100644
--- a/src/main/java/com/youlai/boot/module/workflow/controller/WorkflowTaskController.java
+++ b/src/main/java/com/youlai/boot/module/workflow/controller/WorkflowTaskController.java
@@ -98,4 +98,14 @@ public class WorkflowTaskController {
return Result.success();
}
+ // AI 辅助:未开启 AI 时调用返回业务异常
+
+ @Operation(summary = "AI 生成任务审批摘要")
+ @PostMapping("/{taskId}/ai-summary")
+ public Result summarizeTask(
+ @Parameter(description = "任务ID") @PathVariable String taskId
+ ) {
+ return Result.success(workflowTaskService.aiSummarizeTask(taskId));
+ }
+
}
diff --git a/src/main/java/com/youlai/boot/module/workflow/service/WorkflowModelService.java b/src/main/java/com/youlai/boot/module/workflow/service/WorkflowModelService.java
index d3805c7e..e0ac1b06 100644
--- a/src/main/java/com/youlai/boot/module/workflow/service/WorkflowModelService.java
+++ b/src/main/java/com/youlai/boot/module/workflow/service/WorkflowModelService.java
@@ -7,10 +7,13 @@ import com.youlai.boot.module.workflow.model.query.WorkflowModelPageQuery;
import com.youlai.boot.module.workflow.model.vo.ModelVO;
import com.youlai.boot.module.workflow.model.vo.ModelXmlVO;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
- * 流程模型业务接口(设计器侧:模型 CRUD、XML 保存、部署)
+ * 流程模型业务接口(设计器侧:模型 CRUD、XML 保存、部署、表单引用查询)
*
* @author Ray.Hao
* @since 4.9.0
@@ -43,12 +46,20 @@ public interface WorkflowModelService {
boolean updateModel(String modelId, ModelForm formData);
/**
- * 删除流程模型(未部署的草稿模型;已部署定义不受影响)
+ * 删除流程模型(级联删除其全部发布版本;存在运行中实例时拦截)
*
* @param modelIds 模型ID集合
*/
void removeModels(List modelIds);
+ /**
+ * 查询引用指定表单的流程(已部署定义与模型草稿的表单绑定)
+ *
+ * @param formKeys 表单标识集合
+ * @return 表单标识 → 引用它的流程名称集合(无引用返回空 Map)
+ */
+ Map> getFormReferences(Collection formKeys);
+
/**
* 获取模型 BPMN XML(设计器回显)
*
diff --git a/src/main/java/com/youlai/boot/module/workflow/service/WorkflowTaskService.java b/src/main/java/com/youlai/boot/module/workflow/service/WorkflowTaskService.java
index bcffcdf3..22206e42 100644
--- a/src/main/java/com/youlai/boot/module/workflow/service/WorkflowTaskService.java
+++ b/src/main/java/com/youlai/boot/module/workflow/service/WorkflowTaskService.java
@@ -67,4 +67,12 @@ public interface WorkflowTaskService {
*/
void rejectTask(String taskId, RejectTaskForm formData);
+ /**
+ * 用 AI 汇总表单数据与审批记录,生成审批摘要
+ *
+ * @param taskId 任务ID
+ * @return 摘要文本
+ */
+ String aiSummarizeTask(String taskId);
+
}
diff --git a/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowModelServiceImpl.java b/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowModelServiceImpl.java
index 790b145f..5643ea94 100644
--- a/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowModelServiceImpl.java
+++ b/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowModelServiceImpl.java
@@ -1,5 +1,6 @@
package com.youlai.boot.module.workflow.service.impl;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
@@ -18,18 +19,26 @@ import com.youlai.boot.system.model.entity.Role;
import com.youlai.boot.system.service.RoleService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
+import org.flowable.engine.RuntimeService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
+import java.util.Collection;
import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.TreeSet;
import java.util.stream.Collectors;
/**
@@ -43,9 +52,11 @@ import java.util.stream.Collectors;
@ConditionalOnWorkflowEnabled
@Service
@RequiredArgsConstructor
+@Slf4j
public class WorkflowModelServiceImpl implements WorkflowModelService {
private final RepositoryService repositoryService;
+ private final RuntimeService runtimeService;
private final BpmnModelHelper bpmnModelHelper;
private final RoleService roleService;
@@ -193,22 +204,101 @@ public class WorkflowModelServiceImpl implements WorkflowModelService {
* 删除流程模型
*
* 「流程」对用户是整体:删除设计草稿时级联删除其全部发布版本(含运行实例与历史),
- * 避免残留孤儿定义导致列表出现无模型可管理的流程
+ * 避免残留孤儿定义导致列表出现无模型可管理的流程;有在办实例的流程先拦截,防止在办任务被静默抹除
*
* @param modelIds 模型ID集合
*/
@Override
+ @Transactional(rollbackFor = Exception.class)
public void removeModels(List modelIds) {
- modelIds.forEach(modelId -> {
- Model model = repositoryService.getModel(modelId);
- if (model == null) {
- return;
+ // 先全量校验再删除:批量删除中途抛错时,前面已删的模型无法回退
+ List models = modelIds.stream()
+ .map(repositoryService::getModel)
+ .filter(Objects::nonNull)
+ .toList();
+ models.forEach(this::checkNoRunningInstance);
+ models.forEach(this::removeModelAndDeployments);
+ }
+
+ /**
+ * 校验流程无运行中实例
+ *
+ * 级联删除会连同在办任务一并抹除,审批人无从知晓,故有在办实例时禁止删除
+ */
+ private void checkNoRunningInstance(Model model) {
+ long runningCount = runtimeService.createProcessInstanceQuery()
+ .processDefinitionKey(model.getKey())
+ .count();
+ if (runningCount > 0) {
+ throw new BusinessException("流程【" + model.getName() + "】存在 " + runningCount
+ + " 个运行中的实例,请先撤销或办结后再删除");
+ }
+ }
+
+ /**
+ * 删除模型的全部发布版本(级联流程定义、实例与历史)及设计草稿
+ */
+ private void removeModelAndDeployments(Model model) {
+ repositoryService.createDeploymentQuery()
+ .processDefinitionKey(model.getKey()).list()
+ .forEach(deployment -> repositoryService.deleteDeployment(deployment.getId(), true));
+ repositoryService.deleteModel(model.getId());
+ }
+
+ /**
+ * 查询引用指定表单的流程
+ *
+ * 引用写在 BPMN 的开始节点与用户任务节点(flowable:formKey);模型草稿虽未部署但已绑定表单,
+ * 一并计入:表单被删除后,流程发起或审批时无表单可渲染
+ *
+ * @param formKeys 表单标识集合
+ * @return 表单标识 → 引用它的流程名称集合(无引用返回空 Map)
+ */
+ @Override
+ public Map> getFormReferences(Collection formKeys) {
+ if (CollUtil.isEmpty(formKeys)) {
+ return Map.of();
+ }
+ Set targets = new HashSet<>(formKeys);
+ Map> references = new HashMap<>();
+
+ // 已部署定义:全部版本都算引用,旧版本的实例仍在流转
+ for (ProcessDefinition definition : repositoryService.createProcessDefinitionQuery().list()) {
+ Set referenced;
+ try {
+ referenced = bpmnModelHelper.getFormKeys(definition.getId());
+ } catch (Exception e) {
+ log.warn("流程定义 [{}] 表单引用解析失败,跳过校验", definition.getKey());
+ continue;
}
- repositoryService.createDeploymentQuery()
- .processDefinitionKey(model.getKey()).list()
- .forEach(deployment -> repositoryService.deleteDeployment(deployment.getId(), true));
- repositoryService.deleteModel(modelId);
- });
+ referenced.stream().filter(targets::contains)
+ .forEach(formKey -> recordReference(references, formKey, definition.getName()));
+ }
+
+ // 模型草稿:设计器里已绑定表单,删除后该模型发布即成无表单流程
+ for (Model model : repositoryService.createModelQuery().list()) {
+ byte[] source = repositoryService.getModelEditorSource(model.getId());
+ if (source == null || source.length == 0) {
+ continue;
+ }
+ Set referenced;
+ try {
+ referenced = bpmnModelHelper.getFormKeysFromXml(new String(source, StandardCharsets.UTF_8));
+ } catch (Exception e) {
+ log.warn("流程模型 [{}] 表单引用解析失败,跳过校验", model.getKey());
+ continue;
+ }
+ referenced.stream().filter(targets::contains)
+ .forEach(formKey -> recordReference(references, formKey, model.getName()));
+ }
+ return references;
+ }
+
+ /**
+ * 记录一条引用关系(同一表单被多个流程引用时累加,按流程名称排序保证提示稳定)
+ */
+ private void recordReference(Map> references, String formKey, String processName) {
+ references.computeIfAbsent(formKey, key -> new TreeSet<>()).add(processName);
}
/**
diff --git a/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowTaskServiceImpl.java b/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowTaskServiceImpl.java
index 455b8148..782d4bac 100644
--- a/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowTaskServiceImpl.java
+++ b/src/main/java/com/youlai/boot/module/workflow/service/impl/WorkflowTaskServiceImpl.java
@@ -1,15 +1,21 @@
package com.youlai.boot.module.workflow.service.impl;
+import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.module.form.model.vo.FormDataVO;
import com.youlai.boot.module.form.service.FormDataService;
+import com.youlai.boot.module.form.util.FormRuleValidator;
import com.youlai.boot.module.workflow.model.form.CompleteTaskForm;
import com.youlai.boot.module.workflow.model.form.RejectTaskForm;
import com.youlai.boot.module.workflow.model.query.WorkflowTaskPageQuery;
+import com.youlai.boot.module.workflow.model.vo.ApprovalHistoryVO;
import com.youlai.boot.module.workflow.model.vo.DoneTaskVO;
import com.youlai.boot.module.workflow.model.vo.RejectTargetVO;
import com.youlai.boot.module.workflow.model.vo.TaskDetailVO;
@@ -19,6 +25,8 @@ import com.youlai.boot.module.workflow.service.WorkflowTaskService;
import com.youlai.boot.module.workflow.util.ApprovalHistoryHelper;
import com.youlai.boot.module.workflow.util.BpmnModelHelper;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
+import com.youlai.boot.support.ai.AiChatService;
+import com.youlai.boot.support.ai.PromptLoader;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.identity.Authentication;
@@ -37,6 +45,7 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -58,6 +67,11 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class WorkflowTaskServiceImpl implements WorkflowTaskService {
+ /** 系统提示词名称,对应 resources/prompts 下的相对路径 */
+ private static final String SYSTEM_PROMPT_PATH = "workflow/summary.md";
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
private final TaskService taskService;
private final RuntimeService runtimeService;
private final HistoryService historyService;
@@ -66,6 +80,8 @@ public class WorkflowTaskServiceImpl implements WorkflowTaskService {
private final WorkflowDefinitionService workflowDefinitionService;
private final BpmnModelHelper bpmnModelHelper;
private final ApprovalHistoryHelper approvalHistoryHelper;
+ private final AiChatService aiChatService;
+ private final PromptLoader promptLoader;
/**
* 待办任务分页列表
@@ -379,4 +395,72 @@ public class WorkflowTaskServiceImpl implements WorkflowTaskService {
.collect(Collectors.toMap(ProcessInstance::getId, instance -> instance));
}
+ //---------------------------------------------------
+ // AI 辅助:未开启 AI 时调用返回业务异常
+ //---------------------------------------------------
+
+ /**
+ * 用 AI 汇总表单数据与审批记录,生成审批摘要
+ *
+ * @param taskId 任务ID
+ * @return 摘要文本
+ */
+ @Override
+ public String aiSummarizeTask(String taskId) {
+ TaskDetailVO detail = getTaskDetail(taskId);
+ return aiChatService.chat(promptLoader.get(SYSTEM_PROMPT_PATH), buildAiUserPrompt(detail));
+ }
+
+ /**
+ * 构造用户提示词
+ *
+ * @param detail 任务详情
+ * @return 用户提示词
+ */
+ private String buildAiUserPrompt(TaskDetailVO detail) {
+ StringBuilder text = new StringBuilder();
+ text.append("流程名称:").append(StrUtil.blankToDefault(detail.getProcessName(), "未命名流程")).append("\n");
+ text.append("当前节点:").append(StrUtil.blankToDefault(detail.getTaskName(), "未命名节点")).append("\n");
+ text.append("表单数据:\n").append(buildAiFormDataText(detail));
+ if (CollectionUtil.isNotEmpty(detail.getHistory())) {
+ text.append("审批记录:\n");
+ for (ApprovalHistoryVO history : detail.getHistory()) {
+ text.append("- ")
+ .append(StrUtil.blankToDefault(history.getActivityName(), "环节"))
+ .append("(")
+ .append(StrUtil.blankToDefault(history.getAssignee(), "待处理"))
+ .append("):")
+ .append(StrUtil.blankToDefault(history.getComment(), "无意见"))
+ .append("\n");
+ }
+ }
+ return text.toString();
+ }
+
+ /**
+ * 把表单数据按 rule 的中文标题输出,便于模型理解字段含义
+ *
+ * @param detail 任务详情
+ * @return 逐行「标题:值」文本
+ */
+ private String buildAiFormDataText(TaskDetailVO detail) {
+ Map titles = FormRuleValidator.resolveTitles(detail.getFormJson());
+ if (StrUtil.isBlank(detail.getDataJson())) {
+ return "(无表单数据)\n";
+ }
+ StringBuilder text = new StringBuilder();
+ try {
+ JsonNode data = OBJECT_MAPPER.readTree(detail.getDataJson());
+ Iterator> fields = data.fields();
+ while (fields.hasNext()) {
+ Map.Entry entry = fields.next();
+ String title = titles.getOrDefault(entry.getKey(), entry.getKey());
+ text.append(title).append(":").append(entry.getValue().asText()).append("\n");
+ }
+ } catch (Exception e) {
+ throw new BusinessException("表单数据解析失败");
+ }
+ return text.toString();
+ }
+
}
diff --git a/src/main/java/com/youlai/boot/module/workflow/util/BpmnModelHelper.java b/src/main/java/com/youlai/boot/module/workflow/util/BpmnModelHelper.java
index 4d380c89..2a9a8868 100644
--- a/src/main/java/com/youlai/boot/module/workflow/util/BpmnModelHelper.java
+++ b/src/main/java/com/youlai/boot/module/workflow/util/BpmnModelHelper.java
@@ -18,6 +18,7 @@ import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -98,6 +99,50 @@ public class BpmnModelHelper {
.orElse(null);
}
+ /**
+ * 获取流程定义引用的全部表单标识(开始节点与用户任务节点,去重)
+ *
+ * @param processDefinitionId 流程定义ID
+ * @return formKey 集合,未绑定返回空集合
+ */
+ public Set getFormKeys(String processDefinitionId) {
+ Process process = getMainProcess(processDefinitionId);
+ if (process == null) {
+ return Set.of();
+ }
+ return extractFormKeys(process);
+ }
+
+ /**
+ * 提取 BPMN XML 引用的全部表单标识(开始节点与用户任务节点,去重)
+ *
+ * 供表单删除前校验使用:设计器草稿未部署,其引用不体现在流程定义里
+ *
+ * @param bpmnXml BPMN 2.0 XML 字符串
+ * @return formKey 集合,无引用返回空集合
+ */
+ public Set getFormKeysFromXml(String bpmnXml) {
+ BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(
+ () -> new ByteArrayInputStream(bpmnXml.getBytes(StandardCharsets.UTF_8)), false, true);
+ Process process = bpmnModel.getMainProcess();
+ if (process == null) {
+ throw new BusinessException("流程文件校验失败,未找到主流程");
+ }
+ return extractFormKeys(process);
+ }
+
+ /**
+ * 提取主流程中开始节点与用户任务节点引用的表单标识
+ */
+ private Set extractFormKeys(Process process) {
+ Set formKeys = new HashSet<>();
+ process.findFlowElementsOfType(StartEvent.class).stream()
+ .map(StartEvent::getFormKey).filter(StrUtil::isNotBlank).forEach(formKeys::add);
+ process.findFlowElementsOfType(UserTask.class).stream()
+ .map(UserTask::getFormKey).filter(StrUtil::isNotBlank).forEach(formKeys::add);
+ return formKeys;
+ }
+
/**
* 获取流程定义的 BPMN XML
*
diff --git a/src/main/java/com/youlai/boot/support/ai/AiChatService.java b/src/main/java/com/youlai/boot/support/ai/AiChatService.java
new file mode 100644
index 00000000..432200ba
--- /dev/null
+++ b/src/main/java/com/youlai/boot/support/ai/AiChatService.java
@@ -0,0 +1,123 @@
+package com.youlai.boot.support.ai;
+
+import cn.hutool.core.util.StrUtil;
+import com.youlai.boot.common.exception.BusinessException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * AI 对话服务
+ *
+ * 代码生成、动态表单、工作流的 AI 辅助功能统一走这里;对话客户端由 spring.ai 自动装配提供,
+ * 未开启时调用返回业务异常,不影响应用启动
+ *
+ * @author Ray.Hao
+ * @since 0.1.0
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class AiChatService {
+
+ private final ObjectProvider chatModelProvider;
+
+ private final ObjectProvider chatClientBuilderProvider;
+
+ /**
+ * 对话客户端,首次调用时构建
+ */
+ private ChatClient chatClient;
+
+ /**
+ * 发送提示词并返回模型输出
+ *
+ * @param systemPrompt 系统提示词(角色与约束)
+ * @param userPrompt 用户提示词(输入数据)
+ * @return 模型输出文本
+ */
+ public String chat(String systemPrompt, String userPrompt) {
+ return execute("chat", () -> {
+ String content = client().prompt().system(systemPrompt).user(userPrompt).call().content();
+ return StrUtil.blankToDefault(content, StrUtil.EMPTY).trim();
+ });
+ }
+
+ /**
+ * 发送提示词并映射为实体
+ *
+ * 实体结构不符时框架会携带校验错误自动重试,无需手工解析 JSON
+ *
+ * @param systemPrompt 系统提示词(角色与约束)
+ * @param userPrompt 用户提示词(输入数据)
+ * @param type 目标实体类型
+ * @return 映射后的实体
+ */
+ public T chatEntity(String systemPrompt, String userPrompt, Class type) {
+ return execute("chatEntity(" + type.getSimpleName() + ")", () -> client().prompt()
+ .system(systemPrompt).user(userPrompt).call()
+ .entity(type, spec -> spec.validateSchema()));
+ }
+
+ /**
+ * 发送提示词并映射为 JSON 数组
+ *
+ * 元素结构开放(如 form-create rule)时使用,产出后仍需业务侧校验
+ *
+ * @param systemPrompt 系统提示词(角色与约束)
+ * @param userPrompt 用户提示词(输入数据)
+ * @return JSON 数组
+ */
+ public List