Просмотр исходного кода

refactor: :recycle: 清理废弃模块并统一代码规范

Zimo 11 часов назад
Родитель
Сommit
bfe92c4d2f
100 измененных файлов с 3333 добавлено и 6458 удалено
  1. 1 4
      .env
  2. 0 3
      .env.dev
  3. 0 3
      .env.local
  4. 9 1
      eslint.config.ts
  5. 0 2
      package.json
  6. 538 218
      pnpm-lock.yaml
  7. 40 40
      src/App.vue
  8. 3 1
      src/api/infra/file/index.ts
  9. 6 29
      src/api/login/index.ts
  10. 4 6
      src/api/login/oauth2/index.ts
  11. 0 2
      src/api/login/types.ts
  12. 1 1
      src/api/system/area/index.ts
  13. 2 2
      src/api/system/dept/index.ts
  14. 3 3
      src/api/system/dict/dict.data.ts
  15. 4 7
      src/api/system/dict/dict.type.ts
  16. 1 1
      src/api/system/loginLog/index.ts
  17. 3 3
      src/api/system/mail/account/index.ts
  18. 2 2
      src/api/system/mail/log/index.ts
  19. 3 3
      src/api/system/mail/template/index.ts
  20. 3 3
      src/api/system/menu/index.ts
  21. 3 3
      src/api/system/notice/index.ts
  22. 2 2
      src/api/system/notify/message/index.ts
  23. 3 3
      src/api/system/notify/template/index.ts
  24. 3 3
      src/api/system/oauth2/client.ts
  25. 1 1
      src/api/system/oauth2/token.ts
  26. 2 2
      src/api/system/permission/index.ts
  27. 3 3
      src/api/system/post/index.ts
  28. 3 6
      src/api/system/role/index.ts
  29. 2 2
      src/api/system/sms/smsChannel/index.ts
  30. 1 1
      src/api/system/sms/smsLog/index.ts
  31. 4 7
      src/api/system/sms/smsTemplate/index.ts
  32. 3 3
      src/api/system/social/client/index.ts
  33. 1 1
      src/api/system/social/user/index.ts
  34. 2 2
      src/api/system/tenant/index.ts
  35. 3 3
      src/api/system/tenantPackage/index.ts
  36. 5 14
      src/api/system/user/index.ts
  37. 4 16
      src/api/system/user/profile.ts
  38. 3 16
      src/api/system/user/socialUser.ts
  39. 6 7
      src/components/Backtop/src/Backtop.vue
  40. 14 7
      src/components/ConfigGlobal/src/ConfigGlobal.vue
  41. 10 11
      src/components/ContentWrap/src/ContentWrap.vue
  42. 159 162
      src/components/CountTo/src/CountTo.vue
  43. 138 147
      src/components/Cropper/src/CopperModal.vue
  44. 131 137
      src/components/Cropper/src/Cropper.vue
  45. 103 107
      src/components/Cropper/src/CropperAvatar.vue
  46. 102 108
      src/components/Dialog/src/Dialog.vue
  47. 74 86
      src/components/DictTag/src/DictTag.vue
  48. 79 87
      src/components/Echart/src/Echart.vue
  49. 168 185
      src/components/Editor/src/Editor.vue
  50. 36 42
      src/components/Error/src/Error.vue
  51. 269 289
      src/components/Form/src/Form.vue
  52. 2 2
      src/components/Form/src/componentMap.ts
  53. 2 4
      src/components/Form/src/components/useRenderCheckbox.tsx
  54. 2 4
      src/components/Form/src/components/useRenderRadio.tsx
  55. 23 27
      src/components/Form/src/components/useRenderSelect.tsx
  56. 7 22
      src/components/Form/src/helper.ts
  57. 0 4
      src/components/FormCreate/index.ts
  58. 0 138
      src/components/FormCreate/src/components/AreaSelect.vue
  59. 0 192
      src/components/FormCreate/src/components/DeptSelect.vue
  60. 0 96
      src/components/FormCreate/src/components/DictSelect.vue
  61. 0 86
      src/components/FormCreate/src/components/IframeComponent.vue
  62. 0 312
      src/components/FormCreate/src/components/useApiSelect.tsx
  63. 0 19
      src/components/FormCreate/src/config/index.ts
  64. 0 181
      src/components/FormCreate/src/config/selectRule.ts
  65. 0 75
      src/components/FormCreate/src/config/useAreaSelectRule.ts
  66. 0 65
      src/components/FormCreate/src/config/useDictSelectRule.ts
  67. 0 32
      src/components/FormCreate/src/config/useEditorRule.ts
  68. 0 74
      src/components/FormCreate/src/config/useIframeRule.ts
  69. 0 50
      src/components/FormCreate/src/config/useSelectRule.ts
  70. 0 80
      src/components/FormCreate/src/config/useUploadFileRule.ts
  71. 0 89
      src/components/FormCreate/src/config/useUploadImgRule.ts
  72. 0 84
      src/components/FormCreate/src/config/useUploadImgsRule.ts
  73. 0 31
      src/components/FormCreate/src/type/index.ts
  74. 0 193
      src/components/FormCreate/src/useFormCreateDesigner.ts
  75. 0 61
      src/components/FormCreate/src/utils/index.ts
  76. 49 55
      src/components/Highlight/src/Highlight.vue
  77. 28 32
      src/components/IFrame/src/IFrame.vue
  78. 25 26
      src/components/Icon/src/Icon.vue
  79. 151 176
      src/components/Icon/src/IconSelect.vue
  80. 4 4
      src/components/Icon/src/data.ts
  81. 2 2
      src/components/ImageViewer/index.ts
  82. 21 24
      src/components/ImageViewer/src/ImageViewer.vue
  83. 104 104
      src/components/InputPassword/src/InputPassword.vue
  84. 51 64
      src/components/Pagination/index.vue
  85. 212 219
      src/components/Qrcode/src/Qrcode.vue
  86. 59 72
      src/components/RouterSearch/index.vue
  87. 7 7
      src/components/Tooltip/src/Tooltip.vue
  88. 172 169
      src/components/UploadFile/src/UploadFile.vue
  89. 186 189
      src/components/UploadFile/src/UploadImg.vue
  90. 246 252
      src/components/UploadFile/src/UploadImgs.vue
  91. 19 24
      src/components/UploadFile/src/useUpload.ts
  92. 0 3
      src/components/Verifition/index.ts
  93. 0 379
      src/components/Verifition/src/Verify.vue
  94. 0 198
      src/components/Verifition/src/Verify/VerifyPictureWord.vue
  95. 0 252
      src/components/Verifition/src/Verify/VerifyPoints.vue
  96. 0 372
      src/components/Verifition/src/Verify/VerifySlide.vue
  97. 0 5
      src/components/Verifition/src/Verify/index.ts
  98. 0 14
      src/components/Verifition/src/utils/ase.ts
  99. 0 97
      src/components/Verifition/src/utils/util.ts
  100. 1 1
      src/config/axios/config.ts

+ 1 - 4
.env

@@ -10,9 +10,6 @@ VITE_OPEN=true
 # 租户开关
 VITE_APP_TENANT_ENABLE=true
 
-# 验证码的开关
-VITE_APP_CAPTCHA_ENABLE=true
-
 # 文档地址的开关
 VITE_APP_DOCALERT_ENABLE=true
 
@@ -34,4 +31,4 @@ VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883
 # VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ==
 
 # 百度地图
-VITE_BAIDU_MAP_KEY = 'Y2aJXiswwPxy6mwFs1z9c7U5gwX9WfUN'
+VITE_BAIDU_MAP_KEY = 'Y2aJXiswwPxy6mwFs1z9c7U5gwX9WfUN'

+ 0 - 3
.env.dev

@@ -33,8 +33,5 @@ VITE_OUT_DIR=dist
 # 商城H5会员端域名
 VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
 
-# 验证码的开关
-VITE_APP_CAPTCHA_ENABLE=true
-
 # GoView域名
 VITE_GOVIEW_URL='http://127.0.0.1:3000'

+ 0 - 3
.env.local

@@ -30,8 +30,5 @@ VITE_BASE_PATH=/
 # 商城H5会员端域名
 VITE_MALL_H5_DOMAIN='http://localhost:3000'
 
-# 验证码的开关
-VITE_APP_CAPTCHA_ENABLE=false
-
 # GoView域名
 VITE_GOVIEW_URL='http://127.0.0.1:3000'

+ 9 - 1
eslint.config.ts

@@ -24,15 +24,23 @@ export default antfu(
   {
     rules: {
       'curly': ['error', 'multi'],
+      'no-console': 'warn',
       'no-dupe-else-if': 'error',
       'no-empty-static-block': 'error',
       'no-nonoctal-decimal-escape': 'error',
       'no-shadow': 'off',
       'no-unsafe-optional-chaining': 'error',
+      'no-case-declarations': 'off',
+      'ts/ban-ts-comment': 'off',
+      'regexp/no-unused-capturing-group': 'off',
+      'regexp/no-obscure-range': 'off',
+      'regexp/no-legacy-features': 'off',
+      'regexp/no-super-linear-backtracking': 'off',
+      'regexp/no-useless-quantifier': 'off',
       'ts/no-shadow': [
         'warn',
         {
-          allow: [],
+          allow: ['props', 'config', 'routers'],
           builtinGlobals: false,
           hoist: 'functions',
           ignoreOnInitialization: false,

+ 0 - 2
package.json

@@ -47,8 +47,6 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "2.3.2",
-    "@form-create/designer": "^3.4.0",
-    "@form-create/element-ui": "^3.2.38",
     "@iconify/vue": "^5.0.1",
     "@microsoft/fetch-event-source": "^2.0.1",
     "@vueuse/core": "^14.3.0",

Разница между файлами не показана из-за своего большого размера
+ 538 - 218
pnpm-lock.yaml


+ 40 - 40
src/App.vue

@@ -1,28 +1,28 @@
 <script lang="ts" setup>
-import { isDark } from '@/utils/is'
-import { useAppStore } from '@/store/modules/app'
-import { useDesign } from '@/hooks/web/useDesign'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
-import routerSearch from '@/components/RouterSearch/index.vue'
-
-defineOptions({ name: 'APP' })
-
-const { getPrefixCls } = useDesign()
-const prefixCls = getPrefixCls('app')
-const appStore = useAppStore()
-const currentSize = computed(() => appStore.getCurrentSize)
-const greyMode = computed(() => appStore.getGreyMode)
-const { wsCache } = useCache()
-
-// 根据浏览器当前主题设置系统主题色
-const setDefaultTheme = () => {
-  let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)
-  if (isDarkTheme === null) {
-    isDarkTheme = isDark()
+  import { isDark } from '@/utils/is'
+  import { useAppStore } from '@/store/modules/app'
+  import { useDesign } from '@/hooks/web/useDesign'
+  import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+  import routerSearch from '@/components/RouterSearch/index.vue'
+
+  defineOptions({ name: 'APP' })
+
+  const { getPrefixCls } = useDesign()
+  const prefixCls = getPrefixCls('app')
+  const appStore = useAppStore()
+  const currentSize = computed(() => appStore.getCurrentSize)
+  const greyMode = computed(() => appStore.getGreyMode)
+  const { wsCache } = useCache()
+
+  // 根据浏览器当前主题设置系统主题色
+  const setDefaultTheme = () => {
+    let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)
+    if (isDarkTheme === null) {
+      isDarkTheme = isDark()
+    }
+    appStore.setIsDark(isDarkTheme)
   }
-  appStore.setIsDark(isDarkTheme)
-}
-setDefaultTheme()
+  setDefaultTheme()
 </script>
 <template>
   <ConfigGlobal :size="currentSize">
@@ -31,27 +31,27 @@ setDefaultTheme()
   </ConfigGlobal>
 </template>
 <style lang="scss">
-$prefix-cls: #{$namespace}-app;
+  $prefix-cls: #{$namespace}-app;
 
-.size {
-  width: 100%;
-  height: 100%;
-}
+  .size {
+    width: 100%;
+    height: 100%;
+  }
 
-html,
-body {
-  @extend .size;
+  html,
+  body {
+    @extend .size;
 
-  padding: 0 !important;
-  margin: 0;
-  overflow: hidden;
+    padding: 0 !important;
+    margin: 0;
+    overflow: hidden;
 
-  #app {
-    @extend .size;
+    #app {
+      @extend .size;
+    }
   }
-}
 
-.#{$prefix-cls}-grey-mode {
-  filter: grayscale(100%);
-}
+  .#{$prefix-cls}-grey-mode {
+    filter: grayscale(100%);
+  }
 </style>

+ 3 - 1
src/api/infra/file/index.ts

@@ -1,3 +1,5 @@
+import type { AxiosProgressEvent } from 'axios'
+
 import request from '@/config/axios'
 
 // 文件预签名地址 Response VO
@@ -41,6 +43,6 @@ export const createFile = (data: any) => {
 }
 
 // 上传文件
-export const updateFile = (data: any, onUploadProgress?: Function) => {
+export const updateFile = (data: any, onUploadProgress?: (event: AxiosProgressEvent) => void) => {
   return request.upload({ url: '/infra/file/upload', data, onUploadProgress })
 }

+ 6 - 29
src/api/login/index.ts

@@ -14,13 +14,7 @@ export interface SmsLoginVO {
 
 // 登录
 export const login = (data: UserLoginVO) => {
-  return request.post({
-    url: '/system/auth/login',
-    data,
-    headers: {
-      isEncrypt: false
-    }
-  })
+  return request.post({ url: '/system/auth/login', data, headers: { isEncrypt: false } })
 }
 
 // 注册
@@ -30,12 +24,12 @@ export const register = (data: RegisterVO) => {
 
 // 使用租户名,获得租户编号
 export const getTenantIdByName = (name: string) => {
-  return request.get({ url: `/system/tenant/get-id-by-name?name=${  name}` })
+  return request.get({ url: `/system/tenant/get-id-by-name?name=${name}` })
 }
 
 // 使用租户域名,获得租户信息
 export const getTenantByWebsite = (website: string) => {
-  return request.get({ url: `/system/tenant/get-by-website?website=${  website}` })
+  return request.get({ url: `/system/tenant/get-by-website?website=${website}` })
 }
 
 // 登出
@@ -48,7 +42,7 @@ export const getInfo = () => {
   return request.get({ url: '/system/auth/get-permission-info' })
 }
 
-//获取登录验证码
+// 发送短信验证码
 export const sendSmsCode = (data: SmsCodeVO) => {
   return request.post({ url: '/system/auth/send-sms-code', data })
 }
@@ -60,32 +54,15 @@ export const smsLogin = (data: SmsLoginVO) => {
 
 // 社交快捷登录,使用 code 授权码
 export function socialLogin(type: string, code: string, state: string) {
-  return request.post({
-    url: '/system/auth/social-login',
-    data: {
-      type,
-      code,
-      state
-    }
-  })
+  return request.post({ url: '/system/auth/social-login', data: { type, code, state } })
 }
 
 // 社交授权的跳转
 export const socialAuthRedirect = (type: number, redirectUri: string) => {
   return request.get({
-    url: `/system/auth/social-auth-redirect?type=${  type  }&redirectUri=${  redirectUri}`
+    url: `/system/auth/social-auth-redirect?type=${type}&redirectUri=${redirectUri}`,
   })
 }
-// 获取验证图片以及 token
-export const getCode = (data: any) => {
-  return request.postOriginal({ url: 'system/captcha/get', data })
-}
-
-// 滑动或者点选验证
-export const reqCheck = (data: any) => {
-  return request.postOriginal({ url: 'system/captcha/check', data })
-}
-
 // 通过短信重置密码
 export const smsResetPassword = (data: any) => {
   return request.post({ url: '/system/auth/reset-password', data })

+ 4 - 6
src/api/login/oauth2/index.ts

@@ -2,7 +2,7 @@ import request from '@/config/axios'
 
 // 获得授权信息
 export const getAuthorize = (clientId: string) => {
-  return request.get({ url: `/system/oauth2/authorize?clientId=${  clientId}` })
+  return request.get({ url: `/system/oauth2/authorize?clientId=${clientId}` })
 }
 
 // 发起授权
@@ -26,16 +26,14 @@ export const authorize = (
   // 发起请求
   return request.post({
     url: '/system/oauth2/authorize',
-    headers: {
-      'Content-Type': 'application/x-www-form-urlencoded'
-    },
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
     params: {
       response_type: responseType,
       client_id: clientId,
       redirect_uri: redirectUri,
       state,
       auto_approve: autoApprove,
-      scope: JSON.stringify(scopes)
-    }
+      scope: JSON.stringify(scopes),
+    },
   })
 }

+ 0 - 2
src/api/login/types.ts

@@ -1,7 +1,6 @@
 export interface UserLoginVO {
   username: string
   password: string
-  captchaVerification: string
   socialType?: string
   socialCode?: string
   socialState?: string
@@ -34,5 +33,4 @@ export interface RegisterVO {
   tenantName: string
   username: string
   password: string
-  captchaVerification: string
 }

+ 1 - 1
src/api/system/area/index.ts

@@ -7,5 +7,5 @@ export const getAreaTree = async () => {
 
 // 获得 IP 对应的地区名
 export const getAreaByIp = async (ip: string) => {
-  return await request.get({ url: `/system/area/get-by-ip?ip=${  ip}` })
+  return await request.get({ url: `/system/area/get-by-ip?ip=${ip}` })
 }

+ 2 - 2
src/api/system/dept/index.ts

@@ -30,7 +30,7 @@ export const getDeptPage = async (params: PageParam) => {
 
 // 查询部门详情
 export const getDept = (id: number) => {
-  return request.get({ url: `/system/dept/get?id=${  id}` })
+  return request.get({ url: `/system/dept/get?id=${id}` })
 }
 
 // 新增部门
@@ -45,7 +45,7 @@ export const updateDept = (data: DeptVO) => {
 
 // 删除部门
 export const deleteDept = async (id: number) => {
-  return await request.delete({ url: `/system/dept/delete?id=${  id}` })
+  return await request.delete({ url: `/system/dept/delete?id=${id}` })
 }
 
 // 批量删除部门

+ 3 - 3
src/api/system/dict/dict.data.ts

@@ -25,12 +25,12 @@ export const getDictDataPage = (params: PageParam) => {
 
 // 查询字典数据详情
 export const getDictData = (id: number) => {
-  return request.get({ url: `/system/dict-data/get?id=${  id}` })
+  return request.get({ url: `/system/dict-data/get?id=${id}` })
 }
 
 // 根据字典类型查询字典数据
 export const getDictDataByType = (dictType: string) => {
-  return request.get({ url: `/system/dict-data/type?type=${  dictType}` })
+  return request.get({ url: `/system/dict-data/type?type=${dictType}` })
 }
 
 // 新增字典数据
@@ -45,7 +45,7 @@ export const updateDictData = (data: DictDataVO) => {
 
 // 删除字典数据
 export const deleteDictData = (id: number) => {
-  return request.delete({ url: `/system/dict-data/delete?id=${  id}` })
+  return request.delete({ url: `/system/dict-data/delete?id=${id}` })
 }
 
 // 批量删除字典数据

+ 4 - 7
src/api/system/dict/dict.type.ts

@@ -21,7 +21,7 @@ export const getDictTypePage = (params: PageParam) => {
 
 // 查询字典详情
 export const getDictType = (id: number) => {
-  return request.get({ url: `/system/dict-type/get?id=${  id}` })
+  return request.get({ url: `/system/dict-type/get?id=${id}` })
 }
 
 // 新增字典
@@ -36,7 +36,7 @@ export const updateDictType = (data: DictTypeVO) => {
 
 // 删除字典
 export const deleteDictType = (id: number) => {
-  return request.delete({ url: `/system/dict-type/delete?id=${  id}` })
+  return request.delete({ url: `/system/dict-type/delete?id=${id}` })
 }
 
 // 批量删除字典类型
@@ -45,9 +45,6 @@ export const deleteDictTypeList = (ids: number[]) => {
 }
 
 // 导出字典
-export const exportDictType = (params) => {
-  return request.download({
-    url: '/system/dict-type/export-excel',
-    params
-  })
+export const exportDictType = params => {
+  return request.download({ url: '/system/dict-type/export-excel', params })
 }

+ 1 - 1
src/api/system/loginLog/index.ts

@@ -20,6 +20,6 @@ export const getLoginLogPage = (params: PageParam) => {
 }
 
 // 导出登录日志
-export const exportLoginLog = (params) => {
+export const exportLoginLog = params => {
   return request.download({ url: '/system/login-log/export-excel', params })
 }

+ 3 - 3
src/api/system/mail/account/index.ts

@@ -18,7 +18,7 @@ export const getMailAccountPage = async (params: PageParam) => {
 
 // 查询邮箱账号详情
 export const getMailAccount = async (id: number) => {
-  return await request.get({ url: `/system/mail-account/get?id=${  id}` })
+  return await request.get({ url: `/system/mail-account/get?id=${id}` })
 }
 
 // 新增邮箱账号
@@ -33,14 +33,14 @@ export const updateMailAccount = async (data: MailAccountVO) => {
 
 // 删除邮箱账号
 export const deleteMailAccount = async (id: number) => {
-  return await request.delete({ url: `/system/mail-account/delete?id=${  id}` })
+  return await request.delete({ url: `/system/mail-account/delete?id=${id}` })
 }
 
 // 批量删除邮箱账号
 export const deleteMailAccountList = async (ids: number[]) => {
   return await request.delete({
     url: '/system/mail-account/delete-list',
-    params: { ids: ids.join(',') }
+    params: { ids: ids.join(',') },
   })
 }
 

+ 2 - 2
src/api/system/mail/log/index.ts

@@ -28,10 +28,10 @@ export const getMailLogPage = async (params: PageParam) => {
 
 // 查询邮件日志详情
 export const getMailLog = async (id: number) => {
-  return await request.get({ url: `/system/mail-log/get?id=${  id}` })
+  return await request.get({ url: `/system/mail-log/get?id=${id}` })
 }
 
 // 导出邮件日志
-export const exportMailLog = (params) => {
+export const exportMailLog = params => {
   return request.download({ url: '/system/mail-log/export-excel', params })
 }

+ 3 - 3
src/api/system/mail/template/index.ts

@@ -36,7 +36,7 @@ export const getMailTemplatePage = async (params: PageParam) => {
 
 // 查询邮件模版详情
 export const getMailTemplate = async (id: number) => {
-  return await request.get({ url: `/system/mail-template/get?id=${  id}` })
+  return await request.get({ url: `/system/mail-template/get?id=${id}` })
 }
 
 // 新增邮件模版
@@ -51,14 +51,14 @@ export const updateMailTemplate = async (data: MailTemplateVO) => {
 
 // 删除邮件模版
 export const deleteMailTemplate = async (id: number) => {
-  return await request.delete({ url: `/system/mail-template/delete?id=${  id}` })
+  return await request.delete({ url: `/system/mail-template/delete?id=${id}` })
 }
 
 // 批量删除邮件模版
 export const deleteMailTemplateList = async (ids: number[]) => {
   return await request.delete({
     url: '/system/mail-template/delete-list',
-    params: { ids: ids.join(',') }
+    params: { ids: ids.join(',') },
   })
 }
 

+ 3 - 3
src/api/system/menu/index.ts

@@ -24,13 +24,13 @@ export const getSimpleMenusList = () => {
 }
 
 // 查询菜单列表
-export const getMenuList = (params) => {
+export const getMenuList = params => {
   return request.get({ url: '/system/menu/list', params })
 }
 
 // 获取菜单详情
 export const getMenu = (id: number) => {
-  return request.get({ url: `/system/menu/get?id=${  id}` })
+  return request.get({ url: `/system/menu/get?id=${id}` })
 }
 
 // 新增菜单
@@ -45,5 +45,5 @@ export const updateMenu = (data: MenuVO) => {
 
 // 删除菜单
 export const deleteMenu = (id: number) => {
-  return request.delete({ url: `/system/menu/delete?id=${  id}` })
+  return request.delete({ url: `/system/menu/delete?id=${id}` })
 }

+ 3 - 3
src/api/system/notice/index.ts

@@ -18,7 +18,7 @@ export const getNoticePage = (params: PageParam) => {
 
 // 查询公告详情
 export const getNotice = (id: number) => {
-  return request.get({ url: `/system/notice/get?id=${  id}` })
+  return request.get({ url: `/system/notice/get?id=${id}` })
 }
 
 // 新增公告
@@ -33,7 +33,7 @@ export const updateNotice = (data: NoticeVO) => {
 
 // 删除公告
 export const deleteNotice = (id: number) => {
-  return request.delete({ url: `/system/notice/delete?id=${  id}` })
+  return request.delete({ url: `/system/notice/delete?id=${id}` })
 }
 
 // 批量删除公告
@@ -43,5 +43,5 @@ export const deleteNoticeList = (ids: number[]) => {
 
 // 推送公告
 export const pushNotice = (id: number) => {
-  return request.post({ url: `/system/notice/push?id=${  id}` })
+  return request.post({ url: `/system/notice/push?id=${id}` })
 }

+ 2 - 2
src/api/system/notify/message/index.ts

@@ -28,9 +28,9 @@ export const getMyNotifyMessagePage = async (params: PageParam) => {
 }
 
 // 批量标记已读
-export const updateNotifyMessageRead = async (ids) => {
+export const updateNotifyMessageRead = async ids => {
   return await request.put({
-    url: `/system/notify-message/update-read?${  qs.stringify({ ids }, { indices: false })}`
+    url: `/system/notify-message/update-read?${qs.stringify({ ids }, { indices: false })}`,
   })
 }
 

+ 3 - 3
src/api/system/notify/template/index.ts

@@ -36,7 +36,7 @@ export const getNotifyTemplatePage = async (params: PageParam) => {
 
 // 查询站内信模板详情
 export const getNotifyTemplate = async (id: number) => {
-  return await request.get({ url: `/system/notify-template/get?id=${  id}` })
+  return await request.get({ url: `/system/notify-template/get?id=${id}` })
 }
 
 // 新增站内信模板
@@ -51,14 +51,14 @@ export const updateNotifyTemplate = async (data: NotifyTemplateVO) => {
 
 // 删除站内信模板
 export const deleteNotifyTemplate = async (id: number) => {
-  return await request.delete({ url: `/system/notify-template/delete?id=${  id}` })
+  return await request.delete({ url: `/system/notify-template/delete?id=${id}` })
 }
 
 // 批量删除站内信模板
 export const deleteNotifyTemplateList = async (ids: number[]) => {
   return await request.delete({
     url: '/system/notify-template/delete-list',
-    params: { ids: ids.join(',') }
+    params: { ids: ids.join(',') },
   })
 }
 

+ 3 - 3
src/api/system/oauth2/client.ts

@@ -28,7 +28,7 @@ export const getOAuth2ClientPage = (params: PageParam) => {
 
 // 查询 OAuth2 客户端的详情
 export const getOAuth2Client = (id: number) => {
-  return request.get({ url: `/system/oauth2-client/get?id=${  id}` })
+  return request.get({ url: `/system/oauth2-client/get?id=${id}` })
 }
 
 // 新增 OAuth2 客户端
@@ -43,13 +43,13 @@ export const updateOAuth2Client = (data: OAuth2ClientVO) => {
 
 // 删除 OAuth2
 export const deleteOAuth2Client = (id: number) => {
-  return request.delete({ url: `/system/oauth2-client/delete?id=${  id}` })
+  return request.delete({ url: `/system/oauth2-client/delete?id=${id}` })
 }
 
 // 批量删除 OAuth2 客户端
 export const deleteOAuth2ClientList = (ids: number[]) => {
   return request.delete({
     url: '/system/oauth2-client/delete-list',
-    params: { ids: ids.join(',') }
+    params: { ids: ids.join(',') },
   })
 }

+ 1 - 1
src/api/system/oauth2/token.ts

@@ -18,5 +18,5 @@ export const getAccessTokenPage = (params: PageParam) => {
 
 // 删除 token
 export const deleteAccessToken = (accessToken: string) => {
-  return request.delete({ url: `/system/oauth2-token/delete?accessToken=${  accessToken}` })
+  return request.delete({ url: `/system/oauth2-token/delete?accessToken=${accessToken}` })
 }

+ 2 - 2
src/api/system/permission/index.ts

@@ -18,7 +18,7 @@ export interface PermissionAssignRoleDataScopeReqVO {
 
 // 查询角色拥有的菜单权限
 export const getRoleMenuList = async (roleId: number) => {
-  return await request.get({ url: `/system/permission/list-role-menus?roleId=${  roleId}` })
+  return await request.get({ url: `/system/permission/list-role-menus?roleId=${roleId}` })
 }
 
 // 赋予角色菜单权限
@@ -33,7 +33,7 @@ export const assignRoleDataScope = async (data: PermissionAssignRoleDataScopeReq
 
 // 查询用户拥有的角色数组
 export const getUserRoleList = async (userId: number) => {
-  return await request.get({ url: `/system/permission/list-user-roles?userId=${  userId}` })
+  return await request.get({ url: `/system/permission/list-user-roles?userId=${userId}` })
 }
 
 // 赋予用户角色

+ 3 - 3
src/api/system/post/index.ts

@@ -22,7 +22,7 @@ export const getSimplePostList = async (): Promise<PostVO[]> => {
 
 // 查询岗位详情
 export const getPost = async (id: number) => {
-  return await request.get({ url: `/system/post/get?id=${  id}` })
+  return await request.get({ url: `/system/post/get?id=${id}` })
 }
 
 // 新增岗位
@@ -37,7 +37,7 @@ export const updatePost = async (data: PostVO) => {
 
 // 删除岗位
 export const deletePost = async (id: number) => {
-  return await request.delete({ url: `/system/post/delete?id=${  id}` })
+  return await request.delete({ url: `/system/post/delete?id=${id}` })
 }
 
 // 批量删除岗位
@@ -46,6 +46,6 @@ export const deletePostList = async (ids: number[]) => {
 }
 
 // 导出岗位
-export const exportPost = async (params) => {
+export const exportPost = async params => {
   return await request.download({ url: '/system/post/export-excel', params })
 }

+ 3 - 6
src/api/system/role/index.ts

@@ -24,7 +24,7 @@ export const getSimpleRoleList = async (): Promise<RoleVO[]> => {
 
 // 查询角色详情
 export const getRole = async (id: number) => {
-  return await request.get({ url: `/system/role/get?id=${  id}` })
+  return await request.get({ url: `/system/role/get?id=${id}` })
 }
 
 // 新增角色
@@ -39,7 +39,7 @@ export const updateRole = async (data: RoleVO) => {
 
 // 删除角色
 export const deleteRole = async (id: number) => {
-  return await request.delete({ url: `/system/role/delete?id=${  id}` })
+  return await request.delete({ url: `/system/role/delete?id=${id}` })
 }
 
 // 批量删除角色
@@ -49,8 +49,5 @@ export const deleteRoleList = async (ids: number[]) => {
 
 // 导出角色
 export const exportRole = (params: any) => {
-  return request.download({
-    url: '/system/role/export-excel',
-    params
-  })
+  return request.download({ url: '/system/role/export-excel', params })
 }

+ 2 - 2
src/api/system/sms/smsChannel/index.ts

@@ -24,7 +24,7 @@ export function getSimpleSmsChannelList() {
 
 // 查询短信渠道详情
 export const getSmsChannel = (id: number) => {
-  return request.get({ url: `/system/sms-channel/get?id=${  id}` })
+  return request.get({ url: `/system/sms-channel/get?id=${id}` })
 }
 
 // 新增短信渠道
@@ -39,7 +39,7 @@ export const updateSmsChannel = (data: SmsChannelVO) => {
 
 // 删除短信渠道
 export const deleteSmsChannel = (id: number) => {
-  return request.delete({ url: `/system/sms-channel/delete?id=${  id}` })
+  return request.delete({ url: `/system/sms-channel/delete?id=${id}` })
 }
 
 // 批量删除短信渠道

+ 1 - 1
src/api/system/sms/smsLog/index.ts

@@ -32,6 +32,6 @@ export const getSmsLogPage = (params: PageParam) => {
 }
 
 // 导出短信日志
-export const exportSmsLog = (params) => {
+export const exportSmsLog = params => {
   return request.download({ url: '/system/sms-log/export-excel', params })
 }

+ 4 - 7
src/api/system/sms/smsTemplate/index.ts

@@ -38,7 +38,7 @@ export const getSmsTemplatePage = (params: PageParam) => {
 
 // 查询短信模板详情
 export const getSmsTemplate = (id: number) => {
-  return request.get({ url: `/system/sms-template/get?id=${  id}` })
+  return request.get({ url: `/system/sms-template/get?id=${id}` })
 }
 
 // 新增短信模板
@@ -53,7 +53,7 @@ export const updateSmsTemplate = (data: SmsTemplateVO) => {
 
 // 删除短信模板
 export const deleteSmsTemplate = (id: number) => {
-  return request.delete({ url: `/system/sms-template/delete?id=${  id}` })
+  return request.delete({ url: `/system/sms-template/delete?id=${id}` })
 }
 
 // 批量删除短信模板
@@ -62,11 +62,8 @@ export const deleteSmsTemplateList = (ids: number[]) => {
 }
 
 // 导出短信模板
-export const exportSmsTemplate = (params) => {
-  return request.download({
-    url: '/system/sms-template/export-excel',
-    params
-  })
+export const exportSmsTemplate = params => {
+  return request.download({ url: '/system/sms-template/export-excel', params })
 }
 
 // 发送短信

+ 3 - 3
src/api/system/social/client/index.ts

@@ -13,13 +13,13 @@ export interface SocialClientVO {
 }
 
 // 查询社交客户端列表
-export const getSocialClientPage = async (params) => {
+export const getSocialClientPage = async params => {
   return await request.get({ url: `/system/social-client/page`, params })
 }
 
 // 查询社交客户端详情
 export const getSocialClient = async (id: number) => {
-  return await request.get({ url: `/system/social-client/get?id=${  id}` })
+  return await request.get({ url: `/system/social-client/get?id=${id}` })
 }
 
 // 新增社交客户端
@@ -34,5 +34,5 @@ export const updateSocialClient = async (data: SocialClientVO) => {
 
 // 删除社交客户端
 export const deleteSocialClient = async (id: number) => {
-  return await request.delete({ url: `/system/social-client/delete?id=${  id}` })
+  return await request.delete({ url: `/system/social-client/delete?id=${id}` })
 }

+ 1 - 1
src/api/system/social/user/index.ts

@@ -20,7 +20,7 @@ export const getSocialUserPage = async (params: any) => {
 
 // 查询社交用户详情
 export const getSocialUser = async (id: number) => {
-  return await request.get({ url: `/system/social-user/get?id=${  id}` })
+  return await request.get({ url: `/system/social-user/get?id=${id}` })
 }
 
 // 获得绑定社交用户列表

+ 2 - 2
src/api/system/tenant/index.ts

@@ -39,7 +39,7 @@ export const getTenantPage = (params: TenantPageReqVO) => {
 
 // 查询租户详情
 export const getTenant = (id: number) => {
-  return request.get({ url: `/system/tenant/get?id=${  id}` })
+  return request.get({ url: `/system/tenant/get?id=${id}` })
 }
 
 // 获取租户精简信息列表
@@ -59,7 +59,7 @@ export const updateTenant = (data: TenantVO) => {
 
 // 删除租户
 export const deleteTenant = (id: number) => {
-  return request.delete({ url: `/system/tenant/delete?id=${  id}` })
+  return request.delete({ url: `/system/tenant/delete?id=${id}` })
 }
 
 // 批量删除租户

+ 3 - 3
src/api/system/tenantPackage/index.ts

@@ -19,7 +19,7 @@ export const getTenantPackagePage = (params: PageParam) => {
 
 // 获得租户
 export const getTenantPackage = (id: number) => {
-  return request.get({ url: `/system/tenant-package/get?id=${  id}` })
+  return request.get({ url: `/system/tenant-package/get?id=${id}` })
 }
 
 // 新增租户套餐
@@ -34,14 +34,14 @@ export const updateTenantPackage = (data: TenantPackageVO) => {
 
 // 删除租户套餐
 export const deleteTenantPackage = (id: number) => {
-  return request.delete({ url: `/system/tenant-package/delete?id=${  id}` })
+  return request.delete({ url: `/system/tenant-package/delete?id=${id}` })
 }
 
 // 批量删除租户套餐
 export const deleteTenantPackageList = (ids: number[]) => {
   return request.delete({
     url: '/system/tenant-package/delete-list',
-    params: { ids: ids.join(',') }
+    params: { ids: ids.join(',') },
   })
 }
 

+ 5 - 14
src/api/system/user/index.ts

@@ -30,10 +30,7 @@ export const getSimpleUser = (id: number | string) => {
 
 // 按昵称模糊搜索用户(加好友)
 export const getSimpleUserListByNickname = (nickname: string) => {
-  return request.get<UserVO[]>({
-    url: '/system/user/list-by-nickname',
-    params: { nickname }
-  })
+  return request.get<UserVO[]>({ url: '/system/user/list-by-nickname', params: { nickname } })
 }
 
 // 查询用户管理列表
@@ -48,7 +45,7 @@ export const getUserList = (ids: number[]) => {
 
 // 查询用户详情
 export const getUser = (id: number) => {
-  return request.get({ url: `/system/user/get?id=${  id}` })
+  return request.get({ url: `/system/user/get?id=${id}` })
 }
 
 // 新增用户
@@ -63,7 +60,7 @@ export const updateUser = (data: UserVO) => {
 
 // 删除用户
 export const deleteUser = (id: number) => {
-  return request.delete({ url: `/system/user/delete?id=${  id}` })
+  return request.delete({ url: `/system/user/delete?id=${id}` })
 }
 
 // 批量删除用户
@@ -83,18 +80,12 @@ export const importUserTemplate = () => {
 
 // 用户密码重置
 export const resetUserPassword = (id: number, password: string) => {
-  const data = {
-    id,
-    password
-  }
+  const data = { id, password }
   return request.put({ url: '/system/user/update-password', data })
 }
 
 // 用户状态修改
 export const updateUserStatus = (id: number, status: number) => {
-  const data = {
-    id,
-    status
-  }
+  const data = { id, status }
   return request.put({ url: '/system/user/update-status', data })
 }

+ 4 - 16
src/api/system/user/profile.ts

@@ -4,18 +4,9 @@ export interface ProfileVO {
   id: number
   username: string
   nickname: string
-  dept: {
-    id: number
-    name: string
-  }
-  roles: {
-    id: number
-    name: string
-  }[]
-  posts: {
-    id: number
-    name: string
-  }[]
+  dept: { id: number; name: string }
+  roles: { id: number; name: string }[]
+  posts: { id: number; name: string }[]
   email: string
   mobile: string
   sex: number
@@ -49,9 +40,6 @@ export const updateUserProfile = (data: UserProfileUpdateReqVO) => {
 export const updateUserPassword = (oldPassword: string, newPassword: string) => {
   return request.put({
     url: '/system/user/profile/update-password',
-    data: {
-      oldPassword,
-      newPassword
-    }
+    data: { oldPassword, newPassword },
   })
 }

+ 3 - 16
src/api/system/user/socialUser.ts

@@ -2,30 +2,17 @@ import request from '@/config/axios'
 
 // 社交绑定,使用 code 授权码
 export const socialBind = (type, code, state) => {
-  return request.post({
-    url: '/system/social-user/bind',
-    data: {
-      type,
-      code,
-      state
-    }
-  })
+  return request.post({ url: '/system/social-user/bind', data: { type, code, state } })
 }
 
 // 取消社交绑定
 export const socialUnbind = (type, openid) => {
-  return request.delete({
-    url: '/system/social-user/unbind',
-    data: {
-      type,
-      openid
-    }
-  })
+  return request.delete({ url: '/system/social-user/unbind', data: { type, openid } })
 }
 
 // 社交授权的跳转
 export const socialAuthRedirect = (type, redirectUri) => {
   return request.get({
-    url: `/system/auth/social-auth-redirect?type=${  type  }&redirectUri=${  redirectUri}`
+    url: `/system/auth/social-auth-redirect?type=${type}&redirectUri=${redirectUri}`,
   })
 }

+ 6 - 7
src/components/Backtop/src/Backtop.vue

@@ -1,18 +1,17 @@
 <script lang="ts" setup>
-import { ElBacktop } from 'element-plus'
+  import { ElBacktop } from 'element-plus'
 
-import { useDesign } from '@/hooks/web/useDesign'
+  import { useDesign } from '@/hooks/web/useDesign'
 
-defineOptions({ name: 'BackTop' })
+  defineOptions({ name: 'BackTop' })
 
-const { getPrefixCls, variables } = useDesign()
+  const { getPrefixCls, variables } = useDesign()
 
-const prefixCls = getPrefixCls('backtop')
+  const prefixCls = getPrefixCls('backtop')
 </script>
 
 <template>
   <ElBacktop
     :class="`${prefixCls}-backtop`"
-    :target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
-  />
+    :target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`" />
 </template>

+ 14 - 7
src/components/ConfigGlobal/src/ConfigGlobal.vue

@@ -32,16 +32,23 @@
   // 监听窗口变化
   watch(
     () => width.value,
-    (width: number) => {
-      if (width < 768) {
-        !appStore.getMobile ? appStore.setMobile(true) : undefined
+    (newValue: number) => {
+      if (newValue < 768) {
+        if (!appStore.getMobile) {
+          appStore.setMobile(true)
+        }
+
         setCssVar('--left-menu-min-width', '0')
         appStore.setCollapse(true)
-        normalizeLayout(appStore.getLayout) !== 'sidebar-nav'
-          ? appStore.setLayout('sidebar-nav')
-          : undefined
+
+        if (normalizeLayout(appStore.getLayout) !== 'sidebar-nav') {
+          appStore.setLayout('sidebar-nav')
+        }
       } else {
-        appStore.getMobile ? appStore.setMobile(false) : undefined
+        if (appStore.getMobile) {
+          appStore.setMobile(false)
+        }
+
         setCssVar('--left-menu-min-width', '64px')
       }
     },

+ 10 - 11
src/components/ContentWrap/src/ContentWrap.vue

@@ -1,19 +1,18 @@
 <script lang="ts" setup>
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
 
-defineOptions({ name: 'ContentWrap' })
+  defineOptions({ name: 'ContentWrap' })
 
-defineProps({
-  title: propTypes.string.def(''),
-  message: propTypes.string.def(''),
-  bodyStyle: propTypes.object.def({ padding: '10px', overflow: 'hidden' })
-})
+  defineProps({
+    title: propTypes.string.def(''),
+    message: propTypes.string.def(''),
+    bodyStyle: propTypes.object.def({ padding: '10px', overflow: 'hidden' }),
+  })
 
-const { getPrefixCls } = useDesign()
-
-const prefixCls = getPrefixCls('content-wrap')
+  const { getPrefixCls } = useDesign()
 
+  const prefixCls = getPrefixCls('content-wrap')
 </script>
 
 <template>

+ 159 - 162
src/components/CountTo/src/CountTo.vue

@@ -1,179 +1,176 @@
 <script lang="ts" setup>
-import type { PropType } from 'vue'
-
-import { isNumber } from '@/utils/is'
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
-
-defineOptions({ name: 'CountTo' })
-
-const props = defineProps({
-  startVal: propTypes.number.def(0), // 开始播放值
-  endVal: propTypes.number.def(2021), // 最终值
-  duration: propTypes.number.def(3000), // 动画时长
-  autoplay: propTypes.bool.def(true), // 是否自动播放动画, 默认播放
-  decimals: propTypes.number.validate((value: number) => value >= 0).def(0), // 显示的小数位数, 默认不显示小数
-  decimal: propTypes.string.def('.'), // 小数分隔符号, 默认为点
-  separator: propTypes.string.def(','), // 数字每三位的分隔符, 默认为逗号
-  prefix: propTypes.string.def(''), // 前缀, 数值前面显示的内容
-  suffix: propTypes.string.def(''), // 后缀, 数值后面显示的内容
-  useEasing: propTypes.bool.def(true), // 是否使用缓动效果, 默认启用
-  easingFn: {
-    type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
-    default(t: number, b: number, c: number, d: number) {
-      return (c * (-(2**((-10 * t) / d)) + 1) * 1024) / 1023 + b
-    } // 缓动函数
+  import type { PropType } from 'vue'
+
+  import { defineEmits, defineExpose, defineOptions, defineProps } from 'vue'
+
+  import { isNumber } from '@/utils/is'
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
+
+  defineOptions({ name: 'CountTo' })
+
+  const props = defineProps({
+    startVal: propTypes.number.def(0), // 开始播放值
+    endVal: propTypes.number.def(2021), // 最终值
+    duration: propTypes.number.def(3000), // 动画时长
+    autoplay: propTypes.bool.def(true), // 是否自动播放动画, 默认播放
+    decimals: propTypes.number.validate((value: number) => value >= 0).def(0), // 显示的小数位数, 默认不显示小数
+    decimal: propTypes.string.def('.'), // 小数分隔符号, 默认为点
+    separator: propTypes.string.def(','), // 数字每三位的分隔符, 默认为逗号
+    prefix: propTypes.string.def(''), // 前缀, 数值前面显示的内容
+    suffix: propTypes.string.def(''), // 后缀, 数值后面显示的内容
+    useEasing: propTypes.bool.def(true), // 是否使用缓动效果, 默认启用
+    easingFn: {
+      type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
+      default(t: number, b: number, c: number, d: number) {
+        return (c * (-(2 ** ((-10 * t) / d)) + 1) * 1024) / 1023 + b
+      }, // 缓动函数
+    },
+  })
+
+  const emit = defineEmits(['mounted', 'callback'])
+
+  const { getPrefixCls } = useDesign()
+
+  const prefixCls = getPrefixCls('count-to')
+
+  const formatNumber = (num: number | string) => {
+    const { decimals, decimal, separator, suffix, prefix } = props
+    num = Number(num).toFixed(decimals)
+    num += ''
+    const x = num.split('.')
+    let x1 = x[0]
+    const x2 = x.length > 1 ? decimal + x[1] : ''
+    const rgx = /(\d+)(\d{3})/
+    if (separator && !isNumber(separator)) {
+      while (rgx.test(x1)) {
+        x1 = x1.replace(rgx, `$1${separator}$2`)
+      }
+    }
+    return prefix + x1 + x2 + suffix
   }
-})
-
-const emit = defineEmits(['mounted', 'callback'])
-
-const { getPrefixCls } = useDesign()
-
-const prefixCls = getPrefixCls('count-to')
 
-const formatNumber = (num: number | string) => {
-  const { decimals, decimal, separator, suffix, prefix } = props
-  num = Number(num).toFixed(decimals)
-  num += ''
-  const x = num.split('.')
-  let x1 = x[0]
-  const x2 = x.length > 1 ? decimal + x[1] : ''
-  const rgx = /(\d+)(\d{3})/
-  if (separator && !isNumber(separator)) {
-    while (rgx.test(x1)) {
-      x1 = x1.replace(rgx, `$1${  separator  }$2`)
+  const state = reactive<{
+    localStartVal: number
+    printVal: number | null
+    displayValue: string
+    paused: boolean
+    localDuration: number | null
+    startTime: number | null
+    timestamp: number | null
+    rAF: any
+    remaining: number | null
+  }>({
+    localStartVal: props.startVal,
+    displayValue: formatNumber(props.startVal),
+    printVal: null,
+    paused: false,
+    localDuration: props.duration,
+    startTime: null,
+    timestamp: null,
+    remaining: null,
+    rAF: null,
+  })
+
+  const displayValue = toRef(state, 'displayValue')
+
+  const getCountDown = computed(() => {
+    return props.startVal > props.endVal
+  })
+
+  const count = (timestamp: number) => {
+    const { useEasing, easingFn, endVal } = props
+    if (!state.startTime) state.startTime = timestamp
+    state.timestamp = timestamp
+    const progress = timestamp - state.startTime
+    state.remaining = (state.localDuration as number) - progress
+    if (useEasing) {
+      if (unref(getCountDown)) {
+        state.printVal =
+          state.localStartVal
+          - easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
+      } else {
+        state.printVal = easingFn(
+          progress,
+          state.localStartVal,
+          endVal - state.localStartVal,
+          state.localDuration as number
+        )
+      }
+    } else {
+      if (unref(getCountDown)) {
+        state.printVal =
+          state.localStartVal
+          - (state.localStartVal - endVal) * (progress / (state.localDuration as number))
+      } else {
+        state.printVal =
+          state.localStartVal
+          + (endVal - state.localStartVal) * (progress / (state.localDuration as number))
+      }
+    }
+    if (unref(getCountDown)) {
+      state.printVal = state.printVal < endVal ? endVal : state.printVal
+    } else {
+      state.printVal = state.printVal > endVal ? endVal : state.printVal
+    }
+    state.displayValue = formatNumber(state.printVal!)
+    if (progress < (state.localDuration as number)) {
+      state.rAF = requestAnimationFrame(count)
+    } else {
+      emit('callback')
     }
   }
-  return prefix + x1 + x2 + suffix
-}
-
-const state = reactive<{
-  localStartVal: number
-  printVal: number | null
-  displayValue: string
-  paused: boolean
-  localDuration: number | null
-  startTime: number | null
-  timestamp: number | null
-  rAF: any
-  remaining: number | null
-}>({
-  localStartVal: props.startVal,
-  displayValue: formatNumber(props.startVal),
-  printVal: null,
-  paused: false,
-  localDuration: props.duration,
-  startTime: null,
-  timestamp: null,
-  remaining: null,
-  rAF: null
-})
-
-const displayValue = toRef(state, 'displayValue')
-
-onMounted(() => {
-  if (props.autoplay) {
-    start()
+
+  const start = () => {
+    const { startVal, duration } = props
+    state.localStartVal = startVal
+    state.startTime = null
+    state.localDuration = duration
+    state.paused = false
+    state.rAF = requestAnimationFrame(count)
   }
-  emit('mounted')
-})
 
-const getCountDown = computed(() => {
-  return props.startVal > props.endVal
-})
+  const resume = () => {
+    state.startTime = null
+    state.localDuration = +(state.remaining as number)
+    state.localStartVal = +(state.printVal as number)
+    requestAnimationFrame(count)
+  }
 
-watch([() => props.startVal, () => props.endVal], () => {
-  if (props.autoplay) {
-    start()
+  const reset = () => {
+    state.startTime = null
+    cancelAnimationFrame(state.rAF)
+    state.displayValue = formatNumber(props.startVal)
   }
-})
-
-const start = () => {
-  const { startVal, duration } = props
-  state.localStartVal = startVal
-  state.startTime = null
-  state.localDuration = duration
-  state.paused = false
-  state.rAF = requestAnimationFrame(count)
-}
-
-const pauseResume = () => {
-  if (state.paused) {
-    resume()
-    state.paused = false
-  } else {
-    pause()
-    state.paused = true
+
+  const pause = () => {
+    cancelAnimationFrame(state.rAF)
   }
-}
-
-const pause = () => {
-  cancelAnimationFrame(state.rAF)
-}
-
-const resume = () => {
-  state.startTime = null
-  state.localDuration = +(state.remaining as number)
-  state.localStartVal = +(state.printVal as number)
-  requestAnimationFrame(count)
-}
-
-const reset = () => {
-  state.startTime = null
-  cancelAnimationFrame(state.rAF)
-  state.displayValue = formatNumber(props.startVal)
-}
-
-const count = (timestamp: number) => {
-  const { useEasing, easingFn, endVal } = props
-  if (!state.startTime) state.startTime = timestamp
-  state.timestamp = timestamp
-  const progress = timestamp - state.startTime
-  state.remaining = (state.localDuration as number) - progress
-  if (useEasing) {
-    if (unref(getCountDown)) {
-      state.printVal =
-        state.localStartVal -
-        easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
-    } else {
-      state.printVal = easingFn(
-        progress,
-        state.localStartVal,
-        endVal - state.localStartVal,
-        state.localDuration as number
-      )
+
+  onMounted(() => {
+    if (props.autoplay) {
+      start()
     }
-  } else {
-    if (unref(getCountDown)) {
-      state.printVal =
-        state.localStartVal -
-        (state.localStartVal - endVal) * (progress / (state.localDuration as number))
+    emit('mounted')
+  })
+
+  watch([() => props.startVal, () => props.endVal], () => {
+    if (props.autoplay) {
+      start()
+    }
+  })
+
+  const pauseResume = () => {
+    if (state.paused) {
+      resume()
+      state.paused = false
     } else {
-      state.printVal =
-        state.localStartVal +
-        (endVal - state.localStartVal) * (progress / (state.localDuration as number))
+      pause()
+      state.paused = true
     }
   }
-  if (unref(getCountDown)) {
-    state.printVal = state.printVal < endVal ? endVal : state.printVal
-  } else {
-    state.printVal = state.printVal > endVal ? endVal : state.printVal
-  }
-  state.displayValue = formatNumber(state.printVal!)
-  if (progress < (state.localDuration as number)) {
-    state.rAF = requestAnimationFrame(count)
-  } else {
-    emit('callback')
-  }
-}
-
-defineExpose({
-  pauseResume,
-  reset,
-  start,
-  pause
-})
+
+  defineExpose({ pauseResume, reset, start, pause })
 </script>
 
 <template>

+ 138 - 147
src/components/Cropper/src/CopperModal.vue

@@ -1,88 +1,88 @@
 <script lang="ts" setup>
-import { useI18n } from 'vue-i18n'
+  import { useI18n } from 'vue-i18n'
 
-import { dataURLtoBlob } from '@/utils/filt'
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
-import { CropperImage } from '@/components/Cropper'
+  import { dataURLtoBlob } from '@/utils/filt'
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
+  import { CropperImage } from '@/components/Cropper'
 
-import type { CropendResult, Cropper } from './types'
+  import type { CropendResult, Cropper } from './types'
 
-defineOptions({ name: 'CopperModal' })
+  defineOptions({ name: 'CopperModal' })
 
-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 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
+  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
+  // 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
   }
-  return false
-}
 
-function handleCropend({ imgBase64 }: CropendResult) {
-  previewSource.value = imgBase64
-}
+  function handleCropend({ imgBase64 }: CropendResult) {
+    previewSource.value = imgBase64
+  }
 
-function handleReady(cropperInstance: Cropper) {
-  cropper.value = cropperInstance
-}
+  function handleReady(cropperInstance: Cropper) {
+    cropper.value = cropperInstance
+  }
 
-function handlerToolbar(event: string, arg?: number) {
-  if (!cropper.value) return
-  const cropperImage = cropper.value.getCropperImage()
-  const cropperSelection = cropper.value.getCropperSelection()
+  function handlerToolbar(event: string, arg?: number) {
+    if (!cropper.value) return
+    const cropperImage = cropper.value.getCropperImage()
+    const cropperSelection = cropper.value.getCropperSelection()
 
-  if (event === 'reset') {
-    cropperImage?.$resetTransform()
-    cropperSelection?.$reset()
-  } else if (event === 'rotate') {
-    cropperImage?.$rotate(`${arg}deg`)
-  } else if (event === 'scaleX') {
-    scaleX = scaleX === -1 ? 1 : -1
-    cropperImage?.$scale(scaleX, 1)
-  } else if (event === 'scaleY') {
-    scaleY = scaleY === -1 ? 1 : -1
-    cropperImage?.$scale(1, scaleY)
-  } else if (event === 'zoom') {
-    cropperImage?.$zoom(arg!)
+    if (event === 'reset') {
+      cropperImage?.$resetTransform()
+      cropperSelection?.$reset()
+    } else if (event === 'rotate') {
+      cropperImage?.$rotate(`${arg}deg`)
+    } else if (event === 'scaleX') {
+      scaleX = scaleX === -1 ? 1 : -1
+      cropperImage?.$scale(scaleX, 1)
+    } else if (event === 'scaleY') {
+      scaleY = scaleY === -1 ? 1 : -1
+      cropperImage?.$scale(1, scaleY)
+    } else if (event === 'zoom') {
+      cropperImage?.$zoom(arg!)
+    }
   }
-}
 
-async function handleOk() {
-  const blob = dataURLtoBlob(previewSource.value)
-  emit('uploadSuccess', { source: previewSource.value, data: blob, filename })
-}
+  async function handleOk() {
+    const blob = dataURLtoBlob(previewSource.value)
+    emit('uploadSuccess', { source: previewSource.value, data: blob, filename })
+  }
 
-function openModal() {
-  dialogVisible.value = true
-}
+  function openModal() {
+    dialogVisible.value = true
+  }
 
-function closeModal() {
-  dialogVisible.value = false
-}
+  function closeModal() {
+    dialogVisible.value = false
+  }
 
-defineExpose({ openModal, closeModal })
+  defineExpose({ openModal, closeModal })
 </script>
 <template>
   <div @click.stop>
@@ -91,8 +91,7 @@ defineExpose({ openModal, closeModal })
       :can-fullscreen="false"
       :title="t('cropper.modalTitle')"
       max-height="380px"
-      width="800px"
-    >
+      width="800px">
       <div :class="prefixCls">
         <div :class="`${prefixCls}-left`">
           <div :class="`${prefixCls}-cropper`">
@@ -102,8 +101,7 @@ defineExpose({ openModal, closeModal })
               :src="src"
               height="300px"
               @cropend="handleCropend"
-              @ready="handleReady"
-            />
+              @ready="handleReady" />
           </div>
 
           <div :class="`${prefixCls}-toolbar`">
@@ -120,8 +118,7 @@ defineExpose({ openModal, closeModal })
                   :disabled="!src"
                   size="small"
                   type="primary"
-                  @click="handlerToolbar('reset')"
-                >
+                  @click="handlerToolbar('reset')">
                   <Icon icon="ant-design:reload-outlined" class="mr-1px" />
                 </el-button>
               </el-tooltip>
@@ -130,8 +127,7 @@ defineExpose({ openModal, closeModal })
                   :disabled="!src"
                   size="small"
                   type="primary"
-                  @click="handlerToolbar('rotate', -45)"
-                >
+                  @click="handlerToolbar('rotate', -45)">
                   <Icon icon="ant-design:rotate-left-outlined" class="mr-1px" />
                 </el-button>
               </el-tooltip>
@@ -140,8 +136,7 @@ defineExpose({ openModal, closeModal })
                   :disabled="!src"
                   size="small"
                   type="primary"
-                  @click="handlerToolbar('rotate', 45)"
-                >
+                  @click="handlerToolbar('rotate', 45)">
                   <Icon icon="ant-design:rotate-right-outlined" class="mr-1px" />
                 </el-button>
               </el-tooltip>
@@ -150,8 +145,7 @@ defineExpose({ openModal, closeModal })
                   :disabled="!src"
                   size="small"
                   type="primary"
-                  @click="handlerToolbar('scaleX')"
-                >
+                  @click="handlerToolbar('scaleX')">
                   <Icon icon="vaadin:arrows-long-h" class="mr-1px" />
                 </el-button>
               </el-tooltip>
@@ -160,8 +154,7 @@ defineExpose({ openModal, closeModal })
                   :disabled="!src"
                   size="small"
                   type="primary"
-                  @click="handlerToolbar('scaleY')"
-                >
+                  @click="handlerToolbar('scaleY')">
                   <Icon icon="vaadin:arrows-long-v" class="mr-1px" />
                 </el-button>
               </el-tooltip>
@@ -170,8 +163,7 @@ defineExpose({ openModal, closeModal })
                   :disabled="!src"
                   size="small"
                   type="primary"
-                  @click="handlerToolbar('zoom', 0.1)"
-                >
+                  @click="handlerToolbar('zoom', 0.1)">
                   <Icon icon="ant-design:zoom-in-outlined" class="mr-1px" />
                 </el-button>
               </el-tooltip>
@@ -180,8 +172,7 @@ defineExpose({ openModal, closeModal })
                   :disabled="!src"
                   size="small"
                   type="primary"
-                  @click="handlerToolbar('zoom', -0.1)"
-                >
+                  @click="handlerToolbar('zoom', -0.1)">
                   <Icon icon="ant-design:zoom-out-outlined" class="mr-1px" />
                 </el-button>
               </el-tooltip>
@@ -209,76 +200,76 @@ defineExpose({ openModal, closeModal })
   </div>
 </template>
 <style lang="scss">
-$prefix-cls: #{$namespace}-cropper-am;
+  $prefix-cls: #{$namespace}-cropper-am;
 
-.#{$prefix-cls} {
-  display: flex;
+  .#{$prefix-cls} {
+    display: flex;
 
-  &-left,
-  &-right {
-    height: 340px;
-  }
+    &-left,
+    &-right {
+      height: 340px;
+    }
 
-  &-left {
-    width: 55%;
-  }
+    &-left {
+      width: 55%;
+    }
 
-  &-right {
-    width: 45%;
-  }
+    &-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;
-  }
+    &-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;
-  }
+    &-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%;
+    &-preview {
+      width: 220px;
+      height: 220px;
+      margin: 0 auto;
+      overflow: hidden;
+      border: 1px solid;
+      border-radius: 50%;
 
-    img {
-      width: 100%;
-      height: 100%;
+      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;
+    &-group {
+      display: flex;
+      padding-top: 8px;
+      margin-top: 8px;
+      border-top: 1px solid;
+      justify-content: space-around;
+      align-items: center;
+    }
   }
-}
 </style>

+ 131 - 137
src/components/Cropper/src/Cropper.vue

@@ -1,147 +1,141 @@
 <script lang="ts" setup>
-import type { CSSProperties, PropType } from 'vue'
-
-import Cropper from 'cropperjs'
-import { useDebounceFn } from '@vueuse/core'
-
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
-
-defineOptions({ name: 'Cropper' })
-
-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<Record<string, any>>, default: () => ({}) }
-})
-
-const emit = defineEmits(['cropend', 'ready', 'cropendError'])
-const attrs = useAttrs()
-const imgElRef = ref<HTMLImageElement>()
-const containerRef = ref<HTMLElement>()
-const cropper = ref<Cropper>()
-
-const { getPrefixCls } = useDesign()
-const prefixCls = getPrefixCls('cropper-image')
-const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80)
-
-const getClass = computed(() => {
-  return [prefixCls, attrs.class]
-})
-const getWrapperStyle = computed((): CSSProperties => {
-  return { height: `${`${props.height}`.replace(/px/, '')  }px` }
-})
-
-onMounted(init)
-
-onUnmounted(() => {
-  cropper.value?.destroy()
-})
-
-async function init() {
-  const imgEl = unref(imgElRef)
-  const containerEl = unref(containerRef)
-  if (!imgEl || !containerEl) return
-
-  cropper.value = new Cropper(imgEl, {
-    container: containerEl,
-    ...props.options
+  import type { CSSProperties, PropType } from 'vue'
+
+  import Cropper from 'cropperjs'
+  import { useDebounceFn } from '@vueuse/core'
+
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
+
+  defineOptions({ name: 'Cropper' })
+
+  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<Record<string, any>>, default: () => ({}) },
   })
 
-  // Wait for custom elements to be ready, then configure
-  await nextTick()
-  const cropperSelection = cropper.value.getCropperSelection()
-  const cropperImage = cropper.value.getCropperImage()
-
-  if (cropperSelection) {
-    cropperSelection.initialCoverage = 0.5
-    cropperSelection.aspectRatio = 1
-    cropperSelection.movable = true
-    cropperSelection.resizable = true
-    cropperSelection.addEventListener('change', () => {
-      debounceRealTimeCroppered()
-    })
-  }
+  const emit = defineEmits(['cropend', 'ready', 'cropendError'])
+  const attrs = useAttrs()
+  const imgElRef = ref<HTMLImageElement>()
+  const containerRef = ref<HTMLElement>()
+  const cropper = ref<Cropper>()
 
-  if (cropperImage) {
-    cropperImage.addEventListener('transform', () => {
-      debounceRealTimeCroppered()
-    })
-    // Emit ready once image loads
-    cropperImage.addEventListener('load', () => {
-      emit('ready', cropper.value)
-      realTimeCroppered()
-    })
-  }
-}
+  const { getPrefixCls } = useDesign()
+  const prefixCls = getPrefixCls('cropper-image')
+  const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80)
 
-// Real-time display preview
-function realTimeCroppered() {
-  props.realTimePreview && croppered()
-}
+  const getClass = computed(() => {
+    return [prefixCls, attrs.class]
+  })
+  const getWrapperStyle = computed((): CSSProperties => {
+    return { height: `${`${props.height}`.replace(/px/, '')}px` }
+  })
 
-// event: return base64 and width and height information after cropping
-async function croppered() {
-  if (!cropper.value) return
+  onMounted(init)
 
-  const selection = cropper.value.getCropperSelection()
-  if (!selection) return
+  onUnmounted(() => {
+    cropper.value?.destroy()
+  })
+
+  async function init() {
+    const imgEl = unref(imgElRef)
+    const containerEl = unref(containerRef)
+    if (!imgEl || !containerEl) return
+
+    cropper.value = new Cropper(imgEl, { container: containerEl, ...props.options })
+
+    // Wait for custom elements to be ready, then configure
+    await nextTick()
+    const cropperSelection = cropper.value.getCropperSelection()
+    const cropperImage = cropper.value.getCropperImage()
+
+    if (cropperSelection) {
+      cropperSelection.initialCoverage = 0.5
+      cropperSelection.aspectRatio = 1
+      cropperSelection.movable = true
+      cropperSelection.resizable = true
+      cropperSelection.addEventListener('change', () => {
+        debounceRealTimeCroppered()
+      })
+    }
+
+    if (cropperImage) {
+      cropperImage.addEventListener('transform', () => {
+        debounceRealTimeCroppered()
+      })
+      // Emit ready once image loads
+      cropperImage.addEventListener('load', () => {
+        emit('ready', cropper.value)
+        realTimeCroppered()
+      })
+    }
+  }
 
-  const imgInfo = {
-    x: selection.x,
-    y: selection.y,
-    width: selection.width,
-    height: selection.height
+  // Real-time display preview
+  function realTimeCroppered() {
+    props.realTimePreview && croppered()
   }
 
-  try {
-    let canvas = await selection.$toCanvas()
-    if (props.circled) {
-      canvas = getRoundedCanvas(canvas)
+  // event: return base64 and width and height information after cropping
+  async function croppered() {
+    if (!cropper.value) return
+
+    const selection = cropper.value.getCropperSelection()
+    if (!selection) return
+
+    const imgInfo = {
+      x: selection.x,
+      y: selection.y,
+      width: selection.width,
+      height: selection.height,
     }
-    canvas.toBlob((blob) => {
-      if (!blob) return
-      const fileReader = new FileReader()
-      fileReader.readAsDataURL(blob)
-      fileReader.onloadend = (e) => {
-        emit('cropend', {
-          imgBase64: e.target?.result ?? '',
-          imgInfo
-        })
-      }
-      fileReader.onerror = () => {
-        emit('cropendError')
+
+    try {
+      let canvas = await selection.$toCanvas()
+      if (props.circled) {
+        canvas = getRoundedCanvas(canvas)
       }
-    }, 'image/png')
-  } catch {
-    // Selection may not be ready yet
+      canvas.toBlob(blob => {
+        if (!blob) return
+        const fileReader = new FileReader()
+        fileReader.readAsDataURL(blob)
+        fileReader.onloadend = e => {
+          emit('cropend', { imgBase64: e.target?.result ?? '', imgInfo })
+        }
+        fileReader.onerror = () => {
+          emit('cropendError')
+        }
+      }, 'image/png')
+    } catch {
+      // Selection may not be ready yet
+    }
+  }
+
+  // Get a circular picture canvas
+  function getRoundedCanvas(sourceCanvas: HTMLCanvasElement) {
+    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
   }
-}
-
-// Get a circular picture canvas
-function getRoundedCanvas(sourceCanvas: HTMLCanvasElement) {
-  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>
 <template>
   <div ref="containerRef" :class="getClass" :style="getWrapperStyle">
@@ -149,12 +143,12 @@ function getRoundedCanvas(sourceCanvas: HTMLCanvasElement) {
   </div>
 </template>
 <style lang="scss" scoped>
-$prefix-cls: #{$namespace}-cropper-image;
+  $prefix-cls: #{$namespace}-cropper-image;
 
-.#{$prefix-cls} {
-  // cropperjs 动态插入的自定义元素不受 scoped 样式约束,需要穿透设置高度
-  > :deep(cropper-canvas) {
-    height: 100%;
+  .#{$prefix-cls} {
+    // cropperjs 动态插入的自定义元素不受 scoped 样式约束,需要穿透设置高度
+    > :deep(cropper-canvas) {
+      height: 100%;
+    }
   }
-}
 </style>

+ 103 - 107
src/components/Cropper/src/CropperAvatar.vue

@@ -1,59 +1,56 @@
 <script lang="ts" setup>
-import { useI18n } from 'vue-i18n'
+  import { useI18n } from 'vue-i18n'
 
-import { propTypes } from '@/utils/propTypes'
-import avatar from '@/assets/imgs/avatar.gif'
-import { useDesign } from '@/hooks/web/useDesign'
+  import { propTypes } from '@/utils/propTypes'
+  import avatar from '@/assets/imgs/avatar.gif'
+  import { useDesign } from '@/hooks/web/useDesign'
 
-import CopperModal from './CopperModal.vue'
+  import CopperModal from './CopperModal.vue'
 
-defineOptions({ name: 'CropperAvatar' })
+  defineOptions({ name: 'CropperAvatar' })
 
-const props = defineProps({
-  width: propTypes.string.def('200px'),
-  value: propTypes.string.def(''),
-  showBtn: propTypes.bool.def(true),
-  btnText: propTypes.string.def('')
-})
+  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 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()
+  const cropperModelRef = ref()
 
-watchEffect(() => {
-  sourceValue.value = props.value
-})
+  watchEffect(() => {
+    sourceValue.value = props.value
+  })
 
-watch(
-  () => sourceValue.value,
-  (v: string) => {
-    emit('update:value', v)
+  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()
   }
-)
-
-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
-})
+
+  defineExpose({ open, close })
 </script>
 <template>
   <div class="user-info-head" @click="open()">
@@ -65,79 +62,78 @@ defineExpose({
     <CopperModal
       ref="cropperModelRef"
       :src-value="sourceValue"
-      @upload-success="handleUploadSuccess"
-    />
+      @upload-success="handleUploadSuccess" />
   </div>
 </template>
 <style lang="scss" scoped>
-$prefix-cls: #{$namespace}--cropper-avatar;
+  $prefix-cls: #{$namespace}--cropper-avatar;
 
-.#{$prefix-cls} {
-  display: inline-block;
-  text-align: center;
+  .#{$prefix-cls} {
+    display: inline-block;
+    text-align: center;
 
-  &-image-wrapper {
-    overflow: hidden;
-    cursor: pointer;
-    border: 1px solid;
-    border-radius: 50%;
+    &-image-wrapper {
+      overflow: hidden;
+      cursor: pointer;
+      border: 1px solid;
+      border-radius: 50%;
 
-    img {
-      width: 100%;
+      img {
+        width: 100%;
+      }
     }
-  }
 
-  &-image-mask {
-    position: absolute;
-    width: inherit;
-    height: inherit;
-    cursor: pointer;
-    background: rgb(0 0 0 / 40%);
-    border: inherit;
-    border-radius: inherit;
-    opacity: 0;
-    transition: opacity 0.4s;
-
-    ::v-deep(svg) {
-      margin: auto;
+    &-image-mask {
+      position: absolute;
+      width: inherit;
+      height: inherit;
+      cursor: pointer;
+      background: rgb(0 0 0 / 40%);
+      border: inherit;
+      border-radius: inherit;
+      opacity: 0;
+      transition: opacity 0.4s;
+
+      ::v-deep(svg) {
+        margin: auto;
+      }
     }
+
+    &-image-mask:hover {
+      opacity: 40;
+    }
+
+    &-upload-btn {
+      margin: 10px auto;
+    }
+  }
+
+  .user-info-head {
+    position: relative;
+    display: inline-block;
   }
 
-  &-image-mask:hover {
-    opacity: 40;
+  .img-circle {
+    border-radius: 50%;
+  }
+
+  .img-lg {
+    width: 120px;
+    height: 120px;
   }
 
-  &-upload-btn {
-    margin: 10px auto;
+  .user-info-head:hover::after {
+    position: absolute;
+    inset: 0;
+    font-size: 24px;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-style: normal;
+    line-height: 110px;
+    color: #eee;
+    cursor: pointer;
+    background: rgb(0 0 0 / 50%);
+    border-radius: 50%;
+    content: '+';
   }
-}
-
-.user-info-head {
-  position: relative;
-  display: inline-block;
-}
-
-.img-circle {
-  border-radius: 50%;
-}
-
-.img-lg {
-  width: 120px;
-  height: 120px;
-}
-
-.user-info-head:hover::after {
-  position: absolute;
-  inset: 0;
-  font-size: 24px;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  font-style: normal;
-  line-height: 110px;
-  color: #eee;
-  cursor: pointer;
-  background: rgb(0 0 0 / 50%);
-  border-radius: 50%;
-  content: '+';
-}
 </style>

+ 102 - 108
src/components/Dialog/src/Dialog.vue

@@ -1,89 +1,87 @@
 <script lang="ts" setup>
-import { isNumber } from '@/utils/is'
-import { propTypes } from '@/utils/propTypes'
-
-defineOptions({ name: 'Dialog' })
-
-const props = defineProps({
-  modelValue: propTypes.bool.def(false),
-  title: propTypes.string.def('Dialog'),
-  fullscreen: propTypes.bool.def(true),
-  defaultFullscreen: propTypes.bool.def(false),
-  width: propTypes.oneOfType([String, Number]).def('40%'),
-  scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度
-  maxHeight: propTypes.oneOfType([String, Number]).def('400px'),
-  loading: propTypes.bool.def(false)
-})
-const emits = defineEmits(['update:modelValue'])
-const slots = useSlots()
-const getBindValue = computed(() => {
-  const delArr: string[] = [
-    'fullscreen',
-    'defaultFullscreen',
-    'title',
-    'maxHeight',
-    'appendToBody',
-    'loading'
-  ]
-  const attrs = useAttrs()
-  const obj = { ...attrs, ...props }
-  for (const key in obj) {
-    if (delArr.includes(key)) {
-      delete obj[key]
+  import { defineEmits, defineOptions, defineProps } from 'vue'
+
+  import { isNumber } from '@/utils/is'
+  import { propTypes } from '@/utils/propTypes'
+
+  defineOptions({ name: 'AppDialog' })
+
+  const props = defineProps({
+    modelValue: propTypes.bool.def(false),
+    title: propTypes.string.def('Dialog'),
+    fullscreen: propTypes.bool.def(true),
+    defaultFullscreen: propTypes.bool.def(false),
+    width: propTypes.oneOfType([String, Number]).def('40%'),
+    scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度
+    maxHeight: propTypes.oneOfType([String, Number]).def('400px'),
+    loading: propTypes.bool.def(false),
+  })
+  const emits = defineEmits(['update:modelValue'])
+  const slots = useSlots()
+  const getBindValue = computed(() => {
+    const delArr: string[] = [
+      'fullscreen',
+      'defaultFullscreen',
+      'title',
+      'maxHeight',
+      'appendToBody',
+      'loading',
+    ]
+    const attrs = useAttrs()
+    const obj = { ...attrs, ...props }
+    for (const key in obj) {
+      if (delArr.includes(key)) {
+        delete obj[key]
+      }
     }
-  }
-  return obj
-})
+    return obj
+  })
 
-const isFullscreen = ref(props.defaultFullscreen)
+  const isFullscreen = ref(props.defaultFullscreen)
 
-watch(
-  () => props.modelValue,
-  (visible) => {
-    if (visible) {
-      isFullscreen.value = props.defaultFullscreen
-    }
-  }
-)
-
-const toggleFull = () => {
-  isFullscreen.value = !unref(isFullscreen)
-}
-
-const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
-
-watch(
-  () => isFullscreen.value,
-  async (val: boolean) => {
-    await nextTick()
-    if (val) {
-      const windowHeight = document.documentElement.offsetHeight
-      dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`
-    } else {
-      dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight
+  watch(
+    () => props.modelValue,
+    visible => {
+      if (visible) {
+        isFullscreen.value = props.defaultFullscreen
+      }
     }
-  },
-  {
-    immediate: true
-  }
-)
+  )
 
-const dialogStyle = computed(() => {
-  return {
-    height: unref(dialogHeight)
+  const toggleFull = () => {
+    isFullscreen.value = !unref(isFullscreen)
   }
-})
-
-const closing = ref(false)
 
-function closeHandler() {
-  emits('update:modelValue', false)
-  closing.value = true
-}
+  const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
+
+  watch(
+    () => isFullscreen.value,
+    async (val: boolean) => {
+      await nextTick()
+      if (val) {
+        const windowHeight = document.documentElement.offsetHeight
+        dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`
+      } else {
+        dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight
+      }
+    },
+    { immediate: true }
+  )
+
+  const dialogStyle = computed(() => {
+    return { height: unref(dialogHeight) }
+  })
+
+  const closing = ref(false)
+
+  function closeHandler() {
+    emits('update:modelValue', false)
+    closing.value = true
+  }
 
-function closedHandler() {
-  closing.value = false
-}
+  function closedHandler() {
+    closing.value = false
+  }
 </script>
 
 <template>
@@ -98,31 +96,27 @@ function closedHandler() {
     class="com-dialog"
     :show-close="false"
     @close="closeHandler"
-    @closed="closedHandler"
-  >
+    @closed="closedHandler">
     <template #header="{ close }">
       <div class="pl-15px pr-15px flex h-54px items-center justify-between relative">
         <slot name="title">
           {{ title }}
         </slot>
         <div
-          class="flex h-54px translate-y-[-50%] items-center right-15px top-[50%] justify-between absolute"
-        >
+          class="flex h-54px translate-y-[-50%] items-center right-15px top-[50%] justify-between absolute">
           <Icon
             v-if="fullscreen"
             class="is-hover mr-10px cursor-pointer"
             :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
             color="var(--el-color-info)"
             hover-color="var(--el-color-primary)"
-            @click="toggleFull"
-          />
+            @click="toggleFull" />
           <Icon
             class="is-hover cursor-pointer"
             icon="ep:close"
             hover-color="var(--el-color-primary)"
             color="var(--el-color-info)"
-            @click.stop="close"
-          />
+            @click.stop="close" />
         </div>
       </div>
     </template>
@@ -144,34 +138,34 @@ function closedHandler() {
 </template>
 
 <style lang="scss">
-.com-dialog {
-  .#{$elNamespace}-overlay-dialog {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-  }
+  .com-dialog {
+    .#{$elNamespace}-overlay-dialog {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
 
-  .#{$elNamespace}-dialog {
-    margin: 0 !important;
+    .#{$elNamespace}-dialog {
+      margin: 0 !important;
 
-    &__header {
-      height: 54px;
-      padding: 0;
-      margin-right: 0 !important;
-      border-bottom: 1px solid var(--el-border-color);
-    }
+      &__header {
+        height: 54px;
+        padding: 0;
+        margin-right: 0 !important;
+        border-bottom: 1px solid var(--el-border-color);
+      }
 
-    &__body {
-      padding: 15px !important;
-    }
+      &__body {
+        padding: 15px !important;
+      }
 
-    &__footer {
-      border-top: 1px solid var(--el-border-color);
-    }
+      &__footer {
+        border-top: 1px solid var(--el-border-color);
+      }
 
-    &__headerbtn {
-      top: 0;
+      &__headerbtn {
+        top: 0;
+      }
     }
   }
-}
 </style>

+ 74 - 86
src/components/DictTag/src/DictTag.vue

@@ -1,95 +1,83 @@
 <script lang="tsx">
-import type { PropType } from 'vue';
+  import type { PropType } from 'vue'
 
-import { ElTag } from 'element-plus'
-import { computed, defineComponent } from 'vue'
+  import { ElTag } from 'element-plus'
+  import { computed, defineComponent } from 'vue'
 
-import type { DictDataType} from '@/utils/dict';
+  import type { DictDataType } from '@/utils/dict'
 
-import { isHexColor } from '@/utils/color'
-import { getDictOptions } from '@/utils/dict'
-import { isArray, isBoolean, isNumber, isString } from '@/utils/is'
+  import { isHexColor } from '@/utils/color'
+  import { getDictOptions } from '@/utils/dict'
+  import { isArray, isBoolean, isNumber, isString } from '@/utils/is'
 
-export default defineComponent({
-  name: 'DictTag',
-  props: {
-    type: {
-      type: String as PropType<string>,
-      required: true
+  export default defineComponent({
+    name: 'DictTag',
+    props: {
+      type: { type: String as PropType<string>, required: true },
+      value: { type: [String, Number, Boolean, Array], required: true },
+      // 字符串分隔符 只有当 props.value 传入值为字符串时有效
+      separator: { type: String as PropType<string>, default: ',' },
+      // 每个 tag 之间的间隔,默认为 5px,参考的 el-row 的 gutter
+      gutter: { type: String as PropType<string>, default: '5px' },
     },
-    value: {
-      type: [String, Number, Boolean, Array],
-      required: true
-    },
-    // 字符串分隔符 只有当 props.value 传入值为字符串时有效
-    separator: {
-      type: String as PropType<string>,
-      default: ','
-    },
-    // 每个 tag 之间的间隔,默认为 5px,参考的 el-row 的 gutter
-    gutter: {
-      type: String as PropType<string>,
-      default: '5px'
-    }
-  },
-  setup(props) {
-    const valueArr: any = computed(() => {
-      // 1. 是 Number 类型和 Boolean 类型的情况
-      if (isNumber(props.value) || isBoolean(props.value)) {
-        return [String(props.value)]
-      }
-      // 2. 是字符串(进一步判断是否有包含分隔符号 -> props.sepSymbol )
-      else if (isString(props.value)) {
-        return props.value.split(props.separator)
-      }
-      // 3. 数组
-      else if (isArray(props.value)) {
-        return props.value.map(String)
-      }
-      return []
-    })
-    const renderDictTag = () => {
-      if (!props.type) {
-        return null
-      }
-      // 解决自定义字典标签值为零时标签不渲染的问题
-      if (props.value === undefined || props.value === null || props.value === '') {
-        return null
-      }
-      const dictOptions = getDictOptions(props.type)
+    setup(props) {
+      const valueArr: any = computed(() => {
+        // 1. 是 Number 类型和 Boolean 类型的情况
+        if (isNumber(props.value) || isBoolean(props.value)) {
+          return [String(props.value)]
+        }
+        // 2. 是字符串(进一步判断是否有包含分隔符号 -> props.sepSymbol )
+        else if (isString(props.value)) {
+          return props.value.split(props.separator)
+        }
+        // 3. 数组
+        else if (isArray(props.value)) {
+          return props.value.map(String)
+        }
+        return []
+      })
+      const renderDictTag = () => {
+        if (!props.type) {
+          return null
+        }
+        // 解决自定义字典标签值为零时标签不渲染的问题
+        if (props.value === undefined || props.value === null || props.value === '') {
+          return null
+        }
+        const dictOptions = getDictOptions(props.type)
 
-      return (
-        <div
-          class="dict-tag"
-          style={{
-            display: 'inline-flex',
-            gap: props.gutter,
-            justifyContent: 'center',
-            alignItems: 'center'
-          }}
-        >
-          {dictOptions.map((dict: DictDataType) => {
-            if (valueArr.value.includes(dict.value)) {
-              if (`${dict.colorType  }` === 'primary' || `${dict.colorType  }` === 'default') {
-                dict.colorType = ''
+        return (
+          <div
+            class='dict-tag'
+            style={{
+              display: 'inline-flex',
+              gap: props.gutter,
+              justifyContent: 'center',
+              alignItems: 'center',
+            }}>
+            {dictOptions.map((dict: DictDataType) => {
+              if (valueArr.value.includes(dict.value)) {
+                if (`${dict.colorType}` === 'primary' || `${dict.colorType}` === 'default') {
+                  dict.colorType = ''
+                }
+                return (
+                  // 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
+                  <ElTag
+                    style={dict?.cssClass ? 'color: #fff' : ''}
+                    type={dict?.colorType || null}
+                    color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
+                    disableTransitions={true}>
+                    {dict?.label}
+                  </ElTag>
+                )
               }
-              return (
-                // 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
-                <ElTag
-                  style={dict?.cssClass ? 'color: #fff' : ''}
-                  type={dict?.colorType || null}
-                  color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
-                  disableTransitions={true}
-                >
-                  {dict?.label}
-                </ElTag>
-              )
-            }
-          })}
-        </div>
-      )
-    }
-    return () => renderDictTag()
-  }
-})
+
+              return null
+            })}
+          </div>
+        )
+      }
+      return () => renderDictTag()
+    },
+  })
 </script>

+ 79 - 87
src/components/Echart/src/Echart.vue

@@ -1,121 +1,113 @@
 <script lang="ts" setup>
-import type { PropType } from 'vue'
-import type { EChartsOption } from 'echarts'
+  import type { PropType } from 'vue'
+  import type { EChartsOption } from 'echarts'
 
-import { debounce } from 'lodash-es'
-import 'echarts-wordcloud'
+  import { debounce } from 'lodash-es'
+  import 'echarts-wordcloud'
+  import { defineOptions, defineProps } from 'vue'
 
-import { isString } from '@/utils/is'
-import echarts from '@/plugins/echarts'
-import { propTypes } from '@/utils/propTypes'
-import { useAppStore } from '@/store/modules/app'
-import { useDesign } from '@/hooks/web/useDesign'
+  import { isString } from '@/utils/is'
+  import echarts from '@/plugins/echarts'
+  import { propTypes } from '@/utils/propTypes'
+  import { useAppStore } from '@/store/modules/app'
 
-import 'echarts/lib/component/markPoint'
-import 'echarts/lib/component/markLine'
-import 'echarts/lib/component/markArea'
+  import 'echarts/lib/component/markPoint'
+  import 'echarts/lib/component/markLine'
+  import 'echarts/lib/component/markArea'
 
-defineOptions({ name: 'EChart' })
+  import { useDesign } from '@/hooks/web/useDesign'
 
-const props = defineProps({
-  options: {
-    type: Object as PropType<EChartsOption>,
-    required: true
-  },
-  width: propTypes.oneOfType([Number, String]).def(''),
-  height: propTypes.oneOfType([Number, String]).def('500px'),
-  notMerge: propTypes.bool.def(false)
-})
+  defineOptions({ name: 'EChart' })
 
-const { getPrefixCls, variables } = useDesign()
+  const props = defineProps({
+    options: { type: Object as PropType<EChartsOption>, required: true },
+    width: propTypes.oneOfType([Number, String]).def(''),
+    height: propTypes.oneOfType([Number, String]).def('500px'),
+    notMerge: propTypes.bool.def(false),
+  })
 
-const prefixCls = getPrefixCls('echart')
+  const { getPrefixCls, variables } = useDesign()
 
-const appStore = useAppStore()
+  const prefixCls = getPrefixCls('echart')
 
-const isDark = computed(() => appStore.getIsDark)
+  const appStore = useAppStore()
 
-const theme = computed(() => {
-  const echartTheme: boolean | string = unref(isDark) ? true : 'auto'
+  const isDark = computed(() => appStore.getIsDark)
 
-  return echartTheme
-})
+  const theme = computed(() => {
+    const echartTheme: boolean | string = unref(isDark) ? true : 'auto'
 
-const options = computed(() => {
-  return Object.assign(props.options, {
-    darkMode: unref(theme)
+    return echartTheme
   })
-})
 
-const elRef = ref<ElRef>()
+  const options = computed(() => {
+    return Object.assign({}, props.options, { darkMode: unref(theme) })
+  })
 
-let echartRef: Nullable<echarts.ECharts> = null
+  const elRef = ref<ElRef>()
 
-const contentEl = ref<Element>()
+  let echartRef: Nullable<echarts.ECharts> = null
 
-const styles = computed(() => {
-  const width = isString(props.width) ? props.width : `${props.width}px`
-  const height = isString(props.height) ? props.height : `${props.height}px`
+  const contentEl = ref<Element>()
 
-  return {
-    width,
-    height
-  }
-})
+  const styles = computed(() => {
+    const width = isString(props.width) ? props.width : `${props.width}px`
+    const height = isString(props.height) ? props.height : `${props.height}px`
 
-const initChart = () => {
-  if (unref(elRef) && props.options) {
-    echartRef = echarts.init(unref(elRef) as HTMLElement)
-    echartRef?.setOption(unref(options), props.notMerge)
-  }
-}
+    return { width, height }
+  })
 
-watch(
-  () => options.value,
-  (options) => {
-    if (echartRef) {
-      echartRef?.setOption(options, props.notMerge)
-      echartRef?.resize()
+  const initChart = () => {
+    if (unref(elRef) && props.options) {
+      echartRef = echarts.init(unref(elRef) as HTMLElement)
+      echartRef?.setOption(unref(options), props.notMerge)
     }
-  },
-  {
-    deep: true
   }
-)
 
-const resizeHandler = debounce(() => {
-  if (echartRef) {
-    echartRef.resize()
-  }
-}, 100)
+  watch(
+    () => options.value,
+    newValue => {
+      if (echartRef) {
+        echartRef?.setOption(newValue, props.notMerge)
+        echartRef?.resize()
+      }
+    },
+    { deep: true }
+  )
+
+  const resizeHandler = debounce(() => {
+    if (echartRef) {
+      echartRef.resize()
+    }
+  }, 100)
 
-const contentResizeHandler = async (e: TransitionEvent) => {
-  if (e.propertyName === 'width') {
-    resizeHandler()
+  const contentResizeHandler = async (e: TransitionEvent) => {
+    if (e.propertyName === 'width') {
+      resizeHandler()
+    }
   }
-}
 
-onMounted(() => {
-  initChart()
+  onMounted(() => {
+    initChart()
 
-  window.addEventListener('resize', resizeHandler)
+    window.addEventListener('resize', resizeHandler)
 
-  contentEl.value = document.getElementsByClassName(`${variables.namespace}-layout-content`)[0]
-  unref(contentEl) &&
-    (unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
-})
+    contentEl.value = document.getElementsByClassName(`${variables.namespace}-layout-content`)[0]
+    unref(contentEl)
+      && (unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
+  })
 
-onBeforeUnmount(() => {
-  window.removeEventListener('resize', resizeHandler)
-  unref(contentEl) &&
-    (unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
-})
+  onBeforeUnmount(() => {
+    window.removeEventListener('resize', resizeHandler)
+    unref(contentEl)
+      && (unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
+  })
 
-onActivated(() => {
-  if (echartRef) {
-    echartRef.resize()
-  }
-})
+  onActivated(() => {
+    if (echartRef) {
+      echartRef.resize()
+    }
+  })
 </script>
 
 <template>

+ 168 - 185
src/components/Editor/src/Editor.vue

@@ -1,211 +1,196 @@
 <script lang="ts" setup>
-import type { PropType } from 'vue'
-import type { IDomEditor, IEditorConfig } from '@wangeditor-next/editor';
+  import type { PropType } from 'vue'
+  import type { IDomEditor, IEditorConfig } from '@wangeditor-next/editor'
 
-import merge from 'lodash-es/merge'
-import { ElMessage } from 'element-plus'
-import { i18nChangeLanguage } from '@wangeditor-next/editor'
-import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue'
+  import merge from 'lodash-es/merge'
+  import { ElMessage } from 'element-plus'
+  import { i18nChangeLanguage } from '@wangeditor-next/editor'
+  import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue'
 
-import { isNumber } from '@/utils/is'
-import { propTypes } from '@/utils/propTypes'
-import { useLocaleStore } from '@/store/modules/locale'
-import { useUpload } from '@/components/UploadFile/src/useUpload'
+  import { isNumber } from '@/utils/is'
+  import { propTypes } from '@/utils/propTypes'
+  import { useLocaleStore } from '@/store/modules/locale'
+  import { useUpload } from '@/components/UploadFile/src/useUpload'
 
-defineOptions({ name: 'Editor' })
+  defineOptions({ name: 'Editor' })
 
-const props = defineProps({
-  editorId: propTypes.string.def('wangEditor-1'),
-  height: propTypes.oneOfType([Number, String]).def('500px'),
-  editorConfig: {
-    type: Object as PropType<Partial<IEditorConfig>>,
-    default: () => undefined
-  },
-  readonly: propTypes.bool.def(false),
-  modelValue: propTypes.string.def(''),
-  directory: propTypes.string.def('editor-default')
-})
+  const props = defineProps({
+    editorId: propTypes.string.def('wangEditor-1'),
+    height: propTypes.oneOfType([Number, String]).def('500px'),
+    editorConfig: { type: Object as PropType<Partial<IEditorConfig>>, default: () => undefined },
+    readonly: propTypes.bool.def(false),
+    modelValue: propTypes.string.def(''),
+    directory: propTypes.string.def('editor-default'),
+  })
 
-const emit = defineEmits(['change', 'update:modelValue'])
+  const emit = defineEmits(['change', 'update:modelValue'])
 
-type InsertFnType = (url: string, alt: string, href: string) => void
+  type InsertFnType = (url: string, alt: string, href: string) => void
 
-const localeStore = useLocaleStore()
+  const localeStore = useLocaleStore()
 
-const currentLocale = computed(() => localeStore.getCurrentLocale)
+  const currentLocale = computed(() => localeStore.getCurrentLocale)
 
-i18nChangeLanguage(unref(currentLocale).lang)
+  i18nChangeLanguage(unref(currentLocale).lang)
 
-// 使用项目的上传方法,实现逐个文件上传
-const { httpRequest: imageHttpRequest } = useUpload(`${props.directory}-image`)
-const { httpRequest: videoHttpRequest } = useUpload(`${props.directory}-video`)
+  // 使用项目的上传方法,实现逐个文件上传
+  const { httpRequest: imageHttpRequest } = useUpload(`${props.directory}-image`)
+  const { httpRequest: videoHttpRequest } = useUpload(`${props.directory}-video`)
 
-// 编辑器实例,必须用 shallowRef
-const editorRef = shallowRef<IDomEditor>()
+  // 编辑器实例,必须用 shallowRef
+  const editorRef = shallowRef<IDomEditor>()
 
-const valueHtml = ref('')
+  const valueHtml = ref('')
 
-watch(
-  () => props.modelValue,
-  (val: string) => {
-    if (!val) {
-      val = ''
-    }
-    if (val === unref(valueHtml)) return
-    valueHtml.value = val
-  },
-  {
-    immediate: true
-  }
-)
+  watch(
+    () => props.modelValue,
+    (val: string) => {
+      if (!val) {
+        val = ''
+      }
+      if (val === unref(valueHtml)) return
+      valueHtml.value = val
+    },
+    { immediate: true }
+  )
 
-// 监听
-watch(
-  () => valueHtml.value,
-  (val: string) => {
-    emit('update:modelValue', val)
-  }
-)
-watch(
-  () => props.readonly,
-  async (val) => {
-    // 特殊:等待 editorRef 渲染完成
-    if (!editorRef.value) {
-      await nextTick()
+  // 监听
+  watch(
+    () => valueHtml.value,
+    (val: string) => {
+      emit('update:modelValue', val)
     }
-    if (val) {
-      editorRef.value?.disable()
-    } else {
-      editorRef.value?.enable()
+  )
+  watch(
+    () => props.readonly,
+    async val => {
+      // 特殊:等待 editorRef 渲染完成
+      if (!editorRef.value) {
+        await nextTick()
+      }
+      if (val) {
+        editorRef.value?.disable()
+      } else {
+        editorRef.value?.enable()
+      }
     }
+  )
+
+  const handleCreated = (editor: IDomEditor) => {
+    editorRef.value = editor
   }
-)
-
-const handleCreated = (editor: IDomEditor) => {
-  editorRef.value = editor
-}
-
-// 编辑器配置
-const editorConfig = computed((): IEditorConfig => {
-  return merge(
-    {
-      placeholder: '请输入内容...',
-      readOnly: props.readonly,
-      customAlert: (s: string, t: string) => {
-        switch (t) {
-          case 'success':
-            ElMessage.success(s)
-            break
-          case 'info':
-            ElMessage.info(s)
-            break
-          case 'warning':
-            ElMessage.warning(s)
-            break
-          case 'error':
-            ElMessage.error(s)
-            break
-          default:
-            ElMessage.info(s)
-            break
-        }
-      },
-      autoFocus: false,
-      scroll: true,
-      EXTEND_CONF: {
-        mentionConfig: {
-          showModal: () => {},
-          hideModal: () => {}
-        }
-      },
-      MENU_CONF: {
-        'uploadImage': {
-          // 单个文件的最大体积限制,默认为 2M
-          maxFileSize: 10 * 1024 * 1024,
-          // 最多可上传几个文件,默认为 100
-          maxNumberOfFiles: 100,
-          // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
-          allowedFileTypes: ['image/*'],
-
-          // 使用 customUpload 实现逐个文件上传,复用项目的 httpRequest
-          async customUpload(file: File, insertFn: InsertFnType) {
-            try {
-              const res = await imageHttpRequest({
-                file: file as any,
-                onProgress: () => {},
-                onSuccess: () => {},
-                onError: () => {}
-              } as any)
-              // 兼容前端直连上传和后端上传两种模式的返回格式
-              const url = (res as any).data?.data || (res as any).data
-              insertFn(url, 'image', url)
-            } catch (error: any) {
-              ElMessage.error(error.msg || '图片上传失败')
-              console.error('Upload error:', error)
-            }
+
+  // 编辑器配置
+  const editorConfig = computed((): IEditorConfig => {
+    return merge(
+      {
+        placeholder: '请输入内容...',
+        readOnly: props.readonly,
+        customAlert: (s: string, t: string) => {
+          switch (t) {
+            case 'success':
+              ElMessage.success(s)
+              break
+            case 'info':
+              ElMessage.info(s)
+              break
+            case 'warning':
+              ElMessage.warning(s)
+              break
+            case 'error':
+              ElMessage.error(s)
+              break
+            default:
+              ElMessage.info(s)
+              break
           }
         },
-        'uploadVideo': {
-          // 单个文件的最大体积限制,默认为 10M
-          maxFileSize: 1024 * 1024 * 1024,
-          // 最多可上传几个文件,默认为 100
-          maxNumberOfFiles: 10,
-          // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
-          allowedFileTypes: ['video/*'],
-
-          // 使用 customUpload 实现逐个文件上传,复用项目的 httpRequest
-          async customUpload(file: File, insertFn: InsertFnType) {
-            try {
-              const res = await videoHttpRequest({
-                file: file as any,
-                onProgress: () => {},
-                onSuccess: () => {},
-                onError: () => {}
-              } as any)
-              // 兼容前端直连上传和后端上传两种模式的返回格式
-              const url = (res as any).data?.data || (res as any).data
-              insertFn(url, 'mp4', url)
-            } catch (error: any) {
-              ElMessage.error(error.msg || '视频上传失败')
-              console.error('Upload error:', error)
-            }
-          }
-        }
+        autoFocus: false,
+        scroll: true,
+        EXTEND_CONF: { mentionConfig: { showModal: () => {}, hideModal: () => {} } },
+        MENU_CONF: {
+          uploadImage: {
+            // 单个文件的最大体积限制,默认为 2M
+            maxFileSize: 10 * 1024 * 1024,
+            // 最多可上传几个文件,默认为 100
+            maxNumberOfFiles: 100,
+            // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
+            allowedFileTypes: ['image/*'],
+
+            // 使用 customUpload 实现逐个文件上传,复用项目的 httpRequest
+            async customUpload(file: File, insertFn: InsertFnType) {
+              try {
+                const res = await imageHttpRequest({
+                  file: file as any,
+                  onProgress: () => {},
+                  onSuccess: () => {},
+                  onError: () => {},
+                } as any)
+                // 兼容前端直连上传和后端上传两种模式的返回格式
+                const url = (res as any).data?.data || (res as any).data
+                insertFn(url, 'image', url)
+              } catch (error: any) {
+                ElMessage.error(error.msg || '图片上传失败')
+                console.error('Upload error:', error)
+              }
+            },
+          },
+          uploadVideo: {
+            // 单个文件的最大体积限制,默认为 10M
+            maxFileSize: 1024 * 1024 * 1024,
+            // 最多可上传几个文件,默认为 100
+            maxNumberOfFiles: 10,
+            // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
+            allowedFileTypes: ['video/*'],
+
+            // 使用 customUpload 实现逐个文件上传,复用项目的 httpRequest
+            async customUpload(file: File, insertFn: InsertFnType) {
+              try {
+                const res = await videoHttpRequest({
+                  file: file as any,
+                  onProgress: () => {},
+                  onSuccess: () => {},
+                  onError: () => {},
+                } as any)
+                // 兼容前端直连上传和后端上传两种模式的返回格式
+                const url = (res as any).data?.data || (res as any).data
+                insertFn(url, 'mp4', url)
+              } catch (error: any) {
+                ElMessage.error(error.msg || '视频上传失败')
+                console.error('Upload error:', error)
+              }
+            },
+          },
+        },
+        uploadImgShowBase64: true,
       },
-      uploadImgShowBase64: true
-    },
-    props.editorConfig || {}
-  )
-})
+      props.editorConfig || {}
+    )
+  })
 
-const editorStyle = computed(() => {
-  return {
-    height: isNumber(props.height) ? `${props.height}px` : props.height,
-    width: '100%'
-  }
-})
+  const editorStyle = computed(() => {
+    return { height: isNumber(props.height) ? `${props.height}px` : props.height, width: '100%' }
+  })
 
-// 回调函数
-const handleChange = (editor: IDomEditor) => {
-  emit('change', editor)
-}
+  // 回调函数
+  const handleChange = (editor: IDomEditor) => {
+    emit('change', editor)
+  }
 
-// 组件销毁时,及时销毁编辑器
-onBeforeUnmount(() => {
-  const editor = unref(editorRef.value)
+  // 组件销毁时,及时销毁编辑器
+  onBeforeUnmount(() => {
+    const editor = unref(editorRef.value)
 
-  // 销毁,并移除 editor
-  editor?.destroy()
-})
+    // 销毁,并移除 editor
+    editor?.destroy()
+  })
 
-const getEditorRef = async (): Promise<IDomEditor> => {
-  await nextTick()
-  return unref(editorRef.value) as IDomEditor
-}
+  const getEditorRef = async (): Promise<IDomEditor> => {
+    await nextTick()
+    return unref(editorRef.value) as IDomEditor
+  }
 
-defineExpose({
-  getEditorRef
-})
+  defineExpose({ getEditorRef })
 </script>
 
 <template>
@@ -214,8 +199,7 @@ defineExpose({
     <Toolbar
       :editor="editorRef"
       :editor-id="editorId"
-      class="border-0 b-b-1 border-[var(--tags-view-border-color)] border-solid"
-    />
+      class="border-0 b-b-1 border-[var(--tags-view-border-color)] border-solid" />
     <!-- 编辑器 -->
     <Editor
       v-model="valueHtml"
@@ -223,8 +207,7 @@ defineExpose({
       :editor-id="editorId"
       :style="editorStyle"
       @on-change="handleChange"
-      @on-created="handleCreated"
-    />
+      @on-created="handleCreated" />
   </div>
 </template>
 

+ 36 - 42
src/components/Error/src/Error.vue

@@ -1,48 +1,42 @@
 <script lang="ts" setup>
-import pageError from '@/assets/svgs/404.svg'
-import { propTypes } from '@/utils/propTypes'
-import networkError from '@/assets/svgs/500.svg'
-import noPermission from '@/assets/svgs/403.svg'
-
-defineOptions({ name: 'Error' })
-
-const props = defineProps({
-  type: propTypes.string.validate((v: string) => ['404', '500', '403'].includes(v)).def('404')
-})
-
-const emit = defineEmits(['errorClick'])
-
-interface ErrorMap {
-  url: string
-  message: string
-  buttonText: string
-}
-
-const { t } = useI18n()
-
-const errorMap: {
-  [key: string]: ErrorMap
-} = {
-  '404': {
-    url: pageError,
-    message: t('error.pageError'),
-    buttonText: t('error.returnToHome')
-  },
-  '500': {
-    url: networkError,
-    message: t('error.networkError'),
-    buttonText: t('error.returnToHome')
-  },
-  '403': {
-    url: noPermission,
-    message: t('error.noPermission'),
-    buttonText: t('error.returnToHome')
+  import pageError from '@/assets/svgs/404.svg'
+  import { propTypes } from '@/utils/propTypes'
+  import networkError from '@/assets/svgs/500.svg'
+  import noPermission from '@/assets/svgs/403.svg'
+
+  defineOptions({ name: 'Error' })
+
+  const props = defineProps({
+    type: propTypes.string.validate((v: string) => ['404', '500', '403'].includes(v)).def('404'),
+  })
+
+  const emit = defineEmits(['errorClick'])
+
+  interface ErrorMap {
+    url: string
+    message: string
+    buttonText: string
   }
-}
 
-const btnClick = () => {
-  emit('errorClick', props.type)
-}
+  const { t } = useI18n()
+
+  const errorMap: { [key: string]: ErrorMap } = {
+    '404': { url: pageError, message: t('error.pageError'), buttonText: t('error.returnToHome') },
+    '500': {
+      url: networkError,
+      message: t('error.networkError'),
+      buttonText: t('error.returnToHome'),
+    },
+    '403': {
+      url: noPermission,
+      message: t('error.noPermission'),
+      buttonText: t('error.returnToHome'),
+    },
+  }
+
+  const btnClick = () => {
+    emit('errorClick', props.type)
+  }
 </script>
 
 <template>

+ 269 - 289
src/components/Form/src/Form.vue

@@ -1,312 +1,292 @@
 <script lang="tsx">
-import type { PropType} from 'vue';
-
-import { set } from 'lodash-es'
-import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
-import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
-
-import type { FormSchema, FormSetPropsType } from '@/types/form'
-
-import { findIndex } from '@/utils'
-import { Icon } from '@/components/Icon'
-import { getSlot } from '@/utils/tsxHelper'
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
-
-import type { FormProps } from './types'
-
-import { componentMap } from './componentMap'
-import { useRenderRadio } from './components/useRenderRadio'
-import { useRenderSelect } from './components/useRenderSelect'
-import { useRenderCheckbox } from './components/useRenderCheckbox'
-import {
-  initModel,
-  setComponentProps,
-  setFormItemSlots,
-  setGridProp,
-  setItemComponentSlots,
-  setTextPlaceholder
-} from './helper'
-
-const { getPrefixCls } = useDesign()
-
-const prefixCls = getPrefixCls('form')
-
-export default defineComponent({
-  name: 'Form',
-  props: {
-    // 生成Form的布局结构数组
-    schema: {
-      type: Array as PropType<FormSchema[]>,
-      default: () => []
+  import type { PropType } from 'vue'
+
+  import { set } from 'lodash-es'
+  import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
+  import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
+
+  import type { FormSchema, FormSetPropsType } from '@/types/form'
+
+  import { findIndex } from '@/utils'
+  import { Icon } from '@/components/Icon'
+  import { getSlot } from '@/utils/tsxHelper'
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
+
+  import type { FormProps } from './types'
+
+  import { componentMap } from './componentMap'
+  import { useRenderRadio } from './components/useRenderRadio'
+  import { useRenderSelect } from './components/useRenderSelect'
+  import { useRenderCheckbox } from './components/useRenderCheckbox'
+  import {
+    initModel,
+    setComponentProps,
+    setFormItemSlots,
+    setGridProp,
+    setItemComponentSlots,
+    setTextPlaceholder,
+  } from './helper'
+
+  const { getPrefixCls } = useDesign()
+
+  const prefixCls = getPrefixCls('form')
+
+  export default defineComponent({
+    name: 'AppForm',
+    props: {
+      // 生成Form的布局结构数组
+      schema: { type: Array as PropType<FormSchema[]>, default: () => [] },
+      // 是否需要栅格布局
+      // update by 芋艿:将 true 改成 false,因为项目更常用这种方式
+      isCol: propTypes.bool.def(false),
+      // 表单数据对象
+      model: { type: Object as PropType<Recordable>, default: () => ({}) },
+      // 是否自动设置placeholder
+      autoSetPlaceholder: propTypes.bool.def(true),
+      // 是否自定义内容
+      isCustom: propTypes.bool.def(false),
+      // 表单label宽度
+      labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
+      // 是否 loading 数据中 add by 芋艿
+      vLoading: propTypes.bool.def(false),
     },
-    // 是否需要栅格布局
-    // update by 芋艿:将 true 改成 false,因为项目更常用这种方式
-    isCol: propTypes.bool.def(false),
-    // 表单数据对象
-    model: {
-      type: Object as PropType<Recordable>,
-      default: () => ({})
-    },
-    // 是否自动设置placeholder
-    autoSetPlaceholder: propTypes.bool.def(true),
-    // 是否自定义内容
-    isCustom: propTypes.bool.def(false),
-    // 表单label宽度
-    labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
-    // 是否 loading 数据中 add by 芋艿
-    vLoading: propTypes.bool.def(false)
-  },
-  emits: ['register'],
-  setup(props, { slots, expose, emit }) {
-    // element form 实例
-    const elFormRef = ref<ComponentRef<typeof ElForm>>()
-
-    // useForm传入的props
-    const outsideProps = ref<FormProps>({})
-
-    const mergeProps = ref<FormProps>({})
-
-    const getProps = computed(() => {
-      const propsObj = { ...props }
-      Object.assign(propsObj, unref(mergeProps))
-      return propsObj
-    })
-
-    // 表单数据
-    const formModel = ref<Recordable>({})
-
-    onMounted(() => {
-      emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
-    })
-
-    // 对表单赋值
-    const setValues = (data: Recordable = {}) => {
-      formModel.value = Object.assign(unref(formModel), data)
-    }
-
-    const setProps = (props: FormProps = {}) => {
-      mergeProps.value = Object.assign(unref(mergeProps), props)
-      outsideProps.value = props
-    }
-
-    const delSchema = (field: string) => {
-      const { schema } = unref(getProps)
-
-      const index = findIndex(schema, (v: FormSchema) => v.field === field)
-      if (index > -1) {
-        schema.splice(index, 1)
+    emits: ['register'],
+    setup(props, { slots, expose, emit }) {
+      // element form 实例
+      const elFormRef = ref<ComponentRef<typeof ElForm>>()
+
+      // useForm传入的props
+      const outsideProps = ref<FormProps>({})
+
+      const mergeProps = ref<FormProps>({})
+
+      const getProps = computed(() => {
+        const propsObj = { ...props }
+        Object.assign(propsObj, unref(mergeProps))
+        return propsObj
+      })
+
+      // 表单数据
+      const formModel = ref<Recordable>({})
+
+      onMounted(() => {
+        emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
+      })
+
+      // 对表单赋值
+      const setValues = (data: Recordable = {}) => {
+        formModel.value = Object.assign(unref(formModel), data)
+      }
+
+      const setProps = (formProps: FormProps = {}) => {
+        mergeProps.value = Object.assign(unref(mergeProps), formProps)
+        outsideProps.value = formProps
+      }
+
+      const delSchema = (field: string) => {
+        const { schema } = unref(getProps)
+
+        const index = findIndex(schema, (v: FormSchema) => v.field === field)
+        if (index > -1) {
+          schema.splice(index, 1)
+        }
       }
-    }
 
-    const addSchema = (formSchema: FormSchema, index?: number) => {
-      const { schema } = unref(getProps)
-      if (index !== void 0) {
-        schema.splice(index, 0, formSchema)
-        return
+      const addSchema = (formSchema: FormSchema, index?: number) => {
+        const { schema } = unref(getProps)
+        if (index !== void 0) {
+          schema.splice(index, 0, formSchema)
+          return
+        }
+        schema.push(formSchema)
       }
-      schema.push(formSchema)
-    }
-
-    const setSchema = (schemaProps: FormSetPropsType[]) => {
-      const { schema } = unref(getProps)
-      for (const v of schema) {
-        for (const item of schemaProps) {
-          if (v.field === item.field) {
-            set(v, item.path, item.value)
+
+      const setSchema = (schemaProps: FormSetPropsType[]) => {
+        const { schema } = unref(getProps)
+        for (const v of schema) {
+          for (const item of schemaProps) {
+            if (v.field === item.field) {
+              set(v, item.path, item.value)
+            }
           }
         }
       }
-    }
-
-    const getElFormRef = (): ComponentRef<typeof ElForm> => {
-      return unref(elFormRef) as ComponentRef<typeof ElForm>
-    }
-
-    expose({
-      setValues,
-      formModel,
-      setProps,
-      delSchema,
-      addSchema,
-      setSchema,
-      getElFormRef
-    })
-
-    // 监听表单结构化数组,重新生成formModel
-    watch(
-      () => unref(getProps).schema,
-      (schema = []) => {
-        formModel.value = initModel(schema, unref(formModel))
-      },
-      {
-        immediate: true,
-        deep: true
+
+      const getElFormRef = (): ComponentRef<typeof ElForm> => {
+        return unref(elFormRef) as ComponentRef<typeof ElForm>
       }
-    )
-
-    // 渲染包裹标签,是否使用栅格布局
-    const renderWrap = () => {
-      const { isCol } = unref(getProps)
-      const content = isCol ? (
-        <ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
-      ) : (
-        renderFormItemWrap()
+
+      expose({ setValues, formModel, setProps, delSchema, addSchema, setSchema, getElFormRef })
+
+      // 监听表单结构化数组,重新生成formModel
+      watch(
+        () => unref(getProps).schema,
+        (schema = []) => {
+          formModel.value = initModel(schema, unref(formModel))
+        },
+        { immediate: true, deep: true }
       )
-      return content
-    }
-
-    // 是否要渲染el-col
-    const renderFormItemWrap = () => {
-      // hidden属性表示隐藏,不做渲染
-      const { schema = [], isCol } = unref(getProps)
-
-      return schema
-        .filter((v) => !v.hidden)
-        .map((item) => {
-          // 如果是 Divider 组件,需要自己占用一行
-          const isDivider = item.component === 'Divider'
-          const Com = componentMap.Divider as ReturnType<typeof defineComponent>
-          return isDivider ? (
-            <Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
-          ) : isCol ? (
-            // 如果需要栅格,需要包裹 ElCol
-            <ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
-          ) : (
-            renderFormItem(item)
-          )
-        })
-    }
-
-    // 渲染formItem
-    const renderFormItem = (item: FormSchema) => {
-      // 单独给只有options属性的组件做判断
-      const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
-      const slotsMap: Recordable = {
-        ...setItemComponentSlots(slots, item?.componentProps?.slots, item.field)
+
+      // 渲染options
+      const renderOptions = (item: FormSchema) => {
+        switch (item.component) {
+          case 'Select':
+          case 'SelectV2':
+            const { renderSelectOptions } = useRenderSelect(slots)
+            return renderSelectOptions(item)
+          case 'Radio':
+          case 'RadioButton':
+            const { renderRadioOptions } = useRenderRadio()
+            return renderRadioOptions(item)
+          case 'Checkbox':
+          case 'CheckboxButton':
+            const { renderCheckboxOptions } = useRenderCheckbox()
+            return renderCheckboxOptions(item)
+          default:
+            break
+        }
+      }
+
+      // 渲染formItem
+      const renderFormItem = (item: FormSchema) => {
+        // 单独给只有options属性的组件做判断
+        const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
+        const slotsMap: Recordable = {
+          ...setItemComponentSlots(slots, item?.componentProps?.slots, item.field),
+        }
+        if (
+          item?.component !== 'SelectV2'
+          && item?.component !== 'Cascader'
+          && item?.componentProps?.options
+        ) {
+          slotsMap.default = () => renderOptions(item)
+        }
+
+        const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
+        // 如果有 labelMessage,自动使用插槽渲染
+        if (item?.labelMessage) {
+          formItemSlots.label = () => {
+            return (
+              <>
+                <span>{item.label}</span>
+                <ElTooltip placement='right' raw-content>
+                  {{
+                    content: () => <span v-dompurify-html={item.labelMessage}></span>,
+                    default: () => (
+                      <Icon
+                        icon='ep:warning'
+                        size={16}
+                        color='var(--el-color-primary)'
+                        class='ml-2px top-1px relative'></Icon>
+                    ),
+                  }}
+                </ElTooltip>
+              </>
+            )
+          }
+        }
+        return (
+          <ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
+            {{
+              ...formItemSlots,
+              default: () => {
+                const Com = componentMap[item.component as string] as ReturnType<
+                  typeof defineComponent
+                >
+
+                const { autoSetPlaceholder } = unref(getProps)
+
+                return slots[item.field] ? (
+                  getSlot(slots, item.field, formModel.value)
+                ) : (
+                  <Com
+                    vModel={formModel.value[item.field]}
+                    {...(autoSetPlaceholder && setTextPlaceholder(item))}
+                    {...setComponentProps(item)}
+                    style={item.componentProps?.style}
+                    {...(notRenderOptions.includes(item?.component as string)
+                    && item?.componentProps?.options
+                      ? { options: item?.componentProps?.options || [] }
+                      : {})}>
+                    {{ ...slotsMap }}
+                  </Com>
+                )
+              },
+            }}
+          </ElFormItem>
+        )
       }
-      if (
-        item?.component !== 'SelectV2' &&
-        item?.component !== 'Cascader' &&
-        item?.componentProps?.options
-      ) {
-        slotsMap.default = () => renderOptions(item)
+
+      // 是否要渲染el-col
+      const renderFormItemWrap = () => {
+        // hidden属性表示隐藏,不做渲染
+        const { schema = [], isCol } = unref(getProps)
+
+        return schema
+          .filter(v => !v.hidden)
+          .map(item => {
+            // 如果是 Divider 组件,需要自己占用一行
+            const isDivider = item.component === 'Divider'
+            const Com = componentMap.Divider as ReturnType<typeof defineComponent>
+            return isDivider ? (
+              <Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
+            ) : isCol ? (
+              // 如果需要栅格,需要包裹 ElCol
+              <ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
+            ) : (
+              renderFormItem(item)
+            )
+          })
+      }
+
+      // 渲染包裹标签,是否使用栅格布局
+      const renderWrap = () => {
+        const { isCol } = unref(getProps)
+        const content = isCol ? (
+          <ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
+        ) : (
+          renderFormItemWrap()
+        )
+        return content
       }
 
-      const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
-      // 如果有 labelMessage,自动使用插槽渲染
-      if (item?.labelMessage) {
-        formItemSlots.label = () => {
-          return (
-            <>
-              <span>{item.label}</span>
-              <ElTooltip placement="right" raw-content>
-                {{
-                  content: () => <span v-dompurify-html={item.labelMessage}></span>,
-                  default: () => (
-                    <Icon
-                      icon="ep:warning"
-                      size={16}
-                      color="var(--el-color-primary)"
-                      class="ml-2px top-1px relative"
-                    ></Icon>
-                  )
-                }}
-              </ElTooltip>
-            </>
-          )
+      // 过滤传入Form组件的属性
+      const getFormBindValue = () => {
+        // 避免在标签上出现多余的属性
+        const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
+        const formProps = { ...unref(getProps) }
+        for (const key in formProps) {
+          if (delKeys.includes(key)) {
+            delete formProps[key]
+          }
         }
+        return formProps
       }
-      return (
-        <ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
+
+      return () => (
+        <ElForm
+          ref={elFormRef}
+          {...getFormBindValue()}
+          model={props.isCustom ? props.model : formModel}
+          class={prefixCls}
+          v-loading={props.vLoading}>
           {{
-            ...formItemSlots,
+            // 如果需要自定义,就什么都不渲染,而是提供默认插槽
             default: () => {
-              const Com = componentMap[item.component as string] as ReturnType<
-                typeof defineComponent
-              >
-
-              const { autoSetPlaceholder } = unref(getProps)
-
-              return slots[item.field] ? (
-                getSlot(slots, item.field, formModel.value)
-              ) : (
-                <Com
-                  vModel={formModel.value[item.field]}
-                  {...(autoSetPlaceholder && setTextPlaceholder(item))}
-                  {...setComponentProps(item)}
-                  style={item.componentProps?.style}
-                  {...(notRenderOptions.includes(item?.component as string) &&
-                  item?.componentProps?.options
-                    ? { options: item?.componentProps?.options || [] }
-                    : {})}
-                >
-                  {{ ...slotsMap }}
-                </Com>
-              )
-            }
+              const { isCustom } = unref(getProps)
+              return isCustom ? getSlot(slots, 'default') : renderWrap()
+            },
           }}
-        </ElFormItem>
+        </ElForm>
       )
-    }
-
-    // 渲染options
-    const renderOptions = (item: FormSchema) => {
-      switch (item.component) {
-        case 'Select':
-        case 'SelectV2':
-          const { renderSelectOptions } = useRenderSelect(slots)
-          return renderSelectOptions(item)
-        case 'Radio':
-        case 'RadioButton':
-          const { renderRadioOptions } = useRenderRadio()
-          return renderRadioOptions(item)
-        case 'Checkbox':
-        case 'CheckboxButton':
-          const { renderCheckboxOptions } = useRenderCheckbox()
-          return renderCheckboxOptions(item)
-        default:
-          break
-      }
-    }
-
-    // 过滤传入Form组件的属性
-    const getFormBindValue = () => {
-      // 避免在标签上出现多余的属性
-      const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
-      const props = { ...unref(getProps) }
-      for (const key in props) {
-        if (delKeys.includes(key)) {
-          delete props[key]
-        }
-      }
-      return props
-    }
-
-    return () => (
-      <ElForm
-        ref={elFormRef}
-        {...getFormBindValue()}
-        model={props.isCustom ? props.model : formModel}
-        class={prefixCls}
-        v-loading={props.vLoading}
-      >
-        {{
-          // 如果需要自定义,就什么都不渲染,而是提供默认插槽
-          default: () => {
-            const { isCustom } = unref(getProps)
-            return isCustom ? getSlot(slots, 'default') : renderWrap()
-          }
-        }}
-      </ElForm>
-    )
-  }
-})
+    },
+  })
 </script>
 
 <style lang="scss" scoped>
-.#{$elNamespace}-form.#{$namespace}-form .#{$elNamespace}-row {
-  margin-right: 0 !important;
-  margin-left: 0 !important;
-}
+  .#{$elNamespace}-form.#{$namespace}-form .#{$elNamespace}-row {
+    margin-right: 0 !important;
+    margin-left: 0 !important;
+  }
 </style>

+ 2 - 2
src/components/Form/src/componentMap.ts

@@ -18,7 +18,7 @@ import {
   ElTimePicker,
   ElTimeSelect,
   ElTransfer,
-  ElTreeSelect
+  ElTreeSelect,
 } from 'element-plus'
 
 import type { ComponentName } from '@/types/components'
@@ -52,7 +52,7 @@ const componentMap: Recordable<Component, ComponentName> = {
   Editor,
   UploadImg,
   UploadImgs,
-  UploadFile
+  UploadFile,
 }
 
 export { componentMap }

+ 2 - 4
src/components/Form/src/components/useRenderCheckbox.tsx

@@ -12,7 +12,7 @@ export const useRenderCheckbox = () => {
     const Com = (item.component === 'Checkbox' ? ElCheckbox : ElCheckboxButton) as ReturnType<
       typeof defineComponent
     >
-    return item?.componentProps?.options?.map((option) => {
+    return item?.componentProps?.options?.map(option => {
       const { ...other } = option
       return (
         <Com {...other} label={option[valueAlias || 'value']}>
@@ -22,7 +22,5 @@ export const useRenderCheckbox = () => {
     })
   }
 
-  return {
-    renderCheckboxOptions
-  }
+  return { renderCheckboxOptions }
 }

+ 2 - 4
src/components/Form/src/components/useRenderRadio.tsx

@@ -12,7 +12,7 @@ export const useRenderRadio = () => {
     const Com = (item.component === 'Radio' ? ElRadio : ElRadioButton) as ReturnType<
       typeof defineComponent
     >
-    return item?.componentProps?.options?.map((option) => {
+    return item?.componentProps?.options?.map(option => {
       const { ...other } = option
       return (
         <Com {...other} label={option[valueAlias || 'value']}>
@@ -22,7 +22,5 @@ export const useRenderRadio = () => {
     })
   }
 
-  return {
-    renderRadioOptions
-  }
+  return { renderRadioOptions }
 }

+ 23 - 27
src/components/Form/src/components/useRenderSelect.tsx

@@ -8,27 +8,6 @@ import type { ComponentOptions } from '@/types/components'
 import { getSlot } from '@/utils/tsxHelper'
 
 export const useRenderSelect = (slots: Slots) => {
-  // 渲染 select options
-  const renderSelectOptions = (item: FormSchema) => {
-    // 如果有别名,就取别名
-    const labelAlias = item?.componentProps?.optionsAlias?.labelField
-    return item?.componentProps?.options?.map((option) => {
-      if (option?.options?.length) {
-        return (
-          <ElOptionGroup label={option[labelAlias || 'label']}>
-            {() => {
-              return option?.options?.map((v) => {
-                return renderSelectOptionItem(item, v)
-              })
-            }}
-          </ElOptionGroup>
-        )
-      } else {
-        return renderSelectOptionItem(item, option)
-      }
-    })
-  }
-
   // 渲染 select option item
   const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => {
     // 如果有别名,就取别名
@@ -41,20 +20,37 @@ export const useRenderSelect = (slots: Slots) => {
       <ElOption
         {...other}
         label={labelAlias ? option[labelAlias] : label}
-        value={valueAlias ? option[valueAlias] : value}
-      >
+        value={valueAlias ? option[valueAlias] : value}>
         {{
           default: () =>
             // option 插槽名规则,{field}-option
             item?.componentProps?.optionsSlot
               ? getSlot(slots, `${item.field}-option`, { item: option })
-              : undefined
+              : undefined,
         }}
       </ElOption>
     )
   }
-
-  return {
-    renderSelectOptions
+  // 渲染 select options
+  const renderSelectOptions = (item: FormSchema) => {
+    // 如果有别名,就取别名
+    const labelAlias = item?.componentProps?.optionsAlias?.labelField
+    return item?.componentProps?.options?.map(option => {
+      if (option?.options?.length) {
+        return (
+          <ElOptionGroup label={option[labelAlias || 'label']}>
+            {() => {
+              return option?.options?.map(v => {
+                return renderSelectOptionItem(item, v)
+              })
+            }}
+          </ElOptionGroup>
+        )
+      } else {
+        return renderSelectOptionItem(item, option)
+      }
+    })
   }
+
+  return { renderSelectOptions }
 }

+ 7 - 22
src/components/Form/src/helper.ts

@@ -18,9 +18,7 @@ export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
   const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword']
   const selectMap = ['Select', 'SelectV2', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
   if (textMap.includes(schema?.component as string)) {
-    return {
-      placeholder: t('common.inputText') + schema.label
-    }
+    return { placeholder: t('common.inputText') + schema.label }
   }
   if (selectMap.includes(schema?.component as string)) {
     // 一些范围选择器
@@ -33,12 +31,10 @@ export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
       return {
         startPlaceholder: t('common.startTimeText'),
         endPlaceholder: t('common.endTimeText'),
-        rangeSeparator: '-'
+        rangeSeparator: '-',
       }
     } else {
-      return {
-        placeholder: t('common.selectText') + schema.label
-      }
+      return { placeholder: t('common.selectText') + schema.label }
     }
   }
   return {}
@@ -53,16 +49,8 @@ export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
 export const setGridProp = (col: ColProps = {}): ColProps => {
   const colProps: ColProps = {
     // 如果有span,代表用户优先级更高,所以不需要默认栅格
-    ...(col.span
-      ? {}
-      : {
-          xs: 24,
-          sm: 12,
-          md: 12,
-          lg: 12,
-          xl: 12
-        }),
-    ...col
+    ...(col.span ? {} : { xs: 24, sm: 12, md: 12, lg: 12, xl: 12 }),
+    ...col,
   }
   return colProps
 }
@@ -76,10 +64,7 @@ export const setComponentProps = (item: FormSchema): Recordable => {
   const notNeedClearable = ['ColorPicker']
   const componentProps: Recordable = notNeedClearable.includes(item.component as string)
     ? { ...item.componentProps }
-    : {
-        clearable: true,
-        ...item.componentProps
-      }
+    : { clearable: true, ...item.componentProps }
   // 需要删除额外的属性
   delete componentProps?.slots
   return componentProps
@@ -117,7 +102,7 @@ export const setItemComponentSlots = (
  */
 export const initModel = (schema: FormSchema[], formModel: Recordable) => {
   const model: Recordable = { ...formModel }
-  schema.map((v) => {
+  schema.forEach(v => {
     // 如果是hidden,就删除对应的值
     if (v.hidden) {
       delete model[v.field]

+ 0 - 4
src/components/FormCreate/index.ts

@@ -1,4 +0,0 @@
-import { useApiSelect } from './src/components/useApiSelect'
-import { useFormCreateDesigner } from './src/useFormCreateDesigner'
-
-export { useApiSelect, useFormCreateDesigner }

+ 0 - 138
src/components/FormCreate/src/components/AreaSelect.vue

@@ -1,138 +0,0 @@
-<!-- 省市区选择器 (Element Plus 版本 - Vue3) -->
-<script lang="ts" setup>
-import { onMounted, ref, watch } from 'vue'
-
-import { getAreaTree } from '@/api/system/area'
-import { AreaLevelEnum } from '@/utils/constants'
-
-defineOptions({ name: 'AreaSelect' })
-
-const props = withDefaults(defineProps<Props>(), {
-  modelValue: undefined,
-  level: AreaLevelEnum.DISTRICT,
-  disabled: false,
-  placeholder: '请选择省市区',
-  clearable: true,
-  showAllLevels: true,
-  separator: '/'
-})
-
-const emit = defineEmits<{
-  (e: 'update:modelValue', value: number[] | string[] | undefined): void
-}>()
-
-interface AreaVO {
-  id: number
-  name: string
-  code: string
-  parentId?: number
-  sort?: number
-  status?: number
-  children?: AreaVO[]
-}
-
-interface Props {
-  modelValue?: number[] | string[]
-  level?: (typeof AreaLevelEnum)[keyof typeof AreaLevelEnum]
-  disabled?: boolean
-  placeholder?: string
-  clearable?: boolean
-  showAllLevels?: boolean
-  separator?: string
-  formCreateInject?: any
-}
-
-const cascaderProps = {
-  label: 'name',
-  value: 'id',
-  children: 'children',
-  checkStrictly: true, // 允许选择任意级别
-  emitPath: true // 返回完整路径
-} // Element Plus Cascader 的 props 配置
-
-const areaTree = ref<AreaVO[]>([]) // 地区树形数据
-const selectedValue = ref<number[] | undefined>() // 当前选中值
-const loading = ref(false) // 加载状态
-
-/** 加载地区树形数据 */
-async function loadAreaTree(): Promise<void> {
-  try {
-    loading.value = true
-    const data = await getAreaTree()
-    // 根据 level 限制层级
-    areaTree.value = filterTreeByLevel(data || [], props.level)
-  } catch (error) {
-    console.warn('[AreaSelect] 加载地区数据失败:', error)
-    areaTree.value = []
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 根据层级过滤树形数据 */
-function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
-  if (maxLevel <= 0) {
-    return []
-  }
-  return tree.map((node) => {
-    const newNode = { ...node }
-    // 如果当前是最后一层,移除 children
-    if (maxLevel === 1) {
-      delete newNode.children
-    } else if (node.children && node.children.length > 0) {
-      // 递归处理子节点
-      newNode.children = filterTreeByLevel(node.children, maxLevel - 1)
-    }
-    return newNode
-  })
-}
-
-/** 处理选中值变化 */
-function handleChange(value: number[] | undefined): void {
-  if (value === undefined || value === null) {
-    emit('update:modelValue', undefined)
-    return
-  }
-  emit('update:modelValue', value)
-}
-
-/** 同步 modelValue 到内部选中值 */
-function syncSelectedValue(): void {
-  const newValue = props.modelValue
-  if (newValue === undefined || newValue === null) {
-    selectedValue.value = undefined
-    return
-  }
-
-  // 确保是数组格式
-  if (Array.isArray(newValue)) {
-    selectedValue.value = newValue as number[]
-  } else {
-    selectedValue.value = [newValue as number]
-  }
-}
-
-/** 监听 modelValue 变化 */
-watch(() => props.modelValue, syncSelectedValue, { immediate: true })
-
-/** 组件挂载时加载数据 */
-onMounted(async () => {
-  await loadAreaTree()
-})
-</script>
-
-<template>
-  <el-cascader
-    v-model="selectedValue"
-    class="w-full"
-    :options="areaTree"
-    :props="cascaderProps"
-    :disabled="disabled"
-    :placeholder="placeholder"
-    :clearable="clearable"
-    :show-all-levels="showAllLevels"
-    :separator="separator"
-    :loading="loading"
-    @change="handleChange"
-  />
-</template>

+ 0 - 192
src/components/FormCreate/src/components/DeptSelect.vue

@@ -1,192 +0,0 @@
-<!-- 部门选择器 - 树形结构显示 -->
-<script lang="ts" setup>
-import type {DeptVO} from '@/api/system/dept';
-
-import { handleTree } from '@/utils/tree'
-import {  getSimpleDeptList } from '@/api/system/dept'
-import { useUserStoreWithOut } from '@/store/modules/user'
-
-defineOptions({ name: 'DeptSelect' })
-
-const props = withDefaults(defineProps<Props>(), {
-  multiple: false,
-  returnType: 'id',
-  defaultCurrentDept: false,
-  disabled: false,
-  placeholder: ''
-})
-
-const emit = defineEmits<{
-  (e: 'update:modelValue', value: number | string | number[] | string[] | undefined): void
-}>()
-
-// 接受父组件参数
-interface Props {
-  modelValue?: number | string | number[] | string[]
-  multiple?: boolean
-  returnType?: 'id' | 'name'
-  defaultCurrentDept?: boolean
-  disabled?: boolean
-  placeholder?: string
-  formCreateInject?: any
-}
-
-// 树形选择器配置
-const treeProps = {
-  label: 'name',
-  value: 'id',
-  children: 'children'
-}
-
-// 部门树形数据
-const deptTree = ref<any[]>([])
-// 原始部门列表(用于 returnType='name' 时查找名称)
-const deptList = ref<DeptVO[]>([])
-// 当前选中值
-const selectedValue = ref<number | string | number[] | string[] | undefined>()
-
-// 加载部门树形数据
-const loadDeptTree = async () => {
-  try {
-    const data = await getSimpleDeptList()
-    deptList.value = data
-    deptTree.value = handleTree(data)
-  } catch (error) {
-    console.warn('加载部门数据失败:', error)
-    deptTree.value = []
-  }
-}
-
-// 根据 ID 获取部门名称
-const getDeptNameById = (id: number): string | undefined => {
-  const dept = deptList.value.find((item) => item.id === id)
-  return dept?.name
-}
-
-// 根据名称获取部门 ID
-const getDeptIdByName = (name: string): number | undefined => {
-  const dept = deptList.value.find((item) => item.name === name)
-  return dept?.id
-}
-
-// 处理选中值变化
-const handleChange = (value: number | number[] | undefined) => {
-  if (value === undefined || value === null) {
-    emit('update:modelValue', props.multiple ? [] : undefined)
-    return
-  }
-
-  // 根据 returnType 决定返回值类型
-  if (props.returnType === 'name') {
-    if (props.multiple && Array.isArray(value)) {
-      const names = value.map((id) => getDeptNameById(id)).filter(Boolean) as string[]
-      emit('update:modelValue', names)
-    } else if (!props.multiple && typeof value === 'number') {
-      const name = getDeptNameById(value)
-      emit('update:modelValue', name)
-    }
-  } else {
-    emit('update:modelValue', value)
-  }
-}
-
-// 树节点过滤方法(支持搜索过滤)
-const filterNode = (value: string, data: any) => {
-  if (!value) return true
-  return data.name.includes(value)
-}
-
-// 监听 modelValue 变化,同步到内部选中值
-watch(
-  () => props.modelValue,
-  (newValue) => {
-    if (newValue === undefined || newValue === null) {
-      selectedValue.value = props.multiple ? [] : undefined
-      return
-    }
-
-    // 如果 returnType 是 'name',需要将名称转换为 ID 用于树选择器显示
-    if (props.returnType === 'name') {
-      if (props.multiple && Array.isArray(newValue)) {
-        const ids = (newValue as string[])
-          .map((name) => getDeptIdByName(name))
-          .filter(Boolean) as number[]
-        selectedValue.value = ids
-      } else if (!props.multiple && typeof newValue === 'string') {
-        const id = getDeptIdByName(newValue)
-        selectedValue.value = id
-      }
-    } else {
-      selectedValue.value = newValue as number | number[]
-    }
-  },
-  { immediate: true }
-)
-
-// 检查是否有有效的预设值
-const hasValidPresetValue = (): boolean => {
-  const value = props.modelValue
-  if (value === undefined || value === null || value === '') {
-    return false
-  }
-  if (Array.isArray(value)) {
-    return value.length > 0
-  }
-  return true
-}
-
-// 是否处于表单设计器中:FcDesigner 会向其内部组件 provide('designer'),运行时表单无此注入
-const designerCtx = inject('designer', null)
-
-// 设置默认值(当前用户部门)
-const setDefaultValue = () => {
-  // 仅当 defaultCurrentDept 为 true 时处理
-  if (!props.defaultCurrentDept) return
-
-  // 表单设计器中不设置动态默认值:否则 emit 出去的值会被设计器双向绑定回写到 rule.value,
-  // 随表单设计持久化后,运行时其他用户会拿到这个被污染的固定值,导致默认部门不再跟随当前登录用户。
-  // 仅在「运行时」才设置. 不过会导致表单设计器预览时,无法看到默认值
-  if (designerCtx !== null) return
-
-  // 已有预设值则保留(优先级高于默认当前部门):用于审批回显时保留发起人填写的真实值
-  if (hasValidPresetValue()) return
-
-  // 获取当前用户的部门 ID
-  const userStore = useUserStoreWithOut()
-  const user = userStore.getUser
-  const deptId = user?.deptId
-
-  // 处理 deptId 为空或 0 的边界情况
-  if (!deptId || deptId === 0) return
-
-  // 根据多选模式决定默认值格式
-  const defaultValue = props.multiple ? [deptId] : deptId
-  emit('update:modelValue', defaultValue)
-}
-
-// 组件挂载时加载数据并设置默认值
-onMounted(async () => {
-  await loadDeptTree()
-  // 数据加载完成后设置默认值
-  setDefaultValue()
-})
-</script>
-
-<template>
-  <el-tree-select
-    v-model="selectedValue"
-    class="w-1/1"
-    :data="deptTree"
-    :props="treeProps"
-    :multiple="multiple"
-    :disabled="disabled"
-    :placeholder="placeholder || '请选择部门'"
-    :check-strictly="true"
-    :filterable="true"
-    :filter-node-method="filterNode"
-    :clearable="true"
-    :render-after-expand="false"
-    node-key="id"
-    @change="handleChange"
-  />
-</template>

+ 0 - 96
src/components/FormCreate/src/components/DictSelect.vue

@@ -1,96 +0,0 @@
-<!-- 数据字典 Select 选择器 -->
-<script lang="ts" setup>
-import { getBoolDictOptions, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
-
-defineOptions({ name: 'DictSelect' })
-
-const props = withDefaults(defineProps<Props>(), {
-  valueType: 'str',
-  selectType: 'select'
-})
-
-const emit = defineEmits<{
-  (e: 'update:modelValue', value: any): void
-}>()
-
-const attrs = useAttrs()
-
-// 接受父组件参数
-interface Props {
-  dictType: string // 字典类型
-  modelValue?: any // 选中值,由 form-create 默认 modelValue 绑定
-  valueType?: 'str' | 'int' | 'bool' // 字典值类型
-  selectType?: 'select' | 'radio' | 'checkbox' // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
-  formCreateInject?: any
-}
-
-const selectedValue = ref<any>()
-
-watch(
-  () => props.modelValue,
-  (newValue) => {
-    selectedValue.value = newValue
-  },
-  { immediate: true }
-)
-
-// 获得字典配置
-const getDictOptions = computed(() => {
-  switch (props.valueType) {
-    case 'str':
-      return getStrDictOptions(props.dictType)
-    case 'int':
-      return getIntDictOptions(props.dictType)
-    case 'bool':
-      return getBoolDictOptions(props.dictType)
-    default:
-      return []
-  }
-})
-
-const handleChange = (value: any) => {
-  emit('update:modelValue', value)
-}
-</script>
-
-<template>
-  <el-select
-    v-if="selectType === 'select'"
-    v-model="selectedValue"
-    class="w-1/1"
-    v-bind="attrs"
-    @change="handleChange"
-  >
-    <el-option
-      v-for="(dict, index) in getDictOptions"
-      :key="index"
-      :label="dict.label"
-      :value="dict.value"
-    />
-  </el-select>
-  <el-radio-group
-    v-if="selectType === 'radio'"
-    v-model="selectedValue"
-    class="w-1/1"
-    v-bind="attrs"
-    @change="handleChange"
-  >
-    <el-radio v-for="(dict, index) in getDictOptions" :key="index" :value="dict.value">
-      {{ dict.label }}
-    </el-radio>
-  </el-radio-group>
-  <el-checkbox-group
-    v-if="selectType === 'checkbox'"
-    v-model="selectedValue"
-    class="w-1/1"
-    v-bind="attrs"
-    @change="handleChange"
-  >
-    <el-checkbox
-      v-for="(dict, index) in getDictOptions"
-      :key="index"
-      :label="dict.label"
-      :value="dict.value"
-    />
-  </el-checkbox-group>
-</template>

+ 0 - 86
src/components/FormCreate/src/components/IframeComponent.vue

@@ -1,86 +0,0 @@
-<!-- 网页 iframe 组件 (Element Plus 版本 - Vue3) -->
-<script lang="ts" setup>
-import { computed } from 'vue'
-
-import { isUrl } from '@/utils/is'
-
-defineOptions({ name: 'IframeComponent' })
-
-const props = withDefaults(defineProps<Props>(), {
-  modelValue: '',
-  url: '',
-  height: '500px',
-  width: '100%',
-  frameborder: '0',
-  allowfullscreen: true,
-  loading: 'lazy',
-  sandbox: ''
-})
-
-interface Props {
-  modelValue?: string
-  url?: string
-  height?: string
-  width?: string
-  frameborder?: string
-  allowfullscreen?: boolean
-  loading?: 'eager' | 'lazy'
-  sandbox?: string
-  formCreateInject?: any
-}
-
-const displayUrl = computed(() => props.url || props.modelValue || '') // 显示的 URL(优先使用 url prop,其次使用 modelValue)
-const showPreview = computed(() => {
-  return displayUrl.value && isUrl(displayUrl.value)
-}) // 是否显示预览
-</script>
-
-<template>
-  <div class="iframe-component">
-    <!-- iframe 预览 -->
-    <div v-if="showPreview" class="iframe-preview">
-      <iframe
-        :src="displayUrl"
-        :width="width"
-        :height="height"
-        :frameborder="frameborder"
-        :allowfullscreen="allowfullscreen"
-        :loading="loading"
-        :sandbox="sandbox || undefined"
-        class="iframe-content"
-      ></iframe>
-    </div>
-
-    <!-- 无 URL 或无效 URL 提示 -->
-    <div v-else class="iframe-placeholder">
-      <el-empty description="请在右侧属性面板配置 URL 地址" />
-    </div>
-  </div>
-</template>
-
-<style scoped>
-.iframe-component {
-  width: 100%;
-}
-
-.iframe-preview {
-  overflow: hidden;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-}
-
-.iframe-content {
-  display: block;
-  border: none;
-}
-
-.iframe-placeholder {
-  display: flex;
-  min-height: 200px;
-  background-color: #fafafa;
-  border: 1px dashed #dcdfe6;
-  border-radius: 4px;
-  align-items: center;
-  justify-content: center;
-}
-</style>

+ 0 - 312
src/components/FormCreate/src/components/useApiSelect.tsx

@@ -1,312 +0,0 @@
-import type { ApiSelectProps } from '@/components/FormCreate/src/type'
-
-import { jsonParse } from '@/utils'
-import request from '@/config/axios'
-import { isEmpty } from '@/utils/is'
-import { getCurrentUserId } from '@/utils/auth'
-
-export const useApiSelect = (option: ApiSelectProps) => {
-  return defineComponent({
-    name: option.name,
-    props: {
-      // 选项标签
-      labelField: {
-        type: String,
-        default: () => option.labelField ?? 'label'
-      },
-      // 选项的值
-      valueField: {
-        type: String,
-        default: () => option.valueField ?? 'value'
-      },
-      // api 接口
-      url: {
-        type: String,
-        default: () => option.url ?? ''
-      },
-      // 请求类型
-      method: {
-        type: String,
-        default: 'GET'
-      },
-      // 选项解析函数
-      parseFunc: {
-        type: String,
-        default: ''
-      },
-      // 请求参数
-      data: {
-        type: String,
-        default: ''
-      },
-      // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
-      selectType: {
-        type: String,
-        default: 'select'
-      },
-      // 是否多选
-      multiple: {
-        type: Boolean,
-        default: false
-      },
-      // 是否远程搜索
-      remote: {
-        type: Boolean,
-        default: false
-      },
-      // 远程搜索时携带的参数
-      remoteField: {
-        type: String,
-        default: 'label'
-      },
-      // 返回值类型(用于部门选择器等):id 返回 ID,name 返回名称
-      returnType: {
-        type: String,
-        default: 'id'
-      },
-      // 是否默认选中当前用户(仅 UserSelect 使用)
-      defaultCurrentUser: {
-        type: Boolean,
-        default: false
-      }
-    },
-    setup(props, { emit }) {
-      const attrs = useAttrs()
-      const options = ref<any[]>([]) // 下拉数据
-      const loading = ref(false) // 是否正在从远程获取数据
-      const queryParam = ref<any>() // 当前输入的值
-
-      // 检查是否有有效的预设值
-      const hasValidPresetValue = (): boolean => {
-        const value = attrs.modelValue
-        if (value === undefined || value === null || value === '') {
-          return false
-        }
-        if (Array.isArray(value)) {
-          return value.length > 0
-        }
-        return true
-      }
-
-      // 设置默认当前用户(仅当 defaultCurrentUser 为 true 且无预设值时)
-      const setDefaultCurrentUser = () => {
-        // 仅当组件名为 UserSelect 且 defaultCurrentUser 为 true 时处理
-        if (option.name !== 'UserSelect' || !props.defaultCurrentUser) {
-          return
-        }
-        // 检查是否已有预设值(预设值优先级高于默认当前用户)
-        if (hasValidPresetValue()) {
-          return
-        }
-
-        // 获取当前用户 ID
-        const currentUserId = getCurrentUserId()
-        if (currentUserId) {
-          // 根据多选/单选模式设置默认值
-          const defaultValue = props.multiple ? [currentUserId] : currentUserId
-          emit('update:modelValue', defaultValue)
-        }
-      }
-
-      const getOptions = async () => {
-        options.value = []
-        // 接口选择器
-        if (isEmpty(props.url)) {
-          return
-        }
-
-        switch (props.method) {
-          case 'GET':
-            let url: string = props.url
-            if (props.remote) {
-              if (queryParam.value != undefined) {
-                if (url.includes('?')) {
-                  url = `${url}&${props.remoteField}=${queryParam.value}`
-                } else {
-                  url = `${url}?${props.remoteField}=${queryParam.value}`
-                }
-              }
-            }
-            parseOptions(await request.get({ url }))
-            break
-          case 'POST':
-            const data: any = jsonParse(props.data)
-            if (props.remote) {
-              data[props.remoteField] = queryParam.value
-            }
-            parseOptions(await request.post({ url: props.url, data }))
-            break
-        }
-      }
-
-      function parseOptions(data: any) {
-        //  情况一:如果有自定义解析函数优先使用自定义解析
-        if (!isEmpty(props.parseFunc)) {
-          options.value = parseFunc()?.(data)
-          return
-        }
-        // 情况二:返回的直接是一个列表
-        if (Array.isArray(data)) {
-          parseOptions0(data)
-          return
-        }
-        // 情况二:返回的是分页数据,尝试读取 list
-        data = data.list
-        if (!!data && Array.isArray(data)) {
-          parseOptions0(data)
-          return
-        }
-        // 情况三:不是 yudao-vue-pro 标准返回
-        console.warn(
-          `接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`
-        )
-      }
-
-      function parseOptions0(data: any[]) {
-        if (Array.isArray(data)) {
-          options.value = data.map((item: any) => {
-            const label = parseExpression(item, props.labelField)
-            let value = parseExpression(item, props.valueField)
-
-            // 根据 returnType 决定返回值
-            // 如果设置了 returnType 为 'name',则返回 label 作为 value
-            if (props.returnType === 'name') {
-              value = label
-            }
-
-            return {
-              label,
-              value
-            }
-          })
-          return
-        }
-        console.warn(`接口[${props.url}] 返回结果不是一个数组`)
-      }
-
-      function parseFunc() {
-        let parse: any = null
-        if (props.parseFunc) {
-          // 解析字符串函数
-          parse = new Function(`return ${props.parseFunc}`)()
-        }
-        return parse
-      }
-
-      function parseExpression(data: any, template: string) {
-        // 检测是否使用了表达式
-        if (!template.includes('${')) {
-          return data[template]
-        }
-        // 正则表达式匹配模板字符串中的 ${...}
-        const pattern = /\$\{([^}]*)\}/g
-        // 使用replace函数配合正则表达式和回调函数来进行替换
-        return template.replace(pattern, (_, expr) => {
-          // expr 是匹配到的 ${} 内的表达式(这里是属性名),从 data 中获取对应的值
-          const result = data[expr.trim()] // 去除前后空白,以防用户输入带空格的属性名
-          if (!result) {
-            console.warn(
-              `接口选择器选项模版[${template}][${expr.trim()}] 解析值失败结果为[${result}], 请检查属性名称是否存在于接口返回值中,存在则忽略此条!!!`
-            )
-          }
-          return result
-        })
-      }
-
-      const remoteMethod = async (query: any) => {
-        if (!query) {
-          return
-        }
-        loading.value = true
-        try {
-          queryParam.value = query
-          await getOptions()
-        } finally {
-          loading.value = false
-        }
-      }
-
-      onMounted(async () => {
-        await getOptions()
-        // 设置默认当前用户(在数据加载完成后)
-        setDefaultCurrentUser()
-      })
-
-      const buildSelect = () => {
-        if (props.multiple) {
-          // fix:多写此步是为了解决 multiple 属性问题
-          return (
-            <el-select
-              class="w-1/1"
-              multiple
-              loading={loading.value}
-              {...attrs}
-              remote={props.remote}
-              {...(props.remote && { remoteMethod })}
-            >
-              {options.value.map((item, index) => (
-                <el-option key={index} label={item.label} value={item.value} />
-              ))}
-            </el-select>
-          )
-        }
-        return (
-          <el-select
-            class="w-1/1"
-            loading={loading.value}
-            {...attrs}
-            remote={props.remote}
-            {...(props.remote && { remoteMethod })}
-          >
-            {options.value.map((item, index) => (
-              <el-option key={index} label={item.label} value={item.value} />
-            ))}
-          </el-select>
-        )
-      }
-      const buildCheckbox = () => {
-        if (isEmpty(options.value)) {
-          options.value = [
-            { label: '选项1', value: '选项1' },
-            { label: '选项2', value: '选项2' }
-          ]
-        }
-        return (
-          <el-checkbox-group class="w-1/1" {...attrs}>
-            {options.value.map((item, index) => (
-              <el-checkbox key={index} label={item.label} value={item.value} />
-            ))}
-          </el-checkbox-group>
-        )
-      }
-      const buildRadio = () => {
-        if (isEmpty(options.value)) {
-          options.value = [
-            { label: '选项1', value: '选项1' },
-            { label: '选项2', value: '选项2' }
-          ]
-        }
-        return (
-          <el-radio-group class="w-1/1" {...attrs}>
-            {options.value.map((item, index) => (
-              <el-radio key={index} value={item.value}>
-                {item.label}
-              </el-radio>
-            ))}
-          </el-radio-group>
-        )
-      }
-      return () => (
-        <>
-          {props.selectType === 'select'
-            ? buildSelect()
-            : props.selectType === 'radio'
-              ? buildRadio()
-              : props.selectType === 'checkbox'
-                ? buildCheckbox()
-                : buildSelect()}
-        </>
-      )
-    }
-  })
-}

+ 0 - 19
src/components/FormCreate/src/config/index.ts

@@ -1,19 +0,0 @@
-import { useEditorRule } from './useEditorRule'
-import { useSelectRule } from './useSelectRule'
-import { useIframeRule } from './useIframeRule'
-import { useUploadImgRule } from './useUploadImgRule'
-import { useUploadFileRule } from './useUploadFileRule'
-import { useUploadImgsRule } from './useUploadImgsRule'
-import { useDictSelectRule } from './useDictSelectRule'
-import { useAreaSelectRule } from './useAreaSelectRule'
-
-export {
-  useAreaSelectRule,
-  useDictSelectRule,
-  useEditorRule,
-  useIframeRule,
-  useSelectRule,
-  useUploadFileRule,
-  useUploadImgRule,
-  useUploadImgsRule
-}

+ 0 - 181
src/components/FormCreate/src/config/selectRule.ts

@@ -1,181 +0,0 @@
-const selectRule = [
-  {
-    type: 'select',
-    field: 'selectType',
-    title: '选择器类型',
-    value: 'select',
-    options: [
-      { label: '下拉框', value: 'select' },
-      { label: '单选框', value: 'radio' },
-      { label: '多选框', value: 'checkbox' }
-    ],
-    // 参考 https://www.form-create.com/v3/guide/control 组件联动,单选框和多选框不需要多选属性
-    control: [
-      {
-        value: 'select',
-        condition: '==',
-        method: 'hidden',
-        rule: [
-          'multiple',
-          'clearable',
-          'collapseTags',
-          'multipleLimit',
-          'allowCreate',
-          'filterable',
-          'noMatchText',
-          'remote',
-          'remoteMethod',
-          'reserveKeyword',
-          'defaultFirstOption',
-          'automaticDropdown'
-        ]
-      }
-    ]
-  },
-  {
-    type: 'switch',
-    field: 'filterable',
-    title: '是否可搜索'
-  },
-  { type: 'switch', field: 'multiple', title: '是否多选' },
-  {
-    type: 'switch',
-    field: 'disabled',
-    title: '是否禁用'
-  },
-  { type: 'switch', field: 'clearable', title: '是否可以清空选项' },
-  {
-    type: 'switch',
-    field: 'collapseTags',
-    title: '多选时是否将选中值按文字的形式展示'
-  },
-  {
-    type: 'inputNumber',
-    field: 'multipleLimit',
-    title: '多选时用户最多可以选择的项目数,为 0 则不限制',
-    props: { min: 0 }
-  },
-  {
-    type: 'input',
-    field: 'autocomplete',
-    title: 'autocomplete 属性'
-  },
-  { type: 'input', field: 'placeholder', title: '占位符' },
-  { type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' },
-  {
-    type: 'input',
-    field: 'noMatchText',
-    title: '搜索条件无匹配时显示的文字'
-  },
-  { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' },
-  {
-    type: 'switch',
-    field: 'reserveKeyword',
-    title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词'
-  },
-  {
-    type: 'switch',
-    field: 'defaultFirstOption',
-    title: '在输入框按下回车,选择第一个匹配项'
-  },
-  {
-    type: 'switch',
-    field: 'popperAppendToBody',
-    title: '是否将弹出框插入至 body 元素',
-    value: true
-  },
-  {
-    type: 'switch',
-    field: 'automaticDropdown',
-    title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单'
-  }
-]
-
-const apiSelectRule = [
-  {
-    type: 'input',
-    field: 'url',
-    title: 'url 地址',
-    props: {
-      placeholder: '/system/user/simple-list'
-    }
-  },
-  {
-    type: 'select',
-    field: 'method',
-    title: '请求类型',
-    value: 'GET',
-    options: [
-      { label: 'GET', value: 'GET' },
-      { label: 'POST', value: 'POST' }
-    ],
-    control: [
-      {
-        value: 'GET',
-        condition: '!=',
-        method: 'hidden',
-        rule: [
-          {
-            type: 'input',
-            field: 'data',
-            title: '请求参数 JSON 格式',
-            props: {
-              autosize: true,
-              type: 'textarea',
-              placeholder: '{"type": 1}'
-            }
-          }
-        ]
-      }
-    ]
-  },
-  {
-    type: 'input',
-    field: 'labelField',
-    title: 'label 属性',
-    info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
-    props: {
-      placeholder: 'nickname'
-    }
-  },
-  {
-    type: 'input',
-    field: 'valueField',
-    title: 'value 属性',
-    info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
-    props: {
-      placeholder: 'id'
-    }
-  },
-  {
-    type: 'input',
-    field: 'parseFunc',
-    title: '选项解析函数',
-    info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
-    (data: any)=>{ label: string; value: any }[]`,
-    props: {
-      autosize: true,
-      rows: { minRows: 2, maxRows: 6 },
-      type: 'textarea',
-      placeholder: `
-        function (data) {
-            console.log(data)
-            return data.list.map(item=> ({label: item.nickname,value: item.id}))
-        }`
-    }
-  },
-  {
-    type: 'switch',
-    field: 'remote',
-    info: '是否可搜索',
-    title: '其中的选项是否从服务器远程加载'
-  },
-  {
-    type: 'input',
-    field: 'remoteField',
-    title: '请求参数',
-    info: '远程请求时请求携带的参数名称,如:name'
-  }
-]
-
-export { apiSelectRule, selectRule }

+ 0 - 75
src/components/FormCreate/src/config/useAreaSelectRule.ts

@@ -1,75 +0,0 @@
-import { generateUUID } from '@/utils'
-import { AreaLevelEnum } from '@/utils/constants'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-/**
- * 省市区选择器规则
- */
-export const useAreaSelectRule = () => {
-  const label = '省市区选择器'
-  const name = 'AreaSelect'
-
-  return {
-    icon: 'icon-location',
-    label,
-    name,
-    rule() {
-      return {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-    },
-    props(_, { t }) {
-      return localeProps(t, `${name  }.props`, [
-        makeRequiredRule(),
-        {
-          type: 'select',
-          field: 'level',
-          title: '选择层级',
-          value: AreaLevelEnum.DISTRICT,
-          options: [
-            { label: '省', value: AreaLevelEnum.PROVINCE },
-            { label: '省/市', value: AreaLevelEnum.CITY },
-            { label: '省/市/区', value: AreaLevelEnum.DISTRICT }
-          ],
-          info: '限制可选择的地区层级'
-        },
-        {
-          type: 'input',
-          field: 'placeholder',
-          title: '占位符',
-          value: '请选择省市区'
-        },
-        {
-          type: 'switch',
-          field: 'clearable',
-          title: '是否可清空',
-          value: true
-        },
-        {
-          type: 'switch',
-          field: 'showAllLevels',
-          title: '显示完整路径',
-          value: true,
-          info: '输入框中是否显示选中值的完整路径'
-        },
-        {
-          type: 'input',
-          field: 'separator',
-          title: '分隔符',
-          value: '/',
-          info: '选项分隔符'
-        },
-        {
-          type: 'switch',
-          field: 'disabled',
-          title: '是否禁用',
-          value: false
-        }
-      ])
-    }
-  }
-}

+ 0 - 65
src/components/FormCreate/src/config/useDictSelectRule.ts

@@ -1,65 +0,0 @@
-import { cloneDeep } from 'lodash-es'
-
-import { generateUUID } from '@/utils'
-import * as DictDataApi from '@/api/system/dict/dict.type'
-import { selectRule } from '@/components/FormCreate/src/config/selectRule'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-/**
- * 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule
- */
-export const useDictSelectRule = () => {
-  const label = '字典选择器'
-  const name = 'DictSelect'
-  const rules = cloneDeep(selectRule)
-  const dictOptions = ref<{ label: string; value: string }[]>([]) // 字典类型下拉数据
-  onMounted(async () => {
-    const data = await DictDataApi.getSimpleDictTypeList()
-    if (!data || data.length === 0) {
-      return
-    }
-    dictOptions.value =
-      data?.map((item: DictDataApi.DictTypeVO) => ({
-        label: item.name,
-        value: item.type
-      })) ?? []
-  })
-  return {
-    icon: 'icon-doc-text',
-    label,
-    name,
-    rule() {
-      return {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-    },
-    props(_, { t }) {
-      return localeProps(t, `${name  }.props`, [
-        makeRequiredRule(),
-        {
-          type: 'select',
-          field: 'dictType',
-          title: '字典类型',
-          value: '',
-          options: dictOptions.value
-        },
-        {
-          type: 'select',
-          field: 'valueType',
-          title: '字典值类型',
-          value: 'str',
-          options: [
-            { label: '数字', value: 'int' },
-            { label: '字符串', value: 'str' },
-            { label: '布尔值', value: 'bool' }
-          ]
-        },
-        ...rules
-      ])
-    }
-  }
-}

+ 0 - 32
src/components/FormCreate/src/config/useEditorRule.ts

@@ -1,32 +0,0 @@
-import { generateUUID } from '@/utils'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-export const useEditorRule = () => {
-  const label = '富文本'
-  const name = 'Editor'
-  return {
-    icon: 'icon-editor',
-    label,
-    name,
-    rule() {
-      return {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-    },
-    props(_, { t }) {
-      return localeProps(t, `${name  }.props`, [
-        makeRequiredRule(),
-        {
-          type: 'input',
-          field: 'height',
-          title: '高度'
-        },
-        { type: 'switch', field: 'readonly', title: '是否只读' }
-      ])
-    }
-  }
-}

+ 0 - 74
src/components/FormCreate/src/config/useIframeRule.ts

@@ -1,74 +0,0 @@
-import { generateUUID } from '@/utils'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-/**
- * iframe 组件规则
- */
-export const useIframeRule = () => {
-  const label = '网页 iframe'
-  const name = 'IframeComponent'
-
-  return {
-    icon: 'icon-link',
-    label,
-    name,
-    rule() {
-      return {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-    },
-    props(_, { t }) {
-      return localeProps(t, `${name  }.props`, [
-        makeRequiredRule(),
-        {
-          type: 'input',
-          field: 'url',
-          title: 'URL 地址',
-          value: '',
-          info: '请输入完整的 HTTP 或 HTTPS 地址'
-        },
-        {
-          type: 'input',
-          field: 'height',
-          title: 'iframe 高度',
-          value: '500px',
-          info: '支持 px、%、vh 等单位'
-        },
-        {
-          type: 'input',
-          field: 'width',
-          title: 'iframe 宽度',
-          value: '100%',
-          info: '支持 px、%、vw 等单位'
-        },
-        {
-          type: 'select',
-          field: 'loading',
-          title: '加载方式',
-          value: 'lazy',
-          options: [
-            { label: '懒加载', value: 'lazy' },
-            { label: '立即加载', value: 'eager' }
-          ]
-        },
-        {
-          type: 'switch',
-          field: 'allowfullscreen',
-          title: '允许全屏',
-          value: true
-        },
-        {
-          type: 'input',
-          field: 'sandbox',
-          title: 'sandbox 属性',
-          value: '',
-          info: '安全沙箱限制,如:allow-scripts allow-same-origin'
-        }
-      ])
-    }
-  }
-}

+ 0 - 50
src/components/FormCreate/src/config/useSelectRule.ts

@@ -1,50 +0,0 @@
-import { cloneDeep } from 'lodash-es'
-
-import type { SelectRuleOption } from '@/components/FormCreate/src/type'
-
-import { generateUUID } from '@/utils'
-import { selectRule } from '@/components/FormCreate/src/config/selectRule'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-/**
- * 通用选择器规则 hook
- *
- * @param option 规则配置
- */
-export const useSelectRule = (option: SelectRuleOption) => {
-  const label = option.label
-  const name = option.name
-  const rules = cloneDeep(selectRule)
-  return {
-    icon: option.icon,
-    label,
-    name,
-    event: option.event,
-    rule() {
-      // 构建基础规则
-      const baseRule: any = {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-      // 将自定义 props 的默认值添加到 rule 的 props 中
-      if (option.props && option.props.length > 0) {
-        baseRule.props = {}
-        option.props.forEach((prop: any) => {
-          if (prop.field && prop.value !== undefined) {
-            baseRule.props[prop.field] = prop.value
-          }
-        })
-      }
-      return baseRule
-    },
-    props(_, { t }) {
-      if (!option.props) {
-        option.props = []
-      }
-      return localeProps(t, `${name  }.props`, [makeRequiredRule(), ...option.props, ...rules])
-    }
-  }
-}

+ 0 - 80
src/components/FormCreate/src/config/useUploadFileRule.ts

@@ -1,80 +0,0 @@
-import { generateUUID } from '@/utils'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-export const useUploadFileRule = () => {
-  const label = '文件上传'
-  const name = 'UploadFile'
-  return {
-    icon: 'icon-upload',
-    label,
-    name,
-    rule() {
-      return {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-    },
-    props(_, { t }) {
-      return localeProps(t, `${name  }.props`, [
-        makeRequiredRule(),
-        {
-          type: 'select',
-          field: 'fileType',
-          title: '文件类型',
-          value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
-          options: [
-            { label: 'doc', value: 'doc' },
-            { label: 'xls', value: 'xls' },
-            { label: 'ppt', value: 'ppt' },
-            { label: 'txt', value: 'txt' },
-            { label: 'pdf', value: 'pdf' }
-          ],
-          props: {
-            multiple: true
-          }
-        },
-        {
-          type: 'switch',
-          field: 'autoUpload',
-          title: '是否在选取文件后立即进行上传',
-          value: true
-        },
-        {
-          type: 'switch',
-          field: 'drag',
-          title: '拖拽上传',
-          value: false
-        },
-        {
-          type: 'switch',
-          field: 'isShowTip',
-          title: '是否显示提示',
-          value: true
-        },
-        {
-          type: 'inputNumber',
-          field: 'fileSize',
-          title: '大小限制(MB)',
-          value: 5,
-          props: { min: 0 }
-        },
-        {
-          type: 'inputNumber',
-          field: 'limit',
-          title: '数量限制',
-          value: 5,
-          props: { min: 0 }
-        },
-        {
-          type: 'switch',
-          field: 'disabled',
-          title: '是否禁用',
-          value: false
-        }
-      ])
-    }
-  }
-}

+ 0 - 89
src/components/FormCreate/src/config/useUploadImgRule.ts

@@ -1,89 +0,0 @@
-import { generateUUID } from '@/utils'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-export const useUploadImgRule = () => {
-  const label = '单图上传'
-  const name = 'UploadImg'
-  return {
-    icon: 'icon-upload',
-    label,
-    name,
-    rule() {
-      return {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-    },
-    props(_, { t }) {
-      return localeProps(t, `${name  }.props`, [
-        makeRequiredRule(),
-        {
-          type: 'switch',
-          field: 'drag',
-          title: '拖拽上传',
-          value: false
-        },
-        {
-          type: 'select',
-          field: 'fileType',
-          title: '图片类型限制',
-          value: ['image/jpeg', 'image/png', 'image/gif'],
-          options: [
-            { label: 'image/apng', value: 'image/apng' },
-            { label: 'image/bmp', value: 'image/bmp' },
-            { label: 'image/gif', value: 'image/gif' },
-            { label: 'image/jpeg', value: 'image/jpeg' },
-            { label: 'image/pjpeg', value: 'image/pjpeg' },
-            { label: 'image/svg+xml', value: 'image/svg+xml' },
-            { label: 'image/tiff', value: 'image/tiff' },
-            { label: 'image/webp', value: 'image/webp' },
-            { label: 'image/x-icon', value: 'image/x-icon' }
-          ],
-          props: {
-            multiple: true
-          }
-        },
-        {
-          type: 'inputNumber',
-          field: 'fileSize',
-          title: '大小限制(MB)',
-          value: 5,
-          props: { min: 0 }
-        },
-        {
-          type: 'input',
-          field: 'height',
-          title: '组件高度',
-          value: '150px'
-        },
-        {
-          type: 'input',
-          field: 'width',
-          title: '组件宽度',
-          value: '150px'
-        },
-        {
-          type: 'input',
-          field: 'borderradius',
-          title: '组件边框圆角',
-          value: '8px'
-        },
-        {
-          type: 'switch',
-          field: 'disabled',
-          title: '是否禁用',
-          value: false
-        },
-        {
-          type: 'switch',
-          field: 'showBtnText',
-          title: '是否显示按钮文字',
-          value: true
-        }
-      ])
-    }
-  }
-}

+ 0 - 84
src/components/FormCreate/src/config/useUploadImgsRule.ts

@@ -1,84 +0,0 @@
-import { generateUUID } from '@/utils'
-import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
-
-export const useUploadImgsRule = () => {
-  const label = '多图上传'
-  const name = 'UploadImgs'
-  return {
-    icon: 'icon-upload',
-    label,
-    name,
-    rule() {
-      return {
-        type: name,
-        field: generateUUID(),
-        title: label,
-        info: '',
-        $required: false
-      }
-    },
-    props(_, { t }) {
-      return localeProps(t, `${name  }.props`, [
-        makeRequiredRule(),
-        {
-          type: 'switch',
-          field: 'drag',
-          title: '拖拽上传',
-          value: false
-        },
-        {
-          type: 'select',
-          field: 'fileType',
-          title: '图片类型限制',
-          value: ['image/jpeg', 'image/png', 'image/gif'],
-          options: [
-            { label: 'image/apng', value: 'image/apng' },
-            { label: 'image/bmp', value: 'image/bmp' },
-            { label: 'image/gif', value: 'image/gif' },
-            { label: 'image/jpeg', value: 'image/jpeg' },
-            { label: 'image/pjpeg', value: 'image/pjpeg' },
-            { label: 'image/svg+xml', value: 'image/svg+xml' },
-            { label: 'image/tiff', value: 'image/tiff' },
-            { label: 'image/webp', value: 'image/webp' },
-            { label: 'image/x-icon', value: 'image/x-icon' }
-          ],
-          props: {
-            multiple: true
-          }
-        },
-        {
-          type: 'inputNumber',
-          field: 'fileSize',
-          title: '大小限制(MB)',
-          value: 5,
-          props: { min: 0 }
-        },
-        {
-          type: 'inputNumber',
-          field: 'limit',
-          title: '数量限制',
-          value: 5,
-          props: { min: 0 }
-        },
-        {
-          type: 'input',
-          field: 'height',
-          title: '组件高度',
-          value: '150px'
-        },
-        {
-          type: 'input',
-          field: 'width',
-          title: '组件宽度',
-          value: '150px'
-        },
-        {
-          type: 'input',
-          field: 'borderradius',
-          title: '组件边框圆角',
-          value: '8px'
-        }
-      ])
-    }
-  }
-}

+ 0 - 31
src/components/FormCreate/src/type/index.ts

@@ -1,31 +0,0 @@
-// 左侧拖拽按钮
-export interface MenuItem {
-  label: string
-  name: string
-  icon: string
-}
-
-// 左侧拖拽按钮分类
-export interface Menu {
-  title: string
-  name: string
-  list: MenuItem[]
-}
-
-// 通用下拉组件 Props 类型
-export interface ApiSelectProps {
-  name: string // 组件名称
-  labelField?: string // 选项标签
-  valueField?: string // 选项的值
-  url?: string // url 接口
-  isDict?: boolean // 是否字典选择器
-}
-
-// 选择组件规则配置类型
-export interface SelectRuleOption {
-  label: string // label 名称
-  name: string // 组件名称
-  icon: string // 组件图标
-  props?: any[] // 组件规则
-  event?: any[] // 事件配置
-}

+ 0 - 193
src/components/FormCreate/src/useFormCreateDesigner.ts

@@ -1,193 +0,0 @@
-import type { Ref } from 'vue'
-
-import type { Menu } from '@/components/FormCreate/src/type'
-
-import { generateUUID } from '@/utils'
-import { apiSelectRule } from '@/components/FormCreate/src/config/selectRule'
-
-import {
-  useAreaSelectRule,
-  useDictSelectRule,
-  useEditorRule,
-  useIframeRule,
-  useSelectRule,
-  useUploadFileRule,
-  useUploadImgRule,
-  useUploadImgsRule
-} from './config'
-
-/**
- * 表单设计器增强 hook
- * 新增
- * - 文件上传
- * - 单图上传
- * - 多图上传
- * - 字典选择器
- * - 用户选择器
- * - 部门选择器
- * - 富文本
- */
-export const useFormCreateDesigner = async (designer: Ref) => {
-  const editorRule = useEditorRule()
-  const uploadFileRule = useUploadFileRule()
-  const uploadImgRule = useUploadImgRule()
-  const uploadImgsRule = useUploadImgsRule()
-
-  /**
-   * 构建表单组件
-   */
-  const buildFormComponents = () => {
-    // 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代
-    designer.value?.removeMenuItem('upload')
-    // 移除自带的富文本组件规则,使用 editorRule 替代
-    designer.value?.removeMenuItem('fcEditor')
-    const iframeRule = useIframeRule()
-    const areaSelectRule = useAreaSelectRule()
-    const components = [
-      editorRule,
-      uploadFileRule,
-      uploadImgRule,
-      uploadImgsRule,
-      iframeRule,
-      areaSelectRule
-    ]
-    components.forEach((component) => {
-      // 插入组件规则
-      designer.value?.addComponent(component)
-      // 插入拖拽按钮到 `main` 分类下
-      designer.value?.appendMenuItem('main', {
-        icon: component.icon,
-        name: component.name,
-        label: component.label
-      })
-    })
-  }
-
-  const userSelectRule = useSelectRule({
-    name: 'UserSelect',
-    label: '用户选择器',
-    icon: 'icon-user-o',
-    props: [
-      {
-        type: 'switch',
-        field: 'defaultCurrentUser',
-        title: '默认选中当前用户',
-        value: false
-      }
-    ]
-  })
-  const deptSelectRule = useSelectRule({
-    name: 'DeptSelect',
-    label: '部门选择器',
-    icon: 'icon-address-card-o',
-    props: [
-      {
-        type: 'select',
-        field: 'returnType',
-        title: '返回值类型',
-        value: 'id',
-        options: [
-          { label: '部门编号', value: 'id' },
-          { label: '部门名称', value: 'name' }
-        ]
-      },
-      {
-        type: 'switch',
-        field: 'defaultCurrentDept',
-        title: '默认选中当前部门',
-        value: false
-      }
-    ]
-  })
-  const dictSelectRule = useDictSelectRule()
-  const apiSelectRule0 = useSelectRule({
-    name: 'ApiSelect',
-    label: '接口选择器',
-    icon: 'icon-server',
-    props: [...apiSelectRule],
-    event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus']
-  })
-
-  /**
-   * 构建系统字段菜单
-   */
-  const buildSystemMenu = () => {
-    // 移除自带的下拉选择器组件,使用 currencySelectRule 替代
-    // designer.value?.removeMenuItem('select')
-    // designer.value?.removeMenuItem('radio')
-    // designer.value?.removeMenuItem('checkbox')
-    const components = [userSelectRule, deptSelectRule, dictSelectRule, apiSelectRule0]
-    const menu: Menu = {
-      name: 'system',
-      title: '系统字段',
-      list: components.map((component) => {
-        // 插入组件规则
-        designer.value?.addComponent(component)
-        // 插入拖拽按钮到 `system` 分类下
-        return {
-          icon: component.icon,
-          name: component.name,
-          label: component.label
-        }
-      })
-    }
-    designer.value?.addMenu(menu)
-  }
-
-  /**
-   * 修复重复的字段 ID 问题
-   * 当复制组件时,自动为新组件生成新的字段 ID
-   *
-   * 对应 issue:https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/ICM22X
-   */
-  const fixDuplicateFields = () => {
-    // 获取当前所有规则
-    const rules = designer.value?.getRule() || []
-    const fieldIds = new Set<string>()
-    let hasChanges = false
-
-    // 遍历所有规则,检测并修复重复的字段 ID
-    rules.forEach((rule: any) => {
-      if (rule.field) {
-        if (fieldIds.has(rule.field)) {
-          // 发现重复,生成新的ID
-          const oldField = rule.field
-          const newField = generateUUID()
-          console.log(`[FormCreate] 检测到重复字段ID: ${oldField}, 已自动更新为: ${newField}`)
-          rule.field = newField
-          hasChanges = true
-        } else {
-          fieldIds.add(rule.field)
-        }
-      }
-    })
-
-    // 如果有重复字段被修复,更新设计器
-    if (hasChanges) {
-      designer.value?.setRule(rules)
-    }
-
-    return hasChanges
-  }
-
-  onMounted(async () => {
-    await nextTick()
-    buildFormComponents()
-    buildSystemMenu()
-
-    // 监听设计器内容变化,自动修复重复字段ID
-    let isFixing = false // 防止无限循环
-    watch(
-      () => designer.value?.getRule(),
-      async () => {
-        if (!isFixing) {
-          isFixing = true
-          await nextTick()
-          fixDuplicateFields()
-          isFixing = false
-        }
-      },
-      { deep: true }
-    )
-  })
-}

+ 0 - 61
src/components/FormCreate/src/utils/index.ts

@@ -1,61 +0,0 @@
-export function makeRequiredRule() {
-  return {
-    type: 'Required',
-    field: 'formCreate$required',
-    title: '是否必填'
-  }
-}
-
-export const localeProps = (t, prefix, rules) => {
-  return rules.map((rule) => {
-    if (rule.field === 'formCreate$required') {
-      rule.title = t('props.required') || rule.title
-    } else if (rule.field && rule.field !== '_optionType') {
-      rule.title = t(`components.${  prefix  }.${  rule.field}`) || rule.title
-    }
-    return rule
-  })
-}
-
-/**
- * 解析表单组件的  field, title 等字段(递归,如果组件包含子组件)
- *
- * @param rule  组件的生成规则 https://www.form-create.com/v3/guide/rule
- * @param fields 解析后表单组件字段
- * @param parentTitle  如果是子表单,子表单的标题,默认为空
- */
-export const parseFormFields = (
-  rule: Record<string, any>,
-  fields: Array<Record<string, any>> = [],
-  parentTitle: string = ''
-) => {
-  const { type, field, $required, title: tempTitle, children } = rule
-  if (field && tempTitle) {
-    let title = tempTitle
-    if (parentTitle) {
-      title = `${parentTitle}.${tempTitle}`
-    }
-    let required = false
-    if ($required) {
-      required = true
-    }
-    fields.push({
-      field,
-      title,
-      type,
-      required
-    })
-    // TODO 子表单 需要处理子表单字段
-    // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
-    //   // 解析子表单的字段
-    //   rule.props.rule.forEach((item) => {
-    //     parseFields(item, fieldsPermission, title)
-    //   })
-    // }
-  }
-  if (children && Array.isArray(children)) {
-    children.forEach((rule) => {
-      parseFormFields(rule, fields)
-    })
-  }
-}

+ 49 - 55
src/components/Highlight/src/Highlight.vue

@@ -1,68 +1,62 @@
 <script lang="tsx">
-import type { PropType} from 'vue';
+  import type { PropType } from 'vue'
 
-import { computed, defineComponent, h, unref } from 'vue'
+  import { computed, defineComponent, h, unref } from 'vue'
 
-import { propTypes } from '@/utils/propTypes'
+  import { propTypes } from '@/utils/propTypes'
 
-export default defineComponent({
-  name: 'Highlight',
-  props: {
-    tag: propTypes.string.def('span'),
-    keys: {
-      type: Array as PropType<string[]>,
-      default: () => []
+  export default defineComponent({
+    name: 'Highlight',
+    props: {
+      tag: propTypes.string.def('span'),
+      keys: { type: Array as PropType<string[]>, default: () => [] },
+      color: propTypes.string.def('var(--el-color-primary)'),
     },
-    color: propTypes.string.def('var(--el-color-primary)')
-  },
-  emits: ['click'],
-  setup(props, { emit, slots }) {
-    const keyNodes = computed(() => {
-      return props.keys.map((key) => {
-        return h(
-          'span',
-          {
-            onClick: () => {
-              emit('click', key)
+    emits: ['click'],
+    setup(props, { emit, slots }) {
+      const keyNodes = computed(() => {
+        return props.keys.map(key => {
+          return h(
+            'span',
+            {
+              onClick: () => {
+                emit('click', key)
+              },
+              style: { color: props.color, cursor: 'pointer' },
             },
-            style: {
-              color: props.color,
-              cursor: 'pointer'
-            }
-          },
-          key
-        )
+            key
+          )
+        })
       })
-    })
 
-    const parseText = (text: string) => {
-      props.keys.forEach((key, index) => {
-        const regexp = new RegExp(key, 'g')
-        text = text.replace(regexp, `{{${index}}}`)
-      })
-      return text.split(/\{\{|\}\}/)
-    }
-
-    const renderText = () => {
-      if (!slots?.default) return null
-      const node = slots?.default()[0].children
-
-      if (!node) {
-        return slots?.default()[0]
+      const parseText = (text: string) => {
+        props.keys.forEach((key, index) => {
+          const regexp = new RegExp(key, 'g')
+          text = text.replace(regexp, `{{${index}}}`)
+        })
+        return text.split(/\{\{|\}\}/)
       }
 
-      const textArray = parseText(node as string)
-      const regexp = /^\d*$/
-      const nodes = textArray.map((t) => {
-        if (regexp.test(t)) {
-          return unref(keyNodes)[t] || t
+      const renderText = () => {
+        if (!slots?.default) return null
+        const node = slots?.default()[0].children
+
+        if (!node) {
+          return slots?.default()[0]
         }
-        return t
-      })
-      return h(props.tag, nodes)
-    }
 
-    return () => renderText()
-  }
-})
+        const textArray = parseText(node as string)
+        const regexp = /^\d*$/
+        const nodes = textArray.map(t => {
+          if (regexp.test(t)) {
+            return unref(keyNodes)[t] || t
+          }
+          return t
+        })
+        return h(props.tag, nodes)
+      }
+
+      return () => renderText()
+    },
+  })
 </script>

+ 28 - 32
src/components/IFrame/src/IFrame.vue

@@ -1,42 +1,39 @@
 <script lang="ts" setup>
-import { propTypes } from '@/utils/propTypes'
+  import { propTypes } from '@/utils/propTypes'
 
-defineOptions({ name: 'IFrame' })
+  defineOptions({ name: 'IFrame' })
 
-const props = defineProps({
-  src: propTypes.string.def('')
-})
-const loading = ref(true)
-const frameRef = ref<HTMLIFrameElement | null>(null)
-const init = () => {
-  nextTick(() => {
-    loading.value = true
+  const props = defineProps({ src: propTypes.string.def('') })
+  const loading = ref(true)
+  const frameRef = ref<HTMLIFrameElement | null>(null)
+  const init = () => {
+    nextTick(() => {
+      loading.value = true
+      if (!frameRef.value) return
+      frameRef.value.onload = () => {
+        loading.value = false
+      }
+    })
+  }
+  onMounted(() => {
+    init()
+  })
+  onBeforeUnmount(() => {
     if (!frameRef.value) return
-    frameRef.value.onload = () => {
-      loading.value = false
-    }
+    frameRef.value.onload = null
+    frameRef.value.src = 'about:blank'
   })
-}
-onMounted(() => {
-  init()
-})
-onBeforeUnmount(() => {
-  if (!frameRef.value) return
-  frameRef.value.onload = null
-  frameRef.value.src = 'about:blank'
-})
-watch(
-  () => props.src,
-  () => {
-    init()
-  }
-)
+  watch(
+    () => props.src,
+    () => {
+      init()
+    }
+  )
 </script>
 <template>
   <div
     v-loading="loading"
-    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)] w-full"
-  >
+    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)] w-full">
     <iframe
       ref="frameRef"
       :src="props.src"
@@ -46,7 +43,6 @@ watch(
       width="100%"
       allowfullscreen="true"
       webkitallowfullscreen="true"
-      mozallowfullscreen="true"
-    ></iframe>
+      mozallowfullscreen="true"></iframe>
   </div>
 </template>

+ 25 - 26
src/components/Icon/src/Icon.vue

@@ -1,36 +1,36 @@
 <script lang="ts" setup>
-import { Icon as IconifyIcon } from '@iconify/vue'
+  import { Icon as IconifyIcon } from '@iconify/vue'
 
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
 
-defineOptions({ name: 'Icon' })
+  defineOptions({ name: 'Icon' })
 
-const props = defineProps({
-  // icon name
-  icon: propTypes.string,
-  // icon color
-  color: propTypes.string,
-  // icon size
-  size: propTypes.number.def(16),
-  // icon svg class
-  svgClass: propTypes.string.def('')
-})
+  const props = defineProps({
+    // icon name
+    icon: propTypes.string,
+    // icon color
+    color: propTypes.string,
+    // icon size
+    size: propTypes.number.def(16),
+    // icon svg class
+    svgClass: propTypes.string.def(''),
+  })
 
-const { getPrefixCls } = useDesign()
+  const { getPrefixCls } = useDesign()
 
-const prefixCls = getPrefixCls('icon')
+  const prefixCls = getPrefixCls('icon')
 
-const isLocal = computed(() => props.icon?.startsWith('svg-icon:'))
+  const isLocal = computed(() => props.icon?.startsWith('svg-icon:'))
 
-const symbolId = computed(() => {
-  return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
-})
+  const symbolId = computed(() => {
+    return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
+  })
 
-const getSvgClass = computed(() => {
-  const { svgClass } = props
-  return `iconify ${svgClass}`
-})
+  const getSvgClass = computed(() => {
+    const { svgClass } = props
+    return `iconify ${svgClass}`
+  })
 </script>
 
 <template>
@@ -43,7 +43,6 @@ const getSvgClass = computed(() => {
       v-else
       :icon="symbolId"
       :class="getSvgClass"
-      :style="{ fontSize: `${size}px`, color }"
-    />
+      :style="{ fontSize: `${size}px`, color }" />
   </ElIcon>
 </template>

+ 151 - 176
src/components/Icon/src/IconSelect.vue

@@ -1,129 +1,112 @@
 <script lang="ts" setup>
-import type { CSSProperties } from 'vue'
-
-import { cloneDeep } from 'lodash-es'
-
-import { IconJson } from '@/components/Icon/src/data'
+  import type { CSSProperties } from 'vue'
+
+  import { cloneDeep } from 'lodash-es'
+  import { defineEmits, defineOptions, defineProps } from 'vue'
+
+  import { IconJson } from '@/components/Icon/src/data'
+
+  defineOptions({ name: 'IconSelect' })
+
+  const props = defineProps({
+    modelValue: { require: false, type: String },
+    clearable: { require: false, type: Boolean },
+  })
+
+  const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
+
+  type ParameterCSSProperties = (item?: string) => CSSProperties | undefined
+
+  const visible = ref(false)
+  const inputValue = toRef(props, 'modelValue')
+  const iconList = ref(IconJson)
+  const icon = ref('add-location')
+  const currentActiveType = ref('ep:')
+  // 深拷贝图标数据,前端做搜索
+  const copyIconList = cloneDeep(iconList.value)
+
+  const pageSize = ref(96)
+  const currentPage = ref(1)
+
+  // 搜索条件
+  const filterValue = ref('')
+
+  const tabsList = [
+    { label: 'Element Plus', name: 'ep:' },
+    { label: 'Font Awesome 4', name: 'fa:' },
+    { label: 'Font Awesome 5 Solid', name: 'fa-solid:' },
+  ]
+
+  const pageList = computed(() => {
+    if (currentPage.value === 1) {
+      return copyIconList[currentActiveType.value]
+        ?.filter(v => v.includes(filterValue.value))
+        .slice(currentPage.value - 1, pageSize.value)
+    } else {
+      return copyIconList[currentActiveType.value]
+        ?.filter(v => v.includes(filterValue.value))
+        .slice(
+          pageSize.value * (currentPage.value - 1),
+          pageSize.value * (currentPage.value - 1) + pageSize.value
+        )
+    }
+  })
+  const iconCount = computed(() => {
+    return copyIconList[currentActiveType.value] === undefined
+      ? 0
+      : copyIconList[currentActiveType.value].length
+  })
+
+  const iconItemStyle = computed((): ParameterCSSProperties => {
+    return item => {
+      if (inputValue.value === currentActiveType.value + item) {
+        return { borderColor: 'var(--el-color-primary)', color: 'var(--el-color-primary)' }
+      }
+    }
+  })
 
-defineOptions({ name: 'IconSelect' })
+  function handleClick({ props }) {
+    currentPage.value = 1
+    currentActiveType.value = props.name
+    emit('update:modelValue', currentActiveType.value + iconList.value[currentActiveType.value][0])
+    icon.value = iconList.value[currentActiveType.value][0]
+  }
 
-const props = defineProps({
-  modelValue: {
-    require: false,
-    type: String
-  },
-  clearable: {
-    require: false,
-    type: Boolean
+  function onChangeIcon(item) {
+    icon.value = item
+    emit('update:modelValue', currentActiveType.value + item)
+    visible.value = false
   }
-})
-
-const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
-
-type ParameterCSSProperties = (item?: string) => CSSProperties | undefined
-
-const visible = ref(false)
-const inputValue = toRef(props, 'modelValue')
-const iconList = ref(IconJson)
-const icon = ref('add-location')
-const currentActiveType = ref('ep:')
-// 深拷贝图标数据,前端做搜索
-const copyIconList = cloneDeep(iconList.value)
-
-const pageSize = ref(96)
-const currentPage = ref(1)
-
-// 搜索条件
-const filterValue = ref('')
-
-const tabsList = [
-  {
-    label: 'Element Plus',
-    name: 'ep:'
-  },
-  {
-    label: 'Font Awesome 4',
-    name: 'fa:'
-  },
-  {
-    label: 'Font Awesome 5 Solid',
-    name: 'fa-solid:'
+
+  function onCurrentChange(page) {
+    currentPage.value = page
   }
-]
-
-const pageList = computed(() => {
-  if (currentPage.value === 1) {
-    return copyIconList[currentActiveType.value]
-      ?.filter((v) => v.includes(filterValue.value))
-      .slice(currentPage.value - 1, pageSize.value)
-  } else {
-    return copyIconList[currentActiveType.value]
-      ?.filter((v) => v.includes(filterValue.value))
-      .slice(
-        pageSize.value * (currentPage.value - 1),
-        pageSize.value * (currentPage.value - 1) + pageSize.value
-      )
+
+  function clearIcon() {
+    icon.value = ''
+    emit('update:modelValue', '')
+    visible.value = false
   }
-})
-const iconCount = computed(() => {
-  return copyIconList[currentActiveType.value] == undefined
-    ? 0
-    : copyIconList[currentActiveType.value].length
-})
-
-const iconItemStyle = computed((): ParameterCSSProperties => {
-  return (item) => {
-    if (inputValue.value === currentActiveType.value + item) {
-      return {
-        borderColor: 'var(--el-color-primary)',
-        color: 'var(--el-color-primary)'
+
+  watch(
+    () => {
+      return props.modelValue
+    },
+    () => {
+      if (props.modelValue && props.modelValue.includes(':')) {
+        currentActiveType.value = props.modelValue.substring(0, props.modelValue.indexOf(':') + 1)
+        icon.value = props.modelValue.substring(props.modelValue.indexOf(':') + 1)
       }
     }
-  }
-})
-
-function handleClick({ props }) {
-  currentPage.value = 1
-  currentActiveType.value = props.name
-  emit('update:modelValue', currentActiveType.value + iconList.value[currentActiveType.value][0])
-  icon.value = iconList.value[currentActiveType.value][0]
-}
-
-function onChangeIcon(item) {
-  icon.value = item
-  emit('update:modelValue', currentActiveType.value + item)
-  visible.value = false
-}
-
-function onCurrentChange(page) {
-  currentPage.value = page
-}
-
-function clearIcon() {
-  icon.value = ''
-  emit('update:modelValue', '')
-  visible.value = false
-}
-
-watch(
-  () => {
-    return props.modelValue
-  },
-  () => {
-    if (props.modelValue && props.modelValue.includes(':')) {
-      currentActiveType.value = props.modelValue.substring(0, props.modelValue.indexOf(':') + 1)
-      icon.value = props.modelValue.substring(props.modelValue.indexOf(':') + 1)
+  )
+  watch(
+    () => {
+      return filterValue.value
+    },
+    () => {
+      currentPage.value = 1
     }
-  }
-)
-watch(
-  () => {
-    return filterValue.value
-  },
-  () => {
-    currentPage.value = 1
-  }
-)
+  )
 </script>
 
 <template>
@@ -132,23 +115,18 @@ watch(
       v-model="inputValue"
       :clearable="props.clearable"
       @click="visible = !visible"
-      @clear="clearIcon"
-    >
+      @clear="clearIcon">
       <template #append>
         <ElPopover
-          :popper-options="{
-            placement: 'auto'
-          }"
+          :popper-options="{ placement: 'auto' }"
           :visible="visible"
           :width="355"
           popper-class="pure-popper"
-          trigger="click"
-        >
+          trigger="click">
           <template #reference>
             <div
               class="flex h-32px w-40px cursor-pointer items-center justify-center"
-              @click="visible = !visible"
-            >
+              @click="visible = !visible">
               <Icon :icon="currentActiveType + icon" />
             </div>
           </template>
@@ -161,8 +139,7 @@ watch(
               v-for="(pane, index) in tabsList"
               :key="index"
               :label="pane.label"
-              :name="pane.name"
-            >
+              :name="pane.name">
               <ElDivider border-style="dashed" class="tab-divider" />
               <ElScrollbar height="220px">
                 <ul class="ml-2 flex flex-wrap">
@@ -172,8 +149,7 @@ watch(
                     :style="iconItemStyle(item)"
                     :title="item"
                     class="icon-item mr-2 mt-1 p-2 border border-solid flex w-1/10 cursor-pointer items-center justify-center"
-                    @click="onChangeIcon(item)"
-                  >
+                    @click="onChangeIcon(item)">
                     <Icon :icon="currentActiveType + item" />
                   </li>
                 </ul>
@@ -190,8 +166,7 @@ watch(
             class="flex h-10 items-center justify-center"
             layout="prev, pager, next"
             size="small"
-            @current-change="onCurrentChange"
-          />
+            @current-change="onCurrentChange" />
         </ElPopover>
       </template>
     </ElInput>
@@ -199,49 +174,49 @@ watch(
 </template>
 
 <style lang="scss" scoped>
-.el-divider--horizontal {
-  margin: 1px auto !important;
-}
-
-.tab-divider.el-divider--horizontal {
-  margin: 0 !important;
-}
-
-.icon-item {
-  &:hover {
-    color: var(--el-color-primary);
-    border-color: var(--el-color-primary);
-    transform: scaleX(1.05);
-    transition: all 0.4s;
+  .el-divider--horizontal {
+    margin: 1px auto !important;
+  }
+
+  .tab-divider.el-divider--horizontal {
+    margin: 0 !important;
+  }
+
+  .icon-item {
+    &:hover {
+      color: var(--el-color-primary);
+      border-color: var(--el-color-primary);
+      transform: scaleX(1.05);
+      transition: all 0.4s;
+    }
+  }
+
+  :deep(.el-tabs__nav-next) {
+    font-size: 15px;
+    line-height: 32px;
+    box-shadow: -5px 0 5px -6px #ccc;
+  }
+
+  :deep(.el-tabs__nav-prev) {
+    font-size: 15px;
+    line-height: 32px;
+    box-shadow: 5px 0 5px -6px #ccc;
+  }
+
+  :deep(.el-input-group__append) {
+    padding: 0;
+  }
+
+  :deep(.el-tabs__item) {
+    height: 30px;
+    font-size: 12px;
+    font-weight: normal;
+    line-height: 30px;
+  }
+
+  :deep(.el-tabs__header),
+  :deep(.el-tabs__nav-wrap) {
+    position: static;
+    margin: 0;
   }
-}
-
-:deep(.el-tabs__nav-next) {
-  font-size: 15px;
-  line-height: 32px;
-  box-shadow: -5px 0 5px -6px #ccc;
-}
-
-:deep(.el-tabs__nav-prev) {
-  font-size: 15px;
-  line-height: 32px;
-  box-shadow: 5px 0 5px -6px #ccc;
-}
-
-:deep(.el-input-group__append) {
-  padding: 0;
-}
-
-:deep(.el-tabs__item) {
-  height: 30px;
-  font-size: 12px;
-  font-weight: normal;
-  line-height: 30px;
-}
-
-:deep(.el-tabs__header),
-:deep(.el-tabs__nav-wrap) {
-  position: static;
-  margin: 0;
-}
 </style>

+ 4 - 4
src/components/Icon/src/data.ts

@@ -280,7 +280,7 @@ export const IconJson = {
     'watermelon',
     'wind-power',
     'zoom-in',
-    'zoom-out'
+    'zoom-out',
   ],
   'fa:': [
     '500px',
@@ -931,7 +931,7 @@ export const IconJson = {
     'yoast',
     'youtube',
     'youtube-play',
-    'youtube-square'
+    'youtube-square',
   ],
   'fa-solid:': [
     'abacus',
@@ -1956,6 +1956,6 @@ export const IconJson = {
     'wrench',
     'x-ray',
     'yen-sign',
-    'yin-yang'
-  ]
+    'yin-yang',
+  ],
 }

+ 2 - 2
src/components/ImageViewer/index.ts

@@ -1,4 +1,4 @@
-import type { VNode } from 'vue';
+import type { VNode } from 'vue'
 
 import { createVNode, render } from 'vue'
 
@@ -19,7 +19,7 @@ export function createImageViewer(options: ImageViewerProps) {
     hideOnClickModal = false,
     teleported = false,
     zIndex = 2000,
-    show = true
+    show = true,
   } = options
 
   const propsData: Partial<ImageViewerProps> = {}

+ 21 - 24
src/components/ImageViewer/src/ImageViewer.vue

@@ -1,34 +1,31 @@
 <script lang="ts" setup>
-import type { PropType } from 'vue'
+  import type { PropType } from 'vue'
 
-import { propTypes } from '@/utils/propTypes'
+  import { propTypes } from '@/utils/propTypes'
 
-defineOptions({ name: 'ImageViewer' })
+  defineOptions({ name: 'ImageViewer' })
 
-const props = defineProps({
-  urlList: {
-    type: Array as PropType<string[]>,
-    default: (): string[] => []
-  },
-  zIndex: propTypes.number.def(200),
-  initialIndex: propTypes.number.def(0),
-  infinite: propTypes.bool.def(true),
-  hideOnClickModal: propTypes.bool.def(false),
-  teleported: propTypes.bool.def(false),
-  show: propTypes.bool.def(false)
-})
+  const props = defineProps({
+    urlList: { type: Array as PropType<string[]>, default: (): string[] => [] },
+    zIndex: propTypes.number.def(200),
+    initialIndex: propTypes.number.def(0),
+    infinite: propTypes.bool.def(true),
+    hideOnClickModal: propTypes.bool.def(false),
+    teleported: propTypes.bool.def(false),
+    show: propTypes.bool.def(false),
+  })
 
-const getBindValue = computed(() => {
-  const propsData: Recordable = { ...props }
-  delete propsData.show
-  return propsData
-})
+  const getBindValue = computed(() => {
+    const propsData: Recordable = { ...props }
+    delete propsData.show
+    return propsData
+  })
 
-const show = ref(props.show)
+  const show = ref(props.show)
 
-const close = () => {
-  show.value = false
-}
+  const close = () => {
+    show.value = false
+  }
 </script>
 
 <template>

+ 104 - 104
src/components/InputPassword/src/InputPassword.vue

@@ -1,62 +1,63 @@
 <script lang="ts" setup>
-import type { ZxcvbnResult } from '@zxcvbn-ts/core'
+  import type { ZxcvbnResult } from '@zxcvbn-ts/core'
 
-import { zxcvbn } from '@zxcvbn-ts/core'
+  import { zxcvbn } from '@zxcvbn-ts/core'
+  import { defineEmits, defineOptions, defineProps } from 'vue'
 
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
-import { useConfigGlobal } from '@/hooks/web/useConfigGlobal'
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
+  import { useConfigGlobal } from '@/hooks/web/useConfigGlobal'
 
-defineOptions({ name: 'InputPassword' })
+  defineOptions({ name: 'InputPassword' })
 
-const props = defineProps({
-  // 是否显示密码强度
-  strength: propTypes.bool.def(false),
-  modelValue: propTypes.string.def('')
-})
+  const props = defineProps({
+    // 是否显示密码强度
+    strength: propTypes.bool.def(false),
+    modelValue: propTypes.string.def(''),
+  })
 
-const emit = defineEmits(['update:modelValue'])
+  const emit = defineEmits(['update:modelValue'])
 
-const { getPrefixCls } = useDesign()
+  const { getPrefixCls } = useDesign()
 
-const prefixCls = getPrefixCls('input-password')
+  const prefixCls = getPrefixCls('input-password')
 
-watch(
-  () => props.modelValue,
-  (val: string) => {
-    if (val === unref(valueRef)) return
-    valueRef.value = val
-  }
-)
-
-const { configGlobal } = useConfigGlobal()
+  // 输入框的值
+  const valueRef = ref(props.modelValue)
 
-// 设置input的type属性
-const textType = ref<'password' | 'text'>('password')
+  watch(
+    () => props.modelValue,
+    (val: string) => {
+      if (val === unref(valueRef)) return
+      valueRef.value = val
+    }
+  )
 
-const changeTextType = () => {
-  textType.value = unref(textType) === 'text' ? 'password' : 'text'
-}
+  const { configGlobal } = useConfigGlobal()
 
-// 输入框的值
-const valueRef = ref(props.modelValue)
+  // 设置input的type属性
+  const textType = ref<'password' | 'text'>('password')
 
-// 监听
-watch(
-  () => valueRef.value,
-  (val: string) => {
-    emit('update:modelValue', val)
+  const changeTextType = () => {
+    textType.value = unref(textType) === 'text' ? 'password' : 'text'
   }
-)
 
-// 获取密码强度
-const getPasswordStrength = computed(() => {
-  const value = unref(valueRef)
-  const zxcvbnRef = zxcvbn(unref(valueRef)) as ZxcvbnResult
-  return value ? zxcvbnRef.score : -1
-})
+  // 监听
+  watch(
+    () => valueRef.value,
+    (val: string) => {
+      emit('update:modelValue', val)
+    }
+  )
 
-const getIconName = computed(() => (unref(textType) === 'password' ? 'ep:hide' : 'ep:view'))
+  // 获取密码强度
+  const getPasswordStrength = computed(() => {
+    const value = unref(valueRef)
+    const zxcvbnRef = zxcvbn(unref(valueRef)) as ZxcvbnResult
+    return value ? zxcvbnRef.score : -1
+  })
+
+  const getIconName = computed(() => (unref(textType) === 'password' ? 'ep:hide' : 'ep:view'))
 </script>
 
 <template>
@@ -69,86 +70,85 @@ const getIconName = computed(() => (unref(textType) === 'password' ? 'ep:hide' :
     <div
       v-if="strength"
       :class="`${prefixCls}__bar`"
-      class="mb-6px ml-auto mr-auto mt-10px h-6px relative"
-    >
+      class="mb-6px ml-auto mr-auto mt-10px h-6px relative">
       <div :class="`${prefixCls}__bar--fill`" :data-score="getPasswordStrength"></div>
     </div>
   </div>
 </template>
 
 <style lang="scss" scoped>
-$prefix-cls: #{$namespace}-input-password;
-
-.#{$prefix-cls} {
-  :deep(.#{$elNamespace}-input__clear) {
-    margin-left: 5px;
-  }
+  $prefix-cls: #{$namespace}-input-password;
 
-  &__bar {
-    background-color: var(--el-text-color-disabled);
-    border-radius: var(--el-border-radius-base);
-
-    &::before,
-    &::after {
-      position: absolute;
-      z-index: 10;
-      display: block;
-      width: 20%;
-      height: inherit;
-      background-color: transparent;
-      border-color: var(--el-color-white);
-      border-style: solid;
-      border-width: 0 5px;
-      content: '';
+  .#{$prefix-cls} {
+    :deep(.#{$elNamespace}-input__clear) {
+      margin-left: 5px;
     }
 
-    &::before {
-      left: 20%;
-    }
-
-    &::after {
-      right: 20%;
-    }
+    &__bar {
+      background-color: var(--el-text-color-disabled);
+      border-radius: var(--el-border-radius-base);
 
-    &--fill {
-      position: absolute;
-      width: 0;
-      height: inherit;
-      background-color: transparent;
-      border-radius: inherit;
-      transition:
-        width 0.5s ease-in-out,
-        background 0.25s;
-
-      &[data-score='0'] {
+      &::before,
+      &::after {
+        position: absolute;
+        z-index: 10;
+        display: block;
         width: 20%;
-        background-color: var(--el-color-danger);
+        height: inherit;
+        background-color: transparent;
+        border-color: var(--el-color-white);
+        border-style: solid;
+        border-width: 0 5px;
+        content: '';
       }
 
-      &[data-score='1'] {
-        width: 40%;
-        background-color: var(--el-color-danger);
+      &::before {
+        left: 20%;
       }
 
-      &[data-score='2'] {
-        width: 60%;
-        background-color: var(--el-color-warning);
+      &::after {
+        right: 20%;
       }
 
-      &[data-score='3'] {
-        width: 80%;
-        background-color: var(--el-color-success);
-      }
-
-      &[data-score='4'] {
-        width: 100%;
-        background-color: var(--el-color-success);
+      &--fill {
+        position: absolute;
+        width: 0;
+        height: inherit;
+        background-color: transparent;
+        border-radius: inherit;
+        transition:
+          width 0.5s ease-in-out,
+          background 0.25s;
+
+        &[data-score='0'] {
+          width: 20%;
+          background-color: var(--el-color-danger);
+        }
+
+        &[data-score='1'] {
+          width: 40%;
+          background-color: var(--el-color-danger);
+        }
+
+        &[data-score='2'] {
+          width: 60%;
+          background-color: var(--el-color-warning);
+        }
+
+        &[data-score='3'] {
+          width: 80%;
+          background-color: var(--el-color-success);
+        }
+
+        &[data-score='4'] {
+          width: 100%;
+          background-color: var(--el-color-success);
+        }
       }
     }
-  }
 
-  &--mini > &__bar {
-    border-radius: var(--el-border-radius-small);
+    &--mini > &__bar {
+      border-radius: var(--el-border-radius-small);
+    }
   }
-}
 </style>

+ 51 - 64
src/components/Pagination/index.vue

@@ -1,73 +1,61 @@
 <!-- 基于 ruoyi-vue3 的 Pagination 重构,核心是简化无用的属性,并使用 ts 重写 -->
 <script lang="ts" setup>
-import { computed, watchEffect } from 'vue'
+  import { computed, watchEffect } from 'vue'
 
-import { useAppStore } from '@/store/modules/app'
+  import { useAppStore } from '@/store/modules/app'
 
-defineOptions({ name: 'Pagination' })
+  defineOptions({ name: 'Pagination' })
 
-const props = defineProps({
-  // 总条目数
-  total: {
-    required: true,
-    type: Number
-  },
-  // 当前页数:pageNo
-  page: {
-    type: Number,
-    default: 1
-  },
-  // 每页显示条目个数:pageSize
-  limit: {
-    type: Number,
-    default: 20
-  },
-  // 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
-  // 移动端页码按钮的数量端默认值 5
-  pagerCount: {
-    type: Number,
-    default: document.body.clientWidth < 992 ? 5 : 7
-  }
-})
-const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
-// 此处解决了当全局size为small的时候分页组件样式太大的问题
-const appStore = useAppStore()
-const layoutCurrentSize = computed(() => appStore.currentSize)
-const isSmall = ref<boolean>(layoutCurrentSize.value === 'small')
-watchEffect(() => {
-  isSmall.value = layoutCurrentSize.value === 'small'
-})
+  const props = defineProps({
+    // 总条目数
+    total: { required: true, type: Number },
+    // 当前页数:pageNo
+    page: { type: Number, default: 1 },
+    // 每页显示条目个数:pageSize
+    limit: { type: Number, default: 20 },
+    // 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
+    // 移动端页码按钮的数量端默认值 5
+    pagerCount: { type: Number, default: document.body.clientWidth < 992 ? 5 : 7 },
+  })
+  const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
+  // 此处解决了当全局size为small的时候分页组件样式太大的问题
+  const appStore = useAppStore()
+  const layoutCurrentSize = computed(() => appStore.currentSize)
+  const isSmall = ref<boolean>(layoutCurrentSize.value === 'small')
+  watchEffect(() => {
+    isSmall.value = layoutCurrentSize.value === 'small'
+  })
 
-const currentPage = computed({
-  get() {
-    return props.page
-  },
-  set(val) {
-    // 触发 update:page 事件,更新 limit 属性,从而更新 pageNo
-    emit('update:page', val)
-  }
-})
-const pageSize = computed({
-  get() {
-    return props.limit
-  },
-  set(val) {
-    // 触发 update:limit 事件,更新 limit 属性,从而更新 pageSize
-    emit('update:limit', val)
+  const currentPage = computed({
+    get() {
+      return props.page
+    },
+    set(val) {
+      // 触发 update:page 事件,更新 limit 属性,从而更新 pageNo
+      emit('update:page', val)
+    },
+  })
+  const pageSize = computed({
+    get() {
+      return props.limit
+    },
+    set(val) {
+      // 触发 update:limit 事件,更新 limit 属性,从而更新 pageSize
+      emit('update:limit', val)
+    },
+  })
+  const handleSizeChange = val => {
+    // 如果修改后超过最大页面,强制跳转到第 1 页
+    if (currentPage.value * val > props.total) {
+      currentPage.value = 1
+    }
+    // 触发 pagination 事件,重新加载列表
+    emit('pagination', { page: currentPage.value, limit: val })
   }
-})
-const handleSizeChange = (val) => {
-  // 如果修改后超过最大页面,强制跳转到第 1 页
-  if (currentPage.value * val > props.total) {
-    currentPage.value = 1
+  const handleCurrentChange = val => {
+    // 触发 pagination 事件,重新加载列表
+    emit('pagination', { page: val, limit: pageSize.value })
   }
-  // 触发 pagination 事件,重新加载列表
-  emit('pagination', { page: currentPage.value, limit: val })
-}
-const handleCurrentChange = (val) => {
-  // 触发 pagination 事件,重新加载列表
-  emit('pagination', { page: val, limit: pageSize.value })
-}
 </script>
 <template>
   <el-pagination
@@ -82,6 +70,5 @@ const handleCurrentChange = (val) => {
     class="mb-15px mt-15px float-right"
     layout="total, sizes, prev, pager, next, jumper"
     @size-change="handleSizeChange"
-    @current-change="handleCurrentChange"
-  />
+    @current-change="handleCurrentChange" />
 </template>

+ 212 - 219
src/components/Qrcode/src/Qrcode.vue

@@ -1,229 +1,223 @@
 <script lang="ts" setup>
-import type { PropType} from 'vue';
-import type { QRCodeRenderersOptions } from 'qrcode';
-
-import QRCode from 'qrcode'
-import { cloneDeep } from 'lodash-es'
-import { computed, nextTick, ref, unref, watch } from 'vue'
-
-import type { QrcodeLogo } from '@/types/qrcode'
-
-import { isString } from '@/utils/is'
-import { propTypes } from '@/utils/propTypes'
-import { useDesign } from '@/hooks/web/useDesign'
-
-defineOptions({ name: 'Qrcode' })
-
-const props = defineProps({
-  // img 或者 canvas,img不支持logo嵌套
-  tag: propTypes.string.validate((v: string) => ['canvas', 'img'].includes(v)).def('canvas'),
-  // 二维码内容
-  text: {
-    type: [String, Array] as PropType<string | Recordable[]>,
-    default: null
-  },
-  // qrcode.js配置项
-  options: {
-    type: Object as PropType<QRCodeRenderersOptions>,
-    default: () => ({})
-  },
-  // 宽度
-  width: propTypes.number.def(200),
-  // logo
-  logo: {
-    type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,
-    default: ''
-  },
-  // 是否过期
-  disabled: propTypes.bool.def(false),
-  // 过期提示内容
-  disabledText: propTypes.string.def('')
-})
-
-const emit = defineEmits(['done', 'click', 'disabled-click'])
-
-const { getPrefixCls } = useDesign()
-
-const prefixCls = getPrefixCls('qrcode')
-
-const { toCanvas, toDataURL } = QRCode
-
-const loading = ref(true)
-
-const wrapRef = ref<Nullable<HTMLCanvasElement | HTMLImageElement>>(null)
-
-const renderText = computed(() => String(props.text))
-
-const wrapStyle = computed(() => {
-  return {
-    width: `${props.width  }px`,
-    height: `${props.width  }px`
+  import type { PropType } from 'vue'
+  import type { QRCodeRenderersOptions } from 'qrcode'
+
+  import QRCode from 'qrcode'
+  import { cloneDeep } from 'lodash-es'
+  import {
+    computed,
+    defineEmits,
+    defineOptions,
+    defineProps,
+    nextTick,
+    ref,
+    unref,
+    watch,
+  } from 'vue'
+
+  import type { QrcodeLogo } from '@/types/qrcode'
+
+  import { isString } from '@/utils/is'
+  import { propTypes } from '@/utils/propTypes'
+  import { useDesign } from '@/hooks/web/useDesign'
+
+  defineOptions({ name: 'Qrcode' })
+
+  const props = defineProps({
+    // img 或者 canvas,img不支持logo嵌套
+    tag: propTypes.string.validate((v: string) => ['canvas', 'img'].includes(v)).def('canvas'),
+    // 二维码内容
+    text: { type: [String, Array] as PropType<string | Recordable[]>, default: null },
+    // qrcode.js配置项
+    options: { type: Object as PropType<QRCodeRenderersOptions>, default: () => ({}) },
+    // 宽度
+    width: propTypes.number.def(200),
+    // logo
+    logo: { type: [String, Object] as PropType<Partial<QrcodeLogo> | string>, default: '' },
+    // 是否过期
+    disabled: propTypes.bool.def(false),
+    // 过期提示内容
+    disabledText: propTypes.string.def(''),
+  })
+
+  const emit = defineEmits(['done', 'click', 'disabledClick'])
+
+  const { getPrefixCls } = useDesign()
+
+  const prefixCls = getPrefixCls('qrcode')
+
+  const { toCanvas, toDataURL } = QRCode
+
+  const loading = ref(true)
+
+  const wrapRef = ref<Nullable<HTMLCanvasElement | HTMLImageElement>>(null)
+
+  const renderText = computed(() => String(props.text))
+
+  const wrapStyle = computed(() => {
+    return { width: `${props.width}px`, height: `${props.width}px` }
+  })
+
+  // copy来的方法,用于绘制圆角
+  const canvasRoundRect = (ctx: CanvasRenderingContext2D) => {
+    return (x: number, y: number, w: number, h: number, r: number) => {
+      const minSize = Math.min(w, h)
+      if (r > minSize / 2) {
+        r = minSize / 2
+      }
+      ctx.beginPath()
+      ctx.moveTo(x + r, y)
+      ctx.arcTo(x + w, y, x + w, y + h, r)
+      ctx.arcTo(x + w, y + h, x, y + h, r)
+      ctx.arcTo(x, y + h, x, y, r)
+      ctx.arcTo(x, y, x + w, y, r)
+      ctx.closePath()
+      return ctx
+    }
   }
-})
-
-const initQrcode = async () => {
-  await nextTick()
-  const options = cloneDeep(props.options || {})
-  if (props.tag === 'canvas') {
-    // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
-    options.errorCorrectionLevel =
-      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 | any = await toCanvas(
-      unref(wrapRef) as HTMLCanvasElement,
-      unref(renderText),
-      options
+
+  const createLogoCode = (canvasRef: HTMLCanvasElement) => {
+    const canvasWidth = canvasRef.width
+    const logoOptions: QrcodeLogo = Object.assign(
+      {
+        logoSize: 0.15,
+        bgColor: '#ffffff',
+        borderSize: 0.05,
+        crossOrigin: 'anonymous',
+        borderRadius: 8,
+        logoRadius: 0,
+      },
+      isString(props.logo) ? {} : props.logo
     )
-    if (props.logo) {
-      const url = await createLogoCode(canvasRef)
-      emit('done', url)
-      loading.value = false
-    } else {
-      emit('done', canvasRef.toDataURL())
-      loading.value = false
+    const {
+      logoSize = 0.15,
+      bgColor = '#ffffff',
+      borderSize = 0.05,
+      crossOrigin = 'anonymous',
+      borderRadius = 8,
+      logoRadius = 0,
+    } = logoOptions
+    const logoSrc = isString(props.logo) ? props.logo : props.logo.src
+    const logoWidth = canvasWidth * logoSize
+    const logoXY = (canvasWidth * (1 - logoSize)) / 2
+    const logoBgWidth = canvasWidth * (logoSize + borderSize)
+    const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2
+
+    const ctx = canvasRef.getContext('2d')
+    if (!ctx) return
+
+    // logo 底色
+    canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)
+    ctx.fillStyle = bgColor
+    ctx.fill()
+
+    // logo
+    const image = new Image()
+    if (crossOrigin || logoRadius) {
+      image.setAttribute('crossOrigin', crossOrigin)
+    }
+    ;(image as any).src = logoSrc
+
+    // 使用image绘制可以避免某些跨域情况
+    const drawLogoWithImage = (img: HTMLImageElement) => {
+      ctx.drawImage(img, logoXY, logoXY, logoWidth, logoWidth)
     }
-  } else {
-    const url = await toDataURL(renderText.value, {
-      errorCorrectionLevel: 'H',
-      width: props.width,
-      ...options
+
+    // 使用canvas绘制以获得更多的功能
+    const drawLogoWithCanvas = (img: HTMLImageElement) => {
+      const canvasImage = document.createElement('canvas')
+      canvasImage.width = logoXY + logoWidth
+      canvasImage.height = logoXY + logoWidth
+      const imageCanvas = canvasImage.getContext('2d')
+      if (!imageCanvas || !ctx) return
+      imageCanvas.drawImage(img, logoXY, logoXY, logoWidth, logoWidth)
+
+      canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)
+      if (!ctx) return
+      const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')
+      if (fillStyle) {
+        ctx.fillStyle = fillStyle
+        ctx.fill()
+      }
+    }
+
+    // 将 logo绘制到 canvas上
+    return new Promise((resolve: any) => {
+      image.onload = () => {
+        logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image)
+        resolve(canvasRef.toDataURL())
+      }
     })
-    ;(unref(wrapRef) as HTMLImageElement).src = url
-    emit('done', url)
-    loading.value = false
   }
-}
-
-watch(
-  () => renderText.value,
-  (val) => {
-    if (!val) return
-    initQrcode()
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-
-const createLogoCode = (canvasRef: HTMLCanvasElement) => {
-  const canvasWidth = canvasRef.width
-  const logoOptions: QrcodeLogo = Object.assign(
-    {
-      logoSize: 0.15,
-      bgColor: '#ffffff',
-      borderSize: 0.05,
-      crossOrigin: 'anonymous',
-      borderRadius: 8,
-      logoRadius: 0
-    },
-    isString(props.logo) ? {} : props.logo
-  )
-  const {
-    logoSize = 0.15,
-    bgColor = '#ffffff',
-    borderSize = 0.05,
-    crossOrigin = 'anonymous',
-    borderRadius = 8,
-    logoRadius = 0
-  } = logoOptions
-  const logoSrc = isString(props.logo) ? props.logo : props.logo.src
-  const logoWidth = canvasWidth * logoSize
-  const logoXY = (canvasWidth * (1 - logoSize)) / 2
-  const logoBgWidth = canvasWidth * (logoSize + borderSize)
-  const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2
-
-  const ctx = canvasRef.getContext('2d')
-  if (!ctx) return
-
-  // logo 底色
-  canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)
-  ctx.fillStyle = bgColor
-  ctx.fill()
-
-  // logo
-  const image = new Image()
-  if (crossOrigin || logoRadius) {
-    image.setAttribute('crossOrigin', crossOrigin)
-  }
-  ;(image as any).src = logoSrc
 
-  // 使用image绘制可以避免某些跨域情况
-  const drawLogoWithImage = (image: HTMLImageElement) => {
-    ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
+  // 得到原QrCode的大小,以便缩放得到正确的QrCode大小
+  const getOriginWidth = async (content: string, options: QRCodeRenderersOptions) => {
+    const _canvas = document.createElement('canvas')
+    await toCanvas(_canvas, content, options)
+    return _canvas.width
   }
 
-  // 使用canvas绘制以获得更多的功能
-  const drawLogoWithCanvas = (image: HTMLImageElement) => {
-    const canvasImage = document.createElement('canvas')
-    canvasImage.width = logoXY + logoWidth
-    canvasImage.height = logoXY + logoWidth
-    const imageCanvas = canvasImage.getContext('2d')
-    if (!imageCanvas || !ctx) return
-    imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
-
-    canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)
-    if (!ctx) return
-    const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')
-    if (fillStyle) {
-      ctx.fillStyle = fillStyle
-      ctx.fill()
+  // 对于内容少的QrCode,增大容错率
+  const getErrorCorrectionLevel = (content: string) => {
+    if (content.length > 36) {
+      return 'M'
+    } else if (content.length > 16) {
+      return 'Q'
+    } else {
+      return 'H'
     }
   }
 
-  // 将 logo绘制到 canvas上
-  return new Promise((resolve: any) => {
-    image.onload = () => {
-      logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image)
-      resolve(canvasRef.toDataURL())
-    }
-  })
-}
-
-// 得到原QrCode的大小,以便缩放得到正确的QrCode大小
-const getOriginWidth = async (content: string, options: QRCodeRenderersOptions) => {
-  const _canvas = document.createElement('canvas')
-  await toCanvas(_canvas, content, options)
-  return _canvas.width
-}
-
-// 对于内容少的QrCode,增大容错率
-const getErrorCorrectionLevel = (content: string) => {
-  if (content.length > 36) {
-    return 'M'
-  } else if (content.length > 16) {
-    return 'Q'
-  } else {
-    return 'H'
-  }
-}
-
-// copy来的方法,用于绘制圆角
-const canvasRoundRect = (ctx: CanvasRenderingContext2D) => {
-  return (x: number, y: number, w: number, h: number, r: number) => {
-    const minSize = Math.min(w, h)
-    if (r > minSize / 2) {
-      r = minSize / 2
+  const initQrcode = async () => {
+    await nextTick()
+    const options = cloneDeep(props.options || {})
+    if (props.tag === 'canvas') {
+      // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
+      options.errorCorrectionLevel =
+        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 | any = await toCanvas(
+        unref(wrapRef) as HTMLCanvasElement,
+        unref(renderText),
+        options
+      )
+      if (props.logo) {
+        const url = await createLogoCode(canvasRef)
+        emit('done', url)
+        loading.value = false
+      } else {
+        emit('done', canvasRef.toDataURL())
+        loading.value = false
+      }
+    } else {
+      const url = await toDataURL(renderText.value, {
+        errorCorrectionLevel: 'H',
+        width: props.width,
+        ...options,
+      })
+      ;(unref(wrapRef) as HTMLImageElement).src = url
+      emit('done', url)
+      loading.value = false
     }
-    ctx.beginPath()
-    ctx.moveTo(x + r, y)
-    ctx.arcTo(x + w, y, x + w, y + h, r)
-    ctx.arcTo(x + w, y + h, x, y + h, r)
-    ctx.arcTo(x, y + h, x, y, r)
-    ctx.arcTo(x, y, x + w, y, r)
-    ctx.closePath()
-    return ctx
   }
-}
 
-const clickCode = () => {
-  emit('click')
-}
+  watch(
+    () => renderText.value,
+    val => {
+      if (!val) return
+      initQrcode()
+    },
+    { deep: true, immediate: true }
+  )
 
-const disabledClick = () => {
-  emit('disabled-click')
-}
+  const clickCode = () => {
+    emit('click')
+  }
+
+  const disabledClick = () => {
+    emit('disabledClick')
+  }
 </script>
 
 <template>
@@ -233,8 +227,7 @@ const disabledClick = () => {
       v-if="disabled"
       :class="`${prefixCls}--disabled`"
       class="flex h-full w-full items-center left-0 top-0 justify-center absolute"
-      @click="disabledClick"
-    >
+      @click="disabledClick">
       <div class="font-bold left-[50%] top-[50%] absolute">
         <Icon :size="30" color="var(--el-color-primary)" icon="ep:refresh-right" />
         <div>{{ disabledText }}</div>
@@ -244,15 +237,15 @@ const disabledClick = () => {
 </template>
 
 <style lang="scss" scoped>
-$prefix-cls: #{$namespace}-qrcode;
+  $prefix-cls: #{$namespace}-qrcode;
 
-.#{$prefix-cls} {
-  &--disabled {
-    background: rgb(255 255 255 / 95%);
+  .#{$prefix-cls} {
+    &--disabled {
+      background: rgb(255 255 255 / 95%);
 
-    & > div {
-      transform: translate(-50%, -50%);
+      & > div {
+        transform: translate(-50%, -50%);
+      }
     }
   }
-}
 </style>

+ 59 - 72
src/components/RouterSearch/index.vue

@@ -1,81 +1,72 @@
 <script lang="ts" setup>
-import { propTypes } from '@/utils/propTypes'
-defineProps({
-  isModal: {
-    type: Boolean,
-    default: true
-  },
-  color: propTypes.string.def('')
-})
+  import { defineExpose, defineProps } from 'vue'
 
-const router = useRouter() // 路由对象
-const showSearch = ref(false) // 是否显示弹框
-const showTopSearch = ref(false) // 是否显示顶部搜索框
-const value: Ref = ref('') // 用户输入的值
+  import { propTypes } from '@/utils/propTypes'
+  defineProps({ isModal: { type: Boolean, default: true }, color: propTypes.string.def('') })
 
-const routers = router.getRoutes() // 路由对象
-const options = computed(() => {
-  // 提示选项
-  if (!value.value) {
-    return []
-  }
-  const list = routers.filter((item: any) => {
-    if (item.meta.title?.indexOf(value.value) > -1 || item.path.includes(value.value)) {
-      return true
-    }
-  })
-  return list.map((item) => {
-    return {
-      label: `${item.meta.title}${item.path}`,
-      value: item.path
+  const router = useRouter() // 路由对象
+  const showSearch = ref(false) // 是否显示弹框
+  const showTopSearch = ref(false) // 是否显示顶部搜索框
+  const value: Ref = ref('') // 用户输入的值
+
+  const routers = router.getRoutes() // 路由对象
+  const options = computed(() => {
+    // 提示选项
+    if (!value.value) {
+      return []
     }
+    const list = routers.filter(
+      (item: any) => item.meta.title?.includes(value.value) || item.path.includes(value.value)
+    )
+    return list.map(item => {
+      return { label: `${item.meta.title}${item.path}`, value: item.path }
+    })
   })
-})
 
-function remoteMethod(data) {
-  // 这里可以执行相应的操作(例如打开搜索框等)
-  value.value = data
-}
+  function remoteMethod(data) {
+    // 这里可以执行相应的操作(例如打开搜索框等)
+    value.value = data
+  }
 
-function handleChange(path) {
-  router.push({ path })
-  hiddenSearch()
-  hiddenTopSearch()
-}
+  function handleChange(path) {
+    router.push({ path })
+    hiddenSearch()
+    hiddenTopSearch()
+  }
 
-function hiddenSearch() {
-  showSearch.value = false
-}
+  function hiddenSearch() {
+    showSearch.value = false
+  }
 
-function hiddenTopSearch() {
-  showTopSearch.value = false
-}
+  function hiddenTopSearch() {
+    showTopSearch.value = false
+  }
 
-onMounted(() => {
-  window.addEventListener('keydown', listenKey)
-  window.addEventListener('click', hiddenTopSearch)
-})
+  onMounted(() => {
+    window.addEventListener('keydown', listenKey)
+    window.addEventListener('click', hiddenTopSearch)
+  })
 
-onUnmounted(() => {
-  window.removeEventListener('keydown', listenKey)
-  window.removeEventListener('click', hiddenTopSearch)
-})
+  onUnmounted(() => {
+    window.removeEventListener('keydown', listenKey)
+    window.removeEventListener('click', hiddenTopSearch)
+  })
 
-// 监听 ctrl + k
-function listenKey(event) {
-  if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
-    // 阻止触发浏览器默认事件
-    event.preventDefault()
-    showSearch.value = !showSearch.value
-    // 这里可以执行相应的操作(例如打开搜索框等)
+  // 监听 ctrl + k
+  function listenKey(event) {
+    if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
+      // 阻止触发浏览器默认事件
+      event.preventDefault()
+      showSearch.value = !showSearch.value
+      // 这里可以执行相应的操作(例如打开搜索框等)
+    }
   }
-}
 
-defineExpose({
-  openSearch: () => {
-    showSearch.value = true
-  }
-})
+  defineExpose({
+    openSearch: () => {
+      showSearch.value = true
+    },
+  })
 </script>
 
 <template>
@@ -87,14 +78,12 @@ defineExpose({
       placeholder="请输入菜单内容"
       :remote-method="remoteMethod"
       style="width: 100%"
-      @change="handleChange"
-    >
+      @change="handleChange">
       <el-option
         v-for="item in options"
         :key="item.value"
         :label="item.label"
-        :value="item.value"
-      />
+        :value="item.value" />
     </el-select>
   </ElDialog>
   <div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
@@ -108,14 +97,12 @@ defineExpose({
       class="transition-all-600 overflow-hidden"
       :class="showTopSearch ? '!w-220px ml2' : '!w-0'"
       @click.stop
-      @change="handleChange"
-    >
+      @change="handleChange">
       <el-option
         v-for="item in options"
         :key="item.value"
         :label="item.label"
-        :value="item.value"
-      />
+        :value="item.value" />
     </el-select>
   </div>
 </template>

+ 7 - 7
src/components/Tooltip/src/Tooltip.vue

@@ -1,13 +1,13 @@
 <script lang="ts" setup>
-import { propTypes } from '@/utils/propTypes'
+  import { propTypes } from '@/utils/propTypes'
 
-defineOptions({ name: 'Tooltip' })
+  defineOptions({ name: 'Tooltip' })
 
-defineProps({
-  title: propTypes.string.def(''),
-  message: propTypes.string.def(''),
-  icon: propTypes.string.def('ep:question-filled')
-})
+  defineProps({
+    title: propTypes.string.def(''),
+    message: propTypes.string.def(''),
+    icon: propTypes.string.def('ep:question-filled'),
+  })
 </script>
 <template>
   <span>{{ title }}</span>

+ 172 - 169
src/components/UploadFile/src/UploadFile.vue

@@ -1,150 +1,155 @@
 <script lang="ts" setup>
-import type { UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
-import type { UploadFile } from 'element-plus/es/components/upload/src/upload'
-
-import { isString } from '@/utils/is'
-import { propTypes } from '@/utils/propTypes'
-import { useUpload } from '@/components/UploadFile/src/useUpload'
-
-defineOptions({ name: 'UploadFile' })
-
-const props = defineProps({
-  modelValue: propTypes.oneOfType<string | string[]>([String, Array<string>]).isRequired,
-  fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
-  fileSize: propTypes.number.def(5), // 大小限制(MB)
-  limit: propTypes.number.def(5), // 数量限制
-  autoUpload: propTypes.bool.def(true), // 自动上传
-  drag: propTypes.bool.def(false), // 拖拽上传
-  isShowTip: propTypes.bool.def(true), // 是否显示提示
-  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
-  directory: propTypes.string.def(undefined) // 上传目录 ==> 非必传(默认为 undefined)
-}) ;// 消息弹窗
-const emit = defineEmits(['update:modelValue', 'update:fileSize']) ;const message = useMessage()// ========== 上传相关 ==========
-const uploadList = ref<UploadUserFile[]>([])
-const fileList = ref<UploadUserFile[]>([])
-const uploadNumber = ref<number>(0)
-
-const { uploadUrl, httpRequest } = useUpload(props.directory)
-
-// 移除校验未通过的待上传文件
-const removeRejectedFile = (file: UploadRawFile) => {
-  const index = fileList.value.findIndex((item) => item.uid === file.uid)
-  if (index > -1) {
-    fileList.value.splice(index, 1)
-  }
-}
+  import type { UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
+  import type { UploadFile } from 'element-plus/es/components/upload/src/upload'
 
-// 文件上传之前判断
-const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
-  if (fileList.value.length >= props.limit) {
-    message.error(`上传文件数量不能超过${props.limit}个!`)
-    removeRejectedFile(file)
-    return false
+  import { defineEmits, defineOptions, defineProps } from 'vue'
+
+  import { isString } from '@/utils/is'
+  import { propTypes } from '@/utils/propTypes'
+  import { useUpload } from '@/components/UploadFile/src/useUpload'
+
+  defineOptions({ name: 'UploadFile' })
+
+  const props = defineProps({
+    modelValue: propTypes.oneOfType<string | string[]>([String, Array<string>]).isRequired,
+    fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
+    fileSize: propTypes.number.def(5), // 大小限制(MB)
+    limit: propTypes.number.def(5), // 数量限制
+    autoUpload: propTypes.bool.def(true), // 自动上传
+    drag: propTypes.bool.def(false), // 拖拽上传
+    isShowTip: propTypes.bool.def(true), // 是否显示提示
+    disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
+    directory: propTypes.string.def(undefined), // 上传目录 ==> 非必传(默认为 undefined)
+  }) // 消息弹窗
+  const emit = defineEmits(['update:modelValue', 'update:fileSize'])
+  const message = useMessage() // ========== 上传相关 ==========
+  const uploadList = ref<UploadUserFile[]>([])
+  const fileList = ref<UploadUserFile[]>([])
+  const uploadNumber = ref<number>(0)
+
+  const { uploadUrl, httpRequest } = useUpload(props.directory)
+
+  // 移除校验未通过的待上传文件
+  const removeRejectedFile = (file: UploadRawFile) => {
+    const index = fileList.value.findIndex(item => item.uid === file.uid)
+    if (index > -1) {
+      fileList.value.splice(index, 1)
+    }
   }
-  let fileExtension = ''
-  if (file.name.lastIndexOf('.') > -1) {
-    fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
+
+  // 文件上传之前判断
+  const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
+    if (fileList.value.length >= props.limit) {
+      message.error(`上传文件数量不能超过${props.limit}个!`)
+      removeRejectedFile(file)
+      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.includes(type)) return true
+      return !!(fileExtension && fileExtension.includes(type))
+    })
+    const isLimit = file.size < props.fileSize * 1024 * 1024
+    if (!isImg) {
+      message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
+      removeRejectedFile(file)
+      return false
+    }
+    if (!isLimit) {
+      message.error(`上传文件大小不能超过${props.fileSize}MB!`)
+      removeRejectedFile(file)
+      return false
+    }
+    message.success('正在上传文件,请稍候...')
+    // 只有在验证通过后才增加计数器
+    uploadNumber.value++
+    return true
   }
-  const isImg = props.fileType.some((type: string) => {
-    if (file.type.includes(type)) return true
-    return !!(fileExtension && fileExtension.includes(type))
-  })
-  const isLimit = file.size < props.fileSize * 1024 * 1024
-  if (!isImg) {
-    message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
-    removeRejectedFile(file)
-    return false
+
+  // 发送文件链接列表更新
+  const emitUpdateModelValue = () => {
+    // 情况1:数组结果
+    let result: string | string[] = fileList.value.map(file => file.url!)
+    // 情况2:逗号分隔的字符串
+    if (props.limit === 1 || isString(props.modelValue)) {
+      result = result.join(',')
+    }
+    emit('update:modelValue', result)
   }
-  if (!isLimit) {
-    message.error(`上传文件大小不能超过${props.fileSize}MB!`)
-    removeRejectedFile(file)
-    return false
+
+  // 处理上传的文件发生变化
+  // const handleFileChange = (uploadFile: UploadFile): void => {
+  //   uploadRef.value.data.path = uploadFile.name
+  // }
+  // 文件上传成功
+  const handleFileSuccess: UploadProps['onSuccess'] = (res: any, uploadFile): void => {
+    message.success('上传成功')
+    const response = res as { data: string }
+    const fileSize = uploadFile.raw?.size
+    // 删除自身
+    const index = fileList.value.findIndex(
+      item => (item.response as { data?: string } | undefined)?.data === response.data
+    )
+    fileList.value.splice(index, 1)
+    uploadList.value.push({ name: response.data, url: response.data })
+    if (uploadList.value.length === uploadNumber.value) {
+      fileList.value.push(...uploadList.value)
+      uploadList.value = []
+      uploadNumber.value = 0
+      emitUpdateModelValue()
+      emit('update:fileSize', fileSize)
+    }
   }
-  message.success('正在上传文件,请稍候...')
-  // 只有在验证通过后才增加计数器
-  uploadNumber.value++
-  return true
-}
-// 处理上传的文件发生变化
-// const handleFileChange = (uploadFile: UploadFile): void => {
-//   uploadRef.value.data.path = uploadFile.name
-// }
-// 文件上传成功
-const handleFileSuccess: UploadProps['onSuccess'] = (res: any, uploadFile): void => {
-  message.success('上传成功')
-  const response = res as { data: string }
-  const fileSize = uploadFile.raw?.size
-  // 删除自身
-  const index = fileList.value.findIndex(
-    (item) => (item.response as { data?: string } | undefined)?.data === response.data
-  )
-  fileList.value.splice(index, 1)
-  uploadList.value.push({ name: response.data, url: response.data })
-  if (uploadList.value.length == uploadNumber.value) {
-    fileList.value.push(...uploadList.value)
-    uploadList.value = []
-    uploadNumber.value = 0
-    emitUpdateModelValue()
-    emit('update:fileSize', fileSize)
+  // 文件数超出提示
+  const handleExceed: UploadProps['onExceed'] = (): void => {
+    message.error(`上传文件数量不能超过${props.limit}个!`)
   }
-}
-// 文件数超出提示
-const handleExceed: UploadProps['onExceed'] = (): void => {
-  message.error(`上传文件数量不能超过${props.limit}个!`)
-}
-// 上传错误提示
-const excelUploadError: UploadProps['onError'] = (): void => {
-  message.error('导入数据失败,请您重新上传!')
-  // 上传失败时减少计数器,避免后续上传被阻塞
-  uploadNumber.value = Math.max(0, uploadNumber.value - 1)
-}
-// 删除上传文件
-const handleRemove = (file: UploadFile) => {
-  const index = fileList.value.map((f) => f.name).indexOf(file.name)
-  if (index > -1) {
-    fileList.value.splice(index, 1)
-    emitUpdateModelValue()
-    emit('update:fileSize', undefined)
+  // 上传错误提示
+  const excelUploadError: UploadProps['onError'] = (): void => {
+    message.error('导入数据失败,请您重新上传!')
+    // 上传失败时减少计数器,避免后续上传被阻塞
+    uploadNumber.value = Math.max(0, uploadNumber.value - 1)
   }
-}
-const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
-  console.log(uploadFile)
-}
-
-// 监听模型绑定值变动
-watch(
-  () => props.modelValue,
-  (val: string | string[]) => {
-    if (!val) {
-      fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
-      return
+  // 删除上传文件
+  const handleRemove = (file: UploadFile) => {
+    const index = fileList.value.map(f => f.name).indexOf(file.name)
+    if (index > -1) {
+      fileList.value.splice(index, 1)
+      emitUpdateModelValue()
+      emit('update:fileSize', undefined)
     }
+  }
+  const handlePreview: UploadProps['onPreview'] = uploadFile => {
+    console.log(uploadFile)
+  }
+
+  // 监听模型绑定值变动
+  watch(
+    () => props.modelValue,
+    (val: string | string[]) => {
+      if (!val) {
+        fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
+        return
+      }
 
-    fileList.value = [] // 保障数据为空
-    // 情况1:字符串
-    if (isString(val)) {
+      fileList.value = [] // 保障数据为空
+      // 情况1:字符串
+      if (isString(val)) {
+        fileList.value.push(
+          ...val.split(',').map(url => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
+        )
+        return
+      }
+      // 情况2:数组
       fileList.value.push(
-        ...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
+        ...(val as string[]).map(url => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
       )
-      return
-    }
-    // 情况2:数组
-    fileList.value.push(
-      ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
-    )
-  },
-  { immediate: true, deep: true }
-)
-// 发送文件链接列表更新
-const emitUpdateModelValue = () => {
-  // 情况1:数组结果
-  let result: string | string[] = fileList.value.map((file) => file.url!)
-  // 情况2:逗号分隔的字符串
-  if (props.limit === 1 || isString(props.modelValue)) {
-    result = result.join(',')
-  }
-  emit('update:modelValue', result)
-}
+    },
+    { immediate: true, deep: true }
+  )
 </script>
 <template>
   <div v-if="!disabled" class="upload-file">
@@ -166,8 +171,7 @@ const emitUpdateModelValue = () => {
       :on-success="handleFileSuccess"
       :show-file-list="true"
       class="upload-file-uploader"
-      name="file"
-    >
+      name="file">
       <el-button type="primary">
         <Icon icon="ep:upload-filled" />
         选取文件
@@ -189,8 +193,7 @@ const emitUpdateModelValue = () => {
               :underline="false"
               download
               target="_blank"
-              type="primary"
-            >
+              type="primary">
               下载
             </el-link>
           </div>
@@ -215,34 +218,34 @@ const emitUpdateModelValue = () => {
   </div>
 </template>
 <style lang="scss" scoped>
-.upload-file-uploader {
-  margin-bottom: 5px;
-}
-
-:deep(.upload-file-list .el-upload-list__item) {
-  position: relative;
-  margin-bottom: 10px;
-  line-height: 2;
-  border: 1px solid #e4e7ed;
-}
-
-:deep(.el-upload-list__item-file-name) {
-  max-width: 250px;
-}
-
-:deep(.upload-file-list .ele-upload-list__item-content) {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  color: inherit;
-}
-
-:deep(.ele-upload-list__item-content-action .el-link) {
-  margin-right: 10px;
-}
-
-.file-list-item {
-  border: 1px dashed var(--el-border-color-darker);
-  border-radius: 8px;
-}
+  .upload-file-uploader {
+    margin-bottom: 5px;
+  }
+
+  :deep(.upload-file-list .el-upload-list__item) {
+    position: relative;
+    margin-bottom: 10px;
+    line-height: 2;
+    border: 1px solid #e4e7ed;
+  }
+
+  :deep(.el-upload-list__item-file-name) {
+    max-width: 250px;
+  }
+
+  :deep(.upload-file-list .ele-upload-list__item-content) {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    color: inherit;
+  }
+
+  :deep(.ele-upload-list__item-content-action .el-link) {
+    margin-right: 10px;
+  }
+
+  .file-list-item {
+    border: 1px dashed var(--el-border-color-darker);
+    border-radius: 8px;
+  }
 </style>

+ 186 - 189
src/components/UploadFile/src/UploadImg.vue

@@ -1,90 +1,87 @@
 <script lang="ts" setup>
-import type { UploadProps } from 'element-plus'
-
-import { generateUUID } from '@/utils'
-import { propTypes } from '@/utils/propTypes'
-import { createImageViewer } from '@/components/ImageViewer'
-import { useUpload } from '@/components/UploadFile/src/useUpload'
-
-defineOptions({ name: 'UploadImg' })
-
-// 接受父组件参数
-const props = defineProps({
-  modelValue: propTypes.string.def(''),
-  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)
-  showDelete: propTypes.bool.def(true), // 是否显示删除按钮
-  showBtnText: propTypes.bool.def(true), // 是否显示按钮文字
-  directory: propTypes.string.def(undefined) // 上传目录 ==> 非必传(默认为 undefined)
-})
-
-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 uploadStyle = computed(() => ({
-  '--upload-width': props.width,
-  '--upload-height': props.height,
-  '--upload-border-radius': props.borderradius
-}))
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-// 生成组件唯一id
-const uuid = ref(`id-${  generateUUID()}`)
-// 查看图片
-const imagePreview = (imgUrl: string) => {
-  createImageViewer({
-    zIndex: 9999999,
-    urlList: [imgUrl]
+  import type { UploadProps } from 'element-plus'
+
+  import { generateUUID } from '@/utils'
+  import { propTypes } from '@/utils/propTypes'
+  import { createImageViewer } from '@/components/ImageViewer'
+  import { useUpload } from '@/components/UploadFile/src/useUpload'
+
+  defineOptions({ name: 'UploadImg' })
+
+  // 接受父组件参数
+  const props = defineProps({
+    modelValue: propTypes.string.def(''),
+    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)
+    showDelete: propTypes.bool.def(true), // 是否显示删除按钮
+    showBtnText: propTypes.bool.def(true), // 是否显示按钮文字
+    directory: propTypes.string.def(undefined), // 上传目录 ==> 非必传(默认为 undefined)
   })
-}
-
-const deleteImg = () => {
-  emit('update:modelValue', '')
-}
-
-const { uploadUrl, httpRequest } = useUpload(props.directory)
-
-const editImg = () => {
-  const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
-  dom && dom.dispatchEvent(new MouseEvent('click'))
-}
-
-const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
-  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
-  const imgType = props.fileType
-  if (!imgType.includes(rawFile.type as FileTypes))
-    message.notifyWarning('上传图片不符合所需的格式!')
-  if (!imgSize) message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M!`)
-  return imgType.includes(rawFile.type as FileTypes) && imgSize
-}
-
-// 图片上传成功提示
-const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
-  message.success('上传成功')
-  emit('update:modelValue', res.data)
-}
-
-// 图片上传错误提示
-const uploadError = () => {
-  message.notifyError('图片上传失败,请您重新上传!')
-}
+
+  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 uploadStyle = computed(() => ({
+    '--upload-width': props.width,
+    '--upload-height': props.height,
+    '--upload-border-radius': props.borderradius,
+  }))
+
+  const { t } = useI18n() // 国际化
+  const message = useMessage() // 消息弹窗
+  // 生成组件唯一id
+  const uuid = ref(`id-${generateUUID()}`)
+  // 查看图片
+  const imagePreview = (imgUrl: string) => {
+    createImageViewer({ zIndex: 9999999, urlList: [imgUrl] })
+  }
+
+  const deleteImg = () => {
+    emit('update:modelValue', '')
+  }
+
+  const { uploadUrl, httpRequest } = useUpload(props.directory)
+
+  const editImg = () => {
+    const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
+    dom && dom.dispatchEvent(new MouseEvent('click'))
+  }
+
+  const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
+    const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
+    const imgType = props.fileType
+    if (!imgType.includes(rawFile.type as FileTypes))
+      message.notifyWarning('上传图片不符合所需的格式!')
+    if (!imgSize) message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M!`)
+    return imgType.includes(rawFile.type as FileTypes) && imgSize
+  }
+
+  // 图片上传成功提示
+  const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
+    message.success('上传成功')
+    emit('update:modelValue', res.data)
+  }
+
+  // 图片上传错误提示
+  const uploadError = () => {
+    message.notifyError('图片上传失败,请您重新上传!')
+  }
 </script>
 
 <template>
@@ -94,15 +91,15 @@ const uploadError = () => {
       :accept="fileType.join(',')"
       :action="uploadUrl"
       :before-upload="beforeUpload"
-      class="upload" :class="[drag ? 'no-border' : '']"
+      class="upload"
+      :class="[drag ? 'no-border' : '']"
       :disabled="disabled"
       :drag="drag"
       :http-request="httpRequest"
       :multiple="false"
       :on-error="uploadError"
       :on-success="uploadSuccess"
-      :show-file-list="false"
-    >
+      :show-file-list="false">
       <template v-if="modelValue">
         <img :src="modelValue" class="upload-image" />
         <div class="upload-handle" @click.stop>
@@ -135,145 +132,145 @@ const uploadError = () => {
   </div>
 </template>
 <style lang="scss" scoped>
-.is-error {
-  .upload {
-    :deep(.el-upload),
-    :deep(.el-upload-dragger) {
-      border: 1px dashed var(--el-color-danger) !important;
+  .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;
+        &:hover {
+          border-color: var(--el-color-primary) !important;
+        }
       }
     }
   }
-}
-
-: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 {
+  :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;
-    }
-  }
-}
 
-.upload-box {
-  .no-border {
-    :deep(.el-upload) {
-      border: none !important;
+      &:hover {
+        border: 1px dashed var(--el-border-color-darker) !important;
+      }
     }
   }
 
-  :deep(.upload) {
-    .el-upload {
-      position: relative;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: var(--upload-width);
-      height: var(--upload-height);
-      overflow: hidden;
-      border: 1px dashed var(--el-border-color-darker);
-      border-radius: var(--upload-border-radius);
-      transition: var(--el-transition-duration-fast);
-
-      &:hover {
-        border-color: var(--el-color-primary);
-
-        .upload-handle {
-          opacity: 1;
-        }
+  .upload-box {
+    .no-border {
+      :deep(.el-upload) {
+        border: none !important;
       }
+    }
 
-      .el-upload-dragger {
+    :deep(.upload) {
+      .el-upload {
+        position: relative;
         display: flex;
         align-items: center;
         justify-content: center;
-        width: 100%;
-        height: 100%;
-        padding: 0;
+        width: var(--upload-width);
+        height: var(--upload-height);
         overflow: hidden;
-        background-color: transparent;
         border: 1px dashed var(--el-border-color-darker);
         border-radius: var(--upload-border-radius);
+        transition: var(--el-transition-duration-fast);
 
         &: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;
-      }
+          border-color: var(--el-color-primary);
 
-      .upload-image {
-        width: 100%;
-        height: 100%;
-        object-fit: contain;
-      }
+          .upload-handle {
+            opacity: 1;
+          }
+        }
 
-      .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-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: var(--upload-border-radius);
+
+          &:hover {
+            border: 1px dashed var(--el-color-primary);
+          }
+        }
 
-        .el-icon {
-          font-size: 28px;
-          color: var(--el-text-color-secondary);
+        .el-upload-dragger.is-dragover {
+          background-color: var(--el-color-primary-light-9);
+          border: 2px dashed var(--el-color-primary) !important;
         }
-      }
 
-      .upload-handle {
-        position: absolute;
-        top: 0;
-        right: 0;
-        display: flex;
-        width: 100%;
-        height: 100%;
-        cursor: pointer;
-        background: rgb(0 0 0 / 60%);
-        opacity: 0;
-        box-sizing: border-box;
-        transition: var(--el-transition-duration-fast);
-        align-items: center;
-        justify-content: center;
+        .upload-image {
+          width: 100%;
+          height: 100%;
+          object-fit: contain;
+        }
 
-        .handle-icon {
+        .upload-empty {
+          position: relative;
           display: flex;
           flex-direction: column;
           align-items: center;
           justify-content: center;
-          padding: 0 6%;
-          color: aliceblue;
+          font-size: 12px;
+          line-height: 30px;
+          color: var(--el-color-info);
 
           .el-icon {
-            margin-bottom: 40%;
-            font-size: 130%;
-            line-height: 130%;
+            font-size: 28px;
+            color: var(--el-text-color-secondary);
           }
+        }
 
-          span {
-            font-size: 85%;
-            line-height: 85%;
+        .upload-handle {
+          position: absolute;
+          top: 0;
+          right: 0;
+          display: flex;
+          width: 100%;
+          height: 100%;
+          cursor: pointer;
+          background: rgb(0 0 0 / 60%);
+          opacity: 0;
+          box-sizing: border-box;
+          transition: var(--el-transition-duration-fast);
+          align-items: center;
+          justify-content: center;
+
+          .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;
+    .el-upload__tip {
+      line-height: 18px;
+      text-align: center;
+    }
   }
-}
 </style>

+ 246 - 252
src/components/UploadFile/src/UploadImgs.vue

@@ -1,161 +1,155 @@
 <script lang="ts" setup>
-import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
-
-import { ElNotification } from 'element-plus'
-
-import { propTypes } from '@/utils/propTypes'
-import { createImageViewer } from '@/components/ImageViewer'
-import { useUpload } from '@/components/UploadFile/src/useUpload'
-
-defineOptions({ name: 'UploadImgs' })
-
-const props = defineProps({
-  modelValue: propTypes.oneOfType<string | string[]>([String, Array<string>]).isRequired,
-  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)
-  directory: propTypes.string.def(undefined) // 上传目录 ==> 非必传(默认为 undefined)
-}) ;const emit = defineEmits<UploadEmits>() ;const message = useMessage() // 消息弹窗
-// 查看图片
-const imagePreview = (imgUrl: string) => {
-  createImageViewer({
-    zIndex: 9999999,
-    urlList: [imgUrl]
+  import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
+
+  import { ElNotification } from 'element-plus'
+  import { defineEmits, defineOptions, defineProps } from 'vue'
+
+  import { propTypes } from '@/utils/propTypes'
+  import { createImageViewer } from '@/components/ImageViewer'
+  import { useUpload } from '@/components/UploadFile/src/useUpload'
+
+  defineOptions({ name: 'UploadImgs' })
+
+  const props = defineProps({
+    modelValue: propTypes.oneOfType<string | string[]>([String, Array<string>]).isRequired,
+    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)
+    directory: propTypes.string.def(undefined), // 上传目录 ==> 非必传(默认为 undefined)
   })
-}
-
-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 uploadStyle = computed(() => ({
-  '--upload-width': props.width,
-  '--upload-height': props.height,
-  '--upload-border-radius': props.borderradius
-}))
-
-const { uploadUrl, httpRequest } = useUpload(props.directory)
-
-const fileList = ref<UploadUserFile[]>([])
-const uploadNumber = ref<number>(0)
-const uploadList = ref<UploadUserFile[]>([])
-/**
- * @description 文件上传之前判断
- * @param rawFile 上传的文件
- * */
-const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
-  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
-  const imgType = props.fileType
-  const isValidType = imgType.includes(rawFile.type as FileTypes)
-  const isValidSize = imgSize
-
-  if (!isValidType)
-    ElNotification({
-      title: '温馨提示',
-      message: '上传图片不符合所需的格式!',
-      type: 'warning'
-    })
-  if (!isValidSize)
-    ElNotification({
-      title: '温馨提示',
-      message: `上传图片大小不能超过 ${props.fileSize}M!`,
-      type: 'warning'
-    })
-
-  // 只有在验证通过后才增加计数器
-  if (isValidType && isValidSize) {
-    uploadNumber.value++
+  const emit = defineEmits<UploadEmits>()
+  const message = useMessage() // 消息弹窗
+  // 查看图片
+  const imagePreview = (imgUrl: string) => {
+    createImageViewer({ zIndex: 9999999, urlList: [imgUrl] })
   }
 
-  return isValidType && isValidSize
-}
+  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 uploadStyle = computed(() => ({
+    '--upload-width': props.width,
+    '--upload-height': props.height,
+    '--upload-border-radius': props.borderradius,
+  }))
+
+  const { uploadUrl, httpRequest } = useUpload(props.directory)
+
+  const fileList = ref<UploadUserFile[]>([])
+  const uploadNumber = ref<number>(0)
+  const uploadList = ref<UploadUserFile[]>([])
+  /**
+   * @description 文件上传之前判断
+   * @param rawFile 上传的文件
+   * */
+  const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
+    const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
+    const imgType = props.fileType
+    const isValidType = imgType.includes(rawFile.type as FileTypes)
+    const isValidSize = imgSize
+
+    if (!isValidType)
+      ElNotification({ title: '温馨提示', message: '上传图片不符合所需的格式!', type: 'warning' })
+    if (!isValidSize)
+      ElNotification({
+        title: '温馨提示',
+        message: `上传图片大小不能超过 ${props.fileSize}M!`,
+        type: 'warning',
+      })
+
+    // 只有在验证通过后才增加计数器
+    if (isValidType && isValidSize) {
+      uploadNumber.value++
+    }
+
+    return isValidType && isValidSize
+  }
 
-// 图片上传成功
-interface UploadEmits {
-  (e: 'update:modelValue', value: string[]): void
-}
+  // 图片上传成功
+  interface UploadEmits {
+    (e: 'update:modelValue', value: string[]): void
+  }
 
-const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
-  message.success('上传成功')
-  const response = res as { data: string }
-  // 删除自身
-  const index = fileList.value.findIndex(
-    (item) => (item.response as { data?: string } | undefined)?.data === response.data
-  )
-  fileList.value.splice(index, 1)
-  uploadList.value.push({ name: response.data, url: response.data })
-  if (uploadList.value.length == uploadNumber.value) {
-    fileList.value.push(...uploadList.value)
-    uploadList.value = []
-    uploadNumber.value = 0
-    emitUpdateModelValue()
+  // 发送图片链接列表更新
+  const emitUpdateModelValue = () => {
+    const result: string[] = fileList.value.map(file => file.url!)
+    emit('update:modelValue', result)
   }
-}
-
-// 监听模型绑定值变动
-watch(
-  () => props.modelValue,
-  (val: string | string[]) => {
-    if (!val) {
-      fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
-      return
-    }
 
-    fileList.value = [] // 保障数据为空
-    fileList.value.push(
-      ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
+  const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
+    message.success('上传成功')
+    const response = res as { data: string }
+    // 删除自身
+    const index = fileList.value.findIndex(
+      item => (item.response as { data?: string } | undefined)?.data === response.data
     )
-  },
-  { immediate: true, deep: true }
-)
-// 发送图片链接列表更新
-const emitUpdateModelValue = () => {
-  const result: string[] = fileList.value.map((file) => file.url!)
-  emit('update:modelValue', result)
-}
-// 删除图片
-const handleRemove = (uploadFile: UploadFile) => {
-  fileList.value = fileList.value.filter(
-    (item) => item.url !== uploadFile.url || item.name !== uploadFile.name
-  )
-  emit(
-    'update:modelValue',
-    fileList.value.map((file) => file.url!)
+    fileList.value.splice(index, 1)
+    uploadList.value.push({ name: response.data, url: response.data })
+    if (uploadList.value.length === uploadNumber.value) {
+      fileList.value.push(...uploadList.value)
+      uploadList.value = []
+      uploadNumber.value = 0
+      emitUpdateModelValue()
+    }
+  }
+
+  // 监听模型绑定值变动
+  watch(
+    () => props.modelValue,
+    (val: string | string[]) => {
+      if (!val) {
+        fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
+        return
+      }
+
+      fileList.value = [] // 保障数据为空
+      fileList.value.push(
+        ...(val as string[]).map(url => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
+      )
+    },
+    { immediate: true, deep: true }
   )
-}
-
-// 图片上传错误提示
-const uploadError = () => {
-  ElNotification({
-    title: '温馨提示',
-    message: '图片上传失败,请您重新上传!',
-    type: 'error'
-  })
-  // 上传失败时减少计数器,避免后续上传被阻塞
-  uploadNumber.value = Math.max(0, uploadNumber.value - 1)
-}
-
-// 文件数超出提示
-const handleExceed = () => {
-  ElNotification({
-    title: '温馨提示',
-    message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
-    type: 'warning'
-  })
-}
+
+  // 删除图片
+  const handleRemove = (uploadFile: UploadFile) => {
+    fileList.value = fileList.value.filter(
+      item => item.url !== uploadFile.url || item.name !== uploadFile.name
+    )
+    emit(
+      'update:modelValue',
+      fileList.value.map(file => file.url!)
+    )
+  }
+
+  // 图片上传错误提示
+  const uploadError = () => {
+    ElNotification({ title: '温馨提示', message: '图片上传失败,请您重新上传!', type: 'error' })
+    // 上传失败时减少计数器,避免后续上传被阻塞
+    uploadNumber.value = Math.max(0, uploadNumber.value - 1)
+  }
+
+  // 文件数超出提示
+  const handleExceed = () => {
+    ElNotification({
+      title: '温馨提示',
+      message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
+      type: 'warning',
+    })
+  }
 </script>
 <template>
   <div class="upload-box" :style="uploadStyle">
@@ -164,7 +158,8 @@ const handleExceed = () => {
       :accept="fileType.join(',')"
       :action="uploadUrl"
       :before-upload="beforeUpload"
-      class="upload" :class="[drag ? 'no-border' : '']"
+      class="upload"
+      :class="[drag ? 'no-border' : '']"
       :disabled="disabled"
       :drag="drag"
       :http-request="httpRequest"
@@ -173,8 +168,7 @@ const handleExceed = () => {
       :on-error="uploadError"
       :on-exceed="handleExceed"
       :on-success="uploadSuccess"
-      list-type="picture-card"
-    >
+      list-type="picture-card">
       <div class="upload-empty">
         <slot name="empty">
           <Icon icon="ep:plus" />
@@ -202,135 +196,135 @@ const handleExceed = () => {
 </template>
 
 <style lang="scss" scoped>
-.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;
+  .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) {
+  :deep(.disabled) {
+    .el-upload--picture-card,
     .el-upload-dragger {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: 100%;
-      height: 100%;
-      padding: 0;
-      overflow: hidden;
+      cursor: not-allowed;
+      background: var(--el-disabled-bg-color) !important;
       border: 1px dashed var(--el-border-color-darker);
-      border-radius: var(--upload-border-radius);
 
       &:hover {
-        border: 1px dashed var(--el-color-primary);
+        border-color: var(--el-border-color-darker) !important;
       }
     }
+  }
 
-    .el-upload-dragger.is-dragover {
-      background-color: var(--el-color-primary-light-9);
-      border: 2px dashed var(--el-color-primary) !important;
+  .upload-box {
+    .no-border {
+      :deep(.el-upload--picture-card) {
+        border: none !important;
+      }
     }
 
-    .el-upload-list__item,
-    .el-upload--picture-card {
-      width: var(--upload-width);
-      height: var(--upload-height);
-      background-color: transparent;
-      border-radius: var(--upload-border-radius);
-    }
+    :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: var(--upload-border-radius);
+
+        &:hover {
+          border: 1px dashed var(--el-color-primary);
+        }
+      }
 
-    .upload-image {
-      width: 100%;
-      height: 100%;
-      object-fit: contain;
-    }
+      .el-upload-dragger.is-dragover {
+        background-color: var(--el-color-primary-light-9);
+        border: 2px dashed var(--el-color-primary) !important;
+      }
 
-    .upload-handle {
-      position: absolute;
-      top: 0;
-      right: 0;
-      display: flex;
-      width: 100%;
-      height: 100%;
-      cursor: pointer;
-      background: rgb(0 0 0 / 60%);
-      opacity: 0;
-      box-sizing: border-box;
-      transition: var(--el-transition-duration-fast);
-      align-items: center;
-      justify-content: center;
-
-      .handle-icon {
+      .el-upload-list__item,
+      .el-upload--picture-card {
+        width: var(--upload-width);
+        height: var(--upload-height);
+        background-color: transparent;
+        border-radius: var(--upload-border-radius);
+      }
+
+      .upload-image {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+
+      .upload-handle {
+        position: absolute;
+        top: 0;
+        right: 0;
         display: flex;
-        flex-direction: column;
+        width: 100%;
+        height: 100%;
+        cursor: pointer;
+        background: rgb(0 0 0 / 60%);
+        opacity: 0;
+        box-sizing: border-box;
+        transition: var(--el-transition-duration-fast);
         align-items: center;
         justify-content: center;
-        padding: 0 6%;
-        color: aliceblue;
 
-        .el-icon {
-          margin-bottom: 15%;
-          font-size: 140%;
+        .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%;
+          }
         }
+      }
 
-        span {
-          font-size: 100%;
+      .el-upload-list__item {
+        &:hover {
+          .upload-handle {
+            opacity: 1;
+          }
         }
       }
-    }
 
-    .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);
         }
       }
     }
 
-    .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;
     }
   }
-
-  .el-upload__tip {
-    line-height: 15px;
-    text-align: center;
-  }
-}
 </style>

+ 19 - 24
src/components/UploadFile/src/useUpload.ts

@@ -1,19 +1,29 @@
-import type { AxiosProgressEvent } from 'axios';
+import type { AxiosProgressEvent } from 'axios'
 import type {
   UploadProgressEvent,
   UploadRawFile,
-  UploadRequestOptions
+  UploadRequestOptions,
 } from 'element-plus/es/components/upload/src/upload'
 
 import axios from 'axios'
 
 import * as FileApi from '@/api/infra/file'
 
+/**
+ * 上传类型
+ */
+enum UPLOAD_TYPE {
+  // 客户端直接上传(只支持S3服务)
+  CLIENT = 'client',
+  // 客户端发送到后端上传
+  SERVER = 'server',
+}
+
 /**
  * 获得上传 URL
  */
 export const getUploadUrl = (): string => {
-  return `${import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL  }/infra/file/upload`
+  return `${import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL}/infra/file/upload`
 }
 
 export const useUpload = (directory?: string) => {
@@ -39,10 +49,8 @@ export const useUpload = (directory?: string) => {
       // 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持)
       return axios
         .put(presignedInfo.uploadUrl, options.file, {
-          headers: {
-            'Content-Type': options.file.type || 'application/octet-stream'
-          },
-          onUploadProgress: uploadProgressHandler
+          headers: { 'Content-Type': options.file.type || 'application/octet-stream' },
+          onUploadProgress: uploadProgressHandler,
         })
         .then(() => {
           // 1.4. 记录文件信息到后端(异步)
@@ -55,24 +63,21 @@ export const useUpload = (directory?: string) => {
       // 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
       return new Promise((resolve, reject) => {
         FileApi.updateFile({ file: options.file, directory }, uploadProgressHandler)
-          .then((res) => {
+          .then(res => {
             if (res.code === 0) {
               resolve(res)
             } else {
               reject(res)
             }
           })
-          .catch((res) => {
+          .catch(res => {
             reject(res)
           })
       })
     }
   }
 
-  return {
-    uploadUrl,
-    httpRequest
-  }
+  return { uploadUrl, httpRequest }
 }
 
 /**
@@ -88,18 +93,8 @@ function createFile(vo: FileApi.FilePresignedUrlRespVO, file: UploadRawFile, fil
     path: vo.path,
     name: fileName,
     type: file.type || 'application/octet-stream',
-    size: file.size
+    size: file.size,
   }
   FileApi.createFile(fileVo)
   return fileVo
 }
-
-/**
- * 上传类型
- */
-enum UPLOAD_TYPE {
-  // 客户端直接上传(只支持S3服务)
-  CLIENT = 'client',
-  // 客户端发送到后端上传
-  SERVER = 'server'
-}

+ 0 - 3
src/components/Verifition/index.ts

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

Разница между файлами не показана из-за своего большого размера
+ 0 - 379
src/components/Verifition/src/Verify.vue


+ 0 - 198
src/components/Verifition/src/Verify/VerifyPictureWord.vue

@@ -1,198 +0,0 @@
-<script setup type="text/babel">
-import { getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs } from 'vue'
-
-import { getCode, reqCheck } from '@/api/login'
-
-/**
- * VerifyPictureWord
- * @description 输入文字
- * */
-import { resetSize } from '../utils/util'
-import { aesEncrypt } from '../utils/ase'
-
-const props = defineProps({
-  // 弹出式 pop,固定 fixed
-  mode: {
-    type: String,
-    default: 'fixed'
-  },
-  captchaType: {
-    type: String
-  },
-  // 间隔
-  vSpace: {
-    type: Number,
-    default: 5
-  },
-  imgSize: {
-    type: Object,
-    default() {
-      return {
-        width: '310px',
-        height: '155px'
-      }
-    }
-  },
-  barSize: {
-    type: Object,
-    default() {
-      return {
-        width: '310px',
-        height: '40px'
-      }
-    }
-  }
-})
-
-const { t } = useI18n()
-const { mode, captchaType } = toRefs(props)
-const { proxy } = getCurrentInstance()
-const secretKey = ref(''); // 后端返回的ase加密秘钥
-  const userCode = ref(''); // 用户输入的验证码 暂存至pointJson,无需加密
-  const verificationCodeImg = ref(''); // 后端获取到的背景图片
-  const backToken = ref(''); // 后端返回的token值
-  const setSize = reactive({
-    imgHeight: 0,
-    imgWidth: 0,
-    barHeight: 0,
-    barWidth: 0
-  });
-  const text = ref('');
-  const barAreaColor = ref('#000');
-  const barAreaBorderColor = ref('#ddd');
-  const showRefresh = ref(true);
-  // bindingClick = ref(true)
-  const checking = ref(false)
-
-const init = () => {
-  // 加载页面
-  getPicture()
-  nextTick(() => {
-    const { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
-    setSize.imgHeight = imgHeight
-    setSize.imgWidth = imgWidth
-    setSize.barHeight = barHeight
-    setSize.barWidth = barWidth
-    proxy.$parent.$emit('ready', proxy)
-  })
-}
-onMounted(() => {
-  // 禁止拖拽
-  init()
-  proxy.$el.onselectstart = function () {
-    return false
-  }
-})
-const canvas = ref(null)
-
-const submit = () => {
-  checking.value = true
-  // 发送后端请求
-  const captchaVerification = secretKey.value
-    ? aesEncrypt(`${backToken.value  }---${  userCode.value}`, secretKey.value)
-    : `${backToken.value  }---${  userCode.value}`
-  const data = {
-    captchaType: captchaType.value,
-    pointJson: userCode.value,
-    token: backToken.value
-  }
-  reqCheck(data).then((res) => {
-    if (res.repCode === '0000') {
-      barAreaColor.value = '#4cae4c'
-      barAreaBorderColor.value = '#5cb85c'
-      text.value = t('captcha.success')
-      // bindingClick.value = false
-      if (mode.value === 'pop') {
-        setTimeout(() => {
-          proxy.$parent.clickShow = false
-          refresh()
-        }, 1500)
-      }
-      proxy.$parent.$emit('success', { captchaVerification })
-    } else {
-      proxy.$parent.$emit('error', proxy)
-      barAreaColor.value = '#d9534f'
-      barAreaBorderColor.value = '#d9534f'
-      text.value = t('captcha.fail')
-      setTimeout(() => {
-        refresh()
-      }, 700)
-    }
-    checking.value = false
-  })
-}
-
-const refresh = async function () {
-  barAreaColor.value = '#000'
-  barAreaBorderColor.value = '#ddd'
-  checking.value = false
-
-  userCode.value = ''
-
-  await getPicture()
-  showRefresh.value = true
-}
-
-// 请求背景图片和验证图片
-const getPicture = async () => {
-  const data = {
-    captchaType: captchaType.value
-  }
-  const res = await getCode(data)
-  if (res.repCode === '0000') {
-    verificationCodeImg.value = res.repData.originalImageBase64
-    backToken.value = res.repData.token
-    secretKey.value = res.repData.secretKey
-    text.value = t('captcha.code')
-  } else {
-    text.value = res.repMsg
-  }
-}
-</script>
-<template>
-  <div style="position: relative">
-    <div class="verify-img-out">
-      <div
-        :style="{
-          width: setSize.imgWidth,
-          height: setSize.imgHeight,
-          'background-size': `${setSize.imgWidth  } ${  setSize.imgHeight}`,
-          'margin-bottom': `${vSpace  }px`
-        }"
-        class="verify-img-panel"
-      >
-        <div v-show="showRefresh" class="verify-refresh" style="z-index: 3" @click="refresh">
-          <i class="iconfont icon-refresh"></i>
-        </div>
-        <img
-          ref="canvas"
-          :src="`data:image/png;base64,${  verificationCodeImg}`"
-          alt=""
-          style="display: block; width: 100%; height: 100%"
-          @click="refresh"
-        />
-      </div>
-    </div>
-    <div
-      :style="{
-        width: setSize.imgWidth,
-        color: barAreaColor,
-        'border-color': barAreaBorderColor
-        // 'line-height': barSize.height
-      }"
-      class="verify-bar-area"
-    >
-      <div class="verify-msg">{{ text }}</div>
-      <div
-        :style="{
-          'line-height': barSize.height
-        }"
-      >
-        <input v-model="userCode" class="verify-input" type="text" />
-      </div>
-      <button type="button" class="verify-btn" :disabled="checking" @click="submit">{{
-        t('captcha.verify')
-      }}</button>
-    </div>
-  </div>
-</template>

+ 0 - 252
src/components/Verifition/src/Verify/VerifyPoints.vue

@@ -1,252 +0,0 @@
-<script setup type="text/babel">
-import { getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs } from 'vue'
-
-import { getCode, reqCheck } from '@/api/login'
-
-/**
- * VerifyPoints
- * @description 点选
- * */
-import { resetSize } from './../utils/util'
-import { aesEncrypt } from './../utils/ase'
-
-const props = defineProps({
-  //弹出式pop,固定fixed
-  mode: {
-    type: String,
-    default: 'fixed'
-  },
-  captchaType: {
-    type: String
-  },
-  //间隔
-  vSpace: {
-    type: Number,
-    default: 5
-  },
-  imgSize: {
-    type: Object,
-    default() {
-      return {
-        width: '310px',
-        height: '155px'
-      }
-    }
-  },
-  barSize: {
-    type: Object,
-    default() {
-      return {
-        width: '310px',
-        height: '40px'
-      }
-    }
-  }
-})
-
-const { t } = useI18n()
-const { mode, captchaType } = toRefs(props)
-const { proxy } = getCurrentInstance()
-const secretKey = ref(''); //后端返回的ase加密秘钥
-  const checkNum = ref(3); //默认需要点击的字数
-  const fontPos = reactive([]); //选中的坐标信息
-  const checkPosArr = reactive([]); //用户点击的坐标
-  const num = ref(1); //点击的记数
-  const pointBackImgBase = ref(''); //后端获取到的背景图片
-  const poinTextList = reactive([]); //后端返回的点击字体顺序
-  const backToken = ref(''); //后端返回的token值
-  const setSize = reactive({
-    imgHeight: 0,
-    imgWidth: 0,
-    barHeight: 0,
-    barWidth: 0
-  });
-  const tempPoints = reactive([]);
-  const text = ref('');
-  const barAreaColor = ref(undefined);
-  const barAreaBorderColor = ref(undefined);
-  const showRefresh = ref(true);
-  const bindingClick = ref(true)
-
-const init = () => {
-  //加载页面
-  fontPos.splice(0, fontPos.length)
-  checkPosArr.splice(0, checkPosArr.length)
-  num.value = 1
-  getPictrue()
-  nextTick(() => {
-    const { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
-    setSize.imgHeight = imgHeight
-    setSize.imgWidth = imgWidth
-    setSize.barHeight = barHeight
-    setSize.barWidth = barWidth
-    proxy.$parent.$emit('ready', proxy)
-  })
-}
-onMounted(() => {
-  // 禁止拖拽
-  init()
-  proxy.$el.onselectstart = function () {
-    return false
-  }
-})
-const canvas = ref(null)
-const canvasClick = (e) => {
-  checkPosArr.push(getMousePos(canvas, e))
-  if (num.value == checkNum.value) {
-    num.value = createPoint(getMousePos(canvas, e))
-    //按比例转换坐标值
-    const arr = pointTransfrom(checkPosArr, setSize)
-    checkPosArr.length = 0
-    checkPosArr.push(...arr)
-    //等创建坐标执行完
-    setTimeout(() => {
-      // var flag = this.comparePos(this.fontPos, this.checkPosArr);
-      //发送后端请求
-      const captchaVerification = secretKey.value
-        ? aesEncrypt(`${backToken.value  }---${  JSON.stringify(checkPosArr)}`, secretKey.value)
-        : `${backToken.value  }---${  JSON.stringify(checkPosArr)}`
-      const data = {
-        captchaType: captchaType.value,
-        pointJson: secretKey.value
-          ? aesEncrypt(JSON.stringify(checkPosArr), secretKey.value)
-          : JSON.stringify(checkPosArr),
-        token: backToken.value
-      }
-      reqCheck(data).then((res) => {
-        if (res.repCode == '0000') {
-          barAreaColor.value = '#4cae4c'
-          barAreaBorderColor.value = '#5cb85c'
-          text.value = t('captcha.success')
-          bindingClick.value = false
-          if (mode.value == 'pop') {
-            setTimeout(() => {
-              proxy.$parent.clickShow = false
-              refresh()
-            }, 1500)
-          }
-          proxy.$parent.$emit('success', { captchaVerification })
-        } else {
-          proxy.$parent.$emit('error', proxy)
-          barAreaColor.value = '#d9534f'
-          barAreaBorderColor.value = '#d9534f'
-          text.value = t('captcha.fail')
-          setTimeout(() => {
-            refresh()
-          }, 700)
-        }
-      })
-    }, 400)
-  }
-  if (num.value < checkNum.value) {
-    num.value = createPoint(getMousePos(canvas, e))
-  }
-}
-//获取坐标
-const getMousePos = function (obj, e) {
-  const x = e.offsetX
-  const y = e.offsetY
-  return { x, y }
-}
-//创建坐标点
-const createPoint = function (pos) {
-  tempPoints.push(Object.assign({}, pos))
-  return num.value + 1
-}
-const refresh = async function () {
-  tempPoints.splice(0, tempPoints.length)
-  barAreaColor.value = '#000'
-  barAreaBorderColor.value = '#ddd'
-  bindingClick.value = true
-  fontPos.splice(0, fontPos.length)
-  checkPosArr.splice(0, checkPosArr.length)
-  num.value = 1
-  await getPictrue()
-  showRefresh.value = true
-}
-
-// 请求背景图片和验证图片
-const getPictrue = async () => {
-  const data = {
-    captchaType: captchaType.value
-  }
-  const res = await getCode(data)
-  if (res.repCode == '0000') {
-    pointBackImgBase.value = res.repData.originalImageBase64
-    backToken.value = res.repData.token
-    secretKey.value = res.repData.secretKey
-    poinTextList.value = res.repData.wordList
-    text.value = `${t('captcha.point')  }【${  poinTextList.value.join(',')  }】`
-  } else {
-    text.value = res.repMsg
-  }
-}
-//坐标转换函数
-const pointTransfrom = function (pointArr, imgSize) {
-  const newPointArr = pointArr.map((p) => {
-    const x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth))
-    const y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight))
-    return { x, y }
-  })
-  return newPointArr
-}
-</script>
-<template>
-  <div style="position: relative">
-    <div class="verify-img-out">
-      <div
-        :style="{
-          width: setSize.imgWidth,
-          height: setSize.imgHeight,
-          'background-size': `${setSize.imgWidth  } ${  setSize.imgHeight}`,
-          'margin-bottom': `${vSpace  }px`
-        }"
-        class="verify-img-panel"
-      >
-        <div v-show="showRefresh" class="verify-refresh" style="z-index: 3" @click="refresh">
-          <i class="iconfont icon-refresh"></i>
-        </div>
-        <img
-          ref="canvas"
-          :src="`data:image/png;base64,${  pointBackImgBase}`"
-          alt=""
-          style="display: block; width: 100%; height: 100%"
-          @click="bindingClick ? canvasClick($event) : undefined"
-        />
-
-        <div
-          v-for="(tempPoint, index) in tempPoints"
-          :key="index"
-          :style="{
-            'background-color': '#1abd6c',
-            color: '#fff',
-            'z-index': 9999,
-            width: '20px',
-            height: '20px',
-            'text-align': 'center',
-            'line-height': '20px',
-            'border-radius': '50%',
-            position: 'absolute',
-            top: `${parseInt(tempPoint.y - 10)  }px`,
-            left: `${parseInt(tempPoint.x - 10)  }px`
-          }"
-          class="point-area"
-        >
-          {{ index + 1 }}
-        </div>
-      </div>
-    </div>
-    <!-- 'height': this.barSize.height, -->
-    <div
-      :style="{
-        width: setSize.imgWidth,
-        color: barAreaColor,
-        'border-color': barAreaBorderColor,
-        'line-height': barSize.height
-      }"
-      class="verify-bar-area"
-    >
-      <span class="verify-msg">{{ text }}</span>
-    </div>
-  </div>
-</template>

+ 0 - 372
src/components/Verifition/src/Verify/VerifySlide.vue

@@ -1,372 +0,0 @@
-<script setup type="text/babel">
-import { getCode, reqCheck } from '@/api/login'
-
-/**
- * VerifySlide
- * @description 滑块
- * */
-import { aesEncrypt } from './../utils/ase'
-import { resetSize } from './../utils/util'
-
-const props = defineProps({
-  captchaType: {
-    type: String
-  },
-  type: {
-    type: String,
-    default: '1'
-  },
-  //弹出式pop,固定fixed
-  mode: {
-    type: String,
-    default: 'fixed'
-  },
-  vSpace: {
-    type: Number,
-    default: 5
-  },
-  explain: {
-    type: String,
-    default: ''
-  },
-  imgSize: {
-    type: Object,
-    default() {
-      return {
-        width: '310px',
-        height: '155px'
-      }
-    }
-  },
-  blockSize: {
-    type: Object,
-    default() {
-      return {
-        width: '50px',
-        height: '50px'
-      }
-    }
-  },
-  barSize: {
-    type: Object,
-    default() {
-      return {
-        width: '310px',
-        height: '30px'
-      }
-    }
-  }
-})
-
-const { t } = useI18n()
-const { mode, captchaType, type, blockSize, explain } = toRefs(props)
-const { proxy } = getCurrentInstance()
-const secretKey = ref(''); //后端返回的ase加密秘钥
-  const passFlag = ref(''); //是否通过的标识
-  const backImgBase = ref(''); //验证码背景图片
-  const blockBackImgBase = ref(''); //验证滑块的背景图片
-  const backToken = ref(''); //后端返回的唯一token值
-  const startMoveTime = ref(''); //移动开始的时间
-  const endMovetime = ref(''); //移动结束的时间
-  const tipWords = ref('');
-  const text = ref('');
-  const finishText = ref('');
-  const setSize = reactive({
-    imgHeight: 0,
-    imgWidth: 0,
-    barHeight: 0,
-    barWidth: 0
-  });
-  const moveBlockLeft = ref(undefined);
-  const leftBarWidth = ref(undefined);
-  // 移动中样式
-  const moveBlockBackgroundColor = ref(undefined);
-  const leftBarBorderColor = ref('#ddd');
-  const iconColor = ref(undefined);
-  const iconClass = ref('icon-right');
-  const status = ref(false); //鼠标状态
-  const isEnd = ref(false); //是够验证完成
-  const showRefresh = ref(true);
-  const transitionLeft = ref('');
-  const transitionWidth = ref('');
-  const startLeft = ref(0)
-
-const barArea = computed(() => {
-  return proxy.$el.querySelector('.verify-bar-area')
-})
-const handleMove = (e) => {
-  move(e)
-}
-const handleEnd = () => {
-  end()
-}
-const removeDragListeners = () => {
-  window.removeEventListener('touchmove', handleMove)
-  window.removeEventListener('mousemove', handleMove)
-  window.removeEventListener('touchend', handleEnd)
-  window.removeEventListener('mouseup', handleEnd)
-}
-const addDragListeners = () => {
-  removeDragListeners()
-  window.addEventListener('touchmove', handleMove)
-  window.addEventListener('mousemove', handleMove)
-  window.addEventListener('touchend', handleEnd)
-  window.addEventListener('mouseup', handleEnd)
-}
-const init = () => {
-  if (explain.value === '') {
-    text.value = t('captcha.slide')
-  } else {
-    text.value = explain.value
-  }
-  getPictrue()
-  nextTick(() => {
-    const { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)
-    setSize.imgHeight = imgHeight
-    setSize.imgWidth = imgWidth
-    setSize.barHeight = barHeight
-    setSize.barWidth = barWidth
-    proxy.$parent.$emit('ready', proxy)
-  })
-
-  addDragListeners()
-
-}
-watch(type, () => {
-  init()
-})
-onMounted(() => {
-  // 禁止拖拽
-  init()
-  proxy.$el.onselectstart = function () {
-    return false
-  }
-})
-onBeforeUnmount(() => {
-  removeDragListeners()
-})
-//鼠标按下
-const start = (e) => {
-  e = e || window.event
-  if (!e.touches) {
-    //兼容PC端
-    var x = e.clientX
-  } else {
-    //兼容移动端
-    var x = e.touches[0].pageX
-  }
-  startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left)
-  startMoveTime.value = Date.now() //开始滑动的时间
-  if (isEnd.value == false) {
-    text.value = ''
-    moveBlockBackgroundColor.value = '#337ab7'
-    leftBarBorderColor.value = '#337AB7'
-    iconColor.value = '#fff'
-    e.stopPropagation()
-    status.value = true
-  }
-}
-//鼠标移动
-const move = (e) => {
-  e = e || window.event
-  if (status.value && isEnd.value == false) {
-    if (!e.touches) {
-      //兼容PC端
-      var x = e.clientX
-    } else {
-      //兼容移动端
-      var x = e.touches[0].pageX
-    }
-    const bar_area_left = barArea.value.getBoundingClientRect().left
-    let move_block_left = x - bar_area_left //小方块相对于父元素的left值
-    if (
-      move_block_left >=
-      barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
-    ) {
-      move_block_left =
-        barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2
-    }
-    if (move_block_left <= 0) {
-      move_block_left = parseInt(parseInt(blockSize.value.width) / 2)
-    }
-    //拖动后小方块的left值
-    moveBlockLeft.value = `${move_block_left - startLeft.value  }px`
-    leftBarWidth.value = `${move_block_left - startLeft.value  }px`
-  }
-}
-
-//鼠标松开
-const end = () => {
-  endMovetime.value = Date.now()
-  //判断是否重合
-  if (status.value && isEnd.value == false) {
-    let moveLeftDistance = parseInt((moveBlockLeft.value || '0').replace('px', ''))
-    moveLeftDistance = (moveLeftDistance * 310) / parseInt(setSize.imgWidth)
-    const data = {
-      captchaType: captchaType.value,
-      pointJson: secretKey.value
-        ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)
-        : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
-      token: backToken.value
-    }
-    reqCheck(data).then((res) => {
-      if (res.repCode == '0000') {
-        moveBlockBackgroundColor.value = '#5cb85c'
-        leftBarBorderColor.value = '#5cb85c'
-        iconColor.value = '#fff'
-        iconClass.value = 'icon-check'
-        showRefresh.value = false
-        isEnd.value = true
-        if (mode.value == 'pop') {
-          setTimeout(() => {
-            proxy.$parent.clickShow = false
-            refresh()
-          }, 1500)
-        }
-        passFlag.value = true
-        tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s
-            ${t('captcha.success')}`
-        const captchaVerification = secretKey.value
-          ? aesEncrypt(
-              `${backToken.value  }---${  JSON.stringify({ x: moveLeftDistance, y: 5.0 })}`,
-              secretKey.value
-            )
-          : `${backToken.value  }---${  JSON.stringify({ x: moveLeftDistance, y: 5.0 })}`
-        setTimeout(() => {
-          tipWords.value = ''
-          proxy.$parent.closeBox()
-          proxy.$parent.$emit('success', { captchaVerification })
-        }, 1000)
-      } else {
-        moveBlockBackgroundColor.value = '#d9534f'
-        leftBarBorderColor.value = '#d9534f'
-        iconColor.value = '#fff'
-        iconClass.value = 'icon-close'
-        passFlag.value = false
-        setTimeout(() => {
-          refresh()
-        }, 1000)
-        proxy.$parent.$emit('error', proxy)
-        tipWords.value = t('captcha.fail')
-        setTimeout(() => {
-          tipWords.value = ''
-        }, 1000)
-      }
-    })
-    status.value = false
-  }
-}
-
-const refresh = async () => {
-  showRefresh.value = true
-  finishText.value = ''
-
-  transitionLeft.value = 'left .3s'
-  moveBlockLeft.value = 0
-
-  leftBarWidth.value = undefined
-  transitionWidth.value = 'width .3s'
-
-  leftBarBorderColor.value = '#ddd'
-  moveBlockBackgroundColor.value = '#fff'
-  iconColor.value = '#000'
-  iconClass.value = 'icon-right'
-  isEnd.value = false
-
-  await getPictrue()
-  setTimeout(() => {
-    transitionWidth.value = ''
-    transitionLeft.value = ''
-    text.value = explain.value
-  }, 300)
-}
-
-// 请求背景图片和验证图片
-const getPictrue = async () => {
-  const data = {
-    captchaType: captchaType.value
-  }
-  const res = await getCode(data)
-  if (res.repCode == '0000') {
-    backImgBase.value = res.repData.originalImageBase64
-    blockBackImgBase.value = res.repData.jigsawImageBase64
-    backToken.value = res.repData.token
-    secretKey.value = res.repData.secretKey
-  } else {
-    tipWords.value = res.repMsg
-  }
-}
-</script>
-<template>
-  <div style="position: relative">
-    <div
-      v-if="type === '2'"
-      :style="{ height: `${parseInt(setSize.imgHeight) + vSpace  }px` }"
-      class="verify-img-out"
-    >
-      <div :style="{ width: setSize.imgWidth, height: setSize.imgHeight }" class="verify-img-panel">
-        <img
-          :src="`data:image/png;base64,${  backImgBase}`"
-          alt=""
-          style="display: block; width: 100%; height: 100%"
-        />
-        <div v-show="showRefresh" class="verify-refresh" @click="refresh">
-          <i class="iconfont icon-refresh"></i>
-        </div>
-        <transition name="tips">
-          <span v-if="tipWords" :class="passFlag ? 'suc-bg' : 'err-bg'" class="verify-tips">
-            {{ tipWords }}
-          </span>
-        </transition>
-      </div>
-    </div>
-    <!-- 公共部分 -->
-    <div
-      :style="{ width: setSize.imgWidth, height: barSize.height, 'line-height': barSize.height }"
-      class="verify-bar-area"
-    >
-      <span class="verify-msg" v-text="text"></span>
-      <div
-        :style="{
-          width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,
-          height: barSize.height,
-          'border-color': leftBarBorderColor,
-          transaction: transitionWidth
-        }"
-        class="verify-left-bar"
-      >
-        <span class="verify-msg" v-text="finishText"></span>
-        <div
-          :style="{
-            width: barSize.height,
-            height: barSize.height,
-            'background-color': moveBlockBackgroundColor,
-            left: moveBlockLeft,
-            transition: transitionLeft
-          }"
-          class="verify-move-block"
-          @mousedown="start"
-          @touchstart="start"
-        >
-          <i class="verify-icon iconfont" :class="[iconClass]" :style="{ color: iconColor }"></i>
-          <div
-            v-if="type === '2'"
-            :style="{
-              width: `${Math.floor((parseInt(setSize.imgWidth) * 47) / 310)  }px`,
-              height: setSize.imgHeight,
-              top: `-${  parseInt(setSize.imgHeight) + vSpace  }px`,
-              'background-size': `${setSize.imgWidth  } ${  setSize.imgHeight}`
-            }"
-            class="verify-sub-block"
-          >
-            <img
-              :src="`data:image/png;base64,${  blockBackImgBase}`"
-              alt=""
-              style="display: block; width: 100%; height: 100%; -webkit-user-drag: none"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>

+ 0 - 5
src/components/Verifition/src/Verify/index.ts

@@ -1,5 +0,0 @@
-import VerifySlide from './VerifySlide.vue'
-import VerifyPoints from './VerifyPoints.vue'
-import VerifyPictureWord from './VerifyPictureWord.vue'
-
-export { VerifyPictureWord, VerifyPoints, VerifySlide }

+ 0 - 14
src/components/Verifition/src/utils/ase.ts

@@ -1,14 +0,0 @@
-import CryptoJS from 'crypto-js'
-/**
- * @word 要加密的内容
- * @keyWord String  服务器随机返回的关键字
-   */
-export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {
-  const key = CryptoJS.enc.Utf8.parse(keyWord)
-  const srcs = CryptoJS.enc.Utf8.parse(word)
-  const encrypted = CryptoJS.AES.encrypt(srcs, key, {
-    mode: CryptoJS.mode.ECB,
-    padding: CryptoJS.pad.Pkcs7
-  })
-  return encrypted.toString()
-}

+ 0 - 97
src/components/Verifition/src/utils/util.ts

@@ -1,97 +0,0 @@
-export function resetSize(vm) {
-  let img_width, img_height, bar_width, bar_height //图片的宽度、高度,移动条的宽度、高度
-  const EmployeeWindow = window as any
-  const parentWidth = vm.$el.parentNode.offsetWidth || EmployeeWindow.offsetWidth
-  const parentHeight = vm.$el.parentNode.offsetHeight || EmployeeWindow.offsetHeight
-  if (vm.imgSize.width.includes('%')) {
-    img_width = `${(parseInt(vm.imgSize.width) / 100) * parentWidth  }px`
-  } else {
-    img_width = vm.imgSize.width
-  }
-
-  if (vm.imgSize.height.includes('%')) {
-    img_height = `${(parseInt(vm.imgSize.height) / 100) * parentHeight  }px`
-  } else {
-    img_height = vm.imgSize.height
-  }
-
-  if (vm.barSize.width.includes('%')) {
-    bar_width = `${(parseInt(vm.barSize.width) / 100) * parentWidth  }px`
-  } else {
-    bar_width = vm.barSize.width
-  }
-
-  if (vm.barSize.height.includes('%')) {
-    bar_height = `${(parseInt(vm.barSize.height) / 100) * parentHeight  }px`
-  } else {
-    bar_height = vm.barSize.height
-  }
-
-  return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height }
-}
-
-export const _code_chars = [
-  1,
-  2,
-  3,
-  4,
-  5,
-  6,
-  7,
-  8,
-  9,
-  'a',
-  'b',
-  'c',
-  'd',
-  'e',
-  'f',
-  'g',
-  'h',
-  'i',
-  'j',
-  'k',
-  'l',
-  'm',
-  'n',
-  'o',
-  'p',
-  'q',
-  'r',
-  's',
-  't',
-  'u',
-  'v',
-  'w',
-  'x',
-  'y',
-  'z',
-  'A',
-  'B',
-  'C',
-  'D',
-  'E',
-  'F',
-  'G',
-  'H',
-  'I',
-  'J',
-  'K',
-  'L',
-  'M',
-  'N',
-  'O',
-  'P',
-  'Q',
-  'R',
-  'S',
-  'T',
-  'U',
-  'V',
-  'W',
-  'X',
-  'Y',
-  'Z'
-]
-export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
-export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']

+ 1 - 1
src/config/axios/config.ts

@@ -22,7 +22,7 @@ const config: {
    * 默认接口请求类型
    * 可选值:application/x-www-form-urlencoded multipart/form-data
    */
-  default_headers: 'application/json'
+  default_headers: 'application/json',
 }
 
 export { config }

Некоторые файлы не были показаны из-за большого количества измененных файлов