Quellcode durchsuchen

!338 vue3 重构头像上传
Merge pull request !338 from xingyu/dev

芋道源码 vor 2 Jahren
Ursprung
Commit
893b6cf4f2
26 geänderte Dateien mit 948 neuen und 369 gelöschten Zeilen
  1. 19 19
      README.md
  2. 12 13
      yudao-ui-admin-vue3/README.md
  3. 8 8
      yudao-ui-admin-vue3/package.json
  4. 97 90
      yudao-ui-admin-vue3/pnpm-lock.yaml
  5. 1 1
      yudao-ui-admin-vue3/src/api/system/user/profile.ts
  6. 1 1
      yudao-ui-admin-vue3/src/components/ContentDetailWrap/src/ContentDetailWrap.vue
  7. 4 0
      yudao-ui-admin-vue3/src/components/Cropper/index.ts
  8. 257 0
      yudao-ui-admin-vue3/src/components/Cropper/src/CopperModal.vue
  9. 190 0
      yudao-ui-admin-vue3/src/components/Cropper/src/Cropper.vue
  10. 136 0
      yudao-ui-admin-vue3/src/components/Cropper/src/CropperAvatar.vue
  11. 8 0
      yudao-ui-admin-vue3/src/components/Cropper/src/types.ts
  12. 1 1
      yudao-ui-admin-vue3/src/config/axios/index.ts
  13. 2 0
      yudao-ui-admin-vue3/src/layout/components/Menu/src/Menu.vue
  14. 3 0
      yudao-ui-admin-vue3/src/layout/components/Message/index.ts
  15. 119 0
      yudao-ui-admin-vue3/src/layout/components/Message/src/Message.vue
  16. 2 0
      yudao-ui-admin-vue3/src/layout/components/Setting/src/Setting.vue
  17. 12 0
      yudao-ui-admin-vue3/src/layout/components/Setting/src/components/InterfaceDisplay.vue
  18. 9 2
      yudao-ui-admin-vue3/src/layout/components/ToolHeader.vue
  19. 15 0
      yudao-ui-admin-vue3/src/locales/en.ts
  20. 15 0
      yudao-ui-admin-vue3/src/locales/zh-CN.ts
  21. 1 1
      yudao-ui-admin-vue3/src/router/modules/remaining.ts
  22. 8 0
      yudao-ui-admin-vue3/src/store/modules/app.ts
  23. 25 230
      yudao-ui-admin-vue3/src/views/Profile/components/UserAvatar.vue
  24. 1 1
      yudao-ui-admin-vue3/src/views/infra/codegen/components/CloumInfoForm.vue
  25. 1 1
      yudao-ui-admin-vue3/src/views/system/menu/index.vue
  26. 1 1
      yudao-ui-admin/src/views/infra/codegen/editTable.vue

+ 19 - 19
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 模块    |
 
 ### 后端
 
@@ -223,7 +223,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 框架                                                                  |     说明      |   版本   |
 |----------------------------------------------------------------------|:------------:|:------:|
 | [Vue](https://staging-cn.vuejs.org/)                                 |   Vue 框架    | 3.2.45 |
-| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具  | 4.0.2  |
+| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具  | 4.0.3  |
 | [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.28 |
@@ -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) | -                                                                |
 
 ### 工作流程

+ 12 - 13
yudao-ui-admin-vue3/README.md

@@ -1,16 +1,15 @@
-<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/-Vite4-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>
 
 ## 介绍
@@ -30,11 +29,11 @@
 | 框架 | 说明 | 版本 |
 | --- | --- | --- |
 | [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.45 |
-| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.0.2 |
+| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.0.3 |
 | [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.28 |
-| [vueuse](https://vueuse.org/) | 常用工具集 | 9.6.0 |
+| [vueuse](https://vueuse.org/) | 常用工具集 | 9.8.2 |
 | [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 |

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

@@ -1,6 +1,6 @@
 {
   "name": "yudao-ui-admin-vue3",
-  "version": "1.6.5.1877",
+  "version": "1.6.5.1878",
   "description": "基于vue3、vite4、element-plus、typesScript",
   "author": "xingyu",
   "private": false,
@@ -25,12 +25,13 @@
   },
   "dependencies": {
     "@iconify/iconify": "^3.0.1",
-    "@vueuse/core": "^9.7.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.1",
@@ -46,10 +47,9 @@
     "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",
+    "vue-types": "^5.0.2",
     "vxe-table": "^4.3.7",
     "web-storage-cache": "^1.1.1",
     "xe-utils": "^3.5.7"
@@ -57,7 +57,7 @@
   "devDependencies": {
     "@commitlint/cli": "^17.3.0",
     "@commitlint/config-conventional": "^17.3.0",
-    "@iconify/json": "^2.1.154",
+    "@iconify/json": "^2.1.155",
     "@intlify/unplugin-vue-i18n": "^0.8.1",
     "@purge-icons/generated": "^0.9.0",
     "@types/intro.js": "^5.1.0",
@@ -84,7 +84,7 @@
     "postcss-scss": "^4.0.6",
     "prettier": "^2.8.1",
     "rimraf": "^3.0.2",
-    "rollup": "^3.7.5",
+    "rollup": "^3.8.0",
     "sass": "^1.57.1",
     "stylelint": "^14.16.0",
     "stylelint-config-html": "^1.1.0",
@@ -94,7 +94,7 @@
     "stylelint-order": "^5.0.0",
     "terser": "^5.16.1",
     "typescript": "4.9.4",
-    "vite": "4.0.2",
+    "vite": "4.0.3",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-ejs": "^1.6.4",
     "vite-plugin-eslint": "^1.8.1",
@@ -104,7 +104,7 @@
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-vue-setup-extend": "^0.4.0",
     "vite-plugin-windicss": "^1.8.10",
-    "vue-tsc": "^1.0.14",
+    "vue-tsc": "^1.0.16",
     "windicss": "^3.5.6"
   },
   "engines": {

+ 97 - 90
yudao-ui-admin-vue3/pnpm-lock.yaml

@@ -4,7 +4,7 @@ specifiers:
   '@commitlint/cli': ^17.3.0
   '@commitlint/config-conventional': ^17.3.0
   '@iconify/iconify': ^3.0.1
-  '@iconify/json': ^2.1.154
+  '@iconify/json': ^2.1.155
   '@intlify/unplugin-vue-i18n': ^0.8.1
   '@purge-icons/generated': ^0.9.0
   '@types/intro.js': ^5.1.0
@@ -18,7 +18,7 @@ specifiers:
   '@vitejs/plugin-legacy': ^3.0.1
   '@vitejs/plugin-vue': ^4.0.0
   '@vitejs/plugin-vue-jsx': ^3.0.0
-  '@vueuse/core': ^9.7.0
+  '@vueuse/core': ^9.8.2
   '@wangeditor/editor': ^5.1.23
   '@wangeditor/editor-for-vue': ^5.1.10
   '@zxcvbn-ts/core': ^2.1.0
@@ -26,6 +26,7 @@ specifiers:
   autoprefixer: ^10.4.13
   axios: ^1.2.1
   consola: ^2.15.3
+  cropperjs: ^1.5.13
   crypto-js: ^4.1.1
   dayjs: ^1.11.7
   echarts: ^5.4.1
@@ -50,7 +51,7 @@ specifiers:
   qrcode: ^1.5.1
   qs: ^6.11.0
   rimraf: ^3.0.2
-  rollup: ^3.7.5
+  rollup: ^3.8.0
   sass: ^1.57.1
   stylelint: ^14.16.0
   stylelint-config-html: ^1.1.0
@@ -61,7 +62,7 @@ specifiers:
   terser: ^5.16.1
   typescript: 4.9.4
   url: ^0.11.0
-  vite: 4.0.2
+  vite: 4.0.3
   vite-plugin-compression: ^0.5.1
   vite-plugin-ejs: ^1.6.4
   vite-plugin-eslint: ^1.8.1
@@ -72,11 +73,10 @@ specifiers:
   vite-plugin-vue-setup-extend: ^0.4.0
   vite-plugin-windicss: ^1.8.10
   vue: 3.2.45
-  vue-cropper: ^1.0.3
   vue-i18n: 9.2.2
   vue-router: ^4.1.6
-  vue-tsc: ^1.0.14
-  vue-types: ^5.0.1
+  vue-tsc: ^1.0.16
+  vue-types: ^5.0.2
   vxe-table: ^4.3.7
   web-storage-cache: ^1.1.1
   windicss: ^3.5.6
@@ -84,12 +84,13 @@ specifiers:
 
 dependencies:
   '@iconify/iconify': 3.0.1
-  '@vueuse/core': 9.7.0_vue@3.2.45
+  '@vueuse/core': 9.8.2_vue@3.2.45
   '@wangeditor/editor': 5.1.23
   '@wangeditor/editor-for-vue': 5.1.12_3apfu3xbp6awzuex7ed3sbrv6y
   '@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: registry.npmmirror.com/echarts/5.4.1
@@ -105,10 +106,9 @@ dependencies:
   qs: 6.11.0
   url: 0.11.0
   vue: 3.2.45
-  vue-cropper: 1.0.5
   vue-i18n: 9.2.2_vue@3.2.45
   vue-router: 4.1.6_vue@3.2.45
-  vue-types: 5.0.1_vue@3.2.45
+  vue-types: 5.0.2_vue@3.2.45
   vxe-table: registry.npmmirror.com/vxe-table/4.3.7_vue@3.2.45+xe-utils@3.5.7
   web-storage-cache: 1.1.1
   xe-utils: 3.5.7
@@ -116,7 +116,7 @@ dependencies:
 devDependencies:
   '@commitlint/cli': 17.3.0
   '@commitlint/config-conventional': 17.3.0
-  '@iconify/json': 2.1.154
+  '@iconify/json': 2.1.155
   '@intlify/unplugin-vue-i18n': 0.8.1_vue-i18n@9.2.2
   '@purge-icons/generated': 0.9.0
   '@types/intro.js': 5.1.0
@@ -127,9 +127,9 @@ devDependencies:
   '@types/qs': 6.9.7
   '@typescript-eslint/eslint-plugin': 5.47.0_ncmi6noazr3nzas7jxykisekym
   '@typescript-eslint/parser': 5.47.0_lzzuuodtsqwxnvqeq4g4likcqa
-  '@vitejs/plugin-legacy': registry.npmmirror.com/@vitejs/plugin-legacy/3.0.1_terser@5.16.1+vite@4.0.2
-  '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/4.0.0_vite@4.0.2+vue@3.2.45
-  '@vitejs/plugin-vue-jsx': registry.npmmirror.com/@vitejs/plugin-vue-jsx/3.0.0_vite@4.0.2+vue@3.2.45
+  '@vitejs/plugin-legacy': registry.npmmirror.com/@vitejs/plugin-legacy/3.0.1_terser@5.16.1+vite@4.0.3
+  '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/4.0.0_vite@4.0.3+vue@3.2.45
+  '@vitejs/plugin-vue-jsx': registry.npmmirror.com/@vitejs/plugin-vue-jsx/3.0.0_vite@4.0.3+vue@3.2.45
   autoprefixer: 10.4.13_postcss@8.4.20
   consola: 2.15.3
   eslint: 8.30.0
@@ -143,7 +143,7 @@ devDependencies:
   postcss-scss: 4.0.6_postcss@8.4.20
   prettier: 2.8.1
   rimraf: 3.0.2
-  rollup: 3.7.5
+  rollup: 3.8.0
   sass: 1.57.1
   stylelint: 14.16.0
   stylelint-config-html: 1.1.0_bto6pmslw3cibsiuq6smxboytq
@@ -153,17 +153,17 @@ devDependencies:
   stylelint-order: 5.0.0_stylelint@14.16.0
   terser: 5.16.1
   typescript: 4.9.4
-  vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
-  vite-plugin-compression: 0.5.1_vite@4.0.2
+  vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
+  vite-plugin-compression: 0.5.1_vite@4.0.3
   vite-plugin-ejs: 1.6.4
-  vite-plugin-eslint: 1.8.1_eslint@8.30.0+vite@4.0.2
-  vite-plugin-progress: 0.0.6_vite@4.0.2
-  vite-plugin-purge-icons: registry.npmmirror.com/vite-plugin-purge-icons/0.9.2_vite@4.0.2
-  vite-plugin-style-import: 2.0.0_vite@4.0.2
-  vite-plugin-svg-icons: 2.0.1_vite@4.0.2
-  vite-plugin-vue-setup-extend: 0.4.0_vite@4.0.2
-  vite-plugin-windicss: registry.npmmirror.com/vite-plugin-windicss/1.8.10_vite@4.0.2
-  vue-tsc: 1.0.14_typescript@4.9.4
+  vite-plugin-eslint: 1.8.1_eslint@8.30.0+vite@4.0.3
+  vite-plugin-progress: 0.0.6_vite@4.0.3
+  vite-plugin-purge-icons: registry.npmmirror.com/vite-plugin-purge-icons/0.9.2_vite@4.0.3
+  vite-plugin-style-import: 2.0.0_vite@4.0.3
+  vite-plugin-svg-icons: 2.0.1_vite@4.0.3
+  vite-plugin-vue-setup-extend: 0.4.0_vite@4.0.3
+  vite-plugin-windicss: registry.npmmirror.com/vite-plugin-windicss/1.8.10_vite@4.0.3
+  vue-tsc: 1.0.16_typescript@4.9.4
   windicss: 3.5.6
 
 packages:
@@ -631,8 +631,8 @@ packages:
     dependencies:
       '@iconify/types': 2.0.0
 
-  /@iconify/json/2.1.154:
-    resolution: {integrity: sha512-RpUu4zDoeqIAzD54t8mfCKca+fzASgFf+lFE7Y9OzvScrf+IbUrvyZZEcOkjEB1cB4emDgmWGTAAkVTihhaqxQ==}
+  /@iconify/json/2.1.155:
+    resolution: {integrity: sha512-Zr0809RkIKB/0pX3jFfLX4TI8fv1aW6+FHXlfRiDhFKQSmhcs2x5xYhtxI5GUzl+9o6Le544uUCljmHBYJUFrA==}
     dependencies:
       '@iconify/types': 2.0.0
       pathe: 0.3.9
@@ -1079,44 +1079,44 @@ packages:
       nanoid: registry.npmmirror.com/nanoid/3.3.4
     dev: false
 
-  /@volar/language-core/1.0.14:
-    resolution: {integrity: sha512-j1tMQgw0qCV2amM4qDJNG/zc0yj3ay8HoWNt05IaiCPsULtSSpF/9+F6Izvn0DF7nWOd6MUHTxaQAeZwLfr56Q==}
+  /@volar/language-core/1.0.16:
+    resolution: {integrity: sha512-IGnOxWTs4DZ81TDcmxBAkCBxs97hUblwcjpBsTx/pOGGaSSDQRJPn0wL8NYTybEObU0i7lhEpKZ+0vJfdIy1Kg==}
     dependencies:
-      '@volar/source-map': 1.0.14
+      '@volar/source-map': 1.0.16
       '@vue/reactivity': 3.2.45
       muggle-string: 0.1.0
     dev: true
 
-  /@volar/source-map/1.0.14:
-    resolution: {integrity: sha512-8pHCbEWHWaSDGb/FM9zRIW1lY1OAo16MENVSQGCgTwz7PWf3Gw6WW3TFVKCtzaFhLjPH0i5e9hALy7vBPbSHoA==}
+  /@volar/source-map/1.0.16:
+    resolution: {integrity: sha512-PKjzmQcg8QOGC/1V9tmGh2jcy6bKLhkW5bGidElSr83iDbCzLvldt2/La/QlDxaRCHYLT0MeyuGJBZIChB1dYQ==}
     dependencies:
       muggle-string: 0.1.0
     dev: true
 
-  /@volar/typescript/1.0.14:
-    resolution: {integrity: sha512-67qcjjz7KGFhMCG9EKMA9qJK3BRGQecO4dGyAKfMfClZ/PaVoKfDvJvYo89McGTQ8SeczD48I9TPnaJM0zK8JQ==}
+  /@volar/typescript/1.0.16:
+    resolution: {integrity: sha512-Yov+n4oO3iYnuMt9QJAFpJabfTRCzc7KvjlAwBaSuZy+Gc/f9611MgtqAh5/SIGmltFN8dXn1Ijno8ro8I4lyw==}
     dependencies:
-      '@volar/language-core': 1.0.14
+      '@volar/language-core': 1.0.16
     dev: true
 
-  /@volar/vue-language-core/1.0.14:
-    resolution: {integrity: sha512-grJ4dQ7c/suZmBBmZtw2O2XeDX+rtgpdBtHxMug1NMPRDxj5EZ9WGphWtGnMQj8RyVgpz9ByvV5GbQjk4/wfBw==}
+  /@volar/vue-language-core/1.0.16:
+    resolution: {integrity: sha512-sQ/aW1Vuiyy4OQuh2lthyYicruM3qh9VSk/aDh8/bFvM8GoohHZqVpMN3LYldEJ9eT/rN6u4xmYP54vc/EjX4Q==}
     dependencies:
-      '@volar/language-core': 1.0.14
-      '@volar/source-map': 1.0.14
+      '@volar/language-core': 1.0.16
+      '@volar/source-map': 1.0.16
       '@vue/compiler-dom': 3.2.45
       '@vue/compiler-sfc': 3.2.45
       '@vue/reactivity': 3.2.45
       '@vue/shared': 3.2.45
-      minimatch: 5.1.0
+      minimatch: 5.1.2
       vue-template-compiler: 2.7.14
     dev: true
 
-  /@volar/vue-typescript/1.0.14:
-    resolution: {integrity: sha512-2P0QeGLLY05fDTu8GqY8SR2+jldXRTrkQdD2Nc0sVOjMJ7j3RYYY0wJyZ9hCBDuxV4Micc6jdB8nKS0yxQgNvA==}
+  /@volar/vue-typescript/1.0.16:
+    resolution: {integrity: sha512-M018Ulg/o2FVktAdlr5b/z4K69bYzekxNUA1o39y5Ur6CObc/o+5eDCCS7gIYijWnx9iNKkSQpWWWblJFv7kHQ==}
     dependencies:
-      '@volar/typescript': 1.0.14
-      '@volar/vue-language-core': 1.0.14
+      '@volar/typescript': 1.0.16
+      '@volar/vue-language-core': 1.0.16
     dev: true
 
   /@vue/compiler-core/3.2.45:
@@ -1196,24 +1196,24 @@ packages:
   /@vue/shared/3.2.45:
     resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
 
-  /@vueuse/core/9.7.0_vue@3.2.45:
-    resolution: {integrity: sha512-/AGY/t7jJPxCyRoVTygNKoroTiCvRaaZIW+yeSlBCnI7QRpQ9cvXNTdNaSl3GvSyFbn83+XwZwEZvI1OpQfeGw==}
+  /@vueuse/core/9.8.2_vue@3.2.45:
+    resolution: {integrity: sha512-aWiCmcYIpPt7xjuqYiceODEMHchDYthrJ4AqI+FXPZrR23PZOqdiktbUVyQl2kGlR3H4i9UJ/uimQrwhz9UouQ==}
     dependencies:
       '@types/web-bluetooth': 0.0.16
-      '@vueuse/metadata': 9.7.0
-      '@vueuse/shared': 9.7.0_vue@3.2.45
+      '@vueuse/metadata': 9.8.2
+      '@vueuse/shared': 9.8.2_vue@3.2.45
       vue-demi: 0.13.11_vue@3.2.45
     transitivePeerDependencies:
       - '@vue/composition-api'
       - vue
     dev: false
 
-  /@vueuse/metadata/9.7.0:
-    resolution: {integrity: sha512-M7WsAgw28FNtTH0bzsGuHEtJOPJqPpyeHS6PHq+8UesLgNjZ9waMAntiUrgUQlxt09M4i2lH7y9sRi0jkfeXGA==}
+  /@vueuse/metadata/9.8.2:
+    resolution: {integrity: sha512-N4E/BKS+9VsUeD4WLVRU1J2kCOLh+iikBcMtipFcTyL204132vDYHs27zLAVabJYGnhC0dIVGdhg9pbOZiY2TQ==}
     dev: false
 
-  /@vueuse/shared/9.7.0_vue@3.2.45:
-    resolution: {integrity: sha512-pwmt1y3TJ2s5KqWmkv9ZKEV59GwuZQZk8XLiU+hGswz0jej318ozbea9E4A/A50ksyM26swSFr7sZ9llNPsZHg==}
+  /@vueuse/shared/9.8.2_vue@3.2.45:
+    resolution: {integrity: sha512-ACjrPQzowd5dnabNJt9EoGVobco9/ENiA5qP53vjiuxndlJYuc/UegwhXC7KdQbPX4F45a50+45K3g1wNqOzmA==}
     dependencies:
       vue-demi: 0.13.11_vue@3.2.45
     transitivePeerDependencies:
@@ -2019,6 +2019,10 @@ packages:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
     dev: true
 
+  /cropperjs/1.5.13:
+    resolution: {integrity: sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==}
+    dev: false
+
   /cross-spawn/7.0.3:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
     engines: {node: '>= 8'}
@@ -2319,7 +2323,7 @@ packages:
       '@popperjs/core': /@sxzz/popperjs-es/2.11.7
       '@types/lodash': 4.14.189
       '@types/lodash-es': 4.17.6
-      '@vueuse/core': 9.7.0_vue@3.2.45
+      '@vueuse/core': 9.8.2_vue@3.2.45
       async-validator: 4.2.5
       dayjs: 1.11.7
       escape-html: 1.0.3
@@ -3831,6 +3835,13 @@ packages:
       brace-expansion: 2.0.1
     dev: true
 
+  /minimatch/5.1.2:
+    resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==}
+    engines: {node: '>=10'}
+    dependencies:
+      brace-expansion: 2.0.1
+    dev: true
+
   /minimist-options/4.1.0:
     resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
     engines: {node: '>= 6'}
@@ -4544,8 +4555,8 @@ packages:
       fsevents: 2.3.2
     dev: true
 
-  /rollup/3.7.5:
-    resolution: {integrity: sha512-z0ZbqHBtS/et2EEUKMrAl2CoSdwN7ZPzL17UMiKN9RjjqHShTlv7F9J6ZJZJNREYjBh3TvBrdfjkFDIXFNeuiQ==}
+  /rollup/3.8.0:
+    resolution: {integrity: sha512-+UR6PnUslneJNiJfLSzy4XH6R50ZGF0MS7UCv20ftXrktF/TkvZDwiBtXX65esblLR5p8w6LmXgPwt2f2B8SoQ==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -5338,7 +5349,7 @@ packages:
     engines: {node: '>= 0.8'}
     dev: true
 
-  /vite-plugin-compression/0.5.1_vite@4.0.2:
+  /vite-plugin-compression/0.5.1_vite@4.0.3:
     resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==}
     peerDependencies:
       vite: '>=2.0.0'
@@ -5346,7 +5357,7 @@ packages:
       chalk: 4.1.2
       debug: 4.3.4
       fs-extra: 10.1.0
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -5357,7 +5368,7 @@ packages:
       ejs: 3.1.8
     dev: true
 
-  /vite-plugin-eslint/1.8.1_eslint@8.30.0+vite@4.0.2:
+  /vite-plugin-eslint/1.8.1_eslint@8.30.0+vite@4.0.3:
     resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==}
     peerDependencies:
       eslint: '>=7'
@@ -5367,10 +5378,10 @@ packages:
       '@types/eslint': 8.4.10
       eslint: 8.30.0
       rollup: 2.79.1
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     dev: true
 
-  /vite-plugin-progress/0.0.6_vite@4.0.2:
+  /vite-plugin-progress/0.0.6_vite@4.0.3:
     resolution: {integrity: sha512-pIK2TVEY4XFGrz10CQDdEufBBCDaV0geRHfXV3abGTBr+OF9O0Zmd3ZDrHJXDv4Rl3qAQP4BTCuPYQ3XqstmqA==}
     engines: {node: '>=14', pnpm: '>=7.0.0'}
     peerDependencies:
@@ -5379,10 +5390,10 @@ packages:
       picocolors: 1.0.0
       progress: 2.0.3
       rd: 2.0.1
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     dev: true
 
-  /vite-plugin-style-import/2.0.0_vite@4.0.2:
+  /vite-plugin-style-import/2.0.0_vite@4.0.3:
     resolution: {integrity: sha512-qtoHQae5dSUQPo/rYz/8p190VU5y19rtBaeV7ryLa/AYAU/e9CG89NrN/3+k7MR8mJy/GPIu91iJ3zk9foUOSA==}
     peerDependencies:
       vite: '>=2.0.0'
@@ -5394,10 +5405,10 @@ packages:
       fs-extra: 10.1.0
       magic-string: 0.25.9
       pathe: 0.2.0
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     dev: true
 
-  /vite-plugin-svg-icons/2.0.1_vite@4.0.2:
+  /vite-plugin-svg-icons/2.0.1_vite@4.0.3:
     resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==}
     peerDependencies:
       vite: '>=2.0.0'
@@ -5410,23 +5421,23 @@ packages:
       pathe: 0.2.0
       svg-baker: 1.7.0
       svgo: 2.8.0
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /vite-plugin-vue-setup-extend/0.4.0_vite@4.0.2:
+  /vite-plugin-vue-setup-extend/0.4.0_vite@4.0.3:
     resolution: {integrity: sha512-WMbjPCui75fboFoUTHhdbXzu4Y/bJMv5N9QT9a7do3wNMNHHqrk+Tn2jrSJU0LS5fGl/EG+FEDBYVUeWIkDqXQ==}
     peerDependencies:
       vite: '>=2.0.0'
     dependencies:
       '@vue/compiler-sfc': 3.2.45
       magic-string: 0.25.9
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     dev: true
 
-  /vite/4.0.2_amyfslv6hdwxqbf7f4ldebbtce:
-    resolution: {integrity: sha512-QJaY3R+tFlTagH0exVqbgkkw45B+/bXVBzF2ZD1KB5Z8RiAoiKo60vSUf6/r4c2Vh9jfGBKM4oBI9b4/1ZJYng==}
+  /vite/4.0.3_amyfslv6hdwxqbf7f4ldebbtce:
+    resolution: {integrity: sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
     peerDependencies:
@@ -5454,17 +5465,13 @@ packages:
       esbuild: 0.16.5
       postcss: 8.4.20
       resolve: 1.22.1
-      rollup: 3.7.5
+      rollup: 3.8.0
       sass: 1.57.1
       terser: 5.16.1
     optionalDependencies:
       fsevents: 2.3.2
     dev: true
 
-  /vue-cropper/1.0.5:
-    resolution: {integrity: sha512-D4XXdqWmMWRLOIV9LIh7/mkH6OBOMQDFbRjwntkxmAtxOtwpC9U5ZZ6lSXw5F5cbd4g8znDjk6MuCwIL+fZSrA==}
-    dev: false
-
   /vue-demi/0.13.11_vue@3.2.45:
     resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
     engines: {node: '>=12'}
@@ -5526,19 +5533,19 @@ packages:
       he: 1.2.0
     dev: true
 
-  /vue-tsc/1.0.14_typescript@4.9.4:
-    resolution: {integrity: sha512-HeqtyxMrSRUCnU5nxB0lQc3o7zirMppZ/V6HLL3l4FsObGepH3A3beNmNehpLQs0Gt7DkSWVi3CpVCFgrf+/sQ==}
+  /vue-tsc/1.0.16_typescript@4.9.4:
+    resolution: {integrity: sha512-yZaiJBbcKR1rSLhiF9KryAFH7R63po+N/invr2EAHGXxMzZksE5j1zyQKvrYiqK47ZHLAlCR+re/PHqWp/UzTg==}
     hasBin: true
     peerDependencies:
       typescript: '*'
     dependencies:
-      '@volar/vue-language-core': 1.0.14
-      '@volar/vue-typescript': 1.0.14
+      '@volar/vue-language-core': 1.0.16
+      '@volar/vue-typescript': 1.0.16
       typescript: 4.9.4
     dev: true
 
-  /vue-types/5.0.1_vue@3.2.45:
-    resolution: {integrity: sha512-pHahh/Ht+mxAhSGXfFe0WQidZukYUoCevPoQtePpONrmNP+OjX3/ps/XjYLgw/Zbv4rks242eiVJcjE3EELsyw==}
+  /vue-types/5.0.2_vue@3.2.45:
+    resolution: {integrity: sha512-+/5hnQ65XOYqPs+tEUF8GGTJX95UFVH0wPQo71IJJYh5TKMfik2tGKTLkZ42JqAczANA9hGu5FrZmPgxn20nnA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       vue: ^2.0.0 || ^3.0.0
@@ -6239,7 +6246,7 @@ packages:
       '@iconify/iconify': registry.npmmirror.com/@iconify/iconify/3.0.1
     dev: true
 
-  registry.npmmirror.com/@vitejs/plugin-legacy/3.0.1_terser@5.16.1+vite@4.0.2:
+  registry.npmmirror.com/@vitejs/plugin-legacy/3.0.1_terser@5.16.1+vite@4.0.3:
     resolution: {integrity: sha512-XCtEjxoR3rmy000ujYRBp5kggWqzHz9+F20/yIMUWOzbvu0+KW1e14Fvb8h7SpNn+bfjGW1RiAs1Vrgb7Js+iQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vitejs/plugin-legacy/-/plugin-legacy-3.0.1.tgz}
     id: registry.npmmirror.com/@vitejs/plugin-legacy/3.0.1
     name: '@vitejs/plugin-legacy'
@@ -6255,10 +6262,10 @@ packages:
       regenerator-runtime: registry.npmmirror.com/regenerator-runtime/0.13.11
       systemjs: registry.npmmirror.com/systemjs/6.13.0
       terser: 5.16.1
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     dev: true
 
-  registry.npmmirror.com/@vitejs/plugin-vue-jsx/3.0.0_vite@4.0.2+vue@3.2.45:
+  registry.npmmirror.com/@vitejs/plugin-vue-jsx/3.0.0_vite@4.0.3+vue@3.2.45:
     resolution: {integrity: sha512-vurkuzgac5SYuxd2HUZqAFAWGTF10diKBwJNbCvnWijNZfXd+7jMtqjPFbGt7idOJUn584fP1Ar9j/GN2jQ3Ew==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.0.0.tgz}
     id: registry.npmmirror.com/@vitejs/plugin-vue-jsx/3.0.0
     name: '@vitejs/plugin-vue-jsx'
@@ -6271,13 +6278,13 @@ packages:
       '@babel/core': registry.npmmirror.com/@babel/core/7.20.5
       '@babel/plugin-transform-typescript': registry.npmmirror.com/@babel/plugin-transform-typescript/7.20.2_@babel+core@7.20.5
       '@vue/babel-plugin-jsx': registry.npmmirror.com/@vue/babel-plugin-jsx/1.1.1_@babel+core@7.20.5
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
       vue: 3.2.45
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  registry.npmmirror.com/@vitejs/plugin-vue/4.0.0_vite@4.0.2+vue@3.2.45:
+  registry.npmmirror.com/@vitejs/plugin-vue/4.0.0_vite@4.0.3+vue@3.2.45:
     resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz}
     id: registry.npmmirror.com/@vitejs/plugin-vue/4.0.0
     name: '@vitejs/plugin-vue'
@@ -6287,7 +6294,7 @@ packages:
       vite: ^4.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
       vue: 3.2.45
     dev: true
 
@@ -7026,7 +7033,7 @@ packages:
       picocolors: 1.0.0
     dev: true
 
-  registry.npmmirror.com/vite-plugin-purge-icons/0.9.2_vite@4.0.2:
+  registry.npmmirror.com/vite-plugin-purge-icons/0.9.2_vite@4.0.3:
     resolution: {integrity: sha512-vxJEMyNyckqLr/4HPsW9P6BMLUvOVMvjjFz3jLl4Wke1KWE8ITJUxIUwodxaOmEp9L2lxVk5an3TYeycZCfqFw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite-plugin-purge-icons/-/vite-plugin-purge-icons-0.9.2.tgz}
     id: registry.npmmirror.com/vite-plugin-purge-icons/0.9.2
     name: vite-plugin-purge-icons
@@ -7038,13 +7045,13 @@ packages:
       '@purge-icons/core': registry.npmmirror.com/@purge-icons/core/0.9.1
       '@purge-icons/generated': registry.npmmirror.com/@purge-icons/generated/0.9.0
       rollup-plugin-purge-icons: registry.npmmirror.com/rollup-plugin-purge-icons/0.9.1
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
     transitivePeerDependencies:
       - encoding
       - supports-color
     dev: true
 
-  registry.npmmirror.com/vite-plugin-windicss/1.8.10_vite@4.0.2:
+  registry.npmmirror.com/vite-plugin-windicss/1.8.10_vite@4.0.3:
     resolution: {integrity: sha512-scywsuzo46lcTBohspmF0WiwhWEte6p+OUVrX4yr7VMRvLHMHVfLtJReyD5pppjijG7YOwVsZn7XBWWZtF658Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite-plugin-windicss/-/vite-plugin-windicss-1.8.10.tgz}
     id: registry.npmmirror.com/vite-plugin-windicss/1.8.10
     name: vite-plugin-windicss
@@ -7055,7 +7062,7 @@ packages:
       '@windicss/plugin-utils': registry.npmmirror.com/@windicss/plugin-utils/1.8.10
       debug: registry.npmmirror.com/debug/4.3.4
       kolorist: registry.npmmirror.com/kolorist/1.6.0
-      vite: 4.0.2_amyfslv6hdwxqbf7f4ldebbtce
+      vite: 4.0.3_amyfslv6hdwxqbf7f4ldebbtce
       windicss: registry.npmmirror.com/windicss/3.5.6
     transitivePeerDependencies:
       - supports-color

+ 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 }

+ 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>
   }
 }

+ 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;
 

+ 3 - 0
yudao-ui-admin-vue3/src/layout/components/Message/index.ts

@@ -0,0 +1,3 @@
+import Message from './src/Message.vue'
+
+export { Message }

+ 119 - 0
yudao-ui-admin-vue3/src/layout/components/Message/src/Message.vue

@@ -0,0 +1,119 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import { ElTabs, ElTabPane, ElPopover, ElBadge } from 'element-plus'
+
+const activeName = ref('notice')
+
+const noticeList = ref([
+  { id: 1, title: '版本升级1', date: '2022-12-12 10:00:00' },
+  { id: 2, title: '版本升级2', date: '2022-12-12 10:00:00' },
+  { id: 3, title: '版本升级3', date: '2022-12-12 10:00:00' },
+  { id: 4, title: '版本升级4', date: '2022-12-12 10:00:00' },
+  { id: 5, title: '版本升级5', date: '2022-12-12 10:00:00' }
+])
+const messageList = ref([
+  { id: 1, title: '加班1', date: '2022-12-12 10:00:00' },
+  { id: 2, title: '加班2', date: '2022-12-12 10:00:00' },
+  { id: 3, title: '加班3', date: '2022-12-12 10:00:00' },
+  { id: 4, title: '加班4', date: '2022-12-12 10:00:00' },
+  { id: 5, title: '加班5', date: '2022-12-12 10:00:00' }
+])
+const needList = ref([
+  { id: 1, title: '审批1', date: '2022-12-12 10:00:00' },
+  { id: 2, title: '审批2', date: '2022-12-12 10:00:00' },
+  { id: 3, title: '审批3', date: '2022-12-12 10:00:00' },
+  { id: 4, title: '审批4', date: '2022-12-12 10:00:00' },
+  { id: 5, title: '审批5', date: '2022-12-12 10:00:00' }
+])
+</script>
+<template>
+  <div class="message">
+    <ElPopover placement="bottom" :width="310" trigger="click">
+      <template #reference>
+        <ElBadge :value="noticeList.length" class="item">
+          <Icon icon="ep:bell" :size="18" class="cursor-pointer" />
+        </ElBadge>
+      </template>
+      <ElTabs v-model="activeName">
+        <ElTabPane label="通知(5)" name="notice">
+          <div class="message-list">
+            <template v-for="item in noticeList" :key="item.id">
+              <div class="message-item">
+                <img src="@/assets/imgs/avatar.gif" alt="" class="message-icon" />
+                <div class="message-content">
+                  <span class="message-title">{{ item.title }}</span>
+                  <span class="message-date">{{ item.date }}</span>
+                </div>
+              </div>
+            </template>
+          </div>
+        </ElTabPane>
+        <ElTabPane label="消息(0)" name="message">
+          <div class="message-list">
+            <template v-for="item in messageList" :key="item.id">
+              <div class="message-item">
+                <img src="@/assets/imgs/avatar.gif" alt="" class="message-icon" />
+                <div class="message-content">
+                  <span class="message-title">{{ item.title }}</span>
+                  <span class="message-date">{{ item.date }}</span>
+                </div>
+              </div>
+            </template>
+          </div>
+        </ElTabPane>
+        <ElTabPane label="代办(0)" name="need">
+          <div class="message-list">
+            <template v-for="item in needList" :key="item.id">
+              <div class="message-item">
+                <img src="@/assets/imgs/avatar.gif" alt="" class="message-icon" />
+                <div class="message-content">
+                  <span class="message-title">{{ item.title }}</span>
+                  <span class="message-date">{{ item.date }}</span>
+                </div>
+              </div>
+            </template>
+          </div>
+        </ElTabPane>
+      </ElTabs>
+    </ElPopover>
+  </div>
+</template>
+<style scoped lang="scss">
+.message-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 260px;
+  line-height: 45px;
+}
+.message-list {
+  display: flex;
+  flex-direction: column;
+  .message-item {
+    display: flex;
+    align-items: center;
+    padding: 20px 0;
+    border-bottom: 1px solid var(--el-border-color-light);
+    &:last-child {
+      border: none;
+    }
+    .message-icon {
+      width: 40px;
+      height: 40px;
+      margin: 0 20px 0 5px;
+    }
+    .message-content {
+      display: flex;
+      flex-direction: column;
+      .message-title {
+        margin-bottom: 5px;
+      }
+      .message-date {
+        font-size: 12px;
+        color: var(--el-text-color-secondary);
+      }
+    }
+  }
+}
+</style>

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

@@ -122,6 +122,8 @@ const copyConfig = async () => {
       size: ${appStore.getSize},
       // 多语言图标
       locale: ${appStore.getLocale},
+      // 消息图标
+      message: ${appStore.getMessage},
       // 标签页
       tagsView: ${appStore.getTagsView},
       // 标签页图标

+ 12 - 0
yudao-ui-admin-vue3/src/layout/components/Setting/src/components/InterfaceDisplay.vue

@@ -57,6 +57,13 @@ const localeChange = (show: boolean) => {
   appStore.setLocale(show)
 }
 
+// 消息图标
+const message = ref(appStore.getMessage)
+
+const messageChange = (show: boolean) => {
+  appStore.setMessage(show)
+}
+
 // 标签页
 const tagsView = ref(appStore.getTagsView)
 
@@ -164,6 +171,11 @@ watch(
       <ElSwitch v-model="locale" @change="localeChange" />
     </div>
 
+    <div class="flex justify-between items-center">
+      <span class="text-14px">{{ t('setting.messageIcon') }}</span>
+      <ElSwitch v-model="message" @change="messageChange" />
+    </div>
+
     <div class="flex justify-between items-center">
       <span class="text-14px">{{ t('setting.tagsView') }}</span>
       <ElSwitch v-model="tagsView" @change="tagsViewChange" />

+ 9 - 2
yudao-ui-admin-vue3/src/layout/components/ToolHeader.vue

@@ -1,11 +1,12 @@
 <script lang="tsx">
 import { defineComponent, computed } from 'vue'
+import { Message } from '@/layout/components//Message'
 import { Collapse } from '@/layout/components/Collapse'
-import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
-import { SizeDropdown } from '@/layout/components/SizeDropdown'
 import { UserInfo } from '@/layout/components/UserInfo'
 import { Screenfull } from '@/layout/components/Screenfull'
 import { Breadcrumb } from '@/layout/components/Breadcrumb'
+import { SizeDropdown } from '@/layout/components/SizeDropdown'
+import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
 import { useAppStore } from '@/store/modules/app'
 import { useDesign } from '@/hooks/web/useDesign'
 
@@ -33,6 +34,9 @@ const layout = computed(() => appStore.getLayout)
 // 多语言图标
 const locale = computed(() => appStore.getLocale)
 
+// 消息图标
+const message = computed(() => appStore.getMessage)
+
 export default defineComponent({
   name: 'ToolHeader',
   setup() {
@@ -66,6 +70,9 @@ export default defineComponent({
               color="var(--top-header-text-color)"
             ></LocaleDropdown>
           ) : undefined}
+          {message.value ? (
+            <Message class="hover-trigger" color="var(--top-header-text-color)"></Message>
+          ) : undefined}
           <UserInfo class="hover-trigger"></UserInfo>
         </div>
       </div>

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

@@ -80,6 +80,7 @@ export default {
     screenfullIcon: 'Screenfull icon',
     sizeIcon: 'Size icon',
     localeIcon: 'Locale icon',
+    messageIcon: 'Message icon',
     tagsView: 'Tags view',
     logo: 'Logo',
     greyMode: 'Grey mode',
@@ -426,5 +427,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'
   }
 }

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

@@ -80,6 +80,7 @@ export default {
     screenfullIcon: '全屏图标',
     sizeIcon: '尺寸图标',
     localeIcon: '多语言图标',
+    messageIcon: '消息图标',
     tagsView: '标签页',
     logo: '标志',
     greyMode: '灰色模式',
@@ -419,5 +420,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/router/modules/remaining.ts

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

+ 8 - 0
yudao-ui-admin-vue3/src/store/modules/app.ts

@@ -18,6 +18,7 @@ interface AppState {
   screenfull: boolean
   size: boolean
   locale: boolean
+  message: boolean
   tagsView: boolean
   tagsViewIcon: boolean
   logo: boolean
@@ -53,6 +54,7 @@ export const useAppStore = defineStore('app', {
       screenfull: true, // 全屏图标
       size: true, // 尺寸图标
       locale: true, // 多语言图标
+      message: true, // 消息图标
       tagsView: true, // 标签页
       tagsViewIcon: true, // 是否显示标签图标
       logo: true, // logo
@@ -121,6 +123,9 @@ export const useAppStore = defineStore('app', {
     getLocale(): boolean {
       return this.locale
     },
+    getMessage(): boolean {
+      return this.message
+    },
     getTagsView(): boolean {
       return this.tagsView
     },
@@ -195,6 +200,9 @@ export const useAppStore = defineStore('app', {
     setLocale(locale: boolean) {
       this.locale = locale
     },
+    setMessage(message: boolean) {
+      this.message = message
+    },
     setTagsView(tagsView: boolean) {
       this.tagsView = tagsView
     },

+ 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>

+ 1 - 1
yudao-ui-admin-vue3/src/views/infra/codegen/components/CloumInfoForm.vue

@@ -22,7 +22,7 @@
           <el-option label="Integer" value="Integer" />
           <el-option label="Double" value="Double" />
           <el-option label="BigDecimal" value="BigDecimal" />
-          <el-option label="Date" value="Date" />
+          <el-option label="LocalDateTime" value="LocalDateTime" />
           <el-option label="Boolean" value="Boolean" />
         </el-select>
       </template>

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

@@ -90,7 +90,7 @@
       </template>
       <template v-if="menuForm.type === 2">
         <el-col :span="16">
-          <el-form-item label="路由地址" prop="component">
+          <el-form-item label="组件地址" prop="component">
             <el-input v-model="menuForm.component" placeholder="请输入组件地址" clearable />
           </el-form-item>
         </el-col>

+ 1 - 1
yudao-ui-admin/src/views/infra/codegen/editTable.vue

@@ -31,7 +31,7 @@
                 <el-option label="Integer" value="Integer" />
                 <el-option label="Double" value="Double" />
                 <el-option label="BigDecimal" value="BigDecimal" />
-                <el-option label="Date" value="Date" />
+                <el-option label="LocalDateTime" value="LocalDateTime" />
                 <el-option label="Boolean" value="Boolean" />
               </el-select>
             </template>