瀏覽代碼

feat: 更新mini分支

kailong321200875 1 年之前
父節點
當前提交
607b73a7b3
共有 58 個文件被更改,包括 930 次插入1225 次删除
  1. 1 1
      .env.base
  2. 1 1
      .env.dev
  3. 1 1
      .env.gitee
  4. 1 1
      .env.pro
  5. 1 1
      mock/_createProductionServer.ts
  6. 91 0
      mock/role/index.mock.ts
  7. 0 544
      mock/role/index.ts
  8. 8 11
      mock/user/index.mock.ts
  9. 51 42
      package.json
  10. 0 11
      src/api/common/index.ts
  11. 0 23
      src/api/dashboard/analysis/index.ts
  12. 0 22
      src/api/dashboard/analysis/types.ts
  13. 0 22
      src/api/dashboard/workplace/index.ts
  14. 0 30
      src/api/dashboard/workplace/types.ts
  15. 0 30
      src/api/department/index.ts
  16. 0 32
      src/api/department/types.ts
  17. 5 15
      src/api/login/index.ts
  18. 0 5
      src/api/menu/index.ts
  19. 0 22
      src/api/table/index.ts
  20. 0 9
      src/api/table/types.ts
  21. 45 0
      src/axios/config.ts
  22. 7 6
      src/axios/index.ts
  23. 7 8
      src/axios/service.ts
  24. 0 13
      src/axios/types/index.ts
  25. 3 0
      src/components/Button/index.ts
  26. 121 0
      src/components/Button/src/Button.vue
  27. 3 2
      src/components/Menu/src/Menu.vue
  28. 7 8
      src/components/Menu/src/components/useRenderMenuItem.tsx
  29. 7 1
      src/components/Search/src/Search.vue
  30. 10 9
      src/components/Setting/src/Setting.vue
  31. 14 1
      src/components/Setting/src/components/InterfaceDisplay.vue
  32. 8 8
      src/components/Setting/src/components/LayoutRadioPicker.vue
  33. 107 73
      src/components/Table/src/Table.vue
  34. 8 28
      src/components/UserInfo/src/UserInfo.vue
  35. 27 0
      src/components/VideoPlayer/index.ts
  36. 59 0
      src/components/VideoPlayer/src/VideoPlayer.vue
  37. 3 0
      src/components/VideoPlayerViewer/index.ts
  38. 49 0
      src/components/VideoPlayerViewer/src/VideoPlayerViewer.vue
  39. 2 0
      src/components/index.ts
  40. 0 99
      src/config/axios/config.ts
  41. 24 0
      src/constants/index.ts
  42. 2 1
      src/locales/en.ts
  43. 2 1
      src/locales/zh-CN.ts
  44. 12 17
      src/permission.ts
  45. 2 2
      src/store/index.ts
  46. 16 22
      src/store/modules/app.ts
  47. 0 34
      src/store/modules/dict.ts
  48. 1 4
      src/store/modules/lock.ts
  49. 13 6
      src/store/modules/permission.ts
  50. 27 4
      src/store/modules/tagsView.ts
  51. 102 0
      src/store/modules/user.ts
  52. 11 8
      src/utils/routerHelper.ts
  53. 51 32
      src/views/Login/components/LoginForm.vue
  54. 2 0
      stylelint.config.js
  55. 2 2
      tsconfig.json
  56. 2 1
      types/components.d.ts
  57. 4 3
      types/global.d.ts
  58. 10 9
      vite.config.ts

+ 1 - 1
.env.base

@@ -2,7 +2,7 @@
 NODE_ENV=development
 
 # 接口前缀
-VITE_API_BASE_PATH=base
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/

+ 1 - 1
.env.dev

@@ -2,7 +2,7 @@
 NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=dev
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/dist-dev/

+ 1 - 1
.env.gitee

@@ -2,7 +2,7 @@
 NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=pro
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/vue-element-plus-admin/

+ 1 - 1
.env.pro

@@ -2,7 +2,7 @@
 NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=pro
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/

+ 1 - 1
mock/_createProductionServer.ts

@@ -1,6 +1,6 @@
 import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
 
-const modules = import.meta.glob('./**/*.ts', {
+const modules = import.meta.glob('./**/*.mock.ts', {
   import: 'default',
   eager: true
 })

+ 91 - 0
mock/role/index.mock.ts

@@ -0,0 +1,91 @@
+import { MockMethod } from 'vite-plugin-mock'
+import { SUCCESS_CODE } from '@/constants'
+
+const timeout = 1000
+
+const adminList = [
+  {
+    path: '/level',
+    component: '#',
+    redirect: '/level/menu1/menu1-1/menu1-1-1',
+    name: 'Level',
+    meta: {
+      title: 'router.level',
+      icon: 'carbon:skill-level-advanced'
+    },
+    children: [
+      {
+        path: 'menu1',
+        name: 'Menu1',
+        component: '##',
+        redirect: '/level/menu1/menu1-1/menu1-1-1',
+        meta: {
+          title: 'router.menu1'
+        },
+        children: [
+          {
+            path: 'menu1-1',
+            name: 'Menu11',
+            component: '##',
+            redirect: '/level/menu1/menu1-1/menu1-1-1',
+            meta: {
+              title: 'router.menu11',
+              alwaysShow: true
+            },
+            children: [
+              {
+                path: 'menu1-1-1',
+                name: 'Menu111',
+                component: 'views/Level/Menu111',
+                meta: {
+                  title: 'router.menu111'
+                }
+              }
+            ]
+          },
+          {
+            path: 'menu1-2',
+            name: 'Menu12',
+            component: 'views/Level/Menu12',
+            meta: {
+              title: 'router.menu12'
+            }
+          }
+        ]
+      },
+      {
+        path: 'menu2',
+        name: 'Menu2Demo',
+        component: 'views/Level/Menu2',
+        meta: {
+          title: 'router.menu2'
+        }
+      }
+    ]
+  }
+]
+
+const testList: string[] = [
+  '/level',
+  '/level/menu1',
+  '/level/menu1/menu1-1',
+  '/level/menu1/menu1-1/menu1-1-1',
+  '/level/menu1/menu1-2',
+  '/level/menu2'
+]
+
+export default [
+  // 列表接口
+  {
+    url: '/mock/role/list',
+    method: 'get',
+    timeout,
+    response: ({ query }) => {
+      const { roleName } = query
+      return {
+        code: SUCCESS_CODE,
+        data: roleName === 'admin' ? adminList : testList
+      }
+    }
+  }
+] as MockMethod[]

+ 0 - 544
mock/role/index.ts

@@ -1,544 +0,0 @@
-import config from '@/config/axios/config'
-import { MockMethod } from 'vite-plugin-mock'
-
-const { code } = config
-
-const timeout = 1000
-
-const adminList = [
-  {
-    path: '/dashboard',
-    component: '#',
-    redirect: '/dashboard/analysis',
-    name: 'Dashboard',
-    meta: {
-      title: 'router.dashboard',
-      icon: 'ant-design:dashboard-filled',
-      alwaysShow: true
-    },
-    children: [
-      {
-        path: 'analysis',
-        component: 'views/Dashboard/Analysis',
-        name: 'Analysis',
-        meta: {
-          title: 'router.analysis',
-          noCache: true
-        }
-      },
-      {
-        path: 'workplace',
-        component: 'views/Dashboard/Workplace',
-        name: 'Workplace',
-        meta: {
-          title: 'router.workplace',
-          noCache: true
-        }
-      }
-    ]
-  },
-  {
-    path: '/external-link',
-    component: '#',
-    meta: {},
-    name: 'ExternalLink',
-    children: [
-      {
-        path: 'https://element-plus-admin-doc.cn/',
-        name: 'DocumentLink',
-        meta: {
-          title: 'router.document',
-          icon: 'clarity:document-solid'
-        }
-      }
-    ]
-  },
-  {
-    path: '/guide',
-    component: '#',
-    name: 'Guide',
-    meta: {},
-    children: [
-      {
-        path: 'index',
-        component: 'views/Guide/Guide',
-        name: 'GuideDemo',
-        meta: {
-          title: 'router.guide',
-          icon: 'cib:telegram-plane'
-        }
-      }
-    ]
-  },
-  {
-    path: '/components',
-    component: '#',
-    redirect: '/components/form/default-form',
-    name: 'ComponentsDemo',
-    meta: {
-      title: 'router.component',
-      icon: 'bx:bxs-component',
-      alwaysShow: true
-    },
-    children: [
-      {
-        path: 'form',
-        component: '##',
-        name: 'Form',
-        meta: {
-          title: 'router.form',
-          alwaysShow: true
-        },
-        children: [
-          {
-            path: 'default-form',
-            component: 'views/Components/Form/DefaultForm',
-            name: 'DefaultForm',
-            meta: {
-              title: 'router.defaultForm'
-            }
-          },
-          {
-            path: 'use-form',
-            component: 'views/Components/Form/UseFormDemo',
-            name: 'UseForm',
-            meta: {
-              title: 'UseForm'
-            }
-          }
-        ]
-      },
-      {
-        path: 'table',
-        component: '##',
-        redirect: '/components/table/default-table',
-        name: 'TableDemo',
-        meta: {
-          title: 'router.table',
-          alwaysShow: true
-        },
-        children: [
-          {
-            path: 'default-table',
-            component: 'views/Components/Table/DefaultTable',
-            name: 'DefaultTable',
-            meta: {
-              title: 'router.defaultTable'
-            }
-          },
-          {
-            path: 'use-table',
-            component: 'views/Components/Table/UseTableDemo',
-            name: 'UseTable',
-            meta: {
-              title: 'UseTable'
-            }
-          },
-          {
-            path: 'tree-table',
-            component: 'views/Components/Table/TreeTable',
-            name: 'TreeTable',
-            meta: {
-              title: 'router.TreeTable'
-            }
-          },
-          {
-            path: 'table-image-preview',
-            component: 'views/Components/Table/TableImagePreview',
-            name: 'TableImagePreview',
-            meta: {
-              title: 'router.PicturePreview'
-            }
-          },
-          {
-            path: 'ref-table',
-            component: 'views/Components/Table/RefTable',
-            name: 'RefTable',
-            meta: {
-              title: 'RefTable'
-            }
-          }
-        ]
-      },
-      {
-        path: 'editor-demo',
-        component: '##',
-        redirect: '/components/editor-demo/editor',
-        name: 'EditorDemo',
-        meta: {
-          title: 'router.editor',
-          alwaysShow: true
-        },
-        children: [
-          {
-            path: 'editor',
-            component: 'views/Components/Editor/Editor',
-            name: 'Editor',
-            meta: {
-              title: 'router.richText'
-            }
-          }
-        ]
-      },
-      {
-        path: 'search',
-        component: 'views/Components/Search',
-        name: 'Search',
-        meta: {
-          title: 'router.search'
-        }
-      },
-      {
-        path: 'descriptions',
-        component: 'views/Components/Descriptions',
-        name: 'Descriptions',
-        meta: {
-          title: 'router.descriptions'
-        }
-      },
-      {
-        path: 'image-viewer',
-        component: 'views/Components/ImageViewer',
-        name: 'ImageViewer',
-        meta: {
-          title: 'router.imageViewer'
-        }
-      },
-      {
-        path: 'dialog',
-        component: 'views/Components/Dialog',
-        name: 'Dialog',
-        meta: {
-          title: 'router.dialog'
-        }
-      },
-      {
-        path: 'icon',
-        component: 'views/Components/Icon',
-        name: 'Icon',
-        meta: {
-          title: 'router.icon'
-        }
-      },
-      {
-        path: 'echart',
-        component: 'views/Components/Echart',
-        name: 'Echart',
-        meta: {
-          title: 'router.echart'
-        }
-      },
-      {
-        path: 'count-to',
-        component: 'views/Components/CountTo',
-        name: 'CountTo',
-        meta: {
-          title: 'router.countTo'
-        }
-      },
-      {
-        path: 'qrcode',
-        component: 'views/Components/Qrcode',
-        name: 'Qrcode',
-        meta: {
-          title: 'router.qrcode'
-        }
-      },
-      {
-        path: 'highlight',
-        component: 'views/Components/Highlight',
-        name: 'Highlight',
-        meta: {
-          title: 'router.highlight'
-        }
-      },
-      {
-        path: 'infotip',
-        component: 'views/Components/Infotip',
-        name: 'Infotip',
-        meta: {
-          title: 'router.infotip'
-        }
-      },
-      {
-        path: 'input-password',
-        component: 'views/Components/InputPassword',
-        name: 'InputPassword',
-        meta: {
-          title: 'router.inputPassword'
-        }
-      },
-      {
-        path: 'sticky',
-        component: 'views/Components/Sticky',
-        name: 'Sticky',
-        meta: {
-          title: 'router.sticky'
-        }
-      }
-    ]
-  },
-  {
-    path: '/hooks',
-    component: '#',
-    redirect: '/hooks/useWatermark',
-    name: 'Hooks',
-    meta: {
-      title: 'hooks',
-      icon: 'ic:outline-webhook',
-      alwaysShow: true
-    },
-    children: [
-      {
-        path: 'useWatermark',
-        component: 'views/hooks/useWatermark',
-        name: 'UseWatermark',
-        meta: {
-          title: 'useWatermark'
-        }
-      },
-      {
-        path: 'useCrudSchemas',
-        component: 'views/hooks/useCrudSchemas',
-        name: 'UseCrudSchemas',
-        meta: {
-          title: 'useCrudSchemas'
-        }
-      }
-    ]
-  },
-  {
-    path: '/level',
-    component: '#',
-    redirect: '/level/menu1/menu1-1/menu1-1-1',
-    name: 'Level',
-    meta: {
-      title: 'router.level',
-      icon: 'carbon:skill-level-advanced'
-    },
-    children: [
-      {
-        path: 'menu1',
-        name: 'Menu1',
-        component: '##',
-        redirect: '/level/menu1/menu1-1/menu1-1-1',
-        meta: {
-          title: 'router.menu1'
-        },
-        children: [
-          {
-            path: 'menu1-1',
-            name: 'Menu11',
-            component: '##',
-            redirect: '/level/menu1/menu1-1/menu1-1-1',
-            meta: {
-              title: 'router.menu11',
-              alwaysShow: true
-            },
-            children: [
-              {
-                path: 'menu1-1-1',
-                name: 'Menu111',
-                component: 'views/Level/Menu111',
-                meta: {
-                  title: 'router.menu111'
-                }
-              }
-            ]
-          },
-          {
-            path: 'menu1-2',
-            name: 'Menu12',
-            component: 'views/Level/Menu12',
-            meta: {
-              title: 'router.menu12'
-            }
-          }
-        ]
-      },
-      {
-        path: 'menu2',
-        name: 'Menu2Demo',
-        component: 'views/Level/Menu2',
-        meta: {
-          title: 'router.menu2'
-        }
-      }
-    ]
-  },
-  {
-    path: '/example',
-    component: '#',
-    redirect: '/example/example-dialog',
-    name: 'Example',
-    meta: {
-      title: 'router.example',
-      icon: 'ep:management',
-      alwaysShow: true
-    },
-    children: [
-      {
-        path: 'example-dialog',
-        component: 'views/Example/Dialog/ExampleDialog',
-        name: 'ExampleDialog',
-        meta: {
-          title: 'router.exampleDialog'
-        }
-      },
-      {
-        path: 'example-page',
-        component: 'views/Example/Page/ExamplePage',
-        name: 'ExamplePage',
-        meta: {
-          title: 'router.examplePage'
-        }
-      },
-      {
-        path: 'example-add',
-        component: 'views/Example/Page/ExampleAdd',
-        name: 'ExampleAdd',
-        meta: {
-          title: 'router.exampleAdd',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          showMainRoute: true,
-          activeMenu: '/example/example-page'
-        }
-      },
-      {
-        path: 'example-edit',
-        component: 'views/Example/Page/ExampleEdit',
-        name: 'ExampleEdit',
-        meta: {
-          title: 'router.exampleEdit',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          showMainRoute: true,
-          activeMenu: '/example/example-page'
-        }
-      },
-      {
-        path: 'example-detail',
-        component: 'views/Example/Page/ExampleDetail',
-        name: 'ExampleDetail',
-        meta: {
-          title: 'router.exampleDetail',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          showMainRoute: true,
-          activeMenu: '/example/example-page'
-        }
-      }
-    ]
-  },
-  {
-    path: '/error',
-    component: '#',
-    redirect: '/error/404',
-    name: 'Error',
-    meta: {
-      title: 'router.errorPage',
-      icon: 'ci:error',
-      alwaysShow: true
-    },
-    children: [
-      {
-        path: '404-demo',
-        component: 'views/Error/404',
-        name: '404Demo',
-        meta: {
-          title: '404'
-        }
-      },
-      {
-        path: '403-demo',
-        component: 'views/Error/403',
-        name: '403Demo',
-        meta: {
-          title: '403'
-        }
-      },
-      {
-        path: '500-demo',
-        component: 'views/Error/500',
-        name: '500Demo',
-        meta: {
-          title: '500'
-        }
-      }
-    ]
-  }
-]
-
-const testList: string[] = [
-  '/dashboard',
-  '/dashboard/analysis',
-  '/dashboard/workplace',
-  'external-link',
-  'https://element-plus-admin-doc.cn/',
-  '/guide',
-  '/guide/index',
-  '/components',
-  '/components/form',
-  '/components/form/default-form',
-  '/components/form/use-form',
-  '/components/form/ref-form',
-  '/components/table',
-  '/components/table/default-table',
-  '/components/table/use-table',
-  '/components/table/tree-table',
-  '/components/table/table-image-preview',
-  '/components/table/ref-table',
-  '/components/editor-demo',
-  '/components/editor-demo/editor',
-  '/components/search',
-  '/components/descriptions',
-  '/components/image-viewer',
-  '/components/dialog',
-  '/components/icon',
-  '/components/echart',
-  '/components/count-to',
-  '/components/qrcode',
-  '/components/highlight',
-  '/components/infotip',
-  '/Components/InputPassword',
-  '/Components/Sticky',
-  '/hooks',
-  '/hooks/useWatermark',
-  '/hooks/useCrudSchemas',
-  '/level',
-  '/level/menu1',
-  '/level/menu1/menu1-1',
-  '/level/menu1/menu1-1/menu1-1-1',
-  '/level/menu1/menu1-2',
-  '/level/menu2',
-  '/example',
-  '/example/example-dialog',
-  '/example/example-page',
-  '/example/example-add',
-  '/example/example-edit',
-  '/example/example-detail',
-  '/error',
-  '/error/404-demo',
-  '/error/403-demo',
-  '/error/500-demo'
-]
-
-export default [
-  // 列表接口
-  {
-    url: '/role/list',
-    method: 'get',
-    timeout,
-    response: ({ query }) => {
-      const { roleName } = query
-      return {
-        code: code,
-        data: roleName === 'admin' ? adminList : testList
-      }
-    }
-  }
-] as MockMethod[]

+ 8 - 11
mock/user/index.ts → mock/user/index.mock.ts

@@ -1,7 +1,4 @@
-import config from '@/config/axios/config'
-import { MockMethod } from 'vite-plugin-mock'
-
-const { code } = config
+import { SUCCESS_CODE } from '@/constants'
 
 const timeout = 1000
 
@@ -31,7 +28,7 @@ const List: {
 export default [
   // 列表接口
   {
-    url: '/user/list',
+    url: '/mock/user/list',
     method: 'get',
     response: ({ query }) => {
       const { username, pageIndex, pageSize } = query
@@ -45,8 +42,8 @@ export default [
       )
 
       return {
+        code: SUCCESS_CODE,
         data: {
-          code: code,
           total: mockList.length,
           list: pageList
         }
@@ -55,7 +52,7 @@ export default [
   },
   // 登录接口
   {
-    url: '/user/login',
+    url: '/mock/user/login',
     method: 'post',
     timeout,
     response: ({ body }) => {
@@ -65,7 +62,7 @@ export default [
         if (user.username === data.username && user.password === data.password) {
           hasUser = true
           return {
-            code: code,
+            code: SUCCESS_CODE,
             data: user
           }
         }
@@ -80,14 +77,14 @@ export default [
   },
   // 退出接口
   {
-    url: '/user/loginOut',
+    url: '/mock/user/loginOut',
     method: 'get',
     timeout,
     response: () => {
       return {
-        code: code,
+        code: SUCCESS_CODE,
         data: null
       }
     }
   }
-] as MockMethod[]
+]

+ 51 - 42
package.json

@@ -6,18 +6,18 @@
   "private": false,
   "scripts": {
     "i": "pnpm install",
-    "dev": "vite --mode base",
-    "ts:check": "vue-tsc --noEmit --skipLibCheck",
-    "build:pro": "vite build --mode pro",
-    "build:gitee": "vite build --mode gitee",
-    "build:dev": "vite build --mode dev",
-    "build:test": "npm run ts:check && vite build --mode test",
-    "serve:pro": "vite preview --mode pro",
-    "serve:dev": "vite preview --mode dev",
-    "serve:test": "vite preview --mode test",
-    "npm:check": "npx npm-check-updates",
-    "clean": "npx rimraf node_modules",
-    "clean:cache": "npx rimraf node_modules/.cache",
+    "dev": "pnpm vite --mode base",
+    "ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
+    "build:pro": "pnpm vite build --mode pro",
+    "build:gitee": "pnpm vite build --mode gitee",
+    "build:dev": "pnpm vite build --mode dev",
+    "build:test": "pnpm run ts:check && vite build --mode test",
+    "serve:pro": "pnpm vite preview --mode pro",
+    "serve:dev": "pnpm vite preview --mode dev",
+    "serve:test": "pnpm vite preview --mode test",
+    "npm:check": "pnpx npm-check-updates -u",
+    "clean": "pnpx rimraf node_modules",
+    "clean:cache": "pnpx rimraf node_modules/.cache",
     "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
     "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
     "lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
@@ -26,90 +26,99 @@
     "p": "plop"
   },
   "dependencies": {
+    "@faker-js/faker": "^8.3.1",
     "@iconify/iconify": "^3.1.1",
     "@iconify/vue": "^4.1.1",
-    "@vueuse/core": "^10.6.1",
+    "@vueuse/core": "^10.7.0",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.10",
     "@zxcvbn-ts/core": "^3.0.4",
     "animate.css": "^4.1.1",
     "axios": "^1.6.2",
+    "cropperjs": "^1.6.1",
     "dayjs": "^1.11.10",
+    "driver.js": "^1.3.1",
     "echarts": "^5.4.3",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "^2.4.2",
+    "element-plus": "^2.4.3",
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.1",
-    "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
-    "pinia-plugin-persist": "^1.0.0",
+    "pinia-plugin-persistedstate": "^3.2.0",
     "qrcode": "^1.5.3",
     "qs": "^6.11.2",
-    "sortablejs": "^1.15.0",
     "url": "^0.11.3",
-    "vue": "3.3.8",
-    "vue-i18n": "9.7.0",
+    "vue": "3.3.10",
+    "vue-i18n": "9.8.0",
+    "vue-json-pretty": "^2.2.4",
     "vue-router": "^4.2.5",
-    "vue-types": "^5.1.1"
+    "vue-types": "^5.1.1",
+    "xgplayer": "^3.0.10"
   },
   "devDependencies": {
     "@commitlint/cli": "^18.4.3",
     "@commitlint/config-conventional": "^18.4.3",
-    "@iconify/json": "^2.2.144",
+    "@iconify/json": "^2.2.153",
     "@intlify/unplugin-vue-i18n": "^1.5.0",
     "@purge-icons/generated": "^0.10.0",
+    "@types/fs-extra": "^11.0.4",
+    "@types/inquirer": "^9.0.7",
     "@types/lodash-es": "^4.17.12",
-    "@types/node": "^20.9.4",
+    "@types/node": "^20.10.3",
     "@types/nprogress": "^0.2.3",
     "@types/qrcode": "^1.5.5",
     "@types/qs": "^6.9.10",
     "@types/sortablejs": "^1.15.7",
-    "@typescript-eslint/eslint-plugin": "^6.12.0",
-    "@typescript-eslint/parser": "^6.12.0",
-    "@unocss/transformer-variant-group": "^0.57.7",
-    "@vitejs/plugin-legacy": "^5.1.0",
-    "@vitejs/plugin-vue": "^4.5.0",
+    "@typescript-eslint/eslint-plugin": "^6.13.2",
+    "@typescript-eslint/parser": "^6.13.2",
+    "@unocss/transformer-variant-group": "^0.58.0",
+    "@vitejs/plugin-legacy": "^5.2.0",
+    "@vitejs/plugin-vue": "^4.5.1",
     "@vitejs/plugin-vue-jsx": "^3.1.0",
-    "@vue-macros/volar": "^0.17.3",
     "autoprefixer": "^10.4.16",
+    "chalk": "^5.3.0",
     "consola": "^3.2.3",
-    "eslint": "^8.54.0",
-    "eslint-config-prettier": "^9.0.0",
+    "eslint": "^8.55.0",
+    "eslint-config-prettier": "^9.1.0",
     "eslint-define-config": "^2.0.0",
     "eslint-plugin-prettier": "^5.0.1",
-    "eslint-plugin-vue": "^9.18.1",
+    "eslint-plugin-vue": "^9.19.2",
+    "esno": "^4.0.0",
+    "fs-extra": "^11.2.0",
     "husky": "^8.0.3",
+    "inquirer": "^9.2.12",
     "less": "^4.2.0",
-    "lint-staged": "^15.1.0",
+    "lint-staged": "^15.2.0",
     "plop": "^4.0.0",
-    "postcss": "^8.4.31",
+    "postcss": "^8.4.32",
     "postcss-html": "^1.5.0",
     "postcss-less": "^6.0.0",
     "prettier": "^3.1.0",
     "rimraf": "^5.0.5",
-    "rollup": "^4.5.1",
+    "rollup": "^4.6.1",
     "stylelint": "^15.11.0",
     "stylelint-config-html": "^1.1.0",
     "stylelint-config-recommended": "^13.0.0",
     "stylelint-config-standard": "^34.0.0",
     "stylelint-order": "^6.0.3",
-    "terser": "^5.24.0",
-    "typescript": "5.3.2",
-    "unocss": "^0.57.7",
-    "unplugin-vue-define-options": "^1.4.0",
-    "vite": "5.0.2",
+    "terser": "^5.25.0",
+    "typescript": "5.3.3",
+    "unocss": "^0.58.0",
+    "vite": "5.0.6",
     "vite-plugin-ejs": "^1.7.0",
     "vite-plugin-eslint": "^1.8.1",
-    "vite-plugin-mock": "2.9.6",
+    "vite-plugin-mock": "^2.9.6",
     "vite-plugin-progress": "^0.0.7",
     "vite-plugin-purge-icons": "^0.10.0",
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "^2.0.1",
-    "vue-tsc": "^1.8.22"
+    "vue-tsc": "^1.8.25"
   },
+  "packageManager": "pnpm@8.1.0",
   "engines": {
-    "node": ">= 18.0.0 || >= 20.0.0"
+    "node": ">=18.0.0",
+    "pnpm": ">=8.1.0"
   },
   "license": "MIT",
   "repository": {

+ 0 - 11
src/api/common/index.ts

@@ -1,11 +0,0 @@
-import request from '@/config/axios'
-
-// 获取所有字典
-export const getDictApi = () => {
-  return request.get({ url: '/dict/list' })
-}
-
-// 模拟获取某个字典
-export const getDictOneApi = async () => {
-  return request.get({ url: '/dict/one' })
-}

+ 0 - 23
src/api/dashboard/analysis/index.ts

@@ -1,23 +0,0 @@
-import request from '@/config/axios'
-import type {
-  AnalysisTotalTypes,
-  UserAccessSource,
-  WeeklyUserActivity,
-  MonthlySales
-} from './types'
-
-export const getCountApi = (): Promise<IResponse<AnalysisTotalTypes[]>> => {
-  return request.get({ url: '/analysis/total' })
-}
-
-export const getUserAccessSourceApi = (): Promise<IResponse<UserAccessSource[]>> => {
-  return request.get({ url: '/analysis/userAccessSource' })
-}
-
-export const getWeeklyUserActivityApi = (): Promise<IResponse<WeeklyUserActivity[]>> => {
-  return request.get({ url: '/analysis/weeklyUserActivity' })
-}
-
-export const getMonthlySalesApi = (): Promise<IResponse<MonthlySales[]>> => {
-  return request.get({ url: '/analysis/monthlySales' })
-}

+ 0 - 22
src/api/dashboard/analysis/types.ts

@@ -1,22 +0,0 @@
-export type AnalysisTotalTypes = {
-  users: number
-  messages: number
-  moneys: number
-  shoppings: number
-}
-
-export type UserAccessSource = {
-  value: number
-  name: string
-}
-
-export type WeeklyUserActivity = {
-  value: number
-  name: string
-}
-
-export type MonthlySales = {
-  name: string
-  estimate: number
-  actual: number
-}

+ 0 - 22
src/api/dashboard/workplace/index.ts

@@ -1,22 +0,0 @@
-import request from '@/config/axios'
-import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types'
-
-export const getCountApi = (): Promise<IResponse<WorkplaceTotal>> => {
-  return request.get({ url: '/workplace/total' })
-}
-
-export const getProjectApi = (): Promise<IResponse<Project>> => {
-  return request.get({ url: '/workplace/project' })
-}
-
-export const getDynamicApi = (): Promise<IResponse<Dynamic[]>> => {
-  return request.get({ url: '/workplace/dynamic' })
-}
-
-export const getTeamApi = (): Promise<IResponse<Team[]>> => {
-  return request.get({ url: '/workplace/team' })
-}
-
-export const getRadarApi = (): Promise<IResponse<RadarData[]>> => {
-  return request.get({ url: '/workplace/radar' })
-}

+ 0 - 30
src/api/dashboard/workplace/types.ts

@@ -1,30 +0,0 @@
-export type WorkplaceTotal = {
-  project: number
-  access: number
-  todo: number
-}
-
-export type Project = {
-  name: string
-  icon: string
-  message: string
-  personal: string
-  time: Date | number | string
-}
-
-export type Dynamic = {
-  keys: string[]
-  time: Date | number | string
-}
-
-export type Team = {
-  name: string
-  icon: string
-}
-
-export type RadarData = {
-  personal: number
-  team: number
-  max: number
-  name: string
-}

+ 0 - 30
src/api/department/index.ts

@@ -1,30 +0,0 @@
-import request from '@/config/axios'
-import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
-
-export const getDepartmentApi = () => {
-  return request.get<DepartmentListResponse>({ url: '/department/list' })
-}
-
-export const getUserByIdApi = (params: DepartmentUserParams) => {
-  return request.get<DepartmentUserResponse>({ url: '/department/users', params })
-}
-
-export const deleteUserByIdApi = (ids: string[] | number[]) => {
-  return request.post({ url: '/department/user/delete', data: { ids } })
-}
-
-export const saveUserApi = (data: any) => {
-  return request.post({ url: '/department/user/save', data })
-}
-
-export const saveDepartmentApi = (data: any) => {
-  return request.post({ url: '/department/save', data })
-}
-
-export const deleteDepartmentApi = (ids: string[] | number[]) => {
-  return request.post({ url: '/department/delete', data: { ids } })
-}
-
-export const getDepartmentTableApi = (params: any) => {
-  return request.get({ url: '/department/table/list', params })
-}

+ 0 - 32
src/api/department/types.ts

@@ -1,32 +0,0 @@
-export interface DepartmentItem {
-  id: string
-  departmentName: string
-  children?: DepartmentItem[]
-}
-
-export interface DepartmentListResponse {
-  list: DepartmentItem[]
-}
-
-export interface DepartmentUserParams {
-  pageSize: number
-  pageIndex: number
-  id: string
-  username?: string
-  account?: string
-}
-
-export interface DepartmentUserItem {
-  id: string
-  username: string
-  account: string
-  email: string
-  createTime: string
-  role: string
-  department: DepartmentItem
-}
-
-export interface DepartmentUserResponse {
-  list: DepartmentUserItem[]
-  total: number
-}

+ 5 - 15
src/api/login/index.ts

@@ -1,4 +1,4 @@
-import request from '@/config/axios'
+import request from '@/axios'
 import type { UserType } from './types'
 
 interface RoleParams {
@@ -6,29 +6,19 @@ interface RoleParams {
 }
 
 export const loginApi = (data: UserType): Promise<IResponse<UserType>> => {
-  return request.post({ url: '/user/login', data })
+  return request.post({ url: '/mock/user/login', data })
 }
 
 export const loginOutApi = (): Promise<IResponse> => {
-  return request.get({ url: '/user/loginOut' })
-}
-
-export const getUserListApi = ({ params }: AxiosConfig) => {
-  return request.get<{
-    code: string
-    data: {
-      list: UserType[]
-      total: number
-    }
-  }>({ url: '/user/list', params })
+  return request.get({ url: '/mock/user/loginOut' })
 }
 
 export const getAdminRoleApi = (
   params: RoleParams
 ): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
-  return request.get({ url: '/role/list', params })
+  return request.get({ url: '/mock/role/list', params })
 }
 
 export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
-  return request.get({ url: '/role/list', params })
+  return request.get({ url: '/mock/role/list2', params })
 }

+ 0 - 5
src/api/menu/index.ts

@@ -1,5 +0,0 @@
-import request from '@/config/axios'
-
-export const getMenuListApi = () => {
-  return request.get({ url: '/menu/list' })
-}

+ 0 - 22
src/api/table/index.ts

@@ -1,22 +0,0 @@
-import request from '@/config/axios'
-import type { TableData } from './types'
-
-export const getTableListApi = (params: any) => {
-  return request.get({ url: '/example/list', params })
-}
-
-export const getTreeTableListApi = (params: any) => {
-  return request.get({ url: '/example/treeList', params })
-}
-
-export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
-  return request.post({ url: '/example/save', data })
-}
-
-export const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {
-  return request.get({ url: '/example/detail', params: { id } })
-}
-
-export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {
-  return request.post({ url: '/example/delete', data: { ids } })
-}

+ 0 - 9
src/api/table/types.ts

@@ -1,9 +0,0 @@
-export type TableData = {
-  id: string
-  author: string
-  title: string
-  content: string
-  importance: number
-  display_time: string
-  pageviews: number
-}

+ 45 - 0
src/axios/config.ts

@@ -0,0 +1,45 @@
+import { AxiosResponse, InternalAxiosRequestConfig } from './types'
+import { ElMessage } from 'element-plus'
+import qs from 'qs'
+import { SUCCESS_CODE } from '@/constants'
+import { useUserStoreWithOut } from '@/store/modules/user'
+
+const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
+  if (
+    config.method === 'post' &&
+    config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
+  ) {
+    config.data = qs.stringify(config.data)
+  }
+  if (config.method === 'get' && config.params) {
+    let url = config.url as string
+    url += '?'
+    const keys = Object.keys(config.params)
+    for (const key of keys) {
+      if (config.params[key] !== void 0 && config.params[key] !== null) {
+        url += `${key}=${encodeURIComponent(config.params[key])}&`
+      }
+    }
+    url = url.substring(0, url.length - 1)
+    config.params = {}
+    config.url = url
+  }
+  return config
+}
+
+const defaultResponseInterceptors = (response: AxiosResponse) => {
+  if (response?.config?.responseType === 'blob') {
+    // 如果是文件流,直接过
+    return response
+  } else if (response.data.code === SUCCESS_CODE) {
+    return response.data
+  } else {
+    ElMessage.error(response?.data?.message)
+    if (response?.data?.code === 401) {
+      const userStore = useUserStoreWithOut()
+      userStore.logout()
+    }
+  }
+}
+
+export { defaultResponseInterceptors, defaultRequestInterceptors }

+ 7 - 6
src/config/axios/index.ts → src/axios/index.ts

@@ -1,11 +1,10 @@
 import service from './service'
-
-import config from './config'
-
-const { defaultHeaders } = config
+import { CONTENT_TYPE } from '@/constants'
+import { useUserStoreWithOut } from '@/store/modules/user'
 
 const request = (option: AxiosConfig) => {
-  const { url, method, params, data, headersType, responseType } = option
+  const { url, method, params, data, headers, responseType } = option
+  const userStore = useUserStoreWithOut()
   return service.request({
     url: url,
     method,
@@ -13,7 +12,9 @@ const request = (option: AxiosConfig) => {
     data,
     responseType: responseType,
     headers: {
-      'Content-Type': headersType || defaultHeaders
+      'Content-Type': CONTENT_TYPE,
+      [userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
+      ...headers
     }
   })
 }

+ 7 - 8
src/config/axios/service.ts → src/axios/service.ts

@@ -1,18 +1,16 @@
 import axios, { AxiosError } from 'axios'
-import config, { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
+import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
 
 import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
 import { ElMessage } from 'element-plus'
+import { REQUEST_TIMEOUT } from '@/constants'
 
-const { interceptors, baseUrl } = config
-export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH]
-
-const { requestInterceptors, responseInterceptors } = interceptors
+export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
 
 const abortControllerMap: Map<string, AbortController> = new Map()
 
 const axiosInstance: AxiosInstance = axios.create({
-  ...config,
+  timeout: REQUEST_TIMEOUT,
   baseURL: PATH_URL
 })
 
@@ -28,6 +26,7 @@ axiosInstance.interceptors.response.use(
   (res: AxiosResponse) => {
     const url = res.config.url || ''
     abortControllerMap.delete(url)
+    // 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
     return res
   },
   (error: AxiosError) => {
@@ -37,8 +36,8 @@ axiosInstance.interceptors.response.use(
   }
 )
 
-axiosInstance.interceptors.request.use(requestInterceptors || defaultRequestInterceptors)
-axiosInstance.interceptors.response.use(responseInterceptors || defaultResponseInterceptors)
+axiosInstance.interceptors.request.use(defaultRequestInterceptors)
+axiosInstance.interceptors.response.use(defaultResponseInterceptors)
 
 const service = {
   request: (config: RequestConfig) => {

+ 0 - 13
src/config/axios/types/index.ts → src/axios/types/index.ts

@@ -15,18 +15,6 @@ interface RequestInterceptors<T> {
   responseInterceptors?: (config: T) => T
   responseInterceptorsCatch?: (err: any) => any
 }
-interface AxiosConfig<T = AxiosResponse> {
-  baseUrl: {
-    base: string
-    dev: string
-    pro: string
-    test: string
-  }
-  code: number
-  defaultHeaders: AxiosHeaders
-  timeout: number
-  interceptors: RequestInterceptors<T>
-}
 
 interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
   interceptors?: RequestInterceptors<T>
@@ -36,7 +24,6 @@ export {
   AxiosResponse,
   RequestInterceptors,
   RequestConfig,
-  AxiosConfig,
   AxiosInstance,
   InternalAxiosRequestConfig,
   AxiosRequestHeaders,

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

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

+ 121 - 0
src/components/Button/src/Button.vue

@@ -0,0 +1,121 @@
+<script setup lang="ts">
+import { useDesign } from '@/hooks/web/useDesign'
+import { ElButton, ComponentSize, ButtonType } from 'element-plus'
+import { PropType, Component, computed, unref } from 'vue'
+import { useAppStore } from '@/store/modules/app'
+
+const appStore = useAppStore()
+
+const getTheme = computed(() => appStore.getTheme)
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('button')
+
+const props = defineProps({
+  size: {
+    type: String as PropType<ComponentSize>,
+    default: undefined
+  },
+  type: {
+    type: String as PropType<ButtonType>,
+    default: 'default'
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  plain: {
+    type: Boolean,
+    default: false
+  },
+  text: {
+    type: Boolean,
+    default: false
+  },
+  bg: {
+    type: Boolean,
+    default: false
+  },
+  link: {
+    type: Boolean,
+    default: false
+  },
+  round: {
+    type: Boolean,
+    default: false
+  },
+  circle: {
+    type: Boolean,
+    default: false
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  },
+  loadingIcon: {
+    type: [String, Object] as PropType<String | Component>,
+    default: undefined
+  },
+  icon: {
+    type: [String, Object] as PropType<String | Component>,
+    default: undefined
+  },
+  autofocus: {
+    type: Boolean,
+    default: false
+  },
+  nativeType: {
+    type: String as PropType<'button' | 'submit' | 'reset'>,
+    default: 'button'
+  },
+  autoInsertSpace: {
+    type: Boolean,
+    default: false
+  },
+  color: {
+    type: String,
+    default: ''
+  },
+  darker: {
+    type: Boolean,
+    default: false
+  },
+  tag: {
+    type: [String, Object] as PropType<String | Component>,
+    default: 'button'
+  }
+})
+
+const emits = defineEmits(['click'])
+
+const color = computed(() => {
+  const { type } = props
+  if (type === 'primary') {
+    return unref(getTheme).elColorPrimary
+  }
+  return ''
+})
+
+const style = computed(() => {
+  const { type } = props
+  if (type === 'primary') {
+    return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
+  }
+  return ''
+})
+</script>
+
+<template>
+  <ElButton
+    :class="`${prefixCls} color-#fff`"
+    v-bind="{ ...props }"
+    :color="color"
+    :style="style"
+    @click="() => emits('click')"
+  >
+    <slot></slot>
+    <slot name="icon"></slot>
+    <slot name="loading"></slot>
+  </ElButton>
+</template>

+ 3 - 2
src/components/Menu/src/Menu.vue

@@ -155,6 +155,7 @@ export default defineComponent({
     .is-active {
       & > .@{elNamespace}-sub-menu__title {
         color: var(--left-menu-text-active-color) !important;
+        // background-color: var(--left-menu-bg-color) !important;
       }
     }
 
@@ -168,7 +169,6 @@ export default defineComponent({
     }
 
     // 设置选中时的高亮背景和高亮颜色
-    .@{elNamespace}-sub-menu.is-active,
     .@{elNamespace}-menu-item.is-active {
       color: var(--left-menu-text-active-color) !important;
       background-color: var(--left-menu-bg-active-color) !important;
@@ -235,7 +235,7 @@ export default defineComponent({
       .@{elNamespace}-menu-item.is-active {
         position: relative;
 
-        &:after {
+        &::after {
           display: none !important;
         }
       }
@@ -270,6 +270,7 @@ export default defineComponent({
   .is-active {
     & > .el-sub-menu__title {
       color: var(--left-menu-text-active-color) !important;
+      // background-color: var(--left-menu-bg-color) !important;
     }
   }
 

+ 7 - 8
src/components/Menu/src/components/useRenderMenuItem.tsx

@@ -1,24 +1,24 @@
 import { ElSubMenu, ElMenuItem } from 'element-plus'
-import type { RouteMeta } from 'vue-router'
 import { hasOneShowingChild } from '../helper'
 import { isUrl } from '@/utils/is'
 import { useRenderMenuTitle } from './useRenderMenuTitle'
 import { useDesign } from '@/hooks/web/useDesign'
 import { pathResolve } from '@/utils/routerHelper'
 
+const { renderMenuTitle } = useRenderMenuTitle()
+
 export const useRenderMenuItem = (
   // allRouters: AppRouteRecordRaw[] = [],
   menuMode: 'vertical' | 'horizontal'
 ) => {
   const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
-    return routers.map((v) => {
-      const meta = (v.meta ?? {}) as RouteMeta
-      if (!meta.hidden) {
+    return routers
+      .filter((v) => !v.meta?.hidden)
+      .map((v) => {
+        const meta = v.meta ?? {}
         const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
         const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
 
-        const { renderMenuTitle } = useRenderMenuTitle()
-
         if (
           oneShowingChild &&
           (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
@@ -49,8 +49,7 @@ export const useRenderMenuItem = (
             </ElSubMenu>
           )
         }
-      }
-    })
+      })
   }
 
   return {

+ 7 - 1
src/components/Search/src/Search.vue

@@ -88,6 +88,9 @@ const newSchema = computed(() => {
                   />
                 </div>
               )
+            },
+            label: () => {
+              return <span>&nbsp;</span>
             }
           }
         }
@@ -117,11 +120,14 @@ const setProps = (props: SearchProps = {}) => {
   outsideProps.value = props
 }
 
+const schemaRef = ref<FormSchema[]>([])
+
 // 监听表单结构化数组,重新生成formModel
 watch(
   () => unref(newSchema),
   async (schema = []) => {
     formModel.value = initModel(schema, unref(formModel))
+    schemaRef.value = schema
   },
   {
     immediate: true,
@@ -241,7 +247,7 @@ const onFormValidate = (prop: FormItemProp, isValid: boolean, message: string) =
     hide-required-asterisk
     :inline="getProps.inline"
     :is-col="getProps.isCol"
-    :schema="newSchema"
+    :schema="schemaRef"
     @register="formRegister"
     @validate="onFormValidate"
   />

+ 10 - 9
src/components/Setting/src/Setting.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
+import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
 import { ref, unref, computed, watch } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ThemeSwitch } from '@/components/ThemeSwitch'
@@ -14,7 +14,7 @@ import { useStorage } from '@/hooks/web/useStorage'
 import { useClipboard } from '@vueuse/core'
 import { useDesign } from '@/hooks/web/useDesign'
 
-const { removeStorage } = useStorage()
+const { clear: storageClear } = useStorage('localStorage')
 
 const { getPrefixCls } = useDesign()
 
@@ -174,7 +174,8 @@ const copyConfig = async () => {
         // 头部边框颜色
         topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
       }
-    `
+    `,
+    legacy: true
   })
   if (!isSupported) {
     ElMessage.error(t('setting.copyFailed'))
@@ -188,9 +189,7 @@ const copyConfig = async () => {
 
 // 清空缓存
 const clear = () => {
-  removeStorage('layout')
-  removeStorage('theme')
-  removeStorage('isDark')
+  storageClear()
   window.location.reload()
 }
 </script>
@@ -278,12 +277,14 @@ const clear = () => {
 
     <ElDivider />
     <div>
-      <ElButton type="primary" class="w-full" @click="copyConfig">{{ t('setting.copy') }}</ElButton>
+      <BaseButton type="primary" class="w-full" @click="copyConfig">{{
+        t('setting.copy')
+      }}</BaseButton>
     </div>
     <div class="mt-5px">
-      <ElButton type="danger" class="w-full" @click="clear">
+      <BaseButton type="danger" class="w-full" @click="clear">
         {{ t('setting.clearAndReset') }}
-      </ElButton>
+      </BaseButton>
     </div>
   </ElDrawer>
 </template>

+ 14 - 1
src/components/Setting/src/components/InterfaceDisplay.vue

@@ -108,13 +108,21 @@ const greyModeChange = (show: boolean) => {
 }
 
 // 动态路由
-const dynamicRouter = ref(appStore.getDynamicRouter)
+const dynamicRouter = ref(!!appStore.getDynamicRouter)
 
 const dynamicRouterChange = (show: boolean) => {
   ElMessage.info(t('setting.reExperienced'))
   appStore.setDynamicRouter(show)
 }
 
+// 服务端动态路由
+const serverDynamicRouter = ref(appStore.getServerDynamicRouter)
+
+const serverDynamicRouterChange = (show: boolean) => {
+  ElMessage.info(t('setting.reExperienced'))
+  appStore.setServerDynamicRouter(show)
+}
+
 // 固定菜单
 const fixedMenu = ref(appStore.getFixedMenu)
 
@@ -206,6 +214,11 @@ watch(
       <ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
     </div>
 
+    <div class="flex justify-between items-center">
+      <span class="text-14px">{{ t('setting.serverDynamicRouter') }}</span>
+      <ElSwitch v-model="serverDynamicRouter" @change="serverDynamicRouterChange" />
+    </div>
+
     <div class="flex justify-between items-center">
       <span class="text-14px">{{ t('setting.fixedMenu') }}</span>
       <ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />

+ 8 - 8
src/components/Setting/src/components/LayoutRadioPicker.vue

@@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
     border: 2px solid #e5e7eb;
     border-radius: 4px;
 
-    &:before {
+    &::before {
       position: absolute;
       top: 0;
       left: 0;
@@ -79,14 +79,14 @@ const layout = computed(() => appStore.getLayout)
       content: '';
     }
 
-    &:after {
+    &::after {
       position: absolute;
       top: 0;
       left: 0;
       width: 100%;
       height: 25%;
       background-color: #fff;
-      border-radius: 4px 4px 0 4px;
+      border-radius: 4px 4px 0;
       content: '';
     }
   }
@@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
     border: 2px solid #e5e7eb;
     border-radius: 4px;
 
-    &:before {
+    &::before {
       position: absolute;
       top: 0;
       left: 0;
@@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
       content: '';
     }
 
-    &:after {
+    &::after {
       position: absolute;
       top: 0;
       left: 0;
@@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
     border: 2px solid #e5e7eb;
     border-radius: 4px;
 
-    &:before {
+    &::before {
       position: absolute;
       top: 0;
       left: 0;
@@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
     border: 2px solid #e5e7eb;
     border-radius: 4px;
 
-    &:before {
+    &::before {
       position: absolute;
       top: 0;
       left: 0;
@@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
       content: '';
     }
 
-    &:after {
+    &::after {
       position: absolute;
       top: 0;
       left: 0;

+ 107 - 73
src/components/Table/src/Table.vue

@@ -5,7 +5,9 @@ import {
   ElPagination,
   ComponentSize,
   ElTooltipProps,
-  ElImage
+  ElImage,
+  ElEmpty,
+  ElCard
 } from 'element-plus'
 import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
 import { propTypes } from '@/utils/propTypes'
@@ -15,8 +17,10 @@ import { set, get } from 'lodash-es'
 import { CSSProperties } from 'vue'
 import { getSlot } from '@/utils/tsxHelper'
 import TableActions from './components/TableActions.vue'
-// import Sortable from 'sortablejs'
-// import { Icon } from '@/components/Icon'
+import { isImgPath } from '@/utils/is'
+import { createVideoViewer } from '@/components/VideoPlayer'
+import { Icon } from '@/components/Icon'
+import { BaseButton } from '@/components/Button'
 
 export default defineComponent({
   name: 'Table',
@@ -32,8 +36,6 @@ export default defineComponent({
       type: Array as PropType<TableColumn[]>,
       default: () => []
     },
-    // 展开行
-    // expand: propTypes.bool.def(false),
     // 是否展示分页
     pagination: {
       type: Object as PropType<Pagination>,
@@ -62,7 +64,6 @@ export default defineComponent({
       type: Array as PropType<string[]>,
       default: () => []
     },
-    // sortable: propTypes.bool.def(false),
     height: propTypes.oneOfType([Number, String]),
     maxHeight: propTypes.oneOfType([Number, String]),
     stripe: propTypes.bool.def(false),
@@ -186,9 +187,27 @@ export default defineComponent({
       default: 'fixed'
     },
     scrollbarAlwaysOn: propTypes.bool.def(false),
-    flexible: propTypes.bool.def(false)
+    flexible: propTypes.bool.def(false),
+    // 自定义内容
+    customContent: propTypes.bool.def(false),
+    cardBodyStyle: {
+      type: Object as PropType<CSSProperties>,
+      default: () => ({})
+    },
+    cardBodyClass: {
+      type: String as PropType<string>,
+      default: ''
+    },
+    cardWrapStyle: {
+      type: Object as PropType<CSSProperties>,
+      default: () => ({})
+    },
+    cardWrapClass: {
+      type: String as PropType<string>,
+      default: ''
+    }
   },
-  emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
+  emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
   setup(props, { attrs, emit, slots, expose }) {
     const elTableRef = ref<ComponentRef<typeof ElTable>>()
 
@@ -213,33 +232,6 @@ export default defineComponent({
       return propsObj
     })
 
-    // const sortableEl = ref()
-    // 初始化拖拽
-    // const initDropTable = () => {
-    //   const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
-    //   if (!el) return
-    //   if (unref(sortableEl)) unref(sortableEl).destroy()
-
-    //   sortableEl.value = Sortable.create(el, {
-    //     handle: '.table-move',
-    //     animation: 180,
-    //     onEnd(e: any) {
-    //       emit('sortable-change', e)
-    //     }
-    //   })
-    // }
-
-    // watch(
-    //   () => getProps.value.sortable,
-    //   async (v) => {
-    //     await nextTick()
-    //     v && initDropTable()
-    //   },
-    //   {
-    //     immediate: true
-    //   }
-    // )
-
     const setProps = (props: TableProps = {}) => {
       mergeProps.value = Object.assign(unref(mergeProps), props)
       outsideProps.value = { ...props } as any
@@ -260,7 +252,7 @@ export default defineComponent({
 
     const addColumn = (column: TableColumn, index?: number) => {
       const { columns } = unref(getProps)
-      if (index) {
+      if (index !== void 0) {
         columns.splice(index, 0, column)
       } else {
         columns.push(column)
@@ -391,14 +383,28 @@ export default defineComponent({
     const renderPreview = (url: string) => {
       return (
         <div class="flex items-center">
-          <ElImage
-            src={url}
-            fit="cover"
-            class="w-[100%] h-100px"
-            lazy
-            preview-src-list={[url]}
-            preview-teleported
-          />
+          {isImgPath(url) ? (
+            <ElImage
+              src={url}
+              fit="cover"
+              class="w-[100%]"
+              lazy
+              preview-src-list={[url]}
+              preview-teleported
+            />
+          ) : (
+            <BaseButton
+              type="primary"
+              icon={<Icon icon="ep:video-play" />}
+              onClick={() => {
+                createVideoViewer({
+                  url
+                })
+              }}
+            >
+              预览
+            </BaseButton>
+          )}
         </div>
       )
     }
@@ -438,6 +444,7 @@ export default defineComponent({
               reserveSelection={reserveSelection}
               align={align}
               headerAlign={headerAlign}
+              selectable={v.selectable}
               width="50"
             ></ElTableColumn>
           )
@@ -489,41 +496,68 @@ export default defineComponent({
     return () => {
       const tableSlots = {}
       if (getSlot(slots, 'empty')) {
-        tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', ...args)
+        tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', args)
       }
       if (getSlot(slots, 'append')) {
-        tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', ...args)
+        tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
       }
 
-      // const { sortable } = unref(getProps)
-
-      // const sortableEl = sortable ? (
-      //   <ElTableColumn
-      //     className="table-move cursor-move"
-      //     type="sortable"
-      //     prop="sortable"
-      //     width="60px"
-      //     align="center"
-      //   >
-      //     <Icon icon="ant-design:drag-outlined" />
-      //   </ElTableColumn>
-      // ) : null
-
       return (
         <div v-loading={unref(getProps).loading}>
-          {unref(getProps).showAction ? (
-            <TableActions
-              columns={unref(getProps).columns}
-              onChangSize={changSize}
-              onRefresh={refresh}
-            />
-          ) : null}
-          <ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
-            {{
-              default: () => renderTableColumn(),
-              ...tableSlots
-            }}
-          </ElTable>
+          {unref(getProps).customContent ? (
+            <div class="flex flex-wrap">
+              {unref(getProps)?.data?.length ? (
+                unref(getProps)?.data.map((item) => {
+                  const cardSlots = {
+                    default: () => {
+                      return getSlot(slots, 'content', item)
+                    }
+                  }
+                  if (getSlot(slots, 'content-header')) {
+                    cardSlots['header'] = () => {
+                      return getSlot(slots, 'content-header', item)
+                    }
+                  }
+                  if (getSlot(slots, 'content-footer')) {
+                    cardSlots['footer'] = () => {
+                      return getSlot(slots, 'content-footer', item)
+                    }
+                  }
+                  return (
+                    <ElCard
+                      shadow="hover"
+                      class={unref(getProps).cardWrapClass}
+                      style={unref(getProps).cardWrapStyle}
+                      bodyClass={unref(getProps).cardBodyClass}
+                      bodyStyle={unref(getProps).cardBodyStyle}
+                    >
+                      {cardSlots}
+                    </ElCard>
+                  )
+                })
+              ) : (
+                <div class="flex flex-1 justify-center">
+                  <ElEmpty description="暂无数据" />
+                </div>
+              )}
+            </div>
+          ) : (
+            <>
+              {unref(getProps).showAction ? (
+                <TableActions
+                  columns={unref(getProps).columns}
+                  onChangSize={changSize}
+                  onRefresh={refresh}
+                />
+              ) : null}
+              <ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
+                {{
+                  default: () => renderTableColumn(),
+                  ...tableSlots
+                }}
+              </ElTable>
+            </>
+          )}
           {unref(getProps).pagination ? (
             <ElPagination
               v-model:pageSize={pageSizeRef.value}

+ 8 - 28
src/components/UserInfo/src/UserInfo.vue

@@ -1,49 +1,27 @@
 <script setup lang="ts">
-import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
+import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
 import { useI18n } from '@/hooks/web/useI18n'
-import { useStorage } from '@/hooks/web/useStorage'
-import { resetRouter } from '@/router'
-import { useRouter } from 'vue-router'
-import { loginOutApi } from '@/api/login'
 import { useDesign } from '@/hooks/web/useDesign'
-import { useTagsViewStore } from '@/store/modules/tagsView'
 import LockDialog from './components/LockDialog.vue'
 import { ref, computed } from 'vue'
 import LockPage from './components/LockPage.vue'
 import { useLockStore } from '@/store/modules/lock'
+import { useUserStore } from '@/store/modules/user'
+
+const userStore = useUserStore()
 
 const lockStore = useLockStore()
 
 const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
 
-const tagsViewStore = useTagsViewStore()
-
 const { getPrefixCls } = useDesign()
 
 const prefixCls = getPrefixCls('user-info')
 
 const { t } = useI18n()
 
-const { clear } = useStorage()
-
-const { replace } = useRouter()
-
 const loginOut = () => {
-  ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
-    confirmButtonText: t('common.ok'),
-    cancelButtonText: t('common.cancel'),
-    type: 'warning'
-  })
-    .then(async () => {
-      const res = await loginOutApi().catch(() => {})
-      if (res) {
-        clear()
-        tagsViewStore.delAllViews()
-        resetRouter() // 重置静态路由表
-        replace('/login')
-      }
-    })
-    .catch(() => {})
+  userStore.logoutConfirm()
 }
 
 const dialogVisible = ref<boolean>(false)
@@ -66,7 +44,9 @@ const toDocument = () => {
         alt=""
         class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
       />
-      <span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">Archer</span>
+      <span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">{{
+        userStore.getUserInfo?.username
+      }}</span>
     </div>
     <template #dropdown>
       <ElDropdownMenu>

+ 27 - 0
src/components/VideoPlayer/index.ts

@@ -0,0 +1,27 @@
+import { VNode, createVNode, render } from 'vue'
+import VideoPlayer from './src/VideoPlayer.vue'
+import { isClient } from '@/utils/is'
+import { VideoPlayerViewer } from '@/components/VideoPlayerViewer'
+import { toAnyString } from '@/utils'
+
+export { VideoPlayer }
+
+let instance: Nullable<VNode> = null
+
+export function createVideoViewer(options: { url: string; poster?: string; show?: boolean }) {
+  if (!isClient) return
+  const { url, poster } = options
+
+  const propsData: Partial<{ url: string; poster?: string; show?: boolean; id?: string }> = {}
+  const container = document.createElement('div')
+  const id = toAnyString()
+  container.id = id
+  propsData.url = url
+  propsData.poster = poster
+  propsData.show = true
+  propsData.id = id
+
+  document.body.appendChild(container)
+  instance = createVNode(VideoPlayerViewer, propsData)
+  render(instance, container)
+}

+ 59 - 0
src/components/VideoPlayer/src/VideoPlayer.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import Player from 'xgplayer'
+import { ref, unref, onMounted, watch, onBeforeUnmount, nextTick } from 'vue'
+import 'xgplayer/dist/index.min.css'
+
+const props = defineProps({
+  url: {
+    type: String,
+    default: '',
+    required: true
+  },
+  poster: {
+    type: String,
+    default: ''
+  }
+})
+
+const playerRef = ref<Player>()
+
+const videoEl = ref<HTMLDivElement>()
+
+const intiPlayer = () => {
+  if (!unref(videoEl)) return
+  new Player({
+    autoplay: false,
+    ...props,
+    el: unref(videoEl)
+  })
+}
+
+onMounted(() => {
+  intiPlayer()
+})
+
+watch(
+  () => props,
+  async (newProps) => {
+    await nextTick()
+    if (newProps) {
+      unref(playerRef)?.setConfig(newProps)
+    }
+  },
+  {
+    deep: true
+  }
+)
+
+onBeforeUnmount(() => {
+  unref(playerRef)?.destroy()
+})
+
+defineExpose({
+  playerExpose: () => unref(playerRef)
+})
+</script>
+
+<template>
+  <div ref="videoEl"></div>
+</template>

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

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

+ 49 - 0
src/components/VideoPlayerViewer/src/VideoPlayerViewer.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import { VideoPlayer } from '@/components/VideoPlayer'
+import { ElOverlay } from 'element-plus'
+import { ref, nextTick } from 'vue'
+import { Icon } from '@/components/Icon'
+
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false
+  },
+  url: {
+    type: String,
+    default: '',
+    required: true
+  },
+  poster: {
+    type: String,
+    default: ''
+  },
+  id: {
+    type: String,
+    default: ''
+  }
+})
+
+const visible = ref(props.show)
+
+const close = async () => {
+  visible.value = false
+  await nextTick()
+  const wrap = document.getElementById(props.id)
+  if (!wrap) return
+  document.body.removeChild(wrap)
+}
+</script>
+<template>
+  <ElOverlay v-show="visible" @click="close">
+    <div class="w-full h-full flex justify-center items-center relative" @click="close">
+      <div
+        class="w-44px h-44px color-[#fff] bg-[var(--el-text-color-regular)] rounded-full border-[#fff] flex justify-center items-center cursor-pointer absolute top-40px right-40px"
+        @click="close"
+      >
+        <Icon icon="ep:close" :size="24" />
+      </div>
+      <VideoPlayer :url="url" :poster="poster" />
+    </div>
+  </ElOverlay>
+</template>

+ 2 - 0
src/components/index.ts

@@ -1,6 +1,8 @@
 import type { App } from 'vue'
 import { Icon } from './Icon'
+import { BaseButton } from './Button'
 
 export const setupGlobCom = (app: App<Element>): void => {
   app.component('Icon', Icon)
+  app.component('BaseButton', BaseButton)
 }

+ 0 - 99
src/config/axios/config.ts

@@ -1,99 +0,0 @@
-import {
-  AxiosConfig,
-  AxiosResponse,
-  AxiosRequestHeaders,
-  InternalAxiosRequestConfig
-} from './types'
-import { ElMessage } from 'element-plus'
-import qs from 'qs'
-import { useStorage } from '@/hooks/web/useStorage'
-
-const { clear } = useStorage()
-
-const config: AxiosConfig = {
-  /**
-   * api请求基础路径
-   */
-  baseUrl: {
-    // 开发环境接口前缀
-    base: '',
-
-    // 打包开发环境接口前缀
-    dev: '',
-
-    // 打包生产环境接口前缀
-    pro: '',
-
-    // 打包测试环境接口前缀
-    test: ''
-  },
-
-  /**
-   * 接口成功返回状态码
-   */
-  code: 0,
-
-  /**
-   * 接口请求超时时间
-   */
-  timeout: 60000,
-
-  /**
-   * 默认接口请求类型
-   * 可选值:application/x-www-form-urlencoded multipart/form-data
-   */
-  defaultHeaders: 'application/json',
-
-  interceptors: {
-    //请求拦截
-    // requestInterceptors: (config) => {
-    //   return config
-    // },
-    // 响应拦截器
-    // responseInterceptors: (result: AxiosResponse) => {
-    //   return result
-    // }
-  }
-}
-
-const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
-  if (
-    config.method === 'post' &&
-    (config.headers as AxiosRequestHeaders)['Content-Type'] === 'application/x-www-form-urlencoded'
-  ) {
-    config.data = qs.stringify(config.data)
-  }
-  if (config.method === 'get' && config.params) {
-    let url = config.url as string
-    url += '?'
-    const keys = Object.keys(config.params)
-    for (const key of keys) {
-      if (config.params[key] !== void 0 && config.params[key] !== null) {
-        url += `${key}=${encodeURIComponent(config.params[key])}&`
-      }
-    }
-    url = url.substring(0, url.length - 1)
-    config.params = {}
-    config.url = url
-  }
-  return config
-}
-
-const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
-  if (response?.config?.responseType === 'blob') {
-    // 如果是文件流,直接过
-    return response
-  } else if (response.data.code === config.code) {
-    return response.data
-  } else {
-    ElMessage.error(response?.data?.message)
-    if (response?.data?.code === 401) {
-      // token过期
-      clear()
-      window.location.reload()
-    }
-  }
-}
-
-export { defaultResponseInterceptors, defaultRequestInterceptors }
-export default config

+ 24 - 0
src/constants/index.ts

@@ -0,0 +1,24 @@
+/**
+ * 请求成功状态码
+ */
+export const SUCCESS_CODE = 0
+
+/**
+ * 请求contentType
+ */
+export const CONTENT_TYPE = 'application/json'
+
+/**
+ * 请求超时时间
+ */
+export const REQUEST_TIMEOUT = 60000
+
+/**
+ * 不重定向白名单
+ */
+export const NO_REDIRECT_WHITE_LIST = ['/login']
+
+/**
+ * 不重置路由白名单
+ */
+export const NO_RESET_WHITE_LIST = ['Redirect', 'Login', 'NoFind', 'Root']

+ 2 - 1
src/locales/en.ts

@@ -91,7 +91,8 @@ export default {
     tagsViewIcon: 'Tags view icon',
     dynamicRouter: 'Dynamic router',
     reExperienced: 'Please exit the login experience again',
-    fixedMenu: 'Fixed menu'
+    fixedMenu: 'Fixed menu',
+    serverDynamicRouter: 'Server dynamic router'
   },
   size: {
     default: 'Default',

+ 2 - 1
src/locales/zh-CN.ts

@@ -91,7 +91,8 @@ export default {
     tagsViewIcon: '标签页图标',
     dynamicRouter: '动态路由',
     reExperienced: '请重新退出登录体验',
-    fixedMenu: '固定菜单'
+    fixedMenu: '固定菜单',
+    serverDynamicRouter: '服务端动态路由'
   },
   size: {
     default: '默认',

+ 12 - 17
src/permission.ts

@@ -1,28 +1,24 @@
 import router from './router'
 import { useAppStoreWithOut } from '@/store/modules/app'
-import { useStorage } from '@/hooks/web/useStorage'
 import type { RouteRecordRaw } from 'vue-router'
 import { useTitle } from '@/hooks/web/useTitle'
 import { useNProgress } from '@/hooks/web/useNProgress'
 import { usePermissionStoreWithOut } from '@/store/modules/permission'
 import { usePageLoading } from '@/hooks/web/usePageLoading'
-
-const permissionStore = usePermissionStoreWithOut()
-
-const appStore = useAppStoreWithOut()
-
-const { getStorage } = useStorage()
+import { NO_REDIRECT_WHITE_LIST } from '@/constants'
+import { useUserStoreWithOut } from '@/store/modules/user'
 
 const { start, done } = useNProgress()
 
 const { loadStart, loadDone } = usePageLoading()
 
-const whiteList = ['/login'] // 不重定向白名单
-
 router.beforeEach(async (to, from, next) => {
   start()
   loadStart()
-  if (getStorage(appStore.getUserInfo)) {
+  const permissionStore = usePermissionStoreWithOut()
+  const appStore = useAppStoreWithOut()
+  const userStore = useUserStoreWithOut()
+  if (userStore.getUserInfo) {
     if (to.path === '/login') {
       next({ path: '/' })
     } else {
@@ -32,16 +28,15 @@ router.beforeEach(async (to, from, next) => {
       }
 
       // 开发者可根据实际情况进行修改
-      const roleRouters = getStorage('roleRouters') || []
-      const userInfo = getStorage(appStore.getUserInfo)
+      const roleRouters = userStore.getRoleRouters || []
 
       // 是否使用动态路由
       if (appStore.getDynamicRouter) {
-        userInfo.role === 'admin'
-          ? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
-          : await permissionStore.generateRoutes('test', roleRouters as string[])
+        appStore.serverDynamicRouter
+          ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
+          : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
       } else {
-        await permissionStore.generateRoutes('none')
+        await permissionStore.generateRoutes('static')
       }
 
       permissionStore.getAddRouters.forEach((route) => {
@@ -54,7 +49,7 @@ router.beforeEach(async (to, from, next) => {
       next(nextData)
     }
   } else {
-    if (whiteList.indexOf(to.path) !== -1) {
+    if (NO_REDIRECT_WHITE_LIST.indexOf(to.path) !== -1) {
       next()
     } else {
       next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页

+ 2 - 2
src/store/index.ts

@@ -1,10 +1,10 @@
 import type { App } from 'vue'
 import { createPinia } from 'pinia'
-import piniaPersist from 'pinia-plugin-persist'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 
 const store = createPinia()
 
-store.use(piniaPersist)
+store.use(piniaPluginPersistedstate)
 
 export const setupStore = (app: App<Element>) => {
   app.use(store)

+ 16 - 22
src/store/modules/app.ts

@@ -2,9 +2,6 @@ import { defineStore } from 'pinia'
 import { store } from '../index'
 import { setCssVar, humpToUnderline } from '@/utils'
 import { ElMessage, ComponentSize } from 'element-plus'
-import { useStorage } from '@/hooks/web/useStorage'
-
-const { getStorage, setStorage } = useStorage()
 
 interface AppState {
   breadcrumb: boolean
@@ -21,10 +18,10 @@ interface AppState {
   fixedHeader: boolean
   greyMode: boolean
   dynamicRouter: boolean
+  serverDynamicRouter: boolean
   pageLoading: boolean
   layout: LayoutType
   title: string
-  userInfo: string
   isDark: boolean
   currentSize: ComponentSize
   sizeMap: ComponentSize[]
@@ -37,12 +34,10 @@ interface AppState {
 export const useAppStore = defineStore('app', {
   state: (): AppState => {
     return {
-      userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其它项目冲突
       sizeMap: ['default', 'large', 'small'],
       mobile: false, // 是否是移动端
       title: import.meta.env.VITE_APP_TITLE, // 标题
       pageLoading: false, // 路由跳转loading
-
       breadcrumb: true, // 面包屑
       breadcrumbIcon: true, // 面包屑图标
       collapse: false, // 折叠菜单
@@ -57,13 +52,14 @@ export const useAppStore = defineStore('app', {
       fixedHeader: true, // 固定toolheader
       footer: true, // 显示页脚
       greyMode: false, // 是否开始灰色模式,用于特殊悼念日
-      dynamicRouter: getStorage('dynamicRouter') || false, // 是否动态路由
-      fixedMenu: getStorage('fixedMenu') || false, // 是否固定菜单
+      dynamicRouter: true, // 是否动态路由
+      serverDynamicRouter: true, // 是否服务端渲染动态路由
+      fixedMenu: false, // 是否固定菜单
 
-      layout: getStorage('layout') || 'classic', // layout布局
-      isDark: getStorage('isDark') || false, // 是否是暗黑模式
-      currentSize: getStorage('default') || 'default', // 组件尺寸
-      theme: getStorage('theme') || {
+      layout: 'classic', // layout布局
+      isDark: false, // 是否是暗黑模式
+      currentSize: 'default', // 组件尺寸
+      theme: {
         // 主题色
         elColorPrimary: '#409eff',
         // 左侧菜单边框颜色
@@ -138,6 +134,9 @@ export const useAppStore = defineStore('app', {
     getDynamicRouter(): boolean {
       return this.dynamicRouter
     },
+    getServerDynamicRouter(): boolean {
+      return this.serverDynamicRouter
+    },
     getFixedMenu(): boolean {
       return this.fixedMenu
     },
@@ -150,9 +149,6 @@ export const useAppStore = defineStore('app', {
     getTitle(): string {
       return this.title
     },
-    getUserInfo(): string {
-      return this.userInfo
-    },
     getIsDark(): boolean {
       return this.isDark
     },
@@ -213,11 +209,12 @@ export const useAppStore = defineStore('app', {
       this.greyMode = greyMode
     },
     setDynamicRouter(dynamicRouter: boolean) {
-      setStorage('dynamicRouter', dynamicRouter)
       this.dynamicRouter = dynamicRouter
     },
+    setServerDynamicRouter(serverDynamicRouter: boolean) {
+      this.serverDynamicRouter = serverDynamicRouter
+    },
     setFixedMenu(fixedMenu: boolean) {
-      setStorage('fixedMenu', fixedMenu)
       this.fixedMenu = fixedMenu
     },
     setPageLoading(pageLoading: boolean) {
@@ -229,7 +226,6 @@ export const useAppStore = defineStore('app', {
         return
       }
       this.layout = layout
-      setStorage('layout', this.layout)
     },
     setTitle(title: string) {
       this.title = title
@@ -243,18 +239,15 @@ export const useAppStore = defineStore('app', {
         document.documentElement.classList.add('light')
         document.documentElement.classList.remove('dark')
       }
-      setStorage('isDark', this.isDark)
     },
     setCurrentSize(currentSize: ComponentSize) {
       this.currentSize = currentSize
-      setStorage('currentSize', this.currentSize)
     },
     setMobile(mobile: boolean) {
       this.mobile = mobile
     },
     setTheme(theme: ThemeTypes) {
       this.theme = Object.assign(this.theme, theme)
-      setStorage('theme', this.theme)
     },
     setCssVarTheme() {
       for (const key in this.theme) {
@@ -264,7 +257,8 @@ export const useAppStore = defineStore('app', {
     setFooter(footer: boolean) {
       this.footer = footer
     }
-  }
+  },
+  persist: true
 })
 
 export const useAppStoreWithOut = () => {

+ 0 - 34
src/store/modules/dict.ts

@@ -1,34 +0,0 @@
-import { defineStore } from 'pinia'
-import { store } from '../index'
-
-export interface DictState {
-  isSetDict: boolean
-  dictObj: Recordable
-}
-
-export const useDictStore = defineStore('dict', {
-  state: (): DictState => ({
-    isSetDict: false,
-    dictObj: {}
-  }),
-  getters: {
-    getDictObj(): Recordable {
-      return this.dictObj
-    },
-    getIsSetDict(): boolean {
-      return this.isSetDict
-    }
-  },
-  actions: {
-    setDictObj(dictObj: Recordable) {
-      this.dictObj = dictObj
-    },
-    setIsSetDict(isSetDict: boolean) {
-      this.isSetDict = isSetDict
-    }
-  }
-})
-
-export const useDictStoreWithOut = () => {
-  return useDictStore(store)
-}

+ 1 - 4
src/store/modules/lock.ts

@@ -40,10 +40,7 @@ export const useLockStore = defineStore('lock', {
       }
     }
   },
-  persist: {
-    enabled: true,
-    strategies: [{ key: 'lock', storage: localStorage }]
-  }
+  persist: true
 })
 
 export const useLockStoreWithOut = () => {

+ 13 - 6
src/store/modules/permission.ts

@@ -1,6 +1,10 @@
 import { defineStore } from 'pinia'
 import { asyncRouterMap, constantRouterMap } from '@/router'
-import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
+import {
+  generateRoutesByFrontEnd,
+  generateRoutesByServer,
+  flatMultiLevelRoutes
+} from '@/utils/routerHelper'
 import { store } from '../index'
 import { cloneDeep } from 'lodash-es'
 
@@ -34,17 +38,17 @@ export const usePermissionStore = defineStore('permission', {
   },
   actions: {
     generateRoutes(
-      type: 'admin' | 'test' | 'none',
+      type: 'server' | 'frontEnd' | 'static',
       routers?: AppCustomRouteRecordRaw[] | string[]
     ): Promise<unknown> {
       return new Promise<void>((resolve) => {
         let routerMap: AppRouteRecordRaw[] = []
-        if (type === 'admin') {
+        if (type === 'server') {
           // 模拟后端过滤菜单
-          routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[])
-        } else if (type === 'test') {
+          routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
+        } else if (type === 'frontEnd') {
           // 模拟前端过滤菜单
-          routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])
+          routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
         } else {
           // 直接读取静态路由表
           routerMap = cloneDeep(asyncRouterMap)
@@ -72,6 +76,9 @@ export const usePermissionStore = defineStore('permission', {
     setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
       this.menuTabRouters = routers
     }
+  },
+  persist: {
+    paths: ['routers', 'addRouters', 'menuTabRouters']
   }
 })
 

+ 27 - 4
src/store/modules/tagsView.ts

@@ -4,16 +4,19 @@ import { getRawRoute } from '@/utils/routerHelper'
 import { defineStore } from 'pinia'
 import { store } from '../index'
 import { findIndex } from '@/utils'
+import { useUserStoreWithOut } from './user'
 
 export interface TagsViewState {
   visitedViews: RouteLocationNormalizedLoaded[]
   cachedViews: Set<string>
+  selectedTag?: RouteLocationNormalizedLoaded
 }
 
 export const useTagsViewStore = defineStore('tagsView', {
   state: (): TagsViewState => ({
     visitedViews: [],
-    cachedViews: new Set()
+    cachedViews: new Set(),
+    selectedTag: undefined
   }),
   getters: {
     getVisitedViews(): RouteLocationNormalizedLoaded[] {
@@ -21,6 +24,9 @@ export const useTagsViewStore = defineStore('tagsView', {
     },
     getCachedViews(): string[] {
       return Array.from(this.cachedViews)
+    },
+    getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
+      return this.selectedTag
     }
   },
   actions: {
@@ -44,7 +50,7 @@ export const useTagsViewStore = defineStore('tagsView', {
       const cacheMap: Set<string> = new Set()
       for (const v of this.visitedViews) {
         const item = getRawRoute(v)
-        const needCache = !item.meta?.noCache
+        const needCache = !item?.meta?.noCache
         if (!needCache) {
           continue
         }
@@ -84,8 +90,12 @@ export const useTagsViewStore = defineStore('tagsView', {
     },
     // 删除所有tag
     delAllVisitedViews() {
+      const userStore = useUserStoreWithOut()
+
       // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
-      this.visitedViews = []
+      this.visitedViews = userStore.getUserInfo
+        ? this.visitedViews.filter((tag) => tag?.meta?.affix)
+        : []
     },
     // 删除其它
     delOthersViews(view: RouteLocationNormalizedLoaded) {
@@ -131,8 +141,21 @@ export const useTagsViewStore = defineStore('tagsView', {
           break
         }
       }
+    },
+    // 设置当前选中的tag
+    setSelectedTag(tag: RouteLocationNormalizedLoaded) {
+      this.selectedTag = tag
+    },
+    setTitle(title: string, path?: string) {
+      for (const v of this.visitedViews) {
+        if (v.path === (path ?? this.selectedTag?.path)) {
+          v.meta.title = title
+          break
+        }
+      }
     }
-  }
+  },
+  persist: false
 })
 
 export const useTagsViewStoreWithOut = () => {

+ 102 - 0
src/store/modules/user.ts

@@ -0,0 +1,102 @@
+import { defineStore } from 'pinia'
+import { store } from '../index'
+import { UserLoginType, UserType } from '@/api/login/types'
+import { ElMessageBox } from 'element-plus'
+import { useI18n } from '@/hooks/web/useI18n'
+import { loginOutApi } from '@/api/login'
+import { useTagsViewStore } from './tagsView'
+import router from '@/router'
+
+interface UserState {
+  userInfo?: UserType
+  tokenKey: string
+  token: string
+  roleRouters?: string[] | AppCustomRouteRecordRaw[]
+  rememberMe: boolean
+  loginInfo?: UserLoginType
+}
+
+export const useUserStore = defineStore('user', {
+  state: (): UserState => {
+    return {
+      userInfo: undefined,
+      tokenKey: 'Authorization',
+      token: '',
+      roleRouters: undefined,
+      // 记住我
+      rememberMe: true,
+      loginInfo: undefined
+    }
+  },
+  getters: {
+    getTokenKey(): string {
+      return this.tokenKey
+    },
+    getToken(): string {
+      return this.token
+    },
+    getUserInfo(): UserType | undefined {
+      return this.userInfo
+    },
+    getRoleRouters(): string[] | AppCustomRouteRecordRaw[] | undefined {
+      return this.roleRouters
+    },
+    getRememberMe(): boolean {
+      return this.rememberMe
+    },
+    getLoginInfo(): UserLoginType | undefined {
+      return this.loginInfo
+    }
+  },
+  actions: {
+    setTokenKey(tokenKey: string) {
+      this.tokenKey = tokenKey
+    },
+    setToken(token: string) {
+      this.token = token
+    },
+    setUserInfo(userInfo?: UserType) {
+      this.userInfo = userInfo
+    },
+    setRoleRouters(roleRouters: string[] | AppCustomRouteRecordRaw[]) {
+      this.roleRouters = roleRouters
+    },
+    logoutConfirm() {
+      const { t } = useI18n()
+      ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
+        confirmButtonText: t('common.ok'),
+        cancelButtonText: t('common.cancel'),
+        type: 'warning'
+      })
+        .then(async () => {
+          const res = await loginOutApi().catch(() => {})
+          if (res) {
+            this.reset()
+          }
+        })
+        .catch(() => {})
+    },
+    reset() {
+      const tagsViewStore = useTagsViewStore()
+      tagsViewStore.delAllViews()
+      this.setToken('')
+      this.setUserInfo(undefined)
+      this.setRoleRouters([])
+      router.replace('/login')
+    },
+    logout() {
+      this.reset()
+    },
+    setRememberMe(rememberMe: boolean) {
+      this.rememberMe = rememberMe
+    },
+    setLoginInfo(loginInfo: UserLoginType | undefined) {
+      this.loginInfo = loginInfo
+    }
+  },
+  persist: true
+})
+
+export const useUserStoreWithOut = () => {
+  return useUserStore(store)
+}

+ 11 - 8
src/utils/routerHelper.ts

@@ -3,7 +3,6 @@ import type {
   Router,
   RouteLocationNormalized,
   RouteRecordNormalized,
-  RouteMeta,
   RouteRecordRaw
 } from 'vue-router'
 import { isUrl } from '@/utils/is'
@@ -39,7 +38,7 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
 }
 
 // 前端控制路由生成
-export const generateRoutesFn1 = (
+export const generateRoutesByFrontEnd = (
   routes: AppRouteRecordRaw[],
   keys: string[],
   basePath = '/'
@@ -47,7 +46,7 @@ export const generateRoutesFn1 = (
   const res: AppRouteRecordRaw[] = []
 
   for (const route of routes) {
-    const meta = route.meta as RouteMeta
+    const meta = route.meta ?? {}
     // skip some route
     if (meta.hidden && !meta.canTo) {
       continue
@@ -70,7 +69,7 @@ export const generateRoutesFn1 = (
       if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
         data = Object.assign({}, route)
       } else {
-        const routePath = onlyOneChild ?? pathResolve(basePath, route.path)
+        const routePath = (onlyOneChild ?? pathResolve(basePath, route.path)).trim()
         if (routePath === item || meta.followRoute === item) {
           data = Object.assign({}, route)
         }
@@ -79,7 +78,11 @@ export const generateRoutesFn1 = (
 
     // recursive child routes
     if (route.children && data) {
-      data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path))
+      data.children = generateRoutesByFrontEnd(
+        route.children,
+        keys,
+        pathResolve(basePath, data.path)
+      )
     }
     if (data) {
       res.push(data as AppRouteRecordRaw)
@@ -89,7 +92,7 @@ export const generateRoutesFn1 = (
 }
 
 // 后端控制路由生成
-export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
+export const generateRoutesByServer = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
   const res: AppRouteRecordRaw[] = []
 
   for (const route of routes) {
@@ -112,7 +115,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
     }
     // recursive child routes
     if (route.children) {
-      data.children = generateRoutesFn2(route.children)
+      data.children = generateRoutesByServer(route.children)
     }
     res.push(data as AppRouteRecordRaw)
   }
@@ -122,7 +125,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
 export const pathResolve = (parentPath: string, path: string) => {
   if (isUrl(path)) return path
   const childPath = path.startsWith('/') || !path ? path : `/${path}`
-  return `${parentPath}${childPath}`.replace(/\/\//g, '/')
+  return `${parentPath}${childPath}`.replace(/\/\//g, '/').trim()
 }
 
 // 路由降级

+ 51 - 32
src/views/Login/components/LoginForm.vue

@@ -1,11 +1,10 @@
 <script setup lang="tsx">
-import { reactive, ref, watch } from 'vue'
+import { reactive, ref, watch, onMounted, unref } from 'vue'
 import { Form, FormSchema } from '@/components/Form'
 import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElCheckbox, ElLink } from 'element-plus'
+import { ElCheckbox, ElLink } from 'element-plus'
 import { useForm } from '@/hooks/web/useForm'
 import { loginApi, getTestRoleApi, getAdminRoleApi } from '@/api/login'
-import { useStorage } from '@/hooks/web/useStorage'
 import { useAppStore } from '@/store/modules/app'
 import { usePermissionStore } from '@/store/modules/permission'
 import { useRouter } from 'vue-router'
@@ -13,6 +12,8 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
 import { UserType } from '@/api/login/types'
 import { useValidator } from '@/hooks/web/useValidator'
 import { Icon } from '@/components/Icon'
+import { useUserStore } from '@/store/modules/user'
+import { BaseButton } from '@/components/Button'
 
 const { required } = useValidator()
 
@@ -20,12 +21,12 @@ const emit = defineEmits(['to-register'])
 
 const appStore = useAppStore()
 
+const userStore = useUserStore()
+
 const permissionStore = usePermissionStore()
 
 const { currentRoute, addRoute, push } = useRouter()
 
-const { setStorage } = useStorage()
-
 const { t } = useI18n()
 
 const rules = {
@@ -50,19 +51,19 @@ const schema = reactive<FormSchema[]>([
   {
     field: 'username',
     label: t('login.username'),
-    value: 'admin',
+    // value: 'admin',
     component: 'Input',
     colProps: {
       span: 24
     },
     componentProps: {
-      placeholder: t('login.usernamePlaceholder')
+      placeholder: 'admin or test'
     }
   },
   {
     field: 'password',
     label: t('login.password'),
-    value: 'admin',
+    // value: 'admin',
     component: 'InputPassword',
     colProps: {
       span: 24
@@ -71,7 +72,7 @@ const schema = reactive<FormSchema[]>([
       style: {
         width: '100%'
       },
-      placeholder: t('login.passwordPlaceholder')
+      placeholder: 'admin or test'
     }
   },
   {
@@ -107,14 +108,19 @@ const schema = reactive<FormSchema[]>([
           return (
             <>
               <div class="w-[100%]">
-                <ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={signIn}>
+                <BaseButton
+                  loading={loading.value}
+                  type="primary"
+                  class="w-[100%]"
+                  onClick={signIn}
+                >
                   {t('login.login')}
-                </ElButton>
+                </BaseButton>
               </div>
               <div class="w-[100%] mt-15px">
-                <ElButton class="w-[100%]" onClick={toRegister}>
+                <BaseButton class="w-[100%]" onClick={toRegister}>
                   {t('login.register')}
-                </ElButton>
+                </BaseButton>
               </div>
             </>
           )
@@ -180,10 +186,21 @@ const schema = reactive<FormSchema[]>([
 
 const iconSize = 30
 
-const remember = ref(false)
+const remember = ref(userStore.getRememberMe)
+
+const initLoginInfo = () => {
+  const loginInfo = userStore.getLoginInfo
+  if (loginInfo) {
+    const { username, password } = loginInfo
+    setValues({ username, password })
+  }
+}
+onMounted(() => {
+  initLoginInfo()
+})
 
 const { formRegister, formMethods } = useForm()
-const { getFormData, getElFormExpose } = formMethods
+const { getFormData, getElFormExpose, setValues } = formMethods
 
 const loading = ref(false)
 
@@ -205,13 +222,6 @@ watch(
 
 // 登录
 const signIn = async () => {
-  await permissionStore.generateRoutes('none').catch(() => {})
-  permissionStore.getAddRouters.forEach((route) => {
-    addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
-  })
-  permissionStore.setIsAddRouters(true)
-  push({ path: redirect.value || permissionStore.addRouters[0].path })
-
   const formRef = await getElFormExpose()
   await formRef?.validate(async (isValid) => {
     if (isValid) {
@@ -222,12 +232,22 @@ const signIn = async () => {
         const res = await loginApi(formData)
 
         if (res) {
-          setStorage(appStore.getUserInfo, res.data)
+          // 是否记住我
+          if (unref(remember)) {
+            userStore.setLoginInfo({
+              username: formData.username,
+              password: formData.password
+            })
+          } else {
+            userStore.setLoginInfo(undefined)
+          }
+          userStore.setRememberMe(unref(remember))
+          userStore.setUserInfo(res.data)
           // 是否使用动态路由
           if (appStore.getDynamicRouter) {
             getRole()
           } else {
-            await permissionStore.generateRoutes('none').catch(() => {})
+            await permissionStore.generateRoutes('static').catch(() => {})
             permissionStore.getAddRouters.forEach((route) => {
               addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
             })
@@ -248,17 +268,16 @@ const getRole = async () => {
   const params = {
     roleName: formData.username
   }
-  // admin - 模拟后端过滤菜单
-  // test - 模拟前端过滤菜单
   const res =
-    formData.username === 'admin' ? await getAdminRoleApi(params) : await getTestRoleApi(params)
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await getAdminRoleApi(params)
+      : await getTestRoleApi(params)
   if (res) {
     const routers = res.data || []
-    setStorage('roleRouters', routers)
-
-    formData.username === 'admin'
-      ? await permissionStore.generateRoutes('admin', routers).catch(() => {})
-      : await permissionStore.generateRoutes('test', routers).catch(() => {})
+    userStore.setRoleRouters(routers)
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
+      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
 
     permissionStore.getAddRouters.forEach((route) => {
       addRoute(route as RouteRecordRaw) // 动态添加可访问路由表

+ 2 - 0
stylelint.config.js

@@ -215,6 +215,8 @@ module.exports = {
       extends: ['stylelint-config-recommended', 'stylelint-config-html'],
       rules: {
         'keyframes-name-pattern': null,
+        'selector-class-pattern': null,
+        'no-duplicate-selectors': null,
         'selector-pseudo-class-no-unknown': [
           true,
           {

+ 2 - 2
tsconfig.json

@@ -27,8 +27,8 @@
       "@intlify/unplugin-vue-i18n/types",
       "vite/client",
       "element-plus/global",
-      "vite-plugin-svg-icons/client",
-      "unplugin-vue-define-options/macros-global"
+      "@types/qrcode",
+      "vite-plugin-svg-icons/client"
     ]
   },
   "include": ["src", "types/**/*.d.ts", "mock/**/*.ts"]

+ 2 - 1
types/components.d.ts

@@ -1,6 +1,7 @@
 declare module 'vue' {
   export interface GlobalComponents {
-    Icon: typeof import('../components/Icon/src/Icon.vue')['default']
+    Icon: (typeof import('../components/Icon/src/Icon.vue'))['default']
+    BaseButton: (typeof import('../components/Button/src/Button.vue'))['default']
   }
 }
 

+ 4 - 3
types/global.d.ts

@@ -1,4 +1,5 @@
 import type { CSSProperties } from 'vue'
+import { RawAxiosRequestHeaders } from 'axios'
 declare global {
   declare interface Fn<T = any> {
     (...arg: T[]): T
@@ -25,7 +26,7 @@ declare global {
 
   declare type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'
 
-  declare type AxiosHeaders =
+  declare type AxiosContentType =
     | 'application/json'
     | 'application/x-www-form-urlencoded'
     | 'multipart/form-data'
@@ -39,12 +40,12 @@ declare global {
     data?: any
     url?: string
     method?: AxiosMethod
-    headersType?: string
+    headers?: RawAxiosRequestHeaders
     responseType?: AxiosResponseType
   }
 
   declare interface IResponse<T = any> {
-    code: string
+    code: number
     data: T extends any ? T : T & any
   }
 

+ 10 - 9
vite.config.ts

@@ -10,7 +10,6 @@ import { viteMockServe } from 'vite-plugin-mock'
 import PurgeIcons from 'vite-plugin-purge-icons'
 import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
-import DefineOptions from "unplugin-vue-define-options/vite"
 import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
 import UnoCSS from 'unocss/vite'
 
@@ -32,9 +31,13 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
   return {
     base: env.VITE_BASE_PATH,
     plugins: [
-      Vue(),
+      Vue({
+        script: {
+          // 开启defineModel
+          defineModel: true
+        }
+      }),
       VueJsx(),
-      // WindiCSS(),
       progress(),
       createStyleImportPlugin({
         resolves: [ElementPlusResolve()],
@@ -75,7 +78,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
           setupProdMockServer()
           `
       }),
-      DefineOptions(),
       ViteEjsPlugin({
         title: env.VITE_APP_TITLE
       }),
@@ -142,11 +144,10 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         '@vueuse/core',
         'axios',
         'qs',
-        'echarts',
-        'echarts-wordcloud',
-        '@wangeditor/editor',
-        '@wangeditor/editor-for-vue'
+        '@zxcvbn-ts/core',
+        'dayjs',
+        'xgplayer'
       ]
     }
   }
-}
+}