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> listWorkflowFormOptions(); + /** + * 用 AI 把需求描述转成 form-create 规则,产出经结构校验后才返回 + * + * @param description 需求描述 + * @return 通过结构校验的 form-create rule JSON 字符串 + */ + String aiGenerateRule(String description); + } diff --git a/src/main/java/com/youlai/boot/module/form/service/impl/FormDefinitionServiceImpl.java b/src/main/java/com/youlai/boot/module/form/service/impl/FormDefinitionServiceImpl.java index e63d56b5..963f47bd 100644 --- a/src/main/java/com/youlai/boot/module/form/service/impl/FormDefinitionServiceImpl.java +++ b/src/main/java/com/youlai/boot/module/form/service/impl/FormDefinitionServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -28,6 +29,10 @@ import com.youlai.boot.module.form.model.vo.FormDefinitionPageVO; import com.youlai.boot.module.form.model.vo.FormMenuVO; import com.youlai.boot.module.form.model.vo.FormRenderVO; import com.youlai.boot.module.form.service.FormDefinitionService; +import com.youlai.boot.module.form.util.FormRuleValidator; +import com.youlai.boot.module.workflow.service.WorkflowModelService; +import com.youlai.boot.support.ai.AiChatService; +import com.youlai.boot.support.ai.PromptLoader; import com.youlai.boot.system.enums.MenuTypeEnum; import com.youlai.boot.system.model.entity.Menu; import com.youlai.boot.system.model.entity.RoleMenu; @@ -35,6 +40,7 @@ import com.youlai.boot.system.model.form.MenuForm; import com.youlai.boot.system.service.MenuService; import com.youlai.boot.system.service.RoleMenuService; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,6 +48,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -56,12 +63,18 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class FormDefinitionServiceImpl extends ServiceImpl implements FormDefinitionService { + /** 系统提示词名称,对应 resources/prompts 下的相对路径 */ + private static final String SYSTEM_PROMPT_PATH = "form/system.md"; + private final FormConverter formConverter; private final RedisTemplate redisTemplate; private final MenuService menuService; private final RoleMenuService roleMenuService; private final FormSnapshotMapper formSnapshotMapper; private final FormDataMapper formDataMapper; + private final ObjectProvider workflowModelServiceProvider; + private final AiChatService aiChatService; + private final PromptLoader promptLoader; /** 默认挂载目录名称:发布向导未显式选目录时自动使用/创建 */ private static final String DEFAULT_CATALOG_NAME = "表单中心"; @@ -181,7 +194,7 @@ public class FormDefinitionServiceImpl extends ServiceImpl级联清理关联数据,避免孤儿残留:生成的访问菜单(含其角色授权)、 + *

被流程引用的表单禁止删除;其余级联清理关联数据,避免孤儿残留:生成的访问菜单(含其角色授权)、 * 提交数据、版本快照一并删除

* * @param ids 表单ID列表 @@ -190,6 +203,7 @@ public class FormDefinitionServiceImpl extends ServiceImpl ids) { List forms = this.listByIds(ids); + this.checkProcessReferences(forms); this.removeByIds(ids); for (FormDefinition form : forms) { // 级联删除已生成的访问菜单(deleteMenu 内部同步清理其角色授权并刷新权限缓存) @@ -208,6 +222,31 @@ public class FormDefinitionServiceImpl extends ServiceImpl被引用的表单删除后,流程发起与审批时无表单可渲染,故在删除前整体拦截

+ * + * @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> chatJsonList(String systemPrompt, String userPrompt) { + return execute("chatJsonList", () -> client().prompt() + .system(systemPrompt).user(userPrompt).call() + .entity(new ParameterizedTypeReference>>() { + })); + } + + /** + * 获取对话客户端,首次调用时构建 + * + * @return 对话客户端 + */ + private synchronized ChatClient client() { + if (chatClient == null) { + // 未装配 ChatModel 时 Builder 也建不出来,先判断模型是否存在,避免抛出框架的 BeanCreationException + if (chatModelProvider.getIfAvailable() == null) { + throw new BusinessException( + "AI 功能未开启,请将 spring.ai.model.chat 置为 openai 并配置 spring.ai.openai.api-key"); + } + log.info("AI 对话客户端初始化"); + chatClient = chatClientBuilderProvider.getObject().build(); + } + return chatClient; + } + + /** + * 执行模型调用并记录开始、耗时与异常 + * + * @param operation 调用标识 + * @param call 模型调用 + * @return 调用结果 + */ + private T execute(String operation, Supplier call) { + log.info("AI 调用开始, {}", operation); + long start = System.currentTimeMillis(); + try { + T result = call.get(); + log.info("AI 调用完成, {} 耗时 {} ms", operation, System.currentTimeMillis() - start); + return result; + } catch (RuntimeException e) { + log.error("AI 调用失败, {} 耗时 {} ms", operation, System.currentTimeMillis() - start, e); + throw e; + } + } +} diff --git a/src/main/java/com/youlai/boot/support/ai/PromptLoader.java b/src/main/java/com/youlai/boot/support/ai/PromptLoader.java new file mode 100644 index 00000000..d54b3173 --- /dev/null +++ b/src/main/java/com/youlai/boot/support/ai/PromptLoader.java @@ -0,0 +1,126 @@ +package com.youlai.boot.support.ai; + +import cn.hutool.core.util.StrUtil; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.stereotype.Component; +import org.springframework.util.StreamUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 提示词加载器 + * + *

提示词文本统一放在类路径的 prompts 目录下,一个文件对应一段系统提示词, + * 应用启动时全部载入并缓存;文本中的 {{键}} 在渲染时替换为变量值

+ * + * @author Ray.Hao + * @since 0.1.0 + */ +@Slf4j +@Component +public class PromptLoader { + + /** + * 提示词文件位置 + */ + private static final String LOCATION_PATTERN = "classpath*:prompts/**/*.md"; + + /** + * 提示词目录,用于从资源地址截取提示词名称 + */ + private static final String DIRECTORY = "prompts/"; + + /** + * 占位符左标记 + */ + private static final String PLACEHOLDER_LEFT = "{{"; + + /** + * 占位符右标记 + */ + private static final String PLACEHOLDER_RIGHT = "}}"; + + /** + * 提示词内容,键为 prompts 下的相对路径 + */ + private final Map prompts = new ConcurrentHashMap<>(); + + /** + * 载入全部提示词文件,读取失败则应用启动失败 + */ + @PostConstruct + public void load() { + try { + Resource[] resources = new PathMatchingResourcePatternResolver().getResources(LOCATION_PATTERN); + for (Resource resource : resources) { + try (InputStream inputStream = resource.getInputStream()) { + String content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8).trim(); + prompts.put(resolveName(resource), content); + } + } + log.info("提示词载入完成, {} 个", prompts.size()); + } catch (IOException e) { + throw new IllegalStateException("提示词载入失败", e); + } + } + + /** + * 获取提示词原文 + * + * @param name 提示词名称,prompts 下的相对路径,如 codegen/system.md + * @return 提示词原文 + */ + public String get(String name) { + String prompt = prompts.get(name); + if (prompt == null) { + throw new IllegalStateException("提示词不存在:" + name); + } + return prompt; + } + + /** + * 获取提示词并替换占位符,替换后仍残留 {{ 的视为占位符漏传,直接抛出异常 + * + * @param name 提示词名称,prompts 下的相对路径 + * @param variables 变量值,键与文本中的 {{键}} 对应 + * @return 替换后的提示词 + */ + public String render(String name, Map variables) { + String prompt = get(name); + for (Map.Entry variable : variables.entrySet()) { + prompt = prompt.replace(PLACEHOLDER_LEFT + variable.getKey() + PLACEHOLDER_RIGHT, + StrUtil.nullToEmpty(variable.getValue())); + } + int index = prompt.indexOf(PLACEHOLDER_LEFT); + if (index >= 0) { + int end = prompt.indexOf(PLACEHOLDER_RIGHT, index); + String placeholder = end > index + ? prompt.substring(index, end + PLACEHOLDER_RIGHT.length()) + : prompt.substring(index); + throw new IllegalStateException("提示词 " + name + " 存在未替换的占位符:" + placeholder); + } + return prompt; + } + + /** + * 截取资源地址中 prompts 之后的路径作为提示词名称 + * + * @param resource 提示词资源 + * @return 提示词名称 + */ + private String resolveName(Resource resource) throws IOException { + String url = resource.getURL().toString(); + int index = url.lastIndexOf(DIRECTORY); + if (index < 0) { + throw new IllegalStateException("提示词路径异常:" + url); + } + return url.substring(index + DIRECTORY.length()); + } +} diff --git a/src/main/java/com/youlai/boot/system/controller/DictController.java b/src/main/java/com/youlai/boot/system/controller/DictController.java index 0d2f604e..a1f2d8c4 100644 --- a/src/main/java/com/youlai/boot/system/controller/DictController.java +++ b/src/main/java/com/youlai/boot/system/controller/DictController.java @@ -1,28 +1,28 @@ package com.youlai.boot.system.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.youlai.boot.common.annotation.Log; +import com.youlai.boot.common.enums.ActionTypeEnum; +import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.model.Option; import com.youlai.boot.common.result.PageResult; import com.youlai.boot.common.result.Result; -import com.youlai.boot.common.enums.ActionTypeEnum; -import com.youlai.boot.common.enums.LogModuleEnum; -import com.youlai.boot.system.model.form.DictItemForm; -import com.youlai.boot.system.model.query.DictItemQuery; -import com.youlai.boot.system.model.query.DictQuery; -import com.youlai.boot.system.model.vo.DictItemOptionVO; -import com.youlai.boot.system.model.vo.DictItemPageVO; -import com.youlai.boot.system.model.vo.DictPageVO; import com.youlai.boot.framework.web.repeatsubmit.RepeatSubmit; -import com.youlai.boot.system.model.form.DictForm; -import com.youlai.boot.common.annotation.Log; -import com.youlai.boot.system.service.DictItemService; -import com.youlai.boot.system.service.DictService; import com.youlai.boot.support.sse.SseService; import com.youlai.boot.support.sse.constant.SseTopics; import com.youlai.boot.system.model.dto.DictChangeDTO; +import com.youlai.boot.system.model.form.DictForm; +import com.youlai.boot.system.model.form.DictItemForm; +import com.youlai.boot.system.model.query.DictQuery; +import com.youlai.boot.system.model.query.DictItemQuery; +import com.youlai.boot.system.model.vo.DictItemOptionVO; +import com.youlai.boot.system.model.vo.DictItemPageVO; +import com.youlai.boot.system.model.vo.DictPageVO; +import com.youlai.boot.system.service.DictItemService; +import com.youlai.boot.system.service.DictService; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; @@ -34,8 +34,8 @@ import java.util.List; /** * 字典控制层 * - * @author Ray.Hao - * @since 2.9.0 + * @author Ray Hao + * @since 2022/10/12 */ @Tag(name = "06.字典接口") @RestController @@ -56,9 +56,6 @@ public class DictController { sseService.broadcast(SseTopics.DICT, new DictChangeDTO(dictCode)); } - //--------------------------------------------------- - // 字典相关接口 - //--------------------------------------------------- @Operation(summary = "字典分页列表") @GetMapping @Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.LIST) @@ -106,12 +103,12 @@ public class DictController { @Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.UPDATE) public Result updateDict( @PathVariable Long dictId, - @RequestBody @Valid DictForm dictForm + @RequestBody @Valid DictForm formData ) { - boolean status = dictService.updateDict(dictId, dictForm); + boolean status = dictService.updateDict(dictId, formData); // 发送字典更新通知 - if (status && dictForm.getDictCode() != null) { - sendDictChange(dictForm.getDictCode()); + if (status && formData.getDictCode() != null) { + sendDictChange(formData.getDictCode()); } return Result.judge(status); } @@ -130,20 +127,16 @@ public class DictController { // 发送字典删除通知 for (String dictCode : dictCodes) { - sendDictChange(dictCode); + sendDictChange(dictCode); } return Result.success(); } - - //--------------------------------------------------- - // 字典项相关接口 - //--------------------------------------------------- - @Operation(summary = "字典项分页列表") + @Operation(summary = "获取字典项分页列表") @GetMapping("/{dictCode}/items") - public PageResult getDictItemPage( - @PathVariable String dictCode, + public PageResult getDictItemsPage( + @Parameter(description = "字典编码") @PathVariable String dictCode, @Valid DictItemQuery queryParams ) { queryParams.setDictCode(dictCode); @@ -151,7 +144,7 @@ public class DictController { return PageResult.success(result); } - @Operation(summary = "字典项列表") + @Operation(summary = "获取字典项列表") @GetMapping("/{dictCode}/items/options") public Result> getDictItems( @Parameter(description = "字典编码") @PathVariable String dictCode @@ -160,8 +153,18 @@ public class DictController { return Result.success(list); } + @Operation(summary = "获取字典项表单数据") + @GetMapping("/{dictCode}/items/{itemId}/form") + public Result getDictItemForm( + @PathVariable String dictCode, + @Parameter(description = "字典ID") @PathVariable Long itemId + ) { + DictItemForm formData = dictItemService.getDictItemForm(itemId); + return Result.success(formData); + } + @Operation(summary = "新增字典项") - @PostMapping("/{dictCode}/items") + @PostMapping(value = "/{dictCode}/items") @PreAuthorize("@ss.hasPerm('sys:dict-item:create')") @RepeatSubmit @Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.INSERT) @@ -174,22 +177,12 @@ public class DictController { // 发送字典更新通知 if (result) { - sendDictChange(dictCode); + sendDictChange(dictCode); } return Result.judge(result); } - @Operation(summary = "字典项表单数据") - @GetMapping("/{dictCode}/items/{itemId}/form") - public Result getDictItemForm( - @PathVariable String dictCode, - @Parameter(description = "字典项ID") @PathVariable Long itemId - ) { - DictItemForm formData = dictItemService.getDictItemForm(itemId); - return Result.success(formData); - } - @Operation(summary = "修改字典项") @PutMapping("/{dictCode}/items/{itemId}") @PreAuthorize("@ss.hasPerm('sys:dict-item:update')") @@ -216,15 +209,13 @@ public class DictController { @DeleteMapping("/{dictCode}/items/{itemIds}") @PreAuthorize("@ss.hasPerm('sys:dict-item:delete')") @Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.DELETE) - public Result deleteDictItems( + public Result deleteDictItemByIds( @PathVariable String dictCode, - @Parameter(description = "字典ID,多个以英文逗号(,)拼接") @PathVariable String itemIds + @Parameter(description = "字典项ID,多个以英文逗号(,)拼接") @PathVariable String itemIds ) { dictItemService.deleteDictItemByIds(itemIds); - // 发送字典更新通知 sendDictChange(dictCode); - return Result.success(); } diff --git a/src/main/java/com/youlai/boot/system/controller/MenuController.java b/src/main/java/com/youlai/boot/system/controller/MenuController.java index bdfd55da..11c1e344 100644 --- a/src/main/java/com/youlai/boot/system/controller/MenuController.java +++ b/src/main/java/com/youlai/boot/system/controller/MenuController.java @@ -6,8 +6,10 @@ import com.youlai.boot.common.enums.ActionTypeEnum; import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.model.Option; import com.youlai.boot.common.result.Result; +import com.youlai.boot.system.model.form.MenuAiFillForm; import com.youlai.boot.system.model.form.MenuForm; import com.youlai.boot.system.model.query.MenuQuery; +import com.youlai.boot.system.model.vo.MenuAiVO; import com.youlai.boot.system.model.vo.MenuVO; import com.youlai.boot.system.model.vo.RouteVO; import com.youlai.boot.system.service.MenuService; @@ -48,11 +50,8 @@ public class MenuController { @Operation(summary = "菜单下拉列表") @GetMapping("/options") public Result>> getMenuOptions( - @Parameter(description = "是否只查询父级菜单") - @RequestParam(required = false, defaultValue = "false") boolean onlyParent - ) { - List> menus = menuService.listMenuOptions(onlyParent); - return Result.success(menus); + @Parameter(description = "菜单类型,可传多个,为空查全部") @RequestParam(required = false) List types) { + return Result.success(menuService.listMenuOptions(types)); } @Operation(summary = "当前用户菜单路由列表") @@ -118,5 +117,13 @@ public class MenuController { return Result.judge(result); } + // AI 辅助:未开启 AI 时调用返回业务异常 + + @Operation(summary = "AI 推断菜单的访问路径与权限标识") + @PostMapping("/ai-fill") + public Result aiFillMenu(@RequestBody @Valid MenuAiFillForm form) { + return Result.success(menuService.aiFillMenu(form)); + } + } diff --git a/src/main/java/com/youlai/boot/system/controller/UserController.java b/src/main/java/com/youlai/boot/system/controller/UserController.java index d3dffece..44207118 100644 --- a/src/main/java/com/youlai/boot/system/controller/UserController.java +++ b/src/main/java/com/youlai/boot/system/controller/UserController.java @@ -170,7 +170,8 @@ public class UserController { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8)); - String fileClassPath = "templates" + File.separator + "excel" + File.separator + fileName; + // ClassLoader 只认 / 分隔,用 File.separator 打 jar 后(Linux)会取不到资源 + String fileClassPath = "excel/" + fileName; InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileClassPath); try (ServletOutputStream outputStream = response.getOutputStream(); diff --git a/src/main/java/com/youlai/boot/system/converter/DictItemConverter.java b/src/main/java/com/youlai/boot/system/converter/DictItemConverter.java index 99a354b0..81e22731 100644 --- a/src/main/java/com/youlai/boot/system/converter/DictItemConverter.java +++ b/src/main/java/com/youlai/boot/system/converter/DictItemConverter.java @@ -3,12 +3,9 @@ package com.youlai.boot.system.converter; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.youlai.boot.system.model.entity.DictItem; import com.youlai.boot.system.model.form.DictItemForm; -import com.youlai.boot.system.model.vo.DictPageVO; -import com.youlai.boot.common.model.Option; +import com.youlai.boot.system.model.vo.DictItemPageVO; import org.mapstruct.Mapper; -import java.util.List; - /** * 字典项对象转换器 * @@ -18,12 +15,9 @@ import java.util.List; @Mapper(componentModel = "spring") public interface DictItemConverter { - Page toPageVo(Page page); + Page toPageVo(Page page); DictItemForm toForm(DictItem entity); - DictItem toEntity(DictItemForm formFata); - - Option toOption(DictItem dictItem); - List> toOption(List dictData); + DictItem toEntity(DictItemForm formData); } diff --git a/src/main/java/com/youlai/boot/system/mapper/DictItemMapper.java b/src/main/java/com/youlai/boot/system/mapper/DictItemMapper.java index 64bb7af5..69b877ca 100644 --- a/src/main/java/com/youlai/boot/system/mapper/DictItemMapper.java +++ b/src/main/java/com/youlai/boot/system/mapper/DictItemMapper.java @@ -18,10 +18,10 @@ public interface DictItemMapper extends BaseMapper { /** * 字典项分页列表 + * + * @param page 分页参数 + * @param queryParams 查询参数 + * @return 字典项分页列表 */ Page getDictItemPage(Page page, DictItemQuery queryParams); } - - - - diff --git a/src/main/java/com/youlai/boot/system/mapper/DictMapper.java b/src/main/java/com/youlai/boot/system/mapper/DictMapper.java index c18b5d06..2c9a1c18 100644 --- a/src/main/java/com/youlai/boot/system/mapper/DictMapper.java +++ b/src/main/java/com/youlai/boot/system/mapper/DictMapper.java @@ -26,7 +26,3 @@ public interface DictMapper extends BaseMapper { Page getDictPage(Page page, DictQuery queryParams); } - - - - diff --git a/src/main/java/com/youlai/boot/system/model/entity/Dict.java b/src/main/java/com/youlai/boot/system/model/entity/Dict.java index 1085e4f2..8708c0dd 100644 --- a/src/main/java/com/youlai/boot/system/model/entity/Dict.java +++ b/src/main/java/com/youlai/boot/system/model/entity/Dict.java @@ -6,7 +6,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; /** - * 字典实体 + * 字典实体对象 * * @author Ray.Hao * @since 2022/12/17 @@ -39,8 +39,8 @@ public class Dict extends BaseEntity { private String remark; /** - * 逻辑删除标识(0-未删除 1-已删除) + * 逻辑删除(1-已删除 0-未删除) */ private Integer isDeleted; -} \ No newline at end of file +} diff --git a/src/main/java/com/youlai/boot/system/model/entity/DictItem.java b/src/main/java/com/youlai/boot/system/model/entity/DictItem.java index 0fc80ee2..e90c0c40 100644 --- a/src/main/java/com/youlai/boot/system/model/entity/DictItem.java +++ b/src/main/java/com/youlai/boot/system/model/entity/DictItem.java @@ -37,7 +37,7 @@ public class DictItem extends BaseEntity { private Integer sort; /** - * 状态(1-正常,0-禁用) + * 状态(1-启用,0-停用) */ private Integer status; @@ -64,4 +64,10 @@ public class DictItem extends BaseEntity { */ @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateBy; -} \ No newline at end of file + + /** + * 逻辑删除(1-已删除 0-未删除) + */ + private Integer isDeleted; + +} diff --git a/src/main/java/com/youlai/boot/system/model/entity/Menu.java b/src/main/java/com/youlai/boot/system/model/entity/Menu.java index 2eb8d833..890a71ff 100644 --- a/src/main/java/com/youlai/boot/system/model/entity/Menu.java +++ b/src/main/java/com/youlai/boot/system/model/entity/Menu.java @@ -94,11 +94,6 @@ public class Menu extends BaseEntity { */ private Integer keepAlive; - /** - * 【目录】只有一个子路由是否始终显示(1:是 0:否) - */ - private Integer alwaysShow; - /** * 路由参数 */ diff --git a/src/main/java/com/youlai/boot/system/model/form/DictForm.java b/src/main/java/com/youlai/boot/system/model/form/DictForm.java index b17d320a..039866da 100644 --- a/src/main/java/com/youlai/boot/system/model/form/DictForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/DictForm.java @@ -4,12 +4,8 @@ package com.youlai.boot.system.model.form; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; -import lombok.Getter; -import lombok.Setter; import org.hibernate.validator.constraints.Range; -import java.util.List; - /** * 字典表单对象 * diff --git a/src/main/java/com/youlai/boot/system/model/form/DictItemForm.java b/src/main/java/com/youlai/boot/system/model/form/DictItemForm.java index 472fc745..3c48101b 100644 --- a/src/main/java/com/youlai/boot/system/model/form/DictItemForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/DictItemForm.java @@ -38,12 +38,12 @@ public class DictItemForm { @Range(min = 0, max = 9999, message = "排序值需在 0-9999 之间") private Integer sort; - @Schema(description = "状态(0:禁用,1:启用)") + @Schema(description = "状态(1-启用,0-禁用)") @Range(min = 0, max = 1, message = "状态取值不正确") private Integer status; - @Schema(description = "字典类型(用于显示样式)") - @Size(max = 30, message = "字典类型长度不能超过 30 个字符") + @Schema(description = "标签样式(primary-主要 success-成功 warning-警告 danger-危险 info-信息)") + @Size(max = 30, message = "标签样式长度不能超过 30 个字符") private String tagType; } diff --git a/src/main/java/com/youlai/boot/system/model/form/MenuAiFillForm.java b/src/main/java/com/youlai/boot/system/model/form/MenuAiFillForm.java new file mode 100644 index 00000000..497f9c6f --- /dev/null +++ b/src/main/java/com/youlai/boot/system/model/form/MenuAiFillForm.java @@ -0,0 +1,26 @@ +package com.youlai.boot.system.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 MenuAiFillForm { + + @Schema(description = "菜单名称", example = "公告管理") + @NotBlank(message = "菜单名称不能为空") + private String name; + + @Schema(description = "菜单类型(C-目录 M-菜单 E-外链 B-按钮)", example = "M") + private String type; + + @Schema(description = "上级菜单ID,用于参考同级菜单的命名风格", example = "1") + private Long parentId; +} diff --git a/src/main/java/com/youlai/boot/system/model/form/MenuForm.java b/src/main/java/com/youlai/boot/system/model/form/MenuForm.java index 459ab708..921ee496 100644 --- a/src/main/java/com/youlai/boot/system/model/form/MenuForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/MenuForm.java @@ -60,10 +60,13 @@ public class MenuForm { @Schema(description = "【菜单】是否开启页面缓存", example = "1") private Integer keepAlive; - @Schema(description = "【目录】只有一个子路由是否始终显示", example = "1") - private Integer alwaysShow; - @Schema(description = "路由参数") private List params; + @Schema(description = "新增页面菜单时是否生成增删改查按钮") + private Boolean generateCrudButtons; + + @Schema(description = "按钮权限标识前缀,如 sys:user") + private String buttonPermPrefix; + } diff --git a/src/main/java/com/youlai/boot/system/model/vo/DictItemOptionVO.java b/src/main/java/com/youlai/boot/system/model/vo/DictItemOptionVO.java index 3fc2742c..246b2755 100644 --- a/src/main/java/com/youlai/boot/system/model/vo/DictItemOptionVO.java +++ b/src/main/java/com/youlai/boot/system/model/vo/DictItemOptionVO.java @@ -21,9 +21,7 @@ public class DictItemOptionVO { @Schema(description = "字典项标签") private String label; - @Schema(description = "标签类型") + @Schema(description = "标签样式") private String tagType; } - - \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/model/vo/DictItemPageVO.java b/src/main/java/com/youlai/boot/system/model/vo/DictItemPageVO.java index 53a1b8df..177c4ac0 100644 --- a/src/main/java/com/youlai/boot/system/model/vo/DictItemPageVO.java +++ b/src/main/java/com/youlai/boot/system/model/vo/DictItemPageVO.java @@ -28,12 +28,13 @@ public class DictItemPageVO { @Schema(description = "字典值") private String value; + @Schema(description = "标签样式") + private String tagType; + @Schema(description = "排序") private Integer sort; - @Schema(description = "状态(1:启用,0:禁用)") + @Schema(description = "状态(1-启用,0-停用)") private Integer status; } - - \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/model/vo/DictPageVO.java b/src/main/java/com/youlai/boot/system/model/vo/DictPageVO.java index 5185146b..a2d5454e 100644 --- a/src/main/java/com/youlai/boot/system/model/vo/DictPageVO.java +++ b/src/main/java/com/youlai/boot/system/model/vo/DictPageVO.java @@ -24,8 +24,6 @@ public class DictPageVO { @Schema(description = "字典编码") private String dictCode; - @Schema(description = "字典状态(1-启用,0-禁用)") + @Schema(description = "字典状态(1-启用,0-停用)") private Integer status; } - - \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/model/vo/MenuAiVO.java b/src/main/java/com/youlai/boot/system/model/vo/MenuAiVO.java new file mode 100644 index 00000000..70bae7bb --- /dev/null +++ b/src/main/java/com/youlai/boot/system/model/vo/MenuAiVO.java @@ -0,0 +1,26 @@ +package com.youlai.boot.system.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * AI 推断的菜单配置 + * + * @author Ray.Hao + * @since 0.1.0 + */ +@Schema(description = "AI 推断的菜单配置") +@Data +public class MenuAiVO { + + @Schema(description = "访问路径") + private String routePath; + + @Schema(description = "权限标识") + private String perm; + + @Schema(description = "建议的图标关键词,按贴切程度排列") + private List iconKeywords; +} diff --git a/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java b/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java index 121ca5df..8c32f42e 100644 --- a/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java +++ b/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java @@ -50,10 +50,6 @@ public class RouteVO { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean keepAlive; - @Schema(description = "【目录】只有一个子路由是否始终显示", example = "true") - @JsonInclude(JsonInclude.Include.NON_NULL) - private Boolean alwaysShow; - @Schema(description = "外链地址(内嵌模式用)", example = "https://www.youlai.tech") @JsonInclude(JsonInclude.Include.NON_NULL) private String externalUrl; diff --git a/src/main/java/com/youlai/boot/system/service/DictService.java b/src/main/java/com/youlai/boot/system/service/DictService.java index a064deda..942098b1 100644 --- a/src/main/java/com/youlai/boot/system/service/DictService.java +++ b/src/main/java/com/youlai/boot/system/service/DictService.java @@ -6,7 +6,6 @@ import com.youlai.boot.common.model.Option; import com.youlai.boot.system.model.entity.Dict; import com.youlai.boot.system.model.form.DictForm; import com.youlai.boot.system.model.query.DictQuery; -import com.youlai.boot.system.model.vo.DictItemOptionVO; import com.youlai.boot.system.model.vo.DictPageVO; import java.util.List; diff --git a/src/main/java/com/youlai/boot/system/service/MenuService.java b/src/main/java/com/youlai/boot/system/service/MenuService.java index b465b6fd..287d2dee 100644 --- a/src/main/java/com/youlai/boot/system/service/MenuService.java +++ b/src/main/java/com/youlai/boot/system/service/MenuService.java @@ -2,10 +2,12 @@ package com.youlai.boot.system.service; import com.baomidou.mybatisplus.extension.service.IService; import com.youlai.boot.system.model.dto.MenuCreateDTO; +import com.youlai.boot.system.model.form.MenuAiFillForm; import com.youlai.boot.system.model.form.MenuForm; import com.youlai.boot.common.model.Option; import com.youlai.boot.system.model.entity.Menu; import com.youlai.boot.system.model.query.MenuQuery; +import com.youlai.boot.system.model.vo.MenuAiVO; import com.youlai.boot.system.model.vo.MenuVO; import com.youlai.boot.system.model.vo.RouteVO; @@ -27,9 +29,9 @@ public interface MenuService extends IService { /** * 获取菜单下拉列表 * - * @param onlyParent 是否只查询父级菜单 + * @param types 菜单类型,为空查全部 */ - List> listMenuOptions(boolean onlyParent); + List> listMenuOptions(List types); /** * 新增菜单 @@ -81,4 +83,12 @@ public interface MenuService extends IService { * @param dto 创建菜单数据载体 */ void addMenuForCodegen(Long parentMenuId, MenuCreateDTO dto); + + /** + * 用 AI 推断菜单的访问路径、权限标识与配套按钮,命名风格参照同级菜单 + * + * @param form 菜单信息 + * @return 推断结果 + */ + MenuAiVO aiFillMenu(MenuAiFillForm form); } diff --git a/src/main/java/com/youlai/boot/system/service/impl/DictItemServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/DictItemServiceImpl.java index 48c65a29..4e85259f 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/DictItemServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/DictItemServiceImpl.java @@ -1,5 +1,6 @@ package com.youlai.boot.system.service.impl; +import cn.hutool.core.lang.Assert; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -44,7 +45,6 @@ public class DictItemServiceImpl extends ServiceImpl i return this.baseMapper.getDictItemPage(page, queryParams); } - /** * 获取字典项列表 * @@ -57,6 +57,7 @@ public class DictItemServiceImpl extends ServiceImpl i .eq(DictItem::getDictCode, dictCode) .eq(DictItem::getStatus, 1) .orderByAsc(DictItem::getSort) + .orderByAsc(DictItem::getId) ).stream() .map(item -> { DictItemOptionVO dictItemOptionVo = new DictItemOptionVO(); @@ -67,8 +68,6 @@ public class DictItemServiceImpl extends ServiceImpl i }).toList(); } - - /** * 获取字典项表单 * @@ -76,7 +75,7 @@ public class DictItemServiceImpl extends ServiceImpl i * @return 字典项表单 */ @Override - public DictItemForm getDictItemForm( Long itemId) { + public DictItemForm getDictItemForm(Long itemId) { DictItem entity = this.getById(itemId); return dictItemConverter.toForm(entity); } @@ -89,6 +88,7 @@ public class DictItemServiceImpl extends ServiceImpl i */ @Override public boolean saveDictItem(DictItemForm formData) { + validateValueUnique(formData.getDictCode(), formData.getValue(), null); DictItem entity = dictItemConverter.toEntity(formData); return this.save(entity); } @@ -101,6 +101,7 @@ public class DictItemServiceImpl extends ServiceImpl i */ @Override public boolean updateDictItem(DictItemForm formData) { + validateValueUnique(formData.getDictCode(), formData.getValue(), formData.getId()); DictItem entity = dictItemConverter.toEntity(formData); return this.updateById(entity); } @@ -118,8 +119,20 @@ public class DictItemServiceImpl extends ServiceImpl i this.removeByIds(idList); } + /** + * 校验同一字典下字典项值唯一 + * + * @param dictCode 字典编码 + * @param value 字典项值 + * @param excludeId 更新时传当前字典项ID以排除自身,新增传 null 不排除任何记录 + */ + private void validateValueUnique(String dictCode, String value, Long excludeId) { + long count = this.count(new LambdaQueryWrapper() + .eq(DictItem::getDictCode, dictCode) + .eq(DictItem::getValue, value) + .ne(excludeId != null, DictItem::getId, excludeId) + ); + Assert.isTrue(count == 0, "该字典下字典项值已存在"); + } + } - - - - diff --git a/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java index ffc8e06d..c128890f 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/DictServiceImpl.java @@ -134,7 +134,7 @@ public class DictServiceImpl extends ServiceImpl implements Di .eq(DictItem::getDictCode, entity.getDictCode()) .select(DictItem::getId) ); - if (!dictItemList.isEmpty()){ + if (!dictItemList.isEmpty()) { List dictItemIds = dictItemList.stream().map(DictItem::getId).toList(); DictItem dictItem = new DictItem(); dictItem.setDictCode(dict.getDictCode()); @@ -155,13 +155,14 @@ public class DictServiceImpl extends ServiceImpl implements Di @Transactional @Override public void deleteDictByIds(List ids) { + // 先取字典编码:删除后再查会被逻辑删除条件过滤,导致字典项无法级联删除 + List dictCodes = this.listByIds(ids).stream().map(Dict::getDictCode).toList(); + // 删除字典 this.removeByIds(ids); // 删除字典项 - List list = this.listByIds(ids); - if (!list.isEmpty()) { - List dictCodes = list.stream().map(Dict::getDictCode).toList(); + if (!dictCodes.isEmpty()) { dictItemService.remove(new LambdaQueryWrapper() .in(DictItem::getDictCode, dictCodes) ); @@ -181,7 +182,3 @@ public class DictServiceImpl extends ServiceImpl implements Di } } - - - - diff --git a/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java index 3d922cf1..a81ebb1b 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java @@ -7,14 +7,19 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.common.base.IBaseEnum; import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.framework.security.util.SecurityUtils; +import com.youlai.boot.support.ai.AiChatService; +import com.youlai.boot.support.ai.PromptLoader; import com.youlai.boot.system.converter.MenuConverter; import com.youlai.boot.system.mapper.MenuMapper; import com.youlai.boot.system.model.dto.MenuCreateDTO; import com.youlai.boot.system.model.entity.Menu; +import com.youlai.boot.system.model.form.MenuAiFillForm; import com.youlai.boot.system.model.form.MenuForm; import com.youlai.boot.system.model.query.MenuQuery; +import com.youlai.boot.system.model.vo.MenuAiVO; import com.youlai.boot.system.model.vo.MenuVO; import com.youlai.boot.system.model.vo.RouteVO; import com.youlai.boot.common.constant.SystemConstants; @@ -46,6 +51,12 @@ public class MenuServiceImpl extends ServiceImpl implements Me private final RoleMenuService roleMenuService; + /** 系统提示词名称,对应 resources/prompts 下的相对路径 */ + private static final String SYSTEM_PROMPT_PATH = "menu/system.md"; + + private final AiChatService aiChatService; + private final PromptLoader promptLoader; + /** * 菜单列表 @@ -101,12 +112,12 @@ public class MenuServiceImpl extends ServiceImpl implements Me /** * 菜单下拉数据 * - * @param onlyParent 是否只查询父级菜单 如果为true,排除按钮 + * @param types 菜单类型,为空查全部 */ @Override - public List> listMenuOptions(boolean onlyParent) { + public List> listMenuOptions(List types) { List menuList = this.list(new LambdaQueryWrapper() - .in(onlyParent, Menu::getType, MenuTypeEnum.CATALOG.getValue(), MenuTypeEnum.MENU.getValue()) + .in(CollectionUtil.isNotEmpty(types), Menu::getType, types) .orderByAsc(Menu::getSort) ); return buildMenuOptions(SystemConstants.ROOT_NODE_ID, menuList); @@ -266,8 +277,6 @@ public class MenuServiceImpl extends ServiceImpl implements Me && ObjectUtil.equals(menu.getKeepAlive(), 1)) { meta.setKeepAlive(true); } - meta.setAlwaysShow(ObjectUtil.equals(menu.getAlwaysShow(), 1)); - if (isEmbedded && StrUtil.isNotBlank(externalUrl)) { meta.setExternalUrl(externalUrl); } @@ -306,10 +315,16 @@ public class MenuServiceImpl extends ServiceImpl implements Me if (Objects.equals(menuForm.getParentId(), menuForm.getId())) { throw new BusinessException("父级菜单不能为当前菜单"); } + validateMenuParent(menuForm); Menu entity = menuConverter.toEntity(menuForm); String treePath = generateMenuTreePath(menuForm.getParentId()); entity.setTreePath(treePath); + // 新增菜单未指定排序时排到同级末尾 + if (menuForm.getId() == null && entity.getSort() == null) { + entity.setSort(resolveNextSort(menuForm.getParentId())); + } + List params = menuForm.getParams(); if (CollectionUtil.isNotEmpty(params)) { entity.setParams(params.stream() @@ -331,9 +346,13 @@ public class MenuServiceImpl extends ServiceImpl implements Me boolean result = this.saveOrUpdate(entity); if (result) { - // 编辑刷新角色权限缓存 if (menuForm.getId() != null) { + // 编辑刷新角色权限缓存 roleMenuService.refreshRolePermsCache(); + } else if (Boolean.TRUE.equals(menuForm.getGenerateCrudButtons()) + && StrUtil.isNotBlank(menuForm.getButtonPermPrefix())) { + // 新增页面菜单时按需生成增删改查按钮 + saveCrudButtons(entity.getId(), menuForm.getButtonPermPrefix().trim(), treePath); } } // 修改菜单如果有子菜单,则更新子菜单的树路径 @@ -378,6 +397,77 @@ public class MenuServiceImpl extends ServiceImpl implements Me } } + /** + * 同级菜单的最大排序 + 1 + * + * @param parentId 父菜单ID + * @return 下一个排序值 + */ + private int resolveNextSort(Long parentId) { + Menu maxSortMenu = this.getOne(new LambdaQueryWrapper() + .eq(Menu::getParentId, parentId) + .orderByDesc(Menu::getSort) + .last("LIMIT 1") + ); + return maxSortMenu == null || maxSortMenu.getSort() == null ? 1 : maxSortMenu.getSort() + 1; + } + + /** + * 校验上级菜单层级:按钮只能挂在菜单下,其他类型只能挂在顶级或目录下 + * + * @param menuForm 菜单表单 + */ + private void validateMenuParent(MenuForm menuForm) { + // 父级没变时跳过校验,让历史遗留的非法层级仍可编辑其他字段 + if (menuForm.getId() != null) { + Menu current = this.getById(menuForm.getId()); + if (current != null && Objects.equals(current.getParentId(), menuForm.getParentId())) { + return; + } + } + + boolean isButton = MenuTypeEnum.BUTTON.getValue().equals(menuForm.getType()); + Long parentId = menuForm.getParentId(); + if (parentId == null || SystemConstants.ROOT_NODE_ID.equals(parentId)) { + Assert.isFalse(isButton, "按钮权限只能挂在菜单下"); + return; + } + + Menu parent = this.getById(parentId); + Assert.notNull(parent, "上级菜单不存在"); + if (isButton) { + Assert.isTrue(MenuTypeEnum.MENU.getValue().equals(parent.getType()), "按钮权限只能挂在菜单下"); + } else { + Assert.isFalse(MenuTypeEnum.MENU.getValue().equals(parent.getType()), "菜单下只能挂按钮权限"); + } + } + + /** + * 生成增删改查按钮权限 + * + * @param menuId 所属菜单ID + * @param permPrefix 权限标识前缀,不含末尾冒号,如 sys:user + * @param menuTreePath 所属菜单的树路径 + */ + private void saveCrudButtons(Long menuId, String permPrefix, String menuTreePath) { + String[] names = {"查询", "新增", "修改", "删除"}; + String[] actions = {"list", "create", "update", "delete"}; + // 树路径记录祖先链,等于所属菜单的树路径加上所属菜单ID,删除菜单时据此级联 + String buttonTreePath = menuTreePath + "," + menuId; + + for (int i = 0; i < names.length; i++) { + Menu button = new Menu(); + button.setParentId(menuId); + button.setType(MenuTypeEnum.BUTTON.getValue()); + button.setName(names[i]); + button.setPerm(permPrefix + ":" + actions[i]); + button.setSort(i + 1); + button.setVisible(1); + button.setTreePath(buttonTreePath); + this.save(button); + } + } + /** * 修改菜单显示状态 @@ -456,15 +546,7 @@ public class MenuServiceImpl extends ServiceImpl implements Me return; } - // 获取父级菜单子菜单最带的排序 - Menu maxSortMenu = this.getOne(new LambdaQueryWrapper().eq(Menu::getParentId, parentMenuId) - .orderByDesc(Menu::getSort) - .last("limit 1") - ); - int sort = 1; - if (maxSortMenu != null) { - sort = maxSortMenu.getSort() + 1; - } + int sort = resolveNextSort(parentMenuId); Menu menu = new Menu(); menu.setParentId(parentMenuId); @@ -485,24 +567,72 @@ public class MenuServiceImpl extends ServiceImpl implements Me this.updateById(menu); // 生成 CRUD 按钮权限 - String permPrefix = dto.moduleName() + ":" + dto.tableName().replace("_", "-") + ":"; - String[] actions = {"查询", "新增", "修改", "删除"}; - String[] perms = {"list", "create", "update", "delete"}; - - for (int i = 0; i < actions.length; i++) { - Menu button = new Menu(); - button.setParentId(menu.getId()); - button.setType(MenuTypeEnum.BUTTON.getValue()); - button.setName(actions[i]); - button.setPerm(permPrefix + perms[i]); - button.setSort(i + 1); - this.save(button); - - // 生成treePath - button.setTreePath(treePath + "," + button.getId()); - this.updateById(button); - } + saveCrudButtons(menu.getId(), dto.moduleName() + ":" + dto.tableName().replace("_", "-"), treePath); } } + //--------------------------------------------------- + // AI 辅助:未开启 AI 时调用返回业务异常 + //--------------------------------------------------- + + /** + * 用 AI 推断菜单的访问路径、权限标识与配套按钮 + * + * @param form 菜单信息 + * @return 推断结果 + */ + @Override + public MenuAiVO aiFillMenu(MenuAiFillForm form) { + return aiChatService.chatEntity( + promptLoader.get(SYSTEM_PROMPT_PATH), buildAiUserPrompt(form), MenuAiVO.class); + } + + /** + * 构造用户提示词,附带同级菜单样本供模型沿用既有命名风格 + * + * @param form 菜单信息 + * @return 用户提示词 + */ + private String buildAiUserPrompt(MenuAiFillForm form) { + StringBuilder text = new StringBuilder(); + text.append("菜单名称:").append(form.getName()).append("\n"); + text.append("菜单类型:").append(menuTypeLabel(form.getType())).append("\n"); + + Long parentId = form.getParentId() == null ? SystemConstants.ROOT_NODE_ID : form.getParentId(); + Menu parent = this.getById(parentId); + text.append("上级菜单:").append(parent == null ? "顶级" : parent.getName()).append("\n"); + + List siblings = this.list(new LambdaQueryWrapper() + .eq(Menu::getParentId, parentId) + .orderByAsc(Menu::getSort) + .last("LIMIT 10") + ); + text.append("同级菜单示例:\n"); + if (CollectionUtil.isEmpty(siblings)) { + text.append("(无)\n"); + } else { + siblings.forEach(menu -> text.append("- ") + .append(menu.getName()) + .append("(类型 ").append(menuTypeLabel(menu.getType())) + .append(",路径片段 ").append(StrUtil.blankToDefault(menu.getRoutePath(), "无")) + .append(",权限 ").append(StrUtil.blankToDefault(menu.getPerm(), "无")) + .append(")\n")); + } + return text.toString(); + } + + /** + * 菜单类型取中文名称,枚举无匹配时沿用原值 + * + * @param type 菜单类型 + * @return 中文名称 + */ + private String menuTypeLabel(String type) { + if (StrUtil.isBlank(type)) { + return "未指定"; + } + MenuTypeEnum menuType = IBaseEnum.getEnumByValue(type, MenuTypeEnum.class); + return menuType == null ? type : menuType.getLabel(); + } + } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index a879b862..1d1ab672 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -4,7 +4,6 @@ server: spring: datasource: type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置 url: jdbc:mysql://www.youlai.tech:3306/youlai_admin?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true username: youlai password: 123456 @@ -49,6 +48,10 @@ spring: enable: true # 邮件发送者 from: youlaitech@163.com + # AI 对话默认关闭;启用改 chat 为 openai 并配置 spring.ai.openai 的 api-key、base-url、chat.options.model + ai: + model: + chat: none mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml global-config: @@ -88,14 +91,14 @@ security: allow-multi-login: true # 是否允许多设备登录 # 匿名放行路径,仅跳过认证与授权判断,仍走过滤器链(保留安全响应头) permit-all-urls: - - /api/v1/auth/login/** # 登录接口(账号密码登录) + - /api/v1/auth/login # 登录接口(账号密码登录) - /api/v1/auth/captcha # 验证码获取接口 - /api/v1/auth/sms/** # 短信验证码接口(发送验证码、短信登录) - /api/v1/auth/refresh-token # 刷新令牌接口 - /api/v1/auth/wxma/** # 微信小程序认证接口(静默登录/手机号快捷登录/绑定手机号) - - /api/v1/logs/** # 日志接口(访问日志列表) - /api/v1/auth/qr-code/** # 扫码登录接口(生成票据/查询状态/换取令牌) - /api/v1/forms/public/** # 公开表单接口(匿名渲染/提交,Service 校验 is_public 总闸 + IP 限流) + - /api/v1/health # 健康检查接口(供探针与限流测试) - ${springdoc.swagger-ui.path} # Swagger UI 路径 - /doc.html - /swagger-ui/** @@ -137,23 +140,6 @@ storage: # 存储根目录(相对路径基于工作目录;生产建议挂载独立卷) path: ./uploads/ -# 短信配置 -sms: - # 阿里云短信 - aliyun: - accessKeyId: LTAI5tSMgfxxxxxxdiBJLyR - accessKeySecret: SoOWRqpjtS7xxxxxxZ2PZiMTJOVC - domain: dysmsapi.aliyuncs.com - regionId: cn-shanghai - signName: 有来技术 - templates: - # 注册短信验证码模板 - register: SMS_22xxx771 - # 登录短信验证码模板 - login: SMS_22xxx772 - # 修改手机号短信验证码模板 - change-mobile: SMS_22xxx773 - # springdoc 配置文档: https://springdoc.org/properties.html springdoc: swagger-ui: @@ -185,23 +171,6 @@ knife4j: setting: language: zh_cn -# xxl-job 定时任务配置 -xxl: - job: - # 定时任务开关 - enabled: false - admin: - # 多个地址使用,分割 - addresses: http://127.0.0.1:8686/xxl-job-admin - accessToken: default_token - executor: - appname: xxl-job-executor-${spring.application.name} - address: - ip: - port: 9999 - logpath: /data/applogs/xxl-job/jobhandler - logretentiondays: 30 - # 验证码配置 captcha: # 验证码类型 circle-圆圈干扰验证码|gif-Gif验证码|line-干扰线验证码|shear-扭曲干扰验证码 @@ -231,27 +200,49 @@ captcha: # 验证码有效期(秒) expire-seconds: 120 +# 短信配置 +sms: + # 阿里云短信 + aliyun: + accessKeyId: LTAI5tSMgfxxxxxxdiBJLyR + accessKeySecret: SoOWRqpjtS7xxxxxxZ2PZiMTJOVC + domain: dysmsapi.aliyuncs.com + regionId: cn-shanghai + signName: 有来技术 + templates: + # 注册短信验证码模板 + register: SMS_22xxx771 + # 登录短信验证码模板 + login: SMS_22xxx772 + # 修改手机号短信验证码模板 + change-mobile: SMS_22xxx773 + +# xxl-job 定时任务配置 +xxl: + job: + # 定时任务开关 + enabled: false + admin: + # 调度中心地址,多个地址用逗号分割;3.4.1 起调度中心 context-path 为 /,地址不带 /xxl-job-admin 后缀 + addresses: http://127.0.0.1:8080 + accessToken: default_token + executor: + appname: xxl-job-executor-${spring.application.name} + address: + ip: + port: 9999 + logpath: /data/applogs/xxl-job/jobhandler + logretentiondays: 30 + # 微信小程序配置 wx: miniapp: - appid: Your_AppId - secret: Your_AppSecret + appid: your-app-id + secret: your-app-secret -# 工作流(默认关闭,开启见 README「可选扩展」) +# 工作流(可选扩展,开启只需将 enabled 改为 true,引擎开关自动跟随) workflow: - # 工作流模块开关,未配置视为关闭 enabled: false demo: # 演示流程自动部署开关 enabled: true - -flowable: - # 引擎开关跟随 workflow.enabled - process: - enabled: ${workflow.enabled:false} - idm: - enabled: ${workflow.enabled:false} - eventregistry: - enabled: ${workflow.enabled:false} - # 引擎表初始化策略,true 启动时检查并自动建/更新 ACT_* 表;可选 true/false/create/create-drop - database-schema-update: true diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 8006c685..2d092b6b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -4,7 +4,6 @@ server: spring: datasource: type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置 url: jdbc:mysql://www.youlai.tech:3306/youlai_admin?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true username: youlai password: 123456 @@ -49,6 +48,10 @@ spring: enable: true # 邮件发送者 from: youlaitech@163.com + # AI 对话默认关闭;启用改 chat 为 openai 并配置 spring.ai.openai 的 api-key、base-url、chat.options.model + ai: + model: + chat: none mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml global-config: @@ -88,14 +91,14 @@ security: allow-multi-login: true # 是否允许多设备登录 # 匿名放行路径,仅跳过认证与授权判断,仍走过滤器链(保留安全响应头) permit-all-urls: - - /api/v1/auth/login/** # 登录接口(账号密码登录) + - /api/v1/auth/login # 登录接口(账号密码登录) - /api/v1/auth/captcha # 验证码获取接口 - /api/v1/auth/sms/** # 短信验证码接口(发送验证码、短信登录) - /api/v1/auth/refresh-token # 刷新令牌接口 - /api/v1/auth/wxma/** # 微信小程序认证接口(静默登录/手机号快捷登录/绑定手机号) - - /api/v1/logs/** # 日志接口(访问日志列表) - /api/v1/auth/qr-code/** # 扫码登录接口(生成票据/查询状态/换取令牌) - /api/v1/forms/public/** # 公开表单接口(匿名渲染/提交,Service 校验 is_public 总闸 + IP 限流) + - /api/v1/health # 健康检查接口(供探针与限流测试) - ${springdoc.swagger-ui.path} # Swagger UI 路径 - /doc.html - /swagger-ui/** @@ -136,22 +139,6 @@ storage: local: # 存储根目录(相对路径基于工作目录;生产建议挂载独立卷) path: ./uploads/ -# 短信配置 -sms: - # 阿里云短信 - aliyun: - accessKeyId: LTAI5tSMgfxxxxxxdiBJLyR - accessKeySecret: SoOWRqpjtS7xxxxxxZ2PZiMTJOVC - domain: dysmsapi.aliyuncs.com - regionId: cn-shanghai - signName: 有来技术 - templates: - # 注册短信验证码模板 - register: SMS_22xxx771 - # 登录短信验证码模板 - login: SMS_22xxx772 - # 修改手机号短信验证码模板 - change-mobile: SMS_22xxx773 # springdoc 配置文档: https://springdoc.org/properties.html springdoc: @@ -183,23 +170,6 @@ knife4j: setting: language: zh_cn -# xxl-job 定时任务配置 -xxl: - job: - # 定时任务开关 - enabled: false - admin: - # 多个地址使用,分割 - addresses: http://127.0.0.1:8686/xxl-job-admin - accessToken: default_token - executor: - appname: xxl-job-executor-${spring.application.name} - address: - ip: - port: 9999 - logpath: /data/applogs/xxl-job/jobhandler - logretentiondays: 30 - # 验证码配置 captcha: # 验证码类型 circle-圆圈干扰验证码|gif-Gif验证码|line-干扰线验证码|shear-扭曲干扰验证码 @@ -229,27 +199,53 @@ captcha: # 验证码有效期(秒) expire-seconds: 120 -# 微信小程配置 +# 短信配置 +sms: + # 阿里云短信 + aliyun: + accessKeyId: LTAI5tSMgfxxxxxxdiBJLyR + accessKeySecret: SoOWRqpjtS7xxxxxxZ2PZiMTJOVC + domain: dysmsapi.aliyuncs.com + regionId: cn-shanghai + signName: 有来技术 + templates: + # 注册短信验证码模板 + register: SMS_22xxx771 + # 登录短信验证码模板 + login: SMS_22xxx772 + # 修改手机号短信验证码模板 + change-mobile: SMS_22xxx773 + +# xxl-job 定时任务配置 +xxl: + job: + # 定时任务开关 + enabled: false + admin: + # 调度中心地址,多个地址用逗号分割;3.4.1 起调度中心 context-path 为 /,地址不带 /xxl-job-admin 后缀 + addresses: http://127.0.0.1:8080 + accessToken: default_token + executor: + appname: xxl-job-executor-${spring.application.name} + address: + ip: + port: 9999 + logpath: /data/applogs/xxl-job/jobhandler + logretentiondays: 30 + +# 微信小程序配置 wx: miniapp: - appid: Your_AppId - secret: Your_AppSecret + appid: your-app-id + secret: your-app-secret -# 工作流(默认关闭,开启见 README「可选扩展」) +# 工作流(可选扩展,开启只需将 enabled 改为 true,引擎开关自动跟随) workflow: - # 工作流模块开关,未配置视为关闭 enabled: false demo: # 演示流程自动部署开关 enabled: false flowable: - # 引擎开关跟随 workflow.enabled - process: - enabled: ${workflow.enabled:false} - idm: - enabled: ${workflow.enabled:false} - eventregistry: - enabled: ${workflow.enabled:false} # 引擎表初始化策略,false 要求 ACT_* 表已存在 database-schema-update: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c4668de3..3f79834b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,7 @@ spring: application: name: youlai-boot profiles: - active: dev + active: youlai config: # 引入代码生成模块的配置(数据源、表前缀等),由 codegen.yml 提供 import: classpath:codegen.yml @@ -11,3 +11,25 @@ spring: # 单文件大小上限,取自 storage.upload.max-file-size(单一来源) max-file-size: ${storage.upload.max-file-size:50MB} max-request-size: ${storage.upload.max-file-size:50MB} + # 项目只用对话模型,其余类型关闭自动装配,否则未配置密钥时启动就要求凭据 + ai: + model: + embedding: none + image: none + moderation: none + audio: + speech: none + transcription: none + +# 工作流引擎(业务开关 workflow.enabled 在 application-{profile}.yml) +flowable: + # 流程引擎跟随业务开关 + process: + enabled: ${workflow.enabled:false} + # 用户与角色走系统自有体系(sys_user/sys_role),不启用 Flowable IDM 与事件注册表 + idm: + enabled: false + eventregistry: + enabled: false + # 引擎表初始化策略,true 启动时检查并自动建/更新 ACT_* 表;可选 true/false/create/create-drop + database-schema-update: true diff --git a/src/main/resources/prompts/codegen/system.md b/src/main/resources/prompts/codegen/system.md new file mode 100644 index 00000000..df815200 --- /dev/null +++ b/src/main/resources/prompts/codegen/system.md @@ -0,0 +1,15 @@ +你是代码生成器的配置助手,根据数据库表结构推断代码生成配置。 +取值约束: +- businessName:中文简明业务名(如:用户、角色、通告) +- fieldComment:中文简明字段描述(如:用户名、创建时间) +- formType:{{formTypes}} +- queryType:{{queryTypes}} +- isRequired、isShowInList、isShowInForm、isShowInQuery:1 是,0 否 +- dictType:字典类型编码,无法确定时留空 +推断规则: +- 时间字段用 DATE_TIME 且查询用 BETWEEN,纯日期用 DATE +- 名称、标题等文本字段查询用 LIKE +- 状态、类型等枚举字段用 SELECT 或 RADIO,查询用 EQ +- 布尔或启用状态用 SWITCH +- id、create_time、update_time、is_deleted 等系统字段不出现在表单和查询中 +- columnName 必须与输入完全一致,只允许输出输入中出现的列 diff --git a/src/main/resources/prompts/form/system.md b/src/main/resources/prompts/form/system.md new file mode 100644 index 00000000..ff2c9dcc --- /dev/null +++ b/src/main/resources/prompts/form/system.md @@ -0,0 +1,16 @@ +你是表单设计助手,根据需求描述生成 form-create(Vue3 + Element Plus)表单规则数组。 +规则项结构: +{"type":"input","field":"username","title":"用户名","props":{"placeholder":"请输入用户名"},"validate":[{"required":true,"message":"请输入用户名","trigger":["blur","change"]}]} +可用组件 type: +- 基础:input、textarea、inputNumber、select、radio、checkbox、switch、datePicker、timePicker、slider、rate、upload +- 布局:row(children 为 col 数组)、col(children 为规则项数组) +- 业务组件:DictSelect(字典选择,props:{"code":"字典编码","type":"select|radio|checkbox","placeholder":"请选择"})、FileUpload(附件上传,props:{"limit":5,"maxFileSize":10,"accept":"*","uploadBtnText":"上传文件"}) +约定: +- field 用小写英文下划线命名,全局唯一 +- title 用中文 +- 必填字段加 validate 里的 required +- 日期用 datePicker,日期时间加 props:{"type":"datetime"},日期范围加 props:{"type":"daterange"} +- select/radio/checkbox 的选项写在 props.options,格式 [{"label":"是","value":1}] +- 状态、类型等枚举字段优先用 DictSelect +- 附件、图片类字段用 FileUpload +- 不要输出 children 为空的容器节点 diff --git a/src/main/resources/prompts/menu/system.md b/src/main/resources/prompts/menu/system.md new file mode 100644 index 00000000..23afed83 --- /dev/null +++ b/src/main/resources/prompts/menu/system.md @@ -0,0 +1,15 @@ +你是后台管理系统的菜单配置助手,根据菜单名称推断访问路径、权限标识和图标。 + +取值约束: +- routePath:当前这一级的路径片段,只含一段,不含上级路径,不带前导斜杠;小写英文,多个单词用连字符分隔(如 user、data-report),不用汉语拼音 +- perm:模块:资源:操作 三段式(如 sys:user:create),全部小写英文 +- iconKeywords:2 到 3 个小写英文关键词,描述该菜单最贴切的图标含义(如 user、setting、notice),按贴切程度排列,不要带 el-icon- 前缀 + +推断规则: +- 目录、页面推断 routePath 与 iconKeywords;按钮推断 perm;外链不推断任何字段 +- routePath 不是完整访问地址:不管挂在哪一级,都只输出自己这一段的路径,上位路径由系统拼接 +- 「同级菜单示例」里的路径带前导斜杠是目录的存储格式,输出时不要带 +- 命名风格必须与「同级菜单示例」保持一致,模块前缀优先沿用示例中已出现的写法 +- iconKeywords 用通用的名词语义(如 user、order、chart),不要用形容词 +- 不要自创新的模块前缀,示例中无法参照时按系统现有命名习惯推断 +- 无法确定时留空,不要编造 diff --git a/src/main/resources/prompts/workflow/summary.md b/src/main/resources/prompts/workflow/summary.md new file mode 100644 index 00000000..cac46d8f --- /dev/null +++ b/src/main/resources/prompts/workflow/summary.md @@ -0,0 +1,8 @@ +你是审批助手,根据流程表单数据与审批记录生成审批摘要,帮助审批人快速决策。 +直接输出中文摘要,不要标题、不要 Markdown 标记、不要分点罗列。 +要求: +- 控制在 200 字以内 +- 先说明这是一份什么申请(申请人、事由、关键金额或时间) +- 再说明已完成的审批环节与意见 +- 最后提示需重点关注的异常(金额超阈值、时间冲突、材料缺失等),无异常则不写 +- 不要编造表单与审批记录之外的信息