فهرست منبع

更新项目依赖,替换Ant Design Vue为Element Plus,调整ESLint配置以支持新组件库,重构首页组件以使用Element Plus的配置提供者,优化404页面样式,增强用户体验;更新样式文件以支持Element Plus主题,添加全局组件注册功能,提升代码结构和可读性。

xbx 1 هفته پیش
والد
کامیت
885d361493

+ 1 - 1
.cursor/rules/project.mdc

@@ -10,7 +10,7 @@ alwaysApply: true
 
 本项目基于以下技术栈搭建:
 - Vue 3
-- antd design vue
+
 - Pinia
 - typescript
 - rspack

+ 20 - 2
config/rspack.base.config.ts

@@ -5,7 +5,8 @@ import path from 'path';
 import { isProd } from '../plugin/getEnv';
 import sassEmbedded from 'sass-embedded';
 import ESLintPlugin from 'eslint-rspack-plugin';
-// import Components from 'unplugin-vue-components/rspack';
+ import Components from 'unplugin-vue-components/rspack';
+ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 import AutoImportPlugin from 'unplugin-auto-import/rspack';
 import packageJson from '../package.json';
 
@@ -184,7 +185,7 @@ export const baseConfig = {
         },
       ],
       dts: 'src/types/auto-imports.d.ts',
-      // resolvers: [ElementPlusResolver()],
+       resolvers: [ElementPlusResolver()],
       eslintrc: {
         enabled: false, // Default `false`
         filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`
@@ -214,6 +215,23 @@ export const baseConfig = {
         '../node_modules/.cache/.eslintcache',
       ),
     }),
+    // 添加组件自动导入插件
+    Components({
+      // 配置组件目录,默认为 src/components
+      dirs: ['src/components'],
+      // 组件的有效文件扩展名
+      extensions: ['vue'],
+      // 搜索子目录
+      deep: true,
+      // 自定义组件的解析器
+      resolvers: [ElementPlusResolver()],
+      // 生成 TypeScript 声明文件
+      dts: 'src/types/components.d.ts',
+      // 允许子目录作为组件的命名空间前缀
+      directoryAsNamespace: true,
+      // 自动导入指令
+      directives: true,
+    }),
   ],
   optimization: {
     minimizer: [

+ 47 - 33
eslint.config.mjs

@@ -7,7 +7,6 @@ import { globalIgnores } from 'eslint/config';
 import globals from 'globals';
 import eslintConfigPrettier from 'eslint-config-prettier';
 
-
 // To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
 // import { configureVueProject } from '@vue/eslint-config-typescript'
 // configureVueProject({ scriptLangs: ['ts', 'tsx'] })
@@ -19,25 +18,25 @@ export default defineConfigWithVueTs(
     files: ['**/*.{ts,mts,tsx,vue}'],
     rules: {
       '@typescript-eslint/ban-ts-comment': 'off', // 彻底关闭 ts-comment 警告
-    }
+    },
   },
   globalIgnores([
     // 构建输出
-    '**/dist/**', 
-    '**/dist-ssr/**', 
+    '**/dist/**',
+    '**/dist-ssr/**',
     '**/coverage/**',
-    
+
     // 依赖
     '**/node_modules/**',
-    
+
     // Git相关
     '**/.git/**',
-    
+
     // 缓存目录
     '**/.cache/**',
     '**/.temp/**',
     '**/.eslintcache',
-    
+
     // 编辑器目录和文件
     '**/.vscode/**',
     '**/.idea/**',
@@ -46,7 +45,7 @@ export default defineConfigWithVueTs(
     '**/*.njsproj',
     '**/*.sln',
     '**/*.sw?',
-    
+
     // 配置文件
     '**/*.config.js',
     '**/*.config.ts',
@@ -56,32 +55,32 @@ export default defineConfigWithVueTs(
     '**/rspack.*.ts',
     '**/vite.*.js',
     '**/vite.*.ts',
-    
+
     // 环境文件
     '**/.env*',
     '!**/.env.example',
-    
+
     // 包管理器文件
     '**/pnpm-lock.yaml',
     '**/package-lock.json',
     '**/yarn.lock',
-    
+
     // 历史文件
     '**/.history/**',
-    
+
     // 日志文件
     '**/*.log',
     '**/npm-debug.log*',
     '**/yarn-debug.log*',
     '**/yarn-error.log*',
     '**/pnpm-debug.log*',
-    
+
     // 系统文件
     '**/.DS_Store',
-    '**/Thumbs.db'
+    '**/Thumbs.db',
   ]),
-  { 
-    languageOptions: { 
+  {
+    languageOptions: {
       globals: {
         ...globals.browser,
         ...globals.node,
@@ -90,13 +89,13 @@ export default defineConfigWithVueTs(
       },
       parserOptions: {
         ecmaFeatures: {
-          jsx: true
-        }
-      }
+          jsx: true,
+        },
+      },
     },
     linterOptions: {
-      reportUnusedDisableDirectives: false
-    }
+      reportUnusedDisableDirectives: false,
+    },
   },
   // Vue规则配置
   pluginVue.configs['flat/essential'],
@@ -106,26 +105,40 @@ export default defineConfigWithVueTs(
     rules: {
       // 禁用烦人的 ts-comment 警告
       '@typescript-eslint/ban-ts-comment': 'off',
-      
+
       // Vue特定规则
       'vue/multi-word-component-names': 'off', // 关闭组件名必须是多词的规则
       'vue/first-attribute-linebreak': 'off', // 关闭属性换行规则
       'vue/no-unused-vars': 'off', // 关闭未使用变量的警告
-      'vue/html-self-closing': ['warn', {
-        html: {
-          void: 'always',
-          normal: 'always',
-          component: 'always'
-        }
-      }],
+      'vue/html-self-closing': 'off', // 关闭要求HTML元素自闭合的规则
       'vue/max-attributes-per-line': 'off', // 关闭每行最大属性数量限制
-      'vue/first-attribute-linebreak': 'off', // 关闭属性换行规则
+      'vue/require-default-prop': 'off', // 关闭props必须有默认值的要求
+      'vue/attribute-hyphenation': 'off', // 关闭属性必须使用连字符的要求
+      'vue/no-reserved-component-names': 'off', // 关闭组件名称限制(允许使用Dialog等HTML保留名称)
+      'vue/operator-linebreak': 'off',
+      'vue/singleline-html-element-content-newline': 'off',
+      'vue/no-v-model-argument': 'off',
+      'vue/require-prop-types': 'off',
+      'vue/quote-props': 'off',
+      'vue/no-irregular-whitespace': 'off',
+      'vue/prop-name-casing': 'off',
+      'vue/html-indent': 'off',
+
       // 通用规则
       'no-console': 'off', // 关闭控制台语句的警告
       'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
       '@typescript-eslint/no-require-imports': 'off', // 允许 require 导入
       '@typescript-eslint/ban-ts-comment': 'off', // 完全关闭 ts-comment 警告
-    }
+      'prefer-const': 'warn', // 将never reassigned的let改为warning而不是error
+      '@typescript-eslint/no-unused-expressions': [
+        'error',
+        {
+          allowShortCircuit: true,
+          allowTernary: false,
+          allowTaggedTemplates: false,
+        },
+      ],
+    },
   },
   // TypeScript规则配置
   vueTsConfigs.recommended,
@@ -136,6 +149,7 @@ export default defineConfigWithVueTs(
       '@typescript-eslint/explicit-module-boundary-types': 'off',
       '@typescript-eslint/no-explicit-any': 'off', // 关闭 any 类型的警告
       '@typescript-eslint/no-unused-vars': 'off', // 关闭未使用变量的警告
-    }
+      '@typescript-eslint/no-unsafe-function-type': 'off', // 关闭Function类型警告
+    },
   },
 );

+ 2 - 2
package.json

@@ -23,12 +23,12 @@
     ]
   },
   "dependencies": {
-    "@ant-design/icons-vue": "^7.0.1",
+    "@element-plus/icons-vue": "^2.3.1",
     "@tailwindcss/postcss": "^4.1.11",
     "@vueuse/core": "^13.5.0",
-    "ant-design-vue": "^4.2.6",
     "axios": "^1.9.0",
     "dayjs": "^1.11.13",
+    "element-plus": "^2.10.3",
     "mitt": "^3.0.1",
     "motion-v": "^1.5.0",
     "pinia": "^3.0.3",

+ 132 - 185
pnpm-lock.yaml

@@ -8,24 +8,24 @@ importers:
 
   .:
     dependencies:
-      '@ant-design/icons-vue':
-        specifier: ^7.0.1
-        version: 7.0.1(vue@3.5.14(typescript@5.8.3))
+      '@element-plus/icons-vue':
+        specifier: ^2.3.1
+        version: 2.3.1(vue@3.5.14(typescript@5.8.3))
       '@tailwindcss/postcss':
         specifier: ^4.1.11
         version: 4.1.11
       '@vueuse/core':
         specifier: ^13.5.0
         version: 13.5.0(vue@3.5.14(typescript@5.8.3))
-      ant-design-vue:
-        specifier: ^4.2.6
-        version: 4.2.6(vue@3.5.14(typescript@5.8.3))
       axios:
         specifier: ^1.9.0
         version: 1.9.0
       dayjs:
         specifier: ^1.11.13
         version: 1.11.13
+      element-plus:
+        specifier: ^2.10.3
+        version: 2.10.3(vue@3.5.14(typescript@5.8.3))
       mitt:
         specifier: ^3.0.1
         version: 3.0.1
@@ -149,17 +149,6 @@ packages:
     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
     engines: {node: '>=6.0.0'}
 
-  '@ant-design/colors@6.0.0':
-    resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==}
-
-  '@ant-design/icons-svg@4.4.2':
-    resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==}
-
-  '@ant-design/icons-vue@7.0.1':
-    resolution: {integrity: sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==}
-    peerDependencies:
-      vue: '>=3.0.3'
-
   '@babel/code-frame@7.27.1':
     resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
     engines: {node: '>=6.9.0'}
@@ -177,10 +166,6 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
 
-  '@babel/runtime@7.27.6':
-    resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/types@7.27.1':
     resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==}
     engines: {node: '>=6.9.0'}
@@ -200,11 +185,10 @@ packages:
     resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
     engines: {node: '>=10.0.0'}
 
-  '@emotion/hash@0.9.2':
-    resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
-
-  '@emotion/unitless@0.8.1':
-    resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
+  '@element-plus/icons-vue@2.3.1':
+    resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
+    peerDependencies:
+      vue: ^3.2.0
 
   '@eslint-community/eslint-utils@4.7.0':
     resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
@@ -244,6 +228,15 @@ packages:
     resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@floating-ui/core@1.7.2':
+    resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==}
+
+  '@floating-ui/dom@1.7.2':
+    resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==}
+
+  '@floating-ui/utils@0.2.10':
+    resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
   '@humanfs/core@0.19.1':
     resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
     engines: {node: '>=18.18.0'}
@@ -372,25 +365,21 @@ packages:
     resolution: {integrity: sha512-zhF5ZNaT/7pxrm8xD3dWG1b4x+FO3LbVeZZG448CjpSo5T57kPD+SaGUU1GcPpn6mexf795x0SVS49aH7/e3Dg==}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@rspack/binding-linux-arm64-musl@1.3.10':
     resolution: {integrity: sha512-o3x7IrOSCHK6lcRvdZgsSuOG1CHRQR00xiyLW7kkWmNm7t417LC9xdFWKIK62C5fKXGC5YcTbUkDMnQujespkg==}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@rspack/binding-linux-x64-gnu@1.3.10':
     resolution: {integrity: sha512-FMSi28VZhXMr15picOHFynULhqZ/FODPxRIS6uNrvPRYcbNuiO1v+VHV6X88mhOMmJ/aVF6OwjUO/o2l1FVa9Q==}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@rspack/binding-linux-x64-musl@1.3.10':
     resolution: {integrity: sha512-e0xbY9SlbRGIFz41v1yc0HfREvmgMnLV1bLmTSPK8wI2suIEJ7iYYqsocHOAOk86qLZcxVrTnL6EjUcNaRTWlg==}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@rspack/binding-win32-arm64-msvc@1.3.10':
     resolution: {integrity: sha512-YHJPvEujWeWjU6EUF6sDpaec9rsOtKVvy16YCtGaxRpDQXqfuxibnp6Ge0ZTTrY+joRiWehRA9OUI+3McqI+QA==}
@@ -435,12 +424,12 @@ packages:
     resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==}
     engines: {node: '>=16.0.0'}
 
-  '@simonwep/pickr@1.8.2':
-    resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
-
   '@sinclair/typebox@0.27.8':
     resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
 
+  '@sxzz/popperjs-es@2.11.7':
+    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
+
   '@tailwindcss/node@4.1.11':
     resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==}
 
@@ -479,28 +468,24 @@ packages:
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@tailwindcss/oxide-linux-arm64-musl@4.1.11':
     resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@tailwindcss/oxide-linux-x64-gnu@4.1.11':
     resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@tailwindcss/oxide-linux-x64-musl@4.1.11':
     resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@tailwindcss/oxide-wasm32-wasi@4.1.11':
     resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==}
@@ -596,6 +581,12 @@ packages:
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
+  '@types/lodash-es@4.17.12':
+    resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
+
+  '@types/lodash@4.17.20':
+    resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
+
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
@@ -626,6 +617,9 @@ packages:
   '@types/sockjs@0.3.36':
     resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==}
 
+  '@types/web-bluetooth@0.0.16':
+    resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
+
   '@types/web-bluetooth@0.0.20':
     resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
 
@@ -748,12 +742,18 @@ packages:
     peerDependencies:
       vue: ^3.5.0
 
+  '@vueuse/core@9.13.0':
+    resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
+
   '@vueuse/metadata@10.11.1':
     resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
 
   '@vueuse/metadata@13.5.0':
     resolution: {integrity: sha512-euhItU3b0SqXxSy8u1XHxUCdQ8M++bsRs+TYhOLDU/OykS7KvJnyIFfep0XM5WjIFry9uAPlVSjmVHiqeshmkw==}
 
+  '@vueuse/metadata@9.13.0':
+    resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
+
   '@vueuse/shared@10.11.1':
     resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
 
@@ -762,6 +762,9 @@ packages:
     peerDependencies:
       vue: ^3.5.0
 
+  '@vueuse/shared@9.13.0':
+    resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
+
   '@webassemblyjs/ast@1.14.1':
     resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
 
@@ -887,12 +890,6 @@ packages:
     resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
     engines: {node: '>=12'}
 
-  ant-design-vue@4.2.6:
-    resolution: {integrity: sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==}
-    engines: {node: '>=12.22.0'}
-    peerDependencies:
-      vue: '>=3.2.0'
-
   anymatch@3.1.3:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
@@ -922,9 +919,6 @@ packages:
   array-flatten@1.1.1:
     resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
 
-  array-tree-filter@2.1.0:
-    resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==}
-
   array-unique@0.3.2:
     resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==}
     engines: {node: '>=0.10.0'}
@@ -1147,9 +1141,6 @@ packages:
     resolution: {integrity: sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==}
     engines: {node: '>= 0.8.0'}
 
-  compute-scroll-into-view@1.0.20:
-    resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
-
   concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
 
@@ -1198,9 +1189,6 @@ packages:
     resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
     engines: {node: '>=0.10.0'}
 
-  core-js@3.43.0:
-    resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==}
-
   core-util-is@1.0.3:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
 
@@ -1362,12 +1350,6 @@ packages:
     resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==}
     engines: {node: '>=6'}
 
-  dom-align@1.12.4:
-    resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==}
-
-  dom-scroll-into-view@2.0.1:
-    resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==}
-
   dom-serializer@0.2.2:
     resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
 
@@ -1403,6 +1385,11 @@ packages:
   electron-to-chromium@1.5.155:
     resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==}
 
+  element-plus@2.10.3:
+    resolution: {integrity: sha512-OLpf0iekuvWJrz1+H9ybvem6TYTKSNk6L1QDA3tYq2YWbogKXJnWpHG1UAGKR1B7gx+vUH7M15VIH3EijE9Kgw==}
+    peerDependencies:
+      vue: ^3.2.0
+
   emoji-regex@10.4.0:
     resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
 
@@ -2135,10 +2122,6 @@ packages:
     resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
     engines: {node: '>=0.10.0'}
 
-  is-plain-object@3.0.1:
-    resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
-    engines: {node: '>=0.10.0'}
-
   is-promise@4.0.0:
     resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
 
@@ -2314,28 +2297,24 @@ packages:
     engines: {node: '>= 12.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   lightningcss-linux-arm64-musl@1.30.1:
     resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
     engines: {node: '>= 12.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   lightningcss-linux-x64-gnu@1.30.1:
     resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
     engines: {node: '>= 12.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   lightningcss-linux-x64-musl@1.30.1:
     resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
     engines: {node: '>= 12.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   lightningcss-win32-arm64-msvc@1.30.1:
     resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
@@ -2388,6 +2367,13 @@ packages:
   lodash-es@4.17.21:
     resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
 
+  lodash-unified@1.0.3:
+    resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
+    peerDependencies:
+      '@types/lodash-es': '*'
+      lodash: '*'
+      lodash-es: '*'
+
   lodash.merge@4.6.2:
     resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
 
@@ -2398,10 +2384,6 @@ packages:
     resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
     engines: {node: '>=18'}
 
-  loose-envify@1.4.0:
-    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
-    hasBin: true
-
   magic-string@0.30.17:
     resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
 
@@ -2432,6 +2414,9 @@ packages:
     resolution: {integrity: sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==}
     engines: {node: '>= 4.0.0'}
 
+  memoize-one@6.0.0:
+    resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
+
   merge-descriptors@1.0.3:
     resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
 
@@ -2564,9 +2549,6 @@ packages:
     resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
     engines: {node: '>=0.10.0'}
 
-  nanopop@2.4.2:
-    resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==}
-
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
@@ -2596,6 +2578,9 @@ packages:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
 
+  normalize-wheel-es@1.2.0:
+    resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
+
   nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
 
@@ -2940,9 +2925,6 @@ packages:
   requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
 
-  resize-observer-polyfill@1.5.1:
-    resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
-
   resolve-from@4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -3163,9 +3145,6 @@ packages:
     resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==}
     engines: {node: '>= 10.13.0'}
 
-  scroll-into-view-if-needed@2.2.31:
-    resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
-
   scule@1.3.0:
     resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
 
@@ -3230,9 +3209,6 @@ packages:
     resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
     engines: {node: '>=8'}
 
-  shallow-equal@1.2.1:
-    resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
-
   shebang-command@2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}
@@ -3409,9 +3385,6 @@ packages:
     peerDependencies:
       webpack: ^5.27.0
 
-  stylis@4.3.6:
-    resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
-
   superjson@2.2.2:
     resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
     engines: {node: '>=16'}
@@ -3492,10 +3465,6 @@ packages:
     peerDependencies:
       tslib: ^2
 
-  throttle-debounce@5.0.2:
-    resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
-    engines: {node: '>=12.22'}
-
   thunky@1.1.0:
     resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==}
 
@@ -3746,12 +3715,6 @@ packages:
     peerDependencies:
       vue: ^3.2.0
 
-  vue-types@3.0.2:
-    resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==}
-    engines: {node: '>=10.15.0'}
-    peerDependencies:
-      vue: ^3.0.0
-
   vue@3.5.14:
     resolution: {integrity: sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==}
     peerDependencies:
@@ -3760,9 +3723,6 @@ packages:
       typescript:
         optional: true
 
-  warning@4.0.3:
-    resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
-
   watchpack@2.4.2:
     resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
     engines: {node: '>=10.13.0'}
@@ -3939,18 +3899,6 @@ snapshots:
       '@jridgewell/gen-mapping': 0.3.8
       '@jridgewell/trace-mapping': 0.3.25
 
-  '@ant-design/colors@6.0.0':
-    dependencies:
-      '@ctrl/tinycolor': 3.6.1
-
-  '@ant-design/icons-svg@4.4.2': {}
-
-  '@ant-design/icons-vue@7.0.1(vue@3.5.14(typescript@5.8.3))':
-    dependencies:
-      '@ant-design/colors': 6.0.0
-      '@ant-design/icons-svg': 4.4.2
-      vue: 3.5.14(typescript@5.8.3)
-
   '@babel/code-frame@7.27.1':
     dependencies:
       '@babel/helper-validator-identifier': 7.27.1
@@ -3965,8 +3913,6 @@ snapshots:
     dependencies:
       '@babel/types': 7.27.1
 
-  '@babel/runtime@7.27.6': {}
-
   '@babel/types@7.27.1':
     dependencies:
       '@babel/helper-string-parser': 7.27.1
@@ -3982,9 +3928,9 @@ snapshots:
 
   '@discoveryjs/json-ext@0.5.7': {}
 
-  '@emotion/hash@0.9.2': {}
-
-  '@emotion/unitless@0.8.1': {}
+  '@element-plus/icons-vue@2.3.1(vue@3.5.14(typescript@5.8.3))':
+    dependencies:
+      vue: 3.5.14(typescript@5.8.3)
 
   '@eslint-community/eslint-utils@4.7.0(eslint@9.26.0(jiti@2.4.2))':
     dependencies:
@@ -4030,6 +3976,17 @@ snapshots:
       '@eslint/core': 0.13.0
       levn: 0.4.1
 
+  '@floating-ui/core@1.7.2':
+    dependencies:
+      '@floating-ui/utils': 0.2.10
+
+  '@floating-ui/dom@1.7.2':
+    dependencies:
+      '@floating-ui/core': 1.7.2
+      '@floating-ui/utils': 0.2.10
+
+  '@floating-ui/utils@0.2.10': {}
+
   '@humanfs/core@0.19.1': {}
 
   '@humanfs/node@0.16.6':
@@ -4247,13 +4204,10 @@ snapshots:
 
   '@rspack/lite-tapable@1.0.1': {}
 
-  '@simonwep/pickr@1.8.2':
-    dependencies:
-      core-js: 3.43.0
-      nanopop: 2.4.2
-
   '@sinclair/typebox@0.27.8': {}
 
+  '@sxzz/popperjs-es@2.11.7': {}
+
   '@tailwindcss/node@4.1.11':
     dependencies:
       '@ampproject/remapping': 2.3.0
@@ -4408,6 +4362,12 @@ snapshots:
 
   '@types/json-schema@7.0.15': {}
 
+  '@types/lodash-es@4.17.12':
+    dependencies:
+      '@types/lodash': 4.17.20
+
+  '@types/lodash@4.17.20': {}
+
   '@types/mime@1.3.5': {}
 
   '@types/node-forge@1.3.11':
@@ -4443,6 +4403,8 @@ snapshots:
     dependencies:
       '@types/node': 22.15.19
 
+  '@types/web-bluetooth@0.0.16': {}
+
   '@types/web-bluetooth@0.0.20': {}
 
   '@types/web-bluetooth@0.0.21': {}
@@ -4638,10 +4600,22 @@ snapshots:
       '@vueuse/shared': 13.5.0(vue@3.5.14(typescript@5.8.3))
       vue: 3.5.14(typescript@5.8.3)
 
+  '@vueuse/core@9.13.0(vue@3.5.14(typescript@5.8.3))':
+    dependencies:
+      '@types/web-bluetooth': 0.0.16
+      '@vueuse/metadata': 9.13.0
+      '@vueuse/shared': 9.13.0(vue@3.5.14(typescript@5.8.3))
+      vue-demi: 0.14.10(vue@3.5.14(typescript@5.8.3))
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
   '@vueuse/metadata@10.11.1': {}
 
   '@vueuse/metadata@13.5.0': {}
 
+  '@vueuse/metadata@9.13.0': {}
+
   '@vueuse/shared@10.11.1(vue@3.5.14(typescript@5.8.3))':
     dependencies:
       vue-demi: 0.14.10(vue@3.5.14(typescript@5.8.3))
@@ -4653,6 +4627,13 @@ snapshots:
     dependencies:
       vue: 3.5.14(typescript@5.8.3)
 
+  '@vueuse/shared@9.13.0(vue@3.5.14(typescript@5.8.3))':
+    dependencies:
+      vue-demi: 0.14.10(vue@3.5.14(typescript@5.8.3))
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
   '@webassemblyjs/ast@1.14.1':
     dependencies:
       '@webassemblyjs/helper-numbers': 1.13.2
@@ -4796,32 +4777,6 @@ snapshots:
 
   ansi-styles@6.2.1: {}
 
-  ant-design-vue@4.2.6(vue@3.5.14(typescript@5.8.3)):
-    dependencies:
-      '@ant-design/colors': 6.0.0
-      '@ant-design/icons-vue': 7.0.1(vue@3.5.14(typescript@5.8.3))
-      '@babel/runtime': 7.27.6
-      '@ctrl/tinycolor': 3.6.1
-      '@emotion/hash': 0.9.2
-      '@emotion/unitless': 0.8.1
-      '@simonwep/pickr': 1.8.2
-      array-tree-filter: 2.1.0
-      async-validator: 4.2.5
-      csstype: 3.1.3
-      dayjs: 1.11.13
-      dom-align: 1.12.4
-      dom-scroll-into-view: 2.0.1
-      lodash: 4.17.21
-      lodash-es: 4.17.21
-      resize-observer-polyfill: 1.5.1
-      scroll-into-view-if-needed: 2.2.31
-      shallow-equal: 1.2.1
-      stylis: 4.3.6
-      throttle-debounce: 5.0.2
-      vue: 3.5.14(typescript@5.8.3)
-      vue-types: 3.0.2(vue@3.5.14(typescript@5.8.3))
-      warning: 4.0.3
-
   anymatch@3.1.3:
     dependencies:
       normalize-path: 3.0.0
@@ -4844,8 +4799,6 @@ snapshots:
 
   array-flatten@1.1.1: {}
 
-  array-tree-filter@2.1.0: {}
-
   array-unique@0.3.2: {}
 
   arraybuffer.prototype.slice@1.0.4:
@@ -5124,8 +5077,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  compute-scroll-into-view@1.0.20: {}
-
   concat-map@0.0.1: {}
 
   confbox@0.1.8: {}
@@ -5158,8 +5109,6 @@ snapshots:
 
   copy-descriptor@0.1.1: {}
 
-  core-js@3.43.0: {}
-
   core-util-is@1.0.3: {}
 
   cors@2.8.5:
@@ -5296,10 +5245,6 @@ snapshots:
     dependencies:
       '@leichtgewicht/ip-codec': 2.0.5
 
-  dom-align@1.12.4: {}
-
-  dom-scroll-into-view@2.0.1: {}
-
   dom-serializer@0.2.2:
     dependencies:
       domelementtype: 2.3.0
@@ -5334,6 +5279,27 @@ snapshots:
 
   electron-to-chromium@1.5.155: {}
 
+  element-plus@2.10.3(vue@3.5.14(typescript@5.8.3)):
+    dependencies:
+      '@ctrl/tinycolor': 3.6.1
+      '@element-plus/icons-vue': 2.3.1(vue@3.5.14(typescript@5.8.3))
+      '@floating-ui/dom': 1.7.2
+      '@popperjs/core': '@sxzz/popperjs-es@2.11.7'
+      '@types/lodash': 4.17.20
+      '@types/lodash-es': 4.17.12
+      '@vueuse/core': 9.13.0(vue@3.5.14(typescript@5.8.3))
+      async-validator: 4.2.5
+      dayjs: 1.11.13
+      escape-html: 1.0.3
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
+      memoize-one: 6.0.0
+      normalize-wheel-es: 1.2.0
+      vue: 3.5.14(typescript@5.8.3)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+
   emoji-regex@10.4.0: {}
 
   emoji-regex@8.0.0: {}
@@ -6162,8 +6128,6 @@ snapshots:
     dependencies:
       isobject: 3.0.1
 
-  is-plain-object@3.0.1: {}
-
   is-promise@4.0.0: {}
 
   is-regex@1.2.1:
@@ -6394,6 +6358,12 @@ snapshots:
 
   lodash-es@4.17.21: {}
 
+  lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
+    dependencies:
+      '@types/lodash-es': 4.17.12
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+
   lodash.merge@4.6.2: {}
 
   lodash@4.17.21: {}
@@ -6406,10 +6376,6 @@ snapshots:
       strip-ansi: 7.1.0
       wrap-ansi: 9.0.0
 
-  loose-envify@1.4.0:
-    dependencies:
-      js-tokens: 4.0.0
-
   magic-string@0.30.17:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.0
@@ -6435,6 +6401,8 @@ snapshots:
       tree-dump: 1.0.2(tslib@2.8.1)
       tslib: 2.8.1
 
+  memoize-one@6.0.0: {}
+
   merge-descriptors@1.0.3: {}
 
   merge-descriptors@2.0.0: {}
@@ -6574,8 +6542,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  nanopop@2.4.2: {}
-
   natural-compare@1.4.0: {}
 
   negotiator@0.6.3: {}
@@ -6592,6 +6558,8 @@ snapshots:
 
   normalize-path@3.0.0: {}
 
+  normalize-wheel-es@1.2.0: {}
+
   nth-check@2.1.1:
     dependencies:
       boolbase: 1.0.0
@@ -6943,8 +6911,6 @@ snapshots:
 
   requires-port@1.0.0: {}
 
-  resize-observer-polyfill@1.5.1: {}
-
   resolve-from@4.0.0: {}
 
   resolve-url@0.2.1: {}
@@ -7124,10 +7090,6 @@ snapshots:
       ajv-formats: 2.1.1(ajv@8.17.1)
       ajv-keywords: 5.1.0(ajv@8.17.1)
 
-  scroll-into-view-if-needed@2.2.31:
-    dependencies:
-      compute-scroll-into-view: 1.0.20
-
   scule@1.3.0: {}
 
   select-hose@2.0.0: {}
@@ -7244,8 +7206,6 @@ snapshots:
     dependencies:
       kind-of: 6.0.3
 
-  shallow-equal@1.2.1: {}
-
   shebang-command@2.0.0:
     dependencies:
       shebang-regex: 3.0.0
@@ -7462,8 +7422,6 @@ snapshots:
     dependencies:
       webpack: 5.99.8
 
-  stylis@4.3.6: {}
-
   superjson@2.2.2:
     dependencies:
       copy-anything: 3.0.5
@@ -7562,8 +7520,6 @@ snapshots:
     dependencies:
       tslib: 2.8.1
 
-  throttle-debounce@5.0.2: {}
-
   thunky@1.1.0: {}
 
   tinyglobby@0.2.13:
@@ -7848,11 +7804,6 @@ snapshots:
       '@vue/devtools-api': 6.6.4
       vue: 3.5.14(typescript@5.8.3)
 
-  vue-types@3.0.2(vue@3.5.14(typescript@5.8.3)):
-    dependencies:
-      is-plain-object: 3.0.1
-      vue: 3.5.14(typescript@5.8.3)
-
   vue@3.5.14(typescript@5.8.3):
     dependencies:
       '@vue/compiler-dom': 3.5.14
@@ -7863,10 +7814,6 @@ snapshots:
     optionalDependencies:
       typescript: 5.8.3
 
-  warning@4.0.3:
-    dependencies:
-      loose-envify: 1.4.0
-
   watchpack@2.4.2:
     dependencies:
       glob-to-regexp: 0.4.1

+ 4 - 8
src/App.vue

@@ -1,13 +1,9 @@
-<script setup lang="ts">
-import { App, ConfigProvider } from 'ant-design-vue';
-</script>
+<script setup lang="ts"></script>
 
 <template>
-  <ConfigProvider>
-    <App>
-      <router-view />
-    </App>
-  </ConfigProvider>
+  <el-config-provider>
+    <router-view />
+  </el-config-provider>
 </template>
 
 <style scoped lang="css">

BIN
src/assets/imgs/login.png


+ 204 - 0
src/components/Dialog/index.vue

@@ -0,0 +1,204 @@
+<template>
+  <el-dialog v-bind="megeProps" :fullscreen="fullscreen" class="com-dialog">
+    <template #header v-if="props.header !== false">
+      <el-row>
+        <div class="el-dialog__title zt-dialog__title">
+          <slot name="title">{{ props.title }}</slot>
+          <BasicHelp
+            class="dialog-title-help`"
+            v-if="props.helpMessage"
+            :text="helpMessage"
+            placement="top"
+          />
+        </div>
+        <el-space size="default">
+          <button type="button" class="el-dialog__headerbtn" @click="fullscreen = !fullscreen" v-if="props.showFullscreen">
+            <el-icon class="el-dialog__close"><FullScreen /></el-icon>
+          </button>
+          <button type="button" class="el-dialog__headerbtn" @click="dialogVisible = false" v-if="props.showClose">
+            <el-icon class="el-dialog__close"><Close /></el-icon>
+          </button>
+        </el-space>
+      </el-row>
+    </template>
+
+    <ElScrollbar v-if="props.scroll" :style="dialogStyle">
+      <slot></slot>
+    </ElScrollbar>
+    <slot v-else></slot>
+
+    <template v-if="props.footer" #footer>
+      <slot name="footer">
+        <template v-if="typeof props.footer === 'boolean'">
+          <el-button v-bind="props.cancelButtonProps" @click="handleCancel">{{
+            props.cancelText
+          }}</el-button>
+          <el-button
+            type="primary"
+            v-bind="props.okButtonProps"
+            :loading="okLoading"
+            @click="handleOk"
+            >{{ props.okText }}</el-button
+          >
+        </template>
+        <template v-else-if="typeof props.footer === 'function'">
+          <component :is="props.footer()" />
+        </template>
+      </slot>
+    </template>
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import { defineProps, defineSlots, computed, ref, type VNode, useAttrs,watch,nextTick   } from 'vue'
+import type { ButtonProps } from 'element-plus'
+import { DialogModalProps } from './props'
+import { Close, FullScreen } from '@element-plus/icons-vue'
+import { BasicHelp } from '@/components/helpMessage'
+import { isNumber } from '@/utils/is'
+
+defineOptions({
+  name: 'Dialog'
+})
+
+interface Props extends DialogModalProps {
+  footer?: boolean | (() => VNode)
+  okText?: string
+  cancelText?: string
+  okButtonProps?: Partial<ButtonProps>
+  cancelButtonProps?: Partial<ButtonProps>
+  onOk?: () => void
+  onBeforeOk?: () => Promise<boolean>
+  onCancel?: () => void
+  lockScroll?: boolean
+  closeOnClickModal?: boolean
+  closeOnPressEscape?: boolean
+  modal?: boolean
+  showFullscreen?: boolean
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  footer: true,
+  okText: '确认',
+  showClose: false,
+  cancelText: '取消',
+  lockScroll: true,
+  closeOnClickModal: true,
+  closeOnPressEscape: true,
+  modal: true,
+  showFullscreen: true
+})
+
+const attrs = useAttrs()
+
+const megeProps = computed(() => {
+  return {
+    ...props,
+    ...attrs
+  }
+})
+
+defineSlots<{
+  title: () => VNode
+  footer: () => VNode
+  default: () => VNode
+}>()
+
+const okLoading = ref(false)
+const fullscreen = ref(false)
+
+const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
+
+watch(
+  () => fullscreen.value,
+  async (val: boolean) => {
+    await nextTick()
+    if (val) {
+      const windowHeight = document.documentElement.offsetHeight
+      dialogHeight.value = `${windowHeight - 55 - 60 - (props.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 emit = defineEmits(['update:modelValue'])
+
+const dialogVisible = defineModel('modelValue', {
+  type: Boolean,
+  default: false
+})
+
+const handleOk = async () => {
+  if (props.onBeforeOk) {
+    try {
+      okLoading.value = true
+      const flag = await props.onBeforeOk()
+      if (flag) {
+        okLoading.value = false
+        dialogVisible.value = false
+      }
+    } catch (error) {
+      okLoading.value = false
+    }
+  } else {
+    props.onOk?.()
+    dialogVisible.value = false
+  }
+}
+
+const handleCancel = () => {
+  props.onCancel?.()
+  dialogVisible.value = false
+}
+</script>
+<style lang="scss" scoped>
+:deep(.el-dialog__headerbtn) {
+  position: static;
+  width: auto;
+  height: auto;
+}
+
+.com-dialog {
+  .#{$elNamespace}-overlay-dialog {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .#{$elNamespace}-dialog {
+    margin: 0 !important;
+
+    &__header {
+      height: 54px;
+      padding: 0;
+      margin-right: 0 !important;
+      border-bottom: 1px solid var(--el-border-color);
+    }
+
+    &__body {
+      padding: 15px !important;
+    }
+
+    &__footer {
+      border-top: 1px solid var(--el-border-color);
+    }
+
+    &__headerbtn {
+      top: 0;
+    }
+  }
+}
+
+.zt-dialog__title {
+  flex: 1;
+}
+</style>

+ 84 - 0
src/components/Dialog/props.ts

@@ -0,0 +1,84 @@
+import type { ElDialog, DialogProps as ElDialogProps, ButtonProps } from 'element-plus'
+import type { MaybeRef } from 'vue'
+
+type RawSlot = {
+  [name: string]: unknown
+  $stable?: boolean
+}
+
+
+export interface DialogModalProps extends Partial<ElDialogProps> {
+ 
+  //弹窗标题
+  title: string
+  // 底部插槽
+  footer?: boolean  | (() => VNode)
+
+  //顶部插槽
+  header?: boolean  | VNode | (() => VNode) | RawSlot
+
+  //高度
+  maxHeight?: string | number
+
+  //是否显示滚动条
+  scroll?: boolean
+
+  // 确认按钮文本
+  okText?: string
+
+  // 取消按钮文本
+  cancelText?: string
+
+  // 是否显示确认按钮
+  showOk?: boolean
+
+  //是否显示取消按钮
+  hideCancel?: boolean
+
+  // 是否显示全屏按钮
+  showFullscreen?: boolean
+
+  // 是否在点击模态框时关闭
+  closeOnClickModal?: boolean
+
+  // 是否在按下 ESC 键时关闭
+  closeOnPressEscape?: boolean
+
+  // 是否显示关闭按钮
+  showClose?: boolean
+
+  // 是否显示模态框
+  modal?: boolean
+
+  // 是否显示遮罩层
+  lockScroll?: boolean
+
+  // 确认按钮的属性
+  okButtonProps?: Partial<ButtonProps>
+
+  // 取消按钮的属性
+  cancelButtonProps?: Partial<ButtonProps>
+
+  // 点击确认按钮时的回调
+  onOk?: () => void
+
+  // 点击确认按钮前的回调,返回 false 时阻止关闭
+  onBeforeOk?: () => Promise<boolean>
+
+  // 点击取消按钮时的回调
+  onCancel?: () => void
+
+  // 帮助信息
+  helpMessage?: string
+}
+
+
+export interface DialogContentProps {
+  [key: string]: any
+}
+
+export interface DialogProps  {
+  modalProps?: DialogModalProps,
+  contentProps?: DialogContentProps,
+  content: string | Component | (() => Promise<Component>) | VNode | (() => VNode) | RawSlot
+}

+ 215 - 0
src/components/Dialog/useDialog.ts

@@ -0,0 +1,215 @@
+import {
+  ref,
+  defineAsyncComponent,
+  getCurrentInstance,
+  computed,
+  h,
+  createApp,
+  unref,
+  provide,
+  reactive
+} from 'vue'
+import type { Component, App, VNode, ComputedRef } from 'vue'
+import type { ElDialog } from 'element-plus'
+
+import Dialog from '@/components/Dialog/index.vue'
+
+import { DialogProps, DialogModalProps } from './props'
+
+const defaultModalProps: Omit<DialogModalProps, 'content'> = {
+  title:'',
+  showOk: true,
+  showClose: false,
+  hideCancel: false,
+  okText: '确定',
+  cancelText: '取消',
+  footer: true,
+  header: true,
+  width: '60%'
+}
+
+/**
+ * 获取组件实例的 provides
+ * @param instance Vue 组件实例
+ * @returns 组件实例及其父级的 provides 对象
+ */
+function getProvides(instance: any): Record<string, any> {
+  let provides = instance?.provides || {}
+  if (instance.parent) {
+    provides = { ...provides, ...getProvides(instance.parent) }
+  }
+  return provides
+}
+
+const useDialog = (initialOptions: DialogProps) => {
+  const modalRef = ref<InstanceType<typeof ElDialog> | null>(null)
+  const currentInstance = getCurrentInstance() as any
+  const provides = getProvides(currentInstance)
+  // 用于保存对话框应用实例和可见性状态
+  let dialogApp: App | null = null
+  const visible = ref(false)
+  let container: HTMLElement | null = null
+
+  // 使用reactive存储options,以便能够响应式更新
+  const options = reactive<DialogProps>({
+    ...initialOptions
+  })
+
+  const mergeModalOptions: ComputedRef<DialogModalProps> = computed(() => {
+    return {
+      ...defaultModalProps,
+      ...options.modalProps
+    }
+  })
+
+  const openModal = () => {
+    // 判断content类型
+    const isAsync = typeof options.content === 'function'
+    const isString = typeof options.content === 'string'
+
+    const modalComponent = isAsync
+      ? defineAsyncComponent(options.content as () => Promise<Component>)
+      : isString
+        ? h('div', options.content as string)
+        : options.content
+
+    container = document.createElement('div')
+    document.body.appendChild(container)
+
+    // 状态管理
+    visible.value = true
+
+    dialogApp = createApp({
+      setup() {
+        const closed = () => {
+          if (dialogApp) {
+            dialogApp.unmount()
+            dialogApp = null
+          }
+          if (container) {
+            container.remove()
+            container = null
+          }
+        }
+
+        const instance = getCurrentInstance() as any
+
+        if (instance) {
+          instance.provides = { ...instance.provides, ...provides }
+
+          // 使用provide提供上下文
+          provide('parentInstance', currentInstance)
+          provide('dialogInstance', instance)
+          provide('dialogProvides', provides)
+        }
+
+
+
+        const onOk = async () => {
+          // 调用props中传递的onOk回调函数
+          try {
+            await unref(options.modalProps)?.onOk?.();
+            // 如果需要,可以在这里添加默认的关闭行为
+          } catch (error) {
+            console.error('Dialog onOk error:', error);
+          }
+        }
+
+        return () => {
+          return h(
+            Dialog,
+            {
+              ...unref(mergeModalOptions),
+              modelValue: visible.value,
+              'onUpdate:modelValue': (val: boolean) => {
+                visible.value = val
+              },
+              onOk: onOk,
+              onClosed: () => {
+                
+                //如果需要回调就调用回调
+                unref(options.contentProps)?.onClosed?.()
+                closed()
+              }
+            },
+            () =>
+              h(modalComponent, {
+                ...options.contentProps,
+                // 将当前实例上下文传递给子组件
+                instance: instance,
+                // 传递provides
+                provides: provides,
+                // 如果需要,还可以传递其他上下文信息
+                parentContext: currentInstance
+              })
+          )
+        }
+      }
+    })
+    // 挂载应用
+    dialogApp.mount(container)
+  }
+
+  const closeModal = () => {
+    // 设置可见性为false,触发对话框关闭
+    if (unref(visible)) {
+      visible.value = false
+    }
+
+    // 如果dialogApp存在,手动卸载
+    if (dialogApp) {
+      setTimeout(() => {
+        dialogApp?.unmount()
+        dialogApp = null
+
+        if (container) {
+          container.remove()
+          container = null
+        }
+      }, 300) // 给动画效果一些时间
+    }
+  }
+
+  //更新属性
+  const updateModalProps = (props: Partial<DialogProps>) => {
+    if (!dialogApp) {
+      return false
+    }
+
+    // 更新modalProps
+    if (props.modalProps) {
+      options.modalProps = {
+        ...options.modalProps,
+        ...props.modalProps
+      }
+    }
+
+    // 更新contentProps
+    if (props.contentProps) {
+      options.contentProps = {
+        ...options.contentProps,
+        ...props.contentProps
+      }
+    }
+
+    // 如果提供了新的content,需要重新打开对话框
+    if (props.content) {
+      options.content = props.content
+      closeModal()
+      setTimeout(() => {
+        openModal()
+      }, 300)
+    }
+
+    return true
+  }
+
+  return {
+    modalRef: modalRef,
+    openModal: openModal,
+    closeModal: closeModal,
+    updateModalProps: updateModalProps
+  }
+}
+
+export default useDialog

+ 8 - 0
src/components/Editor/index.ts

@@ -0,0 +1,8 @@
+import Editor from './src/Editor.vue'
+import { IDomEditor } from '@wangeditor/editor'
+
+export interface EditorExpose {
+  getEditorRef: () => Promise<IDomEditor>
+}
+
+export { Editor }

+ 243 - 0
src/components/Editor/src/Editor.vue

@@ -0,0 +1,243 @@
+<script lang="ts" setup>
+import { PropType } from 'vue'
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+import { i18nChangeLanguage, IDomEditor, IEditorConfig } from '@wangeditor/editor'
+import { propTypes } from '@/utils/propTypes'
+import { isNumber } from '@/utils/is'
+import { ElMessage } from 'element-plus'
+import { useLocaleStore } from '@/store/modules/locale'
+import { getAccessToken, getTenantId } from '@/utils/auth'
+import { getUploadUrl } from '@/components/UploadFile/src/useUpload'
+
+defineOptions({ name: 'Editor' })
+
+type InsertFnType = (url: string, alt: string, href: string) => void
+
+const localeStore = useLocaleStore()
+
+const currentLocale = computed(() => localeStore.getCurrentLocale)
+
+i18nChangeLanguage(unref(currentLocale).lang)
+
+const props = defineProps({
+  editorId: propTypes.string.def('wangeEditor-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('')
+})
+
+const emit = defineEmits(['change', 'update:modelValue'])
+
+// 编辑器实例,必须用 shallowRef
+const editorRef = shallowRef<IDomEditor>()
+
+const valueHtml = ref('')
+
+watch(
+  () => props.modelValue,
+  (val: string) => {
+    if (val === unref(valueHtml)) return
+    valueHtml.value = val
+  },
+  {
+    immediate: true
+  }
+)
+
+// 监听
+watch(
+  () => valueHtml.value,
+  (val: string) => {
+    emit('update:modelValue', val)
+  }
+)
+
+const handleCreated = (editor: IDomEditor) => {
+  editorRef.value = editor
+}
+
+// 编辑器配置
+const editorConfig = computed((): IEditorConfig => {
+  return Object.assign(
+    {
+      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,
+      MENU_CONF: {
+        ['uploadImage']: {
+          server: getUploadUrl(),
+          // 单个文件的最大体积限制,默认为 2M
+          maxFileSize: 5 * 1024 * 1024,
+          // 最多可上传几个文件,默认为 100
+          maxNumberOfFiles: 10,
+          // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
+          allowedFileTypes: ['image/*'],
+
+          // 自定义增加 http  header
+          headers: {
+            Accept: '*',
+            Authorization: 'Bearer ' + getAccessToken(),
+            'tenant-id': getTenantId()
+          },
+
+          // 超时时间,默认为 10 秒
+          timeout: 5 * 1000, // 5 秒
+
+          // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
+          fieldName: 'file',
+
+          // 上传之前触发
+          onBeforeUpload(file: File) {
+            // console.log(file)
+            return file
+          },
+          // 上传进度的回调函数
+          onProgress(progress: number) {
+            // progress 是 0-100 的数字
+            console.log('progress', progress)
+          },
+          onSuccess(file: File, res: any) {
+            console.log('onSuccess', file, res)
+          },
+          onFailed(file: File, res: any) {
+            alert(res.message)
+            console.log('onFailed', file, res)
+          },
+          onError(file: File, err: any, res: any) {
+            alert(err.message)
+            console.error('onError', file, err, res)
+          },
+          // 自定义插入图片
+          customInsert(res: any, insertFn: InsertFnType) {
+            insertFn(res.data, 'image', res.data)
+          }
+        },
+        ['uploadVideo']: {
+          server: getUploadUrl(),
+          // 单个文件的最大体积限制,默认为 10M
+          maxFileSize: 10 * 1024 * 1024,
+          // 最多可上传几个文件,默认为 100
+          maxNumberOfFiles: 10,
+          // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
+          allowedFileTypes: ['video/*'],
+
+          // 自定义增加 http  header
+          headers: {
+            Accept: '*',
+            Authorization: 'Bearer ' + getAccessToken(),
+            'tenant-id': getTenantId()
+          },
+
+          // 超时时间,默认为 30 秒
+          timeout: 15 * 1000, // 15 秒
+
+          // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
+          fieldName: 'file',
+
+          // 上传之前触发
+          onBeforeUpload(file: File) {
+            // console.log(file)
+            return file
+          },
+          // 上传进度的回调函数
+          onProgress(progress: number) {
+            // progress 是 0-100 的数字
+            console.log('progress', progress)
+          },
+          onSuccess(file: File, res: any) {
+            console.log('onSuccess', file, res)
+          },
+          onFailed(file: File, res: any) {
+            alert(res.message)
+            console.log('onFailed', file, res)
+          },
+          onError(file: File, err: any, res: any) {
+            alert(err.message)
+            console.error('onError', file, err, res)
+          },
+          // 自定义插入图片
+          customInsert(res: any, insertFn: InsertFnType) {
+            insertFn(res.data, 'mp4', res.data)
+          }
+        }
+      },
+      uploadImgShowBase64: true
+    },
+    props.editorConfig || {}
+  )
+})
+
+const editorStyle = computed(() => {
+  return {
+    height: isNumber(props.height) ? `${props.height}px` : props.height
+  }
+})
+
+// 回调函数
+const handleChange = (editor: IDomEditor) => {
+  emit('change', editor)
+}
+
+// 组件销毁时,及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = unref(editorRef.value)
+
+  // 销毁,并移除 editor
+  editor?.destroy()
+})
+
+const getEditorRef = async (): Promise<IDomEditor> => {
+  await nextTick()
+  return unref(editorRef.value) as IDomEditor
+}
+
+defineExpose({
+  getEditorRef
+})
+</script>
+
+<template>
+  <div class="border-1 border-solid border-[var(--tags-view-border-color)] z-10">
+    <!-- 工具栏 -->
+    <Toolbar
+      :editor="editorRef"
+      :editorId="editorId"
+      class="border-0 b-b-1 border-solid border-[var(--tags-view-border-color)]"
+    />
+    <!-- 编辑器 -->
+    <Editor
+      v-model="valueHtml"
+      :defaultConfig="editorConfig"
+      :editorId="editorId"
+      :style="editorStyle"
+      @on-change="handleChange"
+      @on-created="handleCreated"
+    />
+  </div>
+</template>
+
+<style src="@wangeditor/editor/dist/css/style.css"></style>

+ 0 - 52
src/components/Icon/Index.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { computed } from 'vue';
-import * as AntdIcons from '@ant-design/icons-vue';
 
 defineOptions({
   name: 'SvgIcon'
@@ -9,8 +8,6 @@ defineOptions({
 interface Props {
   /** 图标名称 */
   name: string;
-  /** 图标类型: 'svg' | 'antd' */
-  type?: 'svg' | 'antd';
   /** 图标尺寸 (支持CSS单位) */
   size?: string | number;
   /** 图标颜色 */
@@ -23,26 +20,12 @@ interface Props {
 
 // 设置默认属性值
 const props = withDefaults(defineProps<Props>(), {
-  type: 'svg',
   size: '1em',
   color: 'currentColor',
   spin: false,
   className: ''
 });
 
-// 动态解析Ant Design图标
-const antdIcon = computed(() => {
-  if (props.type !== 'antd') return null;
-  
-  const iconComponent = AntdIcons[props.name as keyof typeof AntdIcons] as any;
-  
-  if (!iconComponent) {
-    console.warn(`Ant Design图标未找到: ${props.name}`);
-  }
-  
-  return iconComponent || null;
-});
-
 // 处理尺寸值,确保有单位
 const formattedSize = computed(() => {
   if (typeof props.size === 'number') return `${props.size}px`;
@@ -61,35 +44,12 @@ const style = computed(() => ({
 <template>
   <!-- SVG图标 -->
   <svg 
-    v-if="type === 'svg'"
     :class="['svg-icon', className]"
     :style="style"
     aria-hidden="true"
   >
     <use :xlink:href="`#icon-${name}`" />
   </svg>
-  
-  <!-- Ant Design图标 -->
-  <component 
-    v-else-if="type === 'antd' && antdIcon"
-    :is="antdIcon"
-    v-bind="{ 
-      spin, 
-      style: { 
-        color, 
-        fontSize: formattedSize
-      } 
-    }"
-  />
-  
-  <!-- 图标未找到时的回退 -->
-  <span 
-    v-else
-    class="icon-fallback"
-    :style="{ fontSize: formattedSize }"
-  >
-    ?
-  </span>
 </template>
 
 <style scoped>
@@ -101,18 +61,6 @@ const style = computed(() => ({
   transition: transform 0.3s ease;
 }
 
-.icon-fallback {
-  display: inline-block;
-  width: 1em;
-  height: 1em;
-  line-height: 1;
-  text-align: center;
-  background-color: #f0f0f0;
-  border-radius: 2px;
-  font-weight: bold;
-  color: #999;
-}
-
 @keyframes icon-spin {
   from { transform: rotate(0deg); }
   to { transform: rotate(360deg); }

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

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

+ 92 - 0
src/components/helpMessage/src/index.vue

@@ -0,0 +1,92 @@
+<script lang="tsx">
+import type { CSSProperties } from 'vue'
+import { defineComponent, computed, unref, PropType } from 'vue'
+import { ElTooltip, ElIcon } from 'element-plus'
+import { QuestionFilled } from '@element-plus/icons-vue'
+import { isString, isArray } from '@/utils/is'
+
+const props = {
+  //最大宽度
+  maxWidth: { type: String, default: '600px' },
+  //颜色
+  color: { type: String, default: '#000' },
+  //是否显示多行的序号比如1.xxxx2.xxxx
+  showIndex: { type: Boolean, default: true },
+  //字体
+  fontSize: { type: Number, default: 15 },
+  //弹窗位置
+  placement: { type: String, default: 'right' },
+  //文字提示字符或列表
+  text: { type: [Array, String] as PropType<string[] | string> }
+}
+
+
+export default defineComponent({
+  name: 'BasicHelp',
+  components: { ElTooltip, ElIcon, QuestionFilled },
+  props: props,
+  setup(props, { slots }) {
+   
+    const getTooltipStyle = computed(
+      (): CSSProperties => ({ color: props.color, fontSize: `${props.fontSize}px` })
+    )
+
+    // 数组渲染成多列,文字就一行
+    function renderTitle() {
+      const textList = props.text
+      // 一行就一个p包裹
+      if (isString(textList)) {
+        return <p>{textList}</p>
+      }
+
+      if (isArray(textList)) {
+        return (textList as string[]).map((text, index) => {
+          return (
+            <p key={text}>
+                <span>
+                  {props.showIndex ? `${index + 1}. ` : ''}
+                {text}
+              </span>
+            </p>
+          )
+        })
+      }
+
+      return null
+    }
+
+    return () => {
+      return (
+        <ElTooltip
+          popperClass="basic-help__wrap"
+          placement={props.placement as any}
+          v-slots={{
+            content: () => <div>{renderTitle()}</div>
+          }}
+        >
+          {slots.default?.()}
+          <div class="help-icon">
+            <ElIcon style={unref(getTooltipStyle)}>
+              <QuestionFilled />
+            </ElIcon>
+          </div>
+        </ElTooltip>
+      )
+    }
+  }
+})
+</script>
+<style lang="scss">
+.basic-help__wrap {
+  p {
+    margin-bottom: 0;
+  }
+}
+.help-icon {
+  display: inline-block;
+  margin-left: 3px;
+  font-size: 14px;
+  color: var(--el-table-text-color);
+  cursor: pointer;
+}
+</style>

+ 4 - 3
src/components/index.ts

@@ -1,12 +1,12 @@
 import { App } from 'vue';
 import SvgIcon from './Icon/Index.vue';
-
+import Dialog from './Dialog/Index.vue';
 
 
 // 需要全局注册的组件列表
 const components = {
   SvgIcon,
-  // 在这里添加更多需要全局注册的组件
+  Dialog
   
 };
 
@@ -26,7 +26,8 @@ export function registerGlobalComponents(app: App) {
 // 导出所有组件,方便按需导入
 export {
   SvgIcon,
-  // 在这里导出更多组件
+  Dialog
+  
 };
 
 // 默认导出注册函数

+ 7 - 3
src/config/axios/service.ts

@@ -63,9 +63,13 @@ service.interceptors.request.use(
     let isToken = (config!.headers || {}).isToken === false
     whiteList.some((v) => {
       if (config.url) {
-        config.url.indexOf(v) > -1
-        return (isToken = false)
+        if (config.url.indexOf(v) > -1) {
+          isToken = false;
+          return true;
+        }
+        return false;
       }
+      return false;
     })
 
     if (getAccessToken() && !isToken) {
@@ -212,7 +216,7 @@ service.interceptors.response.use(
   },
   (error: AxiosError) => {
     console.log('err' + error) // for debug
-    let { message, config } = error || {}
+    const { message, config } = error || {}
     const { t } = useI18n()
     if (message === 'Network Error') {
       message = t('sys.api.errorMessage')

+ 1 - 1
src/main.ts

@@ -1,5 +1,5 @@
 import './styles/index.css';
-/* import './styles/index.scss'; */
+ //import './styles/index.scss'; 
 import { createApp } from 'vue';
 import App from './App.vue';
 import router from './router';

+ 10 - 1
src/styles/index.css

@@ -1,2 +1,11 @@
 /* 导入 Tailwind CSS */
-@import "tailwindcss"; 
+@import "tailwindcss"; 
+
+:root {
+  --el-color-primary: #1677ff !important;
+  --el-color-primary-light-3: #4096ff !important;
+  --el-color-primary-light-5: #69b1ff !important;
+  --el-color-primary-light-7: #91caff !important;
+  --el-color-primary-light-9: #e6f4ff !important;
+  --el-color-primary-dark-2: #0958d9 !important;
+}

+ 0 - 87
src/styles/index.scss

@@ -1,88 +1 @@
-/* 导入变量和重置样式 */
 
-/* 全局样式 */
-/* :root {
-  font-family: $font-family;
-  font-size: $font-size-base;
-  line-height: $line-height-base;
-  font-weight: 400;
-
-  color-scheme: light dark;
-  color: $text-color;
-  background-color: $bg-color;
-
-  font-synthesis: none;
-  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  -webkit-text-size-adjust: 100%;
-}
-
-a {
-  font-weight: 500;
-  color: $primary-color;
-  text-decoration: inherit;
-  
-  &:hover {
-    color: $primary-hover-color;
-  }
-}
-
-body {
-  margin: 0;
-  display: flex;
-  place-items: center;
-  min-width: 320px;
-  min-height: 100vh;
-}
-
-h1 {
-  font-size: 3.2em;
-  line-height: 1.1;
-}
-
-button {
-  border-radius: $border-radius;
-  border: 1px solid $border-color;
-  padding: 0.6em 1.2em;
-  font-size: 1em;
-  font-weight: 500;
-  font-family: inherit;
-  background-color: #1a1a1a;
-  cursor: pointer;
-  transition: border-color $transition-duration;
-  
-  &:hover {
-    border-color: $primary-color;
-  }
-  
-  &:focus,
-  &:focus-visible {
-    outline: 4px auto -webkit-focus-ring-color;
-  }
-} */
-
-.card {
-  padding: 2em;
-}
-
-#app {
-  text-align: center;
-  width: 100%;
-  height: 100%;
-}
-
-@media (prefers-color-scheme: light) {
-  :root {
-    color: $light-text-color;
-    background-color: $light-bg-color;
-  }
-  
-  a:hover {
-    color: #747bff;
-  }
-  
-  button {
-    background-color: #f9f9f9;
-  }
-} 

+ 2 - 0
src/styles/variables.css

@@ -0,0 +1,2 @@
+@charset "UTF-8";
+/* 全局 SCSS 变量 */

+ 2 - 0
src/styles/variables.scss

@@ -45,6 +45,8 @@ $screen-xxl: 1536px; // Tailwind 2xl: 1536px
   }
 }
 
+$elNamespace: el;
+
 // 常用颜色变量
 $primary-color: #646cff;
 $primary-hover-color: #535bf2;

+ 24 - 0
src/types/components.d.ts

@@ -0,0 +1,24 @@
+/* eslint-disable */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+// biome-ignore lint: disable
+export {}
+
+/* prettier-ignore */
+declare module 'vue' {
+  export interface GlobalComponents {
+    EditorSrcEditor: typeof import('./../components/Editor/src/Editor.vue')['default']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElRow: typeof import('element-plus/es')['ElRow']
+    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+    ElSpace: typeof import('element-plus/es')['ElSpace']
+    Icon: typeof import('./../components/Icon/Index.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+  }
+}

+ 27 - 0
src/types/is.d.ts

@@ -0,0 +1,27 @@
+declare module '@/utils/is' {
+  export const is: (val: unknown, type: string) => boolean
+  export const isDef: <T = unknown>(val?: T) => val is T
+  export const isUnDef: <T = unknown>(val?: T) => val is T
+  export const isObject: (val: any) => val is Record<any, any>
+  export const isEmpty: (val: any) => boolean
+  export const isDate: (val: unknown) => val is Date
+  export const isNull: (val: unknown) => val is null
+  export const isNullAndUnDef: (val: unknown) => val is null | undefined
+  export const isNullOrUnDef: (val: unknown) => val is null | undefined
+  export const isNumber: (val: unknown) => val is number
+  export const isPromise: <T = any>(val: unknown) => val is Promise<T>
+  export const isString: (val: unknown) => val is string
+  export const isFunction: (val: unknown) => val is Function
+  export const isBoolean: (val: unknown) => val is boolean
+  export const isRegExp: (val: unknown) => val is RegExp
+  export const isArray: (val: any) => val is Array<any>
+  export const isWindow: (val: any) => val is Window
+  export const isElement: (val: unknown) => val is Element
+  export const isMap: (val: unknown) => val is Map<any, any>
+  export const isServer: boolean
+  export const isClient: boolean
+  export const isUrl: (path: string) => boolean
+  export const isDark: () => boolean
+  export const isImgPath: (path: string) => boolean
+  export const isEmptyVal: (val: any) => boolean
+} 

+ 119 - 0
src/utils/is.ts

@@ -0,0 +1,119 @@
+// copy to vben-admin
+
+const toString = Object.prototype.toString
+
+export const is = (val: unknown, type: string) => {
+  return toString.call(val) === `[object ${type}]`
+}
+
+export const isDef = <T = unknown>(val?: T): val is T => {
+  return typeof val !== 'undefined'
+}
+
+export const isUnDef = <T = unknown>(val?: T): val is T => {
+  return !isDef(val)
+}
+
+export const isObject = (val: any): val is Record<any, any> => {
+  return val !== null && is(val, 'Object')
+}
+
+export const isEmpty = (val: any): boolean => {
+  if (val === null || val === undefined || typeof val === 'undefined') {
+    return true
+  }
+  if (isArray(val) || isString(val)) {
+    return val.length === 0
+  }
+
+  if (val instanceof Map || val instanceof Set) {
+    return val.size === 0
+  }
+
+  if (isObject(val)) {
+    return Object.keys(val).length === 0
+  }
+
+  return false
+}
+
+export const isDate = (val: unknown): val is Date => {
+  return is(val, 'Date')
+}
+
+export const isNull = (val: unknown): val is null => {
+  return val === null
+}
+
+export const isNullAndUnDef = (val: unknown): val is null | undefined => {
+  return isUnDef(val) && isNull(val)
+}
+
+export const isNullOrUnDef = (val: unknown): val is null | undefined => {
+  return isUnDef(val) || isNull(val)
+}
+
+export const isNumber = (val: unknown): val is number => {
+  return is(val, 'Number')
+}
+
+export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
+  return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
+}
+
+export const isString = (val: unknown): val is string => {
+  return is(val, 'String')
+}
+
+// 修复:用更具体的函数类型替代通用的Function类型
+export const isFunction = (val: unknown): val is ((...args: any[]) => any) => {
+  return typeof val === 'function'
+}
+
+export const isBoolean = (val: unknown): val is boolean => {
+  return is(val, 'Boolean')
+}
+
+export const isRegExp = (val: unknown): val is RegExp => {
+  return is(val, 'RegExp')
+}
+
+export const isArray = (val: any): val is Array<any> => {
+  return val && Array.isArray(val)
+}
+
+export const isWindow = (val: any): val is Window => {
+  return typeof window !== 'undefined' && is(val, 'Window')
+}
+
+export const isElement = (val: unknown): val is Element => {
+  return isObject(val) && !!val.tagName
+}
+
+export const isMap = (val: unknown): val is Map<any, any> => {
+  return is(val, 'Map')
+}
+
+export const isServer = typeof window === 'undefined'
+
+export const isClient = !isServer
+
+export const isUrl = (path: string): boolean => {
+  const reg =
+    /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
+  return reg.test(path)
+}
+
+export const isDark = (): boolean => {
+  console.log(window.matchMedia('(prefers-color-scheme: dark)'))
+  return window.matchMedia('(prefers-color-scheme: dark)').matches
+}
+
+// 是否是图片链接
+export const isImgPath = (path: string): boolean => {
+  return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
+}
+
+export const isEmptyVal = (val: any): boolean => {
+  return val === '' || val === null || val === undefined
+}

+ 24 - 0
src/utils/propTypes.ts

@@ -0,0 +1,24 @@
+import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'
+import { CSSProperties } from 'vue'
+
+type PropTypes = VueTypesInterface & {
+  readonly style: VueTypeValidableDef<CSSProperties>
+}
+const newPropTypes = createTypes({
+  func: undefined,
+  bool: undefined,
+  string: undefined,
+  number: undefined,
+  object: undefined,
+  integer: undefined
+}) as PropTypes
+
+class propTypes extends newPropTypes {
+  static get style() {
+    return toValidableType('style', {
+      type: [String, Object]
+    })
+  }
+}
+
+export { propTypes }

+ 254 - 0
src/utils/routerHelper.ts

@@ -0,0 +1,254 @@
+import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router'
+import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
+import { isUrl } from '@/utils/is'
+import { cloneDeep, omit } from 'lodash-es'
+import qs from 'qs'
+
+const modules = import.meta.glob('../views/**/*.{vue,tsx}')
+/**
+ * 注册一个异步组件
+ * @param componentPath 例:/bpm/oa/leave/detail
+ */
+export const registerComponent = (componentPath: string) => {
+  for (const item in modules) {
+    if (item.includes(componentPath)) {
+      // 使用异步组件的方式来动态加载组件
+      // @ts-ignore
+      return defineAsyncComponent(modules[item])
+    }
+  }
+}
+/* Layout */
+export const Layout = () => import('@/layout/Layout.vue')
+
+export const getParentLayout = () => {
+  return () =>
+    new Promise((resolve) => {
+      resolve({
+        name: 'ParentLayout'
+      })
+    })
+}
+
+// 按照路由中meta下的rank等级升序来排序路由
+export const ascending = (arr: any[]) => {
+  arr.forEach((v) => {
+    if (v?.meta?.rank === null) v.meta.rank = undefined
+    if (v?.meta?.rank === 0) {
+      if (v.name !== 'home' && v.path !== '/') {
+        console.warn('rank only the home page can be 0')
+      }
+    }
+  })
+  return arr.sort((a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {
+    return a?.meta?.rank - b?.meta?.rank
+  })
+}
+
+export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => {
+  if (!route) return route
+  const { matched, ...opt } = route
+  return {
+    ...opt,
+    matched: (matched
+      ? matched.map((item) => ({
+          meta: item.meta,
+          name: item.name,
+          path: item.path
+        }))
+      : undefined) as RouteRecordNormalized[]
+  }
+}
+
+// 后端控制路由生成
+export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
+  const res: AppRouteRecordRaw[] = []
+  const modulesRoutesKeys = Object.keys(modules)
+  for (const route of routes) {
+    // 1. 生成 meta 菜单元数据
+    const meta = {
+      title: route.name,
+      icon: route.icon,
+      hidden: !route.visible,
+      noCache: !route.keepAlive,
+      alwaysShow:
+        route.children &&
+        route.children.length === 1 &&
+        (route.alwaysShow !== undefined ? route.alwaysShow : true)
+    } as any
+    // 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
+    // 此时,我们需要解析参数,并且将参数放到 meta.query 中
+    // 这样,后续在 Vue 文件中,可以通过 const { currentRoute } = useRouter() 中,通过 meta.query 获取到参数
+    if (route.component && route.component.indexOf('?') > -1) {
+      const query = route.component.split('?')[1]
+      route.component = route.component.split('?')[0]
+      meta.query = qs.parse(query)
+    }
+
+    // 2. 生成 data(AppRouteRecordRaw)
+    // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive
+    let data: AppRouteRecordRaw = {
+      path:
+        route.path.indexOf('?') > -1 && !isUrl(route.path) ? route.path.split('?')[0] : route.path, // 注意,需要排除 http 这种 url,避免它带 ? 参数被截取掉
+      name:
+        route.componentName && route.componentName.length > 0
+          ? route.componentName
+          : toCamelCase(route.path, true),
+      redirect: route.redirect,
+      meta: meta
+    }
+    //处理顶级非目录路由
+    if (!route.children && route.parentId == 0 && route.component) {
+      data.component = Layout
+      data.meta = {}
+      data.name = toCamelCase(route.path, true) + 'Parent'
+      data.redirect = ''
+      meta.alwaysShow = true
+      const childrenData: AppRouteRecordRaw = {
+        path: '',
+        name:
+          route.componentName && route.componentName.length > 0
+            ? route.componentName
+            : toCamelCase(route.path, true),
+        redirect: route.redirect,
+        meta: meta
+      }
+      const index = route?.component
+        ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))
+        : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))
+      childrenData.component = modules[modulesRoutesKeys[index]]
+      data.children = [childrenData]
+    } else {
+      // 目录
+      if (route.children) {
+        data.component = Layout
+        data.redirect = getRedirect(route.path, route.children)
+        // 外链
+      } else if (isUrl(route.path)) {
+        data = {
+          path: '/external-link',
+          component: Layout,
+          meta: {
+            name: route.name
+          },
+          children: [data]
+        } as AppRouteRecordRaw
+        // 菜单
+      } else {
+        // 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会根path保持一致)
+        const index = route?.component
+          ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))
+          : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))
+        data.component = modules[modulesRoutesKeys[index]]
+      }
+      if (route.children) {
+        data.children = generateRoute(route.children)
+      }
+    }
+    res.push(data as AppRouteRecordRaw)
+  }
+  return res
+}
+export const getRedirect = (parentPath: string, children: AppCustomRouteRecordRaw[]) => {
+  if (!children || children.length == 0) {
+    return parentPath
+  }
+  const path = generateRoutePath(parentPath, children[0].path)
+  // 递归子节点
+  if (children[0].children) return getRedirect(path, children[0].children)
+}
+const generateRoutePath = (parentPath: string, path: string) => {
+  if (parentPath.endsWith('/')) {
+    parentPath = parentPath.slice(0, -1) // 移除默认的 /
+  }
+  if (!path.startsWith('/')) {
+    path = '/' + path
+  }
+  return parentPath + path
+}
+export const pathResolve = (parentPath: string, path: string) => {
+  if (isUrl(path)) return path
+  const childPath = path.startsWith('/') || !path ? path : `/${path}`
+  return `${parentPath}${childPath}`.replace(/\/\//g, '/')
+}
+
+// 路由降级
+export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => {
+  const modules: AppRouteRecordRaw[] = cloneDeep(routes)
+  for (let index = 0; index < modules.length; index++) {
+    const route = modules[index]
+    if (!isMultipleRoute(route)) {
+      continue
+    }
+    promoteRouteLevel(route)
+  }
+  return modules
+}
+
+// 层级是否大于2
+const isMultipleRoute = (route: AppRouteRecordRaw) => {
+  if (!route || !Reflect.has(route, 'children') || !route.children?.length) {
+    return false
+  }
+
+  const children = route.children
+
+  let flag = false
+  for (let index = 0; index < children.length; index++) {
+    const child = children[index]
+    if (child.children?.length) {
+      flag = true
+      break
+    }
+  }
+  return flag
+}
+
+// 生成二级路由
+const promoteRouteLevel = (route: AppRouteRecordRaw) => {
+  let router: Router | null = createRouter({
+    routes: [route as RouteRecordRaw],
+    history: createWebHashHistory()
+  })
+
+  const routes = router.getRoutes()
+  addToChildren(routes, route.children || [], route)
+  router = null
+
+  route.children = route.children?.map((item) => omit(item, 'children'))
+}
+
+// 添加所有子菜单
+const addToChildren = (
+  routes: RouteRecordNormalized[],
+  children: AppRouteRecordRaw[],
+  routeModule: AppRouteRecordRaw
+) => {
+  for (let index = 0; index < children.length; index++) {
+    const child = children[index]
+    const route = routes.find((item) => item.name === child.name)
+    if (!route) {
+      continue
+    }
+    routeModule.children = routeModule.children || []
+    if (!routeModule.children.find((item) => item.name === route.name)) {
+      routeModule.children?.push(route as unknown as AppRouteRecordRaw)
+    }
+    if (child.children?.length) {
+      addToChildren(routes, child.children, routeModule)
+    }
+  }
+}
+const toCamelCase = (str: string, upperCaseFirst: boolean) => {
+  str = (str || '')
+    .replace(/-(.)/g, function (group1: string) {
+      return group1.toUpperCase()
+    })
+    .replaceAll('-', '')
+
+  if (upperCaseFirst && str) {
+    str = str.charAt(0).toUpperCase() + str.slice(1)
+  }
+
+  return str
+}

+ 17 - 0
src/utils/tsxHelper.ts

@@ -0,0 +1,17 @@
+import { Slots } from 'vue'
+import { isFunction } from '@/utils/is'
+
+export const getSlot = (slots: Slots, slot = 'default', data?: Recordable) => {
+
+  // Reflect.has 判断一个对象是否存在某个属性
+  if (!slots || !Reflect.has(slots, slot)) {
+    return null
+  }
+  if (!isFunction(slots[slot])) {
+    console.error(`${slot} is not a function!`)
+    return null
+  }
+  const slotFn = slots[slot]
+  if (!slotFn) return null
+  return slotFn(data)
+}

+ 3 - 2
src/views/home/components/nav.vue

@@ -7,6 +7,7 @@
     ]"
   >
     <div class="container mx-auto px-6 py-4 flex justify-between items-center">
+
       <div class="text-xl font-bold" :class="isScrolled ? 'text-blue-600' : 'text-white'">ZW多人语音合成</div>
 
       <ul class="flex items-center space-x-6">
@@ -24,13 +25,13 @@
           关于我们
         </li>
 
-        <Button type="primary">登录</Button>
+        <el-button type="primary">登录</el-button>
       </ul>
     </div>
   </nav>
 </template>
 <script lang="ts" setup>
-import { Button } from 'ant-design-vue';
+ 
 import { ref, onMounted, onUnmounted } from 'vue';
 
 defineOptions({

+ 52 - 12
src/views/home/index.vue

@@ -11,7 +11,7 @@
     <!--功能介绍区-->
     <section class="h-[100vh] flex items-center w-full relative" id="product" ref="productSection">
       <div class="py-20 container mx-auto px-6">
-        <h2 class="text-4xl font-bold text-white mb-10">我们的特色</h2>
+        <h2 class="text-4xl font-bold text-white mb-10 text-center">我们的特色</h2>
         <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
           <div
             v-for="(item, index) in products"
@@ -41,7 +41,7 @@
           <p class="text-xl text-gray-600 max-w-2xl mx-auto mb-10">用科技让沟通更自然,让信息传递更高效</p>
           <button class="lightning-button bg-blue-600 hover:bg-blue-700 text-white px-8 py-4 rounded-lg font-medium transition text-xl relative overflow-hidden">
             <span class="relative z-10">立即免费试用</span>
-            <span class="lightning"></span>
+            <span class="lightning" />
           </button>
         </div>
       </div>
@@ -61,12 +61,18 @@ import ParallaxBackground from './components/ParallaxBackground.vue';
 import TopAnime from './components/topAnime.vue';
 import { onMounted, onUnmounted, ref } from 'vue';
 import { useIntersectionObserver } from '@vueuse/core';
+import useDialog from '@/components/Dialog/useDialog';
+
 
 // 产品区域的引用
 const productSection = ref(null);
 // 控制产品区域动画的变量
 const isProductVisible = ref(false);
 
+// 动态导入登录页面
+const LoginView = () => import('@/views/login/index.vue');
+
+
 // 使用VueUse的useIntersectionObserver监听元素可见性
 useIntersectionObserver(
   productSection,
@@ -120,6 +126,42 @@ onMounted(() => {
     document.documentElement.style.removeProperty('scrollbar-width');
   });
 });
+
+
+
+// 初始化弹窗
+const loginDialog = useDialog({
+  content: LoginView,
+  modalProps: {
+    title: '',
+    width: '520px',
+    showClose: true,
+    closeOnClickModal: false,
+    header: false,
+    footer: false
+  }
+});
+
+// 显示登录弹窗
+const showLoginDialog = () => {
+  loginDialog.openModal();
+};
+
+
+showLoginDialog()
+
+
+
+
+
+
+
+
+
+
+
+
+
 </script>
 <style lang="scss" scoped>
 .home-page {
@@ -146,24 +188,22 @@ onMounted(() => {
 
 .lightning {
   position: absolute;
-  top: 0;
+  top: -50%;
   left: -100%;
-  width: 100%;
-  height: 100%;
-  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+  width: 20px;
+  height: 200%;
+  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
+  transform: rotate(-15deg);
   z-index: 1;
-  animation: lightning-animation 3s infinite;
+  animation: lightning-animation 2s infinite;
 }
 
 @keyframes lightning-animation {
   0% {
-    left: -100%;
-  }
-  20% {
-    left: 100%;
+    left: -100px;
   }
   100% {
-    left: 100%;
+    left: calc(100% + 100px);
   }
 }
 

+ 68 - 0
src/views/login-demo/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="flex items-center justify-center h-screen bg-gray-100">
+    <div class="text-center bg-white p-10 rounded-lg shadow-md">
+      <h1 class="text-2xl font-bold mb-5 text-gray-800">登录弹窗演示</h1>
+      <p class="text-gray-600 mb-8">点击下方按钮显示登录弹窗</p>
+      
+      <el-button type="primary" @click="showLoginDialog">打开登录弹窗</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import useDialog from '@/components/Dialog/useDialog';
+import { ElMessage } from 'element-plus';
+
+// 动态导入登录页面
+const LoginView = () => import('@/views/login/index.vue');
+
+// 初始化弹窗
+const loginDialog = useDialog({
+  content: LoginView,
+  modalProps: {
+    title: '',
+    width: '520px',
+    showClose: true,
+    closeOnClickModal: false,
+    header: true,
+    footer: false,
+    showFullscreen: true,
+    appendToBody: true,
+    destroyOnClose: true
+  }
+});
+
+// 显示登录弹窗
+const showLoginDialog = () => {
+  loginDialog.openModal();
+};
+</script>
+
+<style lang="scss" scoped>
+.login-demo-page {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100vh;
+  background-color: #f5f7fa;
+  
+  .container {
+    text-align: center;
+    padding: 40px;
+    border-radius: 8px;
+    background-color: #fff;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+    
+    h1 {
+      margin-bottom: 20px;
+      color: #333;
+    }
+    
+    p {
+      margin-bottom: 30px;
+      color: #666;
+    }
+  }
+}
+</style> 

+ 116 - 0
src/views/login/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="w-full h-full flex items-center justify-center relative">
+    <!-- 背景图 -->
+    <div 
+      class="absolute inset-0 bg-cover bg-center"
+      style="background-image: url('../../assets/imgs/login.png');"
+    ></div>
+    
+    <!-- 白色内容区域 - 增加内边距让背景更多露出 -->
+    <div class="bg-white rounded-lg w-[420px] shadow-lg z-10 relative p-8">
+      <h2 class="text-2xl font-bold text-center text-gray-800 mb-8">登录魔音工坊开启创作之旅</h2>
+      
+      <!-- 账号密码登录表单 -->
+      <div class="space-y-6">
+        <!-- 用户名输入框 -->
+        <el-input 
+          v-model="formData.username" 
+          placeholder="请输入用户名" 
+          :prefix-icon="User"
+          class="rounded-md"
+          size="large"
+        ></el-input>
+        
+        <!-- 密码输入框 -->
+        <el-input 
+          v-model="formData.password" 
+          type="password" 
+          placeholder="请输入密码"
+          :prefix-icon="Lock" 
+          show-password
+          class="rounded-md"
+          size="large"
+        ></el-input>
+        
+        <!-- 验证码输入框 -->
+        <div class="flex gap-3">
+          <el-input 
+            v-model="formData.verificationCode" 
+            placeholder="请输入验证码"
+            :prefix-icon="Key"
+            class="flex-1 rounded-md"
+            size="large"
+          ></el-input>
+          <el-button type="primary" class="w-28 h-10 text-sm rounded-md">获取验证码</el-button>
+        </div>
+        
+        <!-- 登录按钮 -->
+        <el-button type="primary" class="w-full h-44 text-base rounded-md mt-8" @click="handleLogin">登录</el-button>
+      </div>
+      
+      <!-- 底部链接区域 -->
+      <div class="mt-8 text-center">
+        <!-- 注册链接 -->
+        <!-- <div class="text-center text-sm">
+          <span class="text-gray-600">还没有账号?</span>
+          <a href="javascript:;" class="text-blue-500 ml-1" @click="handleRegister">立即注册</a>
+        </div> -->
+        
+        <!-- 用户协议 -->
+        <div class="text-center text-xs text-gray-400 mt-4">
+          <span>登录即表示已阅读并同意</span>
+          <a href="javascript:;" class="text-blue-500">《用户协议》</a>
+          <span>和</span>
+          <a href="javascript:;" class="text-blue-500">《隐私协议》</a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+import { User, Lock, Key } from '@element-plus/icons-vue';
+
+// 表单数据
+const formData = reactive({
+  username: '',
+  password: '',
+  verificationCode: ''
+});
+
+// 登录方法
+const handleLogin = async () => {
+  try {
+    // 表单校验
+    if (!formData.username) {
+      ElMessage.warning('请输入用户名');
+      return;
+    }
+    
+    if (!formData.password) {
+      ElMessage.warning('请输入密码');
+      return;
+    }
+    
+    if (!formData.verificationCode) {
+      ElMessage.warning('请输入验证码');
+      return;
+    }
+
+    // 模拟登录请求
+    // TODO: 实现实际登录逻辑
+    ElMessage.success('登录成功');
+  } catch (error) {
+    ElMessage.error('登录失败,请稍后再试');
+    console.error(error);
+  }
+};
+
+// 注册方法
+const handleRegister = () => {
+  // TODO: 实现注册逻辑
+  ElMessage.info('正在开发中...');
+};
+</script> 

+ 102 - 14
src/views/not-found/index.vue

@@ -1,8 +1,33 @@
 <template>
   <div class="not-found-page">
-    <h1>404</h1>
-    <p>页面不存在</p>
-    <router-link to="/">返回首页</router-link>
+    <div class="container">
+      <div class="error-content">
+        <div class="error-code">404</div>
+        <div class="error-divider" />
+        <h1 class="error-title">页面未找到</h1>
+        <p class="error-message">抱歉,您访问的页面不存在</p>
+        <router-link to="/" class="home-button">
+          <span class="button-icon">
+            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+              <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
+              <polyline points="9 22 9 12 15 12 15 22" />
+            </svg>
+          </span>
+          返回首页
+        </router-link>
+      </div>
+      <div class="error-illustration">
+        <svg width="300" height="200" viewBox="0 0 300 200" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M260 160H40C35.5817 160 32 156.418 32 152V48C32 43.5817 35.5817 40 40 40H260C264.418 40 268 43.5817 268 48V152C268 156.418 264.418 160 260 160Z" fill="#E6EDF5"/>
+          <path d="M150 70L180 120H120L150 70Z" fill="#3498db"/>
+          <circle cx="150" cy="130" r="10" fill="#3498db"/>
+          <path d="M32 48C32 43.5817 35.5817 40 40 40H260C264.418 40 268 43.5817 268 48V60H32V48Z" fill="#3498db"/>
+          <circle cx="46" cy="50" r="4" fill="white"/>
+          <circle cx="58" cy="50" r="4" fill="white"/>
+          <circle cx="70" cy="50" r="4" fill="white"/>
+        </svg>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -17,32 +42,95 @@ export default defineComponent({
 <style lang="scss" scoped>
 .not-found-page {
   display: flex;
-  flex-direction: column;
   align-items: center;
   justify-content: center;
   height: 100vh;
-  text-align: center;
+  background-color: #f8fafc;
   
-  h1 {
+  
+  .container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    max-width: 800px;
+    padding: 2rem;
+    
+    @media (min-width: 768px) {
+      flex-direction: row;
+      justify-content: space-between;
+    }
+  }
+  
+  .error-content {
+    text-align: center;
+    margin-bottom: 2rem;
+    
+    @media (min-width: 768px) {
+      text-align: left;
+      margin-right: 2rem;
+      margin-bottom: 0;
+    }
+  }
+  
+  .error-code {
     font-size: 6rem;
+    font-weight: 700;
+    color: #3498db;
+    line-height: 1;
     margin-bottom: 1rem;
-    color: #e74c3c;
   }
   
-  p {
-    font-size: 1.5rem;
+  .error-divider {
+    height: 4px;
+    width: 60px;
+    background-color: #3498db;
+    margin: 1.5rem auto;
+    
+    @media (min-width: 768px) {
+      margin: 1.5rem 0;
+    }
+  }
+  
+  .error-title {
+    font-size: 2rem;
+    font-weight: 600;
+    color: #2c3e50;
+    margin-bottom: 1rem;
+  }
+  
+  .error-message {
+    font-size: 1.1rem;
+    color: #7f8c8d;
     margin-bottom: 2rem;
-    color: #333;
+    line-height: 1.5;
   }
   
-  a {
-    color: #3498db;
+  .home-button {
+    display: inline-flex;
+    align-items: center;
+    background-color: #3498db;
+    color: white;
+    padding: 0.75rem 1.5rem;
+    border-radius: 8px;
+    font-weight: 500;
     text-decoration: none;
-    font-size: 1.2rem;
+    transition: all 0.2s ease;
+    box-shadow: 0 4px 6px rgba(52, 152, 219, 0.2);
     
     &:hover {
-      text-decoration: underline;
+      background-color: #2980b9;
+      transform: translateY(-2px);
+      box-shadow: 0 6px 8px rgba(52, 152, 219, 0.3);
+    }
+    
+    .button-icon {
+      display: inline-flex;
+      margin-right: 0.5rem;
     }
   }
+  
+  .error-illustration {
+    flex-shrink: 0;
+  }
 }
 </style> 

+ 2 - 2
src/views/test/index.vue

@@ -6,11 +6,11 @@
       <!-- 在这里添加您的测试内容 -->
       <div class="test-card">
         <h3>测试组件1</h3>
-        <div class="test-box"></div>
+        <div class="test-box" />
       </div>
       <div class="test-card">
         <h3>测试组件2</h3>
-        <div class="test-box"></div>
+        <div class="test-box" />
       </div>
     </div>
   </div>

+ 5 - 5
tsconfig.json

@@ -16,18 +16,18 @@
     "sourceMap": true,
     "baseUrl": ".",
     "paths": {
-      "@/*": ["src/*"]
+      "@/*": ["./src/*"]
     },
     "typeRoots": ["./node_modules/@types", "./src/types"]
   },
-  "include": ["src"],
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
   "exclude": ["./rspack.*.config.ts", "rspack.config.ts"],
   "ts-node": {
     "compilerOptions": {
       "module": "CommonJS",
-      "esModuleInterop": true, // 启用实验性的 ECMAScript 模块互操作性,方便导入 CommonJS 模块
-      "skipLibCheck": true, // 跳过对库文件的类型检查,可以加快编译速度,
-      "forceConsistentCasingInFileNames": true // 强制文件名大小写一致,避免在不同文件系统中出现文件名大小写不一致的问题
+      "esModuleInterop": true,
+      "skipLibCheck": true,
+      "forceConsistentCasingInFileNames": true
     }
   }
 }