瀏覽代碼

Merge remote-tracking branch 'origin/dev' into feature/springdoc

xingyu 2 年之前
父節點
當前提交
4f2c08f8db
共有 68 個文件被更改,包括 2192 次插入1342 次删除
  1. 25 25
      README.md
  2. 56 77
      sql/mysql/ruoyi-vue-pro.sql
  3. 6 4
      sql/optional/visualization/jimureport.mysql5.7.create.sql
  4. 56 26
      sql/oracle/ruoyi-vue-pro.sql
  5. 45 25
      sql/postgresql/ruoyi-vue-pro.sql
  6. 68 32
      sql/sqlserver/ruoyi-vue-pro.sql
  7. 5 5
      yudao-dependencies/pom.xml
  8. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
  9. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml
  10. 0 1
      yudao-framework/yudao-spring-boot-starter-file/pom.xml
  11. 2 2
      yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
  12. 24 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/expression/AndExpressionX.java
  13. 24 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/expression/OrExpressionX.java
  14. 1 0
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/respVO.vm
  15. 3 6
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm
  16. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
  17. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
  18. 7 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dict/DictTypeDO.java
  19. 10 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dict/DictTypeMapper.java
  20. 1 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImpl.java
  21. 3 0
      yudao-ui-admin-vue3/.env
  22. 15 16
      yudao-ui-admin-vue3/README.md
  23. 2 2
      yudao-ui-admin-vue3/build/vite/index.ts
  24. 24 24
      yudao-ui-admin-vue3/package.json
  25. 312 467
      yudao-ui-admin-vue3/pnpm-lock.yaml
  26. 1 1
      yudao-ui-admin-vue3/src/api/system/user/profile.ts
  27. 1 1
      yudao-ui-admin-vue3/src/components/ContentDetailWrap/src/ContentDetailWrap.vue
  28. 4 0
      yudao-ui-admin-vue3/src/components/Cropper/index.ts
  29. 257 0
      yudao-ui-admin-vue3/src/components/Cropper/src/CopperModal.vue
  30. 190 0
      yudao-ui-admin-vue3/src/components/Cropper/src/Cropper.vue
  31. 136 0
      yudao-ui-admin-vue3/src/components/Cropper/src/CropperAvatar.vue
  32. 8 0
      yudao-ui-admin-vue3/src/components/Cropper/src/types.ts
  33. 2 1
      yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue
  34. 2 1
      yudao-ui-admin-vue3/src/components/Form/src/componentMap.ts
  35. 11 4
      yudao-ui-admin-vue3/src/components/Icon/src/Icon.vue
  36. 1 1
      yudao-ui-admin-vue3/src/components/Qrcode/src/Qrcode.vue
  37. 2 1
      yudao-ui-admin-vue3/src/components/UploadFile/index.ts
  38. 7 32
      yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue
  39. 236 145
      yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImg.vue
  40. 277 0
      yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImgs.vue
  41. 1 1
      yudao-ui-admin-vue3/src/config/axios/index.ts
  42. 69 4
      yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts
  43. 1 1
      yudao-ui-admin-vue3/src/hooks/web/useVxeCrudSchemas.ts
  44. 2 1
      yudao-ui-admin-vue3/src/layout/components/Breadcrumb/src/Breadcrumb.vue
  45. 26 24
      yudao-ui-admin-vue3/src/layout/components/Logo/src/Logo.vue
  46. 2 0
      yudao-ui-admin-vue3/src/layout/components/Menu/src/Menu.vue
  47. 14 0
      yudao-ui-admin-vue3/src/locales/en.ts
  48. 14 0
      yudao-ui-admin-vue3/src/locales/zh-CN.ts
  49. 1 1
      yudao-ui-admin-vue3/src/plugins/vxeTable/index.ts
  50. 1 1
      yudao-ui-admin-vue3/src/router/index.ts
  51. 2 2
      yudao-ui-admin-vue3/src/router/modules/remaining.ts
  52. 1 0
      yudao-ui-admin-vue3/src/types/components.d.ts
  53. 33 2
      yudao-ui-admin-vue3/src/utils/index.ts
  54. 8 1
      yudao-ui-admin-vue3/src/utils/routerHelper.ts
  55. 25 230
      yudao-ui-admin-vue3/src/views/Profile/components/UserAvatar.vue
  56. 2 3
      yudao-ui-admin-vue3/src/views/infra/apiErrorLog/index.vue
  57. 25 3
      yudao-ui-admin-vue3/src/views/infra/fileConfig/index.vue
  58. 1 2
      yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts
  59. 2 10
      yudao-ui-admin-vue3/src/views/system/dept/index.vue
  60. 49 139
      yudao-ui-admin-vue3/src/views/system/menu/index.vue
  61. 75 0
      yudao-ui-admin-vue3/src/views/system/menu/menu.data.ts
  62. 1 1
      yudao-ui-admin-vue3/src/views/system/notice/index.vue
  63. 1 4
      yudao-ui-admin-vue3/src/views/system/oauth2/client/client.data.ts
  64. 1 1
      yudao-ui-admin-vue3/src/views/system/user/index.vue
  65. 1 3
      yudao-ui-admin-vue3/src/views/system/user/user.data.ts
  66. 1 1
      yudao-ui-admin-vue3/tsconfig.json
  67. 2 1
      yudao-ui-admin-vue3/types/env.d.ts
  68. 1 1
      yudao-ui-admin-vue3/vite.config.ts

+ 25 - 25
README.md

@@ -169,21 +169,21 @@ ps:核心功能已经实现,正在对接微信小程序中...
 
 ## 🐨 技术栈
 
-| 项目                    | 说明                 |
-|-----------------------|--------------------|
-| `yudao-dependencies`  | Maven 依赖版本管理       |
-| `yudao-framework`     | Java 框架拓展          |
-| `yudao-server`        | 管理后台 + 用户 APP 的服务端 |
-| `yudao-ui-admin`      | 管理后台的 Vue2 前端项目        |
-| `yudao-ui-admin-vue3`      | 管理后台的 Vue3 前端项目        |
-| `yudao-ui-admin-uniapp`      | 管理后台的 uni-app 多端项目        |
-| `yudao-ui-app`       | 用户 APP 的 UI 界面     |
-| `yudao-module-system` | 系统功能的 Module 模块    |
-| `yudao-module-member` | 会员中心的 Module 模块    |
-| `yudao-module-infra`  | 基础设施的 Module 模块    |
-| `yudao-module-tool`   | 研发工具的 Module 模块    |
-| `yudao-module-bpm`    | 工作流程的 Module 模块    |
-| `yudao-module-pay`    | 支付系统的 Module 模块    |
+| 项目                      | 说明                 |
+|-------------------------|-----------------------|
+| `yudao-dependencies`    | Maven 依赖版本管理       |
+| `yudao-framework`       | Java 框架拓展          |
+| `yudao-server`          | 管理后台 + 用户 APP 的服务端 |
+| `yudao-ui-admin`        | 管理后台的 Vue2 前端项目     |
+| `yudao-ui-admin-vue3`   | 管理后台的 Vue3 前端项目     |
+| `yudao-ui-admin-uniapp` | 管理后台的 uni-app 多端项目  |
+| `yudao-ui-app`          | 用户 APP 的 UI 界面     |
+| `yudao-module-system`   | 系统功能的 Module 模块    |
+| `yudao-module-member`   | 会员中心的 Module 模块    |
+| `yudao-module-infra`    | 基础设施的 Module 模块    |
+| `yudao-module-tool`     | 研发工具的 Module 模块    |
+| `yudao-module-bpm`      | 工作流程的 Module 模块    |
+| `yudao-module-pay`      | 支付系统的 Module 模块    |
 
 ### 后端
 
@@ -193,7 +193,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | [MySQL](https://www.mysql.com/cn/)                                                          | 数据库服务器             | 5.7 / 8.0+  |                                                                |
 | [Druid](https://github.com/alibaba/druid)                                                   | JDBC 连接池、监控组件     | 1.2.15      | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | [MyBatis Plus](https://mp.baomidou.com/)                                                    | MyBatis 增强工具包       | 3.5.2       | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao)         |
-| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源               | 3.5.2       | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
+| [Dynamic Datasource](https://dynamic-datasource.com/)                                       | 动态数据源               | 3.6.0       | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | [Redis](https://redis.io/)                                                                  | key-value 数据库        | 5.0 / 6.0   |                                                                |
 | [Redisson](https://github.com/redisson/redisson)                                            | Redis 客户端            | 3.18.0      | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao)           |
 | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架               | 5.3.24      | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao)               |
@@ -204,12 +204,12 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | [Knife4j](https://gitee.com/xiaoym/knife4j)                                                 | Swagger 增强 UI 实现    | 3.0.3       | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao)         |
 | [Resilience4j](https://github.com/resilience4j/resilience4j)                                | 服务保障组件             | 1.7.1       | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao)    |
 | [SkyWalking](https://skywalking.apache.org/)                                                | 分布式应用追踪系统        | 8.12.0      | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao)      |
-| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台    | 2.7.7       | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
+| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin)                       | Spring Boot 监控平台    | 2.7.9       | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao)           |
 | [Jackson](https://github.com/FasterXML/jackson)                                             | JSON 工具库             | 2.13.3      |                                                                |
 | [MapStruct](https://mapstruct.org/)                                                         | Java Bean 转换         | 1.5.3.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao)       |
 | [Lombok](https://projectlombok.org/)                                                        | 消除冗长的 Java 代码     | 1.18.24     | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao)          |
 | [JUnit](https://junit.org/junit5/)                                                          | Java 单元测试框架        | 5.8.2       | -                                                              |
-| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架         | 4.8.2       | -                                                              |
+| [Mockito](https://github.com/mockito/mockito)                                               | Java Mock 框架         | 4.8.0       | -                                                              |
 
 ### [管理后台 Vue2 前端](./yudao-ui-admin)
 
@@ -223,12 +223,12 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 框架                                                                  |     说明      |   版本   |
 |----------------------------------------------------------------------|:------------:|:------:|
 | [Vue](https://staging-cn.vuejs.org/)                                 |   Vue 框架    | 3.2.45 |
-| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具  | 3.2.3  |
-| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus | 2.2.25 |
+| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具  | 4.0.2  |
+| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus | 2.2.27 |
 | [TypeScript](https://www.typescriptlang.org/docs/)                   |  TypeScript  | 4.9.4  |
-| [pinia](https://pinia.vuejs.org/)                                    |    vuex5     | 2.0.27 |
+| [pinia](https://pinia.vuejs.org/)                                    |    vuex5     | 2.0.28 |
 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) |    国际化     | 9.2.2  |
-| [vxe-table](https://vxetable.cn/)                                    |  vue最强表单  | 4.5.6  |
+| [vxe-table](https://vxetable.cn/)                                    |  vue最强表单  | 4.3.7  |
 
 ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
 
@@ -242,15 +242,15 @@ ps:核心功能已经实现,正在对接微信小程序中...
 ### 系统功能
 
 | 模块       | biu                                                                | biu                                                              | biu                                                              |
-|----------|--------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|
+|------------|--------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|
 | 登录 & 首页  | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg?imageView2/2/format/webp/w/1280)       | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg?imageView2/2/format/webp/w/1280)     | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg?imageView2/2/format/webp/w/1280) |
-| 用户 & 应用      | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg?imageView2/2/format/webp/w/1280)   | ![令牌管理](https://static.iocoder.cn/images/ruoyi-vue-pro/令牌管理.jpg?imageView2/2/format/webp/w/1280) | ![应用管理](https://static.iocoder.cn/images/ruoyi-vue-pro/应用管理.jpg?imageView2/2/format/webp/w/1280)                                                                |
+| 用户 & 应用  | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg?imageView2/2/format/webp/w/1280)   | ![令牌管理](https://static.iocoder.cn/images/ruoyi-vue-pro/令牌管理.jpg?imageView2/2/format/webp/w/1280) | ![应用管理](https://static.iocoder.cn/images/ruoyi-vue-pro/应用管理.jpg?imageView2/2/format/webp/w/1280)                                                                |
 | 租户 & 套餐  | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg?imageView2/2/format/webp/w/1280)   | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | -                                                                |
 | 部门 & 岗位  | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg?imageView2/2/format/webp/w/1280)   | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
 | 菜单 & 角色  | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg?imageView2/2/format/webp/w/1280)   | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
 | 审计日志     | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg?imageView2/2/format/webp/w/1280)   | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
 | 短信       | ![短信渠道](https://static.iocoder.cn/images/ruoyi-vue-pro/短信渠道.jpg?imageView2/2/format/webp/w/1280)   | ![短信模板](https://static.iocoder.cn/images/ruoyi-vue-pro/短信模板.jpg?imageView2/2/format/webp/w/1280) | ![短信日志](https://static.iocoder.cn/images/ruoyi-vue-pro/短信日志.jpg?imageView2/2/format/webp/w/1280) |
-| 字典 & 敏感词      | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg?imageView2/2/format/webp/w/1280)   | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg?imageView2/2/format/webp/w/1280) | ![敏感词](https://static.iocoder.cn/images/ruoyi-vue-pro/敏感词.jpg?imageView2/2/format/webp/w/1280)                                                                |
+| 字典 & 敏感词 | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg?imageView2/2/format/webp/w/1280)   | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg?imageView2/2/format/webp/w/1280) | ![敏感词](https://static.iocoder.cn/images/ruoyi-vue-pro/敏感词.jpg?imageView2/2/format/webp/w/1280)                                                                |
 | 错误码 & 通知 | ![错误码管理](https://static.iocoder.cn/images/ruoyi-vue-pro/错误码管理.jpg?imageView2/2/format/webp/w/1280) | ![通知公告](https://static.iocoder.cn/images/ruoyi-vue-pro/通知公告.jpg?imageView2/2/format/webp/w/1280) | -                                                                |
 
 ### 工作流程

+ 56 - 77
sql/mysql/ruoyi-vue-pro.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80026
  File Encoding         : 65001
 
- Date: 29/07/2022 00:33:25
+ Date: 20/12/2022 00:33:25
 */
 
 SET NAMES utf8mb4;
@@ -300,7 +300,7 @@ CREATE TABLE `bpm_form`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义';
 
 -- ----------------------------
 -- Records of bpm_form
@@ -329,7 +329,7 @@ CREATE TABLE `bpm_oa_leave`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 33 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OA 请假申请表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OA 请假申请表';
 
 -- ----------------------------
 -- Records of bpm_oa_leave
@@ -359,7 +359,7 @@ CREATE TABLE `bpm_process_definition_ext`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 135 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
 
 -- ----------------------------
 -- Records of bpm_process_definition_ext
@@ -389,7 +389,7 @@ CREATE TABLE `bpm_process_instance_ext`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 290 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
 
 -- ----------------------------
 -- Records of bpm_process_instance_ext
@@ -415,7 +415,7 @@ CREATE TABLE `bpm_task_assign_rule`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 265 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
 
 -- ----------------------------
 -- Records of bpm_task_assign_rule
@@ -444,7 +444,7 @@ CREATE TABLE `bpm_task_ext`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 341 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
 
 -- ----------------------------
 -- Records of bpm_task_ext
@@ -469,7 +469,7 @@ CREATE TABLE `bpm_user_group`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 111 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户组';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户组';
 
 -- ----------------------------
 -- Records of bpm_user_group
@@ -504,7 +504,7 @@ CREATE TABLE `infra_api_access_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 35822 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
 
 -- ----------------------------
 -- Records of infra_api_access_log
@@ -546,7 +546,7 @@ CREATE TABLE `infra_api_error_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 647 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
 
 -- ----------------------------
 -- Records of infra_api_error_log
@@ -584,7 +584,7 @@ CREATE TABLE `infra_codegen_column`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1126 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
 
 -- ----------------------------
 -- Records of infra_codegen_column
@@ -616,7 +616,7 @@ CREATE TABLE `infra_codegen_table`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 99 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
 
 -- ----------------------------
 -- Records of infra_codegen_table
@@ -673,7 +673,7 @@ CREATE TABLE `infra_data_source_config`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '数据源配置表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '数据源配置表';
 
 -- ----------------------------
 -- Records of infra_data_source_config
@@ -699,7 +699,7 @@ CREATE TABLE `infra_file`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 93 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file
@@ -753,7 +753,7 @@ CREATE TABLE `infra_file_content`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file_content
@@ -781,13 +781,13 @@ CREATE TABLE `infra_job`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
 
 -- ----------------------------
 -- Records of infra_job
 -- ----------------------------
 BEGIN;
-INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5, '支付通知 Job', 2, 'payNotifyJob', NULL, '* * * * * ?', 0, 0, 0, '1', '2021-10-27 08:34:42', '1', '2022-04-03 20:35:25', b'0');
+INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '支付通知 Job', 2, 'payNotifyJob', NULL, '* * * * * ?', 0, 0, 0, '1', '2021-10-27 08:34:42', '1', '2022-04-03 20:35:25', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -811,7 +811,7 @@ CREATE TABLE `infra_job_log`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 25295 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
 
 -- ----------------------------
 -- Records of infra_job_log
@@ -836,7 +836,7 @@ CREATE TABLE `infra_test_demo`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 108 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
 
 -- ----------------------------
 -- Records of infra_test_demo
@@ -866,7 +866,7 @@ CREATE TABLE `member_user`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   UNIQUE INDEX `uk_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
-) ENGINE = InnoDB AUTO_INCREMENT = 248 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户';
 
 -- ----------------------------
 -- Records of member_user
@@ -893,7 +893,7 @@ CREATE TABLE `pay_app`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付应用信息';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付应用信息';
 
 -- ----------------------------
 -- Records of pay_app
@@ -921,7 +921,7 @@ CREATE TABLE `pay_channel`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付渠道\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付渠道\n';
 
 -- ----------------------------
 -- Records of pay_channel
@@ -947,7 +947,7 @@ CREATE TABLE `pay_merchant`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付商户信息';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付商户信息';
 
 -- ----------------------------
 -- Records of pay_merchant
@@ -972,7 +972,7 @@ CREATE TABLE `pay_notify_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 363051 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付通知 App 的日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付通知 App 的日志';
 
 -- ----------------------------
 -- Records of pay_notify_log
@@ -1004,7 +1004,7 @@ CREATE TABLE `pay_notify_task`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 112 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商户支付、退款等的通知\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商户支付、退款等的通知\n';
 
 -- ----------------------------
 -- Records of pay_notify_task
@@ -1048,7 +1048,7 @@ CREATE TABLE `pay_order`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 125 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付订单\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付订单\n';
 
 -- ----------------------------
 -- Records of pay_order
@@ -1077,7 +1077,7 @@ CREATE TABLE `pay_order_extension`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 124 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付订单\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '支付订单\n';
 
 -- ----------------------------
 -- Records of pay_order_extension
@@ -1166,8 +1166,6 @@ INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`,
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, '运维部门', 101, 5, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:33', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, '市场部门', 102, 1, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-02-16 08:35:45', b'0', 1);
 INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, '财务部门', 102, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:29', b'0', 1);
-INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (110, '新部门', 0, 1, NULL, NULL, NULL, 0, '110', '2022-02-23 20:46:30', '110', '2022-02-23 20:46:30', b'0', 121);
-INSERT INTO `system_dept` (`id`, `name`, `parent_id`, `sort`, `leader_user_id`, `phone`, `email`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, '顶级部门', 0, 1, NULL, NULL, NULL, 0, '113', '2022-03-07 21:44:50', '113', '2022-03-07 21:44:50', b'0', 122);
 COMMIT;
 
 -- ----------------------------
@@ -1335,19 +1333,20 @@ COMMIT;
 -- Table structure for system_dict_type
 -- ----------------------------
 DROP TABLE IF EXISTS `system_dict_type`;
-CREATE TABLE `system_dict_type`  (
-  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典主键',
-  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典名称',
-  `type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典类型',
-  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0正常 1停用)',
-  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
-  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
-  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
-  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
-  PRIMARY KEY (`id`) USING BTREE,
-  UNIQUE INDEX `dict_type`(`type` ASC) USING BTREE
+CREATE TABLE `system_dict_type`(
+   `id`           bigint                                                        NOT NULL AUTO_INCREMENT COMMENT '字典主键',
+   `name`         varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典名称',
+   `type`         varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '字典类型',
+   `status`       tinyint                                                       NOT NULL DEFAULT 0 COMMENT '状态(0正常 1停用)',
+   `remark`       varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
+   `creator`      varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+   `create_time`  datetime                                                      NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+   `updater`      varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+   `update_time`  datetime                                                      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+   `deleted_time` datetime                                                               DEFAULT NULL COMMENT '删除时间',
+   `deleted`      bit(1)                                                        NOT NULL DEFAULT b'0' COMMENT '是否删除',
+   PRIMARY KEY (`id`) USING BTREE,
+   UNIQUE INDEX `dict_type`(`type` ASC,`deleted_time` ASC) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 149 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
 
 -- ----------------------------
@@ -1413,7 +1412,7 @@ CREATE TABLE `system_error_code`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 5832 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
 
 -- ----------------------------
 -- Records of system_error_code
@@ -1442,7 +1441,7 @@ CREATE TABLE `system_login_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1670 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
 
 -- ----------------------------
 -- Records of system_login_log
@@ -1761,7 +1760,7 @@ CREATE TABLE `system_oauth2_access_token`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 404 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_access_token
@@ -1788,7 +1787,7 @@ CREATE TABLE `system_oauth2_approve`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 79 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 批准表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 批准表';
 
 -- ----------------------------
 -- Records of system_oauth2_approve
@@ -1854,7 +1853,7 @@ CREATE TABLE `system_oauth2_code`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 103 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 授权码表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 授权码表';
 
 -- ----------------------------
 -- Records of system_oauth2_code
@@ -1881,7 +1880,7 @@ CREATE TABLE `system_oauth2_refresh_token`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 295 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '刷新令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_refresh_token
@@ -1921,7 +1920,7 @@ CREATE TABLE `system_operate_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 2764 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
 
 -- ----------------------------
 -- Records of system_operate_log
@@ -1947,7 +1946,7 @@ CREATE TABLE `system_post`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表';
 
 -- ----------------------------
 -- Records of system_post
@@ -2303,7 +2302,7 @@ CREATE TABLE `system_sms_code`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
-) ENGINE = InnoDB AUTO_INCREMENT = 480 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
 
 -- ----------------------------
 -- Records of system_sms_code
@@ -2346,7 +2345,7 @@ CREATE TABLE `system_sms_log`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 337 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
 
 -- ----------------------------
 -- Records of system_sms_log
@@ -2416,7 +2415,7 @@ CREATE TABLE `system_social_user`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
 
 -- ----------------------------
 -- Records of system_social_user
@@ -2441,7 +2440,7 @@ CREATE TABLE `system_social_user_bind`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
 
 -- ----------------------------
 -- Records of system_social_user_bind
@@ -2527,12 +2526,7 @@ CREATE TABLE `system_user_post`  (
 -- Records of system_user_post
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (112, 1, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1);
-INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 100, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1);
-INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 114, 3, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1);
-INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 104, 1, '1', '2022-05-16 19:36:28', '1', '2022-05-16 19:36:28', b'0', 1);
-INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (116, 117, 2, '1', '2022-07-09 17:40:26', '1', '2022-07-09 17:40:26', b'0', 1);
-INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (117, 118, 1, '1', '2022-07-09 17:44:44', '1', '2022-07-09 17:44:44', b'0', 1);
+INSERT INTO `system_user_post` (`id`, `user_id`, `post_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0', 1);
 COMMIT;
 
 -- ----------------------------
@@ -2550,7 +2544,7 @@ CREATE TABLE `system_user_role`  (
   `deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
+) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
 
 -- ----------------------------
 -- Records of system_user_role
@@ -2558,21 +2552,6 @@ CREATE TABLE `system_user_role`  (
 BEGIN;
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:17', b'0', 1);
 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:13', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 100, 101, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:13', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 100, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:12', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 100, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:11', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 103, 1, '1', '2022-01-11 13:19:45', '1', '2022-01-11 13:19:45', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 107, 106, '1', '2022-02-20 22:59:33', '1', '2022-02-20 22:59:33', b'0', 118);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 108, 107, '1', '2022-02-20 23:00:50', '1', '2022-02-20 23:00:50', b'0', 119);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (13, 109, 108, '1', '2022-02-20 23:11:50', '1', '2022-02-20 23:11:50', b'0', 120);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (14, 110, 109, '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (15, 111, 110, '110', '2022-02-23 13:14:38', '110', '2022-02-23 13:14:38', b'0', 121);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (16, 113, 111, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (17, 114, 101, '1', '2022-03-19 21:51:13', '1', '2022-03-19 21:51:13', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (19, 116, 113, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (20, 104, 101, '1', '2022-05-28 15:43:57', '1', '2022-05-28 15:43:57', b'0', 1);
-INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (22, 115, 2, '1', '2022-07-21 22:08:30', '1', '2022-07-21 22:08:30', b'0', 1);
 COMMIT;
 
 -- ----------------------------
@@ -2595,7 +2574,7 @@ CREATE TABLE `system_user_session`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户在线 Session';
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户在线 Session';
 
 -- ----------------------------
 -- Records of system_user_session

+ 6 - 4
sql/optional/visualization/jimureport.mysql5.7.create.sql

@@ -418,17 +418,19 @@ CREATE TABLE `jimu_report_data_source`  (
   `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
   `update_time` datetime NULL DEFAULT NULL COMMENT '更新日期',
   `connect_times` int(1) UNSIGNED NULL DEFAULT 0 COMMENT '连接失败次数',
+  `tenant_id` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '多租户标识',
+  `type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型(report:报表;drag:仪表盘)',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_jmdatasource_report_id`(`report_id`) USING BTREE,
   INDEX `idx_jmdatasource_code`(`code`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
 
 -- ----------------------------
 -- Records of jimu_report_data_source
 -- ----------------------------
-INSERT INTO `jimu_report_data_source` VALUES ('1324261983692902402', 'jeewx', '1324261770294071296', '', NULL, 'MYSQL', 'com.mysql.jdbc.Driver', 'jdbc:mysql://127.0.0.1:3306/jeewx-boot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8', 'root', 'root', 'jeecg', '2020-11-05 16:07:15', NULL, '2020-11-05 16:07:15', 0);
-INSERT INTO `jimu_report_data_source` VALUES ('26d21fe4f27920d2f56abc8d90a8e527', 'oracle', '1308645288868712448', '', NULL, 'ORACLE', 'oracle.jdbc.OracleDriver', 'jdbc:oracle:thin:@192.168.1.199:1521:helowin', 'jeecgbootbpm', 'jeecg196283', 'admin', '2021-01-05 19:26:24', NULL, '2021-01-05 19:26:24', 1);
-INSERT INTO `jimu_report_data_source` VALUES ('8f90daf47d15d35ca6cf420748b8b9ba', 'localhost', '1316944968992034816', '', NULL, 'MYSQL5.7', 'com.mysql.cj.jdbc.Driver', 'jdbc:mysql://127.0.0.1:3306/jeecg-boot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8', 'root', 'root', 'admin', '2021-01-13 14:34:00', NULL, '2021-01-13 14:34:00', 0);
+INSERT INTO `jimu_report_data_source` VALUES ('1324261983692902402', 'jeewx', '1324261770294071296', '', NULL, 'MYSQL', 'com.mysql.jdbc.Driver', 'jdbc:mysql://127.0.0.1:3306/jeewx-boot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8', 'root', 'root', 'jeecg', '2020-11-05 16:07:15', NULL, '2020-11-05 16:07:15', 0, NULL, 'report');
+INSERT INTO `jimu_report_data_source` VALUES ('26d21fe4f27920d2f56abc8d90a8e527', 'oracle', '1308645288868712448', '', NULL, 'ORACLE', 'oracle.jdbc.OracleDriver', 'jdbc:oracle:thin:@192.168.1.199:1521:helowin', 'jeecgbootbpm', 'jeecg196283', 'admin', '2021-01-05 19:26:24', NULL, '2021-01-05 19:26:24', 1, NULL, 'report');
+INSERT INTO `jimu_report_data_source` VALUES ('8f90daf47d15d35ca6cf420748b8b9ba', 'localhost', '1316944968992034816', '', NULL, 'MYSQL5.7', 'com.mysql.cj.jdbc.Driver', 'jdbc:mysql://127.0.0.1:3306/jeecg-boot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8', 'root', 'root', 'admin', '2021-01-13 14:34:00', NULL, '2021-01-13 14:34:00', 0, NULL, 'report');
 
 -- ----------------------------
 -- Table structure for jimu_report_db

+ 56 - 26
sql/oracle/ruoyi-vue-pro.sql

@@ -2474,17 +2474,18 @@ COMMIT;
 -- Table structure for SYSTEM_DICT_TYPE
 -- ----------------------------
 DROP TABLE "SYSTEM_DICT_TYPE";
-CREATE TABLE "SYSTEM_DICT_TYPE" (
-  "ID" NUMBER(20,0) NOT NULL,
-  "NAME" NVARCHAR2(100),
-  "TYPE" NVARCHAR2(100),
-  "STATUS" NUMBER(4,0) NOT NULL,
-  "REMARK" NVARCHAR2(500),
-  "CREATOR" NVARCHAR2(64),
-  "CREATE_TIME" DATE NOT NULL,
-  "UPDATER" NVARCHAR2(64),
-  "UPDATE_TIME" DATE NOT NULL,
-  "DELETED" NUMBER(1,0) DEFAULT 0 NOT NULL
+CREATE TABLE "SYSTEM_DICT_TYPE"(
+                                   "ID"           NUMBER(20,0) NOT NULL,
+                                   "NAME"         NVARCHAR2(100),
+                                   "TYPE"         NVARCHAR2(100),
+                                   "STATUS"       NUMBER(4,0) NOT NULL,
+                                   "REMARK"       NVARCHAR2(500),
+                                   "CREATOR"      NVARCHAR2(64),
+                                   "CREATE_TIME"  DATE NOT NULL,
+                                   "UPDATER"      NVARCHAR2(64),
+                                   "UPDATE_TIME"  DATE NOT NULL,
+                                   "DELETED_TIME" DATE,
+                                   "DELETED"      NUMBER(1,0) DEFAULT 0 NOT NULL
 )
 LOGGING
 NOCOMPRESS
@@ -2503,25 +2504,54 @@ PARALLEL 1
 NOCACHE
 DISABLE ROW MOVEMENT
 ;
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."ID" IS '字典主键';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."NAME" IS '字典名称';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."TYPE" IS '字典类型';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."STATUS" IS '状态(0正常 1停用)';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."REMARK" IS '备注';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."CREATOR" IS '创建者';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."CREATE_TIME" IS '创建时间';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."UPDATER" IS '更新者';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."UPDATE_TIME" IS '更新时间';
-COMMENT ON COLUMN "SYSTEM_DICT_TYPE"."DELETED" IS '是否删除';
-COMMENT ON TABLE "SYSTEM_DICT_TYPE" IS '字典类型表';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."ID" IS '字典主键';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."NAME" IS '字典名称';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."TYPE" IS '字典类型';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."STATUS" IS '状态(0正常 1停用)';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."REMARK" IS '备注';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."CREATOR" IS '创建者';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."CREATE_TIME" IS '创建时间';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."UPDATER" IS '更新者';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."UPDATE_TIME" IS '更新时间';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."DELETED_TIME" IS '删除时间';
+COMMENT
+ON COLUMN "SYSTEM_DICT_TYPE"."DELETED" IS '是否删除';
+COMMENT
+ON TABLE "SYSTEM_DICT_TYPE" IS '字典类型表';
 
 -- ----------------------------
 -- Records of SYSTEM_DICT_TYPE
 -- ----------------------------
-INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('1', '用户性别', 'system_user_sex', '0', NULL, 'admin', TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-05-01 12:55:56', 'SYYYY-MM-DD HH24:MI:SS'), '0');
-INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('6', '参数类型', 'infra_config_type', '0', NULL, 'admin', TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), NULL, TO_DATE('2022-02-01 16:36:54', 'SYYYY-MM-DD HH24:MI:SS'), '0');
-INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('7', '通知类型', 'system_notice_type', '0', NULL, 'admin', TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), NULL, TO_DATE('2022-02-01 16:35:26', 'SYYYY-MM-DD HH24:MI:SS'), '0');
-INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('9', '操作类型', 'system_operate_type', '0', NULL, 'admin', TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), '1', TO_DATE('2022-02-16 09:32:21', 'SYYYY-MM-DD HH24:MI:SS'), '0');
+INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER",
+                                "UPDATE_TIME", "DELETED")
+VALUES ('1', '用户性别', 'system_user_sex', '0', NULL, 'admin',
+        TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), '1',
+        TO_DATE('2022-05-01 12:55:56', 'SYYYY-MM-DD HH24:MI:SS'), '0');
+INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER",
+                                "UPDATE_TIME", "DELETED")
+VALUES ('6', '参数类型', 'infra_config_type', '0', NULL, 'admin',
+        TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), NULL,
+        TO_DATE('2022-02-01 16:36:54', 'SYYYY-MM-DD HH24:MI:SS'), '0');
+INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER",
+                                "UPDATE_TIME", "DELETED")
+VALUES ('7', '通知类型', 'system_notice_type', '0', NULL, 'admin',
+        TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), NULL,
+        TO_DATE('2022-02-01 16:35:26', 'SYYYY-MM-DD HH24:MI:SS'), '0');
+INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER",
+                                "UPDATE_TIME", "DELETED")
+VALUES ('9', '操作类型', 'system_operate_type', '0', NULL, 'admin',
+        TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), '1',
+        TO_DATE('2022-02-16 09:32:21', 'SYYYY-MM-DD HH24:MI:SS'), '0');
 INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('10', '系统状态', 'common_status', '0', NULL, 'admin', TO_DATE('2021-01-05 17:03:48', 'SYYYY-MM-DD HH24:MI:SS'), NULL, TO_DATE('2022-02-01 16:21:28', 'SYYYY-MM-DD HH24:MI:SS'), '0');
 INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('11', 'Boolean 是否类型', 'infra_boolean_string', '0', 'boolean 转是否', NULL, TO_DATE('2021-01-19 03:20:08', 'SYYYY-MM-DD HH24:MI:SS'), NULL, TO_DATE('2022-02-01 16:37:10', 'SYYYY-MM-DD HH24:MI:SS'), '0');
 INSERT INTO "SYSTEM_DICT_TYPE" ("ID", "NAME", "TYPE", "STATUS", "REMARK", "CREATOR", "CREATE_TIME", "UPDATER", "UPDATE_TIME", "DELETED") VALUES ('104', '登陆结果', 'system_login_result', '0', '登陆结果', NULL, TO_DATE('2021-01-18 06:17:11', 'SYYYY-MM-DD HH24:MI:SS'), NULL, TO_DATE('2022-02-01 16:36:00', 'SYYYY-MM-DD HH24:MI:SS'), '0');

+ 45 - 25
sql/postgresql/ruoyi-vue-pro.sql

@@ -2357,38 +2357,58 @@ COMMIT;
 -- Table structure for system_dict_type
 -- ----------------------------
 DROP TABLE IF EXISTS "system_dict_type";
-CREATE TABLE "system_dict_type" (
-  "id" int8 NOT NULL,
-  "name" varchar(100) COLLATE "pg_catalog"."default" NOT NULL,
-  "type" varchar(100) COLLATE "pg_catalog"."default" NOT NULL,
-  "status" int2 NOT NULL,
-  "remark" varchar(500) COLLATE "pg_catalog"."default",
-  "creator" varchar(64) COLLATE "pg_catalog"."default",
-  "create_time" timestamp(6) NOT NULL,
-  "updater" varchar(64) COLLATE "pg_catalog"."default",
-  "update_time" timestamp(6) NOT NULL,
-  "deleted" int2 NOT NULL DEFAULT 0
+CREATE TABLE "system_dict_type"(
+                                   "id"           int8                                        NOT NULL,
+                                   "name"         varchar(100) COLLATE "pg_catalog"."default" NOT NULL,
+                                   "type"         varchar(100) COLLATE "pg_catalog"."default" NOT NULL,
+                                   "status"       int2                                        NOT NULL,
+                                   "remark"       varchar(500) COLLATE "pg_catalog"."default",
+                                   "creator"      varchar(64) COLLATE "pg_catalog"."default",
+                                   "create_time"  timestamp(6)                                NOT NULL,
+                                   "updater"      varchar(64) COLLATE "pg_catalog"."default",
+                                   "update_time"  timestamp(6)                                NOT NULL,
+                                   "deleted_time" timestamp(6),
+                                   "deleted"      int2                                        NOT NULL DEFAULT 0
 )
 ;
-COMMENT ON COLUMN "system_dict_type"."id" IS '字典主键';
-COMMENT ON COLUMN "system_dict_type"."name" IS '字典名称';
-COMMENT ON COLUMN "system_dict_type"."type" IS '字典类型';
-COMMENT ON COLUMN "system_dict_type"."status" IS '状态(0正常 1停用)';
-COMMENT ON COLUMN "system_dict_type"."remark" IS '备注';
-COMMENT ON COLUMN "system_dict_type"."creator" IS '创建者';
-COMMENT ON COLUMN "system_dict_type"."create_time" IS '创建时间';
-COMMENT ON COLUMN "system_dict_type"."updater" IS '更新者';
-COMMENT ON COLUMN "system_dict_type"."update_time" IS '更新时间';
-COMMENT ON COLUMN "system_dict_type"."deleted" IS '是否删除';
-COMMENT ON TABLE "system_dict_type" IS '字典类型表';
+COMMENT
+ON COLUMN "system_dict_type"."id" IS '字典主键';
+COMMENT
+ON COLUMN "system_dict_type"."name" IS '字典名称';
+COMMENT
+ON COLUMN "system_dict_type"."type" IS '字典类型';
+COMMENT
+ON COLUMN "system_dict_type"."status" IS '状态(0正常 1停用)';
+COMMENT
+ON COLUMN "system_dict_type"."remark" IS '备注';
+COMMENT
+ON COLUMN "system_dict_type"."creator" IS '创建者';
+COMMENT
+ON COLUMN "system_dict_type"."create_time" IS '创建时间';
+COMMENT
+ON COLUMN "system_dict_type"."updater" IS '更新者';
+COMMENT
+ON COLUMN "system_dict_type"."update_time" IS '更新时间';
+COMMENT
+ON COLUMN "system_dict_type"."deleted_time" IS '删除时间';
+COMMENT
+ON COLUMN "system_dict_type"."deleted" IS '是否删除';
+COMMENT
+ON TABLE "system_dict_type" IS '字典类型表';
 
 -- ----------------------------
 -- Records of system_dict_type
 -- ----------------------------
 BEGIN;
-INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater", "update_time", "deleted") VALUES (1, '用户性别', 'system_user_sex', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:30:31', 0);
-INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater", "update_time", "deleted") VALUES (6, '参数类型', 'infra_config_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:36:54', 0);
-INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater", "update_time", "deleted") VALUES (7, '通知类型', 'system_notice_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:35:26', 0);
+INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater",
+                                "update_time", "deleted")
+VALUES (1, '用户性别', 'system_user_sex', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:30:31', 0);
+INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater",
+                                "update_time", "deleted")
+VALUES (6, '参数类型', 'infra_config_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:36:54', 0);
+INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater",
+                                "update_time", "deleted")
+VALUES (7, '通知类型', 'system_notice_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:35:26', 0);
 INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater", "update_time", "deleted") VALUES (9, '操作类型', 'system_operate_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 09:32:21', 0);
 INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater", "update_time", "deleted") VALUES (10, '系统状态', 'common_status', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:21:28', 0);
 INSERT INTO "system_dict_type" ("id", "name", "type", "status", "remark", "creator", "create_time", "updater", "update_time", "deleted") VALUES (11, 'Boolean 是否类型', 'infra_boolean_string', 0, 'boolean 转是否', '', '2021-01-19 03:20:08', '', '2022-02-01 16:37:10', 0);

+ 68 - 32
sql/sqlserver/ruoyi-vue-pro.sql

@@ -5813,18 +5813,51 @@ IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[sy
 	DROP TABLE [dbo].[system_dict_type]
 GO
 
-CREATE TABLE [dbo].[system_dict_type] (
-  [id] bigint  IDENTITY(1,1) NOT NULL,
-  [name] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
-  [type] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS  NOT NULL,
-  [status] tinyint  NOT NULL,
-  [remark] nvarchar(500) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
-  [creator] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
-  [create_time] datetime2(7)  NOT NULL,
-  [updater] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS  NULL,
-  [update_time] datetime2(7)  NOT NULL,
-  [deleted] bit DEFAULT 0 NOT NULL
-)
+CREATE TABLE [dbo].[system_dict_type]
+(
+    [
+    id]
+    bigint
+    IDENTITY
+(
+    1,
+    1
+) NOT NULL,
+    [name] nvarchar
+(
+    100
+) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
+    [type] nvarchar
+(
+    100
+) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
+    [status] tinyint NOT NULL,
+    [remark] nvarchar
+(
+    500
+) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
+    [creator] nvarchar
+(
+    64
+) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
+    [create_time] datetime2
+(
+    7
+) NOT NULL,
+    [updater] nvarchar
+(
+    64
+) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
+    [update_time] datetime2
+(
+    7
+) NOT NULL,
+    [deleted_time] datetime2
+(
+    7
+),
+    [deleted] bit DEFAULT 0 NOT NULL
+    )
 GO
 
 ALTER TABLE [dbo].[system_dict_type] SET (LOCK_ESCALATION = TABLE)
@@ -5882,26 +5915,29 @@ GO
 EXEC sp_addextendedproperty
 'MS_Description', N'更新者',
 'SCHEMA', N'dbo',
-'TABLE', N'system_dict_type',
-'COLUMN', N'updater'
-GO
-
-EXEC sp_addextendedproperty
-'MS_Description', N'更新时间',
-'SCHEMA', N'dbo',
-'TABLE', N'system_dict_type',
-'COLUMN', N'update_time'
-GO
-
-EXEC sp_addextendedproperty
-'MS_Description', N'是否删除',
-'SCHEMA', N'dbo',
-'TABLE', N'system_dict_type',
-'COLUMN', N'deleted'
-GO
-
-EXEC sp_addextendedproperty
-'MS_Description', N'字典类型表',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'updater'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'更新时间',
+    'SCHEMA', N'dbo',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'update_time'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'删除时间',
+    'SCHEMA', N'dbo',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'deleted_time'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'是否删除',
+    'SCHEMA', N'dbo',
+    'TABLE', N'system_dict_type',
+    'COLUMN', N'deleted'
+    GO
+    EXEC sp_addextendedproperty
+    'MS_Description', N'字典类型表',
 'SCHEMA', N'dbo',
 'TABLE', N'system_dict_type'
 GO

+ 5 - 5
yudao-dependencies/pom.xml

@@ -24,15 +24,15 @@
         <!-- DB 相关 -->
         <druid.version>1.2.15</druid.version>
         <mybatis-plus.version>3.5.2</mybatis-plus.version>
-        <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
-        <dynamic-datasource.version>3.5.2</dynamic-datasource.version>
+        <mybatis-plus-generator.version>3.5.3</mybatis-plus-generator.version>
+        <dynamic-datasource.version>3.6.0</dynamic-datasource.version>
         <redisson.version>3.18.0</redisson.version>
         <!-- 服务保障相关 -->
         <lock4j.version>2.2.3</lock4j.version>
         <resilience4j.version>1.7.1</resilience4j.version>
         <!-- 监控相关 -->
         <skywalking.version>8.12.0</skywalking.version>
-        <spring-boot-admin.version>2.7.7</spring-boot-admin.version>
+        <spring-boot-admin.version>2.7.9</spring-boot-admin.version>
         <opentracing.version>0.33.0</opentracing.version>
         <!-- Test 测试相关 -->
         <podam.version>7.2.11.RELEASE</podam.version>
@@ -53,7 +53,7 @@
         <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
         <commons-net.version>3.8.0</commons-net.version>
         <jsch.version>0.1.55</jsch.version>
-        <tika-core.version>2.5.0</tika-core.version>
+        <tika-core.version>2.6.0</tika-core.version>
         <aj-captcha.version>1.3.0</aj-captcha.version>
         <netty-all.version>4.1.85.Final</netty-all.version>
         <!-- 三方云服务相关 -->
@@ -64,7 +64,7 @@
         <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
         <tencentcloud-sdk-java.version>3.1.637</tencentcloud-sdk-java.version>
         <justauth.version>1.4.0</justauth.version>
-        <jimureport.version>1.5.4</jimureport.version>
+        <jimureport.version>1.5.6</jimureport.version>
         <xercesImpl.version>2.12.2</xercesImpl.version>
     </properties>
 

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
+import cn.iocoder.yudao.framework.expression.OrExpressionX;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
@@ -20,7 +21,6 @@ import net.sf.jsqlparser.expression.Alias;
 import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.LongValue;
 import net.sf.jsqlparser.expression.NullValue;
-import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
 import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
 import net.sf.jsqlparser.expression.operators.relational.InExpression;
@@ -143,8 +143,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
         if (userExpression == null) {
             return deptExpression;
         }
-        // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE dept_id IN ? OR user_id = ?
-        return new OrExpression(deptExpression, userExpression);
+        // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
+        return new OrExpressionX(deptExpression, userExpression);
     }
 
     private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml

@@ -52,7 +52,7 @@
         <dependency>
             <groupId>com.alipay.sdk</groupId>
             <artifactId>alipay-sdk-java</artifactId>
-            <version>4.34.71.ALL</version>
+            <version>4.35.0.ALL</version>
             <exclusions>
                 <exclusion>
                     <groupId>org.bouncycastle</groupId>

+ 0 - 1
yudao-framework/yudao-spring-boot-starter-file/pom.xml

@@ -46,7 +46,6 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.13.4</version>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml

@@ -30,8 +30,8 @@
 
         <!-- DB 相关 -->
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
         </dependency>
         <dependency>
             <groupId>com.oracle.database.jdbc</groupId>

+ 24 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/expression/AndExpressionX.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.framework.expression;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+
+/**
+ * AndExpression 的扩展类(会在原有表达式两端加上括号)
+ */
+public class AndExpressionX extends AndExpression {
+
+    public AndExpressionX() {
+    }
+
+    public AndExpressionX(Expression leftExpression, Expression rightExpression) {
+        this.setLeftExpression(leftExpression);
+        this.setRightExpression(rightExpression);
+    }
+
+    @Override
+    public String toString() {
+        return "(" + super.toString() + ")";
+    }
+
+}

+ 24 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/expression/OrExpressionX.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.framework.expression;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
+
+/**
+ * OrExpression 的扩展类(会在原有表达式两端加上括号)
+ */
+public class OrExpressionX extends OrExpression {
+
+    public OrExpressionX() {
+    }
+
+    public OrExpressionX(Expression leftExpression, Expression rightExpression) {
+        this.setLeftExpression(leftExpression);
+        this.setRightExpression(rightExpression);
+    }
+
+    @Override
+    public String toString() {
+        return "(" + super.toString() + ")";
+    }
+
+}

+ 1 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/java/controller/vo/respVO.vm

@@ -4,6 +4,7 @@ import lombok.*;
 #foreach ($column in $columns)
 #if (${column.javaType} == "LocalDateTime")
 import java.time.LocalDateTime;
+#break
 #end
 #end
 import io.swagger.annotations.*;

+ 3 - 6
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm

@@ -34,9 +34,9 @@ const crudSchemas = reactive<VxeCrudSchema>({
       #if ("" != $dictType)## 有数据字典
       dictType: DICT_TYPE.$dictType.toUpperCase(),
       #if (${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer")
-      dictData: 'number',
+      dictClass: 'number',
       #else
-      dictData: 'string',
+      dictClass: 'string',
       #end
       #end
       #if (!$column.createOperation && !$column.updateOperation)
@@ -78,10 +78,7 @@ const crudSchemas = reactive<VxeCrudSchema>({
       },
       #elseif($column.htmlType == "imageUpload")## 图片上传
       form: {
-        component: 'UploadImg',
-        componentProps: {
-          limit: 1
-        }
+        component: 'UploadImg' // 单图上传,多图为UploadImgs
       },
       #elseif($column.htmlType == "fileUpload")## 图片上传
       form: {

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java

@@ -103,7 +103,7 @@ public class PayOrderServiceImpl implements PayOrderService {
         if (order != null) {
             log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
                     order.getMerchantOrderId(), JsonUtils.toJsonString(order)); // 理论来说,不会出现这个情况
-            return app.getId();
+            return order.getId();
         }
 
         // 创建支付交易单

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java

@@ -130,7 +130,7 @@ public class PayRefundServiceImpl implements PayRefundService {
         } else {
             // 成功,插入退款单 状态为生成.没有和渠道交互
             // TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
-            payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
+            payRefundDO = PayRefundDO.builder()
                     .appId(order.getAppId())
                     .channelOrderNo(order.getChannelOrderNo())
                     .channelCode(order.getChannelCode())

+ 7 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dict/DictTypeDO.java

@@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
+import java.time.LocalDateTime;
+
 /**
  * 字典类型表
  *
@@ -47,4 +49,9 @@ public class DictTypeDO extends BaseDO {
      */
     private String remark;
 
+    /**
+     * 删除时间
+     */
+    private LocalDateTime deletedTime;
+
 }

+ 10 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dict/DictTypeMapper.java

@@ -7,8 +7,12 @@ import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeExpo
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
+import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 @Mapper
@@ -39,4 +43,10 @@ public interface DictTypeMapper extends BaseMapperX<DictTypeDO> {
         return selectOne(DictTypeDO::getName, name);
     }
 
+    @Update("UPDATE system_dict_type SET DELETED = 1,DELETED_TIME=#{deletedTime} WHERE id = #{id}")
+    int deleteById(@Param("id") Long id, @Param("deletedTime") LocalDateTime deletedTime);
+
+    default int deleteById(Long id) {
+        return deleteById(id, LocalDateTime.now());
+    }
 }

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImpl.java

@@ -13,6 +13,7 @@ import com.google.common.annotations.VisibleForTesting;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;

+ 3 - 0
yudao-ui-admin-vue3/.env

@@ -12,3 +12,6 @@ VITE_APP_TENANT_ENABLE=true
 
 # 验证码的开关
 VITE_APP_CAPTCHA_ENABLE=true
+
+# 路由在只有一个子集的时候是否显示父级
+VITE_ROUTE_ALWAYSSHOW_ENABLE=true

+ 15 - 16
yudao-ui-admin-vue3/README.md

@@ -1,21 +1,20 @@
-<h1>🌈 yudao-ui-admin-vue3</h1>
-
-<p align="center">
-    <img src="https://img.shields.io/badge/-Vue3.2-34495e?logo=vue.j" />
-    <img src="https://img.shields.io/badge/-Vite3-646cff?logo=vite&logoColor=white" />
-    <img src="https://img.shields.io/badge/-TypeScript4.9-blue?logo=typescript&logoColor=white" />
-    <img src="https://img.shields.io/badge/-Pinia2-yellow?logo=picpay&logoColor=white" />
-    <img src="https://img.shields.io/badge/-ESLint-4b32c3?logo=eslint&logoColor=white" />
-    <img src="https://img.shields.io/badge/-pnpm7-F69220?logo=pnpm&logoColor=white" />
-    <img src="https://img.shields.io/badge/-Axios-008fc7?logo=axios.js&logoColor=white" />
+# 🌈 yudao-ui-admin-vue3 #
+
+<p style="text-align: center">
+    <img src="https://img.shields.io/badge/-Vue3.2-34495e?logo=vue.j" alt="vue" />
+    <img src="https://img.shields.io/badge/-Vite4-646cff?logo=vite&logoColor=white" alt="vite" />
+    <img src="https://img.shields.io/badge/-TypeScript4.9-blue?logo=typescript&logoColor=white" alt="typescript" />
+    <img src="https://img.shields.io/badge/-Pinia2-yellow?logo=picpay&logoColor=white" alt="Pinia2" />
+    <img src="https://img.shields.io/badge/-ESLint-4b32c3?logo=eslint&logoColor=white" alt="eslint" />
+    <img src="https://img.shields.io/badge/-pnpm7-F69220?logo=pnpm&logoColor=white" alt="pnpm" />
     <img src="https://img.shields.io/badge/-Prettier-ef9421?logo=Prettier&logoColor=white" alt="Prettier">
     <img src="https://img.shields.io/badge/-Sass-1D365D?logo=Sass&logoColor=white" alt="Sass">
-    <img src="https://img.shields.io/badge/-Wind%20CSS-06B6D4?logo=Tailwind%20CSS&logoColor=white" alt="Taiwind">
+    <img src="https://img.shields.io/badge/-Wind%20CSS-06B6D4?logo=Tailwind%20CSS&logoColor=white" alt="WindCSS">
 </p>
 
 ## 介绍
 
-- 基于 vue3.2+ ,TypeScript ,Element Plus 2.2.0+ ,Vite3 ,Pinia ,Vxe-table , Windicss 等开发的后台管理系统
+- 基于 vue3.2+ ,TypeScript ,Element Plus 2.2.0+ ,Vite4 ,Pinia ,Vxe-table , Windicss 等开发的后台管理系统
 
 ## 注意事项
 
@@ -30,12 +29,12 @@
 | 框架 | 说明 | 版本 |
 | --- | --- | --- |
 | [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.45 |
-| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.2.3 |
-| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.23 |
+| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.0.2 |
+| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.27 |
 | [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.9.4 |
-| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.26 |
+| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.28 |
 | [vueuse](https://vueuse.org/) | 常用工具集 | 9.6.0 |
-| [vxe-table](https://vxetable.cn/) | vue 最强表单 | 4.3.6 |
+| [vxe-table](https://vxetable.cn/) | vue 最强表单 | 4.3.7 |
 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
 | [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.6 |
 | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |

+ 2 - 2
yudao-ui-admin-vue3/build/vite/index.ts

@@ -1,7 +1,6 @@
 import { resolve } from 'path'
 import Vue from '@vitejs/plugin-vue'
 import VueJsx from '@vitejs/plugin-vue-jsx'
-import VueI18n from '@intlify/vite-plugin-vue-i18n'
 import WindiCSS from 'vite-plugin-windicss'
 import progress from 'vite-plugin-progress'
 import EslintPlugin from 'vite-plugin-eslint'
@@ -9,6 +8,7 @@ import PurgeIcons from 'vite-plugin-purge-icons'
 import { ViteEjsPlugin } from 'vite-plugin-ejs'
 import viteCompression from 'vite-plugin-compression'
 import vueSetupExtend from 'vite-plugin-vue-setup-extend'
+import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
 import {
   createStyleImportPlugin,
@@ -51,7 +51,7 @@ export function createVitePlugins(VITE_APP_TITLE: string) {
       cache: false,
       include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
     }),
-    VueI18n({
+    VueI18nPlugin({
       runtimeOnly: true,
       compositionOnly: true,
       include: [resolve(__dirname, 'src/locales/**')]

+ 24 - 24
yudao-ui-admin-vue3/package.json

@@ -1,7 +1,7 @@
 {
   "name": "yudao-ui-admin-vue3",
-  "version": "1.6.5.1874",
-  "description": "基于vue3、vite3、element-plus、typesScript",
+  "version": "1.6.5.1878",
+  "description": "基于vue3、vite4、element-plus、typesScript",
   "author": "xingyu",
   "private": false,
   "scripts": {
@@ -25,67 +25,67 @@
   },
   "dependencies": {
     "@iconify/iconify": "^3.0.1",
-    "@vueuse/core": "^9.6.0",
+    "@vueuse/core": "^9.8.2",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.10",
     "@zxcvbn-ts/core": "^2.1.0",
     "animate.css": "^4.1.1",
     "axios": "^1.2.1",
+    "cropperjs": "^1.5.13",
     "crypto-js": "^4.1.1",
     "dayjs": "^1.11.7",
-    "echarts": "^5.4.0",
+    "echarts": "^5.4.1",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "2.2.26",
+    "element-plus": "2.2.27",
     "intro.js": "^6.0.0",
     "jsencrypt": "^3.3.1",
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.0",
     "nprogress": "^0.2.0",
-    "pinia": "^2.0.27",
+    "pinia": "^2.0.28",
     "qrcode": "^1.5.1",
     "qs": "^6.11.0",
     "url": "^0.11.0",
     "vue": "3.2.45",
-    "vue-cropper": "^1.0.3",
     "vue-i18n": "9.2.2",
     "vue-router": "^4.1.6",
     "vue-types": "^5.0.1",
-    "vxe-table": "^4.3.6",
+    "vxe-table": "^4.3.7",
     "web-storage-cache": "^1.1.1",
     "xe-utils": "^3.5.7"
   },
   "devDependencies": {
     "@commitlint/cli": "^17.3.0",
     "@commitlint/config-conventional": "^17.3.0",
-    "@iconify/json": "^2.1.149",
-    "@intlify/vite-plugin-vue-i18n": "^6.0.3",
+    "@iconify/json": "^2.1.155",
+    "@intlify/unplugin-vue-i18n": "^0.8.1",
     "@purge-icons/generated": "^0.9.0",
     "@types/intro.js": "^5.1.0",
     "@types/lodash-es": "^4.17.6",
-    "@types/node": "^18.11.11",
+    "@types/node": "^18.11.17",
     "@types/nprogress": "^0.2.0",
     "@types/qrcode": "^1.5.0",
     "@types/qs": "^6.9.7",
-    "@typescript-eslint/eslint-plugin": "^5.46.0",
-    "@typescript-eslint/parser": "^5.46.0",
-    "@vitejs/plugin-legacy": "^2.3.1",
-    "@vitejs/plugin-vue": "^3.2.0",
-    "@vitejs/plugin-vue-jsx": "^2.1.1",
+    "@typescript-eslint/eslint-plugin": "^5.47.0",
+    "@typescript-eslint/parser": "^5.47.0",
+    "@vitejs/plugin-legacy": "^3.0.1",
+    "@vitejs/plugin-vue": "^4.0.0",
+    "@vitejs/plugin-vue-jsx": "^3.0.0",
     "autoprefixer": "^10.4.13",
     "consola": "^2.15.3",
-    "eslint": "^8.29.0",
+    "eslint": "^8.30.0",
     "eslint-config-prettier": "^8.5.0",
     "eslint-define-config": "^1.12.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^9.8.0",
     "lint-staged": "^13.1.0",
-    "postcss": "^8.4.19",
+    "postcss": "^8.4.20",
     "postcss-html": "^1.5.0",
     "postcss-scss": "^4.0.6",
     "prettier": "^2.8.1",
     "rimraf": "^3.0.2",
-    "rollup": "^3.7.0",
-    "sass": "^1.56.1",
+    "rollup": "^3.7.5",
+    "sass": "^1.57.1",
     "stylelint": "^14.16.0",
     "stylelint-config-html": "^1.1.0",
     "stylelint-config-prettier": "^9.0.4",
@@ -94,17 +94,17 @@
     "stylelint-order": "^5.0.0",
     "terser": "^5.16.1",
     "typescript": "4.9.4",
-    "vite": "3.2.5",
+    "vite": "4.0.2",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-ejs": "^1.6.4",
     "vite-plugin-eslint": "^1.8.1",
     "vite-plugin-progress": "^0.0.6",
-    "vite-plugin-purge-icons": "^0.9.1",
+    "vite-plugin-purge-icons": "^0.9.2",
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-vue-setup-extend": "^0.4.0",
-    "vite-plugin-windicss": "^1.8.8",
-    "vue-tsc": "^1.0.11",
+    "vite-plugin-windicss": "^1.8.10",
+    "vue-tsc": "^1.0.16",
     "windicss": "^3.5.6"
   },
   "engines": {

文件差異過大導致無法顯示
+ 312 - 467
yudao-ui-admin-vue3/pnpm-lock.yaml


+ 1 - 1
yudao-ui-admin-vue3/src/api/system/user/profile.ts

@@ -73,5 +73,5 @@ export const updateUserPwdApi = (oldPassword: string, newPassword: string) => {
 
 // 用户头像上传
 export const uploadAvatarApi = (data) => {
-  return request.put({ url: '/system/user/profile/update-avatar', data })
+  return request.upload({ url: '/system/user/profile/update-avatar', data: data })
 }

+ 1 - 1
yudao-ui-admin-vue3/src/components/ContentDetailWrap/src/ContentDetailWrap.vue

@@ -2,7 +2,7 @@
 import { ElCard } from 'element-plus'
 import { propTypes } from '@/utils/propTypes'
 import { useDesign } from '@/hooks/web/useDesign'
-import { ref, onMounted, defineEmits } from 'vue'
+import { ref, onMounted } from 'vue'
 import { Sticky } from '@/components/Sticky'
 import { useI18n } from '@/hooks/web/useI18n'
 const { t } = useI18n()

+ 4 - 0
yudao-ui-admin-vue3/src/components/Cropper/index.ts

@@ -0,0 +1,4 @@
+import CropperImage from './src/Cropper.vue'
+import CropperAvatar from './src/CropperAvatar.vue'
+
+export { CropperImage, CropperAvatar }

+ 257 - 0
yudao-ui-admin-vue3/src/components/Cropper/src/CopperModal.vue

@@ -0,0 +1,257 @@
+<template>
+  <div>
+    <Dialog
+      v-model="dialogVisible"
+      :title="t('cropper.modalTitle')"
+      width="800px"
+      maxHeight="380px"
+      :canFullscreen="false"
+    >
+      <div :class="prefixCls">
+        <div :class="`${prefixCls}-left`">
+          <div :class="`${prefixCls}-cropper`">
+            <CropperImage
+              v-if="src"
+              :src="src"
+              height="300px"
+              :circled="circled"
+              @cropend="handleCropend"
+              @ready="handleReady"
+            />
+          </div>
+
+          <div :class="`${prefixCls}-toolbar`">
+            <el-upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
+              <el-tooltip :content="t('cropper.selectImage')" placement="bottom">
+                <XButton preIcon="ant-design:upload-outlined" type="primary" />
+              </el-tooltip>
+            </el-upload>
+            <el-space>
+              <el-tooltip :content="t('cropper.btn_reset')" placement="bottom">
+                <XButton
+                  type="primary"
+                  preIcon="ant-design:reload-outlined"
+                  size="small"
+                  :disabled="!src"
+                  @click="handlerToolbar('reset')"
+                />
+              </el-tooltip>
+              <el-tooltip :content="t('cropper.btn_rotate_left')" placement="bottom">
+                <XButton
+                  type="primary"
+                  preIcon="ant-design:rotate-left-outlined"
+                  size="small"
+                  :disabled="!src"
+                  @click="handlerToolbar('rotate', -45)"
+                />
+              </el-tooltip>
+              <el-tooltip :content="t('cropper.btn_rotate_right')" placement="bottom">
+                <XButton
+                  type="primary"
+                  preIcon="ant-design:rotate-right-outlined"
+                  size="small"
+                  :disabled="!src"
+                  @click="handlerToolbar('rotate', 45)"
+                />
+              </el-tooltip>
+              <el-tooltip :content="t('cropper.btn_scale_x')" placement="bottom">
+                <XButton
+                  type="primary"
+                  preIcon="vaadin:arrows-long-h"
+                  size="small"
+                  :disabled="!src"
+                  @click="handlerToolbar('scaleX')"
+                />
+              </el-tooltip>
+              <el-tooltip :content="t('cropper.btn_scale_y')" placement="bottom">
+                <XButton
+                  type="primary"
+                  preIcon="vaadin:arrows-long-v"
+                  size="small"
+                  :disabled="!src"
+                  @click="handlerToolbar('scaleY')"
+                />
+              </el-tooltip>
+              <el-tooltip :content="t('cropper.btn_zoom_in')" placement="bottom">
+                <XButton
+                  type="primary"
+                  preIcon="ant-design:zoom-in-outlined"
+                  size="small"
+                  :disabled="!src"
+                  @click="handlerToolbar('zoom', 0.1)"
+                />
+              </el-tooltip>
+              <el-tooltip :content="t('cropper.btn_zoom_out')" placement="bottom">
+                <XButton
+                  type="primary"
+                  preIcon="ant-design:zoom-out-outlined"
+                  size="small"
+                  :disabled="!src"
+                  @click="handlerToolbar('zoom', -0.1)"
+                />
+              </el-tooltip>
+            </el-space>
+          </div>
+        </div>
+        <div :class="`${prefixCls}-right`">
+          <div :class="`${prefixCls}-preview`">
+            <img :src="previewSource" v-if="previewSource" :alt="t('cropper.preview')" />
+          </div>
+          <template v-if="previewSource">
+            <div :class="`${prefixCls}-group`">
+              <el-avatar :src="previewSource" size="large" />
+              <el-avatar :src="previewSource" :size="48" />
+              <el-avatar :src="previewSource" :size="64" />
+              <el-avatar :src="previewSource" :size="80" />
+            </div>
+          </template>
+        </div>
+      </div>
+      <template #footer>
+        <el-button type="primary" @click="handleOk">{{ t('cropper.okText') }}</el-button>
+      </template>
+    </Dialog>
+  </div>
+</template>
+<script setup lang="ts">
+import { useDesign } from '@/hooks/web/useDesign'
+import { dataURLtoBlob } from '@/utils/filt'
+import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { ElUpload, ElAvatar, ElTooltip, ElSpace } from 'element-plus'
+import { Dialog } from '@/components/Dialog'
+import { CropperImage } from '@/components/Cropper'
+import type { CropendResult, Cropper } from './types'
+import { propTypes } from '@/utils/propTypes'
+
+const props = defineProps({
+  srcValue: propTypes.string.def(''),
+  circled: propTypes.bool.def(true)
+})
+const emit = defineEmits(['uploadSuccess'])
+const { t } = useI18n()
+const { getPrefixCls } = useDesign()
+const prefixCls = getPrefixCls('cropper-am')
+
+const src = ref(props.srcValue)
+const previewSource = ref('')
+const cropper = ref<Cropper>()
+const dialogVisible = ref(false)
+let filename = ''
+let scaleX = 1
+let scaleY = 1
+
+// Block upload
+function handleBeforeUpload(file: File) {
+  const reader = new FileReader()
+  reader.readAsDataURL(file)
+  src.value = ''
+  previewSource.value = ''
+  reader.onload = function (e) {
+    src.value = (e.target?.result as string) ?? ''
+    filename = file.name
+  }
+  return false
+}
+
+function handleCropend({ imgBase64 }: CropendResult) {
+  previewSource.value = imgBase64
+}
+
+function handleReady(cropperInstance: Cropper) {
+  cropper.value = cropperInstance
+}
+
+function handlerToolbar(event: string, arg?: number) {
+  if (event === 'scaleX') {
+    scaleX = arg = scaleX === -1 ? 1 : -1
+  }
+  if (event === 'scaleY') {
+    scaleY = arg = scaleY === -1 ? 1 : -1
+  }
+  cropper?.value?.[event]?.(arg)
+}
+
+async function handleOk() {
+  const blob = dataURLtoBlob(previewSource.value)
+  emit('uploadSuccess', { source: previewSource.value, data: blob, filename: filename })
+}
+function openModal() {
+  dialogVisible.value = true
+}
+function closeModal() {
+  dialogVisible.value = false
+}
+defineExpose({ openModal, closeModal })
+</script>
+<style lang="scss">
+$prefix-cls: #{$namespace}-cropper-am;
+
+.#{$prefix-cls} {
+  display: flex;
+
+  &-left,
+  &-right {
+    height: 340px;
+  }
+
+  &-left {
+    width: 55%;
+  }
+
+  &-right {
+    width: 45%;
+  }
+
+  &-cropper {
+    height: 300px;
+    background: #eee;
+    background-image: linear-gradient(
+        45deg,
+        rgb(0 0 0 / 25%) 25%,
+        transparent 0,
+        transparent 75%,
+        rgb(0 0 0 / 25%) 0
+      ),
+      linear-gradient(
+        45deg,
+        rgb(0 0 0 / 25%) 25%,
+        transparent 0,
+        transparent 75%,
+        rgb(0 0 0 / 25%) 0
+      );
+    background-position: 0 0, 12px 12px;
+    background-size: 24px 24px;
+  }
+
+  &-toolbar {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-top: 10px;
+  }
+
+  &-preview {
+    width: 220px;
+    height: 220px;
+    margin: 0 auto;
+    overflow: hidden;
+    border: 1px solid;
+    border-radius: 50%;
+
+    img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  &-group {
+    display: flex;
+    padding-top: 8px;
+    margin-top: 8px;
+    border-top: 1px solid;
+    justify-content: space-around;
+    align-items: center;
+  }
+}
+</style>

+ 190 - 0
yudao-ui-admin-vue3/src/components/Cropper/src/Cropper.vue

@@ -0,0 +1,190 @@
+<template>
+  <div :class="getClass" :style="getWrapperStyle">
+    <img
+      v-show="isReady"
+      ref="imgElRef"
+      :src="src"
+      :alt="alt"
+      :crossorigin="crossorigin"
+      :style="getImageStyle"
+    />
+  </div>
+</template>
+<script setup lang="ts">
+import {
+  computed,
+  CSSProperties,
+  onMounted,
+  onUnmounted,
+  PropType,
+  ref,
+  unref,
+  useAttrs
+} from 'vue'
+import Cropper from 'cropperjs'
+import 'cropperjs/dist/cropper.css'
+import { useDesign } from '@/hooks/web/useDesign'
+import { useDebounceFn } from '@vueuse/core'
+import { propTypes } from '@/utils/propTypes'
+
+type Options = Cropper.Options
+
+const defaultOptions: Options = {
+  aspectRatio: 1,
+  zoomable: true,
+  zoomOnTouch: true,
+  zoomOnWheel: true,
+  cropBoxMovable: true,
+  cropBoxResizable: true,
+  toggleDragModeOnDblclick: true,
+  autoCrop: true,
+  background: true,
+  highlight: true,
+  center: true,
+  responsive: true,
+  restore: true,
+  checkCrossOrigin: true,
+  checkOrientation: true,
+  scalable: true,
+  modal: true,
+  guides: true,
+  movable: true,
+  rotatable: true
+}
+
+const props = defineProps({
+  src: propTypes.string.def(''),
+  alt: propTypes.string.def(''),
+  circled: propTypes.bool.def(false),
+  realTimePreview: propTypes.bool.def(true),
+  height: propTypes.string.def('360px'),
+  crossorigin: {
+    type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
+    default: undefined
+  },
+  imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
+  options: { type: Object as PropType<Options>, default: () => ({}) }
+})
+
+const emit = defineEmits(['cropend', 'ready', 'cropendError'])
+const attrs = useAttrs()
+const imgElRef = ref<ElRef<HTMLImageElement>>()
+const cropper = ref<Nullable<Cropper>>()
+const isReady = ref(false)
+
+const { getPrefixCls } = useDesign()
+const prefixCls = getPrefixCls('cropper-image')
+const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80)
+
+const getImageStyle = computed((): CSSProperties => {
+  return {
+    height: props.height,
+    maxWidth: '100%',
+    ...props.imageStyle
+  }
+})
+
+const getClass = computed(() => {
+  return [
+    prefixCls,
+    attrs.class,
+    {
+      [`${prefixCls}--circled`]: props.circled
+    }
+  ]
+})
+const getWrapperStyle = computed((): CSSProperties => {
+  return { height: `${props.height}`.replace(/px/, '') + 'px' }
+})
+
+onMounted(init)
+
+onUnmounted(() => {
+  cropper.value?.destroy()
+})
+
+async function init() {
+  const imgEl = unref(imgElRef)
+  if (!imgEl) {
+    return
+  }
+  cropper.value = new Cropper(imgEl, {
+    ...defaultOptions,
+    ready: () => {
+      isReady.value = true
+      realTimeCroppered()
+      emit('ready', cropper.value)
+    },
+    crop() {
+      debounceRealTimeCroppered()
+    },
+    zoom() {
+      debounceRealTimeCroppered()
+    },
+    cropmove() {
+      debounceRealTimeCroppered()
+    },
+    ...props.options
+  })
+}
+
+// Real-time display preview
+function realTimeCroppered() {
+  props.realTimePreview && croppered()
+}
+
+// event: return base64 and width and height information after cropping
+function croppered() {
+  if (!cropper.value) {
+    return
+  }
+  let imgInfo = cropper.value.getData()
+  const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas()
+  canvas.toBlob((blob) => {
+    if (!blob) {
+      return
+    }
+    let fileReader: FileReader = new FileReader()
+    fileReader.readAsDataURL(blob)
+    fileReader.onloadend = (e) => {
+      emit('cropend', {
+        imgBase64: e.target?.result ?? '',
+        imgInfo
+      })
+    }
+    fileReader.onerror = () => {
+      emit('cropendError')
+    }
+  }, 'image/png')
+}
+
+// Get a circular picture canvas
+function getRoundedCanvas() {
+  const sourceCanvas = cropper.value!.getCroppedCanvas()
+  const canvas = document.createElement('canvas')
+  const context = canvas.getContext('2d')!
+  const width = sourceCanvas.width
+  const height = sourceCanvas.height
+  canvas.width = width
+  canvas.height = height
+  context.imageSmoothingEnabled = true
+  context.drawImage(sourceCanvas, 0, 0, width, height)
+  context.globalCompositeOperation = 'destination-in'
+  context.beginPath()
+  context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true)
+  context.fill()
+  return canvas
+}
+</script>
+<style lang="scss">
+$prefix-cls: #{$namespace}-cropper-image;
+
+.#{$prefix-cls} {
+  &--circled {
+    .cropper-view-box,
+    .cropper-face {
+      border-radius: 50%;
+    }
+  }
+}
+</style>

+ 136 - 0
yudao-ui-admin-vue3/src/components/Cropper/src/CropperAvatar.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="user-info-head" @click="open()">
+    <img :src="sourceValue" v-if="sourceValue" class="img-circle img-lg" alt="avatar" />
+    <el-button :class="`${prefixCls}-upload-btn`" @click="open()" v-if="showBtn">
+      {{ btnText ? btnText : t('cropper.selectImage') }}
+    </el-button>
+    <CopperModal
+      ref="cropperModelRef"
+      @upload-success="handleUploadSuccess"
+      :srcValue="sourceValue"
+    />
+  </div>
+</template>
+<script setup lang="ts">
+import { useDesign } from '@/hooks/web/useDesign'
+import { useMessage } from '@/hooks/web/useMessage'
+import { propTypes } from '@/utils/propTypes'
+import { ref, watch, watchEffect } from 'vue'
+import { useI18n } from 'vue-i18n'
+import CopperModal from './CopperModal.vue'
+
+const props = defineProps({
+  width: propTypes.string.def('200px'),
+  value: propTypes.string.def(''),
+  showBtn: propTypes.bool.def(true),
+  btnText: propTypes.string.def('')
+})
+
+const emit = defineEmits(['update:value', 'change'])
+const sourceValue = ref(props.value)
+const { getPrefixCls } = useDesign()
+const prefixCls = getPrefixCls('cropper-avatar')
+const message = useMessage()
+const { t } = useI18n()
+
+const cropperModelRef = ref()
+
+watchEffect(() => {
+  sourceValue.value = props.value
+})
+
+watch(
+  () => sourceValue.value,
+  (v: string) => {
+    emit('update:value', v)
+  }
+)
+
+function handleUploadSuccess({ source, data, filename }) {
+  sourceValue.value = source
+  emit('change', { source, data, filename })
+  message.success(t('cropper.uploadSuccess'))
+}
+
+function open() {
+  cropperModelRef.value.openModal()
+}
+function close() {
+  cropperModelRef.value.closeModal()
+}
+defineExpose({
+  open,
+  close
+})
+</script>
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}--cropper-avatar;
+
+.#{$prefix-cls} {
+  display: inline-block;
+  text-align: center;
+
+  &-image-wrapper {
+    overflow: hidden;
+    cursor: pointer;
+    border: 1px solid;
+    border-radius: 50%;
+
+    img {
+      width: 100%;
+    }
+  }
+
+  &-image-mask {
+    opacity: 0%;
+    position: absolute;
+    width: inherit;
+    height: inherit;
+    border-radius: inherit;
+    border: inherit;
+    background: rgb(0 0 0 / 40%);
+    cursor: pointer;
+    transition: opacity 0.4s;
+
+    ::v-deep(svg) {
+      margin: auto;
+    }
+  }
+
+  &-image-mask:hover {
+    opacity: 4000%;
+  }
+
+  &-upload-btn {
+    margin: 10px auto;
+  }
+}
+.user-info-head {
+  position: relative;
+  display: inline-block;
+}
+.img-circle {
+  border-radius: 50%;
+}
+.img-lg {
+  width: 120px;
+  height: 120px;
+}
+.user-info-head:hover:after {
+  content: '+';
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  color: #eee;
+  background: rgba(0, 0, 0, 0.5);
+  font-size: 24px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  cursor: pointer;
+  line-height: 110px;
+  border-radius: 50%;
+}
+</style>

+ 8 - 0
yudao-ui-admin-vue3/src/components/Cropper/src/types.ts

@@ -0,0 +1,8 @@
+import type Cropper from 'cropperjs'
+
+export interface CropendResult {
+  imgBase64: string
+  imgInfo: Cropper.Data
+}
+
+export type { Cropper }

+ 2 - 1
yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue

@@ -23,6 +23,7 @@ const props = defineProps({
     type: Object as PropType<IEditorConfig>,
     default: () => undefined
   },
+  readonly: propTypes.bool.def(false),
   modelValue: propTypes.string.def('')
 })
 
@@ -61,7 +62,7 @@ const editorConfig = computed((): IEditorConfig => {
   return Object.assign(
     {
       placeholder: '请输入内容...',
-      readOnly: false,
+      readOnly: props.readonly,
       customAlert: (s: string, t: string) => {
         switch (t) {
           case 'success':

+ 2 - 1
yudao-ui-admin-vue3/src/components/Form/src/componentMap.ts

@@ -21,7 +21,7 @@ import {
 } from 'element-plus'
 import { InputPassword } from '@/components/InputPassword'
 import { Editor } from '@/components/Editor'
-import { UploadImg, UploadFile } from '@/components/UploadFile'
+import { UploadImg, UploadImgs, UploadFile } from '@/components/UploadFile'
 import { ComponentName } from '@/types/components'
 
 const componentMap: Recordable<Component, ComponentName> = {
@@ -48,6 +48,7 @@ const componentMap: Recordable<Component, ComponentName> = {
   InputPassword: InputPassword,
   Editor: Editor,
   UploadImg: UploadImg,
+  UploadImgs: UploadImgs,
   UploadFile: UploadFile
 }
 

+ 11 - 4
yudao-ui-admin-vue3/src/components/Icon/src/Icon.vue

@@ -15,7 +15,9 @@ const props = defineProps({
   // icon color
   color: propTypes.string,
   // icon size
-  size: propTypes.number.def(16)
+  size: propTypes.number.def(16),
+  // icon svg class
+  svgClass: propTypes.string.def('')
 })
 
 const elRef = ref<ElRef>(null)
@@ -34,6 +36,11 @@ const getIconifyStyle = computed(() => {
   }
 })
 
+const getSvgClass = computed(() => {
+  const { svgClass } = props
+  return `iconify ${svgClass}`
+})
+
 const updateIcon = async (icon: string) => {
   if (unref(isLocal)) return
 
@@ -66,13 +73,13 @@ watch(
 </script>
 
 <template>
-  <ElIcon :class="prefixCls" :size="size" :color="color">
-    <svg v-if="isLocal" aria-hidden="true">
+  <ElIcon :class="prefixCls" :color="color" :size="size">
+    <svg v-if="isLocal" aria-hidden="true" :class="getSvgClass">
       <use :xlink:href="symbolId" />
     </svg>
 
     <span v-else ref="elRef" :class="$attrs.class" :style="getIconifyStyle">
-      <span class="iconify" :data-icon="symbolId"></span>
+      <span :class="getSvgClass" :data-icon="symbolId"></span>
     </span>
   </ElIcon>
 </template>

+ 1 - 1
yudao-ui-admin-vue3/src/components/Qrcode/src/Qrcode.vue

@@ -64,7 +64,7 @@ const initQrcode = async () => {
       options.errorCorrectionLevel || getErrorCorrectionLevel(unref(renderText))
     const _width: number = await getOriginWidth(unref(renderText), options)
     options.scale = props.width === 0 ? undefined : (props.width / _width) * 4
-    const canvasRef: HTMLCanvasElement = await toCanvas(
+    const canvasRef: HTMLCanvasElement | any = await toCanvas(
       unref(wrapRef) as HTMLCanvasElement,
       unref(renderText),
       options

+ 2 - 1
yudao-ui-admin-vue3/src/components/UploadFile/index.ts

@@ -1,4 +1,5 @@
 import UploadImg from './src/UploadImg.vue'
+import UploadImgs from './src/UploadImgs.vue'
 import UploadFile from './src/UploadFile.vue'
 
-export { UploadImg, UploadFile }
+export { UploadImg, UploadImgs, UploadFile }

+ 7 - 32
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue

@@ -32,8 +32,8 @@
     </el-upload>
   </div>
 </template>
-<script setup lang="ts">
-import { ref, watch } from 'vue'
+<script setup lang="ts" name="UploadFile">
+import { PropType, ref } from 'vue'
 import { useMessage } from '@/hooks/web/useMessage'
 import { propTypes } from '@/utils/propTypes'
 import { getAccessToken, getTenantId } from '@/utils/auth'
@@ -43,7 +43,10 @@ const message = useMessage() // 消息弹窗
 const emit = defineEmits(['update:modelValue'])
 
 const props = defineProps({
-  modelValue: propTypes.oneOfType([String, Object, Array]),
+  modelValue: {
+    type: Array as PropType<UploadUserFile[]>,
+    required: true
+  },
   title: propTypes.string.def('文件上传'),
   updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
   fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
@@ -57,40 +60,12 @@ const props = defineProps({
 const valueRef = ref(props.modelValue)
 const uploadRef = ref<UploadInstance>()
 const uploadList = ref<UploadUserFile[]>([])
-const fileList = ref<UploadUserFile[]>([])
+const fileList = ref<UploadUserFile[]>(props.modelValue)
 const uploadNumber = ref<number>(0)
 const uploadHeaders = ref({
   Authorization: 'Bearer ' + getAccessToken(),
   'tenant-id': getTenantId()
 })
-watch(
-  () => props.modelValue,
-  (val) => {
-    if (val) {
-      // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
-      const list = Array.isArray(props.modelValue)
-        ? props.modelValue
-        : Array.isArray(props.modelValue?.split(','))
-        ? props.modelValue?.split(',')
-        : Array.of(props.modelValue)
-      // 然后将数组转为对象数组
-      fileList.value = list.map((item) => {
-        if (typeof item === 'string') {
-          // edit by 芋道源码
-          item = { name: item, url: item }
-        }
-        return item
-      })
-    } else {
-      fileList.value = []
-      return []
-    }
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
 // 文件上传之前判断
 const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
   if (fileList.value.length >= props.limit) {

+ 236 - 145
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImg.vue

@@ -1,176 +1,267 @@
 <template>
-  <div class="component-upload-image">
+  <div class="upload-box">
     <el-upload
-      ref="uploadRef"
-      :multiple="props.limit > 1"
-      name="file"
-      v-model="valueRef"
-      list-type="picture-card"
-      v-model:file-list="fileList"
-      :show-file-list="true"
       :action="updateUrl"
+      :id="uuid"
+      :class="['upload', drag ? 'no-border' : '']"
+      :multiple="false"
+      :show-file-list="false"
       :headers="uploadHeaders"
-      :limit="props.limit"
       :before-upload="beforeUpload"
-      :on-exceed="handleExceed"
-      :on-success="handleFileSuccess"
-      :on-error="excelUploadError"
-      :on-remove="handleRemove"
-      :on-preview="handlePictureCardPreview"
-      :class="{ hide: fileList.length >= props.limit }"
+      :on-success="uploadSuccess"
+      :on-error="uploadError"
+      :drag="drag"
+      :accept="fileType.join(',')"
     >
-      <Icon icon="ep:upload-filled" />
+      <template v-if="modelValue">
+        <img :src="modelValue" class="upload-image" />
+        <div class="upload-handle" @click.stop>
+          <div class="handle-icon" @click="editImg">
+            <Icon icon="ep:edit" />
+            <span>{{ t('action.edit') }}</span>
+          </div>
+          <div class="handle-icon" @click="imgViewVisible = true">
+            <Icon icon="ep:zoom-in" />
+            <span>{{ t('action.detail') }}</span>
+          </div>
+          <div class="handle-icon" @click="deleteImg">
+            <Icon icon="ep:delete" />
+            <span>{{ t('action.del') }}</span>
+          </div>
+        </div>
+      </template>
+      <template v-else>
+        <div class="upload-empty">
+          <slot name="empty">
+            <Icon icon="ep:plus" />
+            <!-- <span>请上传图片</span> -->
+          </slot>
+        </div>
+      </template>
     </el-upload>
+    <div class="el-upload__tip">
+      <slot name="tip"></slot>
+    </div>
+    <el-image-viewer
+      v-if="imgViewVisible"
+      @close="imgViewVisible = false"
+      :url-list="[modelValue]"
+    />
   </div>
-  <!-- 文件列表 -->
-  <Dialog v-model="dialogVisible" title="预览" width="800" append-to-body>
-    <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
-  </Dialog>
 </template>
-<script setup lang="ts">
-import { ref, watch } from 'vue'
-import { Dialog } from '@/components/Dialog'
+
+<script setup lang="ts" name="UploadImg">
+import { ref } from 'vue'
+import type { UploadProps } from 'element-plus'
+import { ElUpload, ElNotification, ElImageViewer } from 'element-plus'
+import { useI18n } from '@/hooks/web/useI18n'
 import { useMessage } from '@/hooks/web/useMessage'
+import { generateUUID } from '@/utils'
 import { propTypes } from '@/utils/propTypes'
 import { getAccessToken, getTenantId } from '@/utils/auth'
-import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
 
-const message = useMessage() // 消息弹窗
-const emit = defineEmits(['update:modelValue'])
+type FileTypes =
+  | 'image/apng'
+  | 'image/bmp'
+  | 'image/gif'
+  | 'image/jpeg'
+  | 'image/pjpeg'
+  | 'image/png'
+  | 'image/svg+xml'
+  | 'image/tiff'
+  | 'image/webp'
+  | 'image/x-icon'
 
+// 接受父组件参数
 const props = defineProps({
-  modelValue: propTypes.oneOfType([String, Object, Array]),
-  title: propTypes.string.def('图片上传'),
+  modelValue: propTypes.string.def(''),
   updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
-  fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // 文件类型, 例如['png', 'jpg', 'jpeg']
-  fileSize: propTypes.number.def(5), // 大小限制(MB)
-  limit: propTypes.number.def(1), // 数量限制
-  isShowTip: propTypes.bool.def(false) // 是否显示提示
+  drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true)
+  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
+  fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M)
+  fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
+  height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
+  width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
+  borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
 })
-// ========== 上传相关 ==========
-const valueRef = ref(props.modelValue)
-const uploadRef = ref<UploadInstance>()
-const uploadList = ref<UploadUserFile[]>([])
-const fileList = ref<UploadUserFile[]>([])
-const uploadNumber = ref<number>(0)
-const dialogImageUrl = ref()
-const dialogVisible = ref(false)
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+// 生成组件唯一id
+const uuid = ref('id-' + generateUUID())
+// 查看图片
+const imgViewVisible = ref(false)
+
+const emit = defineEmits(['update:modelValue'])
+
+const deleteImg = () => {
+  emit('update:modelValue', '')
+}
+
 const uploadHeaders = ref({
   Authorization: 'Bearer ' + getAccessToken(),
   'tenant-id': getTenantId()
 })
-watch(
-  () => props.modelValue,
-  (val) => {
-    if (val) {
-      // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
-      const list = Array.isArray(props.modelValue)
-        ? props.modelValue
-        : Array.isArray(props.modelValue?.split(','))
-        ? props.modelValue?.split(',')
-        : Array.of(props.modelValue)
-      // 然后将数组转为对象数组
-      fileList.value = list.map((item) => {
-        if (typeof item === 'string') {
-          // edit by 芋道源码
-          item = { name: item, url: item }
-        }
-        return item
-      })
-    } else {
-      fileList.value = []
-      return []
-    }
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-// 文件上传之前判断
-const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
-  if (fileList.value.length >= props.limit) {
-    message.error(`上传文件数量不能超过${props.limit}个!`)
-    return false
-  }
-  let fileExtension = ''
-  if (file.name.lastIndexOf('.') > -1) {
-    fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
-  }
-  const isImg = props.fileType.some((type: string) => {
-    if (file.type.indexOf(type) > -1) return true
-    return !!(fileExtension && fileExtension.indexOf(type) > -1)
-  })
-  const isLimit = file.size < props.fileSize * 1024 * 1024
-  if (!isImg) {
-    message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
-    return false
-  }
-  if (!isLimit) {
-    message.error(`上传文件大小不能超过${props.fileSize}MB!`)
-    return false
-  }
-  message.success('正在上传文件,请稍候...')
-  uploadNumber.value++
-}
-// 处理上传的文件发生变化
-// const handleFileChange = (uploadFile: UploadFile): void => {
-//   uploadRef.value.data.path = uploadFile.name
-// }
-// 文件上传成功
-const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
-  message.success('上传成功')
-  uploadList.value.push({ name: res.data, url: res.data })
-  if (uploadList.value.length == uploadNumber.value) {
-    fileList.value = fileList.value.concat(uploadList.value)
-    uploadList.value = []
-    uploadNumber.value = 0
-    emit('update:modelValue', listToString(fileList.value))
-  }
-}
-// 文件数超出提示
-const handleExceed: UploadProps['onExceed'] = (): void => {
-  message.error(`上传文件数量不能超过${props.limit}个!`)
-}
-// 上传错误提示
-const excelUploadError: UploadProps['onError'] = (): void => {
-  message.error('导入数据失败,请您重新上传!')
+
+const editImg = () => {
+  const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
+  dom && dom.dispatchEvent(new MouseEvent('click'))
 }
-// 删除上传文件
-const handleRemove = (file) => {
-  const findex = fileList.value.map((f) => f.name).indexOf(file.name)
-  if (findex > -1) {
-    fileList.value.splice(findex, 1)
-    emit('update:modelValue', listToString(fileList.value))
-  }
+
+const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
+  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
+  const imgType = props.fileType
+  if (!imgType.includes(rawFile.type as FileTypes))
+    ElNotification({
+      title: '温馨提示',
+      message: '上传图片不符合所需的格式!',
+      type: 'warning'
+    })
+  if (!imgSize)
+    ElNotification({
+      title: '温馨提示',
+      message: `上传图片大小不能超过 ${props.fileSize}M!`,
+      type: 'warning'
+    })
+  return imgType.includes(rawFile.type as FileTypes) && imgSize
 }
-// 对象转成指定字符串分隔
-const listToString = (list: UploadUserFile[], separator?: string) => {
-  let strs = ''
-  separator = separator || ','
-  for (let i in list) {
-    strs += list[i].url + separator
-  }
-  return strs != '' ? strs.substr(0, strs.length - 1) : ''
+
+// 图片上传成功提示
+const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
+  message.success('上传成功')
+  emit('update:modelValue', res.data)
 }
-// 预览
-const handlePictureCardPreview: UploadProps['onPreview'] = (file) => {
-  dialogImageUrl.value = file.url
-  dialogVisible.value = true
+
+// 图片上传错误提示
+const uploadError = () => {
+  ElNotification({
+    title: '温馨提示',
+    message: '图片上传失败,请您重新上传!',
+    type: 'error'
+  })
 }
 </script>
 <style scoped lang="scss">
-// .el-upload--picture-card 控制加号部分
-:deep(.hide .el-upload--picture-card) {
-  display: none;
+.is-error {
+  .upload {
+    :deep(.el-upload),
+    :deep(.el-upload-dragger) {
+      border: 1px dashed var(--el-color-danger) !important;
+      &:hover {
+        border-color: var(--el-color-primary) !important;
+      }
+    }
+  }
 }
-// 去掉动画效果
-:deep(.el-list-enter-active, .el-list-leave-active) {
-  transition: all 0s;
+:deep(.disabled) {
+  .el-upload,
+  .el-upload-dragger {
+    cursor: not-allowed !important;
+    background: var(--el-disabled-bg-color);
+    border: 1px dashed var(--el-border-color-darker) !important;
+    &:hover {
+      border: 1px dashed var(--el-border-color-darker) !important;
+    }
+  }
 }
-
-:deep(.el-list-enter, .el-list-leave-active) {
-  opacity: 0;
-  transform: translateY(0);
+.upload-box {
+  .no-border {
+    :deep(.el-upload) {
+      border: none !important;
+    }
+  }
+  :deep(.upload) {
+    .el-upload {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: v-bind(width);
+      height: v-bind(height);
+      overflow: hidden;
+      border: 1px dashed var(--el-border-color-darker);
+      border-radius: v-bind(borderRadius);
+      transition: var(--el-transition-duration-fast);
+      &:hover {
+        border-color: var(--el-color-primary);
+        .upload-handle {
+          opacity: 1;
+        }
+      }
+      .el-upload-dragger {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+        padding: 0;
+        overflow: hidden;
+        background-color: transparent;
+        border: 1px dashed var(--el-border-color-darker);
+        border-radius: v-bind(borderRadius);
+        &:hover {
+          border: 1px dashed var(--el-color-primary);
+        }
+      }
+      .el-upload-dragger.is-dragover {
+        background-color: var(--el-color-primary-light-9);
+        border: 2px dashed var(--el-color-primary) !important;
+      }
+      .upload-image {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+      .upload-empty {
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        font-size: 12px;
+        line-height: 30px;
+        color: var(--el-color-info);
+        .el-icon {
+          font-size: 28px;
+          color: var(--el-text-color-secondary);
+        }
+      }
+      .upload-handle {
+        position: absolute;
+        top: 0;
+        right: 0;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+        cursor: pointer;
+        background: rgb(0 0 0 / 60%);
+        opacity: 0;
+        transition: var(--el-transition-duration-fast);
+        .handle-icon {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          padding: 0 6%;
+          color: aliceblue;
+          .el-icon {
+            margin-bottom: 40%;
+            font-size: 130%;
+            line-height: 130%;
+          }
+          span {
+            font-size: 85%;
+            line-height: 85%;
+          }
+        }
+      }
+    }
+  }
+  .el-upload__tip {
+    line-height: 18px;
+    text-align: center;
+  }
 }
 </style>

+ 277 - 0
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImgs.vue

@@ -0,0 +1,277 @@
+<template>
+  <div class="upload-box">
+    <el-upload
+      :action="updateUrl"
+      list-type="picture-card"
+      :class="['upload', drag ? 'no-border' : '']"
+      v-model:file-list="fileList"
+      :multiple="true"
+      :limit="limit"
+      :headers="uploadHeaders"
+      :before-upload="beforeUpload"
+      :on-exceed="handleExceed"
+      :on-success="uploadSuccess"
+      :on-error="uploadError"
+      :drag="drag"
+      :accept="fileType.join(',')"
+    >
+      <div class="upload-empty">
+        <slot name="empty">
+          <Icon icon="ep:plus" />
+          <!-- <span>请上传图片</span> -->
+        </slot>
+      </div>
+      <template #file="{ file }">
+        <img :src="file.url" class="upload-image" />
+        <div class="upload-handle" @click.stop>
+          <div class="handle-icon" @click="handlePictureCardPreview(file)">
+            <Icon icon="ep:zoom-in" />
+            <span>查看</span>
+          </div>
+          <div class="handle-icon" @click="handleRemove(file)">
+            <Icon icon="ep:delete" />
+            <span>删除</span>
+          </div>
+        </div>
+      </template>
+    </el-upload>
+    <div class="el-upload__tip">
+      <slot name="tip"></slot>
+    </div>
+    <el-image-viewer
+      v-if="imgViewVisible"
+      @close="imgViewVisible = false"
+      :url-list="[viewImageUrl]"
+    />
+  </div>
+</template>
+<script setup lang="ts" name="UploadImgs">
+import { PropType, ref } from 'vue'
+import { ElUpload, ElNotification, ElImageViewer } from 'element-plus'
+import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus'
+import { useMessage } from '@/hooks/web/useMessage'
+import { propTypes } from '@/utils/propTypes'
+import { getAccessToken, getTenantId } from '@/utils/auth'
+
+const message = useMessage() // 消息弹窗
+
+type FileTypes =
+  | 'image/apng'
+  | 'image/bmp'
+  | 'image/gif'
+  | 'image/jpeg'
+  | 'image/pjpeg'
+  | 'image/png'
+  | 'image/svg+xml'
+  | 'image/tiff'
+  | 'image/webp'
+  | 'image/x-icon'
+
+const props = defineProps({
+  modelValue: {
+    type: Array as PropType<UploadUserFile[]>,
+    required: true
+  },
+  updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
+  drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true)
+  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
+  limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传(默认为 5张)
+  fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M)
+  fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
+  height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
+  width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
+  borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
+})
+
+const uploadHeaders = ref({
+  Authorization: 'Bearer ' + getAccessToken(),
+  'tenant-id': getTenantId()
+})
+
+const fileList = ref<UploadUserFile[]>(props.modelValue)
+
+/**
+ * @description 文件上传之前判断
+ * @param rawFile 上传的文件
+ * */
+const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
+  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
+  const imgType = props.fileType
+  if (!imgType.includes(rawFile.type as FileTypes))
+    ElNotification({
+      title: '温馨提示',
+      message: '上传图片不符合所需的格式!',
+      type: 'warning'
+    })
+  if (!imgSize)
+    ElNotification({
+      title: '温馨提示',
+      message: `上传图片大小不能超过 ${props.fileSize}M!`,
+      type: 'warning'
+    })
+  return imgType.includes(rawFile.type as FileTypes) && imgSize
+}
+
+// 图片上传成功
+interface UploadEmits {
+  (e: 'update:modelValue', value: UploadUserFile[]): void
+}
+const emit = defineEmits<UploadEmits>()
+const uploadSuccess = (response, uploadFile: UploadFile) => {
+  if (!response) return
+  uploadFile.url = response.data
+  emit('update:modelValue', fileList.value)
+  message.success('上传成功')
+}
+
+// 删除图片
+const handleRemove = (uploadFile: UploadFile) => {
+  fileList.value = fileList.value.filter(
+    (item) => item.url !== uploadFile.url || item.name !== uploadFile.name
+  )
+  emit('update:modelValue', fileList.value)
+}
+
+// 图片上传错误提示
+const uploadError = () => {
+  ElNotification({
+    title: '温馨提示',
+    message: '图片上传失败,请您重新上传!',
+    type: 'error'
+  })
+}
+
+// 文件数超出提示
+const handleExceed = () => {
+  ElNotification({
+    title: '温馨提示',
+    message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
+    type: 'warning'
+  })
+}
+
+// 图片预览
+const viewImageUrl = ref('')
+const imgViewVisible = ref(false)
+const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
+  viewImageUrl.value = uploadFile.url!
+  imgViewVisible.value = true
+}
+</script>
+
+<style scoped lang="scss">
+.is-error {
+  .upload {
+    :deep(.el-upload--picture-card),
+    :deep(.el-upload-dragger) {
+      border: 1px dashed var(--el-color-danger) !important;
+      &:hover {
+        border-color: var(--el-color-primary) !important;
+      }
+    }
+  }
+}
+:deep(.disabled) {
+  .el-upload--picture-card,
+  .el-upload-dragger {
+    cursor: not-allowed;
+    background: var(--el-disabled-bg-color) !important;
+    border: 1px dashed var(--el-border-color-darker);
+    &:hover {
+      border-color: var(--el-border-color-darker) !important;
+    }
+  }
+}
+.upload-box {
+  .no-border {
+    :deep(.el-upload--picture-card) {
+      border: none !important;
+    }
+  }
+  :deep(.upload) {
+    .el-upload-dragger {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 100%;
+      padding: 0;
+      overflow: hidden;
+      border: 1px dashed var(--el-border-color-darker);
+      border-radius: v-bind(borderRadius);
+      &:hover {
+        border: 1px dashed var(--el-color-primary);
+      }
+    }
+    .el-upload-dragger.is-dragover {
+      background-color: var(--el-color-primary-light-9);
+      border: 2px dashed var(--el-color-primary) !important;
+    }
+    .el-upload-list__item,
+    .el-upload--picture-card {
+      width: v-bind(width);
+      height: v-bind(height);
+      background-color: transparent;
+      border-radius: v-bind(borderRadius);
+    }
+    .upload-image {
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
+    .upload-handle {
+      position: absolute;
+      top: 0;
+      right: 0;
+      box-sizing: border-box;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 100%;
+      cursor: pointer;
+      background: rgb(0 0 0 / 60%);
+      opacity: 0;
+      transition: var(--el-transition-duration-fast);
+      .handle-icon {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: 0 6%;
+        color: aliceblue;
+        .el-icon {
+          margin-bottom: 15%;
+          font-size: 140%;
+        }
+        span {
+          font-size: 100%;
+        }
+      }
+    }
+    .el-upload-list__item {
+      &:hover {
+        .upload-handle {
+          opacity: 1;
+        }
+      }
+    }
+    .upload-empty {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      font-size: 12px;
+      line-height: 30px;
+      color: var(--el-color-info);
+      .el-icon {
+        font-size: 28px;
+        color: var(--el-text-color-secondary);
+      }
+    }
+  }
+  .el-upload__tip {
+    line-height: 15px;
+    text-align: center;
+  }
+}
+</style>

+ 1 - 1
yudao-ui-admin-vue3/src/config/axios/index.ts

@@ -44,7 +44,7 @@ export default {
   },
   upload: async <T = any>(option: any) => {
     option.headersType = 'multipart/form-data'
-    const res = await request({ method: 'PUT', ...option })
+    const res = await request({ method: 'POST', ...option })
     return res as unknown as Promise<T>
   }
 }

+ 69 - 4
yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts

@@ -1,6 +1,9 @@
 import { reactive } from 'vue'
+import { AxiosPromise } from 'axios'
+import { findIndex } from '@/utils'
 import { eachTree, treeMap, filter } from '@/utils/tree'
 import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'
+import { useI18n } from '@/hooks/web/useI18n'
 import { FormSchema } from '@/types/form'
 import { TableColumn } from '@/types/table'
 import { DescriptionsSchema } from '@/types/descriptions'
@@ -23,6 +26,8 @@ export type CrudSchema = Omit<TableColumn, 'children'> & {
 type CrudSearchParams = {
   // 是否显示在查询项
   show?: boolean
+  // 接口
+  api?: () => Promise<any>
 } & Omit<FormSchema, 'field'>
 
 type CrudTableParams = {
@@ -33,6 +38,8 @@ type CrudTableParams = {
 type CrudFormParams = {
   // 是否显示表单项
   show?: boolean
+  // 接口
+  api?: () => Promise<any>
 } & Omit<FormSchema, 'field'>
 
 type CrudDescriptionsParams = {
@@ -47,6 +54,8 @@ interface AllSchemas {
   detailSchema: DescriptionsSchema[]
 }
 
+const { t } = useI18n()
+
 // 过滤所有结构
 export const useCrudSchemas = (
   crudSchema: CrudSchema[]
@@ -61,13 +70,13 @@ export const useCrudSchemas = (
     detailSchema: []
   })
 
-  const searchSchema = filterSearchSchema(crudSchema)
+  const searchSchema = filterSearchSchema(crudSchema, allSchemas)
   allSchemas.searchSchema = searchSchema || []
 
   const tableColumns = filterTableSchema(crudSchema)
   allSchemas.tableColumns = tableColumns || []
 
-  const formSchema = filterFormSchema(crudSchema)
+  const formSchema = filterFormSchema(crudSchema, allSchemas)
   allSchemas.formSchema = formSchema
 
   const detailSchema = filterDescriptionsSchema(crudSchema)
@@ -79,9 +88,11 @@ export const useCrudSchemas = (
 }
 
 // 过滤 Search 结构
-const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
+const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
   const searchSchema: FormSchema[] = []
 
+  // 获取字典列表队列
+  const searchRequestTask: Array<() => Promise<void>> = []
   eachTree(crudSchema, (schemaItem: CrudSchema) => {
     // 判断是否显示
     if (schemaItem?.isSearch || schemaItem.search?.show) {
@@ -107,12 +118,31 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
         field: schemaItem.field,
         label: schemaItem.search?.label || schemaItem.label
       }
+      if (searchSchemaItem.api) {
+        searchRequestTask.push(async () => {
+          const res = await (searchSchemaItem.api as () => AxiosPromise)()
+          if (res) {
+            const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => {
+              return v.field === searchSchemaItem.field
+            })
+            if (index !== -1) {
+              allSchemas.searchSchema[index]!.componentProps!.options = filterOptions(
+                res,
+                searchSchemaItem.componentProps.optionsAlias?.labelField
+              )
+            }
+          }
+        })
+      }
       // 删除不必要的字段
       delete searchSchemaItem.show
 
       searchSchema.push(searchSchemaItem)
     }
   })
+  for (const task of searchRequestTask) {
+    task()
+  }
   return searchSchema
 }
 
@@ -139,9 +169,12 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
 }
 
 // 过滤 form 结构
-const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
+const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
   const formSchema: FormSchema[] = []
 
+  // 获取字典列表队列
+  const formRequestTask: Array<() => Promise<void>> = []
+
   eachTree(crudSchema, (schemaItem: CrudSchema) => {
     // 判断是否显示
     if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) {
@@ -185,6 +218,23 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
         label: schemaItem.form?.label || schemaItem.label
       }
 
+      if (formSchemaItem.api) {
+        formRequestTask.push(async () => {
+          const res = await (formSchemaItem.api as () => AxiosPromise)()
+          if (res) {
+            const index = findIndex(allSchemas.formSchema, (v: FormSchema) => {
+              return v.field === formSchemaItem.field
+            })
+            if (index !== -1) {
+              allSchemas.formSchema[index]!.componentProps!.options = filterOptions(
+                res,
+                formSchemaItem.componentProps.optionsAlias?.labelField
+              )
+            }
+          }
+        })
+      }
+
       // 删除不必要的字段
       delete formSchemaItem.show
 
@@ -192,6 +242,9 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
     }
   })
 
+  for (const task of formRequestTask) {
+    task()
+  }
   return formSchema
 }
 
@@ -225,3 +278,15 @@ const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[
 
   return descriptionsSchema
 }
+
+// 给options添加国际化
+const filterOptions = (options: Recordable, labelField?: string) => {
+  return options.map((v: Recordable) => {
+    if (labelField) {
+      v['labelField'] = t(v.labelField)
+    } else {
+      v['label'] = t(v.label)
+    }
+    return v
+  })
+}

+ 1 - 1
yudao-ui-admin-vue3/src/hooks/web/useVxeCrudSchemas.ts

@@ -63,7 +63,7 @@ type CrudDescriptionsParams = {
 } & Omit<DescriptionsSchema, 'field'>
 
 type CrudPrintParams = {
-  // 是否显示表单
+  // 是否显示打印
   show?: boolean
 } & Omit<VxeTableDefines.ColumnInfo[], 'field'>
 

+ 2 - 1
yudao-ui-admin-vue3/src/layout/components/Breadcrumb/src/Breadcrumb.vue

@@ -53,7 +53,8 @@ export default defineComponent({
           <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
             {meta?.icon && breadcrumbIcon.value ? (
               <>
-                <Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title)}
+                <Icon icon={meta.icon} class="mr-[2px]" svgClass="inline-block"></Icon>
+                {t(v?.meta?.title)}
               </>
             ) : (
               t(v?.meta?.title)

+ 26 - 24
yudao-ui-admin-vue3/src/layout/components/Logo/src/Logo.vue

@@ -55,31 +55,33 @@ watch(
 </script>
 
 <template>
-  <router-link
-    :class="[
-      prefixCls,
-      layout !== 'classic' ? `${prefixCls}__Top` : '',
-      'flex !h-[var(--logo-height)] items-center cursor-pointer justify-center relative',
-      'dark:bg-[var(--el-bg-color)]'
-    ]"
-    to="/"
-  >
-    <img
-      src="@/assets/imgs/logo.png"
-      class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
-    />
-    <div
-      v-if="show"
+  <div>
+    <router-link
       :class="[
-        'ml-10px text-16px font-700',
-        {
-          'text-[var(--logo-title-text-color)]': layout === 'classic',
-          'text-[var(--top-header-text-color)]':
-            layout === 'topLeft' || layout === 'top' || layout === 'cutMenu'
-        }
+        prefixCls,
+        layout !== 'classic' ? `${prefixCls}__Top` : '',
+        'flex !h-[var(--logo-height)] items-center cursor-pointer justify-center relative',
+        'dark:bg-[var(--el-bg-color)]'
       ]"
+      to="/"
     >
-      {{ title }}
-    </div>
-  </router-link>
+      <img
+        src="@/assets/imgs/logo.png"
+        class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
+      />
+      <div
+        v-if="show"
+        :class="[
+          'ml-10px text-16px font-700',
+          {
+            'text-[var(--logo-title-text-color)]': layout === 'classic',
+            'text-[var(--top-header-text-color)]':
+              layout === 'topLeft' || layout === 'top' || layout === 'cutMenu'
+          }
+        ]"
+      >
+        {{ title }}
+      </div>
+    </router-link>
+  </div>
 </template>

+ 2 - 0
yudao-ui-admin-vue3/src/layout/components/Menu/src/Menu.vue

@@ -161,6 +161,7 @@ $prefix-cls: #{$namespace}-menu;
     // 设置子菜单悬停的高亮和背景色
     .#{$elNamespace}-sub-menu__title,
     .#{$elNamespace}-menu-item {
+      height: 59px;
       &:hover {
         color: var(--left-menu-text-active-color) !important;
         background-color: var(--left-menu-bg-color) !important;
@@ -170,6 +171,7 @@ $prefix-cls: #{$namespace}-menu;
     // 设置选中时的高亮背景和高亮颜色
     .#{$elNamespace}-sub-menu.is-active,
     .#{$elNamespace}-menu-item.is-active {
+      height: 59px;
       color: var(--left-menu-text-active-color) !important;
       background-color: var(--left-menu-bg-active-color) !important;
 

+ 14 - 0
yudao-ui-admin-vue3/src/locales/en.ts

@@ -426,5 +426,19 @@ export default {
       cfPwdMsg: 'Please Enter Confirm Password',
       diffPwd: 'The Passwords Entered Twice No Match'
     }
+  },
+  cropper: {
+    selectImage: 'Select Image',
+    uploadSuccess: 'Uploaded success!',
+    modalTitle: 'Avatar upload',
+    okText: 'Confirm and upload',
+    btn_reset: 'Reset',
+    btn_rotate_left: 'Counterclockwise rotation',
+    btn_rotate_right: 'Clockwise rotation',
+    btn_scale_x: 'Flip horizontal',
+    btn_scale_y: 'Flip vertical',
+    btn_zoom_in: 'Zoom in',
+    btn_zoom_out: 'Zoom out',
+    preview: 'Preivew'
   }
 }

+ 14 - 0
yudao-ui-admin-vue3/src/locales/zh-CN.ts

@@ -419,5 +419,19 @@ export default {
       pwdRules: '长度在 6 到 20 个字符',
       diffPwd: '两次输入密码不一致'
     }
+  },
+  cropper: {
+    selectImage: '选择图片',
+    uploadSuccess: '上传成功',
+    modalTitle: '头像上传',
+    okText: '确认并上传',
+    btn_reset: '重置',
+    btn_rotate_left: '逆时针旋转',
+    btn_rotate_right: '顺时针旋转',
+    btn_scale_x: '水平翻转',
+    btn_scale_y: '垂直翻转',
+    btn_zoom_in: '放大',
+    btn_zoom_out: '缩小',
+    preview: '预览'
   }
 }

+ 1 - 1
yudao-ui-admin-vue3/src/plugins/vxeTable/index.ts

@@ -56,7 +56,7 @@ watch(
       import('./theme/light.scss')
     }
   },
-  { immediate: true }
+  { deep: true }
 )
 // 全局默认参数
 VXETable.setup({

+ 1 - 1
yudao-ui-admin-vue3/src/router/index.ts

@@ -14,7 +14,7 @@ import { usePermissionStoreWithOut } from '@/store/modules/permission'
 import { getInfoApi } from '@/api/login'
 import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
 
-const { wsCache } = useCache('sessionStorage')
+const { wsCache } = useCache()
 
 const { start, done } = useNProgress()
 

+ 2 - 2
yudao-ui-admin-vue3/src/router/modules/remaining.ts

@@ -64,7 +64,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         meta: {
           title: t('router.home'),
           icon: 'ep:home-filled',
-          noCache: true,
+          noCache: false,
           affix: true
         }
       }
@@ -85,7 +85,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         meta: {
           canTo: true,
           hidden: true,
-          noTagsView: true,
+          noTagsView: false,
           icon: 'ep:user',
           title: t('common.profile')
         }

+ 1 - 0
yudao-ui-admin-vue3/src/types/components.d.ts

@@ -22,6 +22,7 @@ export type ComponentName =
   | 'InputPassword'
   | 'Editor'
   | 'UploadImg'
+  | 'UploadImgs'
   | 'UploadFile'
 
 export type ColProps = {

+ 33 - 2
yudao-ui-admin-vue3/src/utils/index.ts

@@ -69,7 +69,7 @@ export const trim = (str: string) => {
  * @param {Date | number | string} time 需要转换的时间
  * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss
  */
-export function formatTime(time: Date | number | string, fmt: string) {
+export const formatTime = (time: Date | number | string, fmt: string) => {
   if (!time) return ''
   else {
     const date = new Date(time)
@@ -100,7 +100,7 @@ export function formatTime(time: Date | number | string, fmt: string) {
 /**
  * 生成随机字符串
  */
-export function toAnyString() {
+export const toAnyString = () => {
   const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
     const r: number = (Math.random() * 16) | 0
     const v: number = c === 'x' ? r : (r & 0x3) | 0x8
@@ -108,3 +108,34 @@ export function toAnyString() {
   })
   return str
 }
+
+export const generateUUID = () => {
+  if (typeof crypto === 'object') {
+    if (typeof crypto.randomUUID === 'function') {
+      return crypto.randomUUID()
+    }
+    if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
+      const callback = (c: any) => {
+        const num = Number(c)
+        return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(
+          16
+        )
+      }
+      return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback)
+    }
+  }
+  let timestamp = new Date().getTime()
+  let performanceNow =
+    (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0
+  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
+    let random = Math.random() * 16
+    if (timestamp > 0) {
+      random = (timestamp + random) % 16 | 0
+      timestamp = Math.floor(timestamp / 16)
+    } else {
+      random = (performanceNow + random) % 16 | 0
+      performanceNow = Math.floor(performanceNow / 16)
+    }
+    return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)
+  })
+}

+ 8 - 1
yudao-ui-admin-vue3/src/utils/routerHelper.ts

@@ -56,7 +56,13 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
       title: route.name,
       icon: route.icon,
       hidden: !route.visible,
-      noCache: !route.keepAlive
+      noCache: !route.keepAlive,
+      alwaysShow:
+        route.children &&
+        route.children.length === 1 &&
+        import.meta.env.VITE_ROUTE_ALWAYSSHOW_ENABLE === 'true'
+          ? true
+          : false
     }
     // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive
     let data: AppRouteRecordRaw = {
@@ -71,6 +77,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
       data.meta = {}
       data.name = toCamelCase(route.path, true) + 'Parent'
       data.redirect = ''
+      meta.alwaysShow = true
       const childrenData: AppRouteRecordRaw = {
         path: '',
         name: toCamelCase(route.path, true),

+ 25 - 230
yudao-ui-admin-vue3/src/views/Profile/components/UserAvatar.vue

@@ -1,245 +1,40 @@
 <template>
-  <div class="user-info-head" @click="editCropper()">
-    <img :src="props.img" title="点击上传头像" class="img-circle img-lg" alt="" />
+  <div class="change-avatar">
+    <CropperAvatar
+      ref="cropperRef"
+      :value="avatar"
+      :showBtn="false"
+      @change="handelUpload"
+      :btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
+      width="120px"
+    />
   </div>
-  <el-dialog
-    v-model="dialogVisible"
-    title="编辑头像"
-    :mask-closable="false"
-    width="800px"
-    append-to-body
-    @opened="cropperVisible = true"
-  >
-    <el-row>
-      <el-col :xs="24" :md="12" :style="{ height: '350px' }">
-        <VueCropper
-          ref="cropper"
-          v-if="cropperVisible"
-          :img="options.img"
-          :info="true"
-          :infoTrue="options.infoTrue"
-          :autoCrop="options.autoCrop"
-          :autoCropWidth="options.autoCropWidth"
-          :autoCropHeight="options.autoCropHeight"
-          :fixedNumber="options.fixedNumber"
-          :fixedBox="options.fixedBox"
-          :centerBox="options.centerBox"
-          @real-time="realTime"
-        />
-      </el-col>
-      <el-col :xs="24" :md="12" :style="{ height: '350px' }">
-        <div
-          class="avatar-upload-preview"
-          :style="{
-            width: previews.w + 'px',
-            height: previews.h + 'px',
-            overflow: 'hidden',
-            margin: '5px'
-          }"
-        >
-          <div :style="previews.div">
-            <img :src="previews.url" :style="previews.img" style="!max-width: 100%" alt="" />
-          </div>
-        </div>
-      </el-col>
-    </el-row>
-    <template #footer>
-      <el-row>
-        <el-col :lg="2" :md="2">
-          <el-upload
-            action="#"
-            :http-request="requestUpload"
-            :show-file-list="false"
-            :before-upload="beforeUpload"
-          >
-            <el-button size="small">
-              <Icon icon="ep:upload-filled" class="mr-5px" />
-              选择
-            </el-button>
-          </el-upload>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 2 }" :md="2">
-          <el-button size="small" @click="changeScale(1)">
-            <Icon icon="ep:zoom-in" class="mr-5px" />
-          </el-button>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
-          <el-button size="small" @click="changeScale(-1)">
-            <Icon icon="ep:zoom-out" class="mr-5px" />
-          </el-button>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
-          <el-button size="small" @click="rotateLeft()">
-            <Icon icon="ep:arrow-left-bold" class="mr-5px" />
-          </el-button>
-        </el-col>
-        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
-          <el-button size="small" @click="rotateRight()">
-            <Icon icon="ep:arrow-right-bold" class="mr-5px" />
-          </el-button>
-        </el-col>
-        <el-col :lg="{ span: 2, offset: 6 }" :md="2">
-          <el-button size="small" type="primary" @click="uploadImg()">提 交</el-button>
-        </el-col>
-      </el-row>
-    </template>
-  </el-dialog>
 </template>
 <script setup lang="ts">
-import { ref, reactive, watch, Ref, UnwrapNestedRefs } from 'vue'
-import VueCropper from 'vue-cropper/lib/vue-cropper.vue'
-import 'vue-cropper/dist/index.css'
-import { ElRow, ElCol, ElUpload, ElMessage, ElDialog } from 'element-plus'
+import { computed, ref } from 'vue'
 import { propTypes } from '@/utils/propTypes'
+import { CropperAvatar } from '@/components/Cropper'
 import { uploadAvatarApi } from '@/api/system/user/profile'
-
-const cropper = ref()
-const dialogVisible = ref(false)
-const cropperVisible = ref(false)
 const props = defineProps({
   img: propTypes.string.def('')
 })
-interface Options {
-  img: string | ArrayBuffer | null // 裁剪图片的地址
-  info: true // 裁剪框的大小信息
-  outputSize: number // 裁剪生成图片的质量 [1至0.1]
-  outputType: 'jpeg' // 裁剪生成图片的格式
-  canScale: boolean // 图片是否允许滚轮缩放
-  autoCrop: boolean // 是否默认生成截图框
-  autoCropWidth: number // 默认生成截图框宽度
-  autoCropHeight: number // 默认生成截图框高度
-  fixedBox: boolean // 固定截图框大小 不允许改变
-  fixed: boolean // 是否开启截图框宽高固定比例
-  fixedNumber: Array<number> // 截图框的宽高比例  需要配合centerBox一起使用才能生效
-  full: boolean // 是否输出原图比例的截图
-  canMoveBox: boolean // 截图框能否拖动
-  original: boolean // 上传图片按照原始比例渲染
-  centerBox: boolean // 截图框是否被限制在图片里面
-  infoTrue: boolean // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
-}
-const options: UnwrapNestedRefs<Options> = reactive({
-  img: '', // 需要剪裁的图片
-  autoCrop: true, // 是否默认生成截图框
-  autoCropWidth: 200, // 默认生成截图框的宽度
-  autoCropHeight: 200, // 默认生成截图框的长度
-  fixedBox: false, // 是否固定截图框的大小 不允许改变
-  info: true, // 裁剪框的大小信息
-  outputSize: 1, // 裁剪生成图片的质量 [1至0.1]
-  outputType: 'jpeg', // 裁剪生成图片的格式
-  canScale: false, // 图片是否允许滚轮缩放
-  fixed: true, // 是否开启截图框宽高固定比例
-  fixedNumber: [1, 1], // 截图框的宽高比例 需要配合centerBox一起使用才能生效
-  full: true, // 是否输出原图比例的截图
-  canMoveBox: false, // 截图框能否拖动
-  original: false, // 上传图片按照原始比例渲染
-  centerBox: true, // 截图框是否被限制在图片里面
-  infoTrue: true // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
+const avatar = computed(() => {
+  return props.img
 })
-const previews: Ref<any> = ref({})
-/** 编辑头像 */
-const editCropper = () => {
-  dialogVisible.value = true
-}
-/** 向左旋转 */
-const rotateLeft = () => {
-  cropper.value.rotateLeft()
-}
-/** 向右旋转 */
-const rotateRight = () => {
-  cropper.value.rotateRight()
-}
-/** 图片缩放 */
-const changeScale = (num: number) => {
-  num = num || 1
-  cropper.value.changeScale(num)
-}
-// 覆盖默认的上传行为
-const requestUpload: any = () => {}
-/** 上传预处理 */
-const beforeUpload = (file: Blob) => {
-  if (file.type.indexOf('image/') == -1) {
-    ElMessage('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。')
-  } else {
-    const reader = new FileReader()
-    // 转化为base64
-    reader.readAsDataURL(file)
-    reader.onload = () => {
-      if (reader.result) {
-        // 获取到需要剪裁的图片 展示到剪裁框中
-        options.img = reader.result as string
-      }
-      return false
-    }
-  }
-}
-/** 上传图片 */
-const uploadImg = () => {
-  cropper.value.getCropBlob((data: any) => {
-    let formData = new FormData()
-    formData.append('avatarFile', data)
-    uploadAvatarApi(formData).then((res) => {
-      options.img = res
-      window.location.reload()
-    })
-    dialogVisible.value = false
-    cropperVisible.value = false
-  })
-}
-/** 实时预览 */
-const realTime = (data: any) => {
-  previews.value = data
+
+const cropperRef = ref()
+const handelUpload = async ({ data }) => {
+  await uploadAvatarApi({ avatarFile: data })
+  cropperRef.value.close()
 }
-watch(
-  () => props.img,
-  () => {
-    if (props.img) {
-      options.img = props.img
-      previews.value.img = props.img
-      previews.value.url = props.img
-    }
-  }
-)
 </script>
 
-<style scoped>
-.user-info-head {
-  position: relative;
-  display: inline-block;
-}
-.img-circle {
-  border-radius: 50%;
-}
-.img-lg {
-  width: 120px;
-  height: 120px;
-}
-.avatar-upload-preview {
-  position: absolute;
-  top: 50%;
-  -webkit-transform: translate(50%, -50%);
-  transform: translate(50%, -50%);
-  width: 200px;
-  height: 200px;
-  border-radius: 50%;
-  -webkit-box-shadow: 0 0 4px #ccc;
-  box-shadow: 0 0 4px #ccc;
-  overflow: hidden;
-}
-.user-info-head:hover:after {
-  content: '+';
-  position: absolute;
-  left: 0;
-  right: 0;
-  top: 0;
-  bottom: 0;
-  color: #eee;
-  background: rgba(0, 0, 0, 0.5);
-  font-size: 24px;
-  font-style: normal;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  cursor: pointer;
-  line-height: 110px;
-  border-radius: 50%;
+<style scoped lang="scss">
+.change-avatar {
+  img {
+    display: block;
+    margin-bottom: 15px;
+    border-radius: 50%;
+  }
 }
 </style>

+ 2 - 3
yudao-ui-admin-vue3/src/views/infra/apiErrorLog/index.vue

@@ -95,9 +95,8 @@ const handleProcessClick = (
   message
     .confirm('确认标记为' + type + '?', t('common.reminder'))
     .then(async () => {
-      ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus).then(() => {
-        message.success(t('common.updateSuccess'))
-      })
+      await ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus)
+      message.success(t('common.updateSuccess'))
     })
     .finally(async () => {
       // 刷新列表

+ 25 - 3
yudao-ui-admin-vue3/src/views/infra/fileConfig/index.vue

@@ -64,8 +64,8 @@
       <el-form-item label="存储器" prop="storage">
         <el-select v-model="form.storage" placeholder="请选择存储器" :disabled="form.id !== 0">
           <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
-            :key="dict.value"
+            v-for="(dict, index) in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
+            :key="index"
             :label="dict.label"
             :value="dict.value"
           />
@@ -197,7 +197,7 @@ const dialogVisible = ref(false) // 是否显示弹出层
 const dialogTitle = ref('edit') // 弹出层标题
 const formRef = ref<FormInstance>() // 表单 Ref
 const detailData = ref() // 详情 Ref
-let form = ref<FileConfigApi.FileConfigVO>({
+const form = ref<FileConfigApi.FileConfigVO>({
   id: 0,
   name: '',
   storage: 0,
@@ -230,6 +230,28 @@ const setDialogTile = (type: string) => {
 const handleCreate = (formEl: FormInstance | undefined) => {
   setDialogTile('create')
   formEl?.resetFields()
+  form.value = {
+    id: 0,
+    name: '',
+    storage: 0,
+    master: false,
+    visible: false,
+    config: {
+      basePath: '',
+      host: '',
+      port: 0,
+      username: '',
+      password: '',
+      mode: '',
+      endpoint: '',
+      bucket: '',
+      accessKey: '',
+      accessSecret: '',
+      domain: ''
+    },
+    remark: '',
+    createTime: new Date()
+  }
 }
 
 // 修改操作

+ 1 - 2
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts

@@ -12,8 +12,7 @@ export const rules = reactive({
   email: [required],
   phone: [
     {
-      min: 11,
-      max: 11,
+      len: 11,
       trigger: 'blur',
       message: '请输入正确的手机号码'
     }

+ 2 - 10
yudao-ui-admin-vue3/src/views/system/dept/index.vue

@@ -75,16 +75,15 @@
   </XModal>
 </template>
 <script setup lang="ts" name="Dept">
-import { nextTick, onMounted, reactive, ref, unref } from 'vue'
+import { nextTick, onMounted, ref, unref } from 'vue'
 import { ElSelect, ElTreeSelect, ElOption } from 'element-plus'
 import { VxeGridInstance } from 'vxe-table'
 import { handleTree, defaultProps } from '@/utils/tree'
-import { required } from '@/utils/formRules.js'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useMessage } from '@/hooks/web/useMessage'
 import { useVxeGrid } from '@/hooks/web/useVxeGrid'
 import { FormExpose } from '@/components/Form'
-import { allSchemas } from './dept.data'
+import { allSchemas, rules } from './dept.data'
 import * as DeptApi from '@/api/system/dept'
 import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
 
@@ -107,13 +106,6 @@ const actionLoading = ref(false) // 遮罩层
 const formRef = ref<FormExpose>() // 表单 Ref
 const deptOptions = ref() // 树形结构
 const userOption = ref<UserVO[]>([])
-// 新增和修改的表单校验
-const rules = reactive({
-  name: [required],
-  sort: [required],
-  path: [required],
-  status: [required]
-})
 
 const getUserList = async () => {
   const res = await getListSimpleUsersApi()

+ 49 - 139
yudao-ui-admin-vue3/src/views/system/menu/index.vue

@@ -1,34 +1,8 @@
 <template>
   <ContentWrap>
-    <!-- 搜索工作栏 -->
-    <el-form :model="queryParams" ref="queryForm" :inline="true">
-      <el-form-item label="菜单名称" prop="name">
-        <el-input v-model="queryParams.name" placeholder="请输入菜单名称" />
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择菜单状态">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <!-- 操作:搜索 -->
-        <XButton
-          type="primary"
-          preIcon="ep:search"
-          :title="t('common.query')"
-          @click="handleQuery()"
-        />
-        <!-- 操作:重置 -->
-        <XButton preIcon="ep:refresh-right" :title="t('common.reset')" @click="resetQuery()" />
-      </el-form-item>
-    </el-form>
-    <vxe-toolbar>
-      <template #buttons>
+    <!-- 列表 -->
+    <vxe-grid ref="xGrid" v-bind="gridOptions" show-overflow class="xtable-scrollbar">
+      <template #toolbar_buttons>
         <!-- 操作:新增 -->
         <XButton
           type="primary"
@@ -37,63 +11,30 @@
           v-hasPermi="['system:menu:create']"
           @click="handleCreate()"
         />
-        <XButton title="展开所有" @click="xTable?.setAllTreeExpand(true)" />
-        <XButton title="关闭所有" @click="xTable?.clearTreeExpand()" />
+        <XButton title="展开所有" @click="xGrid?.setAllTreeExpand(true)" />
+        <XButton title="关闭所有" @click="xGrid?.clearTreeExpand()" />
       </template>
-    </vxe-toolbar>
-    <!-- 列表 -->
-    <vxe-table
-      show-overflow
-      keep-source
-      ref="xTable"
-      :loading="tableLoading"
-      :row-config="{ keyField: 'id' }"
-      :column-config="{ resizable: true }"
-      :tree-config="{ transform: true, rowField: 'id', parentField: 'parentId' }"
-      :print-config="{}"
-      :export-config="{}"
-      :data="tableData"
-    >
-      <vxe-column title="菜单名称" field="name" width="200" tree-node>
-        <template #default="{ row }">
-          <Icon :icon="row.icon" />
-          <span class="ml-3">{{ row.name }}</span>
-        </template>
-      </vxe-column>
-      <vxe-column title="菜单类型" field="type">
-        <template #default="{ row }">
-          <DictTag :type="DICT_TYPE.SYSTEM_MENU_TYPE" :value="row.type" />
-        </template>
-      </vxe-column>
-      <vxe-column title="路由地址" field="path" />
-      <vxe-column title="组件路径" field="component" />
-      <vxe-column title="权限标识" field="permission" />
-      <vxe-column title="排序" field="sort" />
-      <vxe-column title="状态" field="status">
-        <template #default="{ row }">
-          <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
-        </template>
-      </vxe-column>
-      <vxe-column title="创建时间" field="createTime" formatter="formatDate" />
-      <vxe-column title="操作" width="200">
-        <template #default="{ row }">
-          <!-- 操作:修改 -->
-          <XTextButton
-            preIcon="ep:edit"
-            :title="t('action.edit')"
-            v-hasPermi="['system:menu:update']"
-            @click="handleUpdate(row.id)"
-          />
-          <!-- 操作:删除 -->
-          <XTextButton
-            preIcon="ep:delete"
-            :title="t('action.del')"
-            v-hasPermi="['system:menu:delete']"
-            @click="handleDelete(row.id)"
-          />
-        </template>
-      </vxe-column>
-    </vxe-table>
+      <template #name_default="{ row }">
+        <Icon :icon="row.icon" />
+        <span class="ml-3">{{ row.name }}</span>
+      </template>
+      <template #actionbtns_default="{ row }">
+        <!-- 操作:修改 -->
+        <XTextButton
+          preIcon="ep:edit"
+          :title="t('action.edit')"
+          v-hasPermi="['system:menu:update']"
+          @click="handleUpdate(row.id)"
+        />
+        <!-- 操作:删除 -->
+        <XTextButton
+          preIcon="ep:delete"
+          :title="t('action.del')"
+          v-hasPermi="['system:menu:delete']"
+          @click="handleDelete(row.id)"
+        />
+      </template>
+    </vxe-grid>
   </ContentWrap>
   <!-- 添加或修改菜单对话框 -->
   <XModal id="menuModel" v-model="dialogVisible" :title="dialogTitle">
@@ -124,7 +65,7 @@
         <el-radio-group v-model="menuForm.type">
           <el-radio-button
             v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
-            :key="dict.value"
+            :key="dict.label"
             :label="dict.value"
           >
             {{ dict.label }}
@@ -178,7 +119,7 @@
             <el-radio
               border
               v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-              :key="dict.value"
+              :key="dict.label"
               :label="dict.value"
             >
               {{ dict.label }}
@@ -235,7 +176,7 @@
 </template>
 <script setup lang="ts" name="Menu">
 // 全局相关的 import
-import { onMounted, reactive, ref } from 'vue'
+import { ref } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import { useMessage } from '@/hooks/web/useMessage'
@@ -245,9 +186,7 @@ import {
   ElFormItem,
   ElInput,
   ElInputNumber,
-  ElSelect,
   ElTreeSelect,
-  ElOption,
   ElRadio,
   ElRadioGroup,
   ElRadioButton,
@@ -255,21 +194,33 @@ import {
 } from 'element-plus'
 import { Tooltip } from '@/components/Tooltip'
 import { IconSelect } from '@/components/Icon'
-import { VxeTableInstance } from 'vxe-table'
+import { VxeGridInstance } from 'vxe-table'
 // 业务相关的 import
-import * as MenuApi from '@/api/system/menu'
-import { required } from '@/utils/formRules.js'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
 import { handleTree, defaultProps } from '@/utils/tree'
+import * as MenuApi from '@/api/system/menu'
+import { allSchemas, rules } from './menu.data'
+import { useVxeGrid } from '@/hooks/web/useVxeGrid'
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 const { wsCache } = useCache()
 // 列表相关的变量
-const xTable = ref<VxeTableInstance>()
-const tableLoading = ref(false)
-const tableData = ref()
+// 列表相关的变量
+const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
+const treeConfig = {
+  transform: true,
+  rowField: 'id',
+  parentField: 'parentId',
+  expandAll: false
+}
+const { gridOptions, getList, deleteData } = useVxeGrid<MenuApi.MenuVO>({
+  allSchemas: allSchemas,
+  treeConfig: treeConfig,
+  getListApi: MenuApi.getMenuListApi,
+  deleteApi: MenuApi.deleteMenuApi
+})
 // 弹窗相关的变量
 const dialogVisible = ref(false) // 是否显示弹出层
 const dialogTitle = ref('edit') // 弹出层标题
@@ -292,13 +243,6 @@ const menuForm = ref<MenuApi.MenuVO>({
   keepAlive: true,
   createTime: new Date()
 })
-// 新增和修改的表单校验
-const rules = reactive({
-  name: [required],
-  sort: [required],
-  path: [required],
-  status: [required]
-})
 
 // ========== 下拉框[上级菜单] ==========
 const menuOptions = ref<any[]>([]) // 树形结构
@@ -311,31 +255,6 @@ const getTree = async () => {
   menuOptions.value.push(menu)
 }
 
-// ========== 查询 ==========
-const queryParams = reactive<MenuApi.MenuPageReqVO>({
-  name: undefined,
-  status: undefined
-})
-// 执行查询
-const getList = async () => {
-  tableLoading.value = true
-  const res = await MenuApi.getMenuListApi(queryParams)
-  tableData.value = res
-  tableLoading.value = false
-}
-
-// 查询操作
-const handleQuery = async () => {
-  await getList()
-}
-
-// 重置操作
-const resetQuery = async () => {
-  queryParams.name = undefined
-  queryParams.status = undefined
-  await getList()
-}
-
 // ========== 新增/修改 ==========
 
 // 设置标题
@@ -407,7 +326,7 @@ const submitForm = async () => {
     actionLoading.value = false
     wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
     // 操作成功,重新加载列表
-    await getList()
+    await getList(xGrid)
   }
 }
 
@@ -419,15 +338,6 @@ const isExternal = (path: string) => {
 // ========== 删除 ==========
 // 删除操作
 const handleDelete = async (rowId: number) => {
-  message.delConfirm().then(async () => {
-    await MenuApi.deleteMenuApi(rowId)
-    message.success(t('common.delSuccess'))
-    await getList()
-  })
+  await deleteData(xGrid, rowId)
 }
-
-// ========== 初始化 ==========
-onMounted(async () => {
-  await getList()
-})
 </script>

+ 75 - 0
yudao-ui-admin-vue3/src/views/system/menu/menu.data.ts

@@ -0,0 +1,75 @@
+import { reactive } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { DICT_TYPE } from '@/utils/dict'
+import { required } from '@/utils/formRules'
+import { VxeCrudSchema, useVxeCrudSchemas } from '@/hooks/web/useVxeCrudSchemas'
+const { t } = useI18n() // 国际化
+
+// 新增和修改的表单校验
+export const rules = reactive({
+  name: [required],
+  sort: [required],
+  path: [required],
+  status: [required]
+})
+
+// CrudSchema
+const crudSchemas = reactive<VxeCrudSchema>({
+  primaryKey: 'id',
+  primaryType: null,
+  action: true,
+  columns: [
+    {
+      title: '上级菜单',
+      field: 'parentId',
+      isTable: false
+    },
+    {
+      title: '菜单名称',
+      field: 'name',
+      isSearch: true,
+      table: {
+        treeNode: true,
+        align: 'left',
+        width: '200px',
+        slots: {
+          default: 'name_default'
+        }
+      }
+    },
+    {
+      title: '菜单类型',
+      field: 'type',
+      dictType: DICT_TYPE.SYSTEM_MENU_TYPE
+    },
+    {
+      title: '路由地址',
+      field: 'path'
+    },
+    {
+      title: '组件路径',
+      field: 'component'
+    },
+    {
+      title: '权限标识',
+      field: 'permission'
+    },
+    {
+      title: '排序',
+      field: 'sort'
+    },
+    {
+      title: t('common.status'),
+      field: 'status',
+      dictType: DICT_TYPE.COMMON_STATUS,
+      dictClass: 'number',
+      isSearch: true
+    },
+    {
+      title: t('common.createTime'),
+      field: 'createTime',
+      formatter: 'formatDate'
+    }
+  ]
+})
+export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 1 - 1
yudao-ui-admin-vue3/src/views/system/notice/index.vue

@@ -53,7 +53,7 @@
       :data="detailData"
     >
       <template #content="{ row }">
-        <Editor :model-value="row.content" read-only="true" />
+        <Editor :model-value="row.content" :readonly="true" />
       </template>
     </Descriptions>
     <template #footer>

+ 1 - 4
yudao-ui-admin-vue3/src/views/system/oauth2/client/client.data.ts

@@ -43,10 +43,7 @@ const crudSchemas = reactive<VxeCrudSchema>({
         }
       },
       form: {
-        component: 'UploadImg',
-        componentProps: {
-          limit: 1
-        }
+        component: 'UploadImg'
       }
     },
     {

+ 1 - 1
yudao-ui-admin-vue3/src/views/system/user/index.vue

@@ -145,7 +145,7 @@
             v-for="item in postOptions"
             :key="item.id"
             :label="item.name"
-            :value="item.id"
+            :value="(item.id as unknown as number)"
           />
         </el-select>
       </template>

+ 1 - 3
yudao-ui-admin-vue3/src/views/system/user/user.data.ts

@@ -10,12 +10,10 @@ export const rules = reactive({
   username: [required],
   nickname: [required],
   email: [required],
-  postIds: [required],
   status: [required],
   mobile: [
     {
-      min: 11,
-      max: 11,
+      len: 11,
       trigger: 'blur',
       message: '请输入正确的手机号码'
     }

+ 1 - 1
yudao-ui-admin-vue3/tsconfig.json

@@ -24,7 +24,7 @@
       "@/*": ["src/*"]
     },
     "types": [
-      "@intlify/vite-plugin-vue-i18n/client",
+      "@intlify/unplugin-vue-i18n/types",
       "vite/client",
       "element-plus/global",
       "@types/intro.js",

+ 2 - 1
yudao-ui-admin-vue3/types/env.d.ts

@@ -10,7 +10,8 @@ declare module '*.vue' {
 interface ImportMetaEnv {
   readonly VITE_APP_TITLE: string
   readonly VITE_PORT: number
-  readonly VITE_OPEN: boolean
+  readonly VITE_OPEN: string
+  readonly VITE_ROUTE_ALWAYSSHOW_ENABLE: string
   readonly VITE_APP_CAPTCHA_ENABLE: string
   readonly VITE_APP_TENANT_ENABLE: string
   readonly VITE_BASE_URL: string

+ 1 - 1
yudao-ui-admin-vue3/vite.config.ts

@@ -30,7 +30,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       // 端口号
       port: env.VITE_PORT,
       host: "0.0.0.0",
-      open: env.VITE_OPEN,
+      open: env.VITE_OPEN === 'true',
       // 本地跨域代理
       proxy: {
         ['/admin-api']: {

部分文件因文件數量過多而無法顯示