王飞 1 жил өмнө
parent
commit
f80dc8530b
43 өөрчлөгдсөн 1967 нэмэгдсэн , 1044 устгасан
  1. 1 1
      CHANGELOG.md
  2. 7 7
      mock/department/index.ts
  3. 1 1
      mock/menu/index.ts
  4. 2 0
      package.json
  5. 4 0
      src/api/common/index.ts
  6. 41 0
      src/api/config/index.ts
  7. 21 11
      src/api/department/index.ts
  8. 1 0
      src/api/department/types.ts
  9. 26 0
      src/api/dict/index.ts
  10. 0 26
      src/api/manage/dict.ts
  11. 4 0
      src/api/menu/index.ts
  12. 26 2
      src/api/role/index.ts
  13. 21 0
      src/api/user/index.ts
  14. 4 1
      src/components/IconPicker/src/IconPicker.vue
  15. 1 1
      src/components/ImageViewer/index.ts
  16. 4 7
      src/config/axios/config.ts
  17. 12 12
      src/config/axios/service.ts
  18. 31 1
      src/hooks/web/useValidator.ts
  19. 3 3
      src/locales/en.ts
  20. 10 10
      src/locales/zh-CN.ts
  21. 26 2
      src/router/index.ts
  22. 2 2
      src/store/modules/app.ts
  23. 2 0
      src/utils/routerHelper.ts
  24. 160 0
      src/views/Authorization/Config/Config.vue
  25. 95 0
      src/views/Authorization/Config/components/Write.vue
  26. 105 125
      src/views/Authorization/Department/Department.vue
  27. 52 116
      src/views/Authorization/Dict/Dict.vue
  28. 0 278
      src/views/Authorization/Dict/components/Detail.vue
  29. 4 1
      src/views/Authorization/Dict/components/Write.vue
  30. 204 0
      src/views/Authorization/Institution/Institution.vue
  31. 206 0
      src/views/Authorization/Institution/components/User/User.vue
  32. 129 0
      src/views/Authorization/Institution/components/User/components/Write.vue
  33. 300 0
      src/views/Authorization/Institution/components/Write.vue
  34. 37 8
      src/views/Authorization/Menu/Menu.vue
  35. 5 6
      src/views/Authorization/Menu/components/Write.vue
  36. 51 33
      src/views/Authorization/Role/Role.vue
  37. 60 51
      src/views/Authorization/Role/components/Write.vue
  38. 121 289
      src/views/Authorization/User/User.vue
  39. 121 18
      src/views/Authorization/User/components/Write.vue
  40. 42 28
      src/views/Login/components/LoginForm.vue
  41. 3 3
      src/views/hooks/useTagsView.vue
  42. 10 0
      types/router.d.ts
  43. 12 1
      vite.config.ts

+ 1 - 1
CHANGELOG.md

@@ -164,7 +164,7 @@ All notable changes to this project will be documented in this file. See [standa
 * 综合示例重构 ([9a0259d](https://github.com/kailong321200875/vue-element-plus-admin/commit/9a0259de5c47970502db95f4dda24998ad5d9efe))
 * 菜单管理 ([c72b3a3](https://github.com/kailong321200875/vue-element-plus-admin/commit/c72b3a33aab7d3605770a64d23b8a84ef4ad68d2))
 * 角色管理 ([47016a5](https://github.com/kailong321200875/vue-element-plus-admin/commit/47016a535f2b7a22ab498bee197bc30a983f507d))
-* 部门管理 ([28d0785](https://github.com/kailong321200875/vue-element-plus-admin/commit/28d0785be842022cae7808c23e1f19eaab5fb996))
+* 机构管理 ([28d0785](https://github.com/kailong321200875/vue-element-plus-admin/commit/28d0785be842022cae7808c23e1f19eaab5fb996))
 * 重构Dialog组件示例 ([9a78ac9](https://github.com/kailong321200875/vue-element-plus-admin/commit/9a78ac977eb0cfb3bd6c2a9b96e69d9f010017f4))
 
 

+ 7 - 7
mock/department/index.ts

@@ -11,7 +11,7 @@ const citys = ['厦门总公司', '北京分公司', '上海分公司', '福州
 
 for (let i = 0; i < 5; i++) {
   departmentList.push({
-    // 部门名称
+    // 机构名称
     departmentName: citys[i],
     id: toAnyString(),
     createTime: '@datetime',
@@ -21,7 +21,7 @@ for (let i = 0; i < 5; i++) {
     remark: '@cword(10, 15)',
     children: [
       {
-        // 部门名称
+        // 机构名称
         departmentName: '研发部',
         createTime: '@datetime',
         // 状态
@@ -30,7 +30,7 @@ for (let i = 0; i < 5; i++) {
         remark: '@cword(10, 15)'
       },
       {
-        // 部门名称
+        // 机构名称
         departmentName: '产品部',
         createTime: '@datetime',
         // 状态
@@ -39,7 +39,7 @@ for (let i = 0; i < 5; i++) {
         remark: '@cword(10, 15)'
       },
       {
-        // 部门名称
+        // 机构名称
         departmentName: '运营部',
         createTime: '@datetime',
         // 状态
@@ -48,7 +48,7 @@ for (let i = 0; i < 5; i++) {
         remark: '@cword(10, 15)'
       },
       {
-        // 部门名称
+        // 机构名称
         departmentName: '市场部',
         createTime: '@datetime',
         // 状态
@@ -57,7 +57,7 @@ for (let i = 0; i < 5; i++) {
         remark: '@cword(10, 15)'
       },
       {
-        // 部门名称
+        // 机构名称
         departmentName: '销售部',
         createTime: '@datetime',
         // 状态
@@ -66,7 +66,7 @@ for (let i = 0; i < 5; i++) {
         remark: '@cword(10, 15)'
       },
       {
-        // 部门名称
+        // 机构名称
         departmentName: '客服部',
         createTime: '@datetime',
         // 状态

+ 1 - 1
mock/menu/index.ts

@@ -20,7 +20,7 @@ export default [
             {
               path: '/dashboard',
               component: '#',
-              redirect: '/dashboard/analysis',
+              redirect: '/dashboard/workplace',
               name: 'Dashboard',
               status: Mock.Random.integer(0, 1),
               id: 1,

+ 2 - 0
package.json

@@ -43,11 +43,13 @@
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.1",
     "mockjs": "^1.1.0",
+    "moment": "^2.29.4",
     "nprogress": "^0.2.0",
     "pinia": "^2.1.6",
     "pinia-plugin-persist": "^1.0.0",
     "qrcode": "^1.5.3",
     "qs": "^6.11.2",
+    "spark-md5": "^3.0.2",
     "url": "^0.11.3",
     "vue": "3.3.4",
     "vue-i18n": "9.4.1",

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

@@ -9,3 +9,7 @@ export const getDictApi = () => {
 export const getDictOneApi = async () => {
   return request.get({ url: '/dict/one' })
 }
+
+export const getQiniuToken = async () => {
+  return request.get({ url: '/common/qiniu/token' })
+}

+ 41 - 0
src/api/config/index.ts

@@ -0,0 +1,41 @@
+import request from '@/config/axios'
+
+export const getListApi = () => {
+  return request.get({ url: '/sys/config/list' })
+}
+
+export const ailyunOss = (data: any) => {
+  return request.post({ url: '/sys/config/ailyunOss', data })
+}
+
+export const baiduOCR = (data: any) => {
+  return request.post({ url: '/sys/config/baiduOCR', data })
+}
+
+export const commParam = (data: any) => {
+  return request.post({ url: '/sys/config/commParam', data })
+}
+
+export const hongruanArc = (data: any) => {
+  return request.post({ url: '/sys/config/hongruanArc', data })
+}
+
+export const ronglian = (data: any) => {
+  return request.post({ url: '/sys/config/mobile', data })
+}
+
+export const qiniu = (data: any) => {
+  return request.post({ url: '/sys/config/oss', data })
+}
+
+export const refresh = () => {
+  return request.post({ url: '/sys/config/refresh' })
+}
+
+export const changzhuo = (data: any) => {
+  return request.post({ url: '/sys/config/sms', data })
+}
+
+export const youmeng = (data: any) => {
+  return request.post({ url: '/sys/config/um', data })
+}

+ 21 - 11
src/api/department/index.ts

@@ -1,30 +1,40 @@
 import request from '@/config/axios'
-import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
 
-export const getDepartmentApi = () => {
-  return request.get<DepartmentListResponse>({ url: '/department/list' })
+export const getDepartmentItemApi = (id: string) => {
+  return request.get({ url: `/oms/organization/info/${id}` })
 }
 
-export const getUserByIdApi = (params: DepartmentUserParams) => {
-  return request.get<DepartmentUserResponse>({ url: '/department/users', params })
+export const getUserListByIdApi = (params: any) => {
+  return request.get({ url: '/oms/emp/list', params })
 }
 
-export const deleteUserByIdApi = (ids: string[] | number[]) => {
-  return request.post({ url: '/department/user/delete', data: { ids } })
+export const getUserByIdApi = (id: string) => {
+  return request.get({ url: `/oms/emp/info/${id}`, params })
+}
+
+export const deleteUserByIdApi = (data: any) => {
+  return request.post({ url: '/oms/emp/delete', data })
 }
 
 export const saveUserApi = (data: any) => {
-  return request.post({ url: '/department/user/save', data })
+  return request.post({ url: '/oms/emp/save', data })
+}
+export const updateUserApi = (data: any) => {
+  return request.post({ url: '/oms/emp/update', data })
 }
 
 export const saveDepartmentApi = (data: any) => {
-  return request.post({ url: '/department/save', data })
+  return request.post({ url: '/oms/organization/save', data })
+}
+
+export const updateDepartmentApi = (data: any) => {
+  return request.post({ url: '/oms/organization/update', data })
 }
 
 export const deleteDepartmentApi = (ids: string[] | number[]) => {
-  return request.post({ url: '/department/delete', data: { ids } })
+  return request.post({ url: '/oms/organization/delete', data: ids })
 }
 
 export const getDepartmentTableApi = (params: any) => {
-  return request.get({ url: '/department/table/list', params })
+  return request.get({ url: '/oms/organization/list', params })
 }

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

@@ -1,6 +1,7 @@
 export interface DepartmentItem {
   id: string
   departmentName: string
+  photo: string
   children?: DepartmentItem[]
 }
 

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

@@ -0,0 +1,26 @@
+import request from '@/config/axios'
+// import type { FileData } from './types'
+
+export const getTree = () => {
+  return request.get({ url: '/sys/dict/tree' })
+}
+
+export const getDictPage = (params: any) => {
+  return request.get({ url: '/sys/dict/list', params })
+}
+
+export const getDict = (params: any) => {
+  return request.get({ url: '/sys/dict/info/{id}', params })
+}
+
+export const delTableListApi = (data: any) => {
+  return request.post({ url: `/sys/dict/delete`, data })
+}
+
+export const saveTableApi = (data: any) => {
+  return request.post({ url: '/sys/dict/save', data })
+}
+
+export const updateTableApi = (data: any) => {
+  return request.put({ url: '/sys/dict/update', data })
+}

+ 0 - 26
src/api/manage/dict.ts

@@ -1,26 +0,0 @@
-import request from '@/config/axios'
-// import type { FileData } from './types'
-
-export const getTree = (data?: any) => {
-  return request.post({ url: '/api/sysDictionary/showTree', data })
-}
-
-export const getDictPage = (data: any) => {
-  return request.post({ url: '/api/sysDictionary/page', data })
-}
-
-export const getDict = (data: any) => {
-  return request.post({ url: '/api/sysDictionary/list', data })
-}
-
-export const delTableListApi = (id: string | number): Promise<IResponse> => {
-  return request.delete({ url: `/api/sysDictionary/${id}` })
-}
-
-export const saveTableApi = (data: any) => {
-  return request.post({ url: '/api/sysDictionary', data })
-}
-
-export const updateTableApi = (data: any) => {
-  return request.put({ url: '/api/sysDictionary', data })
-}

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

@@ -11,3 +11,7 @@ export const saveMenu = (data: any) => {
 export const deleteMenu = (data: any) => {
   return request.post({ url: '/sys/menu/delete', data })
 }
+
+export const updateMenu = (data: any) => {
+  return request.post({ url: '/sys/menu/update', data })
+}

+ 26 - 2
src/api/role/index.ts

@@ -1,5 +1,29 @@
 import request from '@/config/axios'
 
-export const getRoleListApi = () => {
-  return request.get({ url: '/role/table' })
+export const getRoleListApi = (params: any) => {
+  return request.get({ url: '/sys/role/list', params })
+}
+
+export const getRoleInfo = (params: any) => {
+  return request.get({ url: `/sys/role/info/${params.id}`, params })
+}
+
+export const getRoleMenu = (params: any) => {
+  return request.get({ url: `/sys/role/roleMenu/${params.id}`, params })
+}
+
+export const saveRole = (data: any) => {
+  return request.post({ url: `/sys/role/save`, data })
+}
+
+export const updateRole = (data: any) => {
+  return request.post({ url: `/sys/role/update`, data })
+}
+
+export const getRoleOptionsApi = () => {
+  return request.get({ url: 'sys/role/items' })
+}
+
+export const deleteApi = (data: any) => {
+  return request.post({ url: '/sys/role/delete', data })
 }

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

@@ -0,0 +1,21 @@
+import request from '@/config/axios'
+
+export const getListApi = (params: any) => {
+  return request.get({ url: '/sys/user/list', params })
+}
+
+export const getInfoApi = (params: any) => {
+  return request.get({ url: `/sys/user/info/${params.id}`, params })
+}
+
+export const saveApi = (data: any) => {
+  return request.post({ url: `/sys/user/save`, data })
+}
+
+export const updateApi = (data: any) => {
+  return request.post({ url: `/sys/user/update`, data })
+}
+
+export const deleteApi = (data: any) => {
+  return request.post({ url: `/sys/user/delete`, data })
+}

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

@@ -45,7 +45,8 @@ const icons = [epIcons, antIcons, tIcons]
 const iconName = ref(icons[0].prefix)
 
 const currentIconNameIndex = computed(() => {
-  return icons.findIndex((item) => item.prefix === iconName.value)
+  let index = icons.findIndex((item) => item.prefix === iconName.value)
+  return index >= 0 ? index : 0
 })
 
 const tabChange = () => {
@@ -77,8 +78,10 @@ async function init(icon?: string) {
   const iconInfo = icon.split(':')
   iconName.value = iconInfo[0]
   const wrapIndex = icons.findIndex((item) => item.prefix === iconInfo[0])
+  if (wrapIndex < 0) return
   // 查询当前icon的索引
   const index = icons[wrapIndex].icons.findIndex((item) => item === icon)
+  if (index < 0) return
   // 计算当前icon的页码
   await nextTick()
   currentPage.value = Math.ceil((index + 1) / pageSize.value)

+ 1 - 1
src/components/ImageViewer/index.ts

@@ -13,7 +13,7 @@ export function createImageViewer(options: ImageViewerProps) {
     infinite = true,
     hideOnClickModal = false,
     teleported = false,
-    zIndex = 2000,
+    zIndex = 5000,
     show = true
   } = options
 

+ 4 - 7
src/config/axios/config.ts

@@ -108,14 +108,11 @@ const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
         }
       })
     }
+  } else if (response.data.code === 400) {
+    ElMessage.error((response as any).data.message)
+    return response
   } else {
-    ElMessage.error((response as any).message)
-    clear()
-    tagsViewStore.delAllViews()
-    resetRouter() // 重置静态路由表
-    router.replace('/login')
-    isReLogin = false
-    location.reload()
+    ElMessage.error((response as any).data.message)
   }
 }
 

+ 12 - 12
src/config/axios/service.ts

@@ -31,7 +31,7 @@ axiosInstance.interceptors.request.use((res: InternalAxiosRequestConfig) => {
   return res
 })
 
-let isReLogin = false
+// let isReLogin = false
 axiosInstance.interceptors.response.use(
   (res: AxiosResponse) => {
     const url = res.config.url || ''
@@ -42,17 +42,17 @@ axiosInstance.interceptors.response.use(
   (error: AxiosError) => {
     console.log('err' + error) // for debug
     ElMessage.error(error.message)
-    if (!isReLogin) {
-      isReLogin = true
-      setTimeout(() => {
-        tagsViewStore.delAllViews()
-        resetRouter() // 重置静态路由表
-        router.replace('/login')
-        isReLogin = false
-        location.reload()
-        clear()
-      }, 1000)
-    }
+    // if (!isReLogin) {
+    //   isReLogin = true
+    //   setTimeout(() => {
+    //     tagsViewStore.delAllViews()
+    //     resetRouter() // 重置静态路由表
+    //     router.replace('/login')
+    //     isReLogin = false
+    //     location.reload()
+    //     clear()
+    //   }, 1000)
+    // }
     return Promise.reject(error)
   }
 )

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

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

+ 3 - 3
src/locales/en.ts

@@ -508,14 +508,14 @@ export default {
     remark: 'Remark',
     remarkMessage1: 'Back end control routing permission',
     remarkMessage2: 'Front end control routing permission',
-    // 部门列表
+    // 机构列表
     departmentList: 'Department list',
-    // 搜索部门
+    // 搜索机构
     searchDepartment: 'Search department',
     account: 'Account',
     email: 'Email',
     createTime: 'Create time',
-    // 所属部门
+    // 所属机构
     department: 'Department',
     departmentName: 'Department name',
     status: 'Status',

+ 10 - 10
src/locales/zh-CN.ts

@@ -159,7 +159,7 @@ export default {
     exampleEdit: '综合示例 - 编辑',
     exampleDetail: '综合示例 - 详情',
     errorPage: '错误页面',
-    authorization: '权限管理',
+    authorization: '系统管理',
     user: '用户管理',
     role: '角色管理',
     document: '文档',
@@ -167,7 +167,7 @@ export default {
     sticky: '黏性',
     treeTable: '树形表格',
     PicturePreview: '表格图片预览',
-    department: '部门管理',
+    department: '机构管理',
     menuManagement: '菜单管理',
     permission: '权限测试页',
     function: '功能',
@@ -500,22 +500,22 @@ export default {
     remark: '备注',
     remarkMessage1: '后端控制路由权限',
     remarkMessage2: '前端控制路由权限',
-    // 部门列表
-    departmentList: '部门列表',
-    searchDepartment: '搜索部门',
+    // 机构列表
+    departmentList: '机构列表',
+    searchDepartment: '搜索机构',
     account: '账号',
     email: '邮箱',
     createTime: '创建时间',
-    // 所属部门
-    department: '所属部门',
-    departmentName: '部门名称',
+    // 所属机构
+    department: '所属机构',
+    departmentName: '机构名称',
     status: '状态',
     // 启用
     enable: '启用',
     // 禁用
     disable: '禁用',
-    // 上级部门
-    superiorDepartment: '上级部门'
+    // 上级机构
+    superiorDepartment: '上级机构'
   },
   menu: {
     menuName: '菜单名称',

+ 26 - 2
src/router/index.ts

@@ -10,7 +10,7 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
   {
     path: '/',
     component: Layout,
-    redirect: '/dashboard/analysis',
+    redirect: '/dashboard/workplace',
     name: 'Root',
     meta: {
       hidden: true
@@ -59,7 +59,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
   {
     path: '/dashboard',
     component: Layout,
-    redirect: '/dashboard/analysis',
+    redirect: '/dashboard/workplace',
     name: 'Dashboard',
     meta: {
       title: t('router.dashboard'),
@@ -613,6 +613,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           title: t('router.department')
         }
       },
+      {
+        path: 'institution',
+        component: () => import('@/views/Authorization/Institution/Institution.vue'),
+        name: 'Institution',
+        meta: {
+          title: t('router.department')
+        }
+      },
       {
         path: 'user',
         component: () => import('@/views/Authorization/User/User.vue'),
@@ -621,6 +629,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           title: t('router.user')
         }
       },
+      {
+        path: 'dict',
+        component: () => import('@/views/Authorization/Dict/Dict.vue'),
+        name: 'Dict',
+        meta: {
+          title: '字典管理'
+        }
+      },
       {
         path: 'menu',
         component: () => import('@/views/Authorization/Menu/Menu.vue'),
@@ -637,6 +653,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           title: t('router.role')
         }
       },
+      {
+        path: 'config',
+        component: () => import('@/views/Authorization/Config/Config.vue'),
+        name: 'Config',
+        meta: {
+          title: '系统配置'
+        }
+      },
       {
         path: 'test',
         component: () => import('@/views/Authorization/Test/Test.vue'),

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

@@ -57,8 +57,8 @@ export const useAppStore = defineStore('app', {
       fixedHeader: true, // 固定toolheader
       footer: true, // 显示页脚
       greyMode: false, // 是否开始灰色模式,用于特殊悼念日
-      dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
-      serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
+      dynamicRouter: true, // 是否动态路由getStorage('dynamicRouter')
+      serverDynamicRouter: false, // 是否服务端渲染动态路由getStorage('serverDynamicRouter')
       fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
 
       layout: getStorage('layout') || 'classic', // layout布局

+ 2 - 0
src/utils/routerHelper.ts

@@ -65,6 +65,8 @@ export const generateRoutesByFrontEnd = (
 
     // 开发者可以根据实际情况进行扩展
     for (const item of keys) {
+      // console.log(item)
+      // console.log(route)
       // 通过路径去匹配
       if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
         data = Object.assign({}, route)

+ 160 - 0
src/views/Authorization/Config/Config.vue

@@ -0,0 +1,160 @@
+<script setup lang="tsx">
+import { reactive, ref, unref } from 'vue'
+import {
+  getListApi,
+  ailyunOss,
+  baiduOCR,
+  hongruanArc,
+  commParam,
+  ronglian,
+  qiniu,
+  changzhuo,
+  youmeng
+} from '@/api/config'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { ElButton, ElMessage } from 'element-plus'
+import { ContentWrap } from '@/components/ContentWrap'
+import Write from './components/Write.vue'
+import { Dialog } from '@/components/Dialog'
+
+const { t } = useI18n()
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getListApi()
+    return {
+      list: res.data || []
+    }
+  }
+})
+
+const { dataList, loading } = tableState
+const { getList } = tableMethods
+
+const tableColumns = reactive<TableColumn[]>([
+  {
+    field: 'remark',
+    label: '平台名称'
+  },
+  {
+    field: 'code',
+    label: 'CODE'
+  },
+  {
+    field: 'data',
+    label: '配置数据'
+  },
+  {
+    field: 'action',
+    label: t('userDemo.action'),
+    width: 140,
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <ElButton type="primary" onClick={() => action(row, 'edit')}>
+              {t('exampleDemo.edit')}
+            </ElButton>
+          </>
+        )
+      }
+    }
+  }
+])
+
+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) => {
+  dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
+  actionType.value = type
+  currentRow.value = row
+  dialogVisible.value = true
+}
+
+const resarr = [
+  {
+    code: 'ARC_FACE',
+    res: hongruanArc
+  },
+  {
+    code: 'BAIDU_OCR',
+    res: baiduOCR
+  },
+  {
+    code: 'CLOUD_ALI_YUN_CONFIG_KEY',
+    res: ailyunOss
+  },
+  {
+    code: 'CLOUD_QI_NIU_CONFIG_KEY',
+    res: qiniu
+  },
+  {
+    code: 'COMM_PARAM',
+    res: commParam
+  },
+  {
+    code: 'MOBILE_RONG_LIAN_CONFIG',
+    res: ronglian
+  },
+  {
+    code: 'SMS_CONFIG',
+    res: changzhuo
+  },
+  {
+    code: 'UM_CONFIG',
+    res: youmeng
+  }
+]
+
+const save = async () => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  if (formData) {
+    saveLoading.value = true
+    let option = resarr.find((e) => {
+      return e.code == formData.code
+    })
+    option?.res(formData).then((res) => {
+      if (res) {
+        ElMessage.success('修改成功')
+        dialogVisible.value = false
+        getList()
+      }
+    })
+    saveLoading.value = false
+  }
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <Table
+      :columns="tableColumns"
+      node-key="id"
+      :data="dataList"
+      :loading="loading"
+      @register="tableRegister"
+    />
+  </ContentWrap>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
+    <template #footer>
+      <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+    </template>
+  </Dialog>
+</template>

+ 95 - 0
src/views/Authorization/Config/components/Write.vue

@@ -0,0 +1,95 @@
+<script setup lang="tsx">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, reactive, watch, ref } from 'vue'
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<any>,
+    default: () => null
+  }
+})
+
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'remark',
+    label: '平台名称',
+    component: 'Input',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      readonly: true
+    }
+  },
+  {
+    field: 'code',
+    label: 'CODE',
+    component: 'Input',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      readonly: true
+    }
+  }
+])
+
+const rules = reactive({})
+
+const { formRegister, formMethods } = useForm()
+const { setValues, getFormData, getElFormExpose, addSchema } = formMethods
+
+const submit = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    console.log(formData)
+    return formData
+  }
+}
+
+const initValue = () => {
+  let data = JSON.parse(props.currentRow.data)
+  for (let key in data) {
+    addSchema({
+      field: 'data.' + key,
+      label: key,
+      component: 'Input',
+      colProps: {
+        span: 24
+      }
+    })
+  }
+  let form = {
+    ...props.currentRow,
+    data
+  }
+  setValues(form)
+}
+
+watch(
+  () => props.currentRow,
+  (currentRow) => {
+    if (!currentRow) {
+      return
+    }
+    initValue()
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 105 - 125
src/views/Authorization/Department/Department.vue

@@ -3,33 +3,33 @@ import { ContentWrap } from '@/components/ContentWrap'
 import { Search } from '@/components/Search'
 import { Dialog } from '@/components/Dialog'
 import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElTag } from 'element-plus'
+import { ElButton } from 'element-plus'
 import { Table } from '@/components/Table'
-import {
-  getDepartmentApi,
-  getDepartmentTableApi,
-  saveDepartmentApi,
-  deleteDepartmentApi
-} from '@/api/department'
+import { getDepartmentTableApi, saveDepartmentApi, deleteDepartmentApi } from '@/api/department'
 import type { DepartmentItem } from '@/api/department/types'
 import { useTable } from '@/hooks/web/useTable'
 import { ref, unref, reactive } from 'vue'
 import Write from './components/Write.vue'
 import Detail from './components/Detail.vue'
 import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { getQiniuToken } from '@/api/common'
+import SparkMd5 from 'spark-md5'
+import { useStorage } from '@/hooks/web/useStorage'
 
 const ids = ref<string[]>([])
-
+const { getStorage } = useStorage()
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
     const { currentPage, pageSize } = tableState
     const res = await getDepartmentTableApi({
-      pageIndex: unref(currentPage),
-      pageSize: unref(pageSize),
+      order: '',
+      orderField: '',
+      page: unref(currentPage),
+      limit: unref(pageSize),
       ...unref(searchParams)
     })
     return {
-      list: res.data.list,
+      list: res.data.data,
       total: res.data.total
     }
   },
@@ -48,117 +48,129 @@ const setSearchParams = (params: any) => {
 }
 
 const { t } = useI18n()
-
+const qiniuUrl = 'http://img.ailinzn.com'
+const qiniuUploadUrl = 'https://up-z1.qiniup.com'
+const currentRow = ref<any>()
+const dataObj = ref({
+  key: '',
+  token: ''
+})
 const crudSchemas = reactive<CrudSchema[]>([
   {
-    field: 'selection',
+    field: 'name',
+    label: t('userDemo.departmentName')
+  },
+  {
+    field: 'tel',
+    label: '电话',
     search: {
       hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      type: 'selection'
     }
   },
   {
-    field: 'index',
-    label: t('tableDemo.index'),
-    type: 'index',
+    field: 'code',
+    label: '编码',
     search: {
       hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
     }
   },
   {
-    field: 'id',
-    label: t('userDemo.departmentName'),
-    table: {
-      slots: {
-        default: (data: any) => {
-          return <>{data.row.departmentName}</>
-        }
-      }
+    field: 'description',
+    label: '描述',
+    search: {
+      hidden: true
     },
     form: {
-      component: 'TreeSelect',
+      component: 'Input',
       componentProps: {
-        nodeKey: 'id',
-        props: {
-          label: 'departmentName'
-        }
+        type: 'textarea',
+        rows: 5
       },
-      optionApi: async () => {
-        const res = await getDepartmentApi()
-        return res.data.list
-      }
-    },
-    detail: {
-      slots: {
-        default: (data: any) => {
-          return <>{data.departmentName}</>
-        }
+      colProps: {
+        span: 24
       }
     }
   },
   {
-    field: 'status',
-    label: t('userDemo.status'),
+    field: 'photo',
+    label: 'logo',
     search: {
       hidden: true
     },
-    table: {
-      slots: {
-        default: (data: any) => {
-          const status = data.row.status
-          return (
-            <>
-              <ElTag type={status === 0 ? 'danger' : 'success'}>
-                {status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
-              </ElTag>
-            </>
-          )
-        }
-      }
-    },
     form: {
-      component: 'Select',
+      colProps: {
+        span: 24
+      },
+      component: 'Upload',
       componentProps: {
-        options: [
-          {
-            value: 0,
-            label: t('userDemo.disable')
-          },
-          {
-            value: 1,
-            label: t('userDemo.enable')
-          }
-        ]
-      }
-    },
-    detail: {
-      slots: {
-        default: (data: any) => {
-          return (
-            <>
-              <ElTag type={data.status === 0 ? 'danger' : 'success'}>
-                {data.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
-              </ElTag>
-            </>
-          )
+        action: qiniuUploadUrl,
+        class: 'filePageUploader',
+        data: dataObj.value,
+        fileList: currentRow.value?.photo
+          ? [
+              {
+                url: currentRow.value?.photo
+              }
+            ]
+          : [],
+        headers: {
+          token: getStorage('token')
+        },
+        beforeUpload: (file) => {
+          return new Promise((resolve, reject) => {
+            getQiniuToken()
+              .then((response) => {
+                var photoExt = file.name.substr(file.name.lastIndexOf('.')).toLowerCase() // 获得文件后缀名
+                const fileReader = new FileReader()
+                const chunkSize = 1024 * 1024 * 2 // 每个分片的大小 2MB
+                const chunks = Math.ceil(file.size / chunkSize)
+                let currentChunk = 0
+                var spark = new SparkMd5.ArrayBuffer()
+                const loadFile = (e) => {
+                  spark.append(e.target.result)
+                  currentChunk++
+                  if (currentChunk < chunks) {
+                    // 未读取完继续读取
+                    loadNext()
+                  } else {
+                    // 文件读取完
+                    const md5 = spark.end() // 获取文件的md5值
+                    dataObj.value.token = response.data.data
+                    dataObj.value.key = md5 + photoExt
+                    resolve(true)
+                  }
+                }
+                const loadError = () => {
+                  // 读取文件失败
+                  reject('compute file md5 error')
+                }
+                const loadNext = () => {
+                  fileReader.onload = loadFile
+                  fileReader.onerror = loadError
+                  const start = currentChunk * chunkSize
+                  const end = start + chunkSize >= file.size ? file.size : start + chunkSize
+                  const data = file.slice(start, end)
+                  fileReader.readAsArrayBuffer(data)
+                }
+                loadNext()
+              })
+              .catch((err) => {
+                console.log(err)
+                reject(false)
+              })
+          })
+        },
+        onSuccess: (response) => {
+          const url = qiniuUrl + '/' + response.key
+          currentRow.value.photo = url
+        },
+        slots: {
+          default: () => <ElButton type="primary">上传文件</ElButton>
         }
       }
     }
   },
+
   {
     field: 'createTime',
     label: t('tableDemo.displayTime'),
@@ -166,35 +178,7 @@ const crudSchemas = reactive<CrudSchema[]>([
       hidden: true
     },
     form: {
-      component: 'DatePicker',
-      componentProps: {
-        type: 'datetime',
-        valueFormat: 'YYYY-MM-DD HH:mm:ss'
-      }
-    }
-  },
-  {
-    field: 'remark',
-    label: t('userDemo.remark'),
-    search: {
       hidden: true
-    },
-    form: {
-      component: 'Input',
-      componentProps: {
-        type: 'textarea',
-        rows: 5
-      },
-      colProps: {
-        span: 24
-      }
-    },
-    detail: {
-      slots: {
-        default: (data: any) => {
-          return <>{data.remark}</>
-        }
-      }
     }
   },
   {
@@ -218,9 +202,6 @@ const crudSchemas = reactive<CrudSchema[]>([
               <ElButton type="primary" onClick={() => action(data.row, 'edit')}>
                 {t('exampleDemo.edit')}
               </ElButton>
-              <ElButton type="success" onClick={() => action(data.row, 'detail')}>
-                {t('exampleDemo.detail')}
-              </ElButton>
               <ElButton type="danger" onClick={() => delData(data.row)}>
                 {t('exampleDemo.del')}
               </ElButton>
@@ -238,7 +219,6 @@ const { allSchemas } = useCrudSchemas(crudSchemas)
 const dialogVisible = ref(false)
 const dialogTitle = ref('')
 
-const currentRow = ref<DepartmentItem | null>(null)
 const actionType = ref('')
 
 const AddAction = () => {

+ 52 - 116
src/views/Authorization/Dict/Dict.vue

@@ -3,45 +3,30 @@ import { ContentWrap } from '@/components/ContentWrap'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table } from '@/components/Table'
 import { ref, unref, reactive } from 'vue'
-import { ElButton, ElButtonGroup, ElTooltip, ElTree } from 'element-plus'
-import { getDict, saveTableApi, delTableListApi, updateTableApi } from '@/api/manage/dict'
+import { ElButton } from 'element-plus'
+import { saveTableApi, delTableListApi, updateTableApi, getTree } from '@/api/dict'
 import type { DictData, DictRowData } from '@/api/manage/types'
 import { useTable } from '@/hooks/web/useTable'
-import { Search } from '@/components/Search'
+// import { Search } from '@/components/Search'
 import Write from './components/Write.vue'
 // import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
 import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { useIcon } from '@/hooks/web/useIcon'
-import { listToTree } from '@/utils/tree'
-
-const EditIcon = useIcon({ icon: 'ep:edit' })
-const DetailIcon = useIcon({ icon: 'ep:document' })
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
+// import { listToTree } from '@/utils/tree'
 
 const { t } = useI18n()
-const treeEl = ref<typeof ElTree>()
-const currentNodeKey = ref('')
-const dictList = ref<DictRowData[]>([])
+// const currentNodeKey = ref('')
+// const dictList = ref<DictRowData[]>([])
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
-    const res = await getDict({
-      pid: currentNodeKey.value,
-      ...unref(searchParams)
-    })
+    const res = await getTree()
     return {
-      list:
-        res.data.map((e: DictRowData) => {
-          return {
-            ...e,
-            hasChildren: true
-          }
-        }) || []
+      list: res.data
     }
   },
   fetchDelApi: async () => {
-    const res = await delTableListApi(unref(id))
+    const res = await delTableListApi([id.value])
     return !!res
   }
 })
@@ -51,78 +36,56 @@ const { getList, delList } = tableMethods
 const crudSchemas = reactive<CrudSchema[]>([
   {
     field: 'name',
-    label: '名称'
-  },
-  {
-    field: 'code',
-    label: '编码',
+    label: '字典标签',
     search: {
       hidden: true
     }
   },
   {
-    field: 'showText',
-    label: '显示文字',
+    field: 'pid',
+    label: '上级标签',
     search: {
       hidden: true
+    },
+    form: {
+      component: 'TreeSelect',
+      componentProps: {
+        props: {
+          label: 'name'
+        }
+      },
+      optionApi: async () => {
+        const res = await getTree()
+        return res.data
+      }
     }
   },
   {
-    field: 'value',
-    label: '值',
+    field: 'code',
+    label: '字典名称',
     search: {
       hidden: true
     }
   },
   {
-    field: 'isActive',
-    label: '是否启用',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
+    field: 'value',
+    label: '字典值',
     search: {
-      component: 'Select',
-      componentProps: {
-        options: [
-          {
-            label: '启用',
-            value: '1'
-          },
-          {
-            label: '禁用',
-            value: '0'
-          }
-        ]
-      }
-    },
-    form: {
-      component: 'Switch',
-      value: '1',
-      componentProps: {
-        activeText: '启用',
-        inactiveText: '禁用',
-        activeValue: '1',
-        inactiveValue: '0'
-      }
+      hidden: true
     }
   },
   {
-    field: 'description',
-    label: '备注',
+    field: 'orderNum',
+    label: '排序',
     form: {
       componentProps: {
-        type: 'textarea'
-      },
-      colProps: {
-        span: 24
+        type: 'InputNumber'
       }
     },
     search: {
       hidden: true
     }
   },
-
   {
     field: 'action',
     label: t('userDemo.action'),
@@ -143,22 +106,12 @@ const crudSchemas = reactive<CrudSchema[]>([
           const row = data.row as DictRowData
           return (
             <>
-              <ElButtonGroup>
-                <ElTooltip content="编辑">
-                  <ElButton text icon={EditIcon} onClick={() => action(row, 'edit')} />
-                </ElTooltip>
-                <ElTooltip content="管理子项">
-                  <ElButton text icon={DetailIcon} onClick={() => action(row, 'detail')} />
-                </ElTooltip>
-                <ElTooltip content="删除">
-                  <ElButton
-                    text
-                    icon={DeleteIcon}
-                    type="danger"
-                    onClick={() => delData(data.row)}
-                  />
-                </ElTooltip>
-              </ElButtonGroup>
+              <ElButton type="primary" onClick={() => action(row, 'edit')}>
+                {t('exampleDemo.edit')}
+              </ElButton>
+              <ElButton type="danger" onClick={() => delData(row)}>
+                {t('exampleDemo.del')}
+              </ElButton>
             </>
           )
         }
@@ -169,12 +122,12 @@ const crudSchemas = reactive<CrudSchema[]>([
 
 const { allSchemas } = useCrudSchemas(crudSchemas)
 
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  currentPage.value = 1
-  searchParams.value = params
-  getList()
-}
+// const searchParams = ref({})
+// const setSearchParams = (params: any) => {
+//   currentPage.value = 1
+//   searchParams.value = params
+//   getList()
+// }
 
 const dialogVisible = ref(false)
 const dialogTitle = ref('')
@@ -182,13 +135,9 @@ const dialogTitle = ref('')
 const currentRow = ref<DictData>()
 const actionType = ref('')
 
-getDict({ pid: '' }).then((res) => {
-  dictList.value = listToTree(res.data)
-})
-const currentChange = (data: DictRowData) => {
-  currentNodeKey.value = data.id
-  getList()
-}
+// getDict({ pid: '' }).then((res) => {
+//   dictList.value = listToTree(res.data)
+// })
 
 const AddAction = () => {
   dialogTitle.value = '新增'
@@ -218,13 +167,13 @@ const action = (row: DictData, type: string) => {
 const writeRef = ref<ComponentRef<typeof Write>>()
 // const tableRef = ref<ComponentRef<typeof Table>>()
 const saveLoading = ref(false)
-const save = async (action) => {
+const save = async () => {
   const write = unref(writeRef)
   const formData = await write?.submit()
   if (formData) {
     saveLoading.value = true
     try {
-      if (action == 'add') {
+      if (!formData.id) {
         const res = await saveTableApi(formData)
         if (res) {
           getList()
@@ -247,26 +196,12 @@ const save = async (action) => {
 
 <template>
   <div class="flex w-100% h-100%">
-    <ContentWrap class="flex-1">
-      <ElTree
-        ref="treeEl"
-        :data="dictList"
-        default-expand-all
-        node-key="id"
-        :current-node-key="currentNodeKey"
-        :highlight-current="true"
-        :props="{
-          label: 'name'
-        }"
-        @current-change="currentChange"
-      />
-    </ContentWrap>
     <ContentWrap class="flex-[3] ml-20px">
-      <Search
+      <!-- <Search
         :schema="allSchemas.searchSchema"
         @reset="setSearchParams"
         @search="setSearchParams"
-      />
+      /> -->
 
       <div class="mb-10px">
         <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
@@ -274,6 +209,7 @@ const save = async (action) => {
       <Table
         v-model:current-page="currentPage"
         v-model:page-size="pageSize"
+        row-key="id"
         :columns="allSchemas.tableColumns"
         :data="dataList"
         :loading="loading"

+ 0 - 278
src/views/Authorization/Dict/components/Detail.vue

@@ -1,278 +0,0 @@
-<script setup lang="tsx">
-import { ContentWrap } from '@/components/ContentWrap'
-import { useI18n } from '@/hooks/web/useI18n'
-import { Table } from '@/components/Table'
-import { ref, unref, PropType, reactive } from 'vue'
-import { ElButton, ElButtonGroup, ElTooltip } from 'element-plus'
-import { getDict, saveTableApi, delTableListApi, updateTableApi } from '@/api/manage/dict'
-import type { DictData, DictRowData } from '@/api/manage/types'
-import { useTable } from '@/hooks/web/useTable'
-import { Search } from '@/components/Search'
-import Write from './Write.vue'
-// import Detail from './components/Detail.vue'
-import { Dialog } from '@/components/Dialog'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
-import { useIcon } from '@/hooks/web/useIcon'
-const EditIcon = useIcon({ icon: 'ep:edit' })
-// const DetailIcon = useIcon({ icon: 'ep:document' })
-const DeleteIcon = useIcon({ icon: 'ep:delete' })
-
-const props = defineProps({
-  rowData: {
-    type: Object as PropType<DictData>
-  }
-})
-
-const { t } = useI18n()
-
-const { tableRegister, tableState, tableMethods } = useTable({
-  fetchDataApi: async () => {
-    const res = await getDict({
-      pid: props.rowData?.id,
-      ...unref(searchParams)
-    })
-    return {
-      list:
-        res.data.map((e: DictRowData) => {
-          return {
-            ...e,
-            hasChildren: true
-          }
-        }) || []
-    }
-  },
-  fetchDelApi: async () => {
-    const res = await delTableListApi(unref(id))
-    return !!res
-  }
-})
-const { loading, dataList, pageSize, currentPage } = tableState
-const { getList, delList } = tableMethods
-const crudSchemas = reactive<CrudSchema[]>([
-  {
-    field: 'name',
-    label: '名称'
-  },
-  {
-    field: 'code',
-    label: '编码',
-    search: {
-      hidden: true
-    }
-  },
-  {
-    field: 'showText',
-    label: '显示文字',
-    search: {
-      hidden: true
-    }
-  },
-  {
-    field: 'value',
-    label: '值',
-    search: {
-      hidden: true
-    }
-  },
-  {
-    field: 'isActive',
-    label: '是否启用',
-    minWidth: 100,
-    table: {
-      hidden: false
-    },
-    search: {
-      hidden: true
-    },
-    form: {
-      component: 'Switch',
-      value: '1',
-      componentProps: {
-        activeText: '启用',
-        inactiveText: '禁用',
-        activeValue: '1',
-        inactiveValue: '0'
-      }
-    }
-  },
-  {
-    field: 'description',
-    label: '备注',
-    form: {
-      componentProps: {
-        type: 'textarea'
-      },
-      colProps: {
-        span: 24
-      }
-    },
-    search: {
-      hidden: true
-    }
-  },
-
-  {
-    field: 'action',
-    label: t('userDemo.action'),
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    search: {
-      hidden: true
-    },
-    table: {
-      width: 120,
-      fixed: 'right',
-      slots: {
-        default: (data: any) => {
-          const row = data.row as DictRowData
-          return (
-            <>
-              <ElButtonGroup>
-                <ElTooltip content="编辑">
-                  <ElButton text icon={EditIcon} onClick={() => action(row, 'edit')} />
-                </ElTooltip>
-                <ElTooltip content="删除">
-                  <ElButton
-                    text
-                    icon={DeleteIcon}
-                    type="danger"
-                    onClick={() => delData(data.row)}
-                  />
-                </ElTooltip>
-              </ElButtonGroup>
-            </>
-          )
-        }
-      }
-    }
-  }
-])
-
-const { allSchemas } = useCrudSchemas(crudSchemas)
-const searchParams = ref({})
-const setSearchParams = (params: any) => {
-  currentPage.value = 1
-  searchParams.value = params
-  getList()
-}
-
-const dialogVisible = ref(false)
-const dialogTitle = ref('')
-
-const currentRow = ref<DictData>()
-const actionType = ref('')
-
-const AddAction = (key: string) => {
-  dialogTitle.value = '新增'
-  currentRow.value = undefined
-  if (key) {
-    currentRow.value = {
-      pid: key
-    }
-  }
-  dialogVisible.value = true
-  actionType.value = 'add'
-}
-const delLoading = ref(false)
-const id = ref<string>('')
-const delData = async (row: DictRowData) => {
-  id.value = row.id
-  delLoading.value = true
-
-  await delList(unref(id).length).finally(() => {
-    delLoading.value = false
-  })
-}
-const action = (row: DictData, type: string) => {
-  dialogTitle.value = type === 'edit' ? '编辑' : '子项列表'
-  actionType.value = type
-  currentRow.value = row
-  dialogVisible.value = true
-}
-const writeRef = ref<ComponentRef<typeof Write>>()
-const tableRef = ref<ComponentRef<typeof Table>>()
-const saveLoading = ref(false)
-const save = async () => {
-  const write = unref(writeRef)
-  const formData = await write?.submit()
-  if (formData) {
-    saveLoading.value = true
-    try {
-      if (actionType.value == 'add') {
-        const res = await saveTableApi(formData)
-        if (res) {
-          getList()
-        }
-      } else {
-        const res = await updateTableApi(formData)
-        if (res) {
-          getList()
-        }
-      }
-    } catch (error) {
-      console.log(error)
-    } finally {
-      saveLoading.value = false
-      dialogVisible.value = false
-    }
-  }
-}
-
-defineExpose({
-  getList
-})
-</script>
-
-<template>
-  <div>
-    <Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams" />
-
-    <div class="mb-10px">
-      <ElButton type="primary" @click="AddAction(props.rowData?.id || '')">{{
-        t('exampleDemo.add')
-      }}</ElButton>
-    </div>
-    <Table
-      ref="tableRef"
-      v-model:current-page="currentPage"
-      v-model:page-size="pageSize"
-      :columns="allSchemas.tableColumns"
-      :data="dataList"
-      :loading="loading"
-      row-key="id"
-      @register="tableRegister"
-    />
-
-    <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'"
-        :columns="props.allSchemas.tableColumns"
-        :current-row="currentRow"
-      /> -->
-
-      <template #footer>
-        <ElButton
-          v-if="actionType !== 'detail'"
-          type="primary"
-          :loading="saveLoading"
-          @click="save"
-        >
-          {{ t('exampleDemo.save') }}
-        </ElButton>
-
-        <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
-      </template>
-    </Dialog>
-  </div>
-</template>

+ 4 - 1
src/views/Authorization/Dict/components/Write.vue

@@ -19,7 +19,10 @@ const props = defineProps({
 })
 
 const rules = reactive({
-  code: [required()]
+  code: [required()],
+  name: [required()],
+  value: [required()],
+  pid: [required()]
 })
 
 const { formRegister, formMethods } = useForm()

+ 204 - 0
src/views/Authorization/Institution/Institution.vue

@@ -0,0 +1,204 @@
+<script setup lang="tsx">
+import { reactive, ref, unref } from 'vue'
+import {
+  getDepartmentTableApi,
+  saveDepartmentApi,
+  deleteDepartmentApi,
+  updateDepartmentApi
+} from '@/api/department'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { ElButton, ElMessage, ElMessageBox, ElDrawer } 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 { Dialog } from '@/components/Dialog'
+import moment from 'moment'
+import UserList from './components/User/User.vue'
+const { t } = useI18n()
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getDepartmentTableApi({
+      ...searchParams.value,
+      limit: 100,
+      page: 1,
+      order: '',
+      orderField: ''
+    })
+    return {
+      list: res.data.data || [],
+      total: res.data.total
+    }
+  }
+})
+
+const { dataList, loading, total } = tableState
+const { getList } = tableMethods
+
+const tableColumns = reactive<TableColumn[]>([
+  {
+    field: 'name',
+    label: t('userDemo.departmentName')
+  },
+  {
+    field: 'tel',
+    label: '电话'
+  },
+  {
+    field: 'code',
+    label: '编码'
+  },
+  {
+    field: 'description',
+    label: '描述'
+  },
+  {
+    field: 'createTime',
+    label: t('tableDemo.displayTime'),
+    formatter: (_: Recordable, __: TableColumn, cellValue: string | number) => {
+      return moment(cellValue).format('YYYY-MM-DD HH:mm')
+    }
+  },
+  {
+    field: 'action',
+    label: t('userDemo.action'),
+    width: 260,
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <ElButton type="primary" onClick={() => action(row, 'edit')}>
+              {t('exampleDemo.edit')}
+            </ElButton>
+            <ElButton type="success" onClick={() => handleUser(row)}>
+              用户管理
+            </ElButton>
+            <ElButton type="danger" onClick={() => handleDelete(row)}>
+              {t('exampleDemo.del')}
+            </ElButton>
+          </>
+        )
+      }
+    }
+  }
+])
+
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'name',
+    label: '机构名称',
+    component: 'Input'
+  }
+])
+
+const searchParams = ref({})
+const setSearchParams = (data: any) => {
+  searchParams.value = data
+  getList()
+}
+
+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) => {
+  dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
+  actionType.value = type
+  currentRow.value = row
+  dialogVisible.value = true
+}
+
+const AddAction = () => {
+  dialogTitle.value = t('exampleDemo.add')
+  currentRow.value = undefined
+  dialogVisible.value = true
+  actionType.value = ''
+}
+
+const save = async () => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  if (formData) {
+    saveLoading.value = true
+    if (formData.id) {
+      updateDepartmentApi(formData).then((res) => {
+        if (res) {
+          ElMessage.success('修改成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    } else {
+      saveDepartmentApi(formData).then((res) => {
+        if (res) {
+          ElMessage.success('保存成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    }
+    saveLoading.value = false
+  }
+}
+
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确认删除${row.name}?`, '提示').then(() => {
+    deleteDepartmentApi([row.id]).then((res) => {
+      if (res) {
+        getList()
+        ElMessage.success('删除成功')
+      }
+    })
+  })
+}
+const drawerVisible = ref<boolean>(false)
+const userRef = ref()
+const handleUser = (row: any) => {
+  currentRow.value = row
+  drawerVisible.value = true
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+    <div class="mb-10px">
+      <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
+    </div>
+    <Table
+      :columns="tableColumns"
+      default-expand-all
+      node-key="id"
+      :data="dataList"
+      :loading="loading"
+      :pagination="{
+        total
+      }"
+      @register="tableRegister"
+    />
+  </ContentWrap>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
+
+    <template #footer>
+      <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+    </template>
+  </Dialog>
+  <ElDrawer v-model="drawerVisible" title="用户管理" size="900px">
+    <UserList v-if="drawerVisible" :in-row="currentRow" ref="userRef" />
+  </ElDrawer>
+</template>

+ 206 - 0
src/views/Authorization/Institution/components/User/User.vue

@@ -0,0 +1,206 @@
+<script setup lang="tsx">
+import { reactive, ref, unref } from 'vue'
+import { getUserListByIdApi, saveUserApi, updateUserApi, deleteUserByIdApi } from '@/api/department'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { ElButton, ElMessage, ElMessageBox } from 'element-plus'
+import { Search } from '@/components/Search'
+import { FormSchema } from '@/components/Form'
+// import { ContentWrap } from '@/components/ContentWrap'
+import Write from './components/Write.vue'
+import { Dialog } from '@/components/Dialog'
+import moment from 'moment'
+import { PropType } from 'vue'
+import { onMounted } from 'vue'
+
+const props = defineProps({
+  inRow: {
+    type: Object as PropType<any>,
+    default: () => null
+  }
+})
+
+const { t } = useI18n()
+const orgId = ref('')
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getUserListByIdApi({
+      ...searchParams.value,
+      limit: 100,
+      page: 1,
+      order: '',
+      orgId: props.inRow.id
+    })
+    return {
+      list: res.data.data || [],
+      total: res.data.total
+    }
+  }
+})
+
+const { dataList, loading, total } = tableState
+const { getList } = tableMethods
+
+const tableColumns = reactive<TableColumn[]>([
+  {
+    field: 'name',
+    label: '用户名'
+  },
+  {
+    field: 'mobile',
+    label: '手机号'
+  },
+  {
+    field: 'gender',
+    label: '性别'
+  },
+  {
+    field: 'createAt',
+    label: '创建时间',
+    formatter: (_: Recordable, __: TableColumn, cellValue: string | number) => {
+      return moment(cellValue).format('YYYY-MM-DD HH:mm')
+    }
+  },
+  {
+    field: 'action',
+    label: t('userDemo.action'),
+    width: 240,
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <ElButton type="primary" onClick={() => action(row, 'edit')}>
+              {t('exampleDemo.edit')}
+            </ElButton>
+            <ElButton type="danger" onClick={() => handleDelete(row)}>
+              {t('exampleDemo.del')}
+            </ElButton>
+          </>
+        )
+      }
+    }
+  }
+])
+
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'name',
+    label: '用户名',
+    component: 'Input'
+  }
+])
+
+const searchParams = ref({})
+const setSearchParams = (data: any) => {
+  searchParams.value = data
+  getList()
+}
+
+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) => {
+  dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
+  actionType.value = type
+  currentRow.value = row
+  dialogVisible.value = true
+}
+
+const AddAction = () => {
+  dialogTitle.value = t('exampleDemo.add')
+  currentRow.value = undefined
+  dialogVisible.value = true
+  actionType.value = ''
+}
+
+const save = async () => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  if (formData) {
+    saveLoading.value = true
+    if (formData.id) {
+      updateUserApi(formData).then((res) => {
+        if (res) {
+          ElMessage.success('修改成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    } else {
+      saveUserApi({ ...formData, orgId: orgId.value }).then((res) => {
+        if (res) {
+          ElMessage.success('保存成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    }
+    saveLoading.value = false
+  }
+}
+
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确认删除${row.name}?`, '提示').then(() => {
+    deleteUserByIdApi({
+      empId: row.id,
+      orgId: orgId.value
+    }).then((res) => {
+      if (res) {
+        getList()
+        ElMessage.success('删除成功')
+      }
+    })
+  })
+}
+
+// const initValue = (row: any) => {
+//   orgId.value = row.id
+//   nextTick()
+//   getList()
+// }
+onMounted(() => {
+  getList()
+})
+
+// defineExpose({
+//   initValue
+// })
+</script>
+
+<template>
+  <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+  <div class="mb-10px">
+    <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
+  </div>
+  <Table
+    :columns="tableColumns"
+    default-expand-all
+    node-key="id"
+    :data="dataList"
+    :loading="loading"
+    :pagination="{
+      total
+    }"
+    @register="tableRegister"
+  />
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <Write v-if="actionType !== 'detail'" ref="writeRef" :orgId="orgId" :current-row="currentRow" />
+
+    <template #footer>
+      <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+    </template>
+  </Dialog>
+</template>

+ 129 - 0
src/views/Authorization/Institution/components/User/components/Write.vue

@@ -0,0 +1,129 @@
+<script setup lang="tsx">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, reactive, watch, ref } from 'vue'
+import { useValidator } from '@/hooks/web/useValidator'
+
+import { getUserByIdApi } from '@/api/department'
+
+// const { t } = useI18n()
+
+const { required, isMobilePhone } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<any>,
+    default: () => null
+  }
+})
+
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'name',
+    label: '用户名',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'gender',
+    label: '性别',
+    component: 'RadioGroup',
+    value: '1',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      options: [
+        {
+          label: '男',
+          value: 1
+        },
+        {
+          label: '女',
+          value: 0
+        }
+      ]
+    }
+  },
+
+  {
+    field: 'mobile',
+    label: '手机号',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'password',
+    label: '初始密码',
+    component: 'Input',
+    componentProps: {
+      type: 'password'
+    },
+    colProps: {
+      span: 24
+    }
+  }
+])
+
+const rules = reactive({
+  name: [required()],
+  password: [required()],
+  mobile: [required(), isMobilePhone()]
+})
+
+const { formRegister, formMethods } = useForm()
+const { setValues, getFormData, getElFormExpose } = formMethods
+
+const submit = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+
+    return formData
+  }
+}
+
+const initValue = (row) => {
+  if (row.id) {
+    setSchema([
+      {
+        field: 'password',
+        path: 'remove',
+        value: true
+      }
+    ])
+    getUserByIdApi(props.currentRow.id).then((res) => {
+      setValues(res.data)
+    })
+  }
+}
+
+watch(
+  () => props.currentRow,
+  (currentRow) => {
+    if (!currentRow) {
+      return
+    }
+    initValue()
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 300 - 0
src/views/Authorization/Institution/components/Write.vue

@@ -0,0 +1,300 @@
+<script setup lang="tsx">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, reactive, watch, ref } from 'vue'
+import { useValidator } from '@/hooks/web/useValidator'
+import { ElButton } from 'element-plus'
+import { getDepartmentItemApi } from '@/api/department'
+import { getQiniuToken } from '@/api/common'
+import SparkMd5 from 'spark-md5'
+import { useStorage } from '@/hooks/web/useStorage'
+import { createImageViewer } from '@/components/ImageViewer'
+
+const { getStorage } = useStorage()
+
+const { required } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<any>,
+    default: () => null
+  }
+})
+
+const dataObj = ref({
+  key: '',
+  token: ''
+})
+const qiniuUrl = 'http://img.ailinzn.com'
+const qiniuUploadUrl = 'https://up-z0.qiniup.com'
+const photo = ref<any[]>([])
+const { formRegister, formMethods } = useForm()
+const { setValues, getFormData, getElFormExpose, setProps, setSchema } = formMethods
+
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'name',
+    label: '机构名称',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'photo',
+    label: 'logo',
+    colProps: {
+      span: 24
+    },
+    component: 'Upload',
+    componentProps: {
+      action: qiniuUploadUrl,
+      class: 'filePageUploader',
+      data: () => dataObj.value,
+      fileList: photo.value,
+      listType: 'picture-card',
+      headers: {
+        token: getStorage('token')
+      },
+      onPreview: (files) => {
+        console.log(files)
+        createImageViewer({
+          urlList: [files.url]
+        })
+      },
+      beforeUpload: (file) => {
+        return new Promise((resolve, reject) => {
+          getQiniuToken()
+            .then((response) => {
+              var photoExt = file.name.substr(file.name.lastIndexOf('.')).toLowerCase() // 获得文件后缀名
+              const fileReader = new FileReader()
+              const chunkSize = 1024 * 1024 * 2 // 每个分片的大小 2MB
+              const chunks = Math.ceil(file.size / chunkSize)
+              let currentChunk = 0
+              var spark = new SparkMd5.ArrayBuffer()
+              const loadFile = (e) => {
+                spark.append(e.target.result)
+                currentChunk++
+                if (currentChunk < chunks) {
+                  // 未读取完继续读取
+                  loadNext()
+                } else {
+                  // 文件读取完
+                  const md5 = spark.end() // 获取文件的md5值
+                  dataObj.value.token = response.data
+                  dataObj.value.key = md5 + photoExt
+                  setProps({})
+                  resolve(true)
+                }
+              }
+              const loadError = () => {
+                // 读取文件失败
+                reject('compute file md5 error')
+              }
+              const loadNext = () => {
+                fileReader.onload = loadFile
+                fileReader.onerror = loadError
+                const start = currentChunk * chunkSize
+                const end = start + chunkSize >= file.size ? file.size : start + chunkSize
+                const data = file.slice(start, end)
+                fileReader.readAsArrayBuffer(data)
+              }
+              loadNext()
+            })
+            .catch((err) => {
+              console.log(err)
+              reject(false)
+            })
+        })
+      },
+      onSuccess: (response) => {
+        const url = qiniuUrl + '/' + response.key
+        // currentRow.value.photo = url
+        setValues({
+          photo: url
+        })
+        setSchema([
+          {
+            field: 'photo',
+            path: 'componentProps.fileList',
+            value: [
+              {
+                url: url,
+                name: url
+              }
+            ]
+          }
+        ])
+      },
+      slots: {
+        default: () => <Icon icon="ep:plus" />
+      }
+    }
+  },
+  {
+    field: 'tel',
+    label: '电话',
+    component: 'Input'
+  },
+  {
+    field: 'code',
+    label: '地址',
+    component: 'Input'
+  },
+  {
+    field: 'description',
+    label: '描述',
+    component: 'Input',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      type: 'textarea'
+    }
+  },
+  {
+    field: 'divider',
+    component: 'Divider',
+    label: '初始化管理员信息'
+  },
+  {
+    field: 'realName',
+    label: '用户名',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'gender',
+    label: '性别',
+    component: 'RadioGroup',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      options: [
+        {
+          label: '男',
+          value: 1
+        },
+        {
+          label: '女',
+          value: 0
+        }
+      ]
+    }
+  },
+  {
+    field: 'mobile',
+    label: '手机号',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'password',
+    label: '密码',
+    component: 'Input',
+    componentProps: {
+      type: 'password'
+    },
+    colProps: {
+      span: 24
+    }
+  }
+])
+
+const rules = reactive({
+  name: [required()],
+  photo: [required()],
+  tel: [required()],
+  realName: [required()],
+  gender: [required()],
+  mobile: [required()],
+  password: [required()]
+})
+
+const submit = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    console.log(formData)
+    return formData
+  }
+}
+
+const initValue = () => {
+  setSchema([
+    {
+      field: 'divider',
+      path: 'remove',
+      value: true
+    },
+    {
+      field: 'realName',
+      path: 'remove',
+      value: true
+    },
+    {
+      field: 'gender',
+      path: 'remove',
+      value: true
+    },
+    {
+      field: 'mobile',
+      path: 'remove',
+      value: true
+    },
+    {
+      field: 'password',
+      path: 'remove',
+      value: true
+    }
+  ])
+  getDepartmentItemApi(props.currentRow.id).then((res) => {
+    setValues({
+      ...res.data
+    })
+    setSchema([
+      {
+        field: 'photo',
+        path: 'componentProps.fileList',
+        value: [
+          {
+            url: res.data.photo,
+            name: res.data.photo
+          }
+        ]
+      }
+    ])
+    // photo.value = [res.data.photo]
+  })
+}
+
+watch(
+  () => props.currentRow,
+  (currentRow) => {
+    if (!currentRow) {
+      return
+    }
+    initValue()
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 37 - 8
src/views/Authorization/Menu/Menu.vue

@@ -1,10 +1,10 @@
 <script setup lang="tsx">
 import { reactive, ref, unref } from 'vue'
-import { getMenuListApi, saveMenu } from '@/api/menu'
+import { getMenuListApi, saveMenu, deleteMenu, updateMenu } from '@/api/menu'
 import { useTable } from '@/hooks/web/useTable'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table, TableColumn } from '@/components/Table'
-import { ElButton, ElTag } from 'element-plus'
+import { ElButton, ElMessageBox, ElTag, ElMessage } from 'element-plus'
 // import { Icon } from '@/components/Icon'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
@@ -152,6 +152,14 @@ const action = (row: any, type: string) => {
 
 const handleDelete = (row: any) => {
   console.log(row)
+  ElMessageBox.confirm(`确认删除${row.name}?`, '提示').then(() => {
+    deleteMenu([row.id]).then((res) => {
+      if (res) {
+        getList()
+        ElMessage.success('删除成功')
+      }
+    })
+  })
 }
 
 const AddAction = () => {
@@ -166,12 +174,33 @@ const save = async () => {
   const formData = await write?.submit()
   if (formData) {
     saveLoading.value = true
-    saveMenu(formData).then((res) => {
-      if (res) {
-        saveLoading.value = false
-        dialogVisible.value = false
-      }
-    })
+    if (formData.id) {
+      updateMenu(formData)
+        .then((res) => {
+          if (res) {
+            ElMessage.success('操作成功')
+            saveLoading.value = false
+            dialogVisible.value = false
+            getList()
+          }
+        })
+        .catch(() => {
+          saveLoading.value = false
+        })
+    } else {
+      saveMenu(formData)
+        .then((res) => {
+          if (res) {
+            ElMessage.success('操作成功')
+            saveLoading.value = false
+            dialogVisible.value = false
+            getList()
+          }
+        })
+        .catch(() => {
+          saveLoading.value = false
+        })
+    }
   }
 }
 </script>

+ 5 - 6
src/views/Authorization/Menu/components/Write.vue

@@ -57,7 +57,7 @@ const formSchema = reactive<FormSchema[]>([
     }
   },
   {
-    field: 'parentName',
+    field: 'pid',
     label: '上级菜单',
     component: 'TreeSelect',
     colProps: {
@@ -83,13 +83,11 @@ const formSchema = reactive<FormSchema[]>([
   {
     field: 'url',
     label: '路由',
-    hidden: true,
     colProps: {
       span: 24
     },
     component: 'Input'
   },
-
   {
     field: 'orderNum',
     label: '排序',
@@ -110,7 +108,7 @@ const formSchema = reactive<FormSchema[]>([
 
 const rules = reactive({
   name: [required()],
-  parentName: [required()]
+  url: [required()]
 })
 
 const { formRegister, formMethods } = useForm()
@@ -133,7 +131,7 @@ const changeType = (val: string | number) => {
       {
         field: 'url',
         path: 'hidden',
-        value: true
+        value: false
       },
       {
         field: 'icon',
@@ -151,7 +149,7 @@ const changeType = (val: string | number) => {
       {
         field: 'icon',
         path: 'hidden',
-        value: true
+        value: false
       }
     ])
   } else {
@@ -175,6 +173,7 @@ watch(
   (currentRow) => {
     if (!currentRow) return
     setValues(currentRow)
+    changeType(currentRow.type)
   },
   {
     deep: true,

+ 51 - 33
src/views/Authorization/Role/Role.vue

@@ -1,23 +1,30 @@
 <script setup lang="tsx">
 import { reactive, ref, unref } from 'vue'
-import { getRoleListApi } from '@/api/role'
+import { getRoleListApi, saveRole, updateRole, deleteApi } from '@/api/role'
 import { useTable } from '@/hooks/web/useTable'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Table, TableColumn } from '@/components/Table'
-import { ElButton, ElTag } from 'element-plus'
+import { ElButton, ElMessage, ElMessageBox } from 'element-plus'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
 import { ContentWrap } from '@/components/ContentWrap'
 import Write from './components/Write.vue'
 import { Dialog } from '@/components/Dialog'
+import moment from 'moment'
 
 const { t } = useI18n()
 
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
-    const res = await getRoleListApi()
+    const res = await getRoleListApi({
+      ...searchParams.value,
+      limit: 100,
+      page: 1,
+      order: '',
+      orderField: ''
+    })
     return {
-      list: res.data.list || [],
+      list: res.data.data || [],
       total: res.data.total
     }
   }
@@ -33,36 +40,20 @@ const tableColumns = reactive<TableColumn[]>([
     type: 'index'
   },
   {
-    field: 'roleName',
+    field: 'name',
     label: t('role.roleName')
   },
   {
-    field: 'role',
-    label: t('role.role')
+    field: 'description',
+    label: t('userDemo.remark')
   },
   {
-    field: 'status',
-    label: t('menu.status'),
-    slots: {
-      default: (data: any) => {
-        return (
-          <>
-            <ElTag type={data.row.status === 0 ? 'danger' : 'success'}>
-              {data.row.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
-            </ElTag>
-          </>
-        )
-      }
+    field: 'createAt',
+    label: t('tableDemo.displayTime'),
+    formatter: (_: Recordable, __: TableColumn, cellValue: string | number) => {
+      return moment(cellValue).format('YYYY-MM-DD HH:mm')
     }
   },
-  {
-    field: 'createTime',
-    label: t('tableDemo.displayTime')
-  },
-  {
-    field: 'remark',
-    label: t('userDemo.remark')
-  },
   {
     field: 'action',
     label: t('userDemo.action'),
@@ -75,7 +66,9 @@ const tableColumns = reactive<TableColumn[]>([
             <ElButton type="primary" onClick={() => action(row, 'edit')}>
               {t('exampleDemo.edit')}
             </ElButton>
-            <ElButton type="danger">{t('exampleDemo.del')}</ElButton>
+            <ElButton type="danger" onClick={() => handleDelete(row)}>
+              {t('exampleDemo.del')}
+            </ElButton>
           </>
         )
       }
@@ -85,7 +78,7 @@ const tableColumns = reactive<TableColumn[]>([
 
 const searchSchema = reactive<FormSchema[]>([
   {
-    field: 'roleName',
+    field: 'name',
     label: t('role.roleName'),
     component: 'Input'
   }
@@ -126,12 +119,37 @@ const save = async () => {
   const formData = await write?.submit()
   if (formData) {
     saveLoading.value = true
-    setTimeout(() => {
-      saveLoading.value = false
-      dialogVisible.value = false
-    }, 1000)
+    if (formData.id) {
+      updateRole(formData).then((res) => {
+        if (res) {
+          ElMessage.success('修改成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    } else {
+      saveRole(formData).then((res) => {
+        if (res) {
+          ElMessage.success('保存成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    }
+    saveLoading.value = false
   }
 }
+
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确认删除${row.name}?`, '提示').then(() => {
+    deleteApi([row.id]).then((res) => {
+      if (res) {
+        getList()
+        ElMessage.success('删除成功')
+      }
+    })
+  })
+}
 </script>
 
 <template>

+ 60 - 51
src/views/Authorization/Role/components/Write.vue

@@ -7,6 +7,7 @@ import { useI18n } from '@/hooks/web/useI18n'
 import { ElTree, ElCheckboxGroup, ElCheckbox } from 'element-plus'
 import { getMenuListApi } from '@/api/menu'
 import { filter, eachTree } from '@/utils/tree'
+import { getRoleInfo, getRoleMenu } from '@/api/role'
 import { findIndex } from '@/utils'
 
 const { t } = useI18n()
@@ -24,34 +25,26 @@ const treeRef = ref<typeof ElTree>()
 
 const formSchema = ref<FormSchema[]>([
   {
-    field: 'roleName',
+    field: 'name',
     label: t('role.roleName'),
-    component: 'Input'
-  },
-  {
-    field: 'role',
-    label: t('role.role'),
-    component: 'Input'
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
   },
   {
-    field: 'status',
-    label: t('menu.status'),
-    component: 'Select',
+    field: 'description',
+    label: '备注',
+    component: 'Input',
+    colProps: {
+      span: 24
+    },
     componentProps: {
-      options: [
-        {
-          label: t('userDemo.disable'),
-          value: 0
-        },
-        {
-          label: t('userDemo.enable'),
-          value: 1
-        }
-      ]
+      type: 'textarea'
     }
   },
   {
-    field: 'menu',
+    field: 'ids',
     label: t('role.menu'),
     colProps: {
       span: 24
@@ -59,6 +52,10 @@ const formSchema = ref<FormSchema[]>([
     formItemProps: {
       slots: {
         default: () => {
+          let defaultProps = {
+            label: 'name',
+            children: 'children'
+          }
           return (
             <>
               <div class="flex w-full">
@@ -67,17 +64,12 @@ const formSchema = ref<FormSchema[]>([
                     ref={treeRef}
                     show-checkbox
                     node-key="id"
+                    props={defaultProps}
                     highlight-current
                     expand-on-click-node={false}
                     data={treeData.value}
                     onNode-click={nodeClick}
-                  >
-                    {{
-                      default: (data) => {
-                        return <span>{data.data.meta.title}</span>
-                      }
-                    }}
-                  </ElTree>
+                  ></ElTree>
                 </div>
                 <div class="flex-1">
                   {unref(currentTreeData) && unref(currentTreeData)?.permission ? (
@@ -112,29 +104,27 @@ const { formRegister, formMethods } = useForm()
 const { setValues, getFormData, getElFormExpose } = formMethods
 
 const treeData = ref([])
-const getMenuList = async () => {
+const getMenuList = async (ids) => {
   const res = await getMenuListApi()
   if (res) {
-    treeData.value = res.data.list
-    if (!props.currentRow) return
+    treeData.value = res.data
     await nextTick()
     const checked: any[] = []
-    eachTree(props.currentRow.menu, (v) => {
+    eachTree(ids, (v) => {
       checked.push({
-        id: v.id,
-        permission: v.meta?.permission || []
-      })
-    })
-    eachTree(treeData.value, (v) => {
-      const index = findIndex(checked, (item) => {
-        return item.id === v.id
+        id: v
       })
-      if (index > -1) {
-        const meta = { ...(v.meta || {}) }
-        meta.permission = checked[index].permission
-        v.meta = meta
-      }
     })
+    // eachTree(treeData.value, (v) => {
+    //   const index = findIndex(checked, (item) => {
+    //     return item.id === v
+    //   })
+    //   if (index > -1) {
+    //     const meta = { ...(v.meta || {}) }
+    //     meta.permission = checked[index].permission
+    //     v.meta = meta
+    //   }
+    // })
     for (const item of checked) {
       unref(treeRef)?.setChecked(item.id, true, false)
     }
@@ -144,7 +134,6 @@ const getMenuList = async () => {
     // )
   }
 }
-getMenuList()
 
 const submit = async () => {
   const elForm = await getElFormExpose()
@@ -157,20 +146,40 @@ const submit = async () => {
       ...(unref(treeRef)?.getCheckedKeys() || []),
       ...(unref(treeRef)?.getHalfCheckedKeys() || [])
     ]
-    const data = filter(unref(treeData), (item: any) => {
-      return checkedKeys.includes(item.id)
-    })
-    formData.menu = data || []
+    // const data = filter(unref(treeData), (item: any) => {
+    //   return checkedKeys.includes(item.id)
+    // })
+    formData.ids = checkedKeys || []
     console.log(formData)
     return formData
   }
 }
 
+const initValue = () => {
+  getRoleInfo({
+    id: props.currentRow.id
+  }).then((res) => {
+    getRoleMenu({
+      id: props.currentRow.id
+    }).then((menu) => {
+      setValues({
+        ...res.data,
+        ids: menu.data
+      })
+
+      getMenuList(menu.data)
+    })
+  })
+}
+
 watch(
   () => props.currentRow,
   (currentRow) => {
-    if (!currentRow) return
-    setValues(currentRow)
+    if (!currentRow) {
+      getMenuList([])
+      return
+    }
+    initValue()
   },
   {
     deep: true,

+ 121 - 289
src/views/Authorization/User/User.vue

@@ -1,355 +1,187 @@
 <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 { ElButton, 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 { getListApi, saveApi, updateApi, deleteApi } from '@/api/user'
 import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { ElButton, ElMessage, ElMessageBox } from 'element-plus'
 import { Search } from '@/components/Search'
+import { FormSchema } from '@/components/Form'
+import { ContentWrap } from '@/components/ContentWrap'
 import Write from './components/Write.vue'
-import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
-import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import moment from 'moment'
 
 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 getListApi({
+      ...searchParams.value,
+      limit: 100,
+      page: 1,
+      order: '',
+      orderField: ''
     })
     return {
-      list: res.data.list || [],
-      total: res.data.total || 0
+      list: res.data.data || [],
+      total: res.data.total
     }
-  },
-  fetchDelApi: async () => {
-    const res = await deleteUserByIdApi(unref(ids))
-    return !!res
   }
 })
-const { total, loading, dataList, pageSize, currentPage } = tableState
-const { getList, getElTableExpose, delList } = tableMethods
 
-const crudSchemas = reactive<CrudSchema[]>([
-  {
-    field: 'selection',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      type: 'selection'
-    }
-  },
-  {
-    field: 'index',
-    label: t('userDemo.index'),
-    form: {
-      hidden: true
-    },
-    search: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      type: 'index'
-    }
-  },
-  {
-    field: 'username',
-    label: t('userDemo.username')
-  },
+const { dataList, loading, total } = tableState
+const { getList } = tableMethods
+
+const tableColumns = reactive<TableColumn[]>([
   {
-    field: 'account',
-    label: t('userDemo.account')
+    field: 'name',
+    label: '用户名'
   },
   {
-    field: 'department.id',
-    label: t('userDemo.department'),
-    detail: {
-      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: {
-      type: 'index'
-    }
+    field: 'gender',
+    label: '性别'
   },
   {
-    field: 'role',
-    label: t('userDemo.role'),
-    search: {
-      hidden: true
-    }
+    field: 'email',
+    label: '邮箱'
   },
   {
-    field: 'email',
-    label: t('userDemo.email'),
-    form: {
-      component: 'Input'
-    },
-    search: {
-      hidden: true
-    }
+    field: 'mobile',
+    label: '手机号'
   },
   {
-    field: 'createTime',
-    label: t('userDemo.createTime'),
-    form: {
-      component: 'Input'
-    },
-    search: {
-      hidden: true
+    field: 'createAt',
+    label: '创建时间',
+    formatter: (_: Recordable, __: TableColumn, cellValue: string | number) => {
+      return moment(cellValue).format('YYYY-MM-DD HH:mm')
     }
   },
   {
     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 (
-            <>
-              <ElButton type="primary" onClick={() => action(row, 'edit')}>
-                {t('exampleDemo.edit')}
-              </ElButton>
-              <ElButton type="success" onClick={() => action(row, 'detail')}>
-                {t('exampleDemo.detail')}
-              </ElButton>
-              <ElButton type="danger" onClick={() => delData(row)}>
-                {t('exampleDemo.del')}
-              </ElButton>
-            </>
-          )
-        }
+    width: 240,
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return (
+          <>
+            <ElButton type="primary" onClick={() => action(row, 'edit')}>
+              {t('exampleDemo.edit')}
+            </ElButton>
+            <ElButton type="danger" onClick={() => handleDelete(row)}>
+              {t('exampleDemo.del')}
+            </ElButton>
+          </>
+        )
       }
     }
   }
 ])
 
-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: 'name',
+    label: '用户名',
+    component: 'Input'
   }
-)
+])
 
-const currentChange = (data: DepartmentItem) => {
-  if (data.children) return
-  currentNodeKey.value = data.id
-  currentPage.value = 1
+const searchParams = ref({})
+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 AddAction = () => {
-  dialogTitle.value = t('exampleDemo.add')
-  currentRow.value = undefined
-  dialogVisible.value = true
-  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
+const writeRef = ref<ComponentRef<typeof Write>>()
 
-  await delList(unref(ids).length).finally(() => {
-    delLoading.value = false
-  })
-}
+const saveLoading = ref(false)
 
-const action = (row: DepartmentUserItem, type: string) => {
+const action = (row: any, type: string) => {
   dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
   actionType.value = type
-  currentRow.value = { ...row, department: unref(treeEl)?.getCurrentNode() || {} }
+  currentRow.value = row
   dialogVisible.value = true
 }
 
-const writeRef = ref<ComponentRef<typeof Write>>()
-
-const saveLoading = ref(false)
+const AddAction = () => {
+  dialogTitle.value = t('exampleDemo.add')
+  currentRow.value = undefined
+  dialogVisible.value = true
+  actionType.value = ''
+}
 
 const save = async () => {
   const write = unref(writeRef)
   const formData = await write?.submit()
   if (formData) {
     saveLoading.value = true
-    try {
-      const res = await saveUserApi(formData)
+    if (formData.id) {
+      updateApi(formData).then((res) => {
+        if (res) {
+          ElMessage.success('修改成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    } else {
+      saveApi(formData).then((res) => {
+        if (res) {
+          ElMessage.success('保存成功')
+          dialogVisible.value = false
+          getList()
+        }
+      })
+    }
+    saveLoading.value = false
+  }
+}
+
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确认删除${row.name}?`, '提示').then(() => {
+    deleteApi([row.id]).then((res) => {
       if (res) {
-        currentPage.value = 1
         getList()
+        ElMessage.success('删除成功')
       }
-    } catch (error) {
-      console.log(error)
-    } finally {
-      saveLoading.value = false
-      dialogVisible.value = false
-    }
-  }
+    })
+  })
 }
 </script>
 
 <template>
-  <div class="flex w-100% h-100%">
-    <ContentWrap class="flex-1">
-      <div class="flex justify-center items-center">
-        <div class="flex-1">{{ t('userDemo.departmentList') }}</div>
-        <ElInput
-          v-model="currentDepartment"
-          class="flex-[2]"
-          :placeholder="t('userDemo.searchDepartment')"
-          clearable
-        />
-      </div>
-      <ElDivider />
-      <ElTree
-        ref="treeEl"
-        :data="departmentList"
-        default-expand-all
-        node-key="id"
-        :current-node-key="currentNodeKey"
-        :props="{
-          label: 'departmentName'
-        }"
-        :filter-node-method="filterNode"
-        @current-change="currentChange"
-      />
-    </ContentWrap>
-    <ContentWrap class="flex-[3] ml-20px">
-      <Search
-        :schema="allSchemas.searchSchema"
-        @reset="setSearchParams"
-        @search="setSearchParams"
-      />
-
-      <div class="mb-10px">
-        <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
-        <ElButton :loading="delLoading" type="danger" @click="delData()">
-          {{ t('exampleDemo.del') }}
-        </ElButton>
-      </div>
-      <Table
-        v-model:current-page="currentPage"
-        v-model:page-size="pageSize"
-        :columns="allSchemas.tableColumns"
-        :data="dataList"
-        :loading="loading"
-        @register="tableRegister"
-        :pagination="{
-          total
-        }"
-      />
-    </ContentWrap>
-
-    <Dialog v-model="dialogVisible" :title="dialogTitle">
-      <Write
-        v-if="actionType !== 'detail'"
-        ref="writeRef"
-        :form-schema="allSchemas.formSchema"
-        :current-row="currentRow"
-      />
-
-      <Detail
-        v-if="actionType === 'detail'"
-        :detail-schema="allSchemas.detailSchema"
-        :current-row="currentRow"
-      />
-
-      <template #footer>
-        <ElButton
-          v-if="actionType !== 'detail'"
-          type="primary"
-          :loading="saveLoading"
-          @click="save"
-        >
-          {{ t('exampleDemo.save') }}
-        </ElButton>
-        <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
-      </template>
-    </Dialog>
-  </div>
+  <ContentWrap>
+    <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+    <div class="mb-10px">
+      <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
+    </div>
+    <Table
+      :columns="tableColumns"
+      default-expand-all
+      node-key="id"
+      :data="dataList"
+      :loading="loading"
+      :pagination="{
+        total
+      }"
+      @register="tableRegister"
+    />
+  </ContentWrap>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
+
+    <template #footer>
+      <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+    </template>
+  </Dialog>
 </template>

+ 121 - 18
src/views/Authorization/User/components/Write.vue

@@ -1,30 +1,122 @@
-<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, reactive, watch, ref } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
+// import { useI18n } from '@/hooks/web/useI18n'
+import { getInfoApi } from '@/api/user'
+import { getRoleOptionsApi } from '@/api/role'
 
-const { required } = useValidator()
+// const { t } = useI18n()
+
+const { required, isMobilePhone } = useValidator()
 
 const props = defineProps({
   currentRow: {
-    type: Object as PropType<DepartmentUserItem>,
-    default: () => undefined
-  },
-  formSchema: {
-    type: Array as PropType<FormSchema[]>,
-    default: () => []
+    type: Object as PropType<any>,
+    default: () => null
   }
 })
 
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'name',
+    label: '用户名',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'gender',
+    label: '性别',
+    component: 'RadioGroup',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      options: [
+        {
+          label: '男',
+          value: 1
+        },
+        {
+          label: '女',
+          value: 0
+        }
+      ]
+    }
+  },
+  {
+    field: 'email',
+    label: '邮箱',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'mobile',
+    label: '手机号',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'password',
+    label: '密码',
+    component: 'Input',
+    componentProps: {
+      type: 'password'
+    },
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'avatar',
+    label: '密码',
+    component: 'Input',
+    componentProps: {
+      type: 'password'
+    },
+    hidden: true,
+    value: '1',
+    colProps: {
+      span: 24
+    }
+  },
+
+  {
+    field: 'roles',
+    label: '角色配置',
+    component: 'Select',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      options: [],
+      multiple: true
+    },
+    // 远程加载option
+    optionApi: async () => {
+      const res = await getRoleOptionsApi()
+      return res.data.map((e: any) => {
+        return {
+          label: e.name,
+          value: e.id
+        }
+      })
+    }
+  }
+])
+
 const rules = reactive({
-  username: [required()],
-  account: [required()],
-  'department.id': [required()],
-  role: [required()],
-  email: [required()],
-  createTime: [required()]
+  name: [required()],
+  password: [required()],
+  mobile: [required(), isMobilePhone()],
+  roles: [required()]
 })
 
 const { formRegister, formMethods } = useForm()
@@ -37,15 +129,26 @@ const submit = async () => {
   })
   if (valid) {
     const formData = await getFormData()
+
     return formData
   }
 }
 
+const initValue = () => {
+  getInfoApi({
+    id: props.currentRow.id
+  }).then((res) => {
+    setValues(res.data)
+  })
+}
+
 watch(
   () => props.currentRow,
   (currentRow) => {
-    if (!currentRow) return
-    setValues(currentRow)
+    if (!currentRow) {
+      return
+    }
+    initValue()
   },
   {
     deep: true,

+ 42 - 28
src/views/Login/components/LoginForm.vue

@@ -4,7 +4,7 @@ import { Form, FormSchema } from '@/components/Form'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ElButton, ElCheckbox, ElLink } from 'element-plus'
 import { useForm } from '@/hooks/web/useForm'
-import { loginApi, getTestRoleApi, getAdminRoleApi, getUserInfo } from '@/api/login'
+import { loginApi, getUserInfo } from '@/api/login'
 import { useStorage } from '@/hooks/web/useStorage'
 import { useAppStore } from '@/store/modules/app'
 import { usePermissionStore } from '@/store/modules/permission'
@@ -13,6 +13,7 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
 import { UserLoginType } from '@/api/login/types'
 import { useValidator } from '@/hooks/web/useValidator'
 import { Icon } from '@/components/Icon'
+import { treeToList } from '@/utils/tree'
 
 const { required } = useValidator()
 
@@ -217,20 +218,21 @@ const signIn = async () => {
         if (res) {
           setStorage('token', res.data.token)
           setStorage('tokenHead', res.data.tokenHead)
-          setStorage(appStore.getUserInfo, res.data)
-          const user = await getUserInfo()
-          setStorage(appStore.getUserInfo, user.data)
+          // const user = await getUserInfo()
+
+          // setStorage(appStore.getUserInfo, user.data)
+          getRole()
           // 是否使用动态路由
-          if (appStore.getDynamicRouter) {
-            getRole()
-          } else {
-            await permissionStore.generateRoutes('static').catch(() => {})
-            permissionStore.getAddRouters.forEach((route) => {
-              addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
-            })
-            permissionStore.setIsAddRouters(true)
-            push({ path: redirect.value || permissionStore.addRouters[0].path })
-          }
+          // if (appStore.getDynamicRouter) {
+          //   getRole()
+          // } else {
+          // await permissionStore.generateRoutes('static').catch(() => {})
+          // permissionStore.getAddRouters.forEach((route) => {
+          //   addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
+          // })
+          // permissionStore.setIsAddRouters(true)
+          // push({ path: redirect.value || permissionStore.addRouters[0].path })
+          // }
         }
       } finally {
         loading.value = false
@@ -239,27 +241,39 @@ const signIn = async () => {
   })
 }
 
+const getRouterList = (routes: any[]) => {
+  let arr = treeToList(routes)
+  return arr.map((e) => {
+    return e.url
+  })
+}
+
 // 获取角色信息
 const getRole = async () => {
-  const formData = await getFormData<UserLoginType>()
-  const params = {
-    roleName: formData.username
-  }
-  const res =
-    appStore.getDynamicRouter && appStore.getServerDynamicRouter
-      ? await getAdminRoleApi(params)
-      : await getTestRoleApi(params)
-  if (res) {
-    const routers = res.data || []
-    setStorage('roleRouters', routers)
-    appStore.getDynamicRouter && appStore.getServerDynamicRouter
-      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
-      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
+  // const formData = await getFormData<UserLoginType>()
+  // const params = {
+  //   roleName: formData.username
+  // }
+  // const res =
+  //   appStore.getDynamicRouter && appStore.getServerDynamicRouter
+  //     ? await getAdminRoleApi(params)
+  //     : await getTestRoleApi(params)
+  const res = await getUserInfo()
 
+  setStorage(appStore.getUserInfo, res.data)
+  if (res.data.menuList) {
+    const routers = getRouterList(res.data.menuList) || []
+    setStorage('roleRouters', routers)
+    // appStore.getDynamicRouter && appStore.getServerDynamicRouter
+    //   ? await permissionStore.generateRoutes('server', routers).catch(() => {})
+    //   : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
+    await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
     permissionStore.getAddRouters.forEach((route) => {
+      // console.log(route)
       addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
     })
     permissionStore.setIsAddRouters(true)
+    console.log(redirect.value)
     push({ path: redirect.value || permissionStore.addRouters[0].path })
   }
 }

+ 3 - 3
src/views/hooks/useTagsView.vue

@@ -11,7 +11,7 @@ const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage,
 
 const closeAllTabs = () => {
   closeAll(() => {
-    push('/dashboard/analysis')
+    push('/dashboard/workplace')
   })
 }
 
@@ -33,7 +33,7 @@ const refresh = () => {
 
 const closeCurrentTab = () => {
   closeCurrent(undefined, () => {
-    push('/dashboard/analysis')
+    push('/dashboard/workplace')
   })
 }
 
@@ -42,7 +42,7 @@ const setTabTitle = () => {
 }
 
 const setAnalysisTitle = () => {
-  setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/analysis')
+  setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/workplace')
 }
 </script>
 

+ 10 - 0
types/router.d.ts

@@ -75,4 +75,14 @@ declare global {
     redirect: string
     children?: AppCustomRouteRecordRaw[]
   }
+
+  declare interface AppServeRouteRecordRaw
+    extends Omit<RouteRecordRaw, 'meta' | 'component' | 'children'> {
+    name: string
+    url: string
+    icon: string
+    children?: AppServeRouteRecordRaw[]
+  }
 }
+
+

+ 12 - 1
vite.config.ts

@@ -125,8 +125,19 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         '/sys': {
           target: 'http://192.168.0.131:9101',
           changeOrigin: true,
-          rewrite: path => path.replace(/^\/api/, '')
+          // rewrite: path => path.replace(/^\/api/, '')
+        },
+        '/oms': {
+          target: 'http://192.168.0.131:9101',
+          changeOrigin: true,
+          // rewrite: path => path.replace(/^\/api/, '')
+        },
+        '/common': {
+          target: 'http://192.168.0.131:9101',
+          changeOrigin: true,
+          // rewrite: path => path.replace(/^\/api/, '')
         }
+
       },
       hmr: {
         overlay: false