Ver Fonte

2025-1-14

cmy há 2 meses atrás
pai
commit
2a591c3006
100 ficheiros alterados com 2761 adições e 2135 exclusões
  1. 1 1
      .env.base
  2. 2 2
      .env.dev
  3. 1 1
      .env.gitee
  4. 0 4
      mock/menu/index.mock.ts
  5. 2 1
      package.json
  6. 9 9
      src/App.vue
  7. 0 11
      src/api/common/index.ts
  8. 0 23
      src/api/dashboard/analysis/index.ts
  9. 0 22
      src/api/dashboard/analysis/types.ts
  10. 0 22
      src/api/dashboard/workplace/index.ts
  11. 0 30
      src/api/dashboard/workplace/types.ts
  12. 0 30
      src/api/department/index.ts
  13. 0 32
      src/api/department/types.ts
  14. 8 14
      src/api/login/index.ts
  15. 11 0
      src/api/login/types.ts
  16. 0 38
      src/api/request/index.ts
  17. 0 3
      src/api/request/types.ts
  18. 51 0
      src/api/staff/index.ts
  19. 34 0
      src/api/staff/types.ts
  20. 74 0
      src/api/system/admin/index.ts
  21. 32 0
      src/api/system/admin/types.ts
  22. 48 0
      src/api/system/config/index.ts
  23. 60 0
      src/api/system/config/types.ts
  24. 20 0
      src/api/system/department/index.ts
  25. 21 0
      src/api/system/department/types.ts
  26. 29 0
      src/api/system/file/index.ts
  27. 7 0
      src/api/system/file/types.ts
  28. 6 0
      src/api/system/log/index.ts
  29. 6 5
      src/api/system/menu/index.ts
  30. 5 16
      src/api/system/menu/types.ts
  31. 6 5
      src/api/system/role/index.ts
  32. 6 6
      src/api/system/role/types.ts
  33. 0 26
      src/api/table/index.ts
  34. 0 9
      src/api/table/types.ts
  35. 136 0
      src/api/user/index.ts
  36. 56 0
      src/api/user/types.ts
  37. 3 1
      src/axios/config.ts
  38. 1 1
      src/components/IconPicker/src/IconPicker.vue
  39. 9 0
      src/components/ImageCropping/src/ImageCropping.vue
  40. 6 0
      src/components/Logo/src/Logo.vue
  41. 27 12
      src/components/Menu/src/components/useRenderMenuItem.tsx
  42. 2 4
      src/components/Permission/src/Permission.vue
  43. 2 3
      src/components/Permission/src/utils.ts
  44. 1 1
      src/components/Search/src/Search.vue
  45. 0 1
      src/components/Table/src/Table.vue
  46. 3 0
      src/components/UpFile/index.ts
  47. 226 0
      src/components/UpFile/src/components/upFile.vue
  48. 405 0
      src/components/UpFile/src/index.vue
  49. 141 0
      src/components/UpFile/src/upImageButtom.vue
  50. 13 6
      src/components/UserInfo/src/UserInfo.vue
  51. 3 0
      src/components/UserList/index.ts
  52. 157 0
      src/components/UserList/src/userList.vue
  53. 3 0
      src/components/tableImage/index.ts
  54. 47 0
      src/components/tableImage/tableImage.vue
  55. 2 1
      src/components/verifition/Verify.vue
  56. 12 8
      src/components/verifition/Verify/VerifyPoints.vue
  57. 8 7
      src/components/verifition/Verify/VerifySlide.vue
  58. 6 3
      src/components/verifition/utils/ase.ts
  59. 0 36
      src/components/verifition/utils/util.js
  60. 98 0
      src/components/verifition/utils/util.ts
  61. 10 0
      src/constants/index.ts
  62. 4 2
      src/main.ts
  63. 2 0
      src/permission.ts
  64. 7 12
      src/router/model/Authorization.ts
  65. 0 267
      src/router/model/ComponentsDemo.ts
  66. 0 71
      src/router/model/Example.ts
  67. 0 19
      src/router/model/ExternalLink.ts
  68. 0 52
      src/router/model/Function.ts
  69. 0 20
      src/router/model/Guide.ts
  70. 0 64
      src/router/model/Hooks.ts
  71. 0 62
      src/router/model/Level.ts
  72. 23 0
      src/router/model/Maintenance.ts
  73. 48 0
      src/router/model/Preserve.ts
  74. 47 0
      src/router/model/Staff.ts
  75. 61 0
      src/router/model/System.ts
  76. 40 0
      src/router/model/User.ts
  77. 0 35
      src/router/model/dashboard.ts
  78. 24 4
      src/store/modules/app.ts
  79. 1 0
      src/store/modules/permission.ts
  80. 13 5
      src/store/modules/user.ts
  81. 75 16
      src/utils/analysis.ts
  82. 4 4
      src/utils/color.ts
  83. 17 8
      src/utils/routerHelper.ts
  84. 106 0
      src/utils/searchTime.ts
  85. 41 21
      src/views/Authorization/Menu/Menu.vue
  86. 1 1
      src/views/Authorization/Menu/components/Detail.vue
  87. 12 131
      src/views/Authorization/Menu/components/Write.vue
  88. 3 2
      src/views/Authorization/Menu/components/addApiUrl.vue
  89. 19 9
      src/views/Authorization/Role/Role.vue
  90. 3 3
      src/views/Authorization/Role/components/Write.vue
  91. 227 308
      src/views/Authorization/User/User.vue
  92. 44 9
      src/views/Authorization/User/components/Detail.vue
  93. 123 15
      src/views/Authorization/User/components/Write.vue
  94. 0 53
      src/views/Components/Avatars.vue
  95. 0 100
      src/views/Components/CountTo.vue
  96. 0 192
      src/views/Components/Descriptions.vue
  97. 0 165
      src/views/Components/Dialog.vue
  98. 0 36
      src/views/Components/Echart.vue
  99. 0 23
      src/views/Components/Editor/CodeEditor.vue
  100. 0 32
      src/views/Components/Editor/Editor.vue

+ 1 - 1
.env.base

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

+ 2 - 2
.env.dev

@@ -2,10 +2,10 @@
 VITE_NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=/adminapi
+VITE_API_BASE_PATH=
 
 # 打包路径
-VITE_BASE_PATH=/dist-dev/
+VITE_BASE_PATH=/admin/
 
 # 是否删除debugger
 VITE_DROP_DEBUGGER=false

+ 1 - 1
.env.gitee

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

+ 0 - 4
mock/menu/index.mock.ts

@@ -304,10 +304,7 @@ export default [
                   title: '综合示例-新增',
                   meta: {
                     title: '综合示例-新增',
-                    noTagsView: true,
-                    noCache: true,
                     hidden: true,
-                    showMainRoute: true,
                     activeMenu: '/example/example-page'
                   }
                 },
@@ -322,7 +319,6 @@ export default [
                   title: '综合示例-编辑',
                   meta: {
                     title: '综合示例-编辑',
-                    noTagsView: true,
                     noCache: true,
                     hidden: true,
                     showMainRoute: true,

+ 2 - 1
package.json

@@ -42,7 +42,7 @@
     "driver.js": "^1.3.1",
     "echarts": "^5.5.1",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "2.7.7",
+    "element-plus": "^2.9.1",
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.1",
     "monaco-editor": "^0.50.0",
@@ -51,6 +51,7 @@
     "pinia-plugin-persistedstate": "^3.2.3",
     "qrcode": "^1.5.4",
     "qs": "^6.13.1",
+    "tlbs-map-vue": "^1.2.1",
     "url": "^0.11.4",
     "vue": "^3.5.13",
     "vue-draggable-plus": "^0.5.6",

+ 9 - 9
src/App.vue

@@ -3,7 +3,7 @@ import { computed } from 'vue'
 import { useAppStore } from '@/store/modules/app'
 import { ConfigGlobal } from '@/components/ConfigGlobal'
 import { useDesign } from '@/hooks/web/useDesign'
-import { ElNotification } from 'element-plus'
+// import { ElNotification } from 'element-plus'
 
 const { getPrefixCls } = useDesign()
 
@@ -17,14 +17,14 @@ const greyMode = computed(() => appStore.getGreyMode)
 
 appStore.initTheme()
 
-ElNotification({
-  title: '提示',
-  type: 'warning',
-  duration: 0,
-  dangerouslyUseHTMLString: true,
-  message:
-    '<div><p><strong>遇事不决,请先查阅常见问题,说不定你能找到相关解答</strong></p><p><a href="https://element-plus-admin-doc.cn/guide/fqa.html" target="_blank">链接地址</a></p></div>'
-})
+// ElNotification({
+//   title: '提示',
+//   type: 'warning',
+//   duration: 0,
+//   dangerouslyUseHTMLString: true,
+//   message:
+//     '<div><p><strong>遇事不决,请先查阅常见问题,说不定你能找到相关解答</strong></p><p><a href="https://element-plus-admin-doc.cn/guide/fqa.html" target="_blank">链接地址</a></p></div>'
+// })
 </script>
 
 <template>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 8 - 14
src/api/login/index.ts

@@ -1,5 +1,6 @@
 import request from '@/axios'
 import type { isCaptcha, UserLoginCaptchaType } from './types'
+import { REQUEST_BASE } from '@/constants'
 
 /**
  * 登录API函数
@@ -11,7 +12,7 @@ import type { isCaptcha, UserLoginCaptchaType } from './types'
  * @returns 返回一个Promise对象,包含服务器响应的用户数据,数据结构符合IResponse<UserType>
  */
 export const loginApi = (data: UserLoginCaptchaType): Promise<IResponse> => {
-  return request.post({ url: '/login', data })
+  return request.post({ url: `${REQUEST_BASE}/login`, data })
 }
 
 /**
@@ -23,7 +24,7 @@ export const loginApi = (data: UserLoginCaptchaType): Promise<IResponse> => {
  * @returns {Promise<IResponse>} 返回一个Promise,表示异步操作的结果
  */
 export const loginOutApi = (): Promise<IResponse> => {
-  return request.get({ url: '/logout' })
+  return request.get({ url: `${REQUEST_BASE}/logout` })
 }
 
 /**
@@ -34,23 +35,16 @@ export const loginOutApi = (): Promise<IResponse> => {
  * @returns 返回一个Promise对象,包含服务器响应的结果
  */
 export const isCaptchaImg = (data: isCaptcha): Promise<IResponse> => {
-  return request.post({ url: '/is_captcha', data })
+  return request.post({ url: `${REQUEST_BASE}/is_captcha`, data })
 }
 // 验证码结果验证
 export const ajCaptchaCheck = (data: isCaptcha): Promise<IResponse> => {
-  return request.post({ url: '/ajcheck', data })
+  return request.post({ url: `${REQUEST_BASE}/ajcheck`, data })
 }
 export const ajCaptcha = (params: any): Promise<IResponse> => {
-  return request.get({ url: '/ajcaptcha', params })
+  return request.get({ url: `${REQUEST_BASE}/ajcaptcha`, params })
 }
 
-/**
- * 获取管理员信息
- *
- * 本函数通过发送GET请求到'/info'端点,异步获取管理员信息
- * 不需要任何参数
- * 返回一个Promise对象,解析为IResponse类型,包含管理员信息
- */
-export const getAdminInfo = (): Promise<IResponse> => {
-  return request.get({ url: '/info' })
+export const getLoginInfo = (): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/login/info` })
 }

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

@@ -10,6 +10,17 @@ export interface UserType {
   roleId: string
 }
 
+export interface UserInfo {
+  expires_time: number
+  logo: string
+  logo_square: string
+  menus: any[]
+  prefix: string | null
+  token: string
+  unique_auth: string[]
+  user_info: any
+}
+
 export interface isCaptcha {
   account: string
 }

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

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

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

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

+ 51 - 0
src/api/staff/index.ts

@@ -0,0 +1,51 @@
+import request from '@/axios'
+import { REQUEST_BASE } from '@/constants'
+import { designerSearch, designerData, categoryData, jobsData } from './types'
+export const getDesigner = (params: designerSearch): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/decoration/designer`, params })
+}
+export const addDesigner = (data: designerData): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/decoration/designer`, data })
+}
+export const delDesigner = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/decoration/designer/${id}` })
+}
+export const putDesigner = (data: designerData): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/decoration/designer/${data.id}`, data })
+}
+export const getWorker = (params: designerSearch): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/decoration/worker`, params })
+}
+export const addWorker = (data: designerData): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/decoration/worker`, data })
+}
+export const delWorker = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/decoration/worker/${id}` })
+}
+export const putWorker = (data: designerData): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/decoration/worker/${data.id}`, data })
+}
+export const getStaffCategory = (params: designerSearch): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/decoration/category`, params })
+}
+export const addStaffCategory = (data: categoryData): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/decoration/category`, data })
+}
+export const delStaffCategory = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/decoration/category/${id}` })
+}
+export const putStaffCategory = (data: categoryData): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/decoration/category/${data.id}`, data })
+}
+export const getStaffJobs = (params: { page?: number; limit?: number }): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/decoration/jobs`, params })
+}
+export const addStaffJobs = (data: jobsData): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/decoration/jobs`, data })
+}
+export const delStaffJobs = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/decoration/jobs/${id}` })
+}
+export const putStaffJobs = (data: jobsData): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/decoration/jobs/${data.id}`, data })
+}

+ 34 - 0
src/api/staff/types.ts

@@ -0,0 +1,34 @@
+export type designerSearch = {
+  page?: number
+  limit?: number
+}
+
+export interface designerData {
+  id?: number
+  uid: number | null
+  name: string //真实姓名
+  phone: number | null //手机号
+  gender: 0 | 1 | 2 //性别 0保密 1男 2女
+  avatar: string //头像
+  birth_day_time?: string | number //生日
+  province: string //省
+  city: string //市
+  area: string //区
+  tag_list: Array<string> //标签
+  longitude: string | number //经度
+  latitude: string | number //纬度
+  detail_address: string
+}
+export type categoryData = {
+  id?: number
+  name: string
+  is_show: 1 | 0
+  type: 1 | 2
+}
+export type jobsData = {
+  id?: number
+  name: string
+  is_show: 1 | 0
+  cate_id: number
+  default_price: number
+}

+ 74 - 0
src/api/system/admin/index.ts

@@ -0,0 +1,74 @@
+import request from '@/axios'
+import type { upAdmin, verify, mapSearch } from './types'
+import { REQUEST_BASE, REQUEST_GENERAL } from '@/constants'
+
+/**
+ * 获取管理员信息
+ *
+ * 本函数通过发送GET请求到'/info'端点,异步获取管理员信息
+ * 不需要任何参数
+ * 返回一个Promise对象,解析为IResponse类型,包含管理员信息
+ */
+export const getAdminInfo = (): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/info` })
+}
+
+/**
+ * 更新管理员信息
+ *
+ * 此函数发送一个POST请求到/update_admin端点,以更新管理员的信息
+ * 它携带一个upAdmin类型的data对象作为请求的参数,用于指定更新的详细信息
+ *
+ * @param data 包含管理员更新信息的对象,类型为upAdmin
+ * @returns 返回一个Promise,解析为IResponse类型,包含服务器的响应
+ */
+export const updateAdmin = (data: upAdmin): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/update_admin`, data })
+}
+
+export const upImage = (data: any): Promise<IResponse> => {
+  return request.post({
+    url: `${REQUEST_BASE}/file/upload/${data.type}`,
+    data,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
+
+export const upload = (data: any): Promise<IResponse> => {
+  return request.post({
+    url: `${REQUEST_BASE}/file/video_upload`,
+    data,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
+
+export const ossUpload = (url, data): Promise<IResponse> => {
+  return request.post({
+    url: `${url}`,
+    data,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
+
+export const getCodeKey = (): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_GENERAL}/verify_code` })
+}
+export const postVerify = (data: verify): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_GENERAL}/verify`, data })
+}
+
+export const getCity = (params: { pid: number; type: string }): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/city`, params })
+}
+export const getMapSearch = (params: mapSearch): Promise<IResponse> => {
+  return request.get({ url: `/qqmap/place/v1/suggestion`, params })
+}
+export const getConfigKey = (key: string): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/sys_config`, params: { key } })
+}

+ 32 - 0
src/api/system/admin/types.ts

@@ -0,0 +1,32 @@
+export interface upAdmin {
+  real_name?: string
+  head_pic?: string
+  pwd?: string
+  new_pwd?: string
+  conf_pwd?: string
+  phone?: string
+  code?: string
+}
+
+export interface verify {
+  phone: string | number
+  type: string
+  key: string
+  captchaType: string
+  captchaVerification: string
+}
+export interface mapSearch {
+  key: string
+  keyword: string
+  region?: string
+}
+export interface mapAddressData {
+  adcode: number
+  label: string
+  address?: string
+  lng: number
+  lat: number
+  province?: string
+  city?: string
+  district?: string
+}

+ 48 - 0
src/api/system/config/index.ts

@@ -0,0 +1,48 @@
+import request from '@/axios'
+import type { classList, classUp, configList, classConfigUp } from './types'
+import { REQUEST_BASE } from '@/constants'
+
+export const getClass = (params: classList): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/config_class`, params })
+}
+export const addClass = (params: classUp) => {
+  return request.post({ url: `${REQUEST_BASE}/config_class`, data: params })
+}
+export const delClass = (params: { id: number }) => {
+  return request.delete({ url: `${REQUEST_BASE}/config_class/${params.id}` })
+}
+export const putClassStatus = (params) => {
+  return request.put({
+    url: `${REQUEST_BASE}/config_class/set_status/${params.id}/${params.status}`
+  })
+}
+export const putClass = (params: classUp) => {
+  return request.put({ url: `${REQUEST_BASE}/config_class/${params.id}`, data: params })
+}
+export const getClassConfigList = (params: configList): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/config`, params })
+}
+
+export const addClassConfig = (params: classConfigUp) => {
+  return request.post({ url: `${REQUEST_BASE}/config`, data: params })
+}
+export const delClassConfig = (params: { id: number }) => {
+  return request.delete({ url: `${REQUEST_BASE}/config/${params.id}` })
+}
+export const putClassConfigStatus = (params: { id: number; status: number }) => {
+  return request.put({
+    url: `${REQUEST_BASE}/config/set_status/${params.id}/${params.status}`
+  })
+}
+export const putClassConfig = (params: classConfigUp) => {
+  return request.put({ url: `${REQUEST_BASE}/config/${params.id}`, data: params })
+}
+export const getHeaderBasics = (params: { pid: number }) => {
+  return request.get({ url: `${REQUEST_BASE}/config/header_basics`, params })
+}
+export const getClassConfig = (params: { id: number }): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/config/read/${params.id}` })
+}
+export const configSave = (data): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/config/save`, data })
+}

+ 60 - 0
src/api/system/config/types.ts

@@ -0,0 +1,60 @@
+import { ComponentName } from '@/components/Form'
+
+export interface configList {
+  tab_id?: string | number
+  status?: string
+  page: number
+  limit: number
+}
+
+export interface classList {
+  page: number
+  limit: number
+  status?: string
+  title?: string
+  is_store?: string
+}
+
+export interface classUp {
+  id?: string
+  status: number
+  title: number
+  icon?: string
+  sort?: string | number
+  pid?: number
+  is_store?: string
+  eng_title?: string
+  info?: string
+}
+
+export interface classConfigUp {
+  id?: string
+  menu_name: string
+  info: string
+  type: ComponentName
+  parameter?: string | number
+  configuration: {
+    options?: Array<any> //默认选项
+    required?: Array<string> //验证类型
+    maxlength?: number //最长字数
+    minlength?: number //最短字数
+    input_type: string //输入框类型
+    upload_type: number //上传类型
+  }
+  value: string
+  desc: string
+  sort: number
+  status: number
+  is_store?: string
+  config_tab_id?: string
+}
+export interface headerBasice {
+  id: number
+  label: string
+  pid: number
+  value: string
+  icon: string
+  children: Array<any>
+  loading?: boolean
+  loadData?: boolean
+}

+ 20 - 0
src/api/system/department/index.ts

@@ -0,0 +1,20 @@
+import request from '@/axios'
+import { getAdmins, AddAdmins, putAdmins } from './types'
+import { REQUEST_BASE } from '@/constants'
+
+export const getUserRole = (params: getAdmins) => {
+  return request.get({ url: `${REQUEST_BASE}/admins`, params })
+}
+export const addUserRole = (params: AddAdmins) => {
+  return request.post({ url: `${REQUEST_BASE}/admins`, data: params })
+}
+export const putUserRoleStatus = (params: putAdmins) => {
+  return request.put({ url: `${REQUEST_BASE}/admins/set_status/${params.id}/${params.status}` })
+}
+export const delUserRole = (params: { id: number }) => {
+  return request.delete({ url: `${REQUEST_BASE}/admins/${params.id}` })
+}
+
+export const putUserRole = (params: AddAdmins) => {
+  return request.put({ url: `${REQUEST_BASE}/admins/${params.id}`, data: params })
+}

+ 21 - 0
src/api/system/department/types.ts

@@ -0,0 +1,21 @@
+export interface getAdmins {
+  limit: number
+  page: number
+  name?: string
+  roles?: string
+  status?: string | number
+}
+export interface AddAdmins {
+  id?: string | number //id
+  account: string //账号
+  pwd?: string //密码
+  conf_pwd?: string //确认密码
+  real_name: string //名字
+  phone: string | number //手机号
+  roles: string[] | number[] //角色数组
+  status: string | number //是否启用
+}
+export interface putAdmins {
+  id: string | number //账号
+  status: string | number //是否启用
+}

+ 29 - 0
src/api/system/file/index.ts

@@ -0,0 +1,29 @@
+import request from '@/axios'
+import type { fileList } from './types'
+import { REQUEST_BASE } from '@/constants'
+
+export const getFileList = (params: fileList): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/file`, params })
+}
+export const getFileType = (params: fileList = {}): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/file/category`, params })
+}
+export const setFileType = (data: {
+  pid?: string | number
+  name: string
+  file_type: number
+}): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/file/category`, data })
+}
+export const delFileType = (id: string | number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/file/category/${id}` })
+}
+export const upOnlineFile = (data: { pid: number; images: string }): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/file/online/upload`, data })
+}
+export const delFile = (data: { ids: string }): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/file/delete`, data })
+}
+export const updateFile = (data: { id: number; real_name: string }): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/update/${data.id}`, data })
+}

+ 7 - 0
src/api/system/file/types.ts

@@ -0,0 +1,7 @@
+export interface fileList {
+  page?: number
+  limit?: number
+  file_type?: number
+  pid?: string
+  name?: string
+}

+ 6 - 0
src/api/system/log/index.ts

@@ -0,0 +1,6 @@
+import request from '@/axios'
+import { REQUEST_BASE } from '@/constants'
+
+export const getLogList = (params: { page?: number; limit?: number }): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/log`, params })
+}

+ 6 - 5
src/api/system/menu/index.ts

@@ -1,4 +1,5 @@
 import request from '@/axios'
+import { REQUEST_BASE } from '@/constants'
 
 /**
  * 获取菜单列表的API接口
@@ -9,7 +10,7 @@ import request from '@/axios'
  * @returns {Promise<any>} 返回一个Promise对象,解析为菜单列表的数据
  */
 export const getMenuListApi = (params: any) => {
-  return request.get({ url: '/menus', params })
+  return request.get({ url: `${REQUEST_BASE}/menus`, params })
 }
 
 /**
@@ -22,7 +23,7 @@ export const getMenuListApi = (params: any) => {
  * @returns 返回一个Promise对象,表示菜单项创建的异步操作的结果
  */
 export const newMenuApi = (data) => {
-  return request.post({ url: '/menus', data })
+  return request.post({ url: `${REQUEST_BASE}/menus`, data })
 }
 
 /**
@@ -34,7 +35,7 @@ export const newMenuApi = (data) => {
  * URL中包含菜单项的ID,以确保更新的是正确的菜单项
  */
 export const setMenuApi = (data) => {
-  return request.put({ url: `/menus/${data.id}`, data })
+  return request.put({ url: `${REQUEST_BASE}/menus/${data.id}`, data })
 }
 
 /**
@@ -46,7 +47,7 @@ export const setMenuApi = (data) => {
  * URL中包含菜单项的ID,以确保删除的是正确的菜单项
  */
 export const delMenuApi = (data: { id: number }) => {
-  return request.delete({ url: `/menus/${data.id}` })
+  return request.delete({ url: `${REQUEST_BASE}/menus/${data.id}` })
 }
 /**
  * 获取未添加权限接口列表
@@ -56,5 +57,5 @@ export const delMenuApi = (data: { id: number }) => {
  * 返回一个Promise对象,未添加权限接口列表
  */
 export const apiRuleList = () => {
-  return request.get({ url: '/menus/ruleList' })
+  return request.get({ url: `${REQUEST_BASE}/menus/ruleList` })
 }

+ 5 - 16
src/api/system/menu/types.ts

@@ -1,7 +1,8 @@
-export type MenuData = {
+import { RouteMetaCustom } from 'types/router'
+export interface MenuData {
   id?: number
   path?: string
-  status: number
+  status?: number
   unique_auth?: string
   parentId?: number
   type?: number
@@ -11,22 +12,10 @@ export type MenuData = {
   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
-  }
+  meta?: RouteMetaCustom
   children?: object
 }
-export type addRoleData = {
+export interface addRoleData {
   id?: number | string
   menu_name: string //名称
   icon?: string //图标

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

@@ -1,5 +1,6 @@
 import request from '@/axios'
 import type { roleRequestData, addRole } from './types'
+import { REQUEST_BASE } from '@/constants'
 
 /**
  * 获取角色列表的API接口
@@ -10,7 +11,7 @@ import type { roleRequestData, addRole } from './types'
  * @returns {Promise<IResponse>} 返回一个Promise对象,解析为角色列表的数据
  */
 export const getRoleListApi = (params: roleRequestData): Promise<IResponse> => {
-  return request.get({ url: '/roles', params })
+  return request.get({ url: `${REQUEST_BASE}/roles`, params })
 }
 
 /**
@@ -24,7 +25,7 @@ export const getRoleListApi = (params: roleRequestData): Promise<IResponse> => {
  * @returns 返回一个Promise对象,解析为`IResponse`接口的实例
  */
 export const addRoleApi = (data: addRole): Promise<IResponse> => {
-  return request.post({ url: '/roles', data })
+  return request.post({ url: `${REQUEST_BASE}/roles`, data })
 }
 
 /**
@@ -35,7 +36,7 @@ export const addRoleApi = (data: addRole): Promise<IResponse> => {
  */
 export const getRoleApi = (data: { id: string | number }): Promise<IResponse> => {
   // 发起GET请求到指定的角色URL,以获取角色信息
-  return request.get({ url: `/roles/${data.id}` })
+  return request.get({ url: `${REQUEST_BASE}/roles/${data.id}` })
 }
 
 /**
@@ -48,7 +49,7 @@ export const getRoleApi = (data: { id: string | number }): Promise<IResponse> =>
  * 使用PUT方法是因为它适合于更新资源信息
  */
 export const setRoleApi = (data: any): Promise<IResponse> => {
-  return request.put({ url: `/roles/${data.id}`, data })
+  return request.put({ url: `${REQUEST_BASE}/roles/${data.id}`, data })
 }
 
 /**
@@ -61,5 +62,5 @@ export const setRoleApi = (data: any): Promise<IResponse> => {
  * 请求的URL基于提供的角色ID构建
  */
 export const delRoleApi = (data: { id: string | number }): Promise<IResponse> => {
-  return request.delete({ url: `/roles/${data.id}` })
+  return request.delete({ url: `${REQUEST_BASE}/roles/${data.id}` })
 }

+ 6 - 6
src/api/system/role/types.ts

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

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

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

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

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

+ 136 - 0
src/api/user/index.ts

@@ -0,0 +1,136 @@
+import request from '@/axios'
+import { REQUEST_BASE } from '@/constants'
+import { userSearch, userLevelSearch, userLevelData, userData } from './types'
+export const getUserLevel = (params: userLevelSearch): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user_level`, params })
+}
+export const addUserLevel = (data: userLevelData): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/user_level`, data })
+}
+export const delUserLevel = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/user_level/${id}` })
+}
+export const putUserLevelStatus = (data: { id: number; status: number }): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/user_level/set_status/${data.id}/${data.status}` })
+}
+
+export const putUserLevel = (data: userLevelData): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/user_level/${data.id}`, data })
+}
+/**
+ * 清除用户的等级信息
+ *
+ * 此函数通过发送DELETE请求来清除指定用户等级信息它需要一个用户ID作为参数,
+ * 并返回一个Promise对象,该对象解析为IResponse类型这可以用于处理请求的成功或失败
+ *
+ * @param id 用户ID,用于指定需要清除等级信息的用户
+ * @returns 返回一个Promise对象,包含请求的结果,类型为IResponse
+ */
+export const clearUserLevel = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/user/delLevel/${id}` })
+}
+
+export const getUserList = (params: userSearch): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user`, params })
+}
+export const getUserDetail = (id: number): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user/${id}` })
+}
+export const addUserData = (data: userData): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/user`, data })
+}
+export const delUserData = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/user/${id}` })
+}
+export const putUserData = (data: userData): Promise<IResponse> => {
+  return request.put({
+    url: `${REQUEST_BASE}/user/${data.uid}`,
+    data
+  })
+}
+export const levelTaskBase = (): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user_level/task_base` })
+}
+export const getUserGroup = (params: { page: number; limit: number }): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user_group`, params })
+}
+export const addUserGroup = (group_name: string): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/user_group`, data: { group_name } })
+}
+export const delUserGroup = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/user_group/${id}` })
+}
+export const putUserGroup = (data: { id: number; group_name: string }): Promise<IResponse> => {
+  return request.put({
+    url: `${REQUEST_BASE}/user_group/${data.id}`,
+    data: { group_name: data.group_name }
+  })
+}
+export const setGroup = (data: {
+  group_id: number
+  uids: string
+  all: number
+  where: any
+}): Promise<IResponse> => {
+  return request.put({
+    url: `${REQUEST_BASE}/user/setGroup`,
+    data
+  })
+}
+/**
+ * 用户附加信息【余额流水】
+ * @param params
+ * @param id
+ * @returns
+ */
+export const getBalanceLog = (
+  params: { pm?: number; type?: string; page: number; limit: number },
+  id: number
+): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user/detail/balance_log/${id}`, params })
+}
+/**
+ * 用户附加信息【推荐人列表】
+ * @param params
+ * @param id
+ * @returns
+ */
+export const getSpreadList = (params: { page: number; limit: number }, id): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user/detail/spread/${id}`, params })
+}
+/**
+ * 用户附加信息【佣金流水】
+ * @param params
+ * @param id
+ * @returns
+ */
+export const getBrokerageLog = (
+  params: { page: number; limit: number; pm?: number; type?: string },
+  id
+): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user/detail/brokerage_log/${id}`, params })
+}
+/**
+ * 用户附加信息【其他代币流水】
+ * @param params
+ * @param id
+ * @returns
+ */
+export const getBillLog = (params: { page: number; limit: number }, id): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/user/detail/bill_log/${id}`, params })
+}
+export const putGiveLevel = (data: { level_id: number; id: number }): Promise<IResponse> => {
+  return request.post({
+    url: `${REQUEST_BASE}/user/giveLevel/${data.id}`,
+    data: { level_id: data.level_id }
+  })
+}
+export const updateAccount = (
+  data: { money_type: string; pm: number; num: number; mark: string },
+  id
+): Promise<IResponse> => {
+  return request.put({
+    url: `${REQUEST_BASE}/user/updateAccount/${id}`,
+    data
+  })
+}

+ 56 - 0
src/api/user/types.ts

@@ -0,0 +1,56 @@
+export interface userSearch {
+  nickname?: string //搜索字段模糊匹配
+  status?: '' | 0 | 1 | null //用户状态0:封1:正常
+  is_promoter?: '' | 1 | 0 | null //推广权限 1:开0:关
+  user_type?: string //用户类型
+  country?: string //国家:domestic 国内 abroad 国外
+  province?: string //省
+  city?: string //市
+  user_time_type?: 'all' | 'visitno' | 'visit' | 'add_time' //时间范围类型 all:全部,visitno:访问时间不在,visit:访问时间,add_time:添加时间
+  user_time?: string //时间范围
+  page?: number
+  limit?: number
+  sex?: '' | 0 | 1 | 2 //性别 0保密 1男 2女
+  level?: string //等级
+  group_id?: string //分组
+  now_money?: '' | 'desc' | 'asc' | null //余额排序 desc 降序 asc 升序
+  field_key?: 'all' | 'nickname' | 'phone' | 'uid' // 搜索字段:all 三者皆查,nickname昵称,phone 手机,uid
+}
+export interface userLevelSearch {
+  title?: string
+  is_show?: number | string
+  page?: number
+  limit?: number
+}
+export interface userLevelData {
+  id?: number
+  name: string //等级名称
+  is_forever: number | string //是否永久 1永久 0非永久
+  valid_date: number //有效时间(天) 0为永久
+  grade: number //等级
+  icon?: number //图标
+  image?: number //背景
+  is_show: number //是否启用
+  explain: number //等级描述
+  task?: any[] //等级任务,完成任务即可升级
+}
+export interface userData {
+  uid?: number | null
+  phone: number | null //手机号
+  real_name?: string //真实姓名
+  pwd?: string //密码
+  true_pwd?: string //确认密码
+  trade_pwd?: string //交易密码
+  true_trade_pwd?: string //确认交易密码
+  sex: 0 | 1 | 2 //性别 0保密 1男 2女
+  spread_uid?: string //推荐人ID
+  is_promoter: 1 | 0 | true | false //是否推广员加时间 0不是推广员1是推广员
+  card_id?: string //身份证号码
+  birthday?: string | number //生日
+  mark?: string //备注
+  status: 1 | 0 | true | false //用户状态0:封1:正常
+  level?: number //用户等级
+  group_id?: number //用户分组
+  avatar?: string
+  nickname?: string
+}

+ 3 - 1
src/axios/config.ts

@@ -41,8 +41,10 @@ const defaultResponseInterceptors = (response: AxiosResponse) => {
     return response
   } else if (response.data.status === SUCCESS_CODE) {
     return response.data
+  } else if (response.config.url?.includes('/qqmap/') && response.data.status === 0) {
+    return response.data
   } else {
-    ElMessage.error(response?.data?.message)
+    ElMessage.error(response?.data?.msg)
     if (response?.data?.status === 410000) {
       const userStore = useUserStoreWithOut()
       userStore.logout()

+ 1 - 1
src/components/IconPicker/src/IconPicker.vue

@@ -171,7 +171,7 @@ const inputClear = () => {
           v-model:current-page="currentPage"
           v-model:page-size="pageSize"
           :pager-count="5"
-          small
+          size="small"
           :page-sizes="[100, 200, 300, 400]"
           layout="total, prev, pager, next, jumper"
           :total="filterItemIcons(icons[currentIconNameIndex].icons).length"

+ 9 - 0
src/components/ImageCropping/src/ImageCropping.vue

@@ -6,6 +6,8 @@ import 'cropperjs/dist/cropper.min.css'
 import { ElDivider, ElUpload, UploadFile, ElMessage, ElTooltip } from 'element-plus'
 import { useDebounceFn } from '@vueuse/core'
 import { BaseButton } from '@/components/Button'
+import { useAppStore } from '@/store/modules/app'
+const appStore = useAppStore()
 
 const { getPrefixCls } = useDesign()
 
@@ -110,12 +112,19 @@ const intiCropper = () => {
 }
 
 const uploadChange = (uploadFile: UploadFile) => {
+  // console.log('图片')
   // 判断是否是图片
   if (uploadFile?.raw?.type.indexOf('image') === -1) {
     ElMessage.error('请上传图片格式的文件')
     return
   }
   if (!uploadFile.raw) return
+  const fileSize = uploadFile?.raw?.size ?? 0
+  const maxSize = appStore.getSystemInfo?.upload_file_size_max ?? 0
+  if (fileSize > maxSize) {
+    ElMessage.error(`文件大小超过限制最大${maxSize / 1024 / 1024}M`)
+    return
+  }
   // 获取图片的访问地址
   const url = URL.createObjectURL(uploadFile.raw)
   unref(cropperRef)?.replace(url)

+ 6 - 0
src/components/Logo/src/Logo.vue

@@ -59,6 +59,12 @@ watch(
       to="/"
     >
       <img
+        v-if="appStore.getSystemInfo?.logo_square != ''"
+        :src="appStore.getSystemInfo?.logo_square"
+        class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
+      />
+      <img
+        v-else
         src="@/assets/imgs/logo.png"
         class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
       />

+ 27 - 12
src/components/Menu/src/components/useRenderMenuItem.tsx

@@ -37,18 +37,33 @@ export const useRenderMenuItem = (menuMode) =>
               </ElMenuItem>
             )
           } else {
-            return (
-              <ElSubMenu
-                index={fullPath}
-                teleported
-                popperClass={unref(menuMode) === 'vertical' ? `${prefixCls}-popper--vertical` : ''}
-              >
-                {{
-                  title: () => renderMenuTitle(meta),
-                  default: () => renderMenuItem(v.children!, fullPath)
-                }}
-              </ElSubMenu>
-            )
+            // 判断是否父级元素防止加载出错
+            if (v.children) {
+              return (
+                <ElSubMenu
+                  index={fullPath}
+                  teleported
+                  popperClass={
+                    unref(menuMode) === 'vertical' ? `${prefixCls}-popper--vertical` : ''
+                  }
+                >
+                  {{
+                    title: () => renderMenuTitle(meta),
+                    default: () => renderMenuItem(v.children!, fullPath)
+                  }}
+                </ElSubMenu>
+              )
+            } else {
+              return (
+                <ElMenuItem
+                  index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}
+                >
+                  {{
+                    default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
+                  }}
+                </ElMenuItem>
+              )
+            }
           }
         })
     }

+ 2 - 4
src/components/Permission/src/Permission.vue

@@ -1,16 +1,14 @@
 <script setup lang="ts">
 import { propTypes } from '@/utils/propTypes'
 import { computed, unref } from 'vue'
-import { useRouter } from 'vue-router'
-
-const { currentRoute } = useRouter()
+import { useUserStore } from '@/store/modules/user'
 
 const props = defineProps({
   permission: propTypes.string.def()
 })
 
 const currentPermission = computed(() => {
-  return unref(currentRoute)?.meta?.permission || []
+  return useUserStore().getRoleName || []
 })
 
 const hasPermission = computed(() => {

+ 2 - 3
src/components/Permission/src/utils.ts

@@ -1,9 +1,8 @@
 import { useI18n } from '@/hooks/web/useI18n'
-import router from '@/router'
-
+import { useUserStore } from '@/store/modules/user'
 export const hasPermi = (value: string) => {
   const { t } = useI18n()
-  const permission = (router.currentRoute.value.meta.permission || []) as string[]
+  const permission = (useUserStore().getRoleName || []) as string[]
   if (!value) {
     throw new Error(t('permission.hasPermission'))
   }

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

@@ -46,7 +46,7 @@ const props = defineProps({
 
 const emit = defineEmits(['search', 'reset', 'register', 'validate'])
 
-const visible = ref(true)
+const visible = ref(false)
 
 // 表单数据
 const formModel = ref<Recordable>(props.model)

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

@@ -294,7 +294,6 @@ export default defineComponent({
     const pagination = computed(() => {
       return Object.assign(
         {
-          small: false,
           background: false,
           pagerCount: 7,
           layout: 'sizes, prev, pager, next, jumper, ->, total',

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

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

+ 226 - 0
src/components/UpFile/src/components/upFile.vue

@@ -0,0 +1,226 @@
+<script setup lang="ts">
+import {
+  ElUpload,
+  ElForm,
+  ElFormItem,
+  ElRadioGroup,
+  ElRadio,
+  ElTreeSelect,
+  ElInput,
+  ElMessage
+} from 'element-plus'
+import type { UploadInstance } from 'element-plus'
+import { reactive, ref, watch } from 'vue'
+import { Dialog } from '@/components/Dialog'
+import { useValidator } from '@/hooks/web/useValidator'
+import { getFileType, upOnlineFile } from '@/api/system/file'
+import { REQUEST_BASE } from '@/constants'
+import { useUserStoreWithOut } from '@/store/modules/user'
+const userStore = useUserStoreWithOut()
+
+const modelValue = defineModel<boolean>()
+const props = defineProps({
+  fileType: {
+    type: Number,
+    default: 1
+  }
+})
+
+const { required } = useValidator()
+
+const emit = defineEmits(['confirm'])
+
+const rules = reactive({
+  label: [required()],
+  value: [required()]
+})
+
+const getFileTypeList = () => {
+  getFileType({
+    file_type: props.fileType
+  }).then((res) => {
+    fileTypeList.value = [
+      {
+        label: props.fileType == 1 ? '全部图片' : '全部视频',
+        value: 0,
+        children: []
+      }
+    ]
+    fileTypeList.value.push(
+      ...res.data.list.map(function functionMap(item) {
+        return {
+          label: item.name,
+          value: item.id,
+          children: item.children ? item.children.map(functionMap) : []
+        }
+      })
+    )
+  })
+}
+watch(
+  () => props.fileType,
+  () => {
+    getFileTypeList()
+  },
+  { immediate: true }
+)
+watch(modelValue, () => {
+  form.fileList = []
+})
+
+const fileTypeList = ref<any[]>([])
+const form = reactive({
+  pid: 0, //上传分类id
+  images: '', //上传图片链接地址
+  type: '1', //保存类型1文件2链接地址
+  fileList: []
+})
+const upload = ref<UploadInstance>()
+const saveLoading = ref(false)
+const uploadFileNum = ref(0)
+const confirm = async () => {
+  console.log(form.fileList, 'form.fileList')
+  saveLoading.value = true
+  // 判断是否本地上传
+  if (form.type == '1') {
+    upload.value!.submit()
+    return
+  }
+  if (form.type == '2') {
+    const re = await upOnlineFile({ pid: form.pid, images: form.images })
+    if (re) {
+      uploadFileNum.value = 0
+      modelValue.value = false
+      ElMessage.success('提交成功')
+      saveLoading.value = false
+      form.fileList = []
+      emit('confirm')
+    } else {
+      saveLoading.value = false
+    }
+    return
+  }
+
+  // const elFormExpose = await getElFormExpose()
+  // if (!elFormExpose) return
+  // const valid = await elFormExpose?.validate().catch((err) => {
+  //   console.log(err)
+  // })
+  // if (valid) {
+  //   const formData = await getFormData()
+  //   formData.id = Date.now()
+  //   emit('confirm', formData)
+  //   setValues({
+  //     label: '',
+  //     value: ''
+  //   })
+  //   modelValue.value = false
+  // }
+}
+const successUpLoad = () => {
+  uploadFileNum.value++
+  if (uploadFileNum.value == form.fileList.length) {
+    saveLoading.value = false
+    uploadFileNum.value = 0
+    form.fileList = []
+    modelValue.value = false
+    ElMessage.success('上传成功')
+    emit('confirm')
+  }
+}
+</script>
+
+<template>
+  <Dialog v-model="modelValue" :title="fileType == 1 ? '上传图片' : '上传视频'" width="600px">
+    <template #default>
+      <ElForm
+        :rules="rules"
+        :model="form"
+        label-width="auto"
+        v-loading="saveLoading"
+        element-loading-text="上传中..."
+        style="max-width: 600px"
+      >
+        <el-form-item label="上传方式:">
+          <el-radio-group v-model="form.type">
+            <el-radio value="1">本地上传</el-radio>
+            <el-radio value="2">网络上传</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="上传至分组:">
+          <el-tree-select
+            v-model="form.pid"
+            :data="fileTypeList"
+            check-strictly
+            no-data-text="暂无数据"
+            :placeholder="fileType == 1 ? '全部图片' : '全部视频'"
+            :render-after-expand="false"
+            style="width: 240px"
+          />
+        </el-form-item>
+        <el-form-item label="链接地址:" v-if="form.type == '2'">
+          <ElInput v-model="form.images" placeholder="请输入链接地址" />
+        </el-form-item>
+        <el-form-item label="上传图片:" v-show="form.type == '1'">
+          <ElUpload
+            ref="upload"
+            class="mt-10px"
+            @success="successUpLoad"
+            multiple
+            :action="`${REQUEST_BASE}/file/upload/0`"
+            listType="picture-card"
+            v-model:file-list="form.fileList"
+            accept="image/jpeg,image/png,image/gif,image/jpg,ico"
+            :auto-upload="false"
+            :data="{
+              pid: form.pid
+            }"
+            :headers="{
+              'Authori-Zation': userStore.getTokenKey + userStore.getToken
+            }"
+          >
+            <Icon :size="40" icon="vi-ep:plus" />
+            <template #tip>
+              <div class="el-upload__tip"
+                >建议上传图片最大宽度750px,不超过2MB;仅支持jpeg、png格式,可拖拽调整上传顺序</div
+              >
+            </template>
+          </ElUpload>
+        </el-form-item>
+        <!-- <el-form-item label="上传图片:" v-if="form.type == '1'">
+          <ElUpload
+            :action="`${REQUEST_BASE}/file/video_upload`"
+            multiple
+            v-model:file-list="form.fileList"
+            accept="audio/mpeg,.wma,.wav, video/mp4,.amr,.key,.xlsx,.xls,.apk,application/pdf"
+            :auto-upload="false"
+          >
+            <template #tip>
+              <div class="el-upload__tip">仅支持mp4、mpeg格式</div>
+            </template>
+          </ElUpload>
+        </el-form-item> -->
+      </ElForm>
+    </template>
+    <template #footer>
+      <div>
+        <BaseButton @click="() => (modelValue = false)">取消</BaseButton>
+        <BaseButton type="primary" :loading="saveLoading" @click="confirm">确认</BaseButton>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<style lang="less" scoped>
+:deep(.el-upload-list) {
+  &.el-upload-list--picture-card {
+    .el-upload-list__item {
+      width: 80px;
+      height: 80px;
+    }
+    .el-upload--picture-card {
+      width: 80px;
+      height: 80px;
+    }
+  }
+}
+</style>

+ 405 - 0
src/components/UpFile/src/index.vue

@@ -0,0 +1,405 @@
+<script setup lang="tsx">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ref, unref, nextTick, watch, reactive, onMounted } from 'vue'
+import {
+  ElTree,
+  ElInput,
+  ElDivider,
+  ElButton,
+  ElPagination,
+  ElCard,
+  ElImage,
+  ElTooltip,
+  ElEmpty,
+  ElMessageBox,
+  ElMessage
+} from 'element-plus'
+import { Search } from '@/components/Search'
+import { BaseButton } from '@/components/Button'
+import {
+  getFileType,
+  getFileList,
+  setFileType,
+  delFileType,
+  delFile,
+  updateFile
+} from '@/api/system/file'
+import { FormSchema } from '@/components/Form'
+import type Node from 'element-plus/es/components/tree/src/model/node'
+import upFile from './components/upFile.vue'
+import { useIcon } from '@/hooks/web/useIcon'
+const icon = useIcon({ icon: 'vi-ep:upload-filled' })
+const successicon = useIcon({ icon: 'vi-ep:select' })
+interface Tree {
+  label?: string
+  id: string | number
+  children?: Tree[]
+}
+const { t } = useI18n()
+const currentPage = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const fileType = ref(1)
+const dataList = ref<any[]>([])
+const searchParams = ref({ name: '' })
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'name',
+    label: '文件名',
+    component: 'Input',
+    colProps: { span: 8 }
+  }
+])
+const modelValue = defineModel<any[]>({
+  default: () => []
+})
+// 初始化数据
+onMounted(() => {
+  fetchDepartment()
+})
+
+const setSearchParams = (params: any) => {
+  currentPage.value = 1
+  searchParams.value = params
+  getList()
+}
+
+const getList = async () => {
+  const res = await getFileList({
+    pid: unref(currentNodeKey),
+    page: unref(currentPage),
+    limit: unref(pageSize),
+    file_type: unref(fileType),
+    ...unref(searchParams)
+  })
+  dataList.value = res.data.list.map((ree) => {
+    ree.checked = false
+    return ree
+  })
+  total.value = res.data.count
+}
+const treeEl = ref<typeof ElTree>()
+const currentNodeKey = ref('')
+const departmentList = ref<any[]>([])
+const fetchDepartment = async () => {
+  getList()
+  const res = await getFileType({
+    file_type: unref(fileType)
+  })
+  departmentList.value = res.data.list
+  departmentList.value.unshift({
+    children: [],
+    id: '',
+    name: unref(fileType) == 1 ? '全部图片' : '全部视频'
+  })
+  currentNodeKey.value = ''
+  await nextTick()
+  unref(treeEl)?.setCurrentKey(currentNodeKey.value)
+}
+
+const currentDepartment = ref('')
+watch(
+  () => currentDepartment.value,
+  (val) => {
+    unref(treeEl)!.filter(val)
+  }
+)
+
+const currentChange = (data) => {
+  currentNodeKey.value = data.id
+  currentPage.value = 1
+  getList()
+}
+// const tabChange = async (data) => {
+//   console.log('tabChange', data)
+//   fileType.value = data
+//   currentPage.value = 1
+//   fetchDepartment()
+// }
+
+const filterNode = (value: string, data) => {
+  if (!value) return true
+  return data.title.includes(value)
+}
+
+const showDrawer = ref(false)
+
+const AddAction = () => {
+  showDrawer.value = true
+}
+
+const delLoading = ref(false)
+watch(currentPage, (val) => {
+  if (val) {
+    // console.log(val)
+    currentPage.value = val
+    getList()
+  }
+})
+const append = async (data: Tree) => {
+  const { value } = await ElMessageBox.prompt('请输入名称', '添加文件分类', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputPattern: /^.{1}(.*)$/,
+    inputErrorMessage: '请填写内容'
+  })
+  const res = await setFileType({
+    pid: data.id,
+    name: value,
+    file_type: unref(fileType)
+  })
+
+  if (res) {
+    ElMessage({
+      type: 'success',
+      message: `添加成功`
+    })
+    fetchDepartment()
+  }
+}
+const editorTitle = async (item) => {
+  ElMessageBox.prompt('请输入名称', '编辑文件名称', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputValue: item.name,
+    inputPattern: /^(.*).{1}[\.png,\.jpg,\.jpeg]{1}$/,
+    inputErrorMessage: '请填写正确的文件名'
+  }).then(async ({ value }) => {
+    const res = await updateFile({
+      id: item.att_id,
+      real_name: value
+    })
+    if (res) {
+      ElMessage({
+        type: 'success',
+        message: `编辑成功`
+      })
+      getList()
+    }
+  })
+}
+const remove = async (node: Node, data: Tree) => {
+  await ElMessageBox.confirm('删除分类后无法恢复,是否删除?', '删除提示', {
+    confirmButtonText: '删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+  await delFileType(data.id)
+  ElMessage({
+    type: 'success',
+    message: `删除成功`
+  })
+  const parent = node.parent
+  const children: Tree[] = parent.data.children || parent.data
+  const index = children.findIndex((d) => d.id === data.id)
+  children.splice(index, 1)
+}
+
+const actionFileList = ref<any[]>([])
+const checkedFile = (item) => {
+  if (!checkedFileIs(item)) {
+    actionFileList.value.push(item.att_id)
+    modelValue.value.push(item)
+  } else {
+    const index = actionFileList.value.indexOf(item.att_id)
+    actionFileList.value.splice(index, 1)
+    modelValue.value.splice(index, 1)
+  }
+  // console.log(actionFileList.value, 'actionFileList.value')
+}
+const checkedFileIs = (item) => {
+  // console.log(item, 'item')
+  if (actionFileList.value.indexOf(item.att_id) == -1) {
+    return false
+  } else {
+    return true
+  }
+}
+const delData = async (id?: string) => {
+  await ElMessageBox.confirm('删除图片后无法恢复,是否删除?', '删除提示', {
+    confirmButtonText: '删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+  let ids = ''
+  if (id) {
+    ids = id
+  } else {
+    ids = actionFileList.value.join(',')
+  }
+  const re = await delFile({ ids })
+  actionFileList.value = []
+  modelValue.value = []
+  if (re) {
+    ElMessage({
+      type: 'success',
+      message: `删除成功`
+    })
+    getList()
+  }
+}
+</script>
+
+<template>
+  <div class="flex w-100% h-100%">
+    <ContentWrap class="w-250px">
+      <div class="flex justify-center items-center">
+        <ElInput
+          v-model="currentDepartment"
+          class="flex-[2] mr-3"
+          placeholder="搜索分类"
+          clearable
+        />
+        <BaseButton type="primary" @click="append({ id: '' })"> 新增 </BaseButton>
+      </div>
+      <ElDivider />
+      <ElTree
+        ref="treeEl"
+        :data="departmentList"
+        default-expand-all
+        :expand-on-click-node="false"
+        node-key="id"
+        :current-node-key="currentNodeKey"
+        :props="{
+          label: 'name'
+        }"
+        :filter-node-method="filterNode"
+        @current-change="currentChange"
+      >
+        <template #default="{ node, data }">
+          <span class="custom-tree-node">
+            <span>{{ node.label }}</span>
+            <span @click.stop>
+              <BaseButton link size="small" type="primary" @click="append(data)"> 添加 </BaseButton>
+              <BaseButton link size="small" type="primary" @click="remove(node, data)">
+                删除
+              </BaseButton>
+            </span>
+          </span>
+        </template>
+      </ElTree>
+    </ContentWrap>
+    <ContentWrap class="flex-[3] ml-20px">
+      <!-- <ElTabs class="tabs" v-model="fileType" @tab-change="tabChange">
+        <el-tab-pane label="图片" :name="1" />
+        <el-tab-pane label="视频" :name="2" />
+      </ElTabs> -->
+      <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+      <div class="mb-10px">
+        <BaseButton type="primary" :icon="icon" @click="AddAction">{{
+          fileType == 1 ? '上传图片' : '上传视屏'
+        }}</BaseButton>
+        <BaseButton
+          :loading="delLoading"
+          type="danger"
+          :disabled="actionFileList.length == 0"
+          @click="delData()"
+        >
+          {{ t('exampleDemo.del') }}
+        </BaseButton>
+      </div>
+      <div class="flex flex-wrap justify-start">
+        <ElCard class="card-list mr-3 mb-3" v-for="(item, index) in dataList" :key="index">
+          <template #header>
+            <ElTooltip effect="dark" :content="item.name" placement="top">
+              <div class="flex align-center">
+                <div class="white-nowrap">{{ item.name }}</div>
+                <BaseButton link size="small" type="primary" @click="editorTitle(item)">
+                  修改
+                </BaseButton>
+              </div>
+            </ElTooltip>
+          </template>
+          <ElImage
+            class="card-list-img"
+            fit="cover"
+            :key="index"
+            :src="item.satt_dir"
+            hide-on-click-modal
+            :alt="item.name"
+            :preview-src-list="[item.att_dir]"
+          >
+            <template #error>
+              <div class="h-100% flex items-center justify-center">
+                <Icon icon="vi-ep:picture" :size="30" />
+              </div>
+            </template>
+          </ElImage>
+          <template #footer>
+            <BaseButton
+              class="buttom-checked"
+              link
+              :loading="delLoading"
+              type="danger"
+              @click="delData(item.att_id)"
+            >
+              {{ t('exampleDemo.del') }}
+            </BaseButton>
+            <ElButton
+              class="buttom-checked"
+              :type="!checkedFileIs(item) ? 'info' : 'success'"
+              :icon="successicon"
+              size="small"
+              @click="checkedFile(item)"
+            />
+          </template>
+        </ElCard>
+        <ElEmpty class="flex-1" v-if="dataList.length == 0" description="暂无数据" />
+      </div>
+      <ElPagination
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        layout="sizes, prev, pager, next, jumper, ->, total"
+        :page-sizes="[10, 20, 30, 40, 50, 100]"
+        class="mt-10px"
+        :total="total"
+      />
+    </ContentWrap>
+    <upFile :fileType="fileType" v-model="showDrawer" @confirm="getList" />
+  </div>
+</template>
+<style lang="less" scoped>
+.custom-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding-right: 8px;
+}
+.card-list {
+  font-size: 10px;
+  width: 150px;
+  :deep(&.el-card) {
+    line-height: 1;
+    .el-card__header {
+      padding: 5px 10px !important;
+      .white-nowrap {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        align-self: center;
+      }
+    }
+
+    .el-card__body {
+      padding: 0 !important;
+      height: 150px;
+    }
+    .el-card__footer {
+      padding: 0 !important;
+      text-align: center;
+    }
+  }
+  .card-list-img {
+    width: 100%;
+    height: 100%;
+  }
+  .buttom-checked {
+    width: 74px;
+    height: 30px;
+    margin-left: 0;
+  }
+}
+</style>

+ 141 - 0
src/components/UpFile/src/upImageButtom.vue

@@ -0,0 +1,141 @@
+<script setup lang="tsx">
+import upFile from './index.vue'
+import { PropType, ref, defineModel } from 'vue'
+import { Dialog } from '@/components/Dialog'
+import { ElImage, ElMessage } from 'element-plus'
+const modelValue = defineModel<string[]>({
+  default: () => []
+})
+const props = defineProps({
+  num: {
+    type: Number as PropType<number>,
+    default: () => 1
+  },
+  tip: {
+    type: String as PropType<string>,
+    default: () => ''
+  }
+})
+const actionData = ref<any[]>([])
+const showImage = ref(false)
+const saveLoading = ref(false)
+const saveType = ref('add')
+const upImageIndex = ref(0)
+const editImage = (index: number, type) => {
+  if (type === 'del') {
+    modelValue.value.splice(index, 1)
+    modelValue.value = [...modelValue.value]
+  } else {
+    saveType.value = type
+    if (type === 'edit') {
+      upImageIndex.value = index
+    }
+    showImage.value = true
+  }
+}
+const emit = defineEmits(['confirm'])
+const save = async () => {
+  saveLoading.value = true
+  if (saveType.value === 'add') {
+    const length = modelValue.value.length
+    // console.log(actionData.value.length, props.num, length)
+    if (actionData.value.length > props.num - length) {
+      saveLoading.value = false
+      return ElMessage.error('最多选择再' + props.num + '张图片')
+    }
+    modelValue.value.push(...actionData.value.map((item: any) => item.att_dir))
+    modelValue.value = [...modelValue.value]
+  } else if (saveType.value === 'edit') {
+    if (actionData.value.length > 1) {
+      saveLoading.value = false
+      return ElMessage.error('最多选择1张图片')
+    }
+    modelValue.value.splice(upImageIndex.value, 1, actionData.value[0].att_dir)
+    modelValue.value = [...modelValue.value]
+  }
+
+  actionData.value = []
+  saveLoading.value = false
+  showImage.value = false
+}
+</script>
+
+<template>
+  <div class="w-full h-full">
+    <div class="flex">
+      <template v-if="modelValue.length > 0">
+        <div class="imageItem" v-for="(item, index) in modelValue" :key="index">
+          <ElImage :src="item" class="image">
+            <template #error>
+              <div class="flex items-center justify-center bg-gray-200 w-full h-full">
+                <Icon icon="vi-ep:picture" />
+              </div>
+            </template>
+          </ElImage>
+          <div class="imageDel flex items-center justify-center">
+            <icon @click="editImage(index, 'del')" class="cursor-pointer" icon="vi-ep:delete" />
+            <icon
+              @click="editImage(index, 'edit')"
+              class="ml-5px cursor-pointer"
+              icon="vi-ep:edit"
+            />
+          </div>
+        </div>
+      </template>
+      <div
+        @click="editImage(0, 'add')"
+        v-if="modelValue.length < num"
+        class="imageBorder flex items-center justify-center h-full"
+      >
+        <Icon :size="20" icon="vi-ep:plus" />
+      </div>
+    </div>
+    <div v-if="tip" class="text-xs text-gray-400 mt-5px">{{ tip }}</div>
+    <Dialog maxHeight="630px" v-model="showImage" title="选择图片" width="1190px">
+      <upFile v-model="actionData" />
+      <template #footer>
+        <BaseButton type="primary" :loading="saveLoading" @click="save"> 确定 </BaseButton>
+        <BaseButton @click="showImage = false">关闭</BaseButton>
+      </template>
+    </Dialog>
+  </div>
+</template>
+<style lang="less" scoped>
+.imageItem {
+  width: 50px;
+  height: 50px;
+  border-radius: 10px;
+  position: relative;
+  overflow: hidden;
+  line-height: 0;
+  margin-right: 5px;
+  background-color: rgb(229 231 235 / var(--un-bg-opacity)) /* #e5e7eb */;
+  &:hover {
+    .imageDel {
+      display: flex;
+    }
+  }
+  .imageDel {
+    display: none;
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.5);
+    color: #fff;
+  }
+  .image {
+    width: 100%;
+    height: 100%;
+    border-radius: 10px;
+  }
+}
+
+.imageBorder {
+  width: 50px;
+  height: 50px;
+  border: 1px dashed rgb(96, 98, 102);
+  border-radius: 10px;
+}
+</style>

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

@@ -34,9 +34,9 @@ const lockScreen = () => {
   dialogVisible.value = true
 }
 
-const toDocument = () => {
-  window.open('https://element-plus-admin-doc.cn/')
-}
+// const toDocument = () => {
+//   window.open('https://element-plus-admin-doc.cn/')
+// }
 
 const toPage = (path: string) => {
   push(path)
@@ -47,12 +47,19 @@ const toPage = (path: string) => {
   <ElDropdown class="custom-hover" :class="prefixCls" trigger="click">
     <div class="flex items-center">
       <img
+        v-if="userStore.getUserInfo && userStore.getUserInfo?.user_info?.head_pic != ''"
+        :src="userStore.getUserInfo?.user_info?.head_pic"
+        alt=""
+        class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
+      />
+      <img
+        v-else
         src="@/assets/imgs/avatar.jpg"
         alt=""
         class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
       />
       <span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">{{
-        userStore.getUserInfo?.username
+        userStore.getUserInfo?.user_info?.real_name || userStore.getUserInfo?.user_info?.account
       }}</span>
     </div>
     <template #dropdown>
@@ -62,9 +69,9 @@ const toPage = (path: string) => {
             {{ t('router.personalCenter') }}
           </div>
         </ElDropdownItem>
-        <ElDropdownItem>
+        <!-- <ElDropdownItem>
           <div @click="toDocument">{{ t('common.document') }}</div>
-        </ElDropdownItem>
+        </ElDropdownItem> -->
         <ElDropdownItem divided>
           <div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
         </ElDropdownItem>

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

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

+ 157 - 0
src/components/UserList/src/userList.vue

@@ -0,0 +1,157 @@
+<script setup lang="tsx">
+import { ElDrawer, ElSelect, ElOption, ElAvatar } from 'element-plus'
+import { reactive, ref, unref } from 'vue'
+import { useTable } from '@/hooks/web/useTable'
+import { Table, TableColumn } from '@/components/Table'
+import { BaseButton } from '@/components/Button'
+import { Search } from '@/components/Search'
+import { userSearch } from '@/api/user/types'
+import { FormSchema } from '@/components/Form'
+import { getUserList } from '@/api/user'
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const { data } = await getUserList({
+      page: unref(currentPage) || 1,
+      limit: unref(pageSize) || 10,
+      ...unref(searchParams),
+      status: 1
+    })
+    return {
+      list: data.list || [],
+      total: data.count || 0
+    }
+  }
+})
+const { dataList, loading, currentPage, pageSize, total } = tableState
+const { getList } = tableMethods
+const modelValue = defineModel<boolean>()
+
+const tableColumns = reactive<TableColumn[]>([
+  {
+    field: 'uid',
+    label: 'UID',
+    align: 'center',
+    headerAlign: 'center',
+    minWidth: '80px'
+  },
+  {
+    field: 'info',
+    label: '头像',
+    align: 'center',
+    headerAlign: 'center',
+    minWidth: '80px',
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <ElAvatar shape="circle" size="small" src={row.avatar}>
+              <img src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" />
+            </ElAvatar>
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'real_name',
+    label: '姓名',
+    minWidth: '130px'
+  },
+  {
+    field: 'nickname',
+    label: '昵称',
+    minWidth: '130px'
+  },
+  {
+    field: 'phone',
+    label: '手机号',
+    align: 'center',
+    headerAlign: 'center',
+    minWidth: '130px'
+  },
+  {
+    field: 'action',
+    label: '操作',
+    width: '90px',
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <BaseButton type="primary" onClick={() => confirm(row)}>
+              选择
+            </BaseButton>
+          </>
+        )
+      }
+    }
+  }
+])
+
+const emit = defineEmits(['confirm'])
+const confirm = (row: any) => {
+  const data = Object.assign({}, row)
+  emit('confirm', data)
+}
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'nickname',
+    label: '搜索',
+    component: 'Input',
+    componentProps: {
+      placeholder: '姓名、手机、UID查询',
+      slots: {
+        prepend: () => {
+          return (
+            <>
+              <ElSelect
+                v-model={searchParams.value.field_key}
+                placeholder="全部"
+                style={'width: 80px'}
+              >
+                <ElOption label="全部" value="all" />
+                <ElOption label="昵称" value="nickname" />
+                <ElOption label="手机" value="phone" />
+                <ElOption label="UID" value="uid" />
+              </ElSelect>
+            </>
+          )
+        }
+      }
+    }
+  }
+])
+const searchParams = ref<userSearch>({
+  nickname: '',
+  field_key: 'all'
+})
+const setSearchParams = (data: any) => {
+  searchParams.value = data
+  getList()
+}
+</script>
+
+<template>
+  <ElDrawer v-model="modelValue" title="选择用户" size="700px">
+    <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+    <Table
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
+      :pagination="{
+        total
+      }"
+      :columns="tableColumns"
+      stripe
+      :data="dataList"
+      :loading="loading"
+      @register="tableRegister"
+    />
+    <template #footer>
+      <div>
+        <BaseButton @click="() => (modelValue = false)">关闭</BaseButton>
+      </div>
+    </template>
+  </ElDrawer>
+</template>

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

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

+ 47 - 0
src/components/tableImage/tableImage.vue

@@ -0,0 +1,47 @@
+<script setup lang="tsx">
+import { PropType } from 'vue'
+import { ElImage } from 'element-plus'
+import { Icon } from '@/components/Icon'
+import { createImageViewer } from '@/components/ImageViewer'
+defineProps({
+  src: {
+    type: String,
+    default: ''
+  },
+  alt: {
+    type: String,
+    default: ''
+  },
+  list: {
+    type: Array as PropType<string[]>,
+    default: () => []
+  },
+  ind: {
+    type: Number,
+    default: 0
+  }
+})
+</script>
+
+<template>
+  <div
+    class="lh-none"
+    @click="
+      createImageViewer({
+        urlList: list.length > 0 ? list : [src],
+        initialIndex: ind || 0,
+        hideOnClickModal: true
+      })
+    "
+  >
+    <ElImage :alt="alt" fit="contain" class="w-[50px] h-[50px]" :src="src">
+      <template #error>
+        <div
+          class="flex items-center justify-center w-full h-full bg-gray-200 border-rd-[4px] overflow-hidden"
+        >
+          <Icon icon="vi-ep:picture" />
+        </div>
+      </template>
+    </ElImage>
+  </div>
+</template>

+ 2 - 1
src/components/verifition/Verify.vue

@@ -140,6 +140,7 @@ export default {
     captchaType: {
       immediate: true,
       handler(captchaType) {
+        // console.log(captchaType, 'captchaType')
         switch (captchaType.toString()) {
           case 'blockPuzzle':
             this.verifyType = '2'
@@ -171,7 +172,7 @@ export default {
       const slider = 'slider' + '-' + s.join('')
       const point = 'point' + '-' + s.join('')
       // 判断下是否存在 slider
-      console.log(localStorage.getItem('slider'))
+      // console.log(localStorage.getItem('slider'))
       if (!localStorage.getItem('slider')) {
         localStorage.setItem('slider', slider)
       }

+ 12 - 8
src/components/verifition/Verify/VerifyPoints.vue

@@ -65,6 +65,7 @@
 import { resetSize, _code_chars, _code_color1, _code_color2 } from './../utils/util'
 import { aesEncrypt } from './../utils/ase'
 import { ajCaptcha, ajCaptchaCheck } from '@/api/login'
+import { ElMessage } from 'element-plus'
 
 export default {
   name: 'VerifyPoints',
@@ -151,6 +152,7 @@ export default {
   },
   methods: {
     init() {
+      console.log('初始化')
       // 加载页面
       this.fontPos.splice(0, this.fontPos.length)
       this.checkPosArr.splice(0, this.checkPosArr.length)
@@ -182,7 +184,7 @@ export default {
             token: this.backToken
           }
           ajCaptchaCheck(data).then((res) => {
-            if (res.repCode == '0000') {
+            if (res) {
               this.barAreaColor = '#4cae4c'
               this.barAreaBorderColor = '#5cb85c'
               this.text = '验证成功'
@@ -195,6 +197,7 @@ export default {
               }
               this.$parent.$emit('success', { captchaVerification })
             } else {
+              ElMessage.closeAll()
               this.$parent.$emit('error', this)
               this.barAreaColor = '#d9534f'
               this.barAreaBorderColor = '#d9534f'
@@ -242,19 +245,20 @@ export default {
         clientUid: localStorage.getItem('point'),
         ts: Date.now() // 现在的时间戳
       }
+      // console.log(data, '请求接口')
       ajCaptcha(data).then((res) => {
-        if (res.repCode == '0000') {
-          this.pointBackImgBase = res.repData.originalImageBase64
-          this.backToken = res.repData.token
-          this.secretKey = res.repData.secretKey
-          this.poinTextList = res.repData.wordList
+        if (res.status == 200) {
+          this.pointBackImgBase = res.data.originalImageBase64
+          this.backToken = res.data.token
+          this.secretKey = res.data.secretKey
+          this.poinTextList = res.data.wordList
           this.text = '请依次点击【' + this.poinTextList.join(',') + '】'
         } else {
-          this.text = res.repMsg
+          this.text = res.msg || '请求失败'
         }
 
         // 判断接口请求次数是否失效
-        if (res.repCode == '6201') {
+        if (res.status == '6201') {
           this.pointBackImgBase = null
         }
       })

+ 8 - 7
src/components/verifition/Verify/VerifySlide.vue

@@ -79,7 +79,7 @@
 import { aesEncrypt } from './../utils/ase'
 import { resetSize } from './../utils/util'
 import { ajCaptcha, ajCaptchaCheck } from '@/api/login'
-
+import { ElMessage } from 'element-plus'
 //  "captchaType":"blockPuzzle",
 export default {
   name: 'VerifySlide',
@@ -193,7 +193,7 @@ export default {
     this.$el.onselectstart = function () {
       return false
     }
-    console.log(this.defaultImg)
+    // console.log(this.defaultImg)
   },
   methods: {
     init() {
@@ -289,8 +289,8 @@ export default {
             : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
           token: this.backToken
         }
-        ajCaptchaCheck(data)
-          .then((res) => {
+        ajCaptchaCheck(data).then((res) => {
+          if (res) {
             this.moveBlockBackgroundColor = '#5cb85c'
             this.leftBarBorderColor = '#5cb85c'
             this.iconColor = '#fff'
@@ -316,8 +316,8 @@ export default {
               this.$parent.closeBox()
               this.$parent.$emit('success', { captchaVerification })
             }, 1000)
-          })
-          .catch((res) => {
+          } else {
+            ElMessage.closeAll()
             this.moveBlockBackgroundColor = '#d9534f'
             this.leftBarBorderColor = '#d9534f'
             this.iconColor = '#fff'
@@ -331,7 +331,8 @@ export default {
             setTimeout(() => {
               this.tipWords = ''
             }, 1000)
-          })
+          }
+        })
         this.status = false
       }
     },

+ 6 - 3
src/components/verifition/utils/ase.js → src/components/verifition/utils/ase.ts

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

+ 0 - 36
src/components/verifition/utils/util.js

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

+ 98 - 0
src/components/verifition/utils/util.ts

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

+ 10 - 0
src/constants/index.ts

@@ -13,6 +13,16 @@ export const CONTENT_TYPE: AxiosContentType = 'application/json'
  */
 export const REQUEST_TIMEOUT = 60000
 
+/**
+ * 管理平台请求前缀
+ */
+export const REQUEST_BASE = '/adminapi'
+
+/**
+ * 通用请求前缀
+ */
+export const REQUEST_GENERAL = '/api'
+
 /**
  * 不重定向白名单
  */

+ 4 - 2
src/main.ts

@@ -30,16 +30,18 @@ import { setupRouter } from './router'
 // 权限
 import { setupPermission } from './directives'
 
+// 腾讯地图
+import TlbsMap from 'tlbs-map-vue'
+
 import { createApp } from 'vue'
 
 import App from './App.vue'
 
 import './permission'
-
 // 创建实例
 const setupAll = async () => {
   const app = createApp(App)
-
+  app.use(TlbsMap)
   await setupI18n(app)
 
   setupStore(app)

+ 2 - 0
src/permission.ts

@@ -35,6 +35,7 @@ router.beforeEach(async (to, from, next) => {
         appStore.serverDynamicRouter
           ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
           : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
+        // console.log('动态路由')
       } else {
         await permissionStore.generateRoutes('static')
       }
@@ -52,6 +53,7 @@ router.beforeEach(async (to, from, next) => {
     if (NO_REDIRECT_WHITE_LIST.indexOf(to.path) !== -1) {
       next()
     } else {
+      // console.log('会首页')
       next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
     }
   }

+ 7 - 12
src/router/model/Authorization.ts

@@ -1,28 +1,23 @@
 import { useI18n } from '@/hooks/web/useI18n'
 const { t } = useI18n()
 import { Layout } from '@/utils/routerHelper'
+const pre = 'authorization'
+
 export default {
   path: '/authorization',
   component: Layout,
-  name: 'Authorization',
+  name: `${pre}`,
+  redirect: '/authorization/user',
   meta: {
     title: t('router.authorization'),
     icon: 'vi-eos-icons:role-binding',
     alwaysShow: true
   },
   children: [
-    {
-      path: 'department',
-      component: () => import('@/views/Authorization/Department/Department.vue'),
-      name: 'Department',
-      meta: {
-        title: t('router.department')
-      }
-    },
     {
       path: 'user',
       component: () => import('@/views/Authorization/User/User.vue'),
-      name: 'User',
+      name: `${pre}-user`,
       meta: {
         title: t('router.user')
       }
@@ -30,7 +25,7 @@ export default {
     {
       path: 'menu',
       component: () => import('@/views/Authorization/Menu/Menu.vue'),
-      name: 'Menu',
+      name: `${pre}-menu`,
       meta: {
         title: t('router.menuManagement')
       }
@@ -38,7 +33,7 @@ export default {
     {
       path: 'role',
       component: () => import('@/views/Authorization/Role/Role.vue'),
-      name: 'Role',
+      name: `${pre}-role`,
       meta: {
         title: t('router.role')
       }

+ 0 - 267
src/router/model/ComponentsDemo.ts

@@ -1,267 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout, getParentLayout } from '@/utils/routerHelper'
-export default {
-  path: '/components',
-  component: Layout,
-  name: 'ComponentsDemo',
-  meta: {
-    title: t('router.component'),
-    icon: 'vi-bx:bxs-component',
-    alwaysShow: true
-  },
-  children: [
-    {
-      path: 'form',
-      component: getParentLayout(),
-      redirect: '/components/form/default-form',
-      name: 'Form',
-      meta: {
-        title: t('router.form'),
-        alwaysShow: true
-      },
-      children: [
-        {
-          path: 'default-form',
-          component: () => import('@/views/Components/Form/DefaultForm.vue'),
-          name: 'DefaultForm',
-          meta: {
-            title: t('router.defaultForm')
-          }
-        },
-        {
-          path: 'use-form',
-          component: () => import('@/views/Components/Form/UseFormDemo.vue'),
-          name: 'UseForm',
-          meta: {
-            title: 'UseForm'
-          }
-        }
-      ]
-    },
-    {
-      path: 'table',
-      component: getParentLayout(),
-      redirect: '/components/table/default-table',
-      name: 'TableDemo',
-      meta: {
-        title: t('router.table'),
-        alwaysShow: true
-      },
-      children: [
-        {
-          path: 'default-table',
-          component: () => import('@/views/Components/Table/DefaultTable.vue'),
-          name: 'DefaultTable',
-          meta: {
-            title: t('router.defaultTable')
-          }
-        },
-        {
-          path: 'use-table',
-          component: () => import('@/views/Components/Table/UseTableDemo.vue'),
-          name: 'UseTable',
-          meta: {
-            title: 'UseTable'
-          }
-        },
-        {
-          path: 'tree-table',
-          component: () => import('@/views/Components/Table/TreeTable.vue'),
-          name: 'TreeTable',
-          meta: {
-            title: t('router.treeTable')
-          }
-        },
-        {
-          path: 'table-image-preview',
-          component: () => import('@/views/Components/Table/TableImagePreview.vue'),
-          name: 'TableImagePreview',
-          meta: {
-            title: t('router.PicturePreview')
-          }
-        },
-        {
-          path: 'table-video-preview',
-          component: () => import('@/views/Components/Table/TableVideoPreview.vue'),
-          name: 'TableVideoPreview',
-          meta: {
-            title: t('router.tableVideoPreview')
-          }
-        },
-        {
-          path: 'card-table',
-          component: () => import('@/views/Components/Table/CardTable.vue'),
-          name: 'CardTable',
-          meta: {
-            title: t('router.cardTable')
-          }
-        }
-      ]
-    },
-    {
-      path: 'editor-demo',
-      component: getParentLayout(),
-      redirect: '/components/editor-demo/editor',
-      name: 'EditorDemo',
-      meta: {
-        title: t('router.editor'),
-        alwaysShow: true
-      },
-      children: [
-        {
-          path: 'editor',
-          component: () => import('@/views/Components/Editor/Editor.vue'),
-          name: 'Editor',
-          meta: {
-            title: t('router.richText')
-          }
-        },
-        {
-          path: 'json-editor',
-          component: () => import('@/views/Components/Editor/JsonEditor.vue'),
-          name: 'JsonEditor',
-          meta: {
-            title: t('router.jsonEditor')
-          }
-        }
-      ]
-    },
-    {
-      path: 'search',
-      component: () => import('@/views/Components/Search.vue'),
-      name: 'Search',
-      meta: {
-        title: t('router.search')
-      }
-    },
-    {
-      path: 'descriptions',
-      component: () => import('@/views/Components/Descriptions.vue'),
-      name: 'Descriptions',
-      meta: {
-        title: t('router.descriptions')
-      }
-    },
-    {
-      path: 'image-viewer',
-      component: () => import('@/views/Components/ImageViewer.vue'),
-      name: 'ImageViewer',
-      meta: {
-        title: t('router.imageViewer')
-      }
-    },
-    {
-      path: 'dialog',
-      component: () => import('@/views/Components/Dialog.vue'),
-      name: 'Dialog',
-      meta: {
-        title: t('router.dialog')
-      }
-    },
-    {
-      path: 'icon',
-      component: () => import('@/views/Components/Icon.vue'),
-      name: 'Icon',
-      meta: {
-        title: t('router.icon')
-      }
-    },
-    {
-      path: 'icon-picker',
-      component: () => import('@/views/Components/IconPicker.vue'),
-      name: 'IconPicker',
-      meta: {
-        title: t('router.iconPicker')
-      }
-    },
-    {
-      path: 'echart',
-      component: () => import('@/views/Components/Echart.vue'),
-      name: 'Echart',
-      meta: {
-        title: t('router.echart')
-      }
-    },
-    {
-      path: 'count-to',
-      component: () => import('@/views/Components/CountTo.vue'),
-      name: 'CountTo',
-      meta: {
-        title: t('router.countTo')
-      }
-    },
-    {
-      path: 'qrcode',
-      component: () => import('@/views/Components/Qrcode.vue'),
-      name: 'Qrcode',
-      meta: {
-        title: t('router.qrcode')
-      }
-    },
-    {
-      path: 'highlight',
-      component: () => import('@/views/Components/Highlight.vue'),
-      name: 'Highlight',
-      meta: {
-        title: t('router.highlight')
-      }
-    },
-    {
-      path: 'infotip',
-      component: () => import('@/views/Components/Infotip.vue'),
-      name: 'Infotip',
-      meta: {
-        title: t('router.infotip')
-      }
-    },
-    {
-      path: 'input-password',
-      component: () => import('@/views/Components/InputPassword.vue'),
-      name: 'InputPassword',
-      meta: {
-        title: t('router.inputPassword')
-      }
-    },
-    {
-      path: 'waterfall',
-      component: () => import('@/views/Components/Waterfall.vue'),
-      name: 'waterfall',
-      meta: {
-        title: t('router.waterfall')
-      }
-    },
-    {
-      path: 'image-cropping',
-      component: () => import('@/views/Components/ImageCropping.vue'),
-      name: 'ImageCropping',
-      meta: {
-        title: t('router.imageCropping')
-      }
-    },
-    {
-      path: 'video-player',
-      component: () => import('@/views/Components/VideoPlayer.vue'),
-      name: 'VideoPlayer',
-      meta: {
-        title: t('router.videoPlayer')
-      }
-    },
-    {
-      path: 'avatars',
-      component: () => import('@/views/Components/Avatars.vue'),
-      name: 'Avatars',
-      meta: {
-        title: t('router.avatars')
-      }
-    },
-    {
-      path: 'i-agree',
-      component: () => import('@/views/Components/IAgree.vue'),
-      name: 'IAgree',
-      meta: {
-        title: t('router.iAgree')
-      }
-    }
-  ]
-}

+ 0 - 71
src/router/model/Example.ts

@@ -1,71 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout } from '@/utils/routerHelper'
-export default {
-  path: '/example',
-  component: Layout,
-  redirect: '/example/example-dialog',
-  name: 'Example',
-  meta: {
-    title: t('router.example'),
-    icon: 'vi-ep:management',
-    alwaysShow: true
-  },
-  children: [
-    {
-      path: 'example-dialog',
-      component: () => import('@/views/Example/Dialog/ExampleDialog.vue'),
-      name: 'ExampleDialog',
-      meta: {
-        title: t('router.exampleDialog')
-      }
-    },
-    {
-      path: 'example-page',
-      component: () => import('@/views/Example/Page/ExamplePage.vue'),
-      name: 'ExamplePage',
-      meta: {
-        title: t('router.examplePage')
-      }
-    },
-    {
-      path: 'example-add',
-      component: () => import('@/views/Example/Page/ExampleAdd.vue'),
-      name: 'ExampleAdd',
-      meta: {
-        title: t('router.exampleAdd'),
-        noTagsView: true,
-        noCache: true,
-        hidden: true,
-        canTo: true,
-        activeMenu: '/example/example-page'
-      }
-    },
-    {
-      path: 'example-edit',
-      component: () => import('@/views/Example/Page/ExampleEdit.vue'),
-      name: 'ExampleEdit',
-      meta: {
-        title: t('router.exampleEdit'),
-        noTagsView: true,
-        noCache: true,
-        hidden: true,
-        canTo: true,
-        activeMenu: '/example/example-page'
-      }
-    },
-    {
-      path: 'example-detail',
-      component: () => import('@/views/Example/Page/ExampleDetail.vue'),
-      name: 'ExampleDetail',
-      meta: {
-        title: t('router.exampleDetail'),
-        noTagsView: true,
-        noCache: true,
-        hidden: true,
-        canTo: true,
-        activeMenu: '/example/example-page'
-      }
-    }
-  ]
-}

+ 0 - 19
src/router/model/ExternalLink.ts

@@ -1,19 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout } from '@/utils/routerHelper'
-export default {
-  path: '/external-link',
-  component: Layout,
-  meta: {},
-  name: 'ExternalLink',
-  children: [
-    {
-      path: 'https://element-plus-admin-doc.cn/',
-      name: 'DocumentLink',
-      meta: {
-        title: t('router.document'),
-        icon: 'vi-clarity:document-solid'
-      }
-    }
-  ]
-}

+ 0 - 52
src/router/model/Function.ts

@@ -1,52 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout } from '@/utils/routerHelper'
-export default {
-  path: '/function',
-  component: Layout,
-  redirect: '/function/multipleTabs',
-  name: 'Function',
-  meta: {
-    title: t('router.function'),
-    icon: 'vi-ri:function-fill',
-    alwaysShow: true
-  },
-  children: [
-    {
-      path: 'multiple-tabs',
-      component: () => import('@/views/Function/MultipleTabs.vue'),
-      name: 'MultipleTabs',
-      meta: {
-        title: t('router.multipleTabs')
-      }
-    },
-    {
-      path: 'multiple-tabs-demo/:id',
-      component: () => import('@/views/Function/MultipleTabsDemo.vue'),
-      name: 'MultipleTabsDemo',
-      meta: {
-        hidden: true,
-        title: t('router.details'),
-        canTo: true,
-        activeMenu: '/function/multiple-tabs'
-      }
-    },
-    {
-      path: 'request',
-      component: () => import('@/views/Function/Request.vue'),
-      name: 'Request',
-      meta: {
-        title: t('router.request')
-      }
-    },
-    {
-      path: 'test',
-      component: () => import('@/views/Function/Test.vue'),
-      name: 'Test',
-      meta: {
-        title: t('router.permission'),
-        permission: ['add', 'edit', 'delete']
-      }
-    }
-  ]
-}

+ 0 - 20
src/router/model/Guide.ts

@@ -1,20 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout } from '@/utils/routerHelper'
-export default {
-  path: '/guide',
-  component: Layout,
-  name: 'Guide',
-  meta: {},
-  children: [
-    {
-      path: 'index',
-      component: () => import('@/views/Guide/Guide.vue'),
-      name: 'GuideDemo',
-      meta: {
-        title: t('router.guide'),
-        icon: 'vi-cib:telegram-plane'
-      }
-    }
-  ]
-}

+ 0 - 64
src/router/model/Hooks.ts

@@ -1,64 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout } from '@/utils/routerHelper'
-export default {
-  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'
-      }
-    }
-  ]
-}

+ 0 - 62
src/router/model/Level.ts

@@ -1,62 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout, getParentLayout } from '@/utils/routerHelper'
-export default {
-  path: '/level',
-  component: Layout,
-  redirect: '/level/menu1/menu1-1/menu1-1-1',
-  name: 'Level',
-  meta: {
-    title: t('router.level'),
-    icon: 'vi-carbon:skill-level-advanced'
-  },
-  children: [
-    {
-      path: 'menu1',
-      name: 'Menu1',
-      component: getParentLayout(),
-      redirect: '/level/menu1/menu1-1/menu1-1-1',
-      meta: {
-        title: t('router.menu1')
-      },
-      children: [
-        {
-          path: 'menu1-1',
-          name: 'Menu11',
-          component: getParentLayout(),
-          redirect: '/level/menu1/menu1-1/menu1-1-1',
-          meta: {
-            title: t('router.menu11'),
-            alwaysShow: true
-          },
-          children: [
-            {
-              path: 'menu1-1-1',
-              name: 'Menu111',
-              component: () => import('@/views/Level/Menu111.vue'),
-              meta: {
-                title: t('router.menu111')
-              }
-            }
-          ]
-        },
-        {
-          path: 'menu1-2',
-          name: 'Menu12',
-          component: () => import('@/views/Level/Menu12.vue'),
-          meta: {
-            title: t('router.menu12')
-          }
-        }
-      ]
-    },
-    {
-      path: 'menu2',
-      name: 'Menu2',
-      component: () => import('@/views/Level/Menu2.vue'),
-      meta: {
-        title: t('router.menu2')
-      }
-    }
-  ]
-}

+ 23 - 0
src/router/model/Maintenance.ts

@@ -0,0 +1,23 @@
+import { Layout } from '@/utils/routerHelper'
+const pre = 'maintenance'
+export default {
+  path: '/maintenance',
+  component: Layout,
+  name: `${pre}`,
+  redirect: '/maintenance/clean',
+  meta: {
+    title: '系统维护',
+    icon: 'vi-ep:setting',
+    alwaysShow: true
+  },
+  children: [
+    {
+      path: 'clean',
+      component: () => import('@/views/Authorization/User/User.vue'),
+      name: `${pre}-clean`,
+      meta: {
+        title: '数据清理'
+      }
+    }
+  ]
+}

+ 48 - 0
src/router/model/Preserve.ts

@@ -0,0 +1,48 @@
+import { Layout } from '@/utils/routerHelper'
+const pre = 'preserve'
+
+export default {
+  path: '/preserve',
+  component: Layout,
+  name: `${pre}`,
+  redirect: '/preserve/config/class',
+  meta: {
+    title: '系统维护',
+    icon: 'vi-ep:setting',
+    alwaysShow: true
+  },
+  children: [
+    {
+      path: 'config/class',
+      component: () => import('@/views/System/class/index.vue'),
+      name: `${pre}-config-class`,
+      meta: {
+        title: '配置分类'
+      }
+    },
+    {
+      path: 'config/detail/:id',
+      component: () => import('@/views/System/config/detail.vue'),
+      name: `${pre}-config-detail`,
+      meta: {
+        title: '配置列表'
+      }
+    },
+    {
+      path: 'file/list',
+      name: `${pre}-file-list`,
+      component: () => import('@/views/System/file/list.vue'),
+      meta: {
+        title: '文件管理'
+      }
+    },
+    {
+      path: 'log/list',
+      name: `${pre}-log-list`,
+      component: () => import('@/views/System/log/index.vue'),
+      meta: {
+        title: '系统日志'
+      }
+    }
+  ]
+}

+ 47 - 0
src/router/model/Staff.ts

@@ -0,0 +1,47 @@
+import { Layout } from '@/utils/routerHelper'
+const pre = 'staff'
+
+export default {
+  path: `/${pre}`,
+  component: Layout,
+  name: `${pre}`,
+  meta: {
+    title: '员工管理',
+    icon: 'vi-eos-icons:role-binding',
+    alwaysShow: true
+  },
+  children: [
+    {
+      path: 'designer',
+      component: () => import('@/views/Staff/designer/index.vue'),
+      name: `${pre}-designer`,
+      meta: {
+        title: '设计师'
+      }
+    },
+    {
+      path: 'worker',
+      component: () => import('@/views/Staff/worker/index.vue'),
+      name: `${pre}-worker`,
+      meta: {
+        title: '装修工'
+      }
+    },
+    {
+      path: 'category',
+      component: () => import('@/views/Staff/category/index.vue'),
+      name: `${pre}-category`,
+      meta: {
+        title: '业务分类'
+      }
+    },
+    {
+      path: 'jobs',
+      component: () => import('@/views/Staff/jobs/index.vue'),
+      name: `${pre}-jobs`,
+      meta: {
+        title: '一口价分类'
+      }
+    }
+  ]
+}

+ 61 - 0
src/router/model/System.ts

@@ -0,0 +1,61 @@
+import { Layout } from '@/utils/routerHelper'
+const pre = 'system'
+
+export default {
+  path: '/system',
+  component: Layout,
+  name: `${pre}`,
+  redirect: '/system/config/class',
+  meta: {
+    title: '系统管理',
+    icon: 'vi-ep:setting',
+    alwaysShow: true
+  },
+  children: [
+    {
+      path: 'config/set/base',
+      component: () => import('@/views/System/set/set.vue'),
+      name: `${pre}-set-base`,
+      props: { id: 1 },
+      meta: {
+        title: '基础设置'
+      }
+    },
+    {
+      path: 'config/set/app',
+      component: () => import('@/views/System/set/set.vue'),
+      name: `${pre}-set-app`,
+      props: { id: 2 },
+      meta: {
+        title: '应用设置'
+      }
+    },
+    {
+      path: 'config/set/pay',
+      component: () => import('@/views/System/set/set.vue'),
+      name: `${pre}-set-pay`,
+      props: { id: 6 },
+      meta: {
+        title: '支付设置'
+      }
+    },
+    {
+      path: 'config/set/upload',
+      component: () => import('@/views/System/set/set.vue'),
+      name: `${pre}-set-upload`,
+      props: { id: 9 },
+      meta: {
+        title: '上传设置'
+      }
+    },
+    {
+      path: 'config/set/share',
+      component: () => import('@/views/System/set/set.vue'),
+      name: `${pre}-set-share`,
+      props: { id: 16 },
+      meta: {
+        title: '分销设置'
+      }
+    }
+  ]
+}

+ 40 - 0
src/router/model/User.ts

@@ -0,0 +1,40 @@
+import { Layout } from '@/utils/routerHelper'
+const pre = 'user'
+
+export default {
+  path: `/${pre}`,
+  component: Layout,
+  name: `${pre}`,
+  redirect: '/user/list',
+  meta: {
+    title: '用户管理',
+    icon: 'vi-ep:setting',
+    alwaysShow: true
+  },
+  children: [
+    {
+      path: 'list',
+      component: () => import('@/views/User/list/index.vue'),
+      name: `${pre}-list`,
+      meta: {
+        title: '用户列表'
+      }
+    },
+    {
+      path: 'level',
+      component: () => import('@/views/User/level/index.vue'),
+      name: `${pre}-level`,
+      meta: {
+        title: '应用设置'
+      }
+    },
+    {
+      path: 'group',
+      component: () => import('@/views/User/group/index.vue'),
+      name: `${pre}-group`,
+      meta: {
+        title: '用户分组'
+      }
+    }
+  ]
+}

+ 0 - 35
src/router/model/dashboard.ts

@@ -1,35 +0,0 @@
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-import { Layout } from '@/utils/routerHelper'
-export default {
-  path: '/dashboard',
-  component: Layout,
-  redirect: '/dashboard/analysis',
-  name: 'Dashboard',
-  meta: {
-    title: t('router.dashboard'),
-    icon: 'vi-ant-design:dashboard-filled',
-    alwaysShow: true
-  },
-  children: [
-    {
-      path: 'analysis',
-      component: () => import('@/views/Dashboard/Analysis.vue'),
-      name: 'Analysis',
-      meta: {
-        title: t('router.analysis'),
-        noCache: true,
-        affix: true
-      }
-    },
-    {
-      path: 'workplace',
-      component: () => import('@/views/Dashboard/Workplace.vue'),
-      name: 'Workplace',
-      meta: {
-        title: t('router.workplace'),
-        noCache: true
-      }
-    }
-  ]
-}

+ 24 - 4
src/store/modules/app.ts

@@ -6,6 +6,13 @@ import { ElMessage, ComponentSize } from 'element-plus'
 import { useCssVar } from '@vueuse/core'
 import { unref } from 'vue'
 import { useDark } from '@vueuse/core'
+interface systemInfo {
+  login_logo: string
+  logo_rectangle: string
+  logo_square: string
+  slide: string[]
+  upload_file_size_max: number
+}
 
 interface AppState {
   breadcrumb: boolean
@@ -33,6 +40,7 @@ interface AppState {
   footer: boolean
   theme: ThemeTypes
   fixedMenu: boolean
+  systemInfo: systemInfo
 }
 
 export const useAppStore = defineStore('app', {
@@ -41,15 +49,22 @@ export const useAppStore = defineStore('app', {
       sizeMap: ['default', 'large', 'small'],
       mobile: false, // 是否是移动端
       title: import.meta.env.VITE_APP_TITLE, // 标题
-      pageLoading: false, // 路由跳转loading
+      systemInfo: {
+        login_logo: '', //登录小logo
+        logo_rectangle: '', //大logo
+        logo_square: '', //缩进小logo
+        slide: [], //轮播图
+        upload_file_size_max: 1024 //图片上传大小限制
+      }, //系统logo
+      pageLoading: true, // 路由跳转loading
       breadcrumb: true, // 面包屑
       breadcrumbIcon: true, // 面包屑图标
       collapse: false, // 折叠菜单
-      uniqueOpened: false, // 是否只保持一个子菜单的展开
+      uniqueOpened: true, // 是否只保持一个子菜单的展开
       hamburger: true, // 折叠图标
       screenfull: true, // 全屏图标
       size: true, // 尺寸图标
-      locale: true, // 多语言图标
+      locale: false, // 多语言图标
       tagsView: true, // 标签页
       tagsViewIcon: true, // 是否显示标签图标
       logo: true, // logo
@@ -59,7 +74,6 @@ export const useAppStore = defineStore('app', {
       dynamicRouter: true, // 是否动态路由
       serverDynamicRouter: false, // 是否服务端渲染动态路由
       fixedMenu: false, // 是否固定菜单
-
       layout: 'classic', // layout布局
       isDark: false, // 是否是暗黑模式
       currentSize: 'default', // 组件尺寸
@@ -96,6 +110,9 @@ export const useAppStore = defineStore('app', {
     }
   },
   getters: {
+    getSystemInfo(): systemInfo {
+      return this.systemInfo
+    },
     getBreadcrumb(): boolean {
       return this.breadcrumb
     },
@@ -173,6 +190,9 @@ export const useAppStore = defineStore('app', {
     }
   },
   actions: {
+    setSystemInfo(systemInfo: systemInfo) {
+      this.systemInfo = systemInfo
+    },
     setBreadcrumb(breadcrumb: boolean) {
       this.breadcrumb = breadcrumb
     },

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

@@ -53,6 +53,7 @@ export const usePermissionStore = defineStore('permission', {
           // 直接读取静态路由表
           routerMap = cloneDeep(asyncRouterMap)
         }
+        // console.log('routerMap', routerMap)
         // 动态路由,404一定要放到最后面
         this.addRouters = routerMap.concat([
           {

+ 13 - 5
src/store/modules/user.ts

@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia'
 import { store } from '../index'
-import { UserLoginType, UserType } from '@/api/login/types'
+import { UserLoginType, UserInfo } from '@/api/login/types'
 import { ElMessageBox } from 'element-plus'
 import { useI18n } from '@/hooks/web/useI18n'
 import { loginOutApi } from '@/api/login'
@@ -8,12 +8,13 @@ import { useTagsViewStore } from './tagsView'
 import router from '@/router'
 
 interface UserState {
-  userInfo?: UserType
+  userInfo?: UserInfo
   tokenKey: string
   token: string
   roleRouters?: string[] | AppCustomRouteRecordRaw[]
   rememberMe: boolean
   loginInfo?: UserLoginType
+  roleName?: string[]
 }
 
 export const useUserStore = defineStore('user', {
@@ -25,7 +26,8 @@ export const useUserStore = defineStore('user', {
       roleRouters: undefined,
       // 记住我
       rememberMe: true,
-      loginInfo: undefined
+      loginInfo: undefined,
+      roleName: undefined
     }
   },
   getters: {
@@ -35,7 +37,7 @@ export const useUserStore = defineStore('user', {
     getToken(): string {
       return this.token
     },
-    getUserInfo(): UserType | undefined {
+    getUserInfo(): UserInfo | undefined {
       return this.userInfo
     },
     getRoleRouters(): string[] | AppCustomRouteRecordRaw[] | undefined {
@@ -46,6 +48,9 @@ export const useUserStore = defineStore('user', {
     },
     getLoginInfo(): UserLoginType | undefined {
       return this.loginInfo
+    },
+    getRoleName(): string[] | undefined {
+      return this.roleName
     }
   },
   actions: {
@@ -55,7 +60,7 @@ export const useUserStore = defineStore('user', {
     setToken(token: string) {
       this.token = token
     },
-    setUserInfo(userInfo?: UserType) {
+    setUserInfo(userInfo?: UserInfo) {
       this.userInfo = userInfo
     },
     setRoleRouters(roleRouters: string[] | AppCustomRouteRecordRaw[]) {
@@ -90,6 +95,9 @@ export const useUserStore = defineStore('user', {
     setRememberMe(rememberMe: boolean) {
       this.rememberMe = rememberMe
     },
+    setRoleName(rolename: string[]) {
+      this.roleName = rolename
+    },
     setLoginInfo(loginInfo: UserLoginType | undefined) {
       this.loginInfo = loginInfo
     }

+ 75 - 16
src/utils/analysis.ts

@@ -1,15 +1,4 @@
-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
-// }
+import { MenuData } from '@/api/system/menu/types'
 
 export const roluData = (data: Array<any>): Array<MenuData> => {
   const list = data.map(function arrMap(ls): MenuData {
@@ -30,18 +19,88 @@ export const roluData = (data: Array<any>): Array<MenuData> => {
       meta: {
         icon: ls.icon,
         title: ls.title,
-        activeMenu: ls.extend?.meta.activeMenu,
+        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
+}
+
+export const loginRoluData = (data: any[]): any[] => {
+  const menus = data.map(function arrMap(ls) {
+    const ar: MenuData = {
+      path: ls.path,
+      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.extend?.meta.hidden,
         permissionList: ls.extend?.permissionList
-      },
-      children
+      }
+    }
+    if (ls.children && ls.children.length > 0) {
+      ar.children = ls.children.map((lss) => {
+        return arrMap(lss)
+      })
     }
+    return ar
   })
-  return list
+  return menus
+}
+
+/**
+ * 将Base64编码的字符串转换为Blob对象
+ * 此函数主要用于处理图像或其他二进制数据的Base64表示形式,并将其转换为Blob对象,以便于在Web应用程序中使用
+ * @param base64 Base64编码的字符串,通常以'data:image/jpeg;base64,'开头,后跟实际的Base64数据
+ * @returns 返回一个Promise,解析为包含二进制数据的Blob对象
+ */
+export const parseBlob = (base64) => {
+  // 分割Base64字符串,获取MIME类型和实际的Base64数据
+  const arr = base64.split(',')
+  // 提取MIME类型,如'image/jpeg'
+  const mime = arr[0].match(/:(.*?);/)[1]
+  // 将Base64数据转换为二进制字符串
+  const bstr = atob(arr[1])
+  // 获取二进制字符串的长度
+  const n = bstr.length
+  // 创建一个Uint8Array来存储二进制数据
+  const u8arr = new Uint8Array(n)
+  // 将二进制字符串转换为Uint8Array
+  for (let i = 0; i < n; i++) {
+    u8arr[i] = bstr.charCodeAt(i)
+  }
+  // 使用Uint8Array创建并返回Blob对象
+  return new Blob([u8arr], { type: mime })
+}
+/**
+ * 将Base64编码的图片数据转换为File对象
+ *
+ * @param base64 Base64编码的图片数据
+ * @param name 图片文件的名称
+ * @returns 返回一个File对象,用于后续的图片上传或其他操作
+ */
+export const base64ToImage = (base64, name) => {
+  // 解析Base64编码的图片数据为Blob对象
+  const image = parseBlob(base64)
+  // 使用Blob对象创建一个新的File对象,并指定文件名称和类型
+  const upFial = new File([image], `${name}.${image.type.split('/')[1]}`, {
+    type: image.type
+  })
+  // 返回创建的File对象
+  return upFial
 }

+ 4 - 4
src/utils/color.ts

@@ -61,10 +61,10 @@ export const colorIsDark = (color: string) => {
 }
 
 /**
- * Darkens a HEX color given the passed percentage
- * @param {string} color The color to process
- * @param {number} amount The amount to change the color by
- * @returns {string} The HEX representation of the processed color
+ *给定通过的百分比,使HEX颜色变暗
+ *@paramstring}color要处理的颜色
+ *@param{number}amount更改颜色的量
+ *@返回{string}已处理颜色的十六进制表示
  */
 export const darken = (color: string, amount: number) => {
   color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color

+ 17 - 8
src/utils/routerHelper.ts

@@ -44,8 +44,8 @@ export const generateRoutesByFrontEnd = (
   basePath = '/'
 ): AppRouteRecordRaw[] => {
   const res: AppRouteRecordRaw[] = []
-  for (const route of routes) {
-    const meta = route.meta ?? {}
+  for (const item of keys) {
+    const meta = item.meta ?? {}
     // skip some route
     if (meta.hidden && !meta.canTo) {
       continue
@@ -54,18 +54,20 @@ export const generateRoutesByFrontEnd = (
     let data: Nullable<AppRouteRecordRaw> = null
 
     let onlyOneChild: Nullable<string> = null
-    if (route.children && route.children.length === 1 && !meta.alwaysShow) {
+    // 默认进入第一个子路由
+    if (item.children && item.children.length === 1 && !meta.alwaysShow) {
       onlyOneChild = (
-        isUrl(route.children[0].path)
-          ? route.children[0].path
-          : pathResolve(pathResolve(basePath, route.path), route.children[0].path)
+        isUrl(item.children[0].path)
+          ? item.children[0].path
+          : pathResolve(pathResolve(basePath, item.path), item.children[0].path)
       ) as string
     }
 
     // 开发者可以根据实际情况进行扩展
-    for (const item of keys) {
+    for (const route of routes) {
+      const path = pathResolve(basePath, route.path)
       // 通过路径去匹配
-      if (isUrl(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
+      if (isUrl(item.path) && (onlyOneChild === item.path || path === item.path)) {
         route.meta = Object.assign({}, item.meta)
         data = Object.assign({}, route)
       } else {
@@ -81,6 +83,13 @@ export const generateRoutesByFrontEnd = (
           item.children,
           pathResolve(basePath, data.path)
         )
+        // 设置默认跳转到第一个子目录
+        if (data.children?.length > 0) {
+          data.redirect = pathResolve(pathResolve(basePath, data.path), data.children[0].path)
+        }
+      }
+      if (data) {
+        break
       }
     }
     // recursive child routes

+ 106 - 0
src/utils/searchTime.ts

@@ -0,0 +1,106 @@
+import { FormSchema } from '@/components/Form'
+
+export const searchTime: FormSchema = {
+  field: 'time',
+  label: '时间范围',
+  component: 'DatePicker',
+  value: '',
+  componentProps: {
+    type: 'datetimerange',
+    shortcuts: [
+      {
+        text: '今天',
+        value: () => {
+          const end = new Date()
+          const start = new Date()
+          start.setDate(start.getDate())
+          setDayTimeStart(start)
+          return [start, end]
+        }
+      },
+      {
+        text: '昨天',
+        value: () => {
+          const end = new Date()
+          const start = new Date()
+          setDayTimeStart(end)
+          start.setDate(start.getDate() - 1)
+          setDayTimeStart(start)
+          return [start, end]
+        }
+      },
+      {
+        text: '最近七天',
+        value: () => {
+          const end = new Date()
+          const start = new Date()
+          start.setDate(start.getDate() - 7)
+          setDayTimeStart(start)
+          setDayTimeStart(end)
+          return [start, end]
+        }
+      },
+      {
+        text: '本月',
+        value: () => {
+          const end = new Date()
+          const start = new Date()
+          start.setMonth(start.getMonth() - 1)
+          setDayTimeStart(start)
+          return [start, end]
+        }
+      },
+      {
+        text: '上月',
+        value: () => {
+          const end = new Date()
+          const start = new Date()
+          start.setMonth(start.getMonth() - 3)
+          setDayTimeStart(start)
+          end.setMonth(end.getMonth() - 2)
+          setDayTimeStart(end)
+          return [start, end]
+        }
+      },
+      {
+        text: '本年',
+        value: () => {
+          const end = new Date()
+          const start = new Date()
+          start.setFullYear(start.getFullYear())
+          start.setMonth(0)
+          start.setDate(0)
+          setDayTimeStart(start)
+          return [start, end]
+        }
+      }
+    ],
+    placeholder: '请输入链接搜索'
+  }
+}
+
+/**
+ * 将给定的时间对象设置为当天的开始时间(即00:00:00
+ * 此函数的目的是标准化时间对象,使其代表一天的开始
+ * @param time 一个代表时间的Date对象通过引用传递,函数将直接修改这个对象
+ */
+function setDayTimeStart(time: any) {
+  time.setHours(0)
+  time.setMinutes(0)
+  time.setSeconds(0)
+}
+// function setMonthTimeEnd(time: any) {
+//   time.setHours(0)
+//   time.setMinutes(0)
+//   time.setSeconds(0)
+// }
+// function getLastDayOfMonth(currentDate) {
+//   // 2. 计算当前月份的下一个月的第一天
+//   const nextMonthFirstDay = new Date(currentDate)
+//   nextMonthFirstDay.setMonth(currentDate.getMonth() + 1, 1)
+
+//   // 3. 将下一个月的第一天日期减去一天,得到当前月份的最后一天
+//   const lastDayOfMonth = new Date(nextMonthFirstDay)
+//   lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1)
+//   return lastDayOfMonth
+// }

+ 41 - 21
src/views/Authorization/Menu/Menu.vue

@@ -1,12 +1,12 @@
 <script setup lang="tsx">
 import { reactive, ref, unref } from 'vue'
 import { getMenuListApi, newMenuApi, setMenuApi, delMenuApi } from '@/api/system/menu'
-import { MenuData, addRoleData } from '@/api/system/menu/types'
+import { 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 { ElMessage, ElTag, ElMessageBox } from 'element-plus'
+import { ElMessage, ElTag, ElMessageBox, ElDivider } from 'element-plus'
 import { Icon } from '@/components/Icon'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
@@ -20,11 +20,19 @@ const { t } = useI18n()
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
-    const res = await getMenuListApi(searchParams.value)
-    const list = await roluData(res.data.list)
-    return {
-      list: list || [],
-      total: res.data.count || 0
+    try {
+      const res = await getMenuListApi(searchParams.value)
+      const list = await roluData(res.data.list)
+      return {
+        list: list || [],
+        total: res.data.count || 0
+      }
+    } catch (e) {
+      // console.log(e, 'err')
+      return {
+        list: [],
+        total: 0
+      }
     }
   }
 })
@@ -50,6 +58,8 @@ const tableColumns = reactive<TableColumn[]>([
   {
     field: 'meta.icon',
     label: t('menu.icon'),
+    align: 'center',
+    headerAlign: 'center',
     width: 60,
     slots: {
       default: (data: any) => {
@@ -69,6 +79,8 @@ const tableColumns = reactive<TableColumn[]>([
   {
     field: 'type',
     label: '类型',
+    align: 'center',
+    headerAlign: 'center',
     width: 60,
     slots: {
       default: (data: any) => {
@@ -94,12 +106,14 @@ const tableColumns = reactive<TableColumn[]>([
   {
     field: 'path',
     label: t('menu.path'),
-    minWidth: 120
+    minWidth: 200
   },
   {
     field: 'status',
     label: t('menu.status'),
-    width: 60,
+    align: 'center',
+    headerAlign: 'center',
+    width: 80,
     slots: {
       default: (data: any) => {
         return (
@@ -112,23 +126,34 @@ const tableColumns = reactive<TableColumn[]>([
       }
     }
   },
+  {
+    field: 'sort',
+    label: '排序',
+    align: 'center',
+    headerAlign: 'center',
+    minWidth: 60
+  },
   {
     field: 'action',
     label: t('userDemo.action'),
-    width: 240,
+    width: 150,
     fixed: 'right',
+    align: 'center',
+    headerAlign: 'center',
     slots: {
       default: (data: any) => {
         const row = data.row
         return (
           <>
-            <BaseButton type="primary" onClick={() => action(row, 'edit')}>
+            <BaseButton link size="small" type="primary" onClick={() => action(row, 'edit')}>
               {t('exampleDemo.edit')}
             </BaseButton>
-            <BaseButton type="success" onClick={() => action(row, 'detail')}>
+            <ElDivider direction="vertical" />
+            <BaseButton link size="small" type="success" onClick={() => action(row, 'detail')}>
               {t('exampleDemo.detail')}
             </BaseButton>
-            <BaseButton type="danger" onClick={() => delAction(row)}>
+            <ElDivider direction="vertical" />
+            <BaseButton link size="small" type="danger" onClick={() => delAction(row)}>
               {t('exampleDemo.del')}
             </BaseButton>
           </>
@@ -178,16 +203,12 @@ const setSearchParams = (data: any) => {
 
 const dialogVisible = ref(false)
 const dialogTitle = ref('')
-
 const currentRow = ref()
 const actionType = ref('')
-
 const writeRef = ref<ComponentRef<typeof Write>>()
-
 const saveLoading = ref(false)
-
 const action = (row: any, type: string) => {
-  console.log(row, 'rowdate')
+  // console.log(row, 'rowdate')
   dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
   actionType.value = type
   currentRow.value = row
@@ -202,7 +223,7 @@ const AddAction = () => {
 }
 
 const delAction = async (row: any) => {
-  ElMessageBox.confirm('删除后无法恢复,是否删除菜单?', {
+  ElMessageBox.confirm('删除后无法恢复,是否删除?', {
     confirmButtonText: '删除',
     cancelButtonText: '取消',
     type: 'warning'
@@ -265,7 +286,6 @@ const save = async () => {
     </div>
     <Table
       :columns="tableColumns"
-      default-expand-all
       node-key="id"
       :data="dataList"
       :loading="loading"
@@ -273,7 +293,7 @@ const save = async () => {
     />
   </ContentWrap>
 
-  <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%">
+  <Dialog v-model="dialogVisible" :title="dialogTitle" :width="800">
     <Write
       :menuList="dataList"
       v-if="actionType !== 'detail'"

+ 1 - 1
src/views/Authorization/Menu/components/Detail.vue

@@ -120,7 +120,7 @@ const api = [
     label: '是否隐藏',
     slots: {
       default: (data) => {
-        console.log(data, 'data')
+        // console.log(data, 'data')
         return renderTag(data.meta.hidden)
       }
     }

+ 12 - 131
src/views/Authorization/Menu/components/Write.vue

@@ -5,10 +5,8 @@ import { PropType, reactive, watch, ref, nextTick } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
 import { useI18n } from '@/hooks/web/useI18n'
 import { cloneDeep } from 'lodash-es'
-import { ElButton, ElInput, ElPopconfirm, ElTable, ElTableColumn, ElTag } from 'element-plus'
-import AddButtonPermission from './AddButtonPermission.vue'
+import { ElButton } from 'element-plus'
 import addApiUrl from './addApiUrl.vue'
-import { BaseButton } from '@/components/Button'
 import { Icon } from '@/components/Icon'
 const { t } = useI18n()
 
@@ -25,33 +23,8 @@ const props = defineProps({
   }
 })
 
-const handleClose = async (tag: any) => {
-  const formData = await getFormData()
-  // 删除对应的权限
-  setValues({
-    permissionList: formData?.permissionList?.filter((v: any) => v.value !== tag.value)
-  })
-}
-
-const handleEdit = async (row: any) => {
-  // 深拷贝当前行数据到编辑行
-  permissionEditingRow.value = { ...row }
-}
-
-const handleSave = async () => {
-  const formData = await getFormData()
-  const index = formData?.permissionList?.findIndex((x) => x.id === permissionEditingRow.value.id)
-  if (index !== -1) {
-    formData.permissionList[index] = { ...permissionEditingRow.value }
-    permissionEditingRow.value = null // 重置编辑状态
-  }
-}
-
-const showDrawer = ref(false)
 const showApiDrawer = ref(false)
 
-const permissionEditingRow = ref<any>(null)
-
 const formSchema = reactive<FormSchema[]>([
   {
     field: 'type',
@@ -103,7 +76,6 @@ const formSchema = reactive<FormSchema[]>([
       clearable: true
     },
     optionApi: async () => {
-      console.log(props.menuList, 'props.menuList')
       return props.menuList || []
     }
   },
@@ -169,10 +141,9 @@ const formSchema = reactive<FormSchema[]>([
   {
     field: 'meta.icon',
     label: t('menu.icon'),
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入图标svg类名'
-    }
+    hidden: false,
+    component: 'IconPicker',
+    value: ''
   },
   {
     field: 'path',
@@ -214,94 +185,16 @@ const formSchema = reactive<FormSchema[]>([
       ]
     }
   },
-  {
-    field: 'meta.permissionList',
-    label: t('menu.permission'),
-    component: 'CheckboxGroup',
-    colProps: {
-      span: 24
-    },
-    formItemProps: {
-      slots: {
-        default: (data: any) => (
-          <>
-            <BaseButton
-              class="m-t-5px"
-              type="primary"
-              size="small"
-              onClick={() => (showDrawer.value = true)}
-            >
-              添加权限
-            </BaseButton>
-            <ElTable data={data?.permissionList}>
-              <ElTableColumn type="index" prop="id" />
-              <ElTableColumn
-                prop="value"
-                label="参数"
-                v-slots={{
-                  default: ({ row }: any) =>
-                    permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
-                      <ElInput v-model={permissionEditingRow.value.value} size="small" />
-                    ) : (
-                      <span>{row.value}</span>
-                    )
-                }}
-              />
-              <ElTableColumn
-                prop="label"
-                label="名称"
-                v-slots={{
-                  default: ({ row }: any) =>
-                    permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
-                      <ElInput v-model={permissionEditingRow.value.label} size="small" />
-                    ) : (
-                      <ElTag class="mr-1" key={row.value}>
-                        {row.label}
-                      </ElTag>
-                    )
-                }}
-              />
-              <ElTableColumn
-                label="操作"
-                width="180"
-                v-slots={{
-                  default: ({ row }: any) =>
-                    permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
-                      <ElButton size="small" type="primary" onClick={handleSave}>
-                        确定
-                      </ElButton>
-                    ) : (
-                      <>
-                        <ElButton size="small" type="primary" onClick={() => handleEdit(row)}>
-                          编辑
-                        </ElButton>
-                        <ElPopconfirm
-                          title="Are you sure to delete this?"
-                          onConfirm={() => handleClose(row)}
-                        >
-                          {{
-                            reference: () => (
-                              <ElButton size="small" type="danger">
-                                删除
-                              </ElButton>
-                            )
-                          }}
-                        </ElPopconfirm>
-                      </>
-                    )
-                }}
-              />
-            </ElTable>
-          </>
-        )
-      }
-    }
-  },
   {
     field: 'meta.hidden',
     label: t('menu.hidden'),
     component: 'Switch'
   },
+  {
+    field: 'meta.canTo',
+    label: t('menu.canTo'),
+    component: 'Switch'
+  },
   {
     field: 'meta.alwaysShow',
     label: t('menu.alwaysShow'),
@@ -328,11 +221,7 @@ const formSchema = reactive<FormSchema[]>([
     label: t('menu.noTagsView'),
     component: 'Switch'
   },
-  {
-    field: 'meta.canTo',
-    label: t('menu.canTo'),
-    component: 'Switch'
-  },
+
   {
     field: 'id',
     label: 'ID',
@@ -370,7 +259,7 @@ watch(
   async (value) => {
     if (!value) return
     const currentRow = cloneDeep(value)
-    console.log(currentRow, 'currentRow')
+    // console.log(currentRow, 'currentRow')
     cacheComponent.value = currentRow.type === 1 ? currentRow.component : ''
     setValues(currentRow)
     await nextTick()
@@ -392,7 +281,7 @@ const changeType = (bol: boolean) => {
   setSchema([
     {
       field: 'meta.icon',
-      path: 'remove',
+      path: 'hidden',
       value: bol
     },
     {
@@ -472,13 +361,6 @@ defineExpose({
   submit
 })
 
-const confirm = async (data: any) => {
-  const formData = await getFormData()
-  setValues({
-    permissionList: [...(formData?.permissionList || []), data]
-  })
-}
-
 const openApiSeach = (res: any) => {
   setValues({
     apiUrl: res.rule,
@@ -493,6 +375,5 @@ const openApiSeach = (res: any) => {
 
 <template>
   <Form :rules="rules" @register="formRegister" :schema="formSchema" />
-  <AddButtonPermission v-model="showDrawer" @confirm="confirm" />
   <addApiUrl v-model="showApiDrawer" @confirm="openApiSeach" />
 </template>

+ 3 - 2
src/views/Authorization/Menu/components/addApiUrl.vue

@@ -2,7 +2,7 @@
 import { FormSchema } from '@/components/Form'
 import { apiRuleList } from '@/api/system/menu'
 import { ElDrawer } from 'element-plus'
-import { reactive, ref, watch } from 'vue'
+import { reactive, ref } from 'vue'
 import { useTable } from '@/hooks/web/useTable'
 import { Table, TableColumn } from '@/components/Table'
 import { Search } from '@/components/Search'
@@ -73,7 +73,7 @@ const tableColumns = reactive<TableColumn[]>([
   {
     field: 'action',
     label: '操作',
-    width: '80px',
+    width: '90px',
     slots: {
       default: (data: any) => {
         const row = data.row
@@ -104,6 +104,7 @@ const confirm = (row: any) => {
         :columns="tableColumns"
         default-expand-all
         node-key="id"
+        stripe
         :data="dataList"
         :loading="loading"
         @register="tableRegister"

+ 19 - 9
src/views/Authorization/Role/Role.vue

@@ -4,7 +4,7 @@ import { getRoleListApi, addRoleApi, setRoleApi, delRoleApi } from '@/api/system
 import { useTable } from '@/hooks/web/useTable'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table, TableColumn } from '@/components/Table'
-import { ElTag, ElMessage, ElMessageBox } from 'element-plus'
+import { ElTag, ElMessage, ElMessageBox, ElDivider } from 'element-plus'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
 import { ContentWrap } from '@/components/ContentWrap'
@@ -16,7 +16,6 @@ const { t } = useI18n()
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
-    const { pageSize, currentPage } = tableState
     const res = await getRoleListApi({
       page: unref(currentPage) || 1,
       limit: unref(pageSize) || 10,
@@ -33,21 +32,26 @@ const { tableRegister, tableState, tableMethods } = useTable({
   }
 })
 
-const { dataList, loading, total, currentPage } = tableState
+const { dataList, loading, total, currentPage, pageSize } = tableState
 const { getList } = tableMethods
 
 const tableColumns = reactive<TableColumn[]>([
   {
     field: 'id',
+    width: '80px',
     label: 'ID'
   },
   {
     field: 'role_name',
-    label: t('role.roleName')
+    label: t('role.roleName'),
+    minWidth: '100px'
   },
   {
     field: 'status',
     label: t('menu.status'),
+    align: 'center',
+    headerAlign: 'center',
+    width: 100,
     slots: {
       default: (data: any) => {
         return (
@@ -63,21 +67,24 @@ const tableColumns = reactive<TableColumn[]>([
   {
     field: 'rules',
     label: '权限',
-    minWidth: '100px'
+    minWidth: '150px'
   },
   {
     field: 'action',
     label: t('userDemo.action'),
-    width: 240,
+    align: 'center',
+    headerAlign: 'center',
+    width: 120,
     slots: {
       default: (data: any) => {
         const row = data.row
         return (
           <>
-            <BaseButton type="primary" onClick={() => action(row, 'edit')}>
+            <BaseButton link size="small" type="primary" onClick={() => action(row, 'edit')}>
               {t('exampleDemo.edit')}
             </BaseButton>
-            <BaseButton type="danger" onClick={() => delAction(row)}>
+            <ElDivider direction="vertical" />
+            <BaseButton link size="small" type="danger" onClick={() => delAction(row)}>
               {t('exampleDemo.del')}
             </BaseButton>
           </>
@@ -180,7 +187,7 @@ const deepMenu = (data: Array<any>) => {
 const save = async () => {
   const write = unref(writeRef)
   const formData = await write?.submit()
-  console.log(formData, 'data')
+  // console.log(formData, 'data')
   if (formData) {
     saveLoading.value = true
     try {
@@ -221,9 +228,12 @@ const save = async () => {
       <BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
     </div>
     <Table
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
       :columns="tableColumns"
       default-expand-all
       node-key="id"
+      stripe
       :data="dataList"
       :loading="loading"
       :pagination="{

+ 3 - 3
src/views/Authorization/Role/components/Write.vue

@@ -1,11 +1,11 @@
 <script setup lang="tsx">
 import { Form, FormSchema } from '@/components/Form'
 import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch, ref, unref, nextTick, onMounted } from 'vue'
+import { PropType, reactive, watch, ref, unref, onMounted } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ElTree, ElCheckboxGroup, ElCheckbox } from 'element-plus'
-import { getAdminInfo } from '@/api/login'
+import { getAdminInfo } from '@/api/system/admin'
 import { getRoleApi } from '@/api/system/role'
 import { filter, eachTree } from '@/utils/tree'
 import { roluData } from '@/utils/analysis'
@@ -159,7 +159,7 @@ const submit = async () => {
       return checkedKeys.includes(item.id)
     })
     formData.menu = data || []
-    console.log(formData)
+    // console.log(formData)
     return formData
   }
 }

+ 227 - 308
src/views/Authorization/User/User.vue

@@ -1,242 +1,207 @@
 <script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { useI18n } from '@/hooks/web/useI18n'
-import { Table } from '@/components/Table'
-import { ref, unref, nextTick, watch, reactive } from 'vue'
-import { ElTree, ElInput, ElDivider } from 'element-plus'
-import { getDepartmentApi, getUserByIdApi, saveUserApi, deleteUserByIdApi } from '@/api/department'
-import type { DepartmentItem, DepartmentUserItem } from '@/api/department/types'
+import { reactive, ref, unref } from 'vue'
+import {
+  getUserRole,
+  addUserRole,
+  putUserRole,
+  delUserRole,
+  putUserRoleStatus
+} from '@/api/system/department'
+import { AddAdmins } from '@/api/system/department/types'
 import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { ElMessage, ElMessageBox, ElSwitch, ElTag, ElDivider } 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 { getRoleListApi } from '@/api/system/role'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
 import { BaseButton } from '@/components/Button'
-
 const { t } = useI18n()
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
-    const { pageSize, currentPage } = tableState
-    const res = await getUserByIdApi({
-      id: unref(currentNodeKey),
-      pageIndex: unref(currentPage),
-      pageSize: unref(pageSize),
-      ...unref(searchParams)
+    const res = await getUserRole({
+      ...searchParams.value,
+      page: unref(currentPage) || 1,
+      limit: unref(pageSize) || 10
     })
     return {
-      list: res.data.list || [],
-      total: res.data.total || 0
+      list:
+        res.data.list.map((ee) => {
+          ee.isStatus = ee.status === 1 ? true : false
+          ee.roles = ee.roles.split(',')
+          return ee
+        }) || [],
+      total: res.data.count || 0
     }
-  },
-  fetchDelApi: async () => {
-    const res = await deleteUserByIdApi(unref(ids))
-    return !!res
   }
 })
-const { total, loading, dataList, pageSize, currentPage } = tableState
-const { getList, getElTableExpose, delList } = tableMethods
+const { dataList, loading, total, currentPage, pageSize } = tableState
+const { getList } = tableMethods
 
-const crudSchemas = reactive<CrudSchema[]>([
+const tableColumns = reactive<TableColumn[]>([
   {
-    field: 'selection',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      type: 'selection'
-    }
+    field: 'id',
+    label: 'ID',
+    width: 80
   },
   {
-    field: 'index',
-    label: t('userDemo.index'),
-    form: {
-      hidden: true
-    },
-    search: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      type: 'index'
-    }
-  },
-  {
-    field: 'username',
-    label: t('userDemo.username')
+    field: 'account',
+    label: '账号',
+    width: 100
   },
   {
-    field: 'account',
-    label: t('userDemo.account')
+    field: 'phone',
+    label: '电话',
+    width: 120
   },
   {
-    field: 'department.id',
-    label: t('userDemo.department'),
-    detail: {
-      hidden: true
-      // slots: {
-      //   default: (data: DepartmentUserItem) => {
-      //     return <>{data.department.departmentName}</>
-      //   }
-      // }
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      component: 'TreeSelect',
-      componentProps: {
-        nodeKey: 'id',
-        props: {
-          label: 'departmentName'
-        }
-      },
-      optionApi: async () => {
-        const res = await getDepartmentApi()
-        return res.data.list
-      }
-    },
-    table: {
-      hidden: true
-    }
+    field: 'real_name',
+    label: '姓名',
+    width: 120
   },
   {
-    field: 'role',
-    label: t('userDemo.role'),
-    search: {
-      hidden: true
-    },
-    form: {
-      component: 'Select',
-      value: [],
-      componentProps: {
-        multiple: true,
-        collapseTags: true,
-        maxCollapseTags: 1
-      },
-      optionApi: async () => {
-        const res = await getRoleListApi()
-        return res.data?.list?.map((v) => ({
-          label: v.roleName,
-          value: v.id
-        }))
+    field: 'roles',
+    label: '权限',
+    minWidth: 80,
+    slots: {
+      default: (data: any) => {
+        return (
+          <>
+            {data.row.roles.map((item) => (
+              <ElTag class={'mr-2'}>{item}</ElTag>
+            ))}
+          </>
+        )
       }
     }
   },
   {
-    field: 'email',
-    label: t('userDemo.email'),
-    form: {
-      component: 'Input'
-    },
-    search: {
-      hidden: true
-    }
+    field: '_add_time',
+    label: '创建时间',
+    width: 160
   },
   {
-    field: 'createTime',
-    label: t('userDemo.createTime'),
-    form: {
-      component: 'Input'
-    },
-    search: {
-      hidden: true
+    field: 'status',
+    label: t('menu.status'),
+    align: 'center',
+    headerAlign: 'center',
+    width: 80,
+    slots: {
+      default: (data: any) => {
+        return (
+          <>
+            <ElSwitch
+              inlinePrompt
+              activeText="启用"
+              inactiveText="禁用"
+              v-model={data.row.isStatus}
+              onChange={() => putUserStatus(data.row)}
+            ></ElSwitch>
+          </>
+        )
+      }
     }
   },
   {
     field: 'action',
     label: t('userDemo.action'),
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    search: {
-      hidden: true
-    },
-    table: {
-      width: 240,
-      slots: {
-        default: (data: any) => {
-          const row = data.row as DepartmentUserItem
-          return (
-            <>
-              <BaseButton type="primary" onClick={() => action(row, 'edit')}>
-                {t('exampleDemo.edit')}
-              </BaseButton>
-              <BaseButton type="success" onClick={() => action(row, 'detail')}>
-                {t('exampleDemo.detail')}
-              </BaseButton>
-              <BaseButton type="danger" onClick={() => delData(row)}>
-                {t('exampleDemo.del')}
-              </BaseButton>
-            </>
-          )
-        }
+    width: 160,
+    fixed: 'right',
+    align: 'center',
+    headerAlign: 'center',
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <BaseButton link size="small" type="primary" onClick={() => action(row, 'edit')}>
+              {t('exampleDemo.edit')}
+            </BaseButton>
+            <ElDivider direction="vertical" />
+            <BaseButton link size="small" type="success" onClick={() => action(row, 'detail')}>
+              {t('exampleDemo.detail')}
+            </BaseButton>
+            <ElDivider direction="vertical" />
+            <BaseButton link size="small" type="danger" onClick={() => delAction(row)}>
+              {t('exampleDemo.del')}
+            </BaseButton>
+          </>
+        )
       }
     }
   }
 ])
 
-const { allSchemas } = useCrudSchemas(crudSchemas)
-
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  currentPage.value = 1
-  searchParams.value = params
-  getList()
-}
-
-const treeEl = ref<typeof ElTree>()
-
-const currentNodeKey = ref('')
-const departmentList = ref<DepartmentItem[]>([])
-const fetchDepartment = async () => {
-  const res = await getDepartmentApi()
-  departmentList.value = res.data.list
-  currentNodeKey.value =
-    (res.data.list[0] && res.data.list[0]?.children && res.data.list[0].children[0].id) || ''
-  await nextTick()
-  unref(treeEl)?.setCurrentKey(currentNodeKey.value)
-}
-fetchDepartment()
-
-const currentDepartment = ref('')
-watch(
-  () => currentDepartment.value,
-  (val) => {
-    unref(treeEl)!.filter(val)
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'status',
+    label: t('menu.status'),
+    component: 'Select',
+    componentProps: {
+      options: [
+        {
+          label: '全部',
+          value: ''
+        },
+        {
+          label: t('userDemo.disable'),
+          value: 0
+        },
+        {
+          label: t('userDemo.enable'),
+          value: 1
+        }
+      ]
+    }
+  },
+  {
+    field: 'name',
+    label: '搜索',
+    component: 'Input',
+    value: '',
+    componentProps: {
+      placeholder: '请输入姓名或者账号'
+    }
   }
-)
+])
 
-const currentChange = (data: DepartmentItem) => {
-  // if (data.children) return
-  currentNodeKey.value = data.id
-  currentPage.value = 1
+const searchParams = ref<{ status: number | string; name?: string }>({ status: '', name: '' })
+const setSearchParams = (data: any) => {
+  searchParams.value = data
   getList()
 }
 
-const filterNode = (value: string, data: DepartmentItem) => {
-  if (!value) return true
-  return data.departmentName.includes(value)
-}
-
 const dialogVisible = ref(false)
 const dialogTitle = ref('')
-
-const currentRow = ref<DepartmentUserItem>()
+const currentRow = ref()
 const actionType = ref('')
+const writeRef = ref<ComponentRef<typeof Write>>()
+const saveLoading = ref(false)
+const action = (row: any, type: string) => {
+  dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
+  actionType.value = type
+  currentRow.value = {
+    id: row.id,
+    account: row.account, //账号
+    real_name: row.real_name, //名字
+    pwd: '',
+    conf_pwd: '',
+    phone: row.phone, //手机号
+    roles: row.roles, //角色数组
+    status: row.status === 1 ? true : false //是否启用
+  }
+  dialogVisible.value = true
+}
+
+const putUserStatus = (row: any) => {
+  putUserRoleStatus({ id: row.id, status: row.status === 1 ? 0 : 1 }).then(() => {
+    getList()
+  })
+}
 
 const AddAction = () => {
   dialogTitle.value = t('exampleDemo.add')
@@ -245,140 +210,94 @@ const AddAction = () => {
   actionType.value = ''
 }
 
-const delLoading = ref(false)
-const ids = ref<string[]>([])
-
-const delData = async (row?: DepartmentUserItem) => {
-  const elTableExpose = await getElTableExpose()
-  ids.value = row
-    ? [row.id]
-    : elTableExpose?.getSelectionRows().map((v: DepartmentUserItem) => v.id) || []
-  delLoading.value = true
-
-  await delList(unref(ids).length).finally(() => {
-    delLoading.value = false
+const delAction = async (row: any) => {
+  ElMessageBox.confirm('删除后无法恢复,是否删除?', {
+    confirmButtonText: '删除',
+    cancelButtonText: '取消',
+    type: 'warning'
   })
-}
+    .then(async () => {
+      await delUserRole({ id: row.id })
+      await getList()
 
-const action = (row: DepartmentUserItem, type: string) => {
-  dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
-  actionType.value = type
-  currentRow.value = { ...row, department: unref(treeEl)?.getCurrentNode() || {} }
-  dialogVisible.value = true
+      ElMessage({
+        showClose: true,
+        message: '删除',
+        type: 'success'
+      })
+    })
+    .catch(() => {})
 }
-
-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
-    try {
-      const res = await saveUserApi(formData)
-      if (res) {
-        currentPage.value = 1
-        getList()
-      }
-    } catch (error) {
-      console.log(error)
-    } finally {
-      saveLoading.value = false
-      dialogVisible.value = false
+    const data: AddAdmins = {
+      id: formData.id || '',
+      account: formData.account, //账号
+      pwd: formData.pwd, //密码
+      conf_pwd: formData.conf_pwd, //确认密码
+      real_name: formData.real_name, //名字
+      phone: formData.phone, //手机号
+      roles: formData.roles, //角色数组
+      status: formData.status ? 1 : 0 //是否启用
+    }
+    // console.log(data, 'data')
+    if (actionType.value === 'edit') {
+      await putUserRole(data)
+    } else if (actionType.value === '') {
+      await addUserRole(data)
     }
+    ElMessage({
+      showClose: true,
+      message: '保存成功',
+      type: 'success'
+    })
+    getList()
+    saveLoading.value = false
+    dialogVisible.value = false
   }
 }
 </script>
 
 <template>
-  <div class="flex w-100% h-100%">
-    <ContentWrap class="w-250px">
-      <div class="flex justify-center items-center">
-        <div class="flex-1">{{ t('userDemo.departmentList') }}</div>
-        <ElInput
-          v-model="currentDepartment"
-          class="flex-[2]"
-          :placeholder="t('userDemo.searchDepartment')"
-          clearable
-        />
-      </div>
-      <ElDivider />
-      <ElTree
-        ref="treeEl"
-        :data="departmentList"
-        default-expand-all
-        :expand-on-click-node="false"
-        node-key="id"
-        :current-node-key="currentNodeKey"
-        :props="{
-          label: 'departmentName'
-        }"
-        :filter-node-method="filterNode"
-        @current-change="currentChange"
-      >
-        <template #default="{ data }">
-          <div
-            :title="data.departmentName"
-            class="whitespace-nowrap overflow-ellipsis overflow-hidden"
-          >
-            {{ data.departmentName }}
-          </div>
-        </template>
-      </ElTree>
-    </ContentWrap>
-    <ContentWrap class="flex-[3] ml-20px">
-      <Search
-        :schema="allSchemas.searchSchema"
-        @reset="setSearchParams"
-        @search="setSearchParams"
-      />
+  <ContentWrap>
+    <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+    <div class="mb-10px">
+      <BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
+    </div>
+    <Table
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
+      :columns="tableColumns"
+      default-expand-all
+      node-key="id"
+      stripe
+      :data="dataList"
+      :loading="loading"
+      @register="tableRegister"
+      :pagination="{
+        total
+      }"
+    />
+  </ContentWrap>
 
-      <div class="mb-10px">
-        <BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
-        <BaseButton :loading="delLoading" type="danger" @click="delData()">
-          {{ t('exampleDemo.del') }}
-        </BaseButton>
-      </div>
-      <Table
-        v-model:current-page="currentPage"
-        v-model:page-size="pageSize"
-        :columns="allSchemas.tableColumns"
-        :data="dataList"
-        :loading="loading"
-        @register="tableRegister"
-        :pagination="{
-          total
-        }"
-      />
-    </ContentWrap>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%">
+    <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
 
-    <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"
-      />
+    <Detail v-if="actionType === 'detail'" :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>
-  </div>
+    <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>

+ 44 - 9
src/views/Authorization/User/components/Detail.vue

@@ -1,20 +1,55 @@
-<script setup lang="ts">
-import { PropType } from 'vue'
-import { DepartmentUserItem } from '@/api/department/types'
+<script setup lang="tsx">
+import { PropType, ref } from 'vue'
 import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
+import { ElTag } from 'element-plus'
 
 defineProps({
   currentRow: {
-    type: Object as PropType<DepartmentUserItem>,
+    type: Object as PropType<any>,
     default: () => undefined
-  },
-  detailSchema: {
-    type: Array as PropType<DescriptionsSchema[]>,
-    default: () => []
   }
 })
+const renderTag = (enable?: boolean) => {
+  return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
+}
+
+const menu = ref<DescriptionsSchema[]>([
+  {
+    field: 'account',
+    label: '管理员账号'
+  },
+  {
+    field: 'real_name',
+    label: '管理员姓名'
+  },
+  {
+    field: 'roles',
+    label: '管理员身份',
+    slots: {
+      default: (data) => {
+        return (
+          <>
+            {data.roles.map((item) => (
+              <ElTag class={'mr-2'}>{item}</ElTag>
+            ))}
+          </>
+        )
+      }
+    }
+  },
+
+  {
+    field: 'status',
+    label: '状态',
+    slots: {
+      default: (data) => {
+        return renderTag(data.status)
+      }
+    }
+  }
+])
 </script>
 
 <template>
-  <Descriptions :schema="detailSchema" :data="currentRow || {}" />
+  <Descriptions :schema="menu" :data="currentRow || {}" />
 </template>

+ 123 - 15
src/views/Authorization/User/components/Write.vue

@@ -1,27 +1,134 @@
-<script setup lang="ts">
+<script setup lang="tsx">
 import { Form, FormSchema } from '@/components/Form'
 import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch } from 'vue'
-import { DepartmentUserItem } from '@/api/department/types'
+import { PropType, watch, ref, onMounted } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
+import { cloneDeep } from 'lodash-es'
+import { getRoleListApi } from '@/api/system/role'
 
-const { required } = useValidator()
+const { required, phone, lengthRange } = useValidator()
 
 const props = defineProps({
   currentRow: {
-    type: Object as PropType<DepartmentUserItem>,
-    default: () => undefined
+    type: Object as PropType<any>,
+    default: () => null
+  }
+})
+
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'account',
+    label: '管理员账号',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入管理员账号'
+    }
+  },
+  {
+    field: 'pwd',
+    label: '管理员密码',
+    component: 'Input',
+    componentProps: {
+      type: 'password',
+      placeholder: '请输入登录密码'
+    }
+  },
+  {
+    field: 'conf_pwd',
+    label: '确认密码',
+    component: 'Input',
+    componentProps: {
+      type: 'password',
+      placeholder: '请输入重复输入登录密码'
+    }
+  },
+  {
+    field: 'real_name',
+    label: '管理员姓名',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入管理员姓名'
+    }
   },
-  formSchema: {
-    type: Array as PropType<FormSchema[]>,
-    default: () => []
+  {
+    field: 'phone',
+    label: '管理员电话',
+    component: 'Input',
+    componentProps: {
+      type: 'number',
+      placeholder: '请输入联系电话'
+    }
+  },
+  {
+    field: 'roles',
+    label: '管理员身份',
+    component: 'Select',
+    componentProps: {
+      multiple: true
+    },
+    optionApi: async () => {
+      const re = await getRoleListApi({ page: 1, limit: 9999 })
+      return re.data.list.map((res) => {
+        return {
+          label: res.role_name,
+          value: res.id
+        }
+      })
+    }
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'Switch',
+    value: true
+  },
+  {
+    field: 'id',
+    label: '管理员id',
+    hidden: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入管理员账号'
+    }
   }
+])
+
+const rules = ref({
+  account: [
+    {
+      required: true,
+      ...lengthRange({
+        min: 4,
+        max: 64,
+        message: '管理员账号长度4-64位字符'
+      })
+    }
+  ],
+  pwd: [required('请填写登录密码')],
+  conf_pwd: [required('请确认密码')],
+  roles: [required('请选择管理员身份')],
+  phone: [
+    {
+      required: true,
+      ...phone()
+    }
+  ],
+  real_name: [required('请填写管理员姓名')]
 })
 
-const rules = reactive({
-  username: [required()],
-  account: [required()],
-  'department.id': [required()]
+onMounted(() => {
+  if (props.currentRow) {
+    rules.value.pwd = [
+      {
+        required: false
+      }
+    ]
+    rules.value.conf_pwd = [
+      {
+        required: false
+      }
+    ]
+  }
 })
 
 const { formRegister, formMethods } = useForm()
@@ -40,8 +147,9 @@ const submit = async () => {
 
 watch(
   () => props.currentRow,
-  (currentRow) => {
-    if (!currentRow) return
+  async (value) => {
+    if (!value) return
+    const currentRow = cloneDeep(value)
     setValues(currentRow)
   },
   {

+ 0 - 53
src/views/Components/Avatars.vue

@@ -1,53 +0,0 @@
-<script setup lang="ts">
-import { ContentWrap } from '@/components/ContentWrap'
-import { useI18n } from '@/hooks/web/useI18n'
-import { Avatars, AvatarItem } from '@/components/Avatars'
-import { ref } from 'vue'
-
-const { t } = useI18n()
-
-const data = ref<AvatarItem[]>([
-  {
-    name: 'Lily',
-    url: 'https://avatars.githubusercontent.com/u/3459374?v=4'
-  },
-  {
-    name: 'Amanda',
-    url: 'https://avatars.githubusercontent.com/u/3459375?v=4'
-  },
-  {
-    name: 'Daisy',
-    url: 'https://avatars.githubusercontent.com/u/3459376?v=4'
-  },
-  {
-    name: 'Olivia',
-    url: 'https://avatars.githubusercontent.com/u/3459377?v=4'
-  },
-  {
-    name: 'Tina',
-    url: 'https://avatars.githubusercontent.com/u/3459378?v=4'
-  },
-  {
-    name: 'Kitty',
-    url: 'https://avatars.githubusercontent.com/u/3459323?v=4'
-  },
-  {
-    name: 'Helen',
-    url: 'https://avatars.githubusercontent.com/u/3459324?v=4'
-  },
-  {
-    name: 'Sophia',
-    url: 'https://avatars.githubusercontent.com/u/3459325?v=4'
-  },
-  {
-    name: 'Wendy',
-    url: 'https://avatars.githubusercontent.com/u/3459326?v=4'
-  }
-])
-</script>
-
-<template>
-  <ContentWrap :title="t('router.avatars')" :message="t('avatarsDemo.title')">
-    <Avatars :data="data" />
-  </ContentWrap>
-</template>

+ 0 - 100
src/views/Components/CountTo.vue

@@ -1,100 +0,0 @@
-<script setup lang="ts">
-import { ContentWrap } from '@/components/ContentWrap'
-import { useI18n } from '@/hooks/web/useI18n'
-import { CountTo } from '@/components/CountTo'
-import { ElRow, ElCol, ElInputNumber, ElInput } from 'element-plus'
-import { ref, unref } from 'vue'
-
-const { t } = useI18n()
-
-const countRef = ref<ComponentRef<typeof CountTo>>()
-
-const startVal = ref(0)
-
-const endVal = ref(1314512)
-
-const duration = ref(3000)
-
-const decimals = ref(0)
-
-const separator = ref(',')
-
-const prefix = ref('¥ ')
-
-const suffix = ref(' rmb')
-
-const autoplay = ref(false)
-
-const start = () => {
-  unref(countRef)?.start()
-}
-
-const pauseResume = () => {
-  unref(countRef)?.pauseResume()
-}
-</script>
-
-<template>
-  <ContentWrap :title="t('countToDemo.countTo')" :message="t('countToDemo.countToDes')">
-    <div class="text-center mb-40px">
-      <CountTo
-        ref="countRef"
-        :start-val="startVal"
-        :end-val="endVal"
-        :duration="duration"
-        :decimals="decimals"
-        :separator="separator"
-        :prefix="prefix"
-        :suffix="suffix"
-        :autoplay="autoplay"
-        class="text-30px font-bold text-[var(--el-color-primary)]"
-      />
-    </div>
-    <ElRow :gutter="20" justify="space-between">
-      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
-        <div class="flex mb-20px items-center">
-          <span class="min-w-90px text-right">{{ t('countToDemo.startVal') }}:</span>
-          <ElInputNumber v-model="startVal" :min="0" />
-        </div>
-      </ElCol>
-      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
-        <div class="flex mb-20px items-center">
-          <span class="min-w-90px text-right">{{ t('countToDemo.endVal') }}:</span>
-          <ElInputNumber v-model="endVal" :min="1" />
-        </div>
-      </ElCol>
-      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
-        <div class="flex mb-20px items-center">
-          <span class="min-w-90px text-right">{{ t('countToDemo.duration') }}:</span>
-          <ElInputNumber v-model="duration" :min="1000" />
-        </div>
-      </ElCol>
-      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
-        <div class="flex mb-20px items-center">
-          <span class="min-w-90px text-right">{{ t('countToDemo.separator') }}:</span>
-          <ElInput v-model="separator" />
-        </div>
-      </ElCol>
-      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
-        <div class="flex mb-20px items-center">
-          <span class="min-w-90px text-right">{{ t('countToDemo.prefix') }}:</span>
-          <ElInput v-model="prefix" />
-        </div>
-      </ElCol>
-      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
-        <div class="flex mb-20px items-center">
-          <span class="min-w-90px text-right">{{ t('countToDemo.suffix') }}:</span>
-          <ElInput v-model="suffix" />
-        </div>
-      </ElCol>
-      <ElCol :span="24">
-        <div class="text-center">
-          <BaseButton type="primary" @click="start">{{ t('countToDemo.start') }}</BaseButton>
-          <BaseButton @click="pauseResume">
-            {{ t('countToDemo.pause') }}/{{ t('countToDemo.resume') }}
-          </BaseButton>
-        </div>
-      </ElCol>
-    </ElRow>
-  </ContentWrap>
-</template>

+ 0 - 192
src/views/Components/Descriptions.vue

@@ -1,192 +0,0 @@
-<script setup lang="tsx">
-import { Descriptions } from '@/components/Descriptions'
-import { useI18n } from '@/hooks/web/useI18n'
-import { reactive } from 'vue'
-import { Form } from '@/components/Form'
-import { ElFormItem, ElInput } from 'element-plus'
-import { useValidator } from '@/hooks/web/useValidator'
-import { useForm } from '@/hooks/web/useForm'
-import { DescriptionsSchema } from '@/components/Descriptions'
-
-const { required } = useValidator()
-
-const { t } = useI18n()
-
-const data = reactive({
-  username: 'chenkl',
-  nickName: '梦似花落。',
-  age: 26,
-  phone: '13655971xxxx',
-  email: '502431556@qq.com',
-  addr: '这是一个很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的地址',
-  sex: '男',
-  certy: '3505831994xxxxxxxx'
-})
-
-const schema = reactive<DescriptionsSchema[]>([
-  {
-    field: 'username',
-    label: t('descriptionsDemo.username')
-  },
-  {
-    field: 'nickName',
-    label: t('descriptionsDemo.nickName')
-  },
-  {
-    field: 'phone',
-    label: t('descriptionsDemo.phone')
-  },
-  {
-    field: 'email',
-    label: t('descriptionsDemo.email')
-  },
-  {
-    field: 'addr',
-    label: t('descriptionsDemo.addr'),
-    span: 24
-  }
-])
-
-const schema2 = reactive<DescriptionsSchema[]>([
-  {
-    field: 'username',
-    label: t('descriptionsDemo.username'),
-    slots: {
-      label: (row) => {
-        return <span class="is-required--item">{row.label}</span>
-      },
-      default: () => {
-        return (
-          <ElFormItem prop="username">
-            <ElInput v-model={form.username} />
-          </ElFormItem>
-        )
-      }
-    }
-  },
-  {
-    field: 'nickName',
-    label: t('descriptionsDemo.nickName'),
-    slots: {
-      label: (row) => {
-        return <span class="is-required--item">{row.label}</span>
-      },
-      default: () => {
-        return (
-          <ElFormItem prop="nickName">
-            <ElInput v-model={form.nickName} />
-          </ElFormItem>
-        )
-      }
-    }
-  },
-  {
-    field: 'phone',
-    label: t('descriptionsDemo.phone'),
-    slots: {
-      label: (row) => {
-        return <span class="is-required--item">{row.label}</span>
-      },
-      default: () => {
-        return (
-          <ElFormItem prop="phone">
-            <ElInput v-model={form.phone} />
-          </ElFormItem>
-        )
-      }
-    }
-  },
-  {
-    field: 'email',
-    label: t('descriptionsDemo.email'),
-    slots: {
-      label: (row) => {
-        return <span class="is-required--item">{row.label}</span>
-      },
-      default: () => {
-        return (
-          <ElFormItem prop="email">
-            <ElInput v-model={form.email} />
-          </ElFormItem>
-        )
-      }
-    }
-  },
-  {
-    field: 'addr',
-    label: t('descriptionsDemo.addr'),
-    slots: {
-      label: (row) => {
-        return <span class="is-required--item">{row.label}</span>
-      },
-      default: () => {
-        return (
-          <ElFormItem prop="addr">
-            <ElInput v-model={form.addr} />
-          </ElFormItem>
-        )
-      }
-    },
-    span: 24
-  }
-])
-
-const form = reactive({
-  username: '',
-  nickName: '',
-  phone: '',
-  email: '',
-  addr: ''
-})
-
-const rules = reactive({
-  username: [required()],
-  nickName: [required()],
-  phone: [required()],
-  email: [required()],
-  addr: [required()]
-})
-
-const { formRegister, formMethods } = useForm()
-const { getElFormExpose } = formMethods
-
-const formValidation = async () => {
-  const elFormExpose = await getElFormExpose()
-  elFormExpose?.validate((isValid) => {
-    console.log(isValid)
-  })
-}
-</script>
-
-<template>
-  <Descriptions
-    :title="t('descriptionsDemo.descriptions')"
-    :message="t('descriptionsDemo.descriptionsDes')"
-    :data="data"
-    :schema="schema"
-  />
-
-  <Form is-custom :model="form" :rules="rules" @register="formRegister">
-    <Descriptions
-      :title="t('descriptionsDemo.form')"
-      :data="data"
-      :schema="schema2"
-      class="mt-20px"
-    />
-    <div class="text-center mt-10px">
-      <BaseButton @click="formValidation"> {{ t('formDemo.formValidation') }} </BaseButton>
-    </div>
-  </Form>
-</template>
-
-<style lang="less" scoped>
-:deep(.is-required--item) {
-  position: relative;
-
-  &::before {
-    margin-right: 4px;
-    color: var(--el-color-danger);
-    content: '*';
-  }
-}
-</style>

+ 0 - 165
src/views/Components/Dialog.vue

@@ -1,165 +0,0 @@
-<script setup lang="ts">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Dialog } from '@/components/Dialog'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ref, reactive } from 'vue'
-import { Form, FormSchema } from '@/components/Form'
-import { useValidator } from '@/hooks/web/useValidator'
-import { getDictOneApi } from '@/api/common'
-import { useForm } from '@/hooks/web/useForm'
-import Echart from './Echart.vue'
-import ResizeDialog from '@/components/Dialog/src/ResizeDialog.vue'
-
-const { required } = useValidator()
-
-const { t } = useI18n()
-
-const dialogVisible = ref(false)
-
-const dialogVisible2 = ref(false)
-
-const dialogVisible3 = ref(false)
-
-const dialogVisible4 = ref(false)
-
-const { formRegister, formMethods } = useForm()
-const { getElFormExpose } = formMethods
-
-const schema = reactive<FormSchema[]>([
-  {
-    field: 'field1',
-    label: t('formDemo.input'),
-    component: 'Input',
-    formItemProps: {
-      rules: [required()]
-    }
-  },
-  {
-    field: 'field2',
-    label: t('formDemo.select'),
-    component: 'Select',
-    // componentProps: {
-    //   options: []
-    // },
-    optionApi: async () => {
-      const res = await getDictOneApi()
-      return res.data
-    }
-  },
-  {
-    field: 'field3',
-    label: t('formDemo.radio'),
-    component: 'RadioGroup',
-    componentProps: {
-      options: [
-        {
-          label: 'option-1',
-          value: '1'
-        },
-        {
-          label: 'option-2',
-          value: '2'
-        }
-      ]
-    }
-  },
-  {
-    field: 'field4',
-    label: t('formDemo.checkbox'),
-    component: 'CheckboxGroup',
-    value: [],
-    componentProps: {
-      options: [
-        {
-          label: 'option-1',
-          value: '1'
-        },
-        {
-          label: 'option-2',
-          value: '2'
-        }
-      ]
-    }
-  },
-  {
-    field: 'field5',
-    component: 'DatePicker',
-    label: t('formDemo.datePicker'),
-    componentProps: {
-      type: 'date'
-    }
-  },
-  {
-    field: 'field6',
-    component: 'TimeSelect',
-    label: t('formDemo.timeSelect')
-  }
-])
-
-const formSubmit = async () => {
-  const elFormExpose = await getElFormExpose()
-  elFormExpose?.validate((valid) => {
-    if (valid) {
-      console.log('submit success')
-    } else {
-      console.log('submit fail')
-    }
-  })
-}
-</script>
-
-<template>
-  <ContentWrap :title="t('dialogDemo.dialog')" :message="t('dialogDemo.dialogDes')">
-    <BaseButton type="primary" @click="dialogVisible = !dialogVisible">
-      {{ t('dialogDemo.open') }}
-    </BaseButton>
-
-    <BaseButton type="primary" @click="dialogVisible2 = !dialogVisible2">
-      {{ t('dialogDemo.combineWithForm') }}
-    </BaseButton>
-
-    <Dialog v-model="dialogVisible" :title="t('dialogDemo.dialog')">
-      <Echart />
-      <template #footer>
-        <BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
-      </template>
-    </Dialog>
-
-    <Dialog v-model="dialogVisible2" :title="t('dialogDemo.dialog')">
-      <Form :schema="schema" @register="formRegister" />
-      <template #footer>
-        <BaseButton type="primary" @click="formSubmit">{{ t('dialogDemo.submit') }}</BaseButton>
-        <BaseButton @click="dialogVisible2 = false">{{ t('dialogDemo.close') }}</BaseButton>
-      </template>
-    </Dialog>
-  </ContentWrap>
-
-  <ContentWrap
-    class="mt-10px"
-    :title="t('dialogDemo.resizeDialog')"
-    :message="t('dialogDemo.dialogDes')"
-  >
-    <BaseButton type="primary" @click="dialogVisible3 = !dialogVisible3">
-      {{ t('dialogDemo.open') }}
-    </BaseButton>
-
-    <BaseButton type="primary" @click="dialogVisible4 = !dialogVisible4">
-      {{ t('dialogDemo.combineWithForm') }}
-    </BaseButton>
-
-    <ResizeDialog v-model="dialogVisible3" :title="t('dialogDemo.dialog')">
-      <Echart />
-      <template #footer>
-        <BaseButton @click="dialogVisible3 = false">{{ t('dialogDemo.close') }}</BaseButton>
-      </template>
-    </ResizeDialog>
-
-    <ResizeDialog v-model="dialogVisible4" :title="t('dialogDemo.dialog')">
-      <Form :schema="schema" @register="formRegister" />
-      <template #footer>
-        <BaseButton type="primary" @click="formSubmit">{{ t('dialogDemo.submit') }}</BaseButton>
-        <BaseButton @click="dialogVisible4 = false">{{ t('dialogDemo.close') }}</BaseButton>
-      </template>
-    </ResizeDialog>
-  </ContentWrap>
-</template>

+ 0 - 36
src/views/Components/Echart.vue

@@ -1,36 +0,0 @@
-<script setup lang="ts">
-import { ContentWrap } from '@/components/ContentWrap'
-import { useI18n } from '@/hooks/web/useI18n'
-import { pieOptions, barOptions, lineOptions, wordOptions } from '@/views/Dashboard/echarts-data'
-import { Echart } from '@/components/Echart'
-import { ElRow, ElCol, ElCard } from 'element-plus'
-
-const { t } = useI18n()
-</script>
-
-<template>
-  <ContentWrap :title="t('echartDemo.echart')" :message="t('echartDemo.echartDes')">
-    <ElRow :gutter="20" justify="space-between">
-      <ElCol :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
-        <ElCard shadow="hover" class="mb-20px">
-          <Echart :options="pieOptions" :height="300" />
-        </ElCard>
-      </ElCol>
-      <ElCol :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
-        <ElCard shadow="hover" class="mb-20px">
-          <Echart :options="barOptions" :height="300" />
-        </ElCard>
-      </ElCol>
-      <ElCol :span="24">
-        <ElCard shadow="hover" class="mb-20px">
-          <Echart :options="lineOptions" :height="350" />
-        </ElCard>
-      </ElCol>
-      <ElCol :span="24">
-        <ElCard shadow="hover" class="mb-20px">
-          <Echart :options="wordOptions as any" :height="300" />
-        </ElCard>
-      </ElCol>
-    </ElRow>
-  </ContentWrap>
-</template>

+ 0 - 23
src/views/Components/Editor/CodeEditor.vue

@@ -1,23 +0,0 @@
-<script setup lang="tsx">
-import { CodeEditor } from '@/components/CodeEditor'
-import { useI18n } from '@/hooks/web/useI18n'
-import { ContentWrap } from '@/components/ContentWrap'
-import { ref } from 'vue'
-import { BaseButton } from '@/components/Button'
-import { ElDivider } from 'element-plus'
-const content = ref(
-  'public class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println("Hello, World!");\n  }\n}'
-)
-const { t } = useI18n()
-
-const MonacoEditRef = ref<InstanceType<typeof CodeEditor>>()
-</script>
-<template>
-  <ContentWrap :title="t('richText.codeEditor')" :message="t('richText.codeEditorDes')">
-    <BaseButton @click="console.log(content)">控制台打印内容</BaseButton>
-    <ElDivider />
-    <div class="edit-container h-60vh">
-      <CodeEditor ref="MonacoEditRef" v-model="content" language="java" />
-    </div>
-  </ContentWrap>
-</template>

+ 0 - 32
src/views/Components/Editor/Editor.vue

@@ -1,32 +0,0 @@
-<script setup lang="ts">
-import { ContentWrap } from '@/components/ContentWrap'
-import { Editor, EditorExpose } from '@/components/Editor'
-import { useI18n } from '@/hooks/web/useI18n'
-import { IDomEditor } from '@wangeditor/editor'
-import { ref, onMounted, unref } from 'vue'
-
-const { t } = useI18n()
-
-const change = (editor: IDomEditor) => {
-  console.log(editor.getHtml())
-}
-
-const editorRef = ref<typeof Editor & EditorExpose>()
-
-const defaultHtml = ref('')
-
-onMounted(async () => {
-  const editor = await unref(editorRef)?.getEditorRef()
-  console.log(editor)
-})
-
-setTimeout(() => {
-  defaultHtml.value = '<p>hello <strong>world</strong></p>'
-}, 3000)
-</script>
-
-<template>
-  <ContentWrap :title="t('richText.richText')" :message="t('richText.richTextDes')">
-    <Editor v-model="defaultHtml" ref="editorRef" @change="change" />
-  </ContentWrap>
-</template>

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff