Jelajahi Sumber

项目初始化

王飞 1 tahun lalu
induk
melakukan
0430530bdc
89 mengubah file dengan 1205 tambahan dan 4095 penghapusan
  1. 1 1
      .env.base
  2. 1 1
      .env.dev
  3. 1 1
      .env.gitee
  4. 1 1
      .env.pro
  5. 1 1
      .env.test
  6. 5 0
      .vscode/settings.json
  7. 29 0
      CHANGELOG.md
  8. 1 1
      index.html
  9. 1 1
      mock/analysis/index.ts
  10. 1 15
      mock/department/index.ts
  11. 1 1
      mock/dict/index.ts
  12. 64 0
      mock/request/index.ts
  13. 27 0
      mock/role/index.ts
  14. 1 1
      mock/table/index.ts
  15. 1 1
      mock/user/index.ts
  16. 1 1
      mock/workplace/index.ts
  17. 36 36
      package.json
  18. TEMPAT SAMPAH
      public/favicon.ico
  19. TEMPAT SAMPAH
      public/logo.png
  20. 4 14
      src/App.vue
  21. 0 4
      src/api/common/index.ts
  22. 1 28
      src/api/department/index.ts
  23. 2 32
      src/api/department/types.ts
  24. 9 5
      src/api/login/index.ts
  25. 6 8
      src/api/login/types.ts
  26. 9 1
      src/api/menu/index.ts
  27. 32 0
      src/api/request/index.ts
  28. 3 0
      src/api/request/types.ts
  29. 0 13
      src/api/table/types.ts
  30. TEMPAT SAMPAH
      src/assets/imgs/logo.png
  31. 0 1
      src/components/Backtop/src/Backtop.vue
  32. 3 1
      src/components/Descriptions/src/Descriptions.vue
  33. 3 1
      src/components/Form/src/helper/componentMap.ts
  34. 2 1
      src/components/Form/src/types/index.ts
  35. 1 7
      src/components/Logo/src/Logo.vue
  36. 1 1
      src/components/Search/src/components/ActionButton.vue
  37. 1 0
      src/components/Table/src/Table.vue
  38. 13 16
      src/components/UserInfo/src/UserInfo.vue
  39. 13 16
      src/config/axios/config.ts
  40. 26 2
      src/config/axios/service.ts
  41. 1 1
      src/config/axios/types/index.ts
  42. 47 0
      src/hooks/web/useClipboard.ts
  43. 21 0
      src/hooks/web/useNetwork.ts
  44. 1 0
      src/hooks/web/useTable.ts
  45. 1 31
      src/hooks/web/useValidator.ts
  46. 2 2
      src/layout/Layout.vue
  47. 4 3
      src/locales/en.ts
  48. 4 3
      src/locales/zh-CN.ts
  49. 147 123
      src/router/index.ts
  50. 3 3
      src/store/modules/app.ts
  51. 10 26
      src/utils/propTypes.ts
  52. 7 14
      src/views/Authorization/Department/Department.vue
  53. 71 62
      src/views/Authorization/Menu/Menu.vue
  54. 112 102
      src/views/Authorization/Menu/components/Write.vue
  55. 1 1
      src/views/Authorization/Test/Test.vue
  56. 137 120
      src/views/Authorization/User/User.vue
  57. 8 5
      src/views/Authorization/User/components/Write.vue
  58. 11 0
      src/views/Components/Form/DefaultForm.vue
  59. 1 1
      src/views/Components/IconPicker.vue
  60. 1 1
      src/views/Components/Table/UseTableDemo.vue
  61. 2 45
      src/views/Dashboard/Workplace.vue
  62. 8 59
      src/views/Example/Page/ExamplePage.vue
  63. 0 3
      src/views/Example/Page/components/Write.vue
  64. 167 0
      src/views/Function/Request.vue
  65. 10 8
      src/views/Login/Login.vue
  66. 70 62
      src/views/Login/components/LoginForm.vue
  67. 0 469
      src/views/Manage/Company/CompanyPage.vue
  68. 0 76
      src/views/Manage/Company/components/Write.vue
  69. 0 453
      src/views/Manage/File/FilePage.vue
  70. 0 69
      src/views/Manage/File/components/Write.vue
  71. 0 416
      src/views/Manage/Img/ImgPage.vue
  72. 0 80
      src/views/Manage/Img/components/Write.vue
  73. 0 53
      src/views/Manage/News/NewsAdd.vue
  74. 0 38
      src/views/Manage/News/NewsDetail.vue
  75. 0 67
      src/views/Manage/News/NewsEdit.vue
  76. 0 346
      src/views/Manage/News/NewsPage.vue
  77. 0 65
      src/views/Manage/News/components/Detail.vue
  78. 0 296
      src/views/Manage/News/components/Write.vue
  79. 0 53
      src/views/Manage/Product/ProductAdd.vue
  80. 0 38
      src/views/Manage/Product/ProductDetail.vue
  81. 0 67
      src/views/Manage/Product/ProductEdit.vue
  82. 0 271
      src/views/Manage/Product/ProductPage.vue
  83. 0 71
      src/views/Manage/Product/components/Detail.vue
  84. 0 261
      src/views/Manage/Product/components/Write.vue
  85. 26 0
      src/views/hooks/useClipboard.vue
  86. 12 0
      src/views/hooks/useNetwork.vue
  87. 1 2
      types/global.d.ts
  88. 3 2
      types/router.d.ts
  89. 14 14
      vite.config.ts

+ 1 - 1
.env.base

@@ -8,4 +8,4 @@ VITE_API_BASE_PATH=base
 VITE_BASE_PATH=/
 
 # 标题
-VITE_APP_TITLE=网站后台管理
+VITE_APP_TITLE=ElementAdmin

+ 1 - 1
.env.dev

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=true
 VITE_OUT_DIR=dist-dev
 
 # 标题
-VITE_APP_TITLE=网站后台管理
+VITE_APP_TITLE=ElementAdmin

+ 1 - 1
.env.gitee

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=false
 VITE_OUT_DIR=dist-pro
 
 # 标题
-VITE_APP_TITLE=网站后台管理
+VITE_APP_TITLE=ElementAdmin

+ 1 - 1
.env.pro

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=false
 VITE_OUT_DIR=dist-pro
 
 # 标题
-VITE_APP_TITLE=网站后台管理
+VITE_APP_TITLE=ElementAdmin

+ 1 - 1
.env.test

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=true
 VITE_OUT_DIR=dist-test
 
 # 标题
-VITE_APP_TITLE=网站后台管理
+VITE_APP_TITLE=ElementAdmin

+ 5 - 0
.vscode/settings.json

@@ -0,0 +1,5 @@
+{
+  "i18n-ally.localesPaths": [
+    "src/locales"
+  ]
+}

+ 29 - 0
CHANGELOG.md

@@ -2,6 +2,35 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+## [2.3.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v2.2.0...v2.3.0) (2023-09-24)
+
+
+### Features
+
+* IconPicker ([4490d5e](https://github.com/kailong321200875/vue-element-plus-admin/commit/4490d5eeeb4389f94f90c9c45a30343324db2250))
+* 表格工具栏新增列设置功能 ([9d10ba8](https://github.com/kailong321200875/vue-element-plus-admin/commit/9d10ba821feca414b9b020322859ca4a47291005))
+
+
+### Bug Fixes
+
+* [#326](https://github.com/kailong321200875/vue-element-plus-admin/issues/326) ([c95a4e0](https://github.com/kailong321200875/vue-element-plus-admin/commit/c95a4e0763838e843cf5ce174110a01f2baa8000))
+* default interceptor response return ([c3d8540](https://github.com/kailong321200875/vue-element-plus-admin/commit/c3d8540ab284312f24d9355072f6fb4506ed6d1d))
+* 修复IconPicker BUG ([1e3aa78](https://github.com/kailong321200875/vue-element-plus-admin/commit/1e3aa789260773b1caecdaa32e1cafede22733e3))
+* 修复useCrudSchemas无法自定义label ([aa5deb1](https://github.com/kailong321200875/vue-element-plus-admin/commit/aa5deb13904e45e7cb6ec7285e936b9ebae57273))
+
+
+### Docs
+
+* 更新README ([4947c82](https://github.com/kailong321200875/vue-element-plus-admin/commit/4947c82d6770f0dce2845682f0c41d853268cf82))
+* 更新README ([c3624ce](https://github.com/kailong321200875/vue-element-plus-admin/commit/c3624cee588457e7fedaab360746500337c1b2a7))
+* 更新群二维码 ([ead1ab8](https://github.com/kailong321200875/vue-element-plus-admin/commit/ead1ab8c88c05593d539b56a811809382675faf5))
+
+
+### Styling
+
+* 修复样式层级问题 ([f92d2b6](https://github.com/kailong321200875/vue-element-plus-admin/commit/f92d2b60a956e1963b63e23b446a9d42096704e0))
+* 修改登录样式 ([bdd31f0](https://github.com/kailong321200875/vue-element-plus-admin/commit/bdd31f0621712af89d89b87ac439c3e0b398605a))
+
 ## [2.2.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v2.1.0...v2.2.0) (2023-08-27)
 
 

+ 1 - 1
index.html

@@ -60,7 +60,7 @@
           border-bottom: 0;
           border-left-color: transparent;
           border-radius: 50%;
-          animation: loader-outter 1.5s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
+          animation: loader-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
         }
 
         .app-loading .app-loading-inner {

+ 1 - 1
mock/analysis/index.ts

@@ -3,7 +3,7 @@ import { MockMethod } from 'vite-plugin-mock'
 
 const { code } = config
 
-const timeout = 1
+const timeout = 1000
 
 export default [
   // 分析页统计接口

+ 1 - 15
mock/department/index.ts

@@ -10,13 +10,11 @@ const departmentList: any = []
 const citys = ['厦门总公司', '北京分公司', '上海分公司', '福州分公司', '深圳分公司', '杭州分公司']
 
 for (let i = 0; i < 5; i++) {
-  let departmentId = toAnyString()
   departmentList.push({
     // 部门名称
     departmentName: citys[i],
-    id: departmentId,
+    id: toAnyString(),
     createTime: '@datetime',
-    superiorDepartment: '',
     // 状态
     status: Mock.Random.integer(0, 1),
     // 备注
@@ -26,8 +24,6 @@ for (let i = 0; i < 5; i++) {
         // 部门名称
         departmentName: '研发部',
         createTime: '@datetime',
-        superiorDepartment: departmentId,
-        superiorDepartmentName: citys[i],
         // 状态
         status: Mock.Random.integer(0, 1),
         id: toAnyString(),
@@ -37,8 +33,6 @@ for (let i = 0; i < 5; i++) {
         // 部门名称
         departmentName: '产品部',
         createTime: '@datetime',
-        superiorDepartment: departmentId,
-        superiorDepartmentName: citys[i],
         // 状态
         status: Mock.Random.integer(0, 1),
         id: toAnyString(),
@@ -48,8 +42,6 @@ for (let i = 0; i < 5; i++) {
         // 部门名称
         departmentName: '运营部',
         createTime: '@datetime',
-        superiorDepartment: departmentId,
-        superiorDepartmentName: citys[i],
         // 状态
         status: Mock.Random.integer(0, 1),
         id: toAnyString(),
@@ -59,8 +51,6 @@ for (let i = 0; i < 5; i++) {
         // 部门名称
         departmentName: '市场部',
         createTime: '@datetime',
-        superiorDepartment: departmentId,
-        superiorDepartmentName: citys[i],
         // 状态
         status: Mock.Random.integer(0, 1),
         id: toAnyString(),
@@ -70,8 +60,6 @@ for (let i = 0; i < 5; i++) {
         // 部门名称
         departmentName: '销售部',
         createTime: '@datetime',
-        superiorDepartment: departmentId,
-        superiorDepartmentName: citys[i],
         // 状态
         status: Mock.Random.integer(0, 1),
         id: toAnyString(),
@@ -81,8 +69,6 @@ for (let i = 0; i < 5; i++) {
         // 部门名称
         departmentName: '客服部',
         createTime: '@datetime',
-        superiorDepartment: departmentId,
-        superiorDepartmentName: citys[i],
         // 状态
         status: Mock.Random.integer(0, 1),
         id: toAnyString(),

+ 1 - 1
mock/dict/index.ts

@@ -3,7 +3,7 @@ import { MockMethod } from 'vite-plugin-mock'
 
 const { code } = config
 
-const timeout = 1
+const timeout = 1000
 
 const dictObj: Recordable = {
   importance: [

+ 64 - 0
mock/request/index.ts

@@ -0,0 +1,64 @@
+import config from '@/config/axios/config'
+import { MockMethod } from 'vite-plugin-mock'
+
+const timeout = 600000
+
+const { code } = config
+
+export default [
+  {
+    url: '/request/1',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        code: code,
+        data: 'request-1'
+      }
+    }
+  },
+  {
+    url: '/request/2',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        code: code,
+        data: 'request-2'
+      }
+    }
+  },
+  {
+    url: '/request/3',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        code: code,
+        data: 'request-3'
+      }
+    }
+  },
+  {
+    url: '/request/4',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        code: code,
+        data: 'request-4'
+      }
+    }
+  },
+  {
+    url: '/request/5',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        code: code,
+        data: 'request-5'
+      }
+    }
+  }
+] as MockMethod[]

+ 27 - 0
mock/role/index.ts

@@ -316,6 +316,14 @@ const adminList = [
           title: 'router.details',
           canTo: true
         }
+      },
+      {
+        path: 'request',
+        component: 'views/Function/Request',
+        name: 'Request',
+        meta: {
+          title: 'router.request'
+        }
       }
     ]
   },
@@ -361,6 +369,22 @@ const adminList = [
         meta: {
           title: 'useCrudSchemas'
         }
+      },
+      {
+        path: 'useClipboard',
+        component: 'views/hooks/useClipboard',
+        name: 'UseClipboard',
+        meta: {
+          title: 'useClipboard'
+        }
+      },
+      {
+        path: 'useNetwork',
+        component: 'views/hooks/useNetwork',
+        name: 'UseNetwork',
+        meta: {
+          title: 'useNetwork'
+        }
       }
     ]
   },
@@ -621,11 +645,14 @@ const testList: string[] = [
   'function',
   '/function/multiple-tabs',
   '/function/multiple-tabs-demo/:id',
+  '/function/request',
   '/hooks',
   '/hooks/useWatermark',
   '/hooks/useTagsView',
   '/hooks/useValidator',
   '/hooks/useCrudSchemas',
+  '/hooks/useClipboard',
+  '/hooks/useNetwork',
   '/level',
   '/level/menu1',
   '/level/menu1/menu1-1',

+ 1 - 1
mock/table/index.ts

@@ -5,7 +5,7 @@ import Mock from 'mockjs'
 
 const { code } = config
 
-const timeout = 1
+const timeout = 1000
 
 const count = 100
 

+ 1 - 1
mock/user/index.ts

@@ -3,7 +3,7 @@ import { MockMethod } from 'vite-plugin-mock'
 
 const { code } = config
 
-const timeout = 1
+const timeout = 1000
 
 const List: {
   username: string

+ 1 - 1
mock/workplace/index.ts

@@ -3,7 +3,7 @@ import { MockMethod } from 'vite-plugin-mock'
 
 const { code } = config
 
-const timeout = 1
+const timeout = 1000
 
 export default [
   // 获取统计

+ 36 - 36
package.json

@@ -1,6 +1,6 @@
 {
   "name": "vue-element-plus-admin",
-  "version": "2.2.0",
+  "version": "2.3.0",
   "description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
   "author": "Archer <502431556@qq.com>",
   "private": false,
@@ -29,17 +29,17 @@
   "dependencies": {
     "@iconify/iconify": "^3.1.1",
     "@iconify/vue": "^4.1.1",
-    "@vueuse/core": "^10.3.0",
+    "@vueuse/core": "^10.4.1",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.10",
-    "@zxcvbn-ts/core": "^3.0.3",
+    "@zxcvbn-ts/core": "^3.0.4",
     "animate.css": "^4.1.1",
-    "axios": "^1.4.0",
-    "dayjs": "^1.11.9",
-    "driver.js": "^1.2.1",
+    "axios": "^1.5.0",
+    "dayjs": "^1.11.10",
+    "driver.js": "^1.3.0",
     "echarts": "^5.4.3",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "^2.3.9",
+    "element-plus": "^2.3.14",
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.1",
     "mockjs": "^1.1.0",
@@ -48,37 +48,37 @@
     "pinia-plugin-persist": "^1.0.0",
     "qrcode": "^1.5.3",
     "qs": "^6.11.2",
-    "url": "^0.11.1",
+    "url": "^0.11.3",
     "vue": "3.3.4",
-    "vue-i18n": "9.2.2",
+    "vue-i18n": "9.4.1",
     "vue-json-pretty": "^2.2.4",
-    "vue-router": "^4.2.4",
+    "vue-router": "^4.2.5",
     "vue-types": "^5.1.1"
   },
   "devDependencies": {
     "@commitlint/cli": "^17.7.1",
     "@commitlint/config-conventional": "^17.7.0",
-    "@iconify/json": "^2.2.101",
-    "@intlify/unplugin-vue-i18n": "^0.12.2",
+    "@iconify/json": "^2.2.119",
+    "@intlify/unplugin-vue-i18n": "^1.2.0",
     "@purge-icons/generated": "^0.9.0",
     "@types/fs-extra": "^11.0.2",
     "@types/inquirer": "^9.0.3",
-    "@types/lodash-es": "^4.17.8",
-    "@types/node": "^20.4.10",
-    "@types/nprogress": "^0.2.0",
-    "@types/qrcode": "^1.5.1",
-    "@types/qs": "^6.9.7",
-    "@types/sortablejs": "^1.15.1",
-    "@typescript-eslint/eslint-plugin": "^6.3.0",
-    "@typescript-eslint/parser": "^6.3.0",
-    "@unocss/transformer-variant-group": "^0.55.0",
+    "@types/lodash-es": "^4.17.9",
+    "@types/node": "^20.6.5",
+    "@types/nprogress": "^0.2.1",
+    "@types/qrcode": "^1.5.2",
+    "@types/qs": "^6.9.8",
+    "@types/sortablejs": "^1.15.2",
+    "@typescript-eslint/eslint-plugin": "^6.7.2",
+    "@typescript-eslint/parser": "^6.7.2",
+    "@unocss/transformer-variant-group": "^0.56.1",
     "@vitejs/plugin-legacy": "^4.1.1",
-    "@vitejs/plugin-vue": "^4.2.3",
-    "@vitejs/plugin-vue-jsx": "^3.0.1",
-    "autoprefixer": "^10.4.14",
+    "@vitejs/plugin-vue": "^4.3.4",
+    "@vitejs/plugin-vue-jsx": "^3.0.2",
+    "autoprefixer": "^10.4.16",
     "chalk": "^5.3.0",
     "consola": "^3.2.3",
-    "eslint": "^8.47.0",
+    "eslint": "^8.50.0",
     "eslint-config-prettier": "^9.0.0",
     "eslint-define-config": "^1.23.0",
     "eslint-plugin-prettier": "^5.0.0",
@@ -88,31 +88,31 @@
     "husky": "^8.0.3",
     "inquirer": "^9.2.11",
     "less": "^4.2.0",
-    "lint-staged": "^13.2.3",
-    "plop": "^3.1.2",
-    "postcss": "^8.4.27",
+    "lint-staged": "^14.0.1",
+    "plop": "^4.0.0",
+    "postcss": "^8.4.30",
     "postcss-html": "^1.5.0",
     "postcss-less": "^6.0.0",
-    "prettier": "^3.0.1",
+    "prettier": "^3.0.3",
     "rimraf": "^5.0.1",
-    "rollup": "^3.28.0",
-    "stylelint": "^15.10.2",
+    "rollup": "^3.29.3",
+    "stylelint": "^15.10.3",
     "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.19.2",
-    "typescript": "5.1.6",
-    "unocss": "^0.55.0",
+    "terser": "^5.20.0",
+    "typescript": "5.2.2",
+    "unocss": "^0.56.1",
     "vite": "4.4.9",
     "vite-plugin-ejs": "^1.6.4",
     "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.9.2",
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "^2.0.1",
-    "vue-tsc": "^1.8.8"
+    "vue-tsc": "^1.8.13"
   },
   "engines": {
     "node": ">= 14.18.0"

TEMPAT SAMPAH
public/favicon.ico


TEMPAT SAMPAH
public/logo.png


+ 4 - 14
src/App.vue

@@ -1,26 +1,25 @@
 <script setup lang="ts">
 import { computed } from 'vue'
 import { useAppStore } from '@/store/modules/app'
-import { usePageStore } from '@/store/modules/page'
 import { ConfigGlobal } from '@/components/ConfigGlobal'
-import { isDark } from '@/utils/is'
+// import { isDark } from '@/utils/is'
 import { useDesign } from '@/hooks/web/useDesign'
-import { useStorage } from '@/hooks/web/useStorage'
+// import { useStorage } from '@/hooks/web/useStorage'
 
 const { getPrefixCls } = useDesign()
 
 const prefixCls = getPrefixCls('app')
 
 const appStore = useAppStore()
-const pageStore = usePageStore()
 
 const currentSize = computed(() => appStore.getCurrentSize)
 
 const greyMode = computed(() => appStore.getGreyMode)
 
+// const { getStorage } = useStorage()
+
 // 根据浏览器当前主题设置系统主题色
 // const setDefaultTheme = () => {
-//   const { getStorage } = useStorage()
 //   if (getStorage('isDark') !== null) {
 //     appStore.setIsDark(getStorage('isDark'))
 //     return
@@ -29,15 +28,6 @@ const greyMode = computed(() => appStore.getGreyMode)
 //   appStore.setIsDark(isDarkTheme)
 // }
 
-const setPageData = () => {
-  const { getStorage } = useStorage('localStorage')
-  if (getStorage('pageData') !== null) {
-    pageStore.setAllPageData(getStorage('pageData'))
-    return
-  }
-}
-
-setPageData()
 // setDefaultTheme()
 </script>
 

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

@@ -9,7 +9,3 @@ export const getDictApi = () => {
 export const getDictOneApi = async () => {
   return request.get({ url: '/dict/one' })
 }
-
-export const uploadFile = async (data: any) => {
-  return request.post({ url: '/api/sysApk/uploadFile', headersType: 'multipart/form-data', data })
-}

+ 1 - 28
src/api/department/index.ts

@@ -1,12 +1,5 @@
 import request from '@/config/axios'
-import {
-  DepartmentListResponse,
-  DepartmentUserParams,
-  DepartmentUserResponse,
-  UserParams,
-  SysRole,
-  SysUser
-} from './types'
+import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
 
 export const getDepartmentApi = () => {
   return request.get<DepartmentListResponse>({ url: '/department/list' })
@@ -35,23 +28,3 @@ export const deleteDepartmentApi = (ids: string[] | number[]) => {
 export const getDepartmentTableApi = (params: any) => {
   return request.get({ url: '/department/table/list', params })
 }
-
-export const getUserList = (data: UserParams) => {
-  return request.post<DepartmentUserResponse>({ url: '/api/sysUser/page', data })
-}
-
-export const deleteUserById = (id: number) => {
-  return request.delete({ url: `/api/sysUser/${id}` })
-}
-
-export const getRoleApi = () => {
-  return request.post<SysRole[]>({ url: '/api/sysRole/list' })
-}
-
-export const addUser = (data: any) => {
-  return request.post({ url: '/api/sysUser/add', data })
-}
-
-export const updataUser = (data: any) => {
-  return request.put({ url: '/api/sysUser', data })
-}

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

@@ -17,7 +17,7 @@ export interface DepartmentUserParams {
 }
 
 export interface DepartmentUserItem {
-  id: number
+  id: string
   username: string
   account: string
   email: string
@@ -28,35 +28,5 @@ export interface DepartmentUserItem {
 
 export interface DepartmentUserResponse {
   list: DepartmentUserItem[]
-  totalCount: number
-}
-
-export interface UserParams {
-  pageSize: number
-  pageNum: number
-  name?: string
-  account?: string
-}
-
-
-export interface UserResponse {
-  list: DepartmentUserItem[]
-  totalCount: number
-  totalPage: number
-  pageSize: number
-  currPage: number
-}
-
-export interface SysRole {
-  id: number
-  roleName: string
-  createTime: string
-  roleDesc: string
-}
-
-export interface SysUser {
-  id?: number
-  account: string
-  name: string
-  password: string
+  total: number
 }

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

@@ -1,16 +1,20 @@
 import request from '@/config/axios'
-import type { UserType } from './types'
+import type { UserType, UserLoginType } from './types'
 
 interface RoleParams {
   roleName: string
 }
 
-export const loginApi = (data: UserType): Promise<IResponse<UserType>> => {
-  return request.post({ url: '/api/login/login', data, headersType: 'application/json' })
+export const loginApi = (data: UserLoginType): Promise<IResponse<UserType>> => {
+  return request.post({ url: '/sys/login', data })
 }
 
-export const loginOutApi = (userId: string): Promise<IResponse> => {
-  return request.get({ url: `/api/login/logout/${userId}` })
+export const loginOutApi = (): Promise<IResponse> => {
+  return request.post({ url: '/sys/logout' })
+}
+
+export const getUserInfo = (): Promise<IResponse> => {
+  return request.get({ url: '/sys/info' })
 }
 
 export const getUserListApi = ({ params }: AxiosConfig) => {

+ 6 - 8
src/api/login/types.ts

@@ -1,12 +1,10 @@
-export type UserLoginType = {
-  account: string
+export interface UserLoginType {
+  username: string
   password: string
 }
 
-export type UserType = {
-  account: string
-  password: string
-  role: string
-  roleId: string
-  permissions: string | string[]
+export interface UserType {
+  token: string
+  userId: string | number
+  tokenHead:string
 }

+ 9 - 1
src/api/menu/index.ts

@@ -1,5 +1,13 @@
 import request from '@/config/axios'
 
 export const getMenuListApi = () => {
-  return request.get({ url: '/menu/list' })
+  return request.get({ url: '/sys/menu/tree' })
+}
+
+export const saveMenu = (data: any) => {
+  return request.post({ url: '/sys/menu/save', data })
+}
+
+export const deleteMenu = (data: any) => {
+  return request.post({ url: '/sys/menu/delete', data })
 }

+ 32 - 0
src/api/request/index.ts

@@ -0,0 +1,32 @@
+import request from '@/config/axios'
+import { RequestResponse } from './types'
+
+export const request1 = () => {
+  return request.get<IResponse<RequestResponse>>({
+    url: '/request/1'
+  })
+}
+
+export const request2 = () => {
+  return request.get<IResponse<RequestResponse>>({
+    url: '/request/2'
+  })
+}
+
+export const request3 = () => {
+  return request.get<IResponse<RequestResponse>>({
+    url: '/request/3'
+  })
+}
+
+export const request4 = () => {
+  return request.get<IResponse<RequestResponse>>({
+    url: '/request/4'
+  })
+}
+
+export const request5 = () => {
+  return request.get<IResponse<RequestResponse>>({
+    url: '/request/5'
+  })
+}

+ 3 - 0
src/api/request/types.ts

@@ -0,0 +1,3 @@
+export interface RequestResponse {
+  data: string
+}

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

@@ -7,16 +7,3 @@ export type TableData = {
   display_time: string
   pageviews: number
 }
-
-export type ProductTableData = {
-  id?: string
-  productName: string
-  productPic: string
-  productSpecifications: string
-  productIntroduction: string
-  productDetailed: string
-  productCharacteristic: string
-  productPrice: number
-  productStock: number
-  remark: string
-}

TEMPAT SAMPAH
src/assets/imgs/logo.png


+ 0 - 1
src/components/Backtop/src/Backtop.vue

@@ -10,7 +10,6 @@ const prefixCls = getPrefixCls('backtop')
 <template>
   <ElBacktop
     :class="`${prefixCls}-backtop`"
-    style="z-index: 100"
     :target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
   />
 </template>

+ 3 - 1
src/components/Descriptions/src/Descriptions.vue

@@ -16,6 +16,8 @@ const { getPrefixCls } = useDesign()
 
 const prefixCls = getPrefixCls('descriptions')
 
+const defaultData = '-'
+
 export default defineComponent({
   name: 'Descriptions',
   props: {
@@ -115,7 +117,7 @@ export default defineComponent({
                             default: () =>
                               item.slots?.default
                                 ? item.slots?.default(props.data)
-                                : get(props.data, item.field)
+                                : get(props.data, item.field) ?? defaultData
                           }}
                         </ElDescriptionsItem>
                       )

+ 3 - 1
src/components/Form/src/helper/componentMap.ts

@@ -23,6 +23,7 @@ import {
 import { InputPassword } from '@/components/InputPassword'
 import { Editor } from '@/components/Editor'
 import { JsonEditor } from '@/components/JsonEditor'
+import { IconPicker } from '@/components/IconPicker'
 import { ComponentName } from '../types'
 
 const componentMap: Recordable<Component, ComponentName> = {
@@ -49,7 +50,8 @@ const componentMap: Recordable<Component, ComponentName> = {
   Editor: Editor,
   TreeSelect: ElTreeSelect,
   Upload: ElUpload,
-  JsonEditor: JsonEditor
+  JsonEditor: JsonEditor,
+  IconPicker: IconPicker
 }
 
 export { componentMap }

+ 2 - 1
src/components/Form/src/types/index.ts

@@ -55,7 +55,8 @@ export enum ComponentNameEnum {
   EDITOR = 'Editor',
   TREE_SELECT = 'TreeSelect',
   UPLOAD = 'Upload',
-  JSON_EDITOR = 'JsonEditor'
+  JSON_EDITOR = 'JsonEditor',
+  ICON_PICKER = 'IconPicker'
 }
 
 type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K

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

@@ -28,13 +28,7 @@ watch(
       show.value = true
       return
     }
-    if (!collapse) {
-      setTimeout(() => {
-        show.value = !collapse
-      }, 400)
-    } else {
-      show.value = !collapse
-    }
+    show.value = !collapse
   }
 )
 

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

@@ -50,7 +50,7 @@ const onExpand = () => {
   </ElButton>
   <ElButton
     v-if="showExpand"
-    :icon="useIcon({ icon: visible ? 'ep:arrow-down' : 'ep:arrow-up' })"
+    :icon="useIcon({ icon: visible ? 'ep:arrow-up' : 'ep:arrow-down' })"
     text
     @click="onExpand"
   >

+ 1 - 0
src/components/Table/src/Table.vue

@@ -406,6 +406,7 @@ export default defineComponent({
               reserveSelection={reserveSelection}
               align={align}
               headerAlign={headerAlign}
+              selectable={v.selectable}
               width="50"
             ></ElTableColumn>
           )

+ 13 - 16
src/components/UserInfo/src/UserInfo.vue

@@ -11,7 +11,6 @@ import LockDialog from './components/LockDialog.vue'
 import { ref, computed } from 'vue'
 import LockPage from './components/LockPage.vue'
 import { useLockStore } from '@/store/modules/lock'
-import { useAppStore } from '@/store/modules/app'
 
 const lockStore = useLockStore()
 
@@ -22,13 +21,13 @@ const tagsViewStore = useTagsViewStore()
 const { getPrefixCls } = useDesign()
 
 const prefixCls = getPrefixCls('user-info')
-const appStore = useAppStore()
+
 const { t } = useI18n()
 
-const { clear, getStorage } = useStorage()
+const { clear } = useStorage()
 
 const { replace } = useRouter()
-const userInfo = ref(getStorage(appStore.userInfo))
+
 const loginOut = () => {
   ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
     confirmButtonText: t('common.ok'),
@@ -36,7 +35,7 @@ const loginOut = () => {
     type: 'warning'
   })
     .then(async () => {
-      const res = await loginOutApi(userInfo.value.id).catch(() => {})
+      const res = await loginOutApi().catch(() => {})
       if (res) {
         clear()
         tagsViewStore.delAllViews()
@@ -54,29 +53,27 @@ const lockScreen = () => {
   dialogVisible.value = true
 }
 
-// const toDocument = () => {
-//   window.open('https://element-plus-admin-doc.cn/')
-// }
+const toDocument = () => {
+  window.open('https://element-plus-admin-doc.cn/')
+}
 </script>
 
 <template>
   <ElDropdown class="custom-hover" :class="prefixCls" trigger="click">
     <div class="flex items-center">
-      <!-- <img
+      <img
         src="@/assets/imgs/avatar.jpg"
         alt=""
         class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
-      /> -->
-      <span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">{{
-        userInfo.name
-      }}</span>
+      />
+      <span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">Archer</span>
     </div>
     <template #dropdown>
       <ElDropdownMenu>
-        <!-- <ElDropdownItem divided>
-          <div @click="toDocument">{{ t('common.document') }}</div>
-        </ElDropdownItem> -->
         <ElDropdownItem>
+          <div @click="toDocument">{{ t('common.document') }}</div>
+        </ElDropdownItem>
+        <ElDropdownItem divided>
           <div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
         </ElDropdownItem>
         <ElDropdownItem>

+ 13 - 16
src/config/axios/config.ts

@@ -2,7 +2,6 @@ import {
   AxiosConfig,
   AxiosResponse,
   AxiosRequestHeaders,
-  AxiosError,
   InternalAxiosRequestConfig
 } from './types'
 import { ElMessage, ElMessageBox } from 'element-plus'
@@ -27,7 +26,7 @@ const config: AxiosConfig = {
     dev: '',
 
     // 打包生产环境接口前缀
-    pro: 'http://api.dacundianzi.com',
+    pro: '',
 
     // 打包测试环境接口前缀
     test: ''
@@ -36,7 +35,7 @@ const config: AxiosConfig = {
   /**
    * 接口成功返回状态码
    */
-  code: '200',
+  code: 200,
 
   /**
    * 接口请求超时时间
@@ -47,7 +46,7 @@ const config: AxiosConfig = {
    * 默认接口请求类型
    * 可选值:application/x-www-form-urlencoded multipart/form-data
    */
-  defaultHeaders: 'application/x-www-form-urlencoded',
+  defaultHeaders: 'application/json',
 
   interceptors: {
     //请求拦截
@@ -82,22 +81,19 @@ const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
     config.url = url
   }
   if (getStorage('token')) {
-    config.headers.token = getStorage('token')
+    const tokenHead = getStorage('tokenHead')
+    config.headers[tokenHead] = getStorage('token')
   }
   return config
 }
-;(error: AxiosError) => {
-  Promise.reject(error)
-}
 let isReLogin = false
-
 const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
   if (response?.config?.responseType === 'blob') {
     // 如果是文件流,直接过
     return response
   } else if (response.data.code === config.code) {
     return response.data
-  } else if (response.data.code === 'A0000A') {
+  } else if (response.data.code === 401) {
     if (!isReLogin) {
       isReLogin = true
       ElMessageBox.alert(response.data.message, '提示', {
@@ -113,14 +109,15 @@ const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
       })
     }
   } else {
-    ElMessage.error(response.data.message)
+    ElMessage.error((response as any).message)
+    clear()
+    tagsViewStore.delAllViews()
+    resetRouter() // 重置静态路由表
+    router.replace('/login')
+    isReLogin = false
+    location.reload()
   }
 }
-;(error: AxiosError) => {
-  console.log('err' + error) // for debug
-  ElMessage.error(error.message)
-  return Promise.reject(error)
-}
 
 export { defaultResponseInterceptors, defaultRequestInterceptors }
 export default config

+ 26 - 2
src/config/axios/service.ts

@@ -1,7 +1,15 @@
-import axios from 'axios'
+import axios, { AxiosError } from 'axios'
 import config, { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
 
 import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
+import { ElMessage } from 'element-plus'
+import { useStorage } from '@/hooks/web/useStorage'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+import { resetRouter } from '@/router'
+import router from '@/router'
+
+const { clear } = useStorage()
+const tagsViewStore = useTagsViewStore()
 
 const { interceptors, baseUrl } = config
 export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH]
@@ -23,6 +31,7 @@ axiosInstance.interceptors.request.use((res: InternalAxiosRequestConfig) => {
   return res
 })
 
+let isReLogin = false
 axiosInstance.interceptors.response.use(
   (res: AxiosResponse) => {
     const url = res.config.url || ''
@@ -30,7 +39,22 @@ axiosInstance.interceptors.response.use(
     // 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
     return res
   },
-  (err: any) => err
+  (error: AxiosError) => {
+    console.log('err' + error) // for debug
+    ElMessage.error(error.message)
+    if (!isReLogin) {
+      isReLogin = true
+      setTimeout(() => {
+        tagsViewStore.delAllViews()
+        resetRouter() // 重置静态路由表
+        router.replace('/login')
+        isReLogin = false
+        location.reload()
+        clear()
+      }, 1000)
+    }
+    return Promise.reject(error)
+  }
 )
 
 axiosInstance.interceptors.request.use(requestInterceptors || defaultRequestInterceptors)

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

@@ -22,7 +22,7 @@ interface AxiosConfig<T = AxiosResponse> {
     pro: string
     test: string
   }
-  code: number|string
+  code: number
   defaultHeaders: AxiosHeaders
   timeout: number
   interceptors: RequestInterceptors<T>

+ 47 - 0
src/hooks/web/useClipboard.ts

@@ -0,0 +1,47 @@
+import { ref } from 'vue'
+
+const useClipboard = () => {
+  const copied = ref(false)
+  const text = ref('')
+  const isSupported = ref(false)
+
+  if (!navigator.clipboard && !document.execCommand) {
+    isSupported.value = false
+  } else {
+    isSupported.value = true
+  }
+
+  const copy = (str: string) => {
+    if (navigator.clipboard) {
+      navigator.clipboard.writeText(str).then(() => {
+        text.value = str
+        copied.value = true
+        resetCopied()
+      })
+      return
+    }
+    const input = document.createElement('input')
+    input.setAttribute('readonly', 'readonly')
+    input.setAttribute('value', str)
+    document.body.appendChild(input)
+    input.select()
+    input.setSelectionRange(0, 9999)
+    if (document.execCommand('copy')) {
+      text.value = str
+      document.execCommand('copy')
+      copied.value = true
+      resetCopied()
+    }
+    document.body.removeChild(input)
+  }
+
+  const resetCopied = () => {
+    setTimeout(() => {
+      copied.value = false
+    }, 1500)
+  }
+
+  return { copy, text, copied, isSupported }
+}
+
+export { useClipboard }

+ 21 - 0
src/hooks/web/useNetwork.ts

@@ -0,0 +1,21 @@
+import { ref, onBeforeUnmount } from 'vue'
+
+const useNetwork = () => {
+  const online = ref(true)
+
+  const updateNetwork = () => {
+    online.value = navigator.onLine
+  }
+
+  window.addEventListener('online', updateNetwork)
+  window.addEventListener('offline', updateNetwork)
+
+  onBeforeUnmount(() => {
+    window.removeEventListener('online', updateNetwork)
+    window.removeEventListener('offline', updateNetwork)
+  })
+
+  return { online }
+}
+
+export { useNetwork }

+ 1 - 0
src/hooks/web/useTable.ts

@@ -80,6 +80,7 @@ export const useTable = (config: UseTableConfig) => {
       loading.value = true
       try {
         const res = await config?.fetchDataApi()
+        console.log('fetchDataApi res', res)
         if (res) {
           dataList.value = res.list
           total.value = res.total || 0

+ 1 - 31
src/hooks/web/useValidator.ts

@@ -51,40 +51,10 @@ export const useValidator = () => {
     }
   }
 
-  const isEmail = (message: string): FormItemRule => {
-    return {
-      validator: (_, val, callback) => {
-        // 判断是否是邮箱
-        if (!/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/g.test(val)) {
-          callback(new Error(message))
-        } else {
-          callback()
-        }
-      }
-    }
-  }
-
-  const isMobilePhone = (message: string): FormItemRule => {
-    return {
-      validator: (_, val, callback) => {
-        // 判断是否是手机号
-        if (
-          !/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/g.test(val)
-        ) {
-          callback(new Error(message))
-        } else {
-          callback()
-        }
-      }
-    }
-  }
-
   return {
     required,
     lengthRange,
     notSpace,
-    notSpecialCharacters,
-    isEmail,
-    isMobilePhone
+    notSpecialCharacters
   }
 }

+ 2 - 2
src/layout/Layout.vue

@@ -2,7 +2,7 @@
 import { computed, defineComponent, unref } from 'vue'
 import { useAppStore } from '@/store/modules/app'
 import { Backtop } from '@/components/Backtop'
-// import { Setting } from '@/components/Setting'
+import { Setting } from '@/components/Setting'
 import { useRenderLayout } from './components/useRenderLayout'
 import { useDesign } from '@/hooks/web/useDesign'
 
@@ -59,7 +59,7 @@ export default defineComponent({
 
         <Backtop></Backtop>
 
-        {/* <Setting></Setting> */}
+        <Setting></Setting>
       </section>
     )
   }

+ 4 - 3
src/locales/en.ts

@@ -175,7 +175,8 @@ export default {
     function: 'Function',
     multipleTabs: 'Multiple tabs',
     details: 'Details',
-    iconPicker: 'Icon picker'
+    iconPicker: 'Icon picker',
+    request: 'Request'
   },
   permission: {
     hasPermission: 'Please set the operation permission value'
@@ -321,7 +322,8 @@ export default {
     lazyLoad: 'Lazy load',
     upload: 'Upload',
     // 用户头像
-    userAvatar: 'User avatar'
+    userAvatar: 'User avatar',
+    iconPicker: 'Icon picker'
   },
   guideDemo: {
     guide: 'Guide',
@@ -536,7 +538,6 @@ export default {
     affix: 'Affix',
     noTagsView: 'No tags view',
     activeMenu: 'Active menu',
-    preciousMenu: 'Precious menu',
     canTo: 'Can to',
     name: 'Name'
   },

+ 4 - 3
src/locales/zh-CN.ts

@@ -173,7 +173,8 @@ export default {
     function: '功能',
     multipleTabs: '多开标签页',
     details: '详情页',
-    iconPicker: '图标选择器'
+    iconPicker: '图标选择器',
+    request: '请求'
   },
   permission: {
     hasPermission: '请设置操作权限值'
@@ -316,7 +317,8 @@ export default {
     customContent: '自定义内容',
     lazyLoad: '懒加载',
     upload: '上传',
-    userAvatar: '用户头像'
+    userAvatar: '用户头像',
+    iconPicker: '图标选择器'
   },
   guideDemo: {
     guide: '引导页',
@@ -529,7 +531,6 @@ export default {
     affix: '是否固定在标签页',
     noTagsView: '是否隐藏标签页',
     activeMenu: '高亮菜单',
-    preciousMenu: '上级菜单',
     canTo: '是否可跳转',
     name: '组件名称'
   },

+ 147 - 123
src/router/index.ts

@@ -10,7 +10,7 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
   {
     path: '/',
     component: Layout,
-    redirect: '/dashboard/workplace',
+    redirect: '/dashboard/analysis',
     name: 'Root',
     meta: {
       hidden: true
@@ -59,7 +59,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
   {
     path: '/dashboard',
     component: Layout,
-    redirect: '/dashboard/workplace',
+    redirect: '/dashboard/analysis',
     name: 'Dashboard',
     meta: {
       title: t('router.dashboard'),
@@ -67,6 +67,16 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
       alwaysShow: true
     },
     children: [
+      {
+        path: 'analysis',
+        component: () => import('@/views/Dashboard/Analysis.vue'),
+        name: 'Analysis',
+        meta: {
+          title: t('router.analysis'),
+          noCache: true,
+          affix: true
+        }
+      },
       {
         path: 'workplace',
         component: () => import('@/views/Dashboard/Workplace.vue'),
@@ -78,22 +88,54 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
       }
     ]
   },
-
   {
-    path: '/manage',
+    path: '/external-link',
+    component: Layout,
+    meta: {},
+    name: 'ExternalLink',
+    children: [
+      {
+        path: 'https://element-plus-admin-doc.cn/',
+        name: 'DocumentLink',
+        meta: {
+          title: t('router.document'),
+          icon: 'clarity:document-solid'
+        }
+      }
+    ]
+  },
+  {
+    path: '/guide',
+    component: Layout,
+    name: 'Guide',
+    meta: {},
+    children: [
+      {
+        path: 'index',
+        component: () => import('@/views/Guide/Guide.vue'),
+        name: 'GuideDemo',
+        meta: {
+          title: t('router.guide'),
+          icon: 'cib:telegram-plane'
+        }
+      }
+    ]
+  },
+  {
+    path: '/components',
     component: Layout,
-    redirect: '/manage/news-page',
-    name: 'Manage',
+    name: 'ComponentsDemo',
     meta: {
-      title: '模块管理',
-      icon: 'ep:menu',
+      title: t('router.component'),
+      icon: 'bx:bxs-component',
       alwaysShow: true
     },
     children: [
       {
-        path: 'news-page',
-        component: () => import('@/views/Manage/News/NewsPage.vue'),
-        name: 'NewsPage',
+        path: 'form',
+        component: getParentLayout(),
+        redirect: '/components/form/default-form',
+        name: 'Form',
         meta: {
           title: t('router.form'),
           alwaysShow: true
@@ -198,9 +240,9 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         }
       },
       {
-        path: 'news-add',
-        component: () => import('@/views/Manage/News/NewsAdd.vue'),
-        name: 'NewsAdd',
+        path: 'descriptions',
+        component: () => import('@/views/Components/Descriptions.vue'),
+        name: 'Descriptions',
         meta: {
           title: t('router.descriptions')
         }
@@ -316,6 +358,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           canTo: true,
           activeMenu: '/function/multiple-tabs'
         }
+      },
+      {
+        path: 'request',
+        component: () => import('@/views/Function/Request.vue'),
+        name: 'Request',
+        meta: {
+          title: t('router.request')
+        }
       }
     ]
   },
@@ -361,6 +411,22 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
           title: 'useCrudSchemas'
         }
+      },
+      {
+        path: 'useClipboard',
+        component: () => import('@/views/hooks/useClipboard.vue'),
+        name: 'UseClipboard',
+        meta: {
+          title: 'useClipboard'
+        }
+      },
+      {
+        path: 'useNetwork',
+        component: () => import('@/views/hooks/useNetwork.vue'),
+        name: 'UseNetwork',
+        meta: {
+          title: 'useNetwork'
+        }
       }
     ]
   },
@@ -460,104 +526,70 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           noCache: true,
           hidden: true,
           canTo: true,
-          activeMenu: '/manage/news-page'
+          activeMenu: '/example/example-page'
         }
       },
       {
-        path: 'news-edit',
-        component: () => import('@/views/Manage/News/NewsEdit.vue'),
-        name: 'NewsEdit',
+        path: 'example-edit',
+        component: () => import('@/views/Example/Page/ExampleEdit.vue'),
+        name: 'ExampleEdit',
         meta: {
-          title: '编辑文案',
+          title: t('router.exampleEdit'),
           noTagsView: true,
           noCache: true,
           hidden: true,
           canTo: true,
-          activeMenu: '/manage/news-page'
+          activeMenu: '/example/example-page'
         }
       },
       {
-        path: 'news-detail',
-        component: () => import('@/views/Manage/News/NewsDetail.vue'),
-        name: 'NewsDetail',
+        path: 'example-detail',
+        component: () => import('@/views/Example/Page/ExampleDetail.vue'),
+        name: 'ExampleDetail',
         meta: {
-          title: '文案详情',
+          title: t('router.exampleDetail'),
           noTagsView: true,
           noCache: true,
           hidden: true,
           canTo: true,
-          activeMenu: '/manage/news-page'
+          activeMenu: '/example/example-page'
         }
-      },
-      {
-        path: 'product-page',
-        component: () => import('@/views/Manage/Product/ProductPage.vue'),
-        name: 'ProductPage',
-        meta: {
-          title: '产品管理'
-        }
-      },
-      {
-        path: 'product-add',
-        component: () => import('@/views/Manage/Product/ProductAdd.vue'),
-        name: 'ProductAdd',
-        meta: {
-          title: '新增产品',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/product-page'
-        }
-      },
-      {
-        path: 'product-edit',
-        component: () => import('@/views/Manage/Product/ProductEdit.vue'),
-        name: 'ProductEdit',
-        meta: {
-          title: '编辑产品',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/product-page'
-        }
-      },
-      {
-        path: 'product-detail',
-        component: () => import('@/views/Manage/Product/ProductDetail.vue'),
-        name: 'ProductDetail',
-        meta: {
-          title: '产品详情',
-          noTagsView: true,
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          activeMenu: '/manage/product-page'
-        }
-      },
+      }
+    ]
+  },
+  {
+    path: '/error',
+    component: Layout,
+    redirect: '/error/404',
+    name: 'Error',
+    meta: {
+      title: t('router.errorPage'),
+      icon: 'ci:error',
+      alwaysShow: true
+    },
+    children: [
       {
-        path: 'file-page',
-        component: () => import('@/views/Manage/File/FilePage.vue'),
-        name: 'FilePage',
+        path: '404-demo',
+        component: () => import('@/views/Error/404.vue'),
+        name: '404Demo',
         meta: {
-          title: '文件管理'
+          title: '404'
         }
       },
       {
-        path: 'img-page',
-        component: () => import('@/views/Manage/Img/ImgPage.vue'),
-        name: 'ImgPage',
+        path: '403-demo',
+        component: () => import('@/views/Error/403.vue'),
+        name: '403Demo',
         meta: {
-          title: '图片管理'
+          title: '403'
         }
       },
       {
-        path: 'company-page',
-        component: () => import('@/views/Manage/Company/CompanyPage.vue'),
-        name: 'CompanyPage',
+        path: '500-demo',
+        component: () => import('@/views/Error/500.vue'),
+        name: '500Demo',
         meta: {
-          title: '公司信息管理'
+          title: '500'
         }
       }
     ]
@@ -568,19 +600,19 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
     redirect: '/authorization/user',
     name: 'Authorization',
     meta: {
-      title: '系统管理',
+      title: t('router.authorization'),
       icon: 'eos-icons:role-binding',
       alwaysShow: true
     },
     children: [
-      // {
-      //   path: 'department',
-      //   component: () => import('@/views/Authorization/Department/Department.vue'),
-      //   name: 'Department',
-      //   meta: {
-      //     title: t('router.department')
-      //   }
-      // },
+      {
+        path: 'department',
+        component: () => import('@/views/Authorization/Department/Department.vue'),
+        name: 'Department',
+        meta: {
+          title: t('router.department')
+        }
+      },
       {
         path: 'user',
         component: () => import('@/views/Authorization/User/User.vue'),
@@ -590,38 +622,30 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         }
       },
       {
-        path: 'dict',
-        component: () => import('@/views/Authorization/Dict/Dict.vue'),
-        name: 'Dict',
+        path: 'menu',
+        component: () => import('@/views/Authorization/Menu/Menu.vue'),
+        name: 'Menu',
+        meta: {
+          title: t('router.menuManagement')
+        }
+      },
+      {
+        path: 'role',
+        component: () => import('@/views/Authorization/Role/Role.vue'),
+        name: 'Role',
+        meta: {
+          title: t('router.role')
+        }
+      },
+      {
+        path: 'test',
+        component: () => import('@/views/Authorization/Test/Test.vue'),
+        name: 'Test',
         meta: {
-          title: '字典管理'
+          title: t('router.permission'),
+          permission: ['add', 'edit', 'delete']
         }
       }
-      // {
-      //   path: 'menu',
-      //   component: () => import('@/views/Authorization/Menu/Menu.vue'),
-      //   name: 'Menu',
-      //   meta: {
-      //     title: t('router.menuManagement')
-      //   }
-      // },
-      // {
-      //   path: 'role',
-      //   component: () => import('@/views/Authorization/Role/Role.vue'),
-      //   name: 'Role',
-      //   meta: {
-      //     title: t('router.role')
-      //   }
-      // },
-      // {
-      //   path: 'test',
-      //   component: () => import('@/views/Authorization/Test/Test.vue'),
-      //   name: 'Test',
-      //   meta: {
-      //     title: t('router.permission'),
-      //     permission: ['add', 'edit', 'delete']
-      //   }
-      // }
     ]
   }
 ]

+ 3 - 3
src/store/modules/app.ts

@@ -44,18 +44,18 @@ export const useAppStore = defineStore('app', {
       title: import.meta.env.VITE_APP_TITLE, // 标题
       pageLoading: false, // 路由跳转loading
       breadcrumb: true, // 面包屑
-      breadcrumbIcon: false, // 面包屑图标
+      breadcrumbIcon: true, // 面包屑图标
       collapse: false, // 折叠菜单
       uniqueOpened: false, // 是否只保持一个子菜单的展开
       hamburger: true, // 折叠图标
       screenfull: true, // 全屏图标
       size: true, // 尺寸图标
-      locale: false, // 多语言图标
+      locale: true, // 多语言图标
       tagsView: true, // 标签页
       tagsViewIcon: true, // 是否显示标签图标
       logo: true, // logo
       fixedHeader: true, // 固定toolheader
-      footer: false, // 显示页脚
+      footer: true, // 显示页脚
       greyMode: false, // 是否开始灰色模式,用于特殊悼念日
       dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
       serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由

+ 10 - 26
src/utils/propTypes.ts

@@ -1,29 +1,13 @@
-import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types'
-import { CSSProperties } from 'vue'
-
-// 自定义扩展vue-types
-type PropTypes = VueTypesInterface & {
-  readonly style: VueTypeValidableDef<CSSProperties>
-}
-
-const propTypes = createTypes({
-  func: undefined,
-  bool: undefined,
-  string: undefined,
-  number: undefined,
-  object: undefined,
-  integer: undefined
-}) as PropTypes
-
-// 需要自定义扩展的类型
-// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
-propTypes.extend([
-  {
-    name: 'style',
-    getter: true,
-    type: [String, Object],
-    default: undefined
+import VueTypes, { toType } from 'vue-types'
+import { CSSProperties, PropType } from 'vue'
+
+class propTypes extends VueTypes {
+  static get style() {
+    return toType('style', {
+      type: [String, Object] as PropType<[string | CSSProperties]>,
+      default: undefined
+    })
   }
-])
+}
 
 export { propTypes }

+ 7 - 14
src/views/Authorization/Department/Department.vue

@@ -80,26 +80,19 @@ const crudSchemas = reactive<CrudSchema[]>([
     }
   },
   {
-    field: 'departmentName',
+    field: 'id',
     label: t('userDemo.departmentName'),
-    search: {
-      hidden: true
-    },
-    form: {
-      component: 'Input'
-    }
-  },
-  {
-    field: 'superiorDepartment',
-    label: t('userDemo.superiorDepartment'),
     table: {
-      hidden: true
+      slots: {
+        default: (data: any) => {
+          return <>{data.row.departmentName}</>
+        }
+      }
     },
     form: {
       component: 'TreeSelect',
       componentProps: {
         nodeKey: 'id',
-        checkStrictly: true,
         props: {
           label: 'departmentName'
         }
@@ -112,7 +105,7 @@ const crudSchemas = reactive<CrudSchema[]>([
     detail: {
       slots: {
         default: (data: any) => {
-          return <>{data.superiorDepartmentName}</>
+          return <>{data.departmentName}</>
         }
       }
     }

+ 71 - 62
src/views/Authorization/Menu/Menu.vue

@@ -1,11 +1,11 @@
 <script setup lang="tsx">
 import { reactive, ref, unref } from 'vue'
-import { getMenuListApi } from '@/api/menu'
+import { getMenuListApi, saveMenu } from '@/api/menu'
 import { useTable } from '@/hooks/web/useTable'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table, TableColumn } from '@/components/Table'
 import { ElButton, ElTag } from 'element-plus'
-import { Icon } from '@/components/Icon'
+// import { Icon } from '@/components/Icon'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
 import { ContentWrap } from '@/components/ContentWrap'
@@ -17,8 +17,9 @@ const { t } = useI18n()
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
     const res = await getMenuListApi()
+    console.log(res)
     return {
-      list: res.data.list || []
+      list: res.data || []
     }
   }
 })
@@ -27,74 +28,69 @@ const { dataList, loading } = tableState
 const { getList } = tableMethods
 
 const tableColumns = reactive<TableColumn[]>([
+  // {
+  //   field: 'index',
+  //   label: t('userDemo.index'),
+  //   type: 'index'
+  // },
   {
-    field: 'index',
-    label: t('userDemo.index'),
-    type: 'index'
-  },
-  {
-    field: 'meta.title',
+    field: 'name',
     label: t('menu.menuName')
   },
   {
-    field: 'meta.icon',
-    label: t('menu.icon'),
-    align: 'center',
-    headerAlign: 'center',
-    width: 80,
-    slots: {
-      default: (data: any) => {
-        const icon = data.row.meta.icon
-        if (icon) {
-          return (
-            <>
-              <Icon icon={icon} />
-            </>
-          )
-        } else {
-          return null
-        }
-      }
-    }
+    field: 'icon',
+    label: t('menu.icon')
+    // slots: {
+    //   default: (data: any) => {
+    //     const icon = data.row.icon
+    //     if (icon) {
+    //       return (
+    //         <>
+    //           <Icon icon={icon} />
+    //         </>
+    //       )
+    //     } else {
+    //       return null
+    //     }
+    //   }
+    // }
   },
   {
-    field: 'meta.permission',
-    label: t('menu.permission'),
+    field: 'type',
+    label: '类型',
     slots: {
       default: (data: any) => {
-        const permission = data.row.meta.permission
-        return permission ? <>{permission.join(', ')}</> : null
+        return (
+          <>
+            <ElTag type={data.row.type === 0 ? '' : data.row.type === 1 ? 'success' : 'info'}>
+              {data.row.type === 0 ? '目录' : data.row.type === 1 ? '菜单' : '按钮'}
+            </ElTag>
+          </>
+        )
       }
     }
   },
+  // {
+  //   field: 'component',
+  //   label: t('menu.component'),
+  //   slots: {
+  //     default: (data: any) => {
+  //       const component = data.row.component
+  //       return <>{component === '#' ? '顶级目录' : component === '##' ? '子目录' : component}</>
+  //     }
+  //   }
+  // },
   {
-    field: 'component',
-    label: t('menu.component'),
-    slots: {
-      default: (data: any) => {
-        const component = data.row.component
-        return <>{component === '#' ? '顶级目录' : component === '##' ? '子目录' : component}</>
-      }
-    }
+    field: 'orderNum',
+    label: '排序'
   },
   {
-    field: 'path',
-    label: t('menu.path')
+    field: 'url',
+    label: '路由'
   },
   {
-    field: 'status',
-    label: t('menu.status'),
-    slots: {
-      default: (data: any) => {
-        return (
-          <>
-            <ElTag type={data.row.status === 0 ? 'danger' : 'success'}>
-              {data.row.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
-            </ElTag>
-          </>
-        )
-      }
-    }
+    field: 'permissions',
+    label: '授权标识'
   },
   {
     field: 'action',
@@ -108,7 +104,14 @@ const tableColumns = reactive<TableColumn[]>([
             <ElButton type="primary" onClick={() => action(row, 'edit')}>
               {t('exampleDemo.edit')}
             </ElButton>
-            <ElButton type="danger">{t('exampleDemo.del')}</ElButton>
+            <ElButton
+              type="danger"
+              onClick={() => {
+                handleDelete(row)
+              }}
+            >
+              {t('exampleDemo.del')}
+            </ElButton>
           </>
         )
       }
@@ -147,6 +150,10 @@ const action = (row: any, type: string) => {
   dialogVisible.value = true
 }
 
+const handleDelete = (row: any) => {
+  console.log(row)
+}
+
 const AddAction = () => {
   dialogTitle.value = t('exampleDemo.add')
   currentRow.value = undefined
@@ -159,10 +166,12 @@ const save = async () => {
   const formData = await write?.submit()
   if (formData) {
     saveLoading.value = true
-    setTimeout(() => {
-      saveLoading.value = false
-      dialogVisible.value = false
-    }, 1000)
+    saveMenu(formData).then((res) => {
+      if (res) {
+        saveLoading.value = false
+        dialogVisible.value = false
+      }
+    })
   }
 }
 </script>
@@ -175,7 +184,6 @@ const save = async () => {
     </div>
     <Table
       :columns="tableColumns"
-      default-expand-all
       node-key="id"
       :data="dataList"
       :loading="loading"
@@ -187,9 +195,10 @@ const save = async () => {
     <Write
       v-if="actionType !== 'detail'"
       ref="writeRef"
-      :menu-list="dataList"
+      :treeSelectData="dataList"
       :current-row="currentRow"
     />
+
     <template #footer>
       <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
         {{ t('exampleDemo.save') }}

+ 112 - 102
src/views/Authorization/Menu/components/Write.vue

@@ -14,140 +14,107 @@ const props = defineProps({
     type: Object as PropType<any>,
     default: () => null
   },
-  menuList: {
-    type: Array as PropType<any>
-  }
+  treeSelectData: Array
 })
 
 const formSchema = reactive<FormSchema[]>([
   {
-    field: 'meta.title',
-    label: t('menu.menuName'),
-    component: 'Input'
-  },
-  {
-    field: 'component',
-    label: t('menu.component'),
-    component: 'Input'
-  },
-  {
-    field: 'name',
-    label: t('menu.name'),
-    component: 'Input'
-  },
-  {
-    field: 'preciousMenu',
-    label: t('menu.preciousMenu'),
-    component: 'TreeSelect',
-    componentProps: {
-      nodeKey: 'name',
-      checkStrictly: true,
-      data: props.menuList,
-      props: {
-        label: (data) => {
-          return data.meta.title
-        }
-      }
-    }
-  },
-  {
-    field: 'meta.icon',
-    label: t('menu.icon'),
-    component: 'Input'
-  },
-  {
-    field: 'path',
-    label: t('menu.path'),
-    component: 'Input'
-  },
-  {
-    field: 'status',
-    label: t('menu.status'),
-    component: 'Select',
+    field: 'type',
+    label: '类型',
+    component: 'RadioGroup',
+    value: 0,
+    colProps: {
+      span: 24
+    },
     componentProps: {
       options: [
         {
-          label: t('userDemo.disable'),
+          label: '目录',
           value: 0
         },
         {
-          label: t('userDemo.enable'),
+          label: '菜单',
           value: 1
-        }
-      ]
-    }
-  },
-  {
-    field: 'meta.activeMenu',
-    label: t('menu.activeMenu'),
-    component: 'Input'
-  },
-  {
-    field: 'permission',
-    label: t('menu.permission'),
-    component: 'CheckboxGroup',
-    componentProps: {
-      options: [
-        {
-          label: 'add',
-          value: 'add'
-        },
-        {
-          label: 'edit',
-          value: 'edit'
         },
         {
-          label: 'delete',
-          value: 'delete'
+          label: '按钮',
+          value: 2
         }
-      ]
+      ],
+      on: {
+        change: (val: string | number) => {
+          changeType(val)
+        }
+      }
     }
   },
   {
-    field: 'meta.hidden',
-    label: t('menu.hidden'),
-    component: 'Switch'
-  },
-  {
-    field: 'meta.alwaysShow',
-    label: t('menu.alwaysShow'),
-    component: 'Switch'
-  },
-  {
-    field: 'meta.noCache',
-    label: t('menu.noCache'),
-    component: 'Switch'
+    field: 'name',
+    label: t('menu.menuName'),
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
   },
   {
-    field: 'meta.breadcrumb',
-    label: t('menu.breadcrumb'),
-    component: 'Switch'
+    field: 'parentName',
+    label: '上级菜单',
+    component: 'TreeSelect',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      checkStrictly: true,
+      nodeKey: 'id',
+      props: {
+        label: 'name'
+      },
+      data: props.treeSelectData
+    }
   },
   {
-    field: 'meta.affix',
-    label: t('menu.affix'),
-    component: 'Switch'
+    field: 'icon',
+    label: t('menu.icon'),
+    colProps: {
+      span: 24
+    },
+    component: 'IconPicker'
+  },
+  {
+    field: 'url',
+    label: '路由',
+    hidden: true,
+    colProps: {
+      span: 24
+    },
+    component: 'Input'
   },
+
   {
-    field: 'meta.noTagsView',
-    label: t('menu.noTagsView'),
-    component: 'Switch'
+    field: 'orderNum',
+    label: '排序',
+    colProps: {
+      span: 24
+    },
+    component: 'InputNumber'
   },
   {
-    field: 'canTo',
-    label: t('menu.canTo'),
-    component: 'Switch'
+    field: 'permissions',
+    label: '授权标识',
+    colProps: {
+      span: 24
+    },
+    component: 'Input'
   }
 ])
 
 const rules = reactive({
-  component: [required()],
-  path: [required()],
-  'meta.title': [required()]
+  name: [required()],
+  parentName: [required()]
 })
 
 const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose } = formMethods
+const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
 
 const submit = async () => {
   const elForm = await getElFormExpose()
@@ -160,6 +127,49 @@ const submit = async () => {
   }
 }
 
+const changeType = (val: string | number) => {
+  if (val == 0) {
+    setSchema([
+      {
+        field: 'url',
+        path: 'hidden',
+        value: true
+      },
+      {
+        field: 'icon',
+        path: 'hidden',
+        value: false
+      }
+    ])
+  } else if (val == 1) {
+    setSchema([
+      {
+        field: 'url',
+        path: 'hidden',
+        value: false
+      },
+      {
+        field: 'icon',
+        path: 'hidden',
+        value: true
+      }
+    ])
+  } else {
+    setSchema([
+      {
+        field: 'url',
+        path: 'hidden',
+        value: true
+      },
+      {
+        field: 'icon',
+        path: 'hidden',
+        value: true
+      }
+    ])
+  }
+}
+
 watch(
   () => props.currentRow,
   (currentRow) => {

+ 1 - 1
src/views/Authorization/Test/Test.vue

@@ -8,7 +8,7 @@ const permission = ref('add')
 
 setTimeout(() => {
   permission.value = 'view'
-}, 10000)
+}, 3000)
 </script>
 
 <template>

+ 137 - 120
src/views/Authorization/User/User.vue

@@ -2,119 +2,114 @@
 import { ContentWrap } from '@/components/ContentWrap'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table } from '@/components/Table'
-import { ref, unref, watch, reactive } from 'vue'
-import { ElButton, ElTree, ElTooltip, ElButtonGroup } from 'element-plus'
-import { getUserList, addUser, updataUser, deleteUserById, getRoleApi } from '@/api/department'
-import type { DepartmentUserItem } from '@/api/department/types'
+import { ref, unref, nextTick, watch, reactive } from 'vue'
+import { ElButton, ElTree, ElInput, ElDivider } from 'element-plus'
+import { getDepartmentApi, getUserByIdApi, saveUserApi, deleteUserByIdApi } from '@/api/department'
+import type { DepartmentItem, DepartmentUserItem } from '@/api/department/types'
 import { useTable } from '@/hooks/web/useTable'
 import { Search } from '@/components/Search'
 import Write from './components/Write.vue'
 import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
 import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { useIcon } from '@/hooks/web/useIcon'
-const EditIcon = useIcon({ icon: 'ep:edit' })
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
 
 const { t } = useI18n()
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
     const { pageSize, currentPage } = tableState
-    const res = await getUserList({
-      pageNum: unref(currentPage),
+    const res = await getUserByIdApi({
+      id: unref(currentNodeKey),
+      pageIndex: unref(currentPage),
       pageSize: unref(pageSize),
       ...unref(searchParams)
     })
     return {
       list: res.data.list || [],
-      total: Number(res.data.totalCount)
+      total: res.data.total || 0
     }
   },
   fetchDelApi: async () => {
-    const res = await deleteUserById(unref(id))
+    const res = await deleteUserByIdApi(unref(ids))
     return !!res
   }
 })
 const { total, loading, dataList, pageSize, currentPage } = tableState
-const { getList, delList } = tableMethods
+const { getList, getElTableExpose, delList } = tableMethods
 
 const crudSchemas = reactive<CrudSchema[]>([
   {
-    field: 'name',
-    label: t('userDemo.username')
-  },
-  {
-    field: 'account',
-    label: t('userDemo.account'),
+    field: 'selection',
     search: {
       hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      type: 'selection'
     }
   },
   {
-    field: 'isActive',
-    label: '是否启用',
+    field: 'index',
+    label: t('userDemo.index'),
     form: {
-      component: 'Switch',
-      value: '1',
-      componentProps: {
-        activeText: '启用',
-        inactiveText: '禁用',
-        activeValue: '1',
-        inactiveValue: '0'
-      }
+      hidden: true
     },
     search: {
-      component: 'Select',
-      componentProps: {
-        options: [
-          {
-            value: '1',
-            label: '启用'
-          },
-          {
-            value: '0',
-            label: '禁用'
-          }
-        ]
-      }
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      type: 'index'
     }
   },
   {
-    field: 'roleId',
-    label: t('userDemo.role'),
+    field: 'username',
+    label: t('userDemo.username')
+  },
+  {
+    field: 'account',
+    label: t('userDemo.account')
+  },
+  {
+    field: 'department.id',
+    label: t('userDemo.department'),
+    detail: {
+      slots: {
+        default: (data: DepartmentUserItem) => {
+          return <>{data.department.departmentName}</>
+        }
+      }
+    },
     search: {
       hidden: true
     },
     form: {
-      component: 'Select',
+      component: 'TreeSelect',
+      componentProps: {
+        nodeKey: 'id',
+        props: {
+          label: 'departmentName'
+        }
+      },
       optionApi: async () => {
-        const res = await getRoleApi()
-        return res.data.map((e) => {
-          return {
-            label: e.roleDesc,
-            value: e.id
-          }
-        })
+        const res = await getDepartmentApi()
+        return res.data.list
       }
-    }
-  },
-  {
-    field: 'phone',
-    label: '电话',
-    form: {
-      component: 'Input'
     },
-    search: {
-      hidden: true
+    table: {
+      type: 'index'
     }
   },
   {
-    field: 'address',
-    label: '住址',
-    form: {
-      component: 'Input'
-    },
+    field: 'role',
+    label: t('userDemo.role'),
     search: {
       hidden: true
     }
@@ -129,27 +124,11 @@ const crudSchemas = reactive<CrudSchema[]>([
       hidden: true
     }
   },
-  {
-    field: 'password',
-    label: '密码',
-    form: {
-      component: 'Input',
-      componentProps: {
-        type: 'password'
-      }
-    },
-    table: {
-      hidden: true
-    },
-    search: {
-      hidden: true
-    }
-  },
   {
     field: 'createTime',
     label: t('userDemo.createTime'),
     form: {
-      hidden: true
+      component: 'Input'
     },
     search: {
       hidden: true
@@ -168,26 +147,21 @@ const crudSchemas = reactive<CrudSchema[]>([
       hidden: true
     },
     table: {
-      width: 120,
-      fixed: 'right',
+      width: 240,
       slots: {
         default: (data: any) => {
           const row = data.row as DepartmentUserItem
           return (
             <>
-              <ElButtonGroup>
-                <ElTooltip content="编辑">
-                  <ElButton text icon={EditIcon} onClick={() => action(row, 'edit')} />
-                </ElTooltip>
-                <ElTooltip content="删除">
-                  <ElButton
-                    text
-                    icon={DeleteIcon}
-                    type="danger"
-                    onClick={() => delData(data.row)}
-                  />
-                </ElTooltip>
-              </ElButtonGroup>
+              <ElButton type="primary" onClick={() => action(row, 'edit')}>
+                {t('exampleDemo.edit')}
+              </ElButton>
+              <ElButton type="success" onClick={() => action(row, 'detail')}>
+                {t('exampleDemo.detail')}
+              </ElButton>
+              <ElButton type="danger" onClick={() => delData(row)}>
+                {t('exampleDemo.del')}
+              </ElButton>
             </>
           )
         }
@@ -207,6 +181,18 @@ const setSearchParams = (params: any) => {
 
 const treeEl = ref<typeof ElTree>()
 
+const currentNodeKey = ref('')
+const departmentList = ref<DepartmentItem[]>([])
+const fetchDepartment = async () => {
+  const res = await getDepartmentApi()
+  departmentList.value = res.data.list
+  currentNodeKey.value =
+    (res.data.list[0] && res.data.list[0]?.children && res.data.list[0].children[0].id) || ''
+  await nextTick()
+  unref(treeEl)?.setCurrentKey(currentNodeKey.value)
+}
+fetchDepartment()
+
 const currentDepartment = ref('')
 watch(
   () => currentDepartment.value,
@@ -215,6 +201,18 @@ watch(
   }
 )
 
+const currentChange = (data: DepartmentItem) => {
+  if (data.children) return
+  currentNodeKey.value = data.id
+  currentPage.value = 1
+  getList()
+}
+
+const filterNode = (value: string, data: DepartmentItem) => {
+  if (!value) return true
+  return data.departmentName.includes(value)
+}
+
 const dialogVisible = ref(false)
 const dialogTitle = ref('')
 
@@ -229,25 +227,29 @@ const AddAction = () => {
 }
 
 const delLoading = ref(false)
-const id = ref<number>(0)
+const ids = ref<string[]>([])
 
-const delData = async (row: DepartmentUserItem) => {
-  id.value = row.id
+const delData = async (row?: DepartmentUserItem) => {
+  const elTableExpose = await getElTableExpose()
+  ids.value = row
+    ? [row.id]
+    : elTableExpose?.getSelectionRows().map((v: DepartmentUserItem) => v.id) || []
   delLoading.value = true
 
-  await delList(unref(id)).finally(() => {
+  await delList(unref(ids).length).finally(() => {
     delLoading.value = false
   })
 }
-const writeRef = ref<ComponentRef<typeof Write>>()
 
-const action = async (row: DepartmentUserItem, type: string) => {
+const action = (row: DepartmentUserItem, type: string) => {
   dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
   actionType.value = type
   currentRow.value = { ...row, department: unref(treeEl)?.getCurrentNode() || {} }
   dialogVisible.value = true
 }
 
+const writeRef = ref<ComponentRef<typeof Write>>()
+
 const saveLoading = ref(false)
 
 const save = async () => {
@@ -256,17 +258,13 @@ const save = async () => {
   if (formData) {
     saveLoading.value = true
     try {
-      let res: any
-      if (formData.id) {
-        res = await updataUser(formData)
-      } else {
-        res = await addUser(formData)
-      }
+      const res = await saveUserApi(formData)
       if (res) {
         currentPage.value = 1
         getList()
       }
     } catch (error) {
+      console.log(error)
     } finally {
       saveLoading.value = false
       dialogVisible.value = false
@@ -276,8 +274,32 @@ const save = async () => {
 </script>
 
 <template>
-  <div>
-    <ContentWrap>
+  <div class="flex w-100% h-100%">
+    <ContentWrap class="flex-1">
+      <div class="flex justify-center items-center">
+        <div class="flex-1">{{ t('userDemo.departmentList') }}</div>
+        <ElInput
+          v-model="currentDepartment"
+          class="flex-[2]"
+          :placeholder="t('userDemo.searchDepartment')"
+          clearable
+        />
+      </div>
+      <ElDivider />
+      <ElTree
+        ref="treeEl"
+        :data="departmentList"
+        default-expand-all
+        node-key="id"
+        :current-node-key="currentNodeKey"
+        :props="{
+          label: 'departmentName'
+        }"
+        :filter-node-method="filterNode"
+        @current-change="currentChange"
+      />
+    </ContentWrap>
+    <ContentWrap class="flex-[3] ml-20px">
       <Search
         :schema="allSchemas.searchSchema"
         @reset="setSearchParams"
@@ -286,6 +308,9 @@ const save = async () => {
 
       <div class="mb-10px">
         <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
+        <ElButton :loading="delLoading" type="danger" @click="delData()">
+          {{ t('exampleDemo.del') }}
+        </ElButton>
       </div>
       <Table
         v-model:current-page="currentPage"
@@ -304,15 +329,7 @@ const save = async () => {
       <Write
         v-if="actionType !== 'detail'"
         ref="writeRef"
-        :form-schema="
-          allSchemas.formSchema.filter((e) => {
-            if (actionType === 'edit') {
-              return e.field !== 'password'
-            } else {
-              return true
-            }
-          })
-        "
+        :form-schema="allSchemas.formSchema"
         :current-row="currentRow"
       />
 

+ 8 - 5
src/views/Authorization/User/components/Write.vue

@@ -19,12 +19,16 @@ const props = defineProps({
 })
 
 const rules = reactive({
-  name: [required()],
-  account: [required()]
+  username: [required()],
+  account: [required()],
+  'department.id': [required()],
+  role: [required()],
+  email: [required()],
+  createTime: [required()]
 })
 
 const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
+const { setValues, getFormData, getElFormExpose } = formMethods
 
 const submit = async () => {
   const elForm = await getElFormExpose()
@@ -50,8 +54,7 @@ watch(
 )
 
 defineExpose({
-  submit,
-  setSchema
+  submit
 })
 </script>
 

+ 11 - 0
src/views/Components/Form/DefaultForm.vue

@@ -1774,6 +1774,17 @@ const schema = reactive<FormSchema[]>([
       a: 1,
       b: 2
     }
+  },
+  {
+    field: 'field87',
+    component: 'Divider',
+    label: t('formDemo.iconPicker')
+  },
+  {
+    field: 'field88',
+    component: 'IconPicker',
+    label: t('formDemo.default'),
+    value: 'tdesign:archway'
   }
 ])
 </script>

+ 1 - 1
src/views/Components/IconPicker.vue

@@ -6,7 +6,7 @@ import { useI18n } from '@/hooks/web/useI18n'
 
 const { t } = useI18n()
 
-const currentIcon = ref('tdesign:book-open')
+const currentIcon = ref('')
 </script>
 
 <template>

+ 1 - 1
src/views/Components/Table/UseTableDemo.vue

@@ -110,7 +110,7 @@ const columns = reactive<TableColumn[]>([
 ])
 
 const actionFn = (data: TableSlotDefault) => {
-  console.log(data[0])
+  console.log(data)
 }
 
 const canShowPagination = ref(true)

+ 2 - 45
src/views/Dashboard/Workplace.vue

@@ -2,9 +2,9 @@
 import { ElRow, ElCol, ElSkeleton, ElCard } from 'element-plus'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ref } from 'vue'
+import {} from '@/api/dashboard/workplace'
 
 const loading = ref(false)
-
 const { t } = useI18n()
 </script>
 
@@ -15,54 +15,11 @@ const { t } = useI18n()
         <ElRow :gutter="20" justify="space-between">
           <ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
             <div class="flex items-center">
-              <!-- <img
-                src="@/assets/imgs/avatar.jpg"
-                alt=""
-                class="w-70px h-70px rounded-[50%] mr-20px"
-              /> -->
               <div>
-                <div class="text-20px">
-                  {{ t('workplace.happyDay') }}
-                </div>
-                <!-- <div class="mt-10px text-14px text-gray-500">
-                  {{ t('workplace.toady') }},20℃ - 32℃!
-                </div> -->
+                <div class="text-20px"> 你好,{{ t('workplace.happyDay') }} </div>
               </div>
             </div>
           </ElCol>
-          <ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
-            <!-- <div class="flex h-70px items-center justify-end lt-sm:mt-20px">
-              <div class="px-8px text-right">
-                <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
-                <CountTo
-                  class="text-20px"
-                  :start-val="0"
-                  :end-val="totalSate.project"
-                  :duration="2600"
-                />
-              </div>
-              <ElDivider direction="vertical" />
-              <div class="px-8px text-right">
-                <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div>
-                <CountTo
-                  class="text-20px"
-                  :start-val="0"
-                  :end-val="totalSate.todo"
-                  :duration="2600"
-                />
-              </div>
-              <ElDivider direction="vertical" border-style="dashed" />
-              <div class="px-8px text-right">
-                <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
-                <CountTo
-                  class="text-20px"
-                  :start-val="0"
-                  :end-val="totalSate.access"
-                  :duration="2600"
-                />
-              </div>
-            </div> -->
-          </ElCol>
         </ElRow>
       </ElSkeleton>
     </ElCard>

+ 8 - 59
src/views/Example/Page/ExamplePage.vue

@@ -7,13 +7,10 @@ import { Table } from '@/components/Table'
 import { getTableListApi, delTableListApi } from '@/api/table'
 import { useTable } from '@/hooks/web/useTable'
 import { TableData } from '@/api/table/types'
-import { ref, unref } from 'vue'
+import { reactive, ref, unref } from 'vue'
 import { useRouter } from 'vue-router'
 import { useEmitt } from '@/hooks/event/useEmitt'
 import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { TableSetting } from '@/components/TableSetting'
-import { usePageStore } from '@/store/modules/page'
-import { set } from 'lodash-es'
 
 defineOptions({
   name: 'ExamplePage'
@@ -48,7 +45,7 @@ const { tableRegister, tableState, tableMethods } = useTable({
   }
 })
 const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, getElTableExpose, delList, setColumn } = tableMethods
+const { getList, getElTableExpose, delList } = tableMethods
 
 getList()
 
@@ -63,8 +60,8 @@ useEmitt({
 })
 
 const { t } = useI18n()
-const appStore = usePageStore()
-const crudSchemas: CrudSchema[] = [
+
+const crudSchemas = reactive<CrudSchema[]>([
   {
     field: 'selection',
     search: {
@@ -77,8 +74,7 @@ const crudSchemas: CrudSchema[] = [
       hidden: true
     },
     table: {
-      type: 'selection',
-      hidden: false
+      type: 'selection'
     }
   },
   {
@@ -88,9 +84,6 @@ const crudSchemas: CrudSchema[] = [
     search: {
       hidden: true
     },
-    table: {
-      hidden: false
-    },
     form: {
       hidden: true
     },
@@ -104,9 +97,6 @@ const crudSchemas: CrudSchema[] = [
     search: {
       component: 'Input'
     },
-    table: {
-      hidden: false
-    },
     form: {
       component: 'Input',
       colProps: {
@@ -122,9 +112,6 @@ const crudSchemas: CrudSchema[] = [
     label: t('tableDemo.author'),
     search: {
       hidden: true
-    },
-    table: {
-      hidden: false
     }
   },
   {
@@ -133,9 +120,6 @@ const crudSchemas: CrudSchema[] = [
     search: {
       hidden: true
     },
-    table: {
-      hidden: false
-    },
     form: {
       component: 'DatePicker',
       componentProps: {
@@ -150,9 +134,6 @@ const crudSchemas: CrudSchema[] = [
     search: {
       hidden: true
     },
-    table: {
-      hidden: false
-    },
     form: {
       component: 'Select',
       componentProps: {
@@ -201,9 +182,6 @@ const crudSchemas: CrudSchema[] = [
     search: {
       hidden: true
     },
-    table: {
-      hidden: false
-    },
     form: {
       component: 'InputNumber',
       value: 0
@@ -216,7 +194,7 @@ const crudSchemas: CrudSchema[] = [
       hidden: true
     },
     table: {
-      hidden: true
+      show: false
     },
     form: {
       component: 'Editor',
@@ -247,7 +225,6 @@ const crudSchemas: CrudSchema[] = [
       hidden: true
     },
     table: {
-      hidden: false,
       slots: {
         default: (data: any) => {
           return (
@@ -267,37 +244,10 @@ const crudSchemas: CrudSchema[] = [
       }
     }
   }
-]
+])
 
 // @ts-ignore
-const getSchemas = () => {
-  let localSchemas = appStore.getPageData['ExamplePage']
-  if (localSchemas && localSchemas.schemas) {
-    let localSchemasArr = localSchemas.schemas
-    for (let i = 0; i < localSchemasArr.length; i++) {
-      let item = localSchemasArr[i]
-      let index = crudSchemas.findIndex((e) => {
-        return e.field == item.field
-      })
-      if (index > 0) {
-        set(crudSchemas[index], 'table.hidden', item.table.hidden)
-      }
-    }
-  }
-}
-getSchemas()
-let allSchemas = useCrudSchemas(crudSchemas).allSchemas
-// 修改列设置后调用
-const setSchemas = (schemas: CrudSchema[]) => {
-  let arr = schemas.map((item) => {
-    return {
-      field: item.field,
-      path: 'hidden',
-      value: item.table ? item.table.hidden : false
-    }
-  })
-  setColumn(arr)
-}
+const { allSchemas } = useCrudSchemas(crudSchemas)
 
 const AddAction = () => {
   push('/example/example-add')
@@ -328,7 +278,6 @@ const action = (row: TableData, type: string) => {
       <ElButton :loading="delLoading" type="danger" @click="delData(null)">
         {{ t('exampleDemo.del') }}
       </ElButton>
-      <TableSetting page="ExamplePage" :data="crudSchemas" @set-schemas="setSchemas" />
     </div>
 
     <Table

+ 0 - 3
src/views/Example/Page/components/Write.vue

@@ -39,9 +39,6 @@ const schema = reactive<FormSchema[]>([
     component: 'Input',
     formItemProps: {
       rules: [required()]
-    },
-    componentProps: {
-      placeholder: '请输入作者'
     }
   },
   {

+ 167 - 0
src/views/Function/Request.vue

@@ -0,0 +1,167 @@
+<script lang="ts" setup>
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ElButton } from 'element-plus'
+import { request1, request2, request3, request4, request5 } from '@/api/request'
+import { ref } from 'vue'
+import request from '@/config/axios'
+
+const { t } = useI18n()
+
+const pending = ref<Set<string>>(new Set())
+
+const getRequest1 = async () => {
+  if (pending.value.has('/request/1')) {
+    return
+  }
+  try {
+    pending.value.add('/request/1')
+    const res = await request1()
+    console.log('【res】:', res)
+  } catch (error) {
+    console.log('【error】:', error)
+  } finally {
+    pending.value.delete('/request/1')
+  }
+}
+
+const getRequest2 = async () => {
+  if (pending.value.has('/request/2')) {
+    return
+  }
+  try {
+    pending.value.add('/request/2')
+    const res = await request2()
+    console.log('【res】:', res)
+  } catch (error) {
+    console.log('【error】:', error)
+  } finally {
+    pending.value.delete('/request/2')
+  }
+}
+
+const getRequest3 = async () => {
+  if (pending.value.has('/request/3')) {
+    return
+  }
+  try {
+    pending.value.add('/request/3')
+    const res = await request3()
+    console.log('【res】:', res)
+  } catch (error) {
+    console.log('【error】:', error)
+  } finally {
+    pending.value.delete('/request/3')
+  }
+}
+
+const getRequest4 = async () => {
+  if (pending.value.has('/request/4')) {
+    return
+  }
+  try {
+    pending.value.add('/request/4')
+    const res = await request4()
+    console.log('【res】:', res)
+  } catch (error) {
+    console.log('【error】:', error)
+  } finally {
+    pending.value.delete('/request/4')
+  }
+}
+
+const getRequest5 = async () => {
+  if (pending.value.has('/request/5')) {
+    return
+  }
+  try {
+    pending.value.add('/request/5')
+    const res = await request5()
+    console.log('【res】:', res)
+  } catch (error) {
+    console.log('【error】:', error)
+  } finally {
+    pending.value.delete('/request/5')
+  }
+}
+
+const getAll = () => {
+  getRequest1()
+  getRequest2()
+  getRequest3()
+  getRequest4()
+  getRequest5()
+}
+
+const cancelAll = () => {
+  request.cancelAllRequest()
+  pending.value.clear()
+}
+
+// set转数组
+const setToArray = (set: Set<string>) => {
+  const arr: string[] = []
+  set.forEach((item) => {
+    arr.push(item)
+  })
+  return arr
+}
+
+const clickRequest1 = () => {
+  if (pending.value.has('/request/1')) {
+    request.cancelRequest('/request/1')
+    pending.value.delete('/request/1')
+    return
+  }
+  getRequest1()
+}
+
+const clickRequest2 = () => {
+  if (pending.value.has('/request/2')) {
+    request.cancelRequest('/request/2')
+    pending.value.delete('/request/2')
+    return
+  }
+  getRequest2()
+}
+
+const clickRequest3 = () => {
+  if (pending.value.has('/request/3')) {
+    request.cancelRequest('/request/3')
+    pending.value.delete('/request/3')
+    return
+  }
+  getRequest3()
+}
+
+const clickRequest4 = () => {
+  if (pending.value.has('/request/4')) {
+    request.cancelRequest('/request/4')
+    pending.value.delete('/request/4')
+    return
+  }
+  getRequest4()
+}
+
+const clickRequest5 = () => {
+  if (pending.value.has('/request/5')) {
+    request.cancelRequest('/request/5')
+    pending.value.delete('/request/5')
+    return
+  }
+  getRequest5()
+}
+</script>
+
+<template>
+  <ContentWrap :title="t('router.request')">
+    <p>正在请求的接口:{{ setToArray(pending) }}</p>
+    <ElButton type="primary" @click="clickRequest1">请求/取消request1</ElButton>
+    <ElButton type="primary" @click="clickRequest2">请求/取消request2</ElButton>
+    <ElButton type="primary" @click="clickRequest3">请求/取消request3</ElButton>
+    <ElButton type="primary" @click="clickRequest4">请求/取消request4</ElButton>
+    <ElButton type="primary" @click="clickRequest5">请求/取消request5</ElButton>
+    <ElButton type="primary" @click="getAll">发送五个请求</ElButton>
+    <ElButton type="primary" @click="cancelAll">关闭所有请求</ElButton>
+  </ContentWrap>
+</template>

+ 10 - 8
src/views/Login/Login.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { LoginForm, RegisterForm } from './components'
-// import { ThemeSwitch } from '@/components/ThemeSwitch'
-// import { LocaleDropdown } from '@/components/LocaleDropdown'
+import { ThemeSwitch } from '@/components/ThemeSwitch'
+import { LocaleDropdown } from '@/components/LocaleDropdown'
 import { useI18n } from '@/hooks/web/useI18n'
 import { underlineToHump } from '@/utils'
 import { useAppStore } from '@/store/modules/app'
@@ -50,7 +50,9 @@ const toLogin = () => {
             >
               <img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
               <div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
-              <div class="mt-5 font-normal text-white text-14px" key="3"> 管理网站信息 </div>
+              <div class="mt-5 font-normal text-white text-14px" key="3">
+                {{ t('login.message') }}
+              </div>
             </TransitionGroup>
           </div>
         </div>
@@ -63,10 +65,10 @@ const toLogin = () => {
               <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
             </div>
 
-            <!-- <div class="flex justify-end items-center space-x-10px">
+            <div class="flex justify-end items-center space-x-10px">
               <ThemeSwitch />
               <LocaleDropdown class="lt-xl:text-white dark:text-white" />
-            </div> -->
+            </div>
           </div>
           <Transition appear enter-active-class="animate__animated animate__bounceInRight">
             <div
@@ -74,14 +76,14 @@ const toLogin = () => {
             >
               <LoginForm
                 v-if="isLogin"
-                class="p-20px h-auto m-auto lt-xl:rounded-3xl bg-white"
+                class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
                 @to-register="toRegister"
               />
-              <!-- <RegisterForm
+              <RegisterForm
                 v-else
                 class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
                 @to-login="toLogin"
-              /> -->
+              />
             </div>
           </Transition>
         </div>

+ 70 - 62
src/views/Login/components/LoginForm.vue

@@ -4,13 +4,13 @@ import { Form, FormSchema } from '@/components/Form'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ElButton, ElCheckbox, ElLink } from 'element-plus'
 import { useForm } from '@/hooks/web/useForm'
-import { loginApi, getTestRoleApi, getAdminRoleApi } from '@/api/login'
+import { loginApi, getTestRoleApi, getAdminRoleApi, getUserInfo } 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'
 import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
-import { UserType } from '@/api/login/types'
+import { UserLoginType } from '@/api/login/types'
 import { useValidator } from '@/hooks/web/useValidator'
 import { Icon } from '@/components/Icon'
 
@@ -48,7 +48,7 @@ const schema = reactive<FormSchema[]>([
     }
   },
   {
-    field: 'account',
+    field: 'username',
     label: t('login.username'),
     value: 'admin',
     component: 'Input',
@@ -111,66 +111,71 @@ const schema = reactive<FormSchema[]>([
                   {t('login.login')}
                 </ElButton>
               </div>
+              <div class="w-[100%] mt-15px">
+                <ElButton class="w-[100%]" onClick={toRegister}>
+                  {t('login.register')}
+                </ElButton>
+              </div>
+            </>
+          )
+        }
+      }
+    }
+  },
+  {
+    field: 'other',
+    component: 'Divider',
+    label: t('login.otherLogin'),
+    componentProps: {
+      contentPosition: 'center'
+    }
+  },
+  {
+    field: 'otherIcon',
+    colProps: {
+      span: 24
+    },
+    formItemProps: {
+      slots: {
+        default: () => {
+          return (
+            <>
+              <div class="flex justify-between w-[100%]">
+                <Icon
+                  icon="ant-design:github-filled"
+                  size={iconSize}
+                  class="cursor-pointer ant-icon"
+                  color={iconColor}
+                  hoverColor={hoverColor}
+                />
+                <Icon
+                  icon="ant-design:wechat-filled"
+                  size={iconSize}
+                  class="cursor-pointer ant-icon"
+                  color={iconColor}
+                  hoverColor={hoverColor}
+                />
+                <Icon
+                  icon="ant-design:alipay-circle-filled"
+                  size={iconSize}
+                  color={iconColor}
+                  hoverColor={hoverColor}
+                  class="cursor-pointer ant-icon"
+                />
+                <Icon
+                  icon="ant-design:weibo-circle-filled"
+                  size={iconSize}
+                  color={iconColor}
+                  hoverColor={hoverColor}
+                  class="cursor-pointer ant-icon"
+                />
+              </div>
             </>
           )
         }
       }
     }
   }
-  // {
-  //   field: 'other',
-  //   component: 'Divider',
-  //   label: t('login.otherLogin'),
-  //   componentProps: {
-  //     contentPosition: 'center'
-  //   }
-  // },
-  // {
-  //   field: 'otherIcon',
-  //   colProps: {
-  //     span: 24
-  //   },
-  //   formItemProps: {
-  //     slots: {
-  //       default: () => {
-  //         return (
-  //           <>
-  //             <div class="flex justify-between w-[100%]">
-  //               <Icon
-  //                 icon="ant-design:github-filled"
-  //                 size={iconSize}
-  //                 class="cursor-pointer ant-icon"
-  //                 color={iconColor}
-  //                 hoverColor={hoverColor}
-  //               />
-  //               <Icon
-  //                 icon="ant-design:wechat-filled"
-  //                 size={iconSize}
-  //                 class="cursor-pointer ant-icon"
-  //                 color={iconColor}
-  //                 hoverColor={hoverColor}
-  //               />
-  //               <Icon
-  //                 icon="ant-design:alipay-circle-filled"
-  //                 size={iconSize}
-  //                 color={iconColor}
-  //                 hoverColor={hoverColor}
-  //                 class="cursor-pointer ant-icon"
-  //               />
-  //               <Icon
-  //                 icon="ant-design:weibo-circle-filled"
-  //                 size={iconSize}
-  //                 color={iconColor}
-  //                 hoverColor={hoverColor}
-  //                 class="cursor-pointer ant-icon"
-  //               />
-  //             </div>
-  //           </>
-  //         )
-  //       }
-  //     }
-  //   }
-  // }
 ])
 
 const iconSize = 30
@@ -204,14 +209,17 @@ const signIn = async () => {
   await formRef?.validate(async (isValid) => {
     if (isValid) {
       loading.value = true
-      const formData = await getFormData<UserType>()
+      const formData = await getFormData<UserLoginType>()
 
       try {
         const res = await loginApi(formData)
-
+        console.log(res)
         if (res) {
+          setStorage('token', res.data.token)
+          setStorage('tokenHead', res.data.tokenHead)
           setStorage(appStore.getUserInfo, res.data)
-          setStorage('token', res.token)
+          const user = await getUserInfo()
+          setStorage(appStore.getUserInfo, user.data)
           // 是否使用动态路由
           if (appStore.getDynamicRouter) {
             getRole()
@@ -233,9 +241,9 @@ const signIn = async () => {
 
 // 获取角色信息
 const getRole = async () => {
-  const formData = await getFormData<UserType>()
+  const formData = await getFormData<UserLoginType>()
   const params = {
-    roleName: formData.account
+    roleName: formData.username
   }
   const res =
     appStore.getDynamicRouter && appStore.getServerDynamicRouter

+ 0 - 469
src/views/Manage/Company/CompanyPage.vue

@@ -1,469 +0,0 @@
-<script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Search } from '@/components/Search'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElTooltip, ElButtonGroup } from 'element-plus'
-import { Table } from '@/components/Table'
-import {
-  getTableListApi,
-  delTableListApi,
-  saveTableApi,
-  updateTableApi
-} from '@/api/manage/company'
-import { useTable } from '@/hooks/web/useTable'
-import { ref, unref } from 'vue'
-import { useEmitt } from '@/hooks/event/useEmitt'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { TableSetting } from '@/components/TableSetting'
-import { usePageStore } from '@/store/modules/page'
-import { set } from 'lodash-es'
-import { useStorage } from '@/hooks/web/useStorage'
-import { Dialog } from '@/components/Dialog'
-import { CompanyData } from '@/api/manage/types'
-import Write from './components/Write.vue'
-import { useIcon } from '@/hooks/web/useIcon'
-import { Icon } from '@/components/Icon'
-import { uploadFile } from '@/api/common'
-
-defineOptions({
-  name: 'CompanyPage'
-})
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
-const EditIcon = useIcon({ icon: 'ep:edit' })
-const id = ref<string>('')
-const { getStorage } = useStorage()
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  searchParams.value = params
-  getList()
-}
-const currentRow = ref<CompanyData>({
-  wechatImg: '',
-  companyName: ''
-})
-const wechatImg = ref('')
-const formState = ref<string>('add')
-const { tableRegister, tableState, tableMethods } = useTable({
-  fetchDataApi: async () => {
-    const { currentPage, pageSize } = tableState
-    const res = await getTableListApi({
-      pageNum: unref(currentPage),
-      pageSize: unref(pageSize),
-      ...unref(searchParams)
-    })
-    return {
-      list: res.data.list,
-      total: Number(res.data.totalCount)
-    }
-  },
-  fetchDelApi: async () => {
-    const res = await delTableListApi(unref(id))
-    return !!res
-  }
-})
-const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, delList, setColumn } = tableMethods
-
-getList()
-
-useEmitt({
-  name: 'getList',
-  callback: (type: string) => {
-    if (type === 'add') {
-      currentPage.value = 1
-    }
-    getList()
-  }
-})
-
-const { t } = useI18n()
-const appStore = usePageStore()
-const dialogVisible = ref(false)
-// const fileList = ref([])
-const crudSchemas: CrudSchema[] = [
-  {
-    field: 'companyName',
-    label: '公司名称',
-    minWidth: 100,
-    table: {
-      hidden: false
-    }
-  },
-
-  {
-    field: 'companyPhone',
-    label: '公司电话',
-    minWidth: 120,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {}
-  },
-  {
-    field: 'companyEmail',
-    label: '公司邮箱',
-    minWidth: 120,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {}
-  },
-  {
-    field: 'companyAddress',
-    label: '公司地址',
-    minWidth: 120,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: false
-    }
-  },
-  {
-    field: 'companyBrief',
-    label: '公司简介',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      componentProps: {
-        type: 'textarea'
-      },
-      colProps: {
-        span: 24
-      }
-    }
-  },
-  {
-    field: 'name',
-    label: '负责人',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    form: {}
-  },
-  {
-    field: 'phone',
-    label: '负责人电话',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {}
-  },
-  {
-    field: 'wechat',
-    label: '微信账号',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {}
-  },
-  {
-    field: 'isActive',
-    label: '是否启用',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      component: 'Switch',
-      value: '1',
-      componentProps: {
-        activeText: '启用',
-        inactiveText: '禁用',
-        activeValue: '1',
-        inactiveValue: '0'
-      }
-    }
-  },
-  {
-    field: 'wechatImg',
-    label: '微信二维码',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      },
-      component: 'Upload',
-      componentProps: {
-        httpRequest: (data: any) => {
-          let file = data.file
-          let formData = new FormData()
-          formData.append('file', file)
-          uploadFile(formData).then((response) => {
-            wechatImg.value = response.data.virtualPath
-            currentRow.value.wechatImg = response.data.virtualPath
-          })
-        },
-        limit: 1,
-        showFileList: false,
-        class: 'companyUploader',
-        fileList: wechatImg.value
-          ? [
-              {
-                url: wechatImg.value
-              }
-            ]
-          : [],
-        headers: {
-          token: getStorage('token')
-        },
-        onSuccess: (response) => {
-          wechatImg.value = response.data.virtualPath
-          currentRow.value.wechatImg = response.data.virtualPath
-        },
-        onExceed: (_files, responses) => {
-          wechatImg.value = responses[0].response.data.virtualPath
-          currentRow.value.wechatImg = responses[0].response.data.virtualPath
-        },
-        slots: {
-          default: () => (
-            <>
-              {wechatImg.value ? <img src={wechatImg.value} class="avatar" /> : null}
-              {!wechatImg.value ? (
-                <Icon class="avatar-uploader-icon" icon="ep:plus" size={28}></Icon>
-              ) : null}
-            </>
-          )
-        }
-      }
-    }
-  },
-  {
-    field: 'createTime',
-    label: '创建时间',
-    minWidth: 180,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    }
-  },
-  {
-    field: 'action',
-    width: '120px',
-    label: '操作',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      hidden: false,
-      fixed: 'right',
-      slots: {
-        default: (data: any) => {
-          return (
-            <ElButtonGroup>
-              <ElTooltip content="编辑">
-                <ElButton text icon={EditIcon} onClick={() => handleEdit(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="删除">
-                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
-              </ElTooltip>
-            </ElButtonGroup>
-          )
-        }
-      }
-    }
-  }
-]
-
-// @ts-ignore
-const getSchemas = () => {
-  let localSchemas = appStore.getPageData['CompanyPage']
-  if (localSchemas && localSchemas.schemas) {
-    let localSchemasArr = localSchemas.schemas
-    for (let i = 0; i < localSchemasArr.length; i++) {
-      let item = localSchemasArr[i]
-      let index = crudSchemas.findIndex((e) => {
-        return e.field == item.field
-      })
-      if (index > 0) {
-        set(crudSchemas[index], 'table.hidden', item.table.hidden)
-      }
-    }
-  }
-}
-getSchemas()
-let allSchemas = useCrudSchemas(crudSchemas).allSchemas
-// 修改列设置后调用
-const setSchemas = (schemas: CrudSchema[]) => {
-  let arr = schemas.map((item) => {
-    return {
-      field: item.field,
-      path: 'hidden',
-      value: item.table ? item.table.hidden : false
-    }
-  })
-  setColumn(arr)
-}
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const handleEdit = (row: CompanyData) => {
-  currentRow.value = row
-  wechatImg.value = row.wechatImg
-  formState.value = 'edit'
-  dialogVisible.value = true
-}
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  console.log(formData)
-  if (formData) {
-    delLoading.value = true
-    try {
-      if (formData.id) {
-        let res = await updateTableApi(formData)
-        if (res) {
-          currentPage.value = 1
-          getList()
-        }
-      } else {
-        let res = await saveTableApi(formData)
-        if (res) {
-          currentPage.value = 1
-          getList()
-        }
-      }
-    } catch (error) {
-    } finally {
-      delLoading.value = false
-      dialogVisible.value = false
-    }
-  }
-}
-
-const delLoading = ref(false)
-
-const delData = async (row: CompanyData) => {
-  if (!row.id) return
-  id.value = row?.id
-  delLoading.value = true
-  await delList(unref(id).length).finally(() => {
-    delLoading.value = false
-  })
-}
-
-const handleAdd = () => {
-  formState.value = 'add'
-  currentRow.value = {
-    wechatImg: '',
-    companyName: ''
-  }
-  wechatImg.value = ''
-  dialogVisible.value = true
-}
-</script>
-
-<template>
-  <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
-
-    <div class="mb-10px">
-      <el-button type="primary" @click="handleAdd">新增</el-button>
-      <TableSetting page="CompanyPage" :data="crudSchemas" @set-schemas="setSchemas" />
-    </div>
-
-    <Table
-      v-model:pageSize="pageSize"
-      v-model:currentPage="currentPage"
-      :columns="allSchemas.tableColumns"
-      :data="dataList"
-      :loading="loading"
-      :pagination="{
-        total: total
-      }"
-      @register="tableRegister"
-    />
-  </ContentWrap>
-  <Dialog v-model="dialogVisible" :title="formState == 'add' ? '新增' : '编辑'">
-    <Write ref="writeRef" :form-schema="allSchemas.formSchema" :current-row="currentRow" />
-    <template #footer>
-      <ElButton type="primary" :loading="delLoading" @click="save">
-        {{ t('exampleDemo.save') }}
-      </ElButton>
-      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
-    </template>
-  </Dialog>
-</template>
-@/hooks/event/useEmitt
-
-<style lang="less">
-.uploadBtn {
-  display: inline-block;
-  margin-right: 12px;
-}
-.filePageUploader {
-  width: 100%;
-}
-</style>
-<style scoped>
-.companyUploader .avatar {
-  width: 178px;
-  height: 178px;
-  display: block;
-}
-</style>
-
-<style lang="less">
-.companyUploader .el-upload {
-  border: 1px dashed var(--el-border-color);
-  border-radius: 6px;
-  cursor: pointer;
-  position: relative;
-  overflow: hidden;
-  transition: var(--el-transition-duration-fast);
-  img {
-    width: 178px;
-    height: 178px;
-  }
-  .avatar-uploader-icon {
-    font-size: 28px;
-    color: #8c939d;
-    width: 178px;
-    height: 178px;
-    text-align: center;
-  }
-}
-</style>

+ 0 - 76
src/views/Manage/Company/components/Write.vue

@@ -1,76 +0,0 @@
-<script setup lang="ts">
-import { Form, FormSchema } from '@/components/Form'
-import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch } from 'vue'
-import { CompanyData } from '@/api/manage/types'
-import { useValidator } from '@/hooks/web/useValidator'
-
-const { required, isEmail } = useValidator()
-
-const props = defineProps({
-  currentRow: {
-    type: Object as PropType<CompanyData>,
-    default: () => undefined
-  },
-  formSchema: {
-    type: Array as PropType<FormSchema[]>,
-    default: () => []
-  }
-})
-
-const rules = reactive({
-  companyName: [required()],
-  companyEmail: [
-    required(),
-    {
-      validator: (_rule: any, value: any, callback: any) => {
-        isEmail(value, callback, '请输入正确的邮箱地址')
-      },
-      trigger: 'blur',
-      message: '请输入正确的邮箱地址'
-    }
-  ]
-})
-
-const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
-
-const submit = async () => {
-  const elForm = await getElFormExpose()
-  const valid = await elForm?.validate().catch((err) => {
-    console.log(err)
-  })
-  if (valid) {
-    const formData = await getFormData()
-    return formData
-  }
-}
-
-const resetForm = async () => {
-  const elForm = await getElFormExpose()
-  elForm?.resetFields()
-}
-
-watch(
-  () => props.currentRow,
-  (currentRow) => {
-    if (!currentRow) return
-    setValues(currentRow)
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-
-defineExpose({
-  submit,
-  setSchema,
-  setValues,
-  resetForm
-})
-</script>
-
-<template>
-  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
-</template>

+ 0 - 453
src/views/Manage/File/FilePage.vue

@@ -1,453 +0,0 @@
-<script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Search } from '@/components/Search'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElTooltip, ElMessage, ElButtonGroup } from 'element-plus'
-import { Table } from '@/components/Table'
-import { getTableListApi, delTableListApi, saveTableApi, updateTableApi } from '@/api/manage/file'
-import { useTable } from '@/hooks/web/useTable'
-import { ref, unref } from 'vue'
-import { useEmitt } from '@/hooks/event/useEmitt'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { TableSetting } from '@/components/TableSetting'
-import { usePageStore } from '@/store/modules/page'
-import { set } from 'lodash-es'
-import { useStorage } from '@/hooks/web/useStorage'
-import { Dialog } from '@/components/Dialog'
-import { FileData } from '@/api/manage/types'
-import Write from './components/Write.vue'
-import { useIcon } from '@/hooks/web/useIcon'
-import { Qrcode } from '@/components/Qrcode'
-import { uploadFile } from '@/api/common'
-defineOptions({
-  name: 'FilePage'
-})
-const QRIcon = useIcon({ icon: 'ic:round-qr-code' })
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
-const DownLoadIcon = useIcon({ icon: 'ep:download' })
-const copyIcon = useIcon({ icon: 'ep:document-copy' })
-const EditIcon = useIcon({ icon: 'ep:edit' })
-const id = ref<string>('')
-const QrVisible = ref<boolean>(false)
-const QrSrc = ref<string>('')
-const { getStorage } = useStorage()
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  searchParams.value = params
-  getList()
-}
-const currentRow = ref<FileData>({
-  virtualPath: ''
-})
-const formState = ref<string>('add')
-const { tableRegister, tableState, tableMethods } = useTable({
-  fetchDataApi: async () => {
-    const { currentPage, pageSize } = tableState
-    const res = await getTableListApi({
-      pageNum: unref(currentPage),
-      pageSize: unref(pageSize),
-      ...unref(searchParams)
-    })
-    return {
-      list: res.data.list,
-      total: Number(res.data.totalCount)
-    }
-  },
-  fetchDelApi: async () => {
-    const res = await delTableListApi(unref(id))
-    return !!res
-  }
-})
-const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, delList, setColumn } = tableMethods
-
-getList()
-
-useEmitt({
-  name: 'getList',
-  callback: (type: string) => {
-    if (type === 'add') {
-      currentPage.value = 1
-    }
-    getList()
-  }
-})
-
-const { t } = useI18n()
-const appStore = usePageStore()
-const dialogVisible = ref(false)
-// const fileList = ref([])
-const crudSchemas: CrudSchema[] = [
-  {
-    field: 'name',
-    label: '名称',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      }
-    }
-  },
-  {
-    field: 'version',
-    label: '版本号',
-    minWidth: 100,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    }
-  },
-  {
-    field: 'code',
-    label: '版本序列号',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      component: 'InputNumber'
-    }
-  },
-  {
-    field: 'fileName',
-    label: '文件名',
-    minWidth: 120,
-    table: {
-      hidden: false
-    },
-    form: {
-      hidden: true
-    }
-  },
-  {
-    field: 'virtualPath',
-    label: '文件链接',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      },
-      component: 'Upload',
-      componentProps: {
-        httpRequest: (data: any) => {
-          delLoading.value = true
-          let file = data.file
-          let formData = new FormData()
-          formData.append('file', file)
-          uploadFile(formData).then((response) => {
-            currentRow.value = {
-              ...currentRow.value,
-              ...response.data
-            }
-            delLoading.value = false
-          })
-        },
-        class: 'filePageUploader',
-        fileList: currentRow.value.virtualPath
-          ? [
-              {
-                url: currentRow.value.virtualPath
-              }
-            ]
-          : [],
-        headers: {
-          token: getStorage('token')
-        },
-        onSuccess: (response) => {
-          currentRow.value = {
-            ...currentRow.value,
-            ...response.data
-          }
-          const write = unref(writeRef)
-          write?.setValues({
-            ...response.data
-          })
-        },
-        slots: {
-          default: () => <ElButton type="primary">上传文件</ElButton>
-        }
-      }
-    }
-  },
-  {
-    field: 'filePath',
-    label: '文件路径',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      hidden: true
-    }
-  },
-  {
-    field: 'content',
-    label: '版本更新说明',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      },
-      componentProps: {
-        type: 'textarea'
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      },
-      componentProps: {
-        type: 'textarea'
-      }
-    }
-  },
-  {
-    field: 'createTime',
-    label: '创建时间',
-    minWidth: 160,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    }
-  },
-  {
-    field: 'action',
-    width: '250px',
-    label: '操作',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      hidden: false,
-      fixed: 'right',
-      slots: {
-        default: (data: any) => {
-          return (
-            <ElButtonGroup>
-              <ElTooltip content="编辑">
-                <ElButton text icon={EditIcon} onClick={() => handleEdit(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="二维码">
-                <ElButton text icon={QRIcon} onClick={() => showQrCode(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="下载">
-                <ElButton text icon={DownLoadIcon} onClick={() => downloadFile(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="复制链接">
-                <ElButton text icon={copyIcon} onClick={() => onCopy(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="删除">
-                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
-              </ElTooltip>
-            </ElButtonGroup>
-          )
-        }
-      }
-    }
-  }
-]
-
-// @ts-ignore
-const getSchemas = () => {
-  let localSchemas = appStore.getPageData['FilePage']
-  if (localSchemas && localSchemas.schemas) {
-    let localSchemasArr = localSchemas.schemas
-    for (let i = 0; i < localSchemasArr.length; i++) {
-      let item = localSchemasArr[i]
-      let index = crudSchemas.findIndex((e) => {
-        return e.field == item.field
-      })
-      if (index > 0) {
-        set(crudSchemas[index], 'table.hidden', item.table.hidden)
-      }
-    }
-  }
-}
-getSchemas()
-let allSchemas = useCrudSchemas(crudSchemas).allSchemas
-// 修改列设置后调用
-const setSchemas = (schemas: CrudSchema[]) => {
-  let arr = schemas.map((item) => {
-    return {
-      field: item.field,
-      path: 'hidden',
-      value: item.table ? item.table.hidden : false
-    }
-  })
-  setColumn(arr)
-}
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const handleEdit = (row: FileData) => {
-  currentRow.value = row
-  formState.value = 'edit'
-  dialogVisible.value = true
-}
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    delLoading.value = true
-    try {
-      if (formData.id) {
-        let res = await updateTableApi(formData)
-        if (res) {
-          currentPage.value = 1
-          getList()
-        }
-      } else {
-        let res = await saveTableApi(formData)
-        if (res) {
-          currentPage.value = 1
-          getList()
-        }
-      }
-    } catch (error) {
-    } finally {
-      delLoading.value = false
-      dialogVisible.value = false
-    }
-  }
-}
-
-const delLoading = ref(false)
-
-const delData = async (row: FileData) => {
-  if (!row.id) return
-  id.value = row?.id
-  delLoading.value = true
-  await delList(unref(id).length).finally(() => {
-    delLoading.value = false
-  })
-}
-
-const showQrCode = (row: FileData) => {
-  QrSrc.value = row.virtualPath
-  QrVisible.value = true
-}
-
-const downloadFile = (row: FileData) => {
-  window.open(row.virtualPath)
-}
-
-const onCopy = (row: FileData) => {
-  navigator.clipboard.writeText(row.virtualPath).then(() => {
-    ElMessage({
-      message: '复制成功!',
-      type: 'success'
-    })
-  })
-}
-
-const handleAdd = () => {
-  formState.value = 'add'
-  currentRow.value = {
-    virtualPath: ''
-  }
-  dialogVisible.value = true
-}
-</script>
-
-<template>
-  <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
-
-    <div class="mb-10px">
-      <el-button type="primary" @click="handleAdd">上传文件</el-button>
-      <TableSetting page="FilePage" :data="crudSchemas" @set-schemas="setSchemas" />
-    </div>
-
-    <Table
-      v-model:pageSize="pageSize"
-      v-model:currentPage="currentPage"
-      :columns="allSchemas.tableColumns"
-      :data="dataList"
-      :loading="loading"
-      :pagination="{
-        total: total
-      }"
-      @register="tableRegister"
-    />
-  </ContentWrap>
-  <Dialog v-model="dialogVisible" :title="formState == 'add' ? '新增文件' : '编辑文件'">
-    <Write ref="writeRef" :form-schema="allSchemas.formSchema" :current-row="currentRow" />
-    <template #footer>
-      <ElButton type="primary" :loading="delLoading" @click="save">
-        {{ t('exampleDemo.save') }}
-      </ElButton>
-      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
-    </template>
-  </Dialog>
-  <Dialog v-model="QrVisible" width="430px" title="二维码">
-    <Qrcode
-      :width="395"
-      :options="{
-        color: {
-          dark: '#55D187',
-          light: '#ffffff'
-        }
-      }"
-      :text="QrSrc"
-    />
-    <template #footer>
-      <ElButton @click="QrVisible = false">{{ t('dialogDemo.close') }}</ElButton>
-    </template>
-  </Dialog>
-</template>
-@/hooks/event/useEmitt
-
-<style lang="less">
-.uploadBtn {
-  display: inline-block;
-  margin-right: 12px;
-}
-.filePageUploader {
-  width: 100%;
-}
-</style>

+ 0 - 69
src/views/Manage/File/components/Write.vue

@@ -1,69 +0,0 @@
-<script setup lang="ts">
-import { Form, FormSchema } from '@/components/Form'
-import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch } from 'vue'
-import { FileData } from '@/api/manage/types'
-import { useValidator } from '@/hooks/web/useValidator'
-
-const { required } = useValidator()
-
-const props = defineProps({
-  currentRow: {
-    type: Object as PropType<FileData>,
-    default: () => undefined
-  },
-  formSchema: {
-    type: Array as PropType<FormSchema[]>,
-    default: () => []
-  }
-})
-
-const rules = reactive({
-  name: [required()],
-  version: [required()],
-  code: [required()],
-  virtualPath: [required()]
-})
-
-const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
-
-const submit = async () => {
-  const elForm = await getElFormExpose()
-  const valid = await elForm?.validate().catch((err) => {
-    console.log(err)
-  })
-  if (valid) {
-    const formData = await getFormData()
-    return formData
-  }
-}
-
-const resetForm = async () => {
-  const elForm = await getElFormExpose()
-  elForm?.resetFields()
-}
-
-watch(
-  () => props.currentRow,
-  (currentRow) => {
-    if (!currentRow) return
-    setValues(currentRow)
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-
-defineExpose({
-  submit,
-  setSchema,
-  setValues,
-  resetForm
-})
-</script>
-
-<template>
-  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
-</template>

+ 0 - 416
src/views/Manage/Img/ImgPage.vue

@@ -1,416 +0,0 @@
-<script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Search } from '@/components/Search'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElTooltip, ElMessage, ElButtonGroup } from 'element-plus'
-import { Table } from '@/components/Table'
-import { getTableListApi, delTableListApi, saveTableApi, updateTableApi } from '@/api/manage/img'
-import { useTable } from '@/hooks/web/useTable'
-import { ref, unref } from 'vue'
-import { useEmitt } from '@/hooks/event/useEmitt'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { TableSetting } from '@/components/TableSetting'
-import { usePageStore } from '@/store/modules/page'
-import { set } from 'lodash-es'
-import { useStorage } from '@/hooks/web/useStorage'
-import { Dialog } from '@/components/Dialog'
-import { ImgData } from '@/api/manage/types'
-import Write from './components/Write.vue'
-import { useIcon } from '@/hooks/web/useIcon'
-import { uploadFile } from '@/api/common'
-import { Qrcode } from '@/components/Qrcode'
-import { DictRowData } from '@/api/manage/types'
-import { getDict } from '@/api/manage/dict'
-
-defineOptions({
-  name: 'ImgPage'
-})
-const QRIcon = useIcon({ icon: 'ic:round-qr-code' })
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
-const DownLoadIcon = useIcon({ icon: 'ep:download' })
-const copyIcon = useIcon({ icon: 'ep:document-copy' })
-const EditIcon = useIcon({ icon: 'ep:edit' })
-const id = ref<string>('')
-const QrVisible = ref<boolean>(false)
-const QrSrc = ref<string>('')
-const { getStorage } = useStorage()
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  searchParams.value = params
-  getList()
-}
-const currentRow = ref<ImgData>({
-  bannerUrl: ''
-})
-const formState = ref<string>('add')
-const { tableRegister, tableState, tableMethods } = useTable({
-  fetchDataApi: async () => {
-    const { currentPage, pageSize } = tableState
-    const res = await getTableListApi({
-      pageNum: unref(currentPage),
-      pageSize: unref(pageSize),
-      ...unref(searchParams)
-    })
-    return {
-      list: res.data.list,
-      total: Number(res.data.totalCount)
-    }
-  },
-  fetchDelApi: async () => {
-    const res = await delTableListApi(unref(id))
-    return !!res
-  }
-})
-const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, delList, setColumn } = tableMethods
-
-getList()
-
-useEmitt({
-  name: 'getList',
-  callback: (type: string) => {
-    if (type === 'add') {
-      currentPage.value = 1
-    }
-    getList()
-  }
-})
-const bannerTypeList = ref<DictRowData[]>([])
-
-getDict({
-  pid: '1693799827333287937'
-}).then((res) => {
-  bannerTypeList.value = res.data
-})
-
-const { t } = useI18n()
-const appStore = usePageStore()
-const dialogVisible = ref(false)
-// const fileList = ref([])
-const crudSchemas: CrudSchema[] = [
-  {
-    field: 'bannerType',
-    label: '类型',
-    minWidth: 120,
-    table: {
-      hidden: false,
-      formatter: (_: Recordable, _colomun, cellValue: string) => {
-        let option = bannerTypeList.value.find((item) => item.code === cellValue)
-        return option ? option.name : ''
-      }
-    },
-    form: {
-      component: 'Select',
-      optionApi: async () => {
-        const res = await getDict({
-          pid: '1693799827333287937'
-        })
-        return res.data.map((e: DictRowData) => {
-          return {
-            label: e.name,
-            value: e.code
-          }
-        })
-      }
-    }
-  },
-  {
-    field: 'bannerUrl',
-    label: '图片地址',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      },
-      component: 'Upload',
-      componentProps: {
-        httpRequest: (data: any) => {
-          delLoading.value = true
-          let file = data.file
-          let formData = new FormData()
-          formData.append('file', file)
-          uploadFile(formData).then((response) => {
-            currentRow.value.bannerUrl = response.data.virtualPath
-            const write = unref(writeRef)
-            write?.setValues({
-              bannerUrl: response.data.virtualPath
-            })
-            delLoading.value = false
-          })
-        },
-        class: 'filePageUploader',
-        fileList: currentRow.value.bannerUrl
-          ? [
-              {
-                url: currentRow.value.bannerUrl
-              }
-            ]
-          : [],
-        headers: {
-          token: getStorage('token')
-        },
-        onSuccess: (response) => {
-          currentRow.value.bannerUrl = response.data.virtualPath
-        },
-        slots: {
-          default: () => <ElButton type="primary">上传文件</ElButton>
-        }
-      }
-    }
-  },
-  {
-    field: 'bannerTarget',
-    label: '跳转地址',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      colProps: {
-        span: 24
-      },
-      componentProps: {
-        type: 'textarea'
-      }
-    }
-  },
-  {
-    field: 'createTime',
-    label: '创建时间',
-    minWidth: 160,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    }
-  },
-  {
-    field: 'action',
-    width: '250px',
-    label: '操作',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      hidden: false,
-      fixed: 'right',
-      slots: {
-        default: (data: any) => {
-          return (
-            <ElButtonGroup>
-              <ElTooltip content="编辑">
-                <ElButton text icon={EditIcon} onClick={() => handleEdit(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="二维码">
-                <ElButton text icon={QRIcon} onClick={() => showQrCode(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="下载">
-                <ElButton text icon={DownLoadIcon} onClick={() => downloadFile(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="复制链接">
-                <ElButton text icon={copyIcon} onClick={() => onCopy(data.row)} />
-              </ElTooltip>
-              <ElTooltip content="删除">
-                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
-              </ElTooltip>
-            </ElButtonGroup>
-          )
-        }
-      }
-    }
-  }
-]
-
-// @ts-ignore
-const getSchemas = () => {
-  let localSchemas = appStore.getPageData['ImgPage']
-  if (localSchemas && localSchemas.schemas) {
-    let localSchemasArr = localSchemas.schemas
-    for (let i = 0; i < localSchemasArr.length; i++) {
-      let item = localSchemasArr[i]
-      let index = crudSchemas.findIndex((e) => {
-        return e.field == item.field
-      })
-      if (index > 0) {
-        set(crudSchemas[index], 'table.hidden', item.table.hidden)
-      }
-    }
-  }
-}
-getSchemas()
-let allSchemas = useCrudSchemas(crudSchemas).allSchemas
-// 修改列设置后调用
-const setSchemas = (schemas: CrudSchema[]) => {
-  let arr = schemas.map((item) => {
-    return {
-      field: item.field,
-      path: 'hidden',
-      value: item.table ? item.table.hidden : false
-    }
-  })
-  setColumn(arr)
-}
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const handleEdit = (row: ImgData) => {
-  currentRow.value = row
-  formState.value = 'edit'
-  dialogVisible.value = true
-}
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    delLoading.value = true
-    try {
-      if (formData.id) {
-        let res = await updateTableApi(formData)
-        if (res) {
-          getList()
-        }
-      } else {
-        let res = await saveTableApi(formData)
-        if (res) {
-          currentPage.value = 1
-          getList()
-        }
-      }
-    } catch (error) {
-    } finally {
-      delLoading.value = false
-      dialogVisible.value = false
-    }
-  }
-}
-
-const delLoading = ref(false)
-
-const delData = async (row: ImgData) => {
-  if (!row.id) return
-  id.value = row?.id
-  delLoading.value = true
-  await delList(unref(id).length).finally(() => {
-    delLoading.value = false
-  })
-}
-
-const showQrCode = (row: ImgData) => {
-  QrSrc.value = row.bannerUrl
-  QrVisible.value = true
-}
-
-const downloadFile = (row: ImgData) => {
-  window.open(row.bannerUrl)
-}
-
-const onCopy = (row: ImgData) => {
-  navigator.clipboard.writeText(row.bannerUrl).then(() => {
-    ElMessage({
-      message: '复制成功!',
-      type: 'success'
-    })
-  })
-}
-
-const handleAdd = () => {
-  formState.value = 'add'
-  currentRow.value = {
-    bannerUrl: ''
-  }
-  dialogVisible.value = true
-}
-</script>
-
-<template>
-  <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
-
-    <div class="mb-10px">
-      <el-button type="primary" @click="handleAdd">上传文件</el-button>
-      <TableSetting page="ImgPage" :data="crudSchemas" @set-schemas="setSchemas" />
-    </div>
-
-    <Table
-      v-model:pageSize="pageSize"
-      v-model:currentPage="currentPage"
-      :columns="allSchemas.tableColumns"
-      :data="dataList"
-      :loading="loading"
-      :pagination="{
-        total: total
-      }"
-      @register="tableRegister"
-    />
-  </ContentWrap>
-  <Dialog v-model="dialogVisible" :title="formState == 'add' ? '新增文件' : '编辑文件'">
-    <Write ref="writeRef" :form-schema="allSchemas.formSchema" :current-row="currentRow" />
-    <template #footer>
-      <ElButton type="primary" :loading="delLoading" @click="save">
-        {{ t('exampleDemo.save') }}
-      </ElButton>
-      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
-    </template>
-  </Dialog>
-  <Dialog v-model="QrVisible" width="430px" title="二维码">
-    <Qrcode
-      :width="395"
-      :options="{
-        color: {
-          dark: '#55D187',
-          light: '#ffffff'
-        }
-      }"
-      :text="QrSrc"
-    />
-    <template #footer>
-      <ElButton @click="QrVisible = false">{{ t('dialogDemo.close') }}</ElButton>
-    </template>
-  </Dialog>
-</template>
-@/hooks/event/useEmitt
-
-<style lang="less">
-.uploadBtn {
-  display: inline-block;
-  margin-right: 12px;
-}
-.filePageUploader {
-  width: 100%;
-}
-</style>

+ 0 - 80
src/views/Manage/Img/components/Write.vue

@@ -1,80 +0,0 @@
-<script setup lang="ts">
-import { Form, FormSchema } from '@/components/Form'
-import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch } from 'vue'
-import { ImgData } from '@/api/manage/types'
-import { useValidator } from '@/hooks/web/useValidator'
-
-const { required } = useValidator()
-
-const props = defineProps({
-  currentRow: {
-    type: Object as PropType<ImgData>,
-    default: () => undefined
-  },
-  formSchema: {
-    type: Array as PropType<FormSchema[]>,
-    default: () => []
-  }
-})
-
-const rules = reactive({
-  bannerUrl: [required()]
-})
-
-const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
-
-const submit = async () => {
-  const elForm = await getElFormExpose()
-  const valid = await elForm?.validate().catch((err) => {
-    console.log(err)
-  })
-  if (valid) {
-    const formData = await getFormData()
-    return formData
-  }
-}
-
-const resetForm = async () => {
-  const elForm = await getElFormExpose()
-  elForm?.resetFields()
-}
-
-watch(
-  () => props.currentRow,
-  (currentRow) => {
-    if (!currentRow) return
-    setValues(currentRow)
-    setSchema([
-      {
-        field: 'bannerUrl',
-        path: 'componentProps.fileList',
-        value: props.currentRow?.bannerUrl
-          ? [
-              {
-                url: props.currentRow.bannerUrl,
-                name: props.currentRow.bannerUrl
-              }
-            ]
-          : []
-      }
-    ])
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-
-defineExpose({
-  submit,
-  setSchema,
-  setValues,
-  resetForm
-})
-</script>
-
-<template>
-  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
-</template>

+ 0 - 53
src/views/Manage/News/NewsAdd.vue

@@ -1,53 +0,0 @@
-<script setup lang="ts">
-import Write from './components/Write.vue'
-import { ContentDetailWrap } from '@/components/ContentDetailWrap'
-import { ref, unref } from 'vue'
-import { ElButton } from 'element-plus'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useRouter } from 'vue-router'
-import { saveTableApi } from '@/api/manage/news'
-import { useEmitt } from '@/hooks/event/useEmitt'
-
-const { emitter } = useEmitt()
-
-const { push, go } = useRouter()
-
-const { t } = useI18n()
-
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const loading = ref(false)
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    loading.value = true
-    const res = await saveTableApi(formData)
-      .catch(() => {})
-      .finally(() => {
-        loading.value = false
-      })
-    if (res) {
-      emitter.emit('getList', 'add')
-      push('/manage/news-page')
-    }
-  }
-}
-</script>
-
-<template>
-  <ContentDetailWrap :title="t('exampleDemo.add')" @back="push('/manage/news-page')">
-    <Write ref="writeRef" />
-
-    <template #header>
-      <ElButton @click="go(-1)">
-        {{ t('common.back') }}
-      </ElButton>
-      <ElButton type="primary" :loading="loading" @click="save">
-        {{ t('exampleDemo.save') }}
-      </ElButton>
-    </template>
-  </ContentDetailWrap>
-</template>
-@/hooks/event/useEmitt

+ 0 - 38
src/views/Manage/News/NewsDetail.vue

@@ -1,38 +0,0 @@
-<script setup lang="ts">
-import Detail from './components/Detail.vue'
-import { ContentDetailWrap } from '@/components/ContentDetailWrap'
-import { ref } from 'vue'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useRouter, useRoute } from 'vue-router'
-import { getTableDetApi } from '@/api/manage/news'
-import { NewsTableData } from '@/api/manage/types'
-import { ElButton } from 'element-plus'
-
-const { push, go } = useRouter()
-
-const { query } = useRoute()
-
-const { t } = useI18n()
-
-const currentRow = ref<Nullable<NewsTableData>>(null)
-
-const getTableDet = async () => {
-  const res = await getTableDetApi(query.id as string)
-  if (res) {
-    currentRow.value = res.data
-  }
-}
-
-getTableDet()
-</script>
-
-<template>
-  <ContentDetailWrap :title="t('exampleDemo.detail')" @back="push('/manage/news-page')">
-    <template #header>
-      <ElButton @click="go(-1)">
-        {{ t('common.back') }}
-      </ElButton>
-    </template>
-    <Detail :current-row="currentRow" />
-  </ContentDetailWrap>
-</template>

+ 0 - 67
src/views/Manage/News/NewsEdit.vue

@@ -1,67 +0,0 @@
-<script setup lang="ts">
-import Write from './components/Write.vue'
-import { ContentDetailWrap } from '@/components/ContentDetailWrap'
-import { ref, unref } from 'vue'
-import { ElButton } from 'element-plus'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useRouter, useRoute } from 'vue-router'
-import { updateTableApi, getTableDetApi } from '@/api/manage/news'
-import { NewsTableData } from '@/api/manage/types'
-import { useEmitt } from '@/hooks/event/useEmitt'
-
-const { emitter } = useEmitt()
-
-const { push, go } = useRouter()
-
-const { query } = useRoute()
-
-const { t } = useI18n()
-
-const currentRow = ref<Nullable<NewsTableData>>(null)
-
-const getTableDet = async () => {
-  const res = await getTableDetApi(query.id as string)
-  if (res) {
-    currentRow.value = res.data
-  }
-}
-
-getTableDet()
-
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const loading = ref(false)
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    loading.value = true
-    const res = await updateTableApi(formData)
-      .catch(() => {})
-      .finally(() => {
-        loading.value = false
-      })
-    if (res) {
-      emitter.emit('getList', 'editor')
-      push('/manage/news-page')
-    }
-  }
-}
-</script>
-
-<template>
-  <ContentDetailWrap :title="t('exampleDemo.edit')" @back="push('/manage/news-page')">
-    <Write ref="writeRef" :current-row="currentRow" />
-
-    <template #header>
-      <ElButton @click="go(-1)">
-        {{ t('common.back') }}
-      </ElButton>
-      <ElButton type="primary" :loading="loading" @click="save">
-        {{ t('exampleDemo.save') }}
-      </ElButton>
-    </template>
-  </ContentDetailWrap>
-</template>
-@/hooks/event/useEmitt

+ 0 - 346
src/views/Manage/News/NewsPage.vue

@@ -1,346 +0,0 @@
-<script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Search } from '@/components/Search'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElButtonGroup, ElTooltip } from 'element-plus'
-import { Table } from '@/components/Table'
-import { getTableListApi, delTableListApi } from '@/api/manage/news'
-import { useTable } from '@/hooks/web/useTable'
-import { NewsTableData } from '@/api/manage/types'
-import { ref, unref } from 'vue'
-import { useRouter } from 'vue-router'
-import { useEmitt } from '@/hooks/event/useEmitt'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { TableSetting } from '@/components/TableSetting'
-import { usePageStore } from '@/store/modules/page'
-import { set } from 'lodash-es'
-import { useIcon } from '@/hooks/web/useIcon'
-const EditIcon = useIcon({ icon: 'ep:edit' })
-const DetailIcon = useIcon({ icon: 'ep:document' })
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
-defineOptions({
-  name: 'NewsPage'
-})
-
-const { push } = useRouter()
-
-const id = ref<string>('')
-
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  searchParams.value = params
-  getList()
-}
-
-const { tableRegister, tableState, tableMethods } = useTable({
-  fetchDataApi: async () => {
-    const { currentPage, pageSize } = tableState
-    const res = await getTableListApi({
-      pageNum: unref(currentPage),
-      pageSize: unref(pageSize),
-      ...unref(searchParams)
-    })
-    return {
-      list: res.data.list,
-      total: Number(res.data.totalCount)
-    }
-  },
-  fetchDelApi: async () => {
-    const res = await delTableListApi(unref(id))
-    return !!res
-  }
-})
-const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, delList, setColumn } = tableMethods
-
-getList()
-
-useEmitt({
-  name: 'getList',
-  callback: (type: string) => {
-    if (type === 'add') {
-      currentPage.value = 1
-    }
-    getList()
-  }
-})
-
-const { t } = useI18n()
-const appStore = usePageStore()
-const crudSchemas: CrudSchema[] = [
-  {
-    field: 'articleTitle',
-    label: '文章标题',
-    minWidth: 150,
-    search: {
-      component: 'Input'
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      component: 'Input',
-      colProps: {
-        span: 24
-      }
-    },
-    detail: {
-      span: 24
-    }
-  },
-  {
-    field: 'categoryId',
-    label: '文章类目',
-    width: 120,
-    table: {
-      hidden: false,
-      slots: {
-        default: (data: any) => {
-          let arr = [
-            {
-              label: '新闻资讯',
-              value: '0'
-            },
-            {
-              label: '经典案例',
-              value: '1'
-            },
-            {
-              label: '解决方案',
-              value: '2'
-            },
-            {
-              label: '技术支持',
-              value: '3'
-            }
-          ]
-          let item = arr.find((e) => {
-            return e.value === data.row.categoryId
-          })
-          return item ? item.label : ''
-        }
-      }
-    },
-    search: {
-      component: 'Select',
-      componentProps: {
-        options: [
-          {
-            label: '新闻资讯',
-            value: '0'
-          },
-          {
-            label: '经典案例',
-            value: '1'
-          },
-          {
-            label: '解决方案',
-            value: '2'
-          },
-          {
-            label: '技术支持',
-            value: '3'
-          }
-        ]
-      }
-    }
-  },
-  {
-    field: 'introduce',
-    label: '文章简介',
-    minWidth: 180,
-    form: {
-      component: 'Input',
-      componentProps: {
-        type: 'textarea'
-      },
-      colProps: {
-        span: 24
-      }
-    },
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    }
-  },
-  {
-    field: 'weight',
-    label: '权重',
-    width: 100,
-    headerAlign: 'center',
-    align: 'right',
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      component: 'InputNumber',
-      value: 0
-    }
-  },
-  {
-    field: 'articleViews',
-    label: '当前浏览量',
-    width: 100,
-    headerAlign: 'center',
-    align: 'right',
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      component: 'InputNumber',
-      value: 0
-    }
-  },
-  {
-    field: 'articleContent',
-    label: '文章内容',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: true
-    },
-    form: {
-      component: 'Editor',
-      colProps: {
-        span: 24
-      }
-    },
-    detail: {
-      span: 24,
-      slots: {
-        default: (data: any) => {
-          return <div innerHTML={data.content}></div>
-        }
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    minWidth: 120,
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    }
-  },
-  {
-    field: 'action',
-    width: '160px',
-    fixed: 'right',
-    label: t('tableDemo.action'),
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      hidden: false,
-      slots: {
-        default: (data: any) => {
-          return (
-            <ElButtonGroup>
-              <ElTooltip content="编辑">
-                <ElButton text icon={EditIcon} onClick={() => action(data.row, 'edit')} />
-              </ElTooltip>
-              <ElTooltip content="详情">
-                <ElButton text icon={DetailIcon} onClick={() => action(data.row, 'detail')} />
-              </ElTooltip>
-              <ElTooltip content="删除">
-                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
-              </ElTooltip>
-            </ElButtonGroup>
-          )
-        }
-      }
-    }
-  }
-]
-
-// @ts-ignore
-const getSchemas = () => {
-  let localSchemas = appStore.getPageData['NewsPage']
-  if (localSchemas && localSchemas.schemas) {
-    let localSchemasArr = localSchemas.schemas
-    for (let i = 0; i < localSchemasArr.length; i++) {
-      let item = localSchemasArr[i]
-      let index = crudSchemas.findIndex((e) => {
-        return e.field == item.field
-      })
-      if (index > 0) {
-        set(crudSchemas[index], 'table.hidden', item.table.hidden)
-      }
-    }
-  }
-}
-getSchemas()
-let allSchemas = useCrudSchemas(crudSchemas).allSchemas
-// 修改列设置后调用
-const setSchemas = (schemas: CrudSchema[]) => {
-  let arr = schemas.map((item) => {
-    return {
-      field: item.field,
-      path: 'hidden',
-      value: item.table ? item.table.hidden : false
-    }
-  })
-  setColumn(arr)
-}
-
-const AddAction = () => {
-  push('/manage/news-add')
-}
-
-const delLoading = ref(false)
-
-const delData = async (row: NewsTableData) => {
-  id.value = row?.id || ''
-  delLoading.value = true
-  await delList(unref(id).length).finally(() => {
-    delLoading.value = false
-  })
-}
-
-const action = (row: NewsTableData, type: string) => {
-  push(`/manage/news-${type}?id=${row.id}`)
-}
-</script>
-
-<template>
-  <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
-
-    <div class="mb-10px">
-      <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
-      <TableSetting page="NewsPage" :data="crudSchemas" @set-schemas="setSchemas" />
-    </div>
-
-    <Table
-      v-model:pageSize="pageSize"
-      v-model:currentPage="currentPage"
-      :columns="allSchemas.tableColumns"
-      :data="dataList"
-      :loading="loading"
-      :pagination="{
-        total: total
-      }"
-      @register="tableRegister"
-    />
-  </ContentWrap>
-</template>
-@/hooks/event/useEmitt

+ 0 - 65
src/views/Manage/News/components/Detail.vue

@@ -1,65 +0,0 @@
-<script setup lang="tsx">
-import { PropType, reactive } from 'vue'
-import type { NewsTableData } from '@/api/manage/types'
-import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ElTag } from 'element-plus'
-
-const { t } = useI18n()
-
-defineProps({
-  currentRow: {
-    type: Object as PropType<Nullable<NewsTableData>>,
-    default: () => null
-  }
-})
-
-const schema = reactive<DescriptionsSchema[]>([
-  {
-    field: 'articleTitle',
-    label: '文章标题',
-    span: 24
-  },
-  {
-    field: 'introduce',
-    label: '文章简介',
-    span: 24
-  },
-  {
-    field: 'weight',
-    label: '权重'
-  },
-  {
-    field: 'articleViews',
-    label: '当前浏览量'
-  },
-  {
-    field: 'articlePic',
-    label: '文章展图',
-    slots: {
-      default: (data: any) => {
-        return <img style="width:100%" src={data.articlePic}></img>
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    span: 24
-  },
-  {
-    field: 'articleContent',
-    label: '文章内容',
-    span: 24,
-    slots: {
-      default: (data: any) => {
-        return <div innerHTML={data.articleContent}></div>
-      }
-    }
-  }
-])
-</script>
-
-<template>
-  <Descriptions :schema="schema" :data="currentRow || {}" />
-</template>

+ 0 - 296
src/views/Manage/News/components/Write.vue

@@ -1,296 +0,0 @@
-<script setup lang="tsx">
-import { Form, FormSchema } from '@/components/Form'
-import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch, ref } from 'vue'
-import { NewsTableData } from '@/api/manage/types'
-import { useValidator } from '@/hooks/web/useValidator'
-import { IDomEditor } from '@wangeditor/editor'
-import { useStorage } from '@/hooks/web/useStorage'
-import { Icon } from '@/components/Icon'
-import { uploadFile } from '@/api/common'
-const { required } = useValidator()
-const { getStorage } = useStorage()
-const props = defineProps({
-  currentRow: {
-    type: Object as PropType<Nullable<NewsTableData>>,
-    default: () => null
-  }
-})
-
-const articlePic = ref(props.currentRow?.articlePic)
-
-const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
-type InsertFnType = (url: string, alt: string, href: string) => void
-const schema = reactive<FormSchema[]>([
-  {
-    field: 'articleTitle',
-    label: '文章标题',
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    },
-    colProps: {
-      span: 24
-    }
-  },
-  {
-    field: 'introduce',
-    label: '文章简介',
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    },
-    colProps: {
-      span: 24
-    },
-    componentProps: {
-      type: 'textarea',
-      placeholder: '请输入'
-    }
-  },
-  {
-    field: 'categoryId',
-    label: '文章类目',
-    component: 'Select',
-    colProps: {
-      span: 8
-    },
-    componentProps: {
-      options: [
-        {
-          label: '新闻资讯',
-          value: '0'
-        },
-        {
-          label: '经典案例',
-          value: '1'
-        },
-        {
-          label: '解决方案',
-          value: '2'
-        },
-        {
-          label: '技术支持',
-          value: '3'
-        }
-      ]
-    }
-  },
-  {
-    field: 'weight',
-    label: '权重',
-    component: 'InputNumber',
-    value: 0,
-    colProps: {
-      span: 8
-    },
-    formItemProps: {
-      rules: [required()]
-    }
-  },
-  {
-    field: 'articleViews',
-    label: '当前浏览量',
-    component: 'InputNumber',
-    value: 0,
-    colProps: {
-      span: 8
-    },
-    formItemProps: {
-      rules: [required()]
-    }
-  },
-  {
-    field: 'articlePic',
-    component: 'Upload',
-    label: '文章展图',
-    componentProps: {
-      httpRequest: (data: any) => {
-        let file = data.file
-        let formData = new FormData()
-        formData.append('file', file)
-        uploadFile(formData).then((response) => {
-          setValues({
-            articlePic: response.data.virtualPath
-          })
-          articlePic.value = response.data.virtualPath
-        })
-      },
-      showFileList: false,
-      class: 'NewsUploader',
-      headers: {
-        token: getStorage('token')
-      },
-      onSuccess: (response) => {
-        setValues({
-          articlePic: response.data.virtualPath
-        })
-        articlePic.value = response.data.virtualPath
-      },
-      slots: {
-        default: () => (
-          <>
-            {articlePic.value ? <img src={articlePic.value} class="avatar" /> : null}
-            {!articlePic.value ? (
-              <Icon class="avatar-uploader-icon" icon="ep:plus" size={28}></Icon>
-            ) : null}
-          </>
-        )
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    component: 'Input',
-    colProps: {
-      span: 24
-    },
-    componentProps: {
-      type: 'textarea',
-      placeholder: '请输入'
-    }
-  },
-  {
-    field: 'articleContent',
-    label: '文章内容',
-    component: 'Editor',
-    colProps: {
-      span: 24
-    },
-    componentProps: {
-      defaultHtml: '',
-      editorConfig: {
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'file',
-            headers: {
-              token: getStorage('token')
-            },
-            async customUpload(file: File, insertFn: InsertFnType) {
-              // 定义表单数据
-              let formData = new FormData()
-              formData.append('file', file)
-              // 上传文件
-              uploadFile(formData).then((res) => {
-                // 调用 insertFn 方法插入图片
-                insertFn(res.data.virtualPath, res.data.fileName, '')
-              })
-            },
-            customInsert(res: any, insertFn: InsertFnType) {
-              // 从 res 中找到 url alt href ,然后插入图片
-              insertFn(res.data.virtualPath, res.fileName, '')
-            }
-          },
-          uploadVideo: {
-            fieldName: 'file',
-            headers: {
-              token: getStorage('token')
-            },
-            async customUpload(file: File, insertFn: InsertFnType) {
-              let formData = new FormData()
-              formData.append('file', file)
-              uploadFile(formData).then((res) => {
-                // 将视频上传成功后的路径和文件名插入到指定位置
-                insertFn(res.data.virtualPath, res.data.fileName, '')
-              })
-            },
-            customInsert(res: any, insertFn: InsertFnType) {
-              // 从 res 中找到 url alt href ,然后插入视频
-              insertFn(res.data.virtualPath, res.fileName, '')
-            }
-          }
-        }
-      },
-      // @ts-ignore
-      onChange: (edit: IDomEditor) => {
-        setValues({
-          articleContent: edit.getHtml()
-        })
-      }
-    }
-  }
-])
-
-const rules = reactive({})
-
-// 定义一个submit函数,用于提交表单
-const submit = async () => {
-  // 获取表单元素
-  const elForm = await getElFormExpose()
-  // 调用表单元素的validate方法,获取表单验证结果
-  const valid = await elForm?.validate().catch((err) => {
-    // 如果表单验证失败,输出错误信息
-    console.log(err)
-  })
-  // 如果表单验证成功,获取表单数据
-  if (valid) {
-    const formData = await getFormData()
-    // 返回表单数据
-    return formData
-  }
-}
-
-watch(
-  () => props.currentRow,
-  (currentRow) => {
-    // 如果当前行为空,则返回
-    if (!currentRow) return
-    // 设置值
-    setValues(currentRow)
-    // 设置模式
-    setSchema([
-      {
-        field: 'articleContent',
-        path: 'componentProps.defaultHtml',
-        value: currentRow.articleContent
-      }
-    ])
-    // 设置图片
-    articlePic.value = currentRow.articlePic
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-defineExpose({
-  submit
-})
-</script>
-
-<template>
-  <Form :rules="rules" @register="formRegister" :schema="schema" />
-</template>
-
-<style scoped>
-.NewsUploader .avatar {
-  width: 178px;
-  height: 178px;
-  display: block;
-}
-</style>
-
-<style lang="less">
-/**添加图片上传组件样式 */
-.NewsUploader .el-upload {
-  border: 1px dashed var(--el-border-color);
-  border-radius: 6px;
-  cursor: pointer;
-  position: relative;
-  overflow: hidden;
-  transition: var(--el-transition-duration-fast);
-  img {
-    width: 178px;
-    height: 178px;
-  }
-  .avatar-uploader-icon {
-    font-size: 28px;
-    color: #8c939d;
-    width: 178px;
-    height: 178px;
-    text-align: center;
-  }
-}
-</style>

+ 0 - 53
src/views/Manage/Product/ProductAdd.vue

@@ -1,53 +0,0 @@
-<script setup lang="ts">
-import Write from './components/Write.vue'
-import { ContentDetailWrap } from '@/components/ContentDetailWrap'
-import { ref, unref } from 'vue'
-import { ElButton } from 'element-plus'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useRouter } from 'vue-router'
-import { saveTableApi } from '@/api/manage/product'
-import { useEmitt } from '@/hooks/event/useEmitt'
-
-const { emitter } = useEmitt()
-
-const { push, go } = useRouter()
-
-const { t } = useI18n()
-
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const loading = ref(false)
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    loading.value = true
-    const res = await saveTableApi(formData)
-      .catch(() => {})
-      .finally(() => {
-        loading.value = false
-      })
-    if (res) {
-      emitter.emit('getList', 'add')
-      push('/manage/product-page')
-    }
-  }
-}
-</script>
-
-<template>
-  <ContentDetailWrap :title="t('exampleDemo.add')" @back="push('/manage/product-page')">
-    <Write ref="writeRef" />
-
-    <template #header>
-      <ElButton @click="go(-1)">
-        {{ t('common.back') }}
-      </ElButton>
-      <ElButton type="primary" :loading="loading" @click="save">
-        {{ t('exampleDemo.save') }}
-      </ElButton>
-    </template>
-  </ContentDetailWrap>
-</template>
-@/hooks/event/useEmitt

+ 0 - 38
src/views/Manage/Product/ProductDetail.vue

@@ -1,38 +0,0 @@
-<script setup lang="ts">
-import Detail from './components/Detail.vue'
-import { ContentDetailWrap } from '@/components/ContentDetailWrap'
-import { ref } from 'vue'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useRouter, useRoute } from 'vue-router'
-import { getTableDetApi } from '@/api/manage/product'
-import { ProductTableData } from '@/api/manage/types'
-import { ElButton } from 'element-plus'
-
-const { push, go } = useRouter()
-
-const { query } = useRoute()
-
-const { t } = useI18n()
-
-const currentRow = ref<Nullable<ProductTableData>>(null)
-
-const getTableDet = async () => {
-  const res = await getTableDetApi(query.id as string)
-  if (res) {
-    currentRow.value = res.data
-  }
-}
-
-getTableDet()
-</script>
-
-<template>
-  <ContentDetailWrap :title="t('exampleDemo.detail')" @back="push('/manage/product-page')">
-    <template #header>
-      <ElButton @click="go(-1)">
-        {{ t('common.back') }}
-      </ElButton>
-    </template>
-    <Detail :current-row="currentRow" />
-  </ContentDetailWrap>
-</template>

+ 0 - 67
src/views/Manage/Product/ProductEdit.vue

@@ -1,67 +0,0 @@
-<script setup lang="ts">
-import Write from './components/Write.vue'
-import { ContentDetailWrap } from '@/components/ContentDetailWrap'
-import { ref, unref } from 'vue'
-import { ElButton } from 'element-plus'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useRouter, useRoute } from 'vue-router'
-import { updateTableApi, getTableDetApi } from '@/api/manage/product'
-import { ProductTableData } from '@/api/manage/types'
-import { useEmitt } from '@/hooks/event/useEmitt'
-
-const { emitter } = useEmitt()
-
-const { push, go } = useRouter()
-
-const { query } = useRoute()
-
-const { t } = useI18n()
-
-const currentRow = ref<Nullable<ProductTableData>>(null)
-
-const getTableDet = async () => {
-  const res = await getTableDetApi(query.id as string)
-  if (res) {
-    currentRow.value = res.data
-  }
-}
-
-getTableDet()
-
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const loading = ref(false)
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    loading.value = true
-    const res = await updateTableApi(formData)
-      .catch(() => {})
-      .finally(() => {
-        loading.value = false
-      })
-    if (res) {
-      emitter.emit('getList', 'editor')
-      push('/manage/product-page')
-    }
-  }
-}
-</script>
-
-<template>
-  <ContentDetailWrap :title="t('exampleDemo.edit')" @back="push('/manage/product-page')">
-    <Write ref="writeRef" :current-row="currentRow" />
-
-    <template #header>
-      <ElButton @click="go(-1)">
-        {{ t('common.back') }}
-      </ElButton>
-      <ElButton type="primary" :loading="loading" @click="save">
-        {{ t('exampleDemo.save') }}
-      </ElButton>
-    </template>
-  </ContentDetailWrap>
-</template>
-@/hooks/event/useEmitt

+ 0 - 271
src/views/Manage/Product/ProductPage.vue

@@ -1,271 +0,0 @@
-<script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Search } from '@/components/Search'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElButtonGroup, ElTooltip } from 'element-plus'
-import { Table } from '@/components/Table'
-import { getTableListApi, delTableListApi } from '@/api/manage/product'
-import { useTable } from '@/hooks/web/useTable'
-import { ProductTableData } from '@/api/table/types'
-import { ref, unref } from 'vue'
-import { useRouter } from 'vue-router'
-import { useEmitt } from '@/hooks/event/useEmitt'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { TableSetting } from '@/components/TableSetting'
-import { usePageStore } from '@/store/modules/page'
-import { set } from 'lodash-es'
-import { useIcon } from '@/hooks/web/useIcon'
-
-defineOptions({
-  name: 'ProductPage'
-})
-
-const { push } = useRouter()
-
-const EditIcon = useIcon({ icon: 'ep:edit' })
-const DetailIcon = useIcon({ icon: 'ep:document' })
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
-
-const id = ref<string>('')
-
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  searchParams.value = params
-  getList()
-}
-
-const { tableRegister, tableState, tableMethods } = useTable({
-  fetchDataApi: async () => {
-    const { currentPage, pageSize } = tableState
-    const res = await getTableListApi({
-      pageNum: unref(currentPage),
-      pageSize: unref(pageSize),
-      ...unref(searchParams)
-    })
-    return {
-      list: res.data.list,
-      total: Number(res.data.totalCount)
-    }
-  },
-  fetchDelApi: async () => {
-    const res = await delTableListApi(unref(id))
-    return !!res
-  }
-})
-const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, delList, setColumn } = tableMethods
-
-getList()
-
-useEmitt({
-  name: 'getList',
-  callback: (type: string) => {
-    if (type === 'add') {
-      currentPage.value = 1
-    }
-    getList()
-  }
-})
-
-const { t } = useI18n()
-const appStore = usePageStore()
-const crudSchemas: CrudSchema[] = [
-  {
-    field: 'productName',
-    label: '产品名称',
-    search: {
-      component: 'Input'
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      component: 'Input',
-      colProps: {
-        span: 24
-      }
-    },
-    detail: {
-      span: 24
-    }
-  },
-  {
-    field: 'productIntroduction',
-    label: '产品简介',
-    form: {
-      component: 'Input',
-      componentProps: {
-        type: 'textarea'
-      },
-      colProps: {
-        span: 24
-      }
-    },
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    }
-  },
-  {
-    field: 'productPrice',
-    label: '产品价格',
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      component: 'InputNumber',
-      value: 0
-    }
-  },
-  {
-    field: 'productStock',
-    label: '产品库存',
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      component: 'InputNumber',
-      value: 0
-    }
-  },
-  {
-    field: 'productDetailed',
-    label: '产品详情',
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: true
-    },
-    form: {
-      component: 'Editor',
-      colProps: {
-        span: 24
-      }
-    },
-    detail: {
-      span: 24,
-      slots: {
-        default: (data: any) => {
-          return <div innerHTML={data.content}></div>
-        }
-      }
-    }
-  },
-  {
-    field: 'action',
-    width: '160px',
-    label: t('tableDemo.action'),
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      hidden: false,
-      slots: {
-        default: (data: any) => {
-          return (
-            <ElButtonGroup>
-              <ElTooltip content="编辑">
-                <ElButton text icon={EditIcon} onClick={() => action(data.row, 'edit')} />
-              </ElTooltip>
-              <ElTooltip content="详情">
-                <ElButton text icon={DetailIcon} onClick={() => action(data.row, 'detail')} />
-              </ElTooltip>
-              <ElTooltip content="删除">
-                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
-              </ElTooltip>
-            </ElButtonGroup>
-          )
-        }
-      }
-    }
-  }
-]
-
-// @ts-ignore
-const getSchemas = () => {
-  let localSchemas = appStore.getPageData['ProductPage']
-  if (localSchemas && localSchemas.schemas) {
-    let localSchemasArr = localSchemas.schemas
-    for (let i = 0; i < localSchemasArr.length; i++) {
-      let item = localSchemasArr[i]
-      let index = crudSchemas.findIndex((e) => {
-        return e.field == item.field
-      })
-      if (index > 0) {
-        set(crudSchemas[index], 'table.hidden', item.table.hidden)
-      }
-    }
-  }
-}
-getSchemas()
-let allSchemas = useCrudSchemas(crudSchemas).allSchemas
-// 修改列设置后调用
-const setSchemas = (schemas: CrudSchema[]) => {
-  let arr = schemas.map((item) => {
-    return {
-      field: item.field,
-      path: 'hidden',
-      value: item.table ? item.table.hidden : false
-    }
-  })
-  setColumn(arr)
-}
-
-const AddAction = () => {
-  push('/manage/product-add')
-}
-
-const delLoading = ref(false)
-
-const delData = async (row: ProductTableData) => {
-  if (!row.id) return
-  id.value = row?.id
-  delLoading.value = true
-  await delList(unref(id).length).finally(() => {
-    delLoading.value = false
-  })
-}
-
-const action = (row: ProductTableData, type: string) => {
-  push(`/manage/product-${type}?id=${row.id}`)
-}
-</script>
-
-<template>
-  <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
-
-    <div class="mb-10px">
-      <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
-      <TableSetting page="ProductPage" :data="crudSchemas" @set-schemas="setSchemas" />
-    </div>
-
-    <Table
-      v-model:pageSize="pageSize"
-      v-model:currentPage="currentPage"
-      :columns="allSchemas.tableColumns"
-      :data="dataList"
-      :loading="loading"
-      :pagination="{
-        total: total
-      }"
-      @register="tableRegister"
-    />
-  </ContentWrap>
-</template>
-@/hooks/event/useEmitt

+ 0 - 71
src/views/Manage/Product/components/Detail.vue

@@ -1,71 +0,0 @@
-<script setup lang="tsx">
-import { PropType, reactive } from 'vue'
-import type { ProductTableData } from '@/api/manage/types'
-import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
-
-defineProps({
-  currentRow: {
-    type: Object as PropType<Nullable<ProductTableData>>,
-    default: () => null
-  }
-})
-
-const schema = reactive<DescriptionsSchema[]>([
-  {
-    field: 'productName',
-    label: '产品名称',
-    span: 24
-  },
-  {
-    field: 'productIntroduction',
-    label: '产品简介',
-
-    span: 24
-  },
-  {
-    field: 'productPrice',
-    label: '产品价格'
-  },
-  {
-    field: 'productStock',
-    label: '库存'
-  },
-  {
-    field: 'productSpecifications',
-    label: '产品规格'
-  },
-  {
-    field: 'productCharacteristic',
-    label: '产品特点'
-  },
-  {
-    field: 'productPic',
-    label: '产品图片',
-    slots: {
-      default: (data: any) => {
-        return <img style="width:100%" src={data.productPic}></img>
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    span: 24
-  },
-
-  {
-    field: 'productDetailed',
-    label: '产品详情',
-    span: 24,
-    slots: {
-      default: (data: any) => {
-        return <div innerHTML={data.productDetailed}></div>
-      }
-    }
-  }
-])
-</script>
-
-<template>
-  <Descriptions :schema="schema" :data="currentRow || {}" />
-</template>

+ 0 - 261
src/views/Manage/Product/components/Write.vue

@@ -1,261 +0,0 @@
-<script setup lang="tsx">
-import { Form, FormSchema } from '@/components/Form'
-import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch, ref } from 'vue'
-import { ProductTableData } from '@/api/manage/types'
-import { useValidator } from '@/hooks/web/useValidator'
-import { IDomEditor } from '@wangeditor/editor'
-import { useStorage } from '@/hooks/web/useStorage'
-import { Icon } from '@/components/Icon'
-import { uploadFile } from '@/api/common'
-const { required } = useValidator()
-const { getStorage } = useStorage()
-const props = defineProps({
-  currentRow: {
-    type: Object as PropType<Nullable<ProductTableData>>,
-    default: () => null
-  }
-})
-const productPic = ref(props.currentRow?.productPic)
-
-const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
-
-type InsertFnType = (url: string, alt: string, href: string) => void
-
-const schema = reactive<FormSchema[]>([
-  {
-    field: 'productName',
-    label: '产品名称',
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    },
-    colProps: {
-      span: 24
-    }
-  },
-  {
-    field: 'productIntroduction',
-    label: '产品简介',
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    },
-    colProps: {
-      span: 24
-    },
-    componentProps: {
-      type: 'textarea',
-      placeholder: '请输入'
-    }
-  },
-  {
-    field: 'productPrice',
-    label: '产品价格',
-    component: 'InputNumber',
-    value: 0,
-    formItemProps: {
-      rules: [required()]
-    }
-  },
-  {
-    field: 'productStock',
-    label: '库存',
-    component: 'InputNumber',
-    value: 0,
-    formItemProps: {
-      rules: [required()]
-    }
-  },
-  {
-    field: 'productSpecifications',
-    label: '产品规格',
-    component: 'Input'
-  },
-  {
-    field: 'productCharacteristic',
-    label: '产品特点',
-    component: 'Input'
-  },
-  {
-    field: 'productPic',
-    component: 'Upload',
-    label: '产品图片',
-    componentProps: {
-      httpRequest: (data: any) => {
-        let file = data.file
-        let formData = new FormData()
-        formData.append('file', file)
-        uploadFile(formData).then((response) => {
-          setValues({
-            productPic: response.data.virtualPath
-          })
-          productPic.value = response.data.virtualPath
-        })
-      },
-      showFileList: false,
-      class: 'productUploader',
-      headers: {
-        token: getStorage('token')
-      },
-      onSuccess: (response) => {
-        setValues({
-          productPic: response.data.virtualPath
-        })
-        productPic.value = response.data.virtualPath
-      },
-      slots: {
-        default: () => (
-          <>
-            {productPic.value ? <img src={productPic.value} class="avatar" /> : null}
-            {!productPic.value ? (
-              <Icon class="avatar-uploader-icon" icon="ep:plus" size={28}></Icon>
-            ) : null}
-          </>
-        )
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    component: 'Input',
-    colProps: {
-      span: 24
-    },
-    componentProps: {
-      type: 'textarea',
-      placeholder: '请输入'
-    }
-  },
-  {
-    field: 'productDetailed',
-    component: 'Editor',
-    colProps: {
-      span: 24
-    },
-    componentProps: {
-      defaultHtml: '',
-      editorConfig: {
-        MENU_CONF: {
-          uploadImage: {
-            async customUpload(file: File, insertFn: InsertFnType) {
-              let formData = new FormData()
-              formData.append('file', file)
-              uploadFile(formData).then((res) => {
-                insertFn(res.data.virtualPath, res.data.fileName, '')
-              })
-            },
-            fieldName: 'file',
-            headers: {
-              token: getStorage('token')
-            },
-            customInsert(res: any, insertFn: InsertFnType) {
-              // 从 res 中找到 url alt href ,然后插入图片
-              insertFn(res.data.virtualPath, res.fileName, '')
-            }
-          },
-          uploadVideo: {
-            async customUpload(file: File, insertFn: InsertFnType) {
-              let formData = new FormData()
-              formData.append('file', file)
-              uploadFile(formData).then((res) => {
-                insertFn(res.data.virtualPath, res.data.fileName, '')
-              })
-            },
-            fieldName: 'file',
-            headers: {
-              token: getStorage('token')
-            },
-            customInsert(res: any, insertFn: InsertFnType) {
-              // 从 res 中找到 url alt href ,然后插入视频
-              insertFn(res.data.virtualPath, res.fileName, '')
-            }
-          }
-        }
-      },
-      // @ts-ignore
-      onChange: (edit: IDomEditor) => {
-        setValues({
-          productDetailed: edit.getHtml()
-        })
-      }
-    },
-    label: '产品详情'
-  }
-])
-
-const rules = reactive({
-  productName: [required()]
-})
-
-const submit = async () => {
-  const elForm = await getElFormExpose()
-  const valid = await elForm?.validate().catch((err) => {
-    console.log(err)
-  })
-  if (valid) {
-    const formData = await getFormData()
-    return formData
-  }
-}
-
-watch(
-  () => props.currentRow,
-  (currentRow) => {
-    if (!currentRow) return
-    setValues(currentRow)
-    setSchema([
-      {
-        field: 'productDetailed',
-        path: 'componentProps.defaultHtml',
-        value: currentRow.productDetailed
-      }
-    ])
-    productPic.value = currentRow.productPic
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-
-defineExpose({
-  submit
-})
-</script>
-
-<template>
-  <Form :rules="rules" @register="formRegister" :schema="schema" />
-</template>
-
-<style scoped>
-.productUploader .avatar {
-  width: 178px;
-  height: 178px;
-  display: block;
-}
-</style>
-
-<style lang="less">
-.productUploader .el-upload {
-  border: 1px dashed var(--el-border-color);
-  border-radius: 6px;
-  cursor: pointer;
-  position: relative;
-  overflow: hidden;
-  transition: var(--el-transition-duration-fast);
-  img {
-    width: 178px;
-    height: 178px;
-  }
-  .avatar-uploader-icon {
-    font-size: 28px;
-    color: #8c939d;
-    width: 178px;
-    height: 178px;
-    text-align: center;
-  }
-}
-</style>

+ 26 - 0
src/views/hooks/useClipboard.vue

@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useClipboard } from '@/hooks/web/useClipboard'
+import { ElButton, ElInput } from 'element-plus'
+import { ref } from 'vue'
+
+const { copy, copied, text, isSupported } = useClipboard()
+
+const source = ref('')
+</script>
+
+<template>
+  <ContentWrap title="useClipboard">
+    <ElInput v-model="source" placeholder="请输入要复制的内容" />
+    <div v-if="isSupported">
+      <ElButton @click="copy(source)" type="primary" class="mt-20px">
+        <span v-if="!copied">复制</span>
+        <span v-else>已复制</span>
+      </ElButton>
+      <p>
+        当前已复制: <code>{{ text || 'none' }}</code>
+      </p>
+    </div>
+    <p v-else> 你的浏览器不支持 Clipboard API </p>
+  </ContentWrap>
+</template>

+ 12 - 0
src/views/hooks/useNetwork.vue

@@ -0,0 +1,12 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useNetwork } from '@/hooks/web/useNetwork'
+
+const { online } = useNetwork()
+</script>
+
+<template>
+  <ContentWrap title="useNetwork">
+    当前网络状态: <code>{{ online ? '已连接' : '已断开' }}</code>
+  </ContentWrap>
+</template>

+ 1 - 2
types/global.d.ts

@@ -45,8 +45,7 @@ declare global {
 
   declare interface IResponse<T = any> {
     code: string
-    data: T extends any ? T : T & any,
-    token: string
+    data: T extends any ? T : T & any
   }
 
   declare interface ThemeTypes {

+ 3 - 2
types/router.d.ts

@@ -57,7 +57,7 @@ type Component<T = any> =
   | (() => Promise<T>)
 
 declare global {
-  declare interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
+  declare interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta' | 'children'> {
     name: string
     meta: RouteMetaCustom
     component?: Component | string
@@ -66,7 +66,8 @@ declare global {
     fullPath?: string
   }
 
-  declare interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
+  declare interface AppCustomRouteRecordRaw
+    extends Omit<RouteRecordRaw, 'meta' | 'component' | 'children'> {
     name: string
     meta: RouteMetaCustom
     component: string

+ 14 - 14
vite.config.ts

@@ -6,7 +6,7 @@ import VueJsx from '@vitejs/plugin-vue-jsx'
 import progress from 'vite-plugin-progress'
 import EslintPlugin from 'vite-plugin-eslint'
 import { ViteEjsPlugin } from "vite-plugin-ejs"
-// import { viteMockServe } from 'vite-plugin-mock'
+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'
@@ -67,17 +67,17 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         svgoOptions: true
       }),
       PurgeIcons(),
-      // viteMockServe({
-      //   ignore: /^\_/,
-      //   mockPath: 'mock',
-      //   localEnabled: !isBuild,
-      //   prodEnabled: isBuild,
-      //   injectCode: `
-      //     import { setupProdMockServer } from '../mock/_createProductionServer'
+      viteMockServe({
+        ignore: /^\_/,
+        mockPath: 'mock',
+        localEnabled: !isBuild,
+        prodEnabled: isBuild,
+        injectCode: `
+          import { setupProdMockServer } from '../mock/_createProductionServer'
 
-      //     setupProdMockServer()
-      //     `
-      // }),
+          setupProdMockServer()
+          `
+      }),
       ViteEjsPlugin({
         title: env.VITE_APP_TITLE
       }),
@@ -122,10 +122,10 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       port: 4000,
       proxy: {
         // 选项写法
-        '^/api': {
-          target: 'http://api.dacundianzi.com/',
+        '/sys': {
+          target: 'http://192.168.0.131:9101',
           changeOrigin: true,
-          rewrite: path => path
+          rewrite: path => path.replace(/^\/api/, '')
         }
       },
       hmr: {