cmy 1 ano atrás
pai
commit
130fd72de5
45 arquivos alterados com 1349 adições e 1219 exclusões
  1. 2 2
      .env.base
  2. 1 1
      .env.dev
  3. 1 1
      .env.gitee
  4. 14 14
      mock/user/index.mock.ts
  5. 41 41
      package.json
  6. 31 25
      src/api/login/index.ts
  7. 15 6
      src/api/login/types.ts
  8. 0 5
      src/api/menu/index.ts
  9. 0 5
      src/api/role/index.ts
  10. 60 0
      src/api/system/menu/index.ts
  11. 44 0
      src/api/system/menu/types.ts
  12. 65 0
      src/api/system/role/index.ts
  13. 11 0
      src/api/system/role/types.ts
  14. BIN
      src/assets/imgs/default.jpg
  15. 0 2
      src/axios/config.ts
  16. 1 1
      src/axios/index.ts
  17. 3 1
      src/components/Dialog/src/Dialog.vue
  18. 27 11
      src/components/verifition/Verify.vue
  19. 55 49
      src/components/verifition/Verify/VerifyPoints.vue
  20. 128 112
      src/components/verifition/Verify/VerifySlide.vue
  21. 2 0
      src/components/verifition/index.ts
  22. 0 1
      src/hooks/web/useTable.ts
  23. 0 1
      src/permission.ts
  24. 11 76
      src/router/index.ts
  25. 0 1
      src/router/model/Authorization.ts
  26. 1 1
      src/store/modules/app.ts
  27. 0 6
      src/store/modules/permission.ts
  28. 1 1
      src/store/modules/user.ts
  29. 47 0
      src/utils/analysis.ts
  30. 13 12
      src/utils/routerHelper.ts
  31. 0 341
      src/views/Authorization/Department/Department.vue
  32. 0 20
      src/views/Authorization/Department/components/Detail.vue
  33. 0 59
      src/views/Authorization/Department/components/Write.vue
  34. 119 33
      src/views/Authorization/Menu/Menu.vue
  35. 2 2
      src/views/Authorization/Menu/components/AddButtonPermission.vue
  36. 50 34
      src/views/Authorization/Menu/components/Detail.vue
  37. 203 114
      src/views/Authorization/Menu/components/Write.vue
  38. 118 0
      src/views/Authorization/Menu/components/addApiUrl.vue
  39. 101 23
      src/views/Authorization/Role/Role.vue
  40. 0 106
      src/views/Authorization/Role/components/Detail.vue
  41. 38 33
      src/views/Authorization/Role/components/Write.vue
  42. 1 1
      src/views/Authorization/User/User.vue
  43. 0 1
      src/views/Login/Login.vue
  44. 137 70
      src/views/Login/components/LoginForm.vue
  45. 6 7
      vite.config.ts

+ 2 - 2
.env.base

@@ -2,7 +2,7 @@
 VITE_NODE_ENV=development
 
 # 接口前缀
-VITE_API_BASE_PATH='/adminapi'
+VITE_API_BASE_PATH=/adminapi
 
 # 打包路径
 VITE_BASE_PATH=/
@@ -14,7 +14,7 @@ VITE_APP_TITLE=ElementAdmin
 VITE_USE_ALL_ELEMENT_PLUS_STYLE=true
 
 # 是否开启mock
-VITE_USE_MOCK=true
+VITE_USE_MOCK=false
 
 # 是否使用在线图标
 VITE_USE_ONLINE_ICON=true

+ 1 - 1
.env.dev

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

+ 1 - 1
.env.gitee

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

+ 14 - 14
mock/user/index.mock.ts

@@ -3,22 +3,22 @@ import { SUCCESS_CODE } from '@/constants'
 const timeout = 1000
 
 const List: {
-  account: string
-  pwd: string
+  username: string
+  password: string
   role: string
   roleId: string
   permissions: string | string[]
 }[] = [
   {
-    account: 'admin',
-    pwd: 'admin',
+    username: 'admin',
+    password: 'admin',
     role: 'admin',
     roleId: '1',
     permissions: ['*.*.*']
   },
   {
-    account: 'test',
-    pwd: 'test',
+    username: 'test',
+    password: 'test',
     role: 'test',
     roleId: '2',
     permissions: ['example:dialog:create', 'example:dialog:delete']
@@ -31,10 +31,10 @@ export default [
     url: '/mock/user/list',
     method: 'get',
     response: ({ query }) => {
-      const { account, pageIndex, pageSize } = query
+      const { username, pageIndex, pageSize } = query
 
       const mockList = List.filter((item) => {
-        if (account && item.account.indexOf(account) < 0) return false
+        if (username && item.username.indexOf(username) < 0) return false
         return true
       })
       const pageList = mockList.filter(
@@ -52,17 +52,17 @@ export default [
   },
   // 登录接口
   {
-    url: '/adminapi/login',
+    url: '/mock/user/login',
     method: 'post',
     timeout,
     response: ({ body }) => {
       const data = body
       let hasUser = false
       for (const user of List) {
-        if (user.account === data.account && user.pwd === data.pwd) {
+        if (user.username === data.username && user.password === data.password) {
           hasUser = true
           return {
-            status: SUCCESS_CODE,
+            code: SUCCESS_CODE,
             data: user
           }
         }
@@ -77,12 +77,12 @@ export default [
   },
   // 退出接口
   {
-    url: '/adminapi/logout',
-    method: 'post',
+    url: '/mock/user/loginOut',
+    method: 'get',
     timeout,
     response: () => {
       return {
-        status: SUCCESS_CODE,
+        code: SUCCESS_CODE,
         data: null
       }
     }

+ 41 - 41
package.json

@@ -30,14 +30,15 @@
   "dependencies": {
     "@iconify/iconify": "^3.1.1",
     "@iconify/vue": "^4.1.2",
-    "@vueuse/core": "^10.11.0",
+    "@vueuse/core": "^10.11.1",
     "@wangeditor/editor": "^5.1.23",
-    "@wangeditor/editor-for-vue": "^5.1.10",
+    "@wangeditor/editor-for-vue": "^5.1.12",
     "@zxcvbn-ts/core": "^3.0.4",
     "animate.css": "^4.1.1",
-    "axios": "^1.7.2",
+    "axios": "^1.7.8",
     "cropperjs": "^1.6.2",
-    "dayjs": "^1.11.11",
+    "crypto-js": "^4.2.0",
+    "dayjs": "^1.11.13",
     "driver.js": "^1.3.1",
     "echarts": "^5.5.1",
     "echarts-wordcloud": "^2.1.0",
@@ -46,72 +47,71 @@
     "mitt": "^3.0.1",
     "monaco-editor": "^0.50.0",
     "nprogress": "^0.2.0",
-    "pinia": "^2.1.7",
-    "pinia-plugin-persistedstate": "^3.2.1",
-    "qrcode": "^1.5.3",
-    "qs": "^6.12.3",
-    "url": "^0.11.3",
-    "vue": "3.4.32",
-    "vue-draggable-plus": "^0.5.2",
+    "pinia": "^2.2.6",
+    "pinia-plugin-persistedstate": "^3.2.3",
+    "qrcode": "^1.5.4",
+    "qs": "^6.13.1",
+    "url": "^0.11.4",
+    "vue": "^3.5.13",
+    "vue-draggable-plus": "^0.5.6",
     "vue-i18n": "9.13.1",
     "vue-json-pretty": "^2.4.0",
-    "vue-router": "^4.4.0",
+    "vue-router": "^4.5.0",
     "vue-types": "^5.1.3",
-    "vue3-puzzle-vcode": "^1.1.7",
-    "xgplayer": "^3.0.18"
+    "xgplayer": "^3.0.20"
   },
   "devDependencies": {
-    "@commitlint/cli": "^19.3.0",
-    "@commitlint/config-conventional": "^19.2.2",
-    "@iconify/json": "^2.2.229",
+    "@commitlint/cli": "^19.6.0",
+    "@commitlint/config-conventional": "^19.6.0",
+    "@iconify/json": "^2.2.276",
     "@intlify/unplugin-vue-i18n": "^4.0.0",
     "@types/fs-extra": "^11.0.4",
     "@types/inquirer": "^9.0.7",
     "@types/lodash-es": "^4.17.12",
     "@types/mockjs": "^1.0.10",
-    "@types/node": "^20.14.11",
+    "@types/node": "^20.17.8",
     "@types/nprogress": "^0.2.3",
     "@types/qrcode": "^1.5.5",
-    "@types/qs": "^6.9.15",
+    "@types/qs": "^6.9.17",
     "@types/sortablejs": "^1.15.8",
-    "@typescript-eslint/eslint-plugin": "^7.16.1",
-    "@typescript-eslint/parser": "^7.16.1",
-    "@unocss/transformer-variant-group": "^0.61.5",
-    "@vitejs/plugin-legacy": "^5.4.1",
-    "@vitejs/plugin-vue": "^5.0.5",
-    "@vitejs/plugin-vue-jsx": "^4.0.0",
-    "autoprefixer": "^10.4.19",
+    "@typescript-eslint/eslint-plugin": "^7.18.0",
+    "@typescript-eslint/parser": "^7.18.0",
+    "@unocss/transformer-variant-group": "^0.61.9",
+    "@vitejs/plugin-legacy": "^5.4.3",
+    "@vitejs/plugin-vue": "^5.2.1",
+    "@vitejs/plugin-vue-jsx": "^4.1.1",
+    "autoprefixer": "^10.4.20",
     "chalk": "^5.3.0",
     "consola": "^3.2.3",
-    "eslint": "^9.7.0",
+    "eslint": "^9.15.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-define-config": "^2.1.0",
     "eslint-plugin-prettier": "^5.2.1",
-    "eslint-plugin-vue": "^9.27.0",
-    "esno": "^4.7.0",
+    "eslint-plugin-vue": "^9.31.0",
+    "esno": "^4.8.0",
     "fs-extra": "^11.2.0",
-    "husky": "^9.1.0",
-    "inquirer": "^10.0.3",
-    "less": "^4.2.0",
-    "lint-staged": "^15.2.7",
+    "husky": "^9.1.7",
+    "inquirer": "^10.2.2",
+    "less": "^4.2.1",
+    "lint-staged": "^15.2.10",
     "mockjs": "^1.1.0",
     "plop": "^4.0.1",
-    "postcss": "^8.4.39",
+    "postcss": "^8.4.49",
     "postcss-html": "^1.7.0",
     "postcss-less": "^6.0.0",
-    "prettier": "^3.3.3",
+    "prettier": "^3.4.1",
     "rimraf": "^6.0.1",
-    "rollup": "^4.18.1",
+    "rollup": "^4.27.4",
     "rollup-plugin-visualizer": "^5.12.0",
-    "stylelint": "^16.7.0",
+    "stylelint": "^16.10.0",
     "stylelint-config-html": "^1.1.0",
     "stylelint-config-recommended": "^14.0.1",
     "stylelint-config-standard": "^36.0.1",
     "stylelint-order": "^6.0.4",
-    "terser": "^5.31.3",
+    "terser": "^5.36.0",
     "typescript": "5.5.3",
-    "typescript-eslint": "^7.16.1",
-    "unocss": "^0.61.5",
+    "typescript-eslint": "^7.18.0",
+    "unocss": "^0.61.9",
     "vite": "5.3.4",
     "vite-plugin-ejs": "^1.7.0",
     "vite-plugin-eslint": "^1.8.1",
@@ -121,7 +121,7 @@
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-url-copy": "^1.1.4",
-    "vue-tsc": "^2.0.26"
+    "vue-tsc": "^2.1.10"
   },
   "packageManager": "pnpm@8.1.0",
   "engines": {

+ 31 - 25
src/api/login/index.ts

@@ -1,50 +1,56 @@
 import request from '@/axios'
-import type { UserType } from './types'
+import type { isCaptcha, UserLoginCaptchaType } from './types'
 
-interface RoleParams {
-  roleName: string
-}
 /**
  * 登录API函数
  *
  * 该函数通过POST请求向服务器发送用户登录信息,以完成用户登录过程
  * 主要作用是与后端服务器进行交互,验证用户身份
  *
- * @param data 用户登录信息,包括用户名和密码等数据结构符合UserType类型
+ * @param data 用户登录信息,包括用户名和密码等数据结构
  * @returns 返回一个Promise对象,包含服务器响应的用户数据,数据结构符合IResponse<UserType>
  */
-export const loginApi = (data: UserType): Promise<IResponse<UserType>> => {
+export const loginApi = (data: UserLoginCaptchaType): Promise<IResponse> => {
   return request.post({ url: '/login', data })
 }
+
 /**
  * 登出API,用于用户退出登录
  *
- * 此函数通过发送POST请求到'/logout'端点,实现用户退出登录的功能
- * 它不接受任何参数
+ * 此函数通过发送GET请求到'/logout'端点,实现用户退出登录的功能
+ * 它没有参数,并返回一个Promise对象,该对象解析为IResponse类型
  *
  * @returns {Promise<IResponse>} 返回一个Promise,表示异步操作的结果
- * 其中IResponse是响应数据的类型,包含了登出操作的响应信息
  */
 export const loginOutApi = (): Promise<IResponse> => {
-  return request.post({ url: '/logout' })
+  return request.get({ url: '/logout' })
 }
 
-export const getUserListApi = ({ params }: AxiosConfig) => {
-  return request.get<{
-    code: string
-    data: {
-      list: UserType[]
-      total: number
-    }
-  }>({ url: '/mock/user/list', params })
+/**
+ * 判断该用户登陆是否需要验证码
+ *
+ *
+ * @param data 用户账号用于
+ * @returns 返回一个Promise对象,包含服务器响应的结果
+ */
+export const isCaptchaImg = (data: isCaptcha): Promise<IResponse> => {
+  return request.post({ url: '/is_captcha', data })
 }
-
-export const getAdminRoleApi = (
-  params: RoleParams
-): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
-  return request.get({ url: '/mock/role/list', params })
+// 验证码结果验证
+export const ajCaptchaCheck = (data: isCaptcha): Promise<IResponse> => {
+  return request.post({ url: '/ajcheck', data })
+}
+export const ajCaptcha = (params: any): Promise<IResponse> => {
+  return request.get({ url: '/ajcaptcha', params })
 }
 
-export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
-  return request.get({ url: '/mock/role/list2', params })
+/**
+ * 获取管理员信息
+ *
+ * 本函数通过发送GET请求到'/info'端点,异步获取管理员信息
+ * 不需要任何参数
+ * 返回一个Promise对象,解析为IResponse类型,包含管理员信息
+ */
+export const getAdminInfo = (): Promise<IResponse> => {
+  return request.get({ url: '/info' })
 }

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

@@ -1,13 +1,22 @@
 export interface UserLoginType {
-  account: string
-  pwd: string
-  captchaType?: string
-  captchaVerification?: string
+  username: string
+  password: string
 }
 
 export interface UserType {
-  account: string
-  pwd: string
+  username: string
+  password: string
   role: string
   roleId: string
 }
+
+export interface isCaptcha {
+  account: string
+}
+
+export interface UserLoginCaptchaType {
+  account: string
+  pwd: string
+  captchaType?: string
+  captchaVerification?: string
+}

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

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

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

@@ -1,5 +0,0 @@
-import request from '@/axios'
-
-export const getRoleListApi = () => {
-  return request.get({ url: '/mock/role/table' })
-}

+ 60 - 0
src/api/system/menu/index.ts

@@ -0,0 +1,60 @@
+import request from '@/axios'
+
+/**
+ * 获取菜单列表的API接口
+ *
+ * 该函数通过发送GET请求到'/menus'端点,获取系统中的菜单列表
+ * 不需要任何参数,也不产生任何副作用
+ *
+ * @returns {Promise<any>} 返回一个Promise对象,解析为菜单列表的数据
+ */
+export const getMenuListApi = (params: any) => {
+  return request.get({ url: '/menus', params })
+}
+
+/**
+ * 创建新的菜单项
+ *
+ * 该函数通过发送POST请求到'/menus'端点,用于在服务器上创建一个新的菜单项
+ * 它接受一个数据对象作为参数,该对象包含要创建的菜单项的相关信息
+ *
+ * @param data 要创建的菜单项的数据对象,包含菜单项的详细信息
+ * @returns 返回一个Promise对象,表示菜单项创建的异步操作的结果
+ */
+export const newMenuApi = (data) => {
+  return request.post({ url: '/menus', data })
+}
+
+/**
+ * 更新菜单项的API
+ * @param {Object} data - 包含菜单项ID和其它需要更新的信息的对象
+ * @returns {Promise} - 返回一个Promise对象,表示异步操作
+ *
+ * 此函数通过发送PUT请求来更新服务器上的菜单项
+ * URL中包含菜单项的ID,以确保更新的是正确的菜单项
+ */
+export const setMenuApi = (data) => {
+  return request.put({ url: `/menus/${data.id}`, data })
+}
+
+/**
+ * 删除菜单项的API
+ * @param {Object} data - 包含菜单项ID和其它需要删除的信息的对象
+ * @returns {Promise} - 返回一个Promise对象,表示异步操作
+ *
+ * 此函数通过发送DELETE请求来更新服务器上的菜单项
+ * URL中包含菜单项的ID,以确保删除的是正确的菜单项
+ */
+export const delMenuApi = (data: { id: number }) => {
+  return request.delete({ url: `/menus/${data.id}` })
+}
+/**
+ * 获取未添加权限接口列表
+ *
+ * 通过发送GET请求到'/menus/ruleList'来获取规则列表
+ * 此函数不需要任何参数
+ * 返回一个Promise对象,未添加权限接口列表
+ */
+export const apiRuleList = () => {
+  return request.get({ url: '/menus/ruleList' })
+}

+ 44 - 0
src/api/system/menu/types.ts

@@ -0,0 +1,44 @@
+export type MenuData = {
+  id?: number
+  path?: string
+  status: number
+  unique_auth?: string
+  parentId?: number
+  type?: number
+  component?: string
+  apiUrl?: string
+  methods?: string
+  sort?: number
+  name?: string
+  title?: string
+  meta?: {
+    icon?: string
+    title: string
+    activeMenu?: boolean
+    hidden?: boolean
+    alwaysShow?: boolean
+    noCache?: boolean
+    breadcrumb?: boolean
+    affix?: boolean
+    noTagsView?: boolean
+    canTo?: boolean
+    permissionList?: object
+  }
+  children?: object
+}
+export type addRoleData = {
+  id?: number | string
+  menu_name: string //名称
+  icon?: string //图标
+  params?: string //参数
+  menu_path?: string //路由地址
+  api_url?: string //接口地址
+  methods?: string //请求方式 post get delete
+  unique_auth: string //唯一标识
+  pid?: number | string //父id
+  sort?: number | string //排序
+  auth_type: number | string //权限类型 1 菜单,2功能
+  is_show: number | string //是否禁用1启用0禁用
+  is_show_path?: number | string //是否显示路由0显示1隐藏
+  extend?: string | object
+}

+ 65 - 0
src/api/system/role/index.ts

@@ -0,0 +1,65 @@
+import request from '@/axios'
+import type { roleRequestData, addRole } from './types'
+
+/**
+ * 获取角色列表的API接口
+ *
+ * 该函数通过发送GET请求到'/roles'端点,来获取系统中的角色列表
+ * 不需要任何参数,也不需要用户身份验证
+ *
+ * @returns {Promise<IResponse>} 返回一个Promise对象,解析为角色列表的数据
+ */
+export const getRoleListApi = (params: roleRequestData): Promise<IResponse> => {
+  return request.get({ url: '/roles', params })
+}
+
+/**
+ * 添加角色的API请求函数
+ *
+ * 此函数通过发送POST请求到'/roles'端点,来添加一个新的角色
+ * 它要求一个符合`addRole`接口的对象作为参数,该对象包含了需要添加的角色的信息
+ * 函数返回一个Promise对象,该对象解析为一个包含服务器响应的`IResponse`对象
+ *
+ * @param data 角色信息,符合`addRole`接口的要求
+ * @returns 返回一个Promise对象,解析为`IResponse`接口的实例
+ */
+export const addRoleApi = (data: addRole): Promise<IResponse> => {
+  return request.post({ url: '/roles', data })
+}
+
+/**
+ * 获取角色信息的API函数
+ *
+ * @param data - 包含角色ID的对象,用于指定要获取的角色
+ * @returns 返回一个Promise,解析为IResponse类型,包含角色信息的响应数据
+ */
+export const getRoleApi = (data: { id: string | number }): Promise<IResponse> => {
+  // 发起GET请求到指定的角色URL,以获取角色信息
+  return request.get({ url: `/roles/${data.id}` })
+}
+
+/**
+ * 更新角色信息的API请求函数
+ *
+ * @param data 包含角色信息的对象,其中应包括角色ID和其他相关信息
+ * @returns 返回一个Promise,解析为IResponse类型,包含响应数据
+ *
+ * 此函数通过发送PUT请求来更新服务器上的角色信息请求的URL基于提供的角色ID构建
+ * 使用PUT方法是因为它适合于更新资源信息
+ */
+export const setRoleApi = (data: any): Promise<IResponse> => {
+  return request.put({ url: `/roles/${data.id}`, data })
+}
+
+/**
+ * 删除角色的API请求函数
+ *
+ * @param data 包含角色ID的对象,用于指定要删除的角色
+ * @returns 返回一个Promise,解析为IResponse类型,包含响应数据
+ *
+ * 此函数通过发送DELETE请求来删除服务器上的角色
+ * 请求的URL基于提供的角色ID构建
+ */
+export const delRoleApi = (data: { id: string | number }): Promise<IResponse> => {
+  return request.delete({ url: `/roles/${data.id}` })
+}

+ 11 - 0
src/api/system/role/types.ts

@@ -0,0 +1,11 @@
+export type roleRequestData = {
+  status: number | string
+  role_name: string
+  page: number
+  limit: number
+}
+export type addRole = {
+  status: number | string
+  role_name: string
+  checked_menus: Array<any>
+}

BIN
src/assets/imgs/default.jpg


+ 0 - 2
src/axios/config.ts

@@ -5,7 +5,6 @@ import { SUCCESS_CODE, TRANSFORM_REQUEST_DATA } from '@/constants'
 import { useUserStoreWithOut } from '@/store/modules/user'
 import { objToFormData } from '@/utils'
 
-// 请求前数据处理
 const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
   if (
     config.method === 'post' &&
@@ -36,7 +35,6 @@ const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
   return config
 }
 
-// 请求后数据处理
 const defaultResponseInterceptors = (response: AxiosResponse) => {
   if (response?.config?.responseType === 'blob') {
     // 如果是文件流,直接过

+ 1 - 1
src/axios/index.ts

@@ -14,7 +14,7 @@ const request = (option: AxiosConfig) => {
     responseType: responseType,
     headers: {
       'Content-Type': CONTENT_TYPE,
-      [userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
+      'Authori-Zation': userStore.getTokenKey + userStore.getToken,
       ...headers
     }
   })

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

@@ -10,7 +10,8 @@ const props = defineProps({
   modelValue: propTypes.bool.def(false),
   title: propTypes.string.def('Dialog'),
   fullscreen: propTypes.bool.def(true),
-  maxHeight: propTypes.oneOfType([String, Number]).def('400px')
+  maxHeight: propTypes.oneOfType([String, Number]).def('400px'),
+  width: propTypes.oneOfType([String, Number]).def('50%')
 })
 
 const getBindValue = computed(() => {
@@ -73,6 +74,7 @@ const dialogStyle = computed(() => {
     top="0"
     :close-on-click-modal="false"
     :show-close="false"
+    :width="width"
   >
     <template #header="{ close }">
       <div class="flex justify-between items-center h-54px pl-15px pr-15px relative">

+ 27 - 11
src/components/verifition/Verify.vue

@@ -11,11 +11,24 @@
         </span>
       </div>
       <div class="verifybox-bottom" :style="{ padding: mode == 'pop' ? '15px' : '0' }">
-        <!-- 验证码容器 -->
-        <components
-          :is="componentType"
-          v-if="componentType"
-          ref="instance"
+        <VerifySlide
+          v-if="componentType == 'VerifySlide'"
+          ref="VerifySlide"
+          :captcha-type="captchaType"
+          :type="verifyType"
+          :figure="figure"
+          :arith="arith"
+          :mode="mode"
+          :v-space="vSpace"
+          :explain="explain"
+          :img-size="imgSize"
+          :block-size="blockSize"
+          :bar-size="barSize"
+          :default-img="defaultImg"
+        />
+        <VerifyPoints
+          v-else-if="componentType == 'VerifyPoints'"
+          ref="VerifyPoints"
           :captcha-type="captchaType"
           :type="verifyType"
           :figure="figure"
@@ -37,8 +50,9 @@
  * Verify 验证码组件
  * @description 分发验证码使用
  * */
-import VerifySlide from './Verify/VerifySlide'
-import VerifyPoints from './Verify/VerifyPoints'
+import VerifySlide from './Verify/VerifySlide.vue'
+import VerifyPoints from './Verify/VerifyPoints.vue'
+import codeImg from '@/assets/imgs/default.jpg'
 
 export default {
   name: 'Vue2Verify',
@@ -53,11 +67,13 @@ export default {
       type: String,
       default() {
         // 默认语言不输入为浏览器语言
+        let language = ''
         if (navigator.language) {
-          return navigator.language
+          language = navigator.language
         } else {
-          return navigator.browserLanguage
+          language = navigator.browserLanguage
         }
+        return language
       }
     },
     captchaType: {
@@ -105,12 +121,12 @@ export default {
       // 所用组件类型
       componentType: undefined,
       // 默认图片
-      defaultImg: require('@/assets/images/default.jpg')
+      defaultImg: codeImg
     }
   },
   computed: {
     instance() {
-      return this.$refs.instance || {}
+      return this.$refs[this.componentType] || {}
     },
     showBox() {
       if (this.mode == 'pop') {

+ 55 - 49
src/components/verifition/Verify/VerifyPoints.vue

@@ -1,42 +1,42 @@
 <template>
-  <div
-    style="position: relative"
-  >
+  <div style="position: relative">
     <div class="verify-img-out">
       <div
         class="verify-img-panel"
-        :style="{'width': setSize.imgWidth,
-                 'height': setSize.imgHeight,
-                 'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight,
-                 'margin-bottom': vSpace + 'px'}"
+        :style="{
+          width: setSize.imgWidth,
+          height: setSize.imgHeight,
+          'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
+          'margin-bottom': vSpace + 'px'
+        }"
       >
-        <div v-show="showRefresh" class="verify-refresh" style="z-index:3" @click="refresh">
-          <i class="iconfont icon-refresh" />
+        <div v-show="showRefresh" class="verify-refresh" style="z-index: 3" @click="refresh">
+          <i class="iconfont icon-refresh"></i>
         </div>
         <img
           ref="canvas"
-          :src="pointBackImgBase?('data:image/png;base64,'+pointBackImgBase):defaultImg"
+          :src="pointBackImgBase ? 'data:image/png;base64,' + pointBackImgBase : defaultImg"
           alt=""
-          style="width:100%;height:100%;display:block"
-          @click="bindingClick?canvasClick($event):undefined"
-        >
+          style="width: 100%; height: 100%; display: block"
+          @click="bindingClick ? canvasClick($event) : undefined"
+        />
 
         <div
           v-for="(tempPoint, index) in tempPoints"
           :key="index"
           class="point-area"
           :style="{
-            'background-color':'#1abd6c',
-            color:'#fff',
-            'z-index':9999,
-            width:'20px',
-            height:'20px',
-            'text-align':'center',
-            'line-height':'20px',
+            'background-color': '#1abd6c',
+            color: '#fff',
+            'z-index': 9999,
+            width: '20px',
+            height: '20px',
+            'text-align': 'center',
+            'line-height': '20px',
             'border-radius': '50%',
-            position:'absolute',
-            top:parseInt(tempPoint.y-10) + 'px',
-            left:parseInt(tempPoint.x-10) + 'px'
+            position: 'absolute',
+            top: parseInt(tempPoint.y - 10) + 'px',
+            left: parseInt(tempPoint.x - 10) + 'px'
           }"
         >
           {{ index + 1 }}
@@ -46,10 +46,12 @@
     <!-- 'height': this.barSize.height, -->
     <div
       class="verify-bar-area"
-      :style="{'width': setSize.imgWidth,
-               'color': this.barAreaColor,
-               'border-color': this.barAreaBorderColor,
-               'line-height':this.barSize.height}"
+      :style="{
+        width: setSize.imgWidth,
+        color: this.barAreaColor,
+        'border-color': this.barAreaBorderColor,
+        'line-height': this.barSize.height
+      }"
     >
       <span class="verify-msg">{{ text }}</span>
     </div>
@@ -57,12 +59,12 @@
 </template>
 <script type="text/babel">
 /**
-     * VerifyPoints
-     * @description 点选
-     * */
+ * VerifyPoints
+ * @description 点选
+ * */
 import { resetSize, _code_chars, _code_color1, _code_color2 } from './../utils/util'
 import { aesEncrypt } from './../utils/ase'
-import {ajCaptcha, ajCaptchaCheck} from "../../../api/common";
+import { ajCaptcha, ajCaptchaCheck } from '@/api/login'
 
 export default {
   name: 'VerifyPoints',
@@ -73,7 +75,7 @@ export default {
       default: 'fixed'
     },
     captchaType: {
-      type: String,
+      type: String
     },
     // 间隔
     vSpace: {
@@ -143,7 +145,7 @@ export default {
   },
   mounted() {
     // 禁止拖拽
-    this.$el.onselectstart = function() {
+    this.$el.onselectstart = function () {
       return false
     }
   },
@@ -155,7 +157,7 @@ export default {
       this.num = 1
       this.getPictrue()
       this.$nextTick(() => {
-        this.setSize = this.resetSize(this)	// 重新设置宽度高度
+        this.setSize = this.resetSize(this) // 重新设置宽度高度
         this.$parent.$emit('ready', this)
       })
     },
@@ -169,13 +171,17 @@ export default {
         setTimeout(() => {
           // var flag = this.comparePos(this.fontPos, this.checkPosArr);
           // 发送后端请求
-          var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify(this.checkPosArr), this.secretKey) : this.backToken + '---' + JSON.stringify(this.checkPosArr)
+          const captchaVerification = this.secretKey
+            ? aesEncrypt(this.backToken + '---' + JSON.stringify(this.checkPosArr), this.secretKey)
+            : this.backToken + '---' + JSON.stringify(this.checkPosArr)
           const data = {
             captchaType: this.captchaType,
-            'pointJson': this.secretKey ? aesEncrypt(JSON.stringify(this.checkPosArr), this.secretKey) : JSON.stringify(this.checkPosArr),
-            'token': this.backToken
+            pointJson: this.secretKey
+              ? aesEncrypt(JSON.stringify(this.checkPosArr), this.secretKey)
+              : JSON.stringify(this.checkPosArr),
+            token: this.backToken
           }
-          ajCaptchaCheck(data).then(res => {
+          ajCaptchaCheck(data).then((res) => {
             if (res.repCode == '0000') {
               this.barAreaColor = '#4cae4c'
               this.barAreaBorderColor = '#5cb85c'
@@ -206,17 +212,17 @@ export default {
     },
 
     // 获取坐标
-    getMousePos: function(obj, e) {
-      var x = e.offsetX
-      var y = e.offsetY
+    getMousePos: function (obj, e) {
+      const x = e.offsetX
+      const y = e.offsetY
       return { x, y }
     },
     // 创建坐标点
-    createPoint: function(pos) {
+    createPoint: function (pos) {
       this.tempPoints.push(Object.assign({}, pos))
       return ++this.num
     },
-    refresh: function() {
+    refresh: function () {
       this.tempPoints.splice(0, this.tempPoints.length)
       this.barAreaColor = '#000'
       this.barAreaBorderColor = '#ddd'
@@ -234,9 +240,9 @@ export default {
       const data = {
         captchaType: this.captchaType,
         clientUid: localStorage.getItem('point'),
-        ts: Date.now(), // 现在的时间戳
+        ts: Date.now() // 现在的时间戳
       }
-      ajCaptcha(data).then(res => {
+      ajCaptcha(data).then((res) => {
         if (res.repCode == '0000') {
           this.pointBackImgBase = res.repData.originalImageBase64
           this.backToken = res.repData.token
@@ -255,14 +261,14 @@ export default {
     },
     // 坐标转换函数
     pointTransfrom(pointArr, imgSize) {
-      var newPointArr = pointArr.map(p => {
-        const x = Math.round(310 * p.x / parseInt(imgSize.imgWidth))
-        const y = Math.round(155 * p.y / parseInt(imgSize.imgHeight))
+      const newPointArr = pointArr.map((p) => {
+        const x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth))
+        const y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight))
         return { x, y }
       })
       // console.log(newPointArr,"newPointArr");
       return newPointArr
     }
-  },
+  }
 }
 </script>

+ 128 - 112
src/components/verifition/Verify/VerifySlide.vue

@@ -1,56 +1,70 @@
 <template>
-  <div style="position: relative;">
+  <div style="position: relative">
     <div
       v-if="type === '2'"
       class="verify-img-out"
-      :style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}"
+      :style="{ height: parseInt(setSize.imgHeight) + vSpace + 'px' }"
     >
-      <div
-        class="verify-img-panel"
-        :style="{width: setSize.imgWidth,
-                 height: setSize.imgHeight,}"
-      >
-        <img :src="backImgBase?('data:image/png;base64,'+backImgBase):defaultImg" alt="" style="width:100%;height:100%;display:block">
-        <div v-show="showRefresh" class="verify-refresh" @click="refresh"><i class="iconfont icon-refresh" />
+      <div class="verify-img-panel" :style="{ width: setSize.imgWidth, height: setSize.imgHeight }">
+        <img
+          :src="backImgBase ? 'data:image/png;base64,' + backImgBase : defaultImg"
+          alt=""
+          style="width: 100%; height: 100%; display: block"
+        />
+        <div v-show="showRefresh" class="verify-refresh" @click="refresh"
+          ><i class="iconfont icon-refresh"></i>
         </div>
         <transition name="tips">
-          <span v-if="tipWords" class="verify-tips" :class="passFlag ?'suc-bg':'err-bg'">{{ tipWords }}</span>
+          <span v-if="tipWords" class="verify-tips" :class="passFlag ? 'suc-bg' : 'err-bg'">{{
+            tipWords
+          }}</span>
         </transition>
       </div>
     </div>
     <!-- 公共部分 -->
     <div
       class="verify-bar-area"
-      :style="{width: setSize.imgWidth,
-               height: barSize.height,
-               'line-height':barSize.height}"
+      :style="{ width: setSize.imgWidth, height: barSize.height, 'line-height': barSize.height }"
     >
-      <span class="verify-msg" v-text="text" />
+      <span class="verify-msg" v-text="text"></span>
       <div
         class="verify-left-bar"
-        :style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}"
+        :style="{
+          width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,
+          height: barSize.height,
+          'border-color': leftBarBorderColor,
+          transaction: transitionWidth
+        }"
       >
-        <span class="verify-msg" v-text="finishText" />
+        <span class="verify-msg" v-text="finishText"></span>
         <div
           class="verify-move-block"
-          :style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}"
+          :style="{
+            width: barSize.height,
+            height: barSize.height,
+            'background-color': moveBlockBackgroundColor,
+            left: moveBlockLeft,
+            transition: transitionLeft
+          }"
           @touchstart="start"
           @mousedown="start"
         >
-          <i
-            :class="['verify-icon iconfont', iconClass]"
-            :style="{color: iconColor}"
-          />
+          <i :class="['verify-icon iconfont', iconClass]" :style="{ color: iconColor }"></i>
           <div
             v-if="type === '2'"
             class="verify-sub-block"
-            :style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px',
-                     'height': setSize.imgHeight,
-                     'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
-                     'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
+            :style="{
+              width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px',
+              height: setSize.imgHeight,
+              top: '-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
+              'background-size': setSize.imgWidth + ' ' + setSize.imgHeight
             }"
           >
-            <img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block">
+            <img
+              :src="'data:image/png;base64,' + blockBackImgBase"
+              alt=""
+              style="width: 100%; height: 100%; display: block"
+            />
           </div>
         </div>
       </div>
@@ -59,19 +73,19 @@
 </template>
 <script type="text/babel">
 /**
-     * VerifySlide
-     * @description 滑块
-     * */
+ * VerifySlide
+ * @description 滑块
+ * */
 import { aesEncrypt } from './../utils/ase'
 import { resetSize } from './../utils/util'
-import {ajCaptcha, ajCaptchaCheck} from "../../../api/common";
+import { ajCaptcha, ajCaptchaCheck } from '@/api/login'
 
 //  "captchaType":"blockPuzzle",
 export default {
   name: 'VerifySlide',
   props: {
     captchaType: {
-      type: String,
+      type: String
     },
     type: {
       type: String,
@@ -151,7 +165,7 @@ export default {
       iconColor: undefined,
       iconClass: 'icon-right',
       status: false, // 鼠标状态
-      isEnd: false,		// 是够验证完成
+      isEnd: false, // 是够验证完成
       showRefresh: true,
       transitionLeft: '',
       transitionWidth: ''
@@ -176,7 +190,7 @@ export default {
   },
   mounted() {
     // 禁止拖拽
-    this.$el.onselectstart = function() {
+    this.$el.onselectstart = function () {
       return false
     }
     console.log(this.defaultImg)
@@ -186,53 +200,39 @@ export default {
       this.text = this.explain
       this.getPictrue()
       this.$nextTick(() => {
-        const setSize = this.resetSize(this)	// 重新设置宽度高度
+        const setSize = this.resetSize(this) // 重新设置宽度高度
         for (const key in setSize) {
-          this.$set(this.setSize, key, setSize[key])
+          this.setSize[key] = setSize[key]
         }
         this.$parent.$emit('ready', this)
       })
 
-      var _this = this
-
-      window.removeEventListener('touchmove', function(e) {
-        _this.move(e)
-      })
-      window.removeEventListener('mousemove', function(e) {
-        _this.move(e)
-      })
+      // 使用箭头函数捕获 this
+      window.removeEventListener('touchmove', this.move)
+      window.removeEventListener('mousemove', this.move)
 
       // 鼠标松开
-      window.removeEventListener('touchend', function() {
-        _this.end()
-      })
-      window.removeEventListener('mouseup', function() {
-        _this.end()
-      })
+      window.removeEventListener('touchend', this.end)
+      window.removeEventListener('mouseup', this.end)
 
-      window.addEventListener('touchmove', function(e) {
-        _this.move(e)
-      })
-      window.addEventListener('mousemove', function(e) {
-        _this.move(e)
-      })
+      window.addEventListener('touchmove', this.move)
+      window.addEventListener('mousemove', this.move)
 
       // 鼠标松开
-      window.addEventListener('touchend', function() {
-        _this.end()
-      })
-      window.addEventListener('mouseup', function() {
-        _this.end()
-      })
+      window.addEventListener('touchend', this.end)
+      window.addEventListener('mouseup', this.end)
     },
 
     // 鼠标按下
-    start: function(e) {
+    start: function (e) {
       e = e || window.event
-      if (!e.touches) { // 兼容PC端
-        var x = e.clientX
-      } else { // 兼容移动端
-        var x = e.touches[0].pageX
+      let x = ''
+      if (!e.touches) {
+        // 兼容PC端
+        x = e.clientX
+      } else {
+        // 兼容移动端
+        x = e.touches[0].pageX
       }
       this.startLeft = Math.floor(x - this.barArea.getBoundingClientRect().left)
       this.startMoveTime = +new Date() // 开始滑动的时间
@@ -246,42 +246,51 @@ export default {
       }
     },
     // 鼠标移动
-    move: function(e) {
+    move: function (e) {
       e = e || window.event
       if (this.status && this.isEnd == false) {
-        if (!e.touches) { // 兼容PC端
-          var x = e.clientX
-        } else { // 兼容移动端
-          var x = e.touches[0].pageX
+        let x = ''
+        if (!e.touches) {
+          // 兼容PC端
+          x = e.clientX
+        } else {
+          // 兼容移动端
+          x = e.touches[0].pageX
         }
-        var bar_area_left = this.barArea.getBoundingClientRect().left
-        var move_block_left = x - bar_area_left // 小方块相对于父元素的left值
-        if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) {
-          move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2
+        const bar_area_left = this.barArea.getBoundingClientRect().left
+        let move_block_left = x - bar_area_left // 小方块相对于父元素的left值
+        if (
+          move_block_left >=
+          this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2
+        ) {
+          move_block_left =
+            this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2
         }
         if (move_block_left <= 0) {
           move_block_left = parseInt(parseInt(this.blockSize.width) / 2)
         }
         // 拖动后小方块的left值
-        this.moveBlockLeft = (move_block_left - this.startLeft) + 'px'
-        this.leftBarWidth = (move_block_left - this.startLeft) + 'px'
+        this.moveBlockLeft = move_block_left - this.startLeft + 'px'
+        this.leftBarWidth = move_block_left - this.startLeft + 'px'
       }
     },
 
     // 鼠标松开
-    end: function() {
+    end: function () {
       this.endMovetime = +new Date()
-      var _this = this
       // 判断是否重合
       if (this.status && this.isEnd == false) {
-        var moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', ''))
-        moveLeftDistance = moveLeftDistance * 310 / parseInt(this.setSize.imgWidth)
+        let moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', ''))
+        moveLeftDistance = (moveLeftDistance * 310) / parseInt(this.setSize.imgWidth)
         const data = {
           captchaType: this.captchaType,
-          'pointJson': this.secretKey ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
-          'token': this.backToken
+          pointJson: this.secretKey
+            ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey)
+            : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
+          token: this.backToken
         }
-        ajCaptchaCheck(data).then(res => {
+        ajCaptchaCheck(data)
+          .then((res) => {
             this.moveBlockBackgroundColor = '#5cb85c'
             this.leftBarBorderColor = '#5cb85c'
             this.iconColor = '#fff'
@@ -296,32 +305,37 @@ export default {
             }
             this.passFlag = true
             this.tipWords = `${((this.endMovetime - this.startMoveTime) / 1000).toFixed(2)}s验证成功`
-            var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
+            const captchaVerification = this.secretKey
+              ? aesEncrypt(
+                  this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
+                  this.secretKey
+                )
+              : this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
             setTimeout(() => {
               this.tipWords = ''
               this.$parent.closeBox()
               this.$parent.$emit('success', { captchaVerification })
             }, 1000)
-        }).catch(res=>{
-          this.moveBlockBackgroundColor = '#d9534f'
-          this.leftBarBorderColor = '#d9534f'
-          this.iconColor = '#fff'
-          this.iconClass = 'icon-close'
-          this.passFlag = false
-          setTimeout(function() {
-            _this.refresh()
-          }, 1000)
-          this.$parent.$emit('error', this)
-          this.tipWords = '验证失败'
-          setTimeout(() => {
-            this.tipWords = ''
-          }, 1000)
-        })
+          })
+          .catch((res) => {
+            this.moveBlockBackgroundColor = '#d9534f'
+            this.leftBarBorderColor = '#d9534f'
+            this.iconColor = '#fff'
+            this.iconClass = 'icon-close'
+            this.passFlag = false
+            setTimeout(() => {
+              this.refresh()
+            }, 1000)
+            this.$parent.$emit('error', this)
+            this.tipWords = '验证失败'
+            setTimeout(() => {
+              this.tipWords = ''
+            }, 1000)
+          })
         this.status = false
       }
     },
-
-    refresh: function() {
+    refresh: function () {
       this.showRefresh = true
       this.finishText = ''
 
@@ -347,23 +361,25 @@ export default {
 
     // 请求背景图片和验证图片
     getPictrue() {
+      // console.log('请求验证码')
       const data = {
         captchaType: this.captchaType,
         clientUid: localStorage.getItem('slider'),
-        ts: Date.now(), // 现在的时间戳
+        ts: Date.now() // 现在的时间戳
       }
-      ajCaptcha(data).then(res => {
+      ajCaptcha(data)
+        .then((res) => {
           this.backImgBase = res.data.originalImageBase64
           this.blockBackImgBase = res.data.jigsawImageBase64
           this.backToken = res.data.token
           this.secretKey = res.data.secretKey
-      }).catch(res =>{
-        this.tipWords = res.msg
-        this.backImgBase = null
-        this.blockBackImgBase = null
-      })
-    },
-  },
+        })
+        .catch((res) => {
+          this.tipWords = res.msg
+          this.backImgBase = null
+          this.blockBackImgBase = null
+        })
+    }
+  }
 }
 </script>
-

+ 2 - 0
src/components/verifition/index.ts

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

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

@@ -80,7 +80,6 @@ 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

+ 0 - 1
src/permission.ts

@@ -32,7 +32,6 @@ router.beforeEach(async (to, from, next) => {
 
       // 是否使用动态路由
       if (appStore.getDynamicRouter) {
-        console.log(appStore, 'appStore.serverDynamicRouter')
         appStore.serverDynamicRouter
           ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
           : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])

+ 11 - 76
src/router/index.ts

@@ -4,23 +4,21 @@ import type { App } from 'vue'
 import { Layout } from '@/utils/routerHelper'
 import { useI18n } from '@/hooks/web/useI18n'
 import { NO_RESET_WHITE_LIST } from '@/constants'
-import Hooks from './model/Hooks'
-
 const { t } = useI18n()
-// const modules = import.meta.glob('./model/*.ts', {
-//   import: 'default',
-//   eager: true
-// })
+const modules = import.meta.glob('./model/*.ts', {
+  import: 'default',
+  eager: true
+})
 
-// const mockModules: any[] = []
-// Object.keys(modules).forEach(async (key) => {
-//   mockModules.push(modules[key])
-// })
+const mockModules: any[] = []
+Object.keys(modules).forEach(async (key) => {
+  mockModules.unshift(modules[key])
+})
 export const constantRouterMap: AppRouteRecordRaw[] = [
   {
     path: '/',
     component: Layout,
-    redirect: '/dashboard/analysis',
+    redirect: '/personal/personal-center',
     name: 'Root',
     meta: {
       hidden: true
@@ -33,7 +31,7 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
     children: [
       {
         path: '/redirect/:path(.*)',
-        name: 'Redirect',
+        name: 'Redirectindex',
         component: () => import('@/views/Redirect/Redirect.vue'),
         meta: {}
       }
@@ -88,70 +86,7 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
   }
 ]
 
-export const asyncRouterMap: AppRouteRecordRaw[] = [
-  {
-    path: '/hooks',
-    component: Layout,
-    redirect: '/hooks/useWatermark',
-    name: 'Hooks',
-    meta: {
-      title: 'hooks',
-      icon: 'vi-ic:outline-webhook',
-      alwaysShow: true
-    },
-    children: [
-      {
-        path: 'useWatermark',
-        component: () => import('@/views/hooks/useWatermark.vue'),
-        name: 'UseWatermark',
-        meta: {
-          title: 'useWatermark'
-        }
-      },
-      {
-        path: 'useTagsView',
-        component: () => import('@/views/hooks/useTagsView.vue'),
-        name: 'UseTagsView',
-        meta: {
-          title: 'useTagsView'
-        }
-      },
-      {
-        path: 'useValidator',
-        component: () => import('@/views/hooks/useValidator.vue'),
-        name: 'UseValidator',
-        meta: {
-          title: 'useValidator'
-        }
-      },
-      {
-        path: 'useCrudSchemas',
-        component: () => import('@/views/hooks/useCrudSchemas.vue'),
-        name: 'UseCrudSchemas',
-        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'
-        }
-      }
-    ]
-  }
-]
-console.log(asyncRouterMap, 'asyncRouterMap')
+export const asyncRouterMap: AppRouteRecordRaw[] = [...mockModules]
 const router = createRouter({
   history: createWebHashHistory(),
   strict: true,

+ 0 - 1
src/router/model/Authorization.ts

@@ -4,7 +4,6 @@ import { Layout } from '@/utils/routerHelper'
 export default {
   path: '/authorization',
   component: Layout,
-  redirect: '/authorization/user',
   name: 'Authorization',
   meta: {
     title: t('router.authorization'),

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

@@ -59,6 +59,7 @@ export const useAppStore = defineStore('app', {
       dynamicRouter: true, // 是否动态路由
       serverDynamicRouter: false, // 是否服务端渲染动态路由
       fixedMenu: false, // 是否固定菜单
+
       layout: 'classic', // layout布局
       isDark: false, // 是否是暗黑模式
       currentSize: 'default', // 组件尺寸
@@ -215,7 +216,6 @@ export const useAppStore = defineStore('app', {
       this.dynamicRouter = dynamicRouter
     },
     setServerDynamicRouter(serverDynamicRouter: boolean) {
-      console.log(serverDynamicRouter, 'serverDynamicRouter')
       this.serverDynamicRouter = serverDynamicRouter
     },
     setFixedMenu(fixedMenu: boolean) {

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

@@ -42,21 +42,15 @@ export const usePermissionStore = defineStore('permission', {
       routers?: AppCustomRouteRecordRaw[] | string[]
     ): Promise<unknown> {
       return new Promise<void>((resolve) => {
-        console.log(type, 'type')
         let routerMap: AppRouteRecordRaw[] = []
         if (type === 'server') {
           // 模拟后端过滤菜单
-          console.log(asyncRouterMap, 'asyncRouterMap333')
-
           routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
         } else if (type === 'frontEnd') {
           // 模拟前端过滤菜单
-          console.log(asyncRouterMap, 'asyncRouterMap222')
-
           routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
         } else {
           // 直接读取静态路由表
-          console.log(asyncRouterMap, 'asyncRouterMap111')
           routerMap = cloneDeep(asyncRouterMap)
         }
         // 动态路由,404一定要放到最后面

+ 1 - 1
src/store/modules/user.ts

@@ -20,7 +20,7 @@ export const useUserStore = defineStore('user', {
   state: (): UserState => {
     return {
       userInfo: undefined,
-      tokenKey: 'Authorization',
+      tokenKey: 'Bearer ',
       token: '',
       roleRouters: undefined,
       // 记住我

+ 47 - 0
src/utils/analysis.ts

@@ -0,0 +1,47 @@
+import { MenuData, addRoleData } from '@/api/system/menu/types'
+
+// export const userMenuData = (data: any): addRoleData => {
+//   const comp = component as any
+//   comp.install = (app: any) => {
+//     app.component(comp.name || comp.displayName, component)
+//     if (alias) {
+//       app.config.globalProperties[alias] = component
+//     }
+//   }
+//   return component as T & Plugin
+// }
+
+export const roluData = (data: Array<any>): Array<MenuData> => {
+  const list = data.map(function arrMap(ls): MenuData {
+    const children = ls.children.map((lss) => {
+      return arrMap(lss)
+    })
+    return {
+      id: ls.id,
+      path: ls.menu_path,
+      status: ls.is_show,
+      unique_auth: ls.unique_auth,
+      parentId: ls.pid,
+      type: ls.auth_type || 0,
+      apiUrl: ls.api_url,
+      methods: ls.methods,
+      sort: ls.sort,
+      title: ls.title,
+      meta: {
+        icon: ls.icon,
+        title: ls.title,
+        activeMenu: ls.extend?.meta.activeMenu,
+        hidden: !!ls.is_show_path,
+        alwaysShow: !!ls.extend?.meta.alwaysShow,
+        noCache: !!ls.extend?.meta.noCache,
+        breadcrumb: !!ls.extend?.meta.breadcrumb,
+        affix: !!ls.extend?.meta.affix,
+        noTagsView: !!ls.extend?.meta.noTagsView,
+        canTo: !!ls.extend?.meta.canTo,
+        permissionList: ls.extend?.permissionList
+      },
+      children
+    }
+  })
+  return list
+}

+ 13 - 12
src/utils/routerHelper.ts

@@ -40,11 +40,10 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
 // 前端控制路由生成
 export const generateRoutesByFrontEnd = (
   routes: AppRouteRecordRaw[],
-  keys: string[],
+  keys: any[],
   basePath = '/'
 ): AppRouteRecordRaw[] => {
   const res: AppRouteRecordRaw[] = []
-
   for (const route of routes) {
     const meta = route.meta ?? {}
     // skip some route
@@ -66,24 +65,26 @@ export const generateRoutesByFrontEnd = (
     // 开发者可以根据实际情况进行扩展
     for (const item of keys) {
       // 通过路径去匹配
-      if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
+      if (isUrl(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
+        route.meta = Object.assign({}, item.meta)
         data = Object.assign({}, route)
       } else {
         const routePath = (onlyOneChild ?? pathResolve(basePath, route.path)).trim()
-        if (routePath === item || meta.followRoute === item) {
+        if (routePath === item.path || meta.followRoute === item.path) {
+          route.meta = Object.assign({}, item.meta)
           data = Object.assign({}, route)
         }
       }
+      if (route.children && data) {
+        data.children = generateRoutesByFrontEnd(
+          route.children,
+          item.children,
+          pathResolve(basePath, data.path)
+        )
+      }
     }
-
     // recursive child routes
-    if (route.children && data) {
-      data.children = generateRoutesByFrontEnd(
-        route.children,
-        keys,
-        pathResolve(basePath, data.path)
-      )
-    }
+
     if (data) {
       res.push(data as AppRouteRecordRaw)
     }

+ 0 - 341
src/views/Authorization/Department/Department.vue

@@ -1,341 +0,0 @@
-<script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Search } from '@/components/Search'
-import { Dialog } from '@/components/Dialog'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ElTag } from 'element-plus'
-import { Table } from '@/components/Table'
-import {
-  getDepartmentApi,
-  getDepartmentTableApi,
-  saveDepartmentApi,
-  deleteDepartmentApi
-} from '@/api/department'
-import type { DepartmentItem } from '@/api/department/types'
-import { useTable } from '@/hooks/web/useTable'
-import { ref, unref, reactive } from 'vue'
-import Write from './components/Write.vue'
-import Detail from './components/Detail.vue'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { BaseButton } from '@/components/Button'
-
-const ids = ref<string[]>([])
-
-const { tableRegister, tableState, tableMethods } = useTable({
-  fetchDataApi: async () => {
-    const { currentPage, pageSize } = tableState
-    const res = await getDepartmentTableApi({
-      pageIndex: unref(currentPage),
-      pageSize: unref(pageSize),
-      ...unref(searchParams)
-    })
-    return {
-      list: res.data.list,
-      total: res.data.total
-    }
-  },
-  fetchDelApi: async () => {
-    const res = await deleteDepartmentApi(unref(ids))
-    return !!res
-  }
-})
-const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, getElTableExpose, delList } = tableMethods
-
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  searchParams.value = params
-  getList()
-}
-
-const { t } = useI18n()
-
-const crudSchemas = reactive<CrudSchema[]>([
-  {
-    field: 'selection',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      type: 'selection'
-    }
-  },
-  {
-    field: 'index',
-    label: t('tableDemo.index'),
-    type: 'index',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    }
-  },
-  {
-    field: 'id',
-    label: t('userDemo.departmentName'),
-    table: {
-      slots: {
-        default: (data: any) => {
-          return <>{data.row.departmentName}</>
-        }
-      }
-    },
-    form: {
-      component: 'TreeSelect',
-      componentProps: {
-        nodeKey: 'id',
-        props: {
-          label: 'departmentName'
-        }
-      },
-      optionApi: async () => {
-        const res = await getDepartmentApi()
-        return res.data.list
-      }
-    },
-    detail: {
-      slots: {
-        default: (data: any) => {
-          return <>{data.departmentName}</>
-        }
-      }
-    }
-  },
-  {
-    field: 'status',
-    label: t('userDemo.status'),
-    search: {
-      hidden: true
-    },
-    table: {
-      slots: {
-        default: (data: any) => {
-          const status = data.row.status
-          return (
-            <>
-              <ElTag type={status === 0 ? 'danger' : 'success'}>
-                {status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
-              </ElTag>
-            </>
-          )
-        }
-      }
-    },
-    form: {
-      component: 'Select',
-      componentProps: {
-        options: [
-          {
-            value: 0,
-            label: t('userDemo.disable')
-          },
-          {
-            value: 1,
-            label: t('userDemo.enable')
-          }
-        ]
-      }
-    },
-    detail: {
-      slots: {
-        default: (data: any) => {
-          return (
-            <>
-              <ElTag type={data.status === 0 ? 'danger' : 'success'}>
-                {data.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
-              </ElTag>
-            </>
-          )
-        }
-      }
-    }
-  },
-  {
-    field: 'createTime',
-    label: t('tableDemo.displayTime'),
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    }
-  },
-  {
-    field: 'remark',
-    label: t('userDemo.remark'),
-    search: {
-      hidden: true
-    },
-    form: {
-      component: 'Input',
-      componentProps: {
-        type: 'textarea',
-        rows: 5
-      },
-      colProps: {
-        span: 24
-      }
-    },
-    detail: {
-      slots: {
-        default: (data: any) => {
-          return <>{data.remark}</>
-        }
-      }
-    }
-  },
-  {
-    field: 'action',
-    width: '260px',
-    label: t('tableDemo.action'),
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      slots: {
-        default: (data: any) => {
-          return (
-            <>
-              <BaseButton type="primary" onClick={() => action(data.row, 'edit')}>
-                {t('exampleDemo.edit')}
-              </BaseButton>
-              <BaseButton type="success" onClick={() => action(data.row, 'detail')}>
-                {t('exampleDemo.detail')}
-              </BaseButton>
-              <BaseButton type="danger" onClick={() => delData(data.row)}>
-                {t('exampleDemo.del')}
-              </BaseButton>
-            </>
-          )
-        }
-      }
-    }
-  }
-])
-
-// @ts-ignore
-const { allSchemas } = useCrudSchemas(crudSchemas)
-
-const dialogVisible = ref(false)
-const dialogTitle = ref('')
-
-const currentRow = ref<DepartmentItem | null>(null)
-const actionType = ref('')
-
-const AddAction = () => {
-  dialogTitle.value = t('exampleDemo.add')
-  currentRow.value = null
-  dialogVisible.value = true
-  actionType.value = ''
-}
-
-const delLoading = ref(false)
-
-const delData = async (row: DepartmentItem | null) => {
-  const elTableExpose = await getElTableExpose()
-  ids.value = row
-    ? [row.id]
-    : elTableExpose?.getSelectionRows().map((v: DepartmentItem) => v.id) || []
-  delLoading.value = true
-  await delList(unref(ids).length).finally(() => {
-    delLoading.value = false
-  })
-}
-
-const action = (row: DepartmentItem, type: string) => {
-  dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
-  actionType.value = type
-  currentRow.value = row
-  dialogVisible.value = true
-}
-
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const saveLoading = ref(false)
-
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    saveLoading.value = true
-    const res = await saveDepartmentApi(formData)
-      .catch(() => {})
-      .finally(() => {
-        saveLoading.value = false
-      })
-    if (res) {
-      dialogVisible.value = false
-      currentPage.value = 1
-      getList()
-    }
-  }
-}
-</script>
-
-<template>
-  <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
-
-    <div class="mb-10px">
-      <BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
-      <BaseButton :loading="delLoading" type="danger" @click="delData(null)">
-        {{ t('exampleDemo.del') }}
-      </BaseButton>
-    </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="dialogTitle">
-    <Write
-      v-if="actionType !== 'detail'"
-      ref="writeRef"
-      :form-schema="allSchemas.formSchema"
-      :current-row="currentRow"
-    />
-
-    <Detail
-      v-if="actionType === 'detail'"
-      :detail-schema="allSchemas.detailSchema"
-      :current-row="currentRow"
-    />
-
-    <template #footer>
-      <BaseButton
-        v-if="actionType !== 'detail'"
-        type="primary"
-        :loading="saveLoading"
-        @click="save"
-      >
-        {{ t('exampleDemo.save') }}
-      </BaseButton>
-      <BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
-    </template>
-  </Dialog>
-</template>

+ 0 - 20
src/views/Authorization/Department/components/Detail.vue

@@ -1,20 +0,0 @@
-<script setup lang="ts">
-import { PropType } from 'vue'
-import { DepartmentItem } from '@/api/department/types'
-import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
-
-defineProps({
-  currentRow: {
-    type: Object as PropType<Nullable<DepartmentItem>>,
-    default: () => null
-  },
-  detailSchema: {
-    type: Array as PropType<DescriptionsSchema[]>,
-    default: () => []
-  }
-})
-</script>
-
-<template>
-  <Descriptions :schema="detailSchema" :data="currentRow || {}" />
-</template>

+ 0 - 59
src/views/Authorization/Department/components/Write.vue

@@ -1,59 +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 { useValidator } from '@/hooks/web/useValidator'
-import { DepartmentItem } from '@/api/department/types'
-
-const { required } = useValidator()
-
-const props = defineProps({
-  currentRow: {
-    type: Object as PropType<Nullable<DepartmentItem>>,
-    default: () => null
-  },
-  formSchema: {
-    type: Array as PropType<FormSchema[]>,
-    default: () => []
-  }
-})
-
-const rules = reactive({
-  id: [required()],
-  status: [required()]
-})
-
-const { formRegister, formMethods } = useForm()
-const { setValues, getFormData, getElFormExpose } = 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
-  }
-}
-
-watch(
-  () => props.currentRow,
-  (currentRow) => {
-    if (!currentRow) return
-    setValues(currentRow)
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-
-defineExpose({
-  submit
-})
-</script>
-
-<template>
-  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
-</template>

+ 119 - 33
src/views/Authorization/Menu/Menu.vue

@@ -1,10 +1,12 @@
 <script setup lang="tsx">
 import { reactive, ref, unref } from 'vue'
-import { getMenuListApi } from '@/api/menu'
+import { getMenuListApi, newMenuApi, setMenuApi, delMenuApi } from '@/api/system/menu'
+import { MenuData, addRoleData } from '@/api/system/menu/types'
+import { roluData } from '@/utils/analysis'
 import { useTable } from '@/hooks/web/useTable'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table, TableColumn } from '@/components/Table'
-import { ElTag } from 'element-plus'
+import { ElMessage, ElTag, ElMessageBox } from 'element-plus'
 import { Icon } from '@/components/Icon'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
@@ -18,25 +20,26 @@ const { t } = useI18n()
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
-    const res = await getMenuListApi()
+    const res = await getMenuListApi(searchParams.value)
+    const list = await roluData(res.data.list)
     return {
-      list: res.data.list || []
+      list: list || [],
+      total: res.data.count || 0
     }
   }
 })
-
 const { dataList, loading } = tableState
 const { getList } = tableMethods
 
 const tableColumns = reactive<TableColumn[]>([
   {
-    field: 'index',
-    label: t('userDemo.index'),
-    type: 'index'
+    field: 'id',
+    label: 'ID'
   },
   {
     field: 'meta.title',
     label: t('menu.menuName'),
+    minWidth: 120,
     slots: {
       default: (data: any) => {
         const title = data.row.meta.title
@@ -47,6 +50,7 @@ const tableColumns = reactive<TableColumn[]>([
   {
     field: 'meta.icon',
     label: t('menu.icon'),
+    width: 60,
     slots: {
       default: (data: any) => {
         const icon = data.row.meta.icon
@@ -62,33 +66,40 @@ const tableColumns = reactive<TableColumn[]>([
       }
     }
   },
-  // {
-  //   field: 'meta.permission',
-  //   label: t('menu.permission'),
-  //   slots: {
-  //     default: (data: any) => {
-  //       const permission = data.row.meta.permission
-  //       return permission ? <>{permission.join(', ')}</> : null
-  //     }
-  //   }
-  // },
   {
-    field: 'component',
-    label: t('menu.component'),
+    field: 'type',
+    label: '类型',
+    width: 60,
+    slots: {
+      default: (data: any) => {
+        return <>{data.row.type == 1 ? '菜单' : '接口'}</>
+      }
+    }
+  },
+  {
+    field: 'apiUrl',
+    label: '接口地址',
+    minWidth: 120,
     slots: {
       default: (data: any) => {
-        const component = data.row.component
-        return <>{component === '#' ? '顶级目录' : component === '##' ? '子目录' : component}</>
+        return <>{data.row.apiUrl}</>
       }
     }
   },
+  {
+    field: 'unique_auth',
+    label: '权限标识',
+    width: 200
+  },
   {
     field: 'path',
-    label: t('menu.path')
+    label: t('menu.path'),
+    minWidth: 120
   },
   {
     field: 'status',
     label: t('menu.status'),
+    width: 60,
     slots: {
       default: (data: any) => {
         return (
@@ -105,6 +116,7 @@ const tableColumns = reactive<TableColumn[]>([
     field: 'action',
     label: t('userDemo.action'),
     width: 240,
+    fixed: 'right',
     slots: {
       default: (data: any) => {
         const row = data.row
@@ -116,7 +128,9 @@ const tableColumns = reactive<TableColumn[]>([
             <BaseButton type="success" onClick={() => action(row, 'detail')}>
               {t('exampleDemo.detail')}
             </BaseButton>
-            <BaseButton type="danger">{t('exampleDemo.del')}</BaseButton>
+            <BaseButton type="danger" onClick={() => delAction(row)}>
+              {t('exampleDemo.del')}
+            </BaseButton>
           </>
         )
       }
@@ -126,13 +140,37 @@ const tableColumns = reactive<TableColumn[]>([
 
 const searchSchema = reactive<FormSchema[]>([
   {
-    field: 'meta.title',
+    field: 'is_show',
+    label: t('menu.status'),
+    component: 'Select',
+    componentProps: {
+      options: [
+        {
+          label: t('userDemo.disable'),
+          value: 0
+        },
+        {
+          label: t('userDemo.enable'),
+          value: 1
+        }
+      ]
+    }
+  },
+  {
+    field: 'type',
+    label: '类型',
+    value: 1,
+    component: 'Input',
+    hidden: true
+  },
+  {
+    field: 'keyword',
     label: t('menu.menuName'),
     component: 'Input'
   }
 ])
 
-const searchParams = ref({})
+const searchParams = ref({ type: 1 })
 const setSearchParams = (data: any) => {
   searchParams.value = data
   getList()
@@ -149,6 +187,7 @@ const writeRef = ref<ComponentRef<typeof Write>>()
 const saveLoading = ref(false)
 
 const action = (row: any, type: string) => {
+  console.log(row, 'rowdate')
   dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
   actionType.value = type
   currentRow.value = row
@@ -162,16 +201,58 @@ const AddAction = () => {
   actionType.value = ''
 }
 
+const delAction = async (row: any) => {
+  ElMessageBox.confirm('删除后无法恢复,是否删除菜单?', {
+    confirmButtonText: '删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      await delMenuApi({ id: row.id })
+      await getList()
+
+      ElMessage({
+        showClose: true,
+        message: '删除',
+        type: 'success'
+      })
+    })
+    .catch(() => {})
+}
 const save = async () => {
   const write = unref(writeRef)
   const formData = await write?.submit()
-  console.log(formData)
   if (formData) {
     saveLoading.value = true
-    setTimeout(() => {
-      saveLoading.value = false
-      dialogVisible.value = false
-    }, 1000)
+    const data: addRoleData = {
+      id: formData.id,
+      menu_name: formData.meta.title, //名称
+      icon: formData.meta.icon, //图标
+      params: '', //参数
+      menu_path: formData.path, //路由地址
+      api_url: formData.apiUrl, //接口地址
+      methods: formData.methods, //请求方式 post get delete
+      unique_auth: formData.unique_auth, //唯一标识
+      pid: formData.parentId, //父id
+      sort: formData.sort, //排序
+      auth_type: formData.type === 2 ? 2 : 1, //权限类型 1 菜单,2功能
+      is_show: formData.status, //是否禁用1启用0禁用
+      is_show_path: formData.meta.hidden ? 1 : 0, //是否显示路由0显示1隐藏
+      extend: formData
+    }
+    if (actionType.value === 'edit') {
+      await setMenuApi(data)
+    } else if (actionType.value === '') {
+      await newMenuApi(data)
+    }
+    ElMessage({
+      showClose: true,
+      message: '保存成功',
+      type: 'success'
+    })
+    getList()
+    saveLoading.value = false
+    dialogVisible.value = false
   }
 }
 </script>
@@ -192,8 +273,13 @@ const save = async () => {
     />
   </ContentWrap>
 
-  <Dialog v-model="dialogVisible" :title="dialogTitle">
-    <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%">
+    <Write
+      :menuList="dataList"
+      v-if="actionType !== 'detail'"
+      ref="writeRef"
+      :current-row="currentRow"
+    />
 
     <Detail v-if="actionType === 'detail'" :current-row="currentRow" />
 

+ 2 - 2
src/views/Authorization/Menu/components/AddButtonPermission.vue

@@ -12,7 +12,7 @@ const { required } = useValidator()
 const formSchema = reactive<FormSchema[]>([
   {
     field: 'label',
-    label: 'label',
+    label: '名称',
     component: 'Input',
     colProps: {
       span: 24
@@ -20,7 +20,7 @@ const formSchema = reactive<FormSchema[]>([
   },
   {
     field: 'value',
-    label: 'value',
+    label: '参数',
     component: 'Input',
     colProps: {
       span: 24

+ 50 - 34
src/views/Authorization/Menu/components/Detail.vue

@@ -1,60 +1,73 @@
 <script setup lang="tsx">
-import { PropType, ref } from 'vue'
+import { PropType, ref, computed } from 'vue'
 import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
 import { Icon } from '@/components/Icon'
 import { ElTag } from 'element-plus'
 
-defineProps({
+const props = defineProps({
   currentRow: {
     type: Object as PropType<any>,
     default: () => undefined
   }
 })
-
+//
+const ishidden = computed(() => props.currentRow?.type === 1)
 const renderTag = (enable?: boolean) => {
   return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
 }
 
-const detailSchema = ref<DescriptionsSchema[]>([
+const menu = [
   {
-    field: 'type',
-    label: '菜单类型',
-    span: 24,
-    slots: {
-      default: (data) => {
-        const type = data.type
-        return <>{type === 1 ? '菜单' : '目录'}</>
-      }
-    }
+    field: 'meta.title',
+    label: '接口名称'
   },
   {
-    field: 'parentName',
-    label: '父级菜单'
+    field: 'apiUrl',
+    label: '接口地址'
   },
   {
-    field: 'meta.title',
-    label: '菜单名称'
+    field: 'methods',
+    label: '请求方式'
   },
   {
-    field: 'component',
-    label: '组件',
+    field: 'unique_auth',
+    label: '权限标识'
+  },
+  {
+    field: 'status',
+    label: '状态',
     slots: {
       default: (data) => {
-        const component = data.component
-        return <>{component === '#' ? '顶级目录' : component === '##' ? '子目录' : component}</>
+        return renderTag(data.status)
       }
     }
+  }
+]
+const api = [
+  {
+    field: 'type',
+    label: '类型',
+    span: 24,
+    slots: {
+      default: (data) => {
+        return <>{data.type === 1 ? '菜单' : '接口'}</>
+      }
+    }
+  },
+  {
+    field: 'meta.title',
+    label: '菜单名称'
   },
   {
-    field: 'name',
-    label: '组件名称'
+    field: 'unique_auth',
+    label: '权限标识'
   },
   {
     field: 'meta.icon',
     label: '图标',
     slots: {
       default: (data) => {
-        const icon = data.icon
+        const icon = data.meta.icon
         if (icon) {
           return (
             <>
@@ -94,11 +107,11 @@ const detailSchema = ref<DescriptionsSchema[]>([
     }
   },
   {
-    field: 'menuState',
+    field: 'status',
     label: '菜单状态',
     slots: {
       default: (data) => {
-        return renderTag(data.menuState)
+        return renderTag(data.status)
       }
     }
   },
@@ -107,7 +120,8 @@ const detailSchema = ref<DescriptionsSchema[]>([
     label: '是否隐藏',
     slots: {
       default: (data) => {
-        return renderTag(data.enableHidden)
+        console.log(data, 'data')
+        return renderTag(data.meta.hidden)
       }
     }
   },
@@ -116,7 +130,7 @@ const detailSchema = ref<DescriptionsSchema[]>([
     label: '是否一直显示',
     slots: {
       default: (data) => {
-        return renderTag(data.enableDisplay)
+        return renderTag(data.meta.alwaysShow)
       }
     }
   },
@@ -125,7 +139,7 @@ const detailSchema = ref<DescriptionsSchema[]>([
     label: '是否清除缓存',
     slots: {
       default: (data) => {
-        return renderTag(data.enableCleanCache)
+        return renderTag(data.meta.noCache)
       }
     }
   },
@@ -134,7 +148,7 @@ const detailSchema = ref<DescriptionsSchema[]>([
     label: '是否显示面包屑',
     slots: {
       default: (data) => {
-        return renderTag(data.enableShowCrumb)
+        return renderTag(data.meta.breadcrumb)
       }
     }
   },
@@ -143,7 +157,7 @@ const detailSchema = ref<DescriptionsSchema[]>([
     label: '是否固定标签页',
     slots: {
       default: (data) => {
-        return renderTag(data.enablePinnedTab)
+        return renderTag(data.meta.affix)
       }
     }
   },
@@ -152,7 +166,7 @@ const detailSchema = ref<DescriptionsSchema[]>([
     label: '是否隐藏标签页',
     slots: {
       default: (data) => {
-        return renderTag(data.enableHiddenTab)
+        return renderTag(data.meta.noTagsView)
       }
     }
   },
@@ -161,11 +175,13 @@ const detailSchema = ref<DescriptionsSchema[]>([
     label: '是否可跳转',
     slots: {
       default: (data) => {
-        return renderTag(data.enableSkip)
+        return renderTag(data.meta.canTo)
       }
     }
   }
-])
+]
+
+const detailSchema = ref<DescriptionsSchema[]>(ishidden.value ? api : menu)
 </script>
 
 <template>

+ 203 - 114
src/views/Authorization/Menu/components/Write.vue

@@ -1,15 +1,15 @@
 <script setup lang="tsx">
 import { Form, FormSchema } from '@/components/Form'
 import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch, ref, unref } from 'vue'
+import { PropType, reactive, watch, ref, nextTick } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
 import { useI18n } from '@/hooks/web/useI18n'
-import { getMenuListApi } from '@/api/menu'
+import { cloneDeep } from 'lodash-es'
 import { ElButton, ElInput, ElPopconfirm, ElTable, ElTableColumn, ElTag } from 'element-plus'
 import AddButtonPermission from './AddButtonPermission.vue'
+import addApiUrl from './addApiUrl.vue'
 import { BaseButton } from '@/components/Button'
-import { cloneDeep } from 'lodash-es'
-
+import { Icon } from '@/components/Icon'
 const { t } = useI18n()
 
 const { required } = useValidator()
@@ -18,6 +18,10 @@ const props = defineProps({
   currentRow: {
     type: Object as PropType<any>,
     default: () => null
+  },
+  menuList: {
+    type: Array,
+    default: () => []
   }
 })
 
@@ -44,7 +48,8 @@ const handleSave = async () => {
 }
 
 const showDrawer = ref(false)
-// 存储正在编辑的行的数据
+const showApiDrawer = ref(false)
+
 const permissionEditingRow = ref<any>(null)
 
 const formSchema = reactive<FormSchema[]>([
@@ -52,53 +57,29 @@ const formSchema = reactive<FormSchema[]>([
     field: 'type',
     label: '菜单类型',
     component: 'RadioButton',
-    value: 0,
+    value: 1,
     colProps: {
       span: 24
     },
     componentProps: {
       options: [
-        {
-          label: '目录',
-          value: 0
-        },
         {
           label: '菜单',
           value: 1
+        },
+        {
+          label: '接口',
+          value: 2
         }
       ],
       on: {
         change: async (val: number) => {
-          const formData = await getFormData()
           if (val === 1) {
-            setSchema([
-              {
-                field: 'component',
-                path: 'componentProps.disabled',
-                value: false
-              }
-            ])
-            setValues({
-              component: unref(cacheComponent)
-            })
-          } else {
-            setSchema([
-              {
-                field: 'component',
-                path: 'componentProps.disabled',
-                value: true
-              }
-            ])
-
-            if (formData.parentId === void 0) {
-              setValues({
-                component: '#'
-              })
-            } else {
-              setValues({
-                component: '##'
-              })
-            }
+            //显示隐藏部分
+            changeType(false)
+          } else if (val === 2) {
+            //隐藏显示部分
+            changeType(true)
           }
         }
       }
@@ -119,75 +100,107 @@ const formSchema = reactive<FormSchema[]>([
       expandOnClickNode: false,
       checkStrictly: true,
       checkOnClickNode: true,
-      clearable: true,
-      on: {
-        change: async (val: number) => {
-          const formData = await getFormData()
-          if (val && formData.type === 0) {
-            setValues({
-              component: '##'
-            })
-          } else if (!val && formData.type === 0) {
-            setValues({
-              component: '#'
-            })
-          } else if (formData.type === 1) {
-            setValues({
-              component: unref(cacheComponent) ?? ''
-            })
-          }
-        }
-      }
+      clearable: true
     },
     optionApi: async () => {
-      const res = await getMenuListApi()
-      return res.data.list || []
+      console.log(props.menuList, 'props.menuList')
+      return props.menuList || []
     }
   },
   {
     field: 'meta.title',
     label: t('menu.menuName'),
-    component: 'Input'
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入菜单名称'
+    }
   },
   {
-    field: 'component',
-    label: '组件',
+    field: 'unique_auth',
+    label: '权限标识',
     component: 'Input',
-    value: '#',
     componentProps: {
-      disabled: true,
-      placeholder: '#为顶级目录,##为子目录',
-      on: {
-        change: (val: string) => {
-          cacheComponent.value = val
-        }
+      placeholder: '请输入权限标识'
+    }
+  },
+  {
+    field: 'apiUrl',
+    label: '接口地址',
+    remove: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入接口地址',
+      slots: {
+        append: () => (
+          <ElButton onClick={() => (showApiDrawer.value = true)}>
+            <Icon icon="vi-ep:expand"></Icon>
+          </ElButton>
+        )
       }
     }
   },
   {
-    field: 'name',
-    label: t('menu.name'),
-    component: 'Input'
+    field: 'methods',
+    label: '请求方式',
+    component: 'Select',
+    remove: true,
+    componentProps: {
+      options: [
+        {
+          label: 'POST',
+          value: 'POST'
+        },
+        {
+          label: 'GET',
+          value: 'GET'
+        },
+        {
+          label: 'PUT',
+          value: 'PUT'
+        },
+        {
+          label: 'DELETE',
+          value: 'DELETE'
+        }
+      ]
+    }
   },
+
   {
     field: 'meta.icon',
     label: t('menu.icon'),
-    component: 'Input'
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入图标svg类名'
+    }
   },
   {
     field: 'path',
     label: t('menu.path'),
-    component: 'Input'
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入路由地址'
+    }
   },
   {
     field: 'meta.activeMenu',
     label: t('menu.activeMenu'),
     component: 'Input'
   },
+  {
+    field: 'sort',
+    label: '排序',
+    component: 'Input',
+    componentProps: {
+      type: 'number',
+      placeholder: '请输入数字越大越靠前'
+    }
+  },
   {
     field: 'status',
     label: t('menu.status'),
     component: 'Select',
+    value: 1,
     componentProps: {
       options: [
         {
@@ -202,7 +215,7 @@ const formSchema = reactive<FormSchema[]>([
     }
   },
   {
-    field: 'permissionList',
+    field: 'meta.permissionList',
     label: t('menu.permission'),
     component: 'CheckboxGroup',
     colProps: {
@@ -224,7 +237,7 @@ const formSchema = reactive<FormSchema[]>([
               <ElTableColumn type="index" prop="id" />
               <ElTableColumn
                 prop="value"
-                label="Value"
+                label="参数"
                 v-slots={{
                   default: ({ row }: any) =>
                     permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
@@ -236,7 +249,7 @@ const formSchema = reactive<FormSchema[]>([
               />
               <ElTableColumn
                 prop="label"
-                label="Label"
+                label="名称"
                 v-slots={{
                   default: ({ row }: any) =>
                     permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
@@ -249,7 +262,7 @@ const formSchema = reactive<FormSchema[]>([
                 }}
               />
               <ElTableColumn
-                label="Operations"
+                label="操作"
                 width="180"
                 v-slots={{
                   default: ({ row }: any) =>
@@ -302,6 +315,7 @@ const formSchema = reactive<FormSchema[]>([
   {
     field: 'meta.breadcrumb',
     label: t('menu.breadcrumb'),
+    value: true,
     component: 'Switch'
   },
   {
@@ -318,11 +332,19 @@ const formSchema = reactive<FormSchema[]>([
     field: 'meta.canTo',
     label: t('menu.canTo'),
     component: 'Switch'
+  },
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'Input',
+    hidden: true,
+    componentProps: {
+      type: 'number'
+    }
   }
 ])
 
 const rules = reactive({
-  component: [required()],
   path: [required()],
   'meta.title': [required()]
 })
@@ -345,45 +367,20 @@ const cacheComponent = ref('')
 
 watch(
   () => props.currentRow,
-  (value) => {
+  async (value) => {
     if (!value) return
     const currentRow = cloneDeep(value)
+    console.log(currentRow, 'currentRow')
     cacheComponent.value = currentRow.type === 1 ? currentRow.component : ''
-    if (currentRow.parentId === 0) {
-      setSchema([
-        {
-          field: 'component',
-          path: 'componentProps.disabled',
-          value: true
-        }
-      ])
-    } else {
-      setSchema([
-        {
-          field: 'component',
-          path: 'componentProps.disabled',
-          value: false
-        }
-      ])
-    }
+    setValues(currentRow)
+    await nextTick()
     if (currentRow.type === 1) {
-      setSchema([
-        {
-          field: 'component',
-          path: 'componentProps.disabled',
-          value: false
-        }
-      ])
-    } else {
-      setSchema([
-        {
-          field: 'component',
-          path: 'componentProps.disabled',
-          value: true
-        }
-      ])
+      //显示隐藏部分
+      changeType(false)
+    } else if (currentRow.type === 2) {
+      //隐藏显示部分
+      changeType(true)
     }
-    setValues(currentRow)
   },
   {
     deep: true,
@@ -391,6 +388,86 @@ watch(
   }
 )
 
+const changeType = (bol: boolean) => {
+  setSchema([
+    {
+      field: 'meta.icon',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'path',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.activeMenu',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'name',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.permissionList',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.hidden',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.alwaysShow',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.noCache',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.breadcrumb',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.affix',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.noTagsView',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'meta.canTo',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'permissionList',
+      path: 'remove',
+      value: bol
+    },
+    {
+      field: 'apiUrl',
+      path: 'remove',
+      value: !bol
+    },
+    {
+      field: 'methods',
+      path: 'remove',
+      value: !bol
+    }
+  ])
+}
+
 defineExpose({
   submit
 })
@@ -401,9 +478,21 @@ const confirm = async (data: any) => {
     permissionList: [...(formData?.permissionList || []), data]
   })
 }
+
+const openApiSeach = (res: any) => {
+  setValues({
+    apiUrl: res.rule,
+    methods: res.method,
+    meta: {
+      title: res.real_name
+    }
+  })
+  showApiDrawer.value = false
+}
 </script>
 
 <template>
   <Form :rules="rules" @register="formRegister" :schema="formSchema" />
   <AddButtonPermission v-model="showDrawer" @confirm="confirm" />
+  <addApiUrl v-model="showApiDrawer" @confirm="openApiSeach" />
 </template>

+ 118 - 0
src/views/Authorization/Menu/components/addApiUrl.vue

@@ -0,0 +1,118 @@
+<script setup lang="tsx">
+import { FormSchema } from '@/components/Form'
+import { apiRuleList } from '@/api/system/menu'
+import { ElDrawer } from 'element-plus'
+import { reactive, ref, watch } from 'vue'
+import { useTable } from '@/hooks/web/useTable'
+import { Table, TableColumn } from '@/components/Table'
+import { Search } from '@/components/Search'
+import { BaseButton } from '@/components/Button'
+import { ContentWrap } from '@/components/ContentWrap'
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await apiRuleList()
+    if (searchParams.value) {
+      const list: Record<string, any>[] = [] // 显式指定类型
+      res.data.forEach((item: any) => {
+        console.log(
+          item.real_name.indexOf(searchParams.value),
+          'item.real_name.indexOf(searchParams.value)'
+        )
+        if (item.real_name.indexOf(searchParams.value) !== -1) {
+          list.push(item)
+        }
+      })
+      return {
+        list: list || []
+      }
+    } else {
+      return {
+        list: res.data || []
+      }
+    }
+  }
+})
+const { getList } = tableMethods
+
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'title',
+    label: '接口名',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入接口名称'
+    }
+  }
+])
+const { dataList, loading } = tableState
+
+const searchParams = ref('')
+const setSearchParams = (data: any) => {
+  searchParams.value = data.title
+  getList()
+}
+
+const modelValue = defineModel<boolean>()
+
+const tableColumns = reactive<TableColumn[]>([
+  {
+    field: 'method',
+    label: '请求',
+    width: '60px'
+  },
+  {
+    field: 'rule',
+    label: '接口地址',
+    width: '330px'
+  },
+  {
+    field: 'real_name',
+    label: '接口名'
+  },
+  {
+    field: 'action',
+    label: '操作',
+    width: '80px',
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <BaseButton type="primary" onClick={() => confirm(row)}>
+              选择
+            </BaseButton>
+          </>
+        )
+      }
+    }
+  }
+])
+
+const emit = defineEmits(['confirm'])
+const confirm = (row: any) => {
+  emit('confirm', row)
+}
+</script>
+
+<template>
+  <ElDrawer v-model="modelValue" title="选择接口" size="700px">
+    <ContentWrap>
+      <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+
+      <Table
+        :columns="tableColumns"
+        default-expand-all
+        node-key="id"
+        :data="dataList"
+        :loading="loading"
+        @register="tableRegister"
+      />
+    </ContentWrap>
+    <template #footer>
+      <div>
+        <BaseButton @click="() => (modelValue = false)">关闭</BaseButton>
+      </div>
+    </template>
+  </ElDrawer>
+</template>

+ 101 - 23
src/views/Authorization/Role/Role.vue

@@ -1,15 +1,14 @@
 <script setup lang="tsx">
 import { reactive, ref, unref } from 'vue'
-import { getRoleListApi } from '@/api/role'
+import { getRoleListApi, addRoleApi, setRoleApi, delRoleApi } from '@/api/system/role'
 import { useTable } from '@/hooks/web/useTable'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table, TableColumn } from '@/components/Table'
-import { ElTag } from 'element-plus'
+import { ElTag, ElMessage, ElMessageBox } from 'element-plus'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
 import { ContentWrap } from '@/components/ContentWrap'
 import Write from './components/Write.vue'
-import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
 import { BaseButton } from '@/components/Button'
 
@@ -17,7 +16,16 @@ const { t } = useI18n()
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
-    const res = await getRoleListApi()
+    const { pageSize, currentPage } = tableState
+    const res = await getRoleListApi({
+      page: unref(currentPage) || 1,
+      limit: unref(pageSize) || 10,
+      role_name: searchParams.value.role_name || '',
+      status:
+        searchParams.value.status !== undefined
+          ? (searchParams.value.status as string | number)
+          : ''
+    })
     return {
       list: res.data.list || [],
       total: res.data.total
@@ -25,17 +33,16 @@ const { tableRegister, tableState, tableMethods } = useTable({
   }
 })
 
-const { dataList, loading, total } = tableState
+const { dataList, loading, total, currentPage } = tableState
 const { getList } = tableMethods
 
 const tableColumns = reactive<TableColumn[]>([
   {
-    field: 'index',
-    label: t('userDemo.index'),
-    type: 'index'
+    field: 'id',
+    label: 'ID'
   },
   {
-    field: 'roleName',
+    field: 'role_name',
     label: t('role.roleName')
   },
   {
@@ -54,12 +61,9 @@ const tableColumns = reactive<TableColumn[]>([
     }
   },
   {
-    field: 'createTime',
-    label: t('tableDemo.displayTime')
-  },
-  {
-    field: 'remark',
-    label: t('userDemo.remark')
+    field: 'rules',
+    label: '权限',
+    minWidth: '100px'
   },
   {
     field: 'action',
@@ -73,10 +77,9 @@ const tableColumns = reactive<TableColumn[]>([
             <BaseButton type="primary" onClick={() => action(row, 'edit')}>
               {t('exampleDemo.edit')}
             </BaseButton>
-            <BaseButton type="success" onClick={() => action(row, 'detail')}>
-              {t('exampleDemo.detail')}
+            <BaseButton type="danger" onClick={() => delAction(row)}>
+              {t('exampleDemo.del')}
             </BaseButton>
-            <BaseButton type="danger">{t('exampleDemo.del')}</BaseButton>
           </>
         )
       }
@@ -86,13 +89,37 @@ const tableColumns = reactive<TableColumn[]>([
 
 const searchSchema = reactive<FormSchema[]>([
   {
-    field: 'roleName',
+    field: 'role_name',
     label: t('role.roleName'),
     component: 'Input'
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'Select',
+    componentProps: {
+      options: [
+        {
+          label: '全部',
+          value: ''
+        },
+        {
+          label: '不显示',
+          value: 0
+        },
+        {
+          label: '显示',
+          value: 1
+        }
+      ]
+    }
   }
 ])
 
-const searchParams = ref({})
+const searchParams = ref({
+  status: 1,
+  role_name: ''
+})
 const setSearchParams = (data: any) => {
   searchParams.value = data
   getList()
@@ -115,6 +142,24 @@ const action = (row: any, type: string) => {
   dialogVisible.value = true
 }
 
+const delAction = (row: any) => {
+  ElMessageBox.confirm('删除后无法恢复,是否删除?', '删除', {
+    confirmButtonText: '删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      delRoleApi({ id: row.id }).then(() => {
+        ElMessage({
+          message: '删除成功',
+          type: 'success'
+        })
+        getList()
+      })
+    })
+    .catch(() => {})
+}
+
 const AddAction = () => {
   dialogTitle.value = t('exampleDemo.add')
   currentRow.value = undefined
@@ -122,15 +167,49 @@ const AddAction = () => {
   actionType.value = ''
 }
 
+const deepMenu = (data: Array<any>) => {
+  const menu: any[] = []
+  data.forEach((item) => {
+    menu.push(item.id)
+    if (item.children) {
+      menu.push(...deepMenu(item.children))
+    }
+  })
+  return menu
+}
 const save = async () => {
   const write = unref(writeRef)
   const formData = await write?.submit()
+  console.log(formData, 'data')
   if (formData) {
     saveLoading.value = true
-    setTimeout(() => {
+    try {
+      let res: any = {}
+      const data = {
+        checked_menus: deepMenu(formData.menu),
+        role_name: formData.role_name,
+        status: formData.status,
+        id: currentRow.value ? currentRow.value.id : ''
+      }
+      if (actionType.value == 'edit') {
+        res = await setRoleApi(data)
+      } else {
+        res = await addRoleApi(data)
+      }
+      ElMessage({
+        message: '添加成功',
+        type: 'success'
+      })
+      if (res) {
+        currentPage.value = 1
+        getList()
+      }
+    } catch (error) {
+      console.log(error)
+    } finally {
       saveLoading.value = false
       dialogVisible.value = false
-    }, 1000)
+    }
   }
 }
 </script>
@@ -156,7 +235,6 @@ const save = async () => {
 
   <Dialog v-model="dialogVisible" :title="dialogTitle">
     <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
-    <Detail v-else :current-row="currentRow" />
 
     <template #footer>
       <BaseButton

+ 0 - 106
src/views/Authorization/Role/components/Detail.vue

@@ -1,106 +0,0 @@
-<script setup lang="tsx">
-import { PropType, ref, unref, nextTick } from 'vue'
-import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
-import { ElTag, ElTree } from 'element-plus'
-import { findIndex } from '@/utils'
-import { getMenuListApi } from '@/api/menu'
-
-defineProps({
-  currentRow: {
-    type: Object as PropType<any>,
-    default: () => undefined
-  }
-})
-
-const filterPermissionName = (value: string) => {
-  const index = findIndex(unref(currentTreeData)?.permissionList || [], (item) => {
-    return item.value === value
-  })
-  return (unref(currentTreeData)?.permissionList || [])[index].label ?? ''
-}
-
-const renderTag = (enable?: boolean) => {
-  return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
-}
-
-const treeRef = ref<typeof ElTree>()
-
-const currentTreeData = ref()
-const nodeClick = (treeData: any) => {
-  currentTreeData.value = treeData
-}
-
-const treeData = ref<any[]>([])
-const getMenuList = async () => {
-  const res = await getMenuListApi()
-  if (res) {
-    treeData.value = res.data.list
-    await nextTick()
-  }
-}
-getMenuList()
-
-const detailSchema = ref<DescriptionsSchema[]>([
-  {
-    field: 'roleName',
-    label: '角色名称'
-  },
-  {
-    field: 'status',
-    label: '状态',
-    slots: {
-      default: (data: any) => {
-        return renderTag(data.status)
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: '备注',
-    span: 24
-  },
-  {
-    field: 'permissionList',
-    label: '菜单分配',
-    span: 24,
-    slots: {
-      default: () => {
-        return (
-          <>
-            <div class="flex w-full">
-              <div class="flex-1">
-                <ElTree
-                  ref={treeRef}
-                  node-key="id"
-                  props={{ children: 'children', label: 'title' }}
-                  highlight-current
-                  expand-on-click-node={false}
-                  data={treeData.value}
-                  onNode-click={nodeClick}
-                >
-                  {{
-                    default: (data) => {
-                      return <span>{data?.data?.title}</span>
-                    }
-                  }}
-                </ElTree>
-              </div>
-              <div class="flex-1">
-                {unref(currentTreeData)
-                  ? unref(currentTreeData)?.meta?.permission?.map((v: string) => {
-                      return <ElTag class="ml-2 mt-2">{filterPermissionName(v)}</ElTag>
-                    })
-                  : null}
-              </div>
-            </div>
-          </>
-        )
-      }
-    }
-  }
-])
-</script>
-
-<template>
-  <Descriptions :schema="detailSchema" :data="currentRow || {}" />
-</template>

+ 38 - 33
src/views/Authorization/Role/components/Write.vue

@@ -1,13 +1,16 @@
 <script setup lang="tsx">
 import { Form, FormSchema } from '@/components/Form'
 import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch, ref, unref, nextTick } from 'vue'
+import { PropType, reactive, watch, ref, unref, nextTick, onMounted } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ElTree, ElCheckboxGroup, ElCheckbox } from 'element-plus'
-import { getMenuListApi } from '@/api/menu'
+import { getAdminInfo } from '@/api/login'
+import { getRoleApi } from '@/api/system/role'
 import { filter, eachTree } from '@/utils/tree'
+import { roluData } from '@/utils/analysis'
 import { findIndex } from '@/utils'
+import { MenuData } from '@/api/system/menu/types'
 
 const { t } = useI18n()
 
@@ -22,9 +25,13 @@ const props = defineProps({
 
 const treeRef = ref<typeof ElTree>()
 
+onMounted(async () => {
+  getMenuList()
+})
+
 const formSchema = ref<FormSchema[]>([
   {
-    field: 'roleName',
+    field: 'role_name',
     label: t('role.roleName'),
     component: 'Input'
   },
@@ -46,7 +53,7 @@ const formSchema = ref<FormSchema[]>([
     }
   },
   {
-    field: 'menu',
+    field: 'checked_menus',
     label: t('role.menu'),
     colProps: {
       span: 24
@@ -99,7 +106,7 @@ const nodeClick = (treeData: any) => {
 }
 
 const rules = reactive({
-  roleName: [required()],
+  role_name: [required()],
   role: [required()],
   status: [required()]
 })
@@ -107,41 +114,39 @@ const rules = reactive({
 const { formRegister, formMethods } = useForm()
 const { setValues, getFormData, getElFormExpose } = formMethods
 
-const treeData = ref([])
+const treeData = ref<MenuData[]>([])
 const getMenuList = async () => {
-  const res = await getMenuListApi()
-  if (res) {
-    treeData.value = res.data.list
-    if (!props.currentRow) return
-    await nextTick()
-    const checked: any[] = []
-    eachTree(props.currentRow.menu, (v) => {
-      checked.push({
-        id: v.id,
-        permission: v.meta?.permission || []
+  const res = await getAdminInfo()
+  const list = roluData(res.data.role)
+  if (list) {
+    treeData.value = list
+
+    if (props.currentRow?.id) {
+      const checked: any[] = []
+      const rolelist = await getRoleApi({ id: props.currentRow?.id })
+
+      eachTree(rolelist.data.rules.split(','), (v) => {
+        checked.push({
+          id: v,
+          permission: v.meta?.permission || []
+        })
       })
-    })
-    eachTree(treeData.value, (v) => {
-      const index = findIndex(checked, (item) => {
-        return item.id === v.id
+      eachTree(treeData.value, (v) => {
+        const index = findIndex(checked, (item) => {
+          return item.id === v.id
+        })
+        if (index > -1) {
+          const meta = { ...(v.meta || {}) }
+          meta.permission = checked[index].permission
+          v.meta = meta
+        }
       })
-      if (index > -1) {
-        const meta = { ...(v.meta || {}) }
-        meta.permission = checked[index].permission
-        v.meta = meta
+      for (const item of checked) {
+        unref(treeRef)?.setChecked(item.id, true, false)
       }
-    })
-    for (const item of checked) {
-      unref(treeRef)?.setChecked(item.id, true, false)
     }
-    // unref(treeRef)?.setCheckedKeys(
-    //   checked.map((v) => v.id),
-    //   false
-    // )
   }
 }
-getMenuList()
-
 const submit = async () => {
   const elForm = await getElFormExpose()
   const valid = await elForm?.validate().catch((err) => {

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

@@ -11,7 +11,7 @@ import { Search } from '@/components/Search'
 import Write from './components/Write.vue'
 import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
-import { getRoleListApi } from '@/api/role'
+import { getRoleListApi } from '@/api/system/role'
 import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
 import { BaseButton } from '@/components/Button'
 

+ 0 - 1
src/views/Login/Login.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-// @ts-ignore
 import { LoginForm, RegisterForm } from './components'
 import { ThemeSwitch } from '@/components/ThemeSwitch'
 import { LocaleDropdown } from '@/components/LocaleDropdown'

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

@@ -2,18 +2,20 @@
 import { reactive, ref, watch, onMounted, unref } from 'vue'
 import { Form, FormSchema } from '@/components/Form'
 import { useI18n } from '@/hooks/web/useI18n'
-import { ElCheckbox, ElLink } from 'element-plus'
+import { ElCheckbox } from 'element-plus'
 import { useForm } from '@/hooks/web/useForm'
-import { loginApi, getTestRoleApi, getAdminRoleApi } from '@/api/login'
+import { isCaptchaImg, loginApi } from '@/api/login'
 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 { useValidator } from '@/hooks/web/useValidator'
-import { Icon } from '@/components/Icon'
 import { useUserStore } from '@/store/modules/user'
 import { BaseButton } from '@/components/Button'
+import Verify from '@/components/verifition/Verify.vue'
+import { ElLoading } from 'element-plus'
+import { MenuData } from '@/api/system/menu/types'
 
 const { required } = useValidator()
 
@@ -49,7 +51,7 @@ const schema = reactive<FormSchema[]>([
     }
   },
   {
-    field: 'account',
+    field: 'username',
     label: t('login.username'),
     // value: 'admin',
     component: 'Input',
@@ -61,7 +63,7 @@ const schema = reactive<FormSchema[]>([
     }
   },
   {
-    field: 'pwd',
+    field: 'password',
     label: t('login.password'),
     // value: 'admin',
     component: 'InputPassword',
@@ -121,16 +123,13 @@ const schema = reactive<FormSchema[]>([
     }
   }
 ])
-
-const iconSize = 30
-
 const remember = ref(userStore.getRememberMe)
 
 const initLoginInfo = () => {
   const loginInfo = userStore.getLoginInfo
   if (loginInfo) {
-    const { account, pwd } = loginInfo
-    setValues({ account, pwd })
+    const { username, password } = loginInfo
+    setValues({ username, password })
   }
 }
 onMounted(() => {
@@ -142,10 +141,6 @@ const { getFormData, getElFormExpose, setValues } = formMethods
 
 const loading = ref(false)
 
-const iconColor = '#999'
-
-const hoverColor = 'var(--el-color-primary)'
-
 const redirect = ref<string>('')
 
 watch(
@@ -157,43 +152,26 @@ watch(
     immediate: true
   }
 )
+const verify = ref()
 
 // 登录
 const signIn = async () => {
+  //获取表单对象
   const formRef = await getElFormExpose()
   await formRef?.validate(async (isValid) => {
     if (isValid) {
       loading.value = true
       const formData = await getFormData<UserType>()
-      console.log(formData)
+
       try {
-        console.log(formData, 'formData')
-
-        const res = await loginApi(formData)
-        console.log(res, 'res')
-        if (res) {
-          // 是否记住我
-          if (unref(remember)) {
-            userStore.setLoginInfo({
-              account: formData.account,
-              pwd: formData.pwd
-            })
-          } else {
-            userStore.setLoginInfo(undefined)
-          }
-          userStore.setRememberMe(unref(remember))
-          userStore.setUserInfo(res.data)
-          // 是否使用动态路由
-          if (appStore.getDynamicRouter) {
-            getRole()
-          } else {
-            await permissionStore.generateRoutes('static').catch(() => {})
-            permissionStore.getAddRouters.forEach((route) => {
-              addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
-            })
-            permissionStore.setIsAddRouters(true)
-            push({ path: redirect.value || permissionStore.addRouters[0].path })
-          }
+        const captcha = await isCaptchaImg({ account: formData.username })
+        console.log(captcha, 'isCaptchaImg')
+        console.log(verify, 'verify')
+        // 判断是否需要验证码
+        if (captcha.data.is_captcha) {
+          verify.value.show()
+        } else {
+          loginForm()
         }
       } finally {
         loading.value = false
@@ -202,31 +180,112 @@ const signIn = async () => {
   })
 }
 
-// 获取角色信息
-const getRole = async () => {
+const loginForm = async (captcha?: any) => {
+  const loading = ElLoading.service({
+    lock: true,
+    text: '登陆中...',
+    background: 'rgba(0, 0, 0, 0.7)'
+  })
+  //获取表单数据
   const formData = await getFormData<UserType>()
-  const params = {
-    roleName: formData.account
+  const redata = {
+    account: formData.username,
+    pwd: formData.password,
+    captchaType: '',
+    captchaVerification: ''
+  }
+  if (captcha) {
+    redata.captchaType = 'blockPuzzle'
+    redata.captchaVerification = captcha.captchaVerification
   }
-  const res =
-    appStore.getDynamicRouter && appStore.getServerDynamicRouter
-      ? await getAdminRoleApi(params)
-      : await getTestRoleApi(params)
+  //登录
+  const res = await loginApi(redata)
+
   if (res) {
-    const routers = res.data || []
-    userStore.setRoleRouters(routers)
-    appStore.getDynamicRouter && appStore.getServerDynamicRouter
-      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
-      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
-
-    permissionStore.getAddRouters.forEach((route) => {
-      addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
-    })
-    permissionStore.setIsAddRouters(true)
-    push({ path: redirect.value || permissionStore.addRouters[0].path })
+    // 是否记住我
+    if (unref(remember)) {
+      userStore.setLoginInfo({
+        username: formData.username,
+        password: formData.password
+      })
+    } else {
+      userStore.setLoginInfo(undefined)
+    }
+    userStore.setRememberMe(unref(remember))
+    userStore.setUserInfo(res.data)
+    userStore.setToken(res.data.token)
+    console.log(appStore.getDynamicRouter, 'appStore.getDynamicRouter')
+    // 是否使用动态路由
+    if (appStore.getDynamicRouter) {
+      loading.close()
+      getRole(res.data.menus)
+    } else {
+      await permissionStore.generateRoutes('static').catch(() => {})
+      permissionStore.getAddRouters.forEach((route) => {
+        addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
+      })
+      permissionStore.setIsAddRouters(true)
+      push({ path: redirect.value || permissionStore.addRouters[0].path })
+      loading.close()
+    }
+  } else {
+    loading.close()
   }
 }
 
+// 获取角色信息
+const getRole = async (menus: any) => {
+  const loading = ElLoading.service({
+    lock: true,
+    text: '获取路由...',
+    background: 'rgba(0, 0, 0, 0.7)'
+  })
+  const list = menus.map(function arrMap(ls): MenuData {
+    const ar: MenuData = {
+      path: ls.path,
+      status: ls.extend.status,
+      meta: {
+        icon: ls.icon,
+        title: ls.title,
+        activeMenu: !!ls.extend?.meta.activeMenu,
+        alwaysShow: !!ls.extend?.meta.alwaysShow,
+        noCache: !!ls.extend?.meta.noCache,
+        breadcrumb: !!ls.extend?.meta.breadcrumb,
+        affix: !!ls.extend?.meta.affix,
+        noTagsView: !!ls.extend?.meta.noTagsView,
+        canTo: !!ls.extend?.meta.canTo,
+        hidden: !!ls.is_show_path,
+        permissionList: ls.extend?.permissionList
+      }
+    }
+    if (ls.children && ls.children.length > 0) {
+      ar.children = ls.children.map((lss) => {
+        return arrMap(lss)
+      })
+    }
+
+    return ar
+  })
+  const routers = list || []
+  userStore.setRoleRouters(routers)
+  console.log(appStore.getServerDynamicRouter, 'appStore.getServerDynamicRouter')
+  appStore.getDynamicRouter && appStore.getServerDynamicRouter
+    ? await permissionStore.generateRoutes('server', routers).catch(() => {})
+    : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
+  permissionStore.getAddRouters.forEach((route) => {
+    addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
+  })
+  permissionStore.setIsAddRouters(true)
+  console.log(permissionStore.addRouters, 'permissionStore.addRouters')
+  console.log(redirect.value, 'redirect.value')
+  push({ path: redirect.value || permissionStore.addRouters[0].path })
+  loading.close()
+}
+
+const success = (params) => {
+  loginForm(params)
+}
+
 // 去注册页面
 const toRegister = () => {
   emit('to-register')
@@ -234,13 +293,21 @@ const toRegister = () => {
 </script>
 
 <template>
-  <Form
-    :schema="schema"
-    :rules="rules"
-    label-position="top"
-    hide-required-asterisk
-    size="large"
-    class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
-    @register="formRegister"
-  />
+  <div>
+    <Verify
+      @success="success"
+      captchaType="blockPuzzle"
+      :imgSize="{ width: '330px', height: '155px' }"
+      ref="verify"
+    />
+    <Form
+      :schema="schema"
+      :rules="rules"
+      label-position="top"
+      hide-required-asterisk
+      size="large"
+      class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
+      @register="formRegister"
+    />
+  </div>
 </template>

+ 6 - 7
vite.config.ts

@@ -144,13 +144,12 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       port: 4000,
       proxy: {
         // 选项写法
-        // '/api': {
-        //   target: 'http://127.0.0.1:8000',
-        //   changeOrigin: true,
-        //   rewrite: (path) => path.replace(/^\/api/, '')
-        // },
-         // 选项写法
-         '/adminapi': {
+        '/api': {
+          target: 'http://127.0.0.1:8000',
+          changeOrigin: true,
+          rewrite: (path) => path.replace(/^\/api/, '')
+        },
+        '/adminapi': {
           target: 'https://zhuangxiu.qiniu1314.com',
           changeOrigin: true
           // rewrite: (path) => path.replace(/^\//, '')