Browse Source

feat: 系统管理

commit c6577279b8b1c94c9737d32a9ca1ba3437c5979b
Author: K <1175047471@qq.com>
Date:   Fri Apr 12 09:40:22 2024 +0800

    chore: 取消 token 刷新

commit 349b8176f28e294fbde7dd8a4c50f064785fdf9d
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 21:20:39 2024 +0800

    chore: cleanup

commit 309887ade9b8a8bb4efd35f09983ddcd3afb76df
Author: lipenghui <mrkezhi@163.com>
Date:   Thu Apr 11 21:15:02 2024 +0800

    fix: 岗位查看

commit 0f4f83dd51c2ed454fc1ee3561f315bc7e35dc6a
Author: lipenghui <mrkezhi@163.com>
Date:   Thu Apr 11 21:13:47 2024 +0800

    feat: 租户管理

commit 04e5d8d685616a601fd1d3b82ab18c4442536de8
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 21:09:50 2024 +0800

    chore: fix useinfo 未更新

commit 75ed18056c878c445ede439f27a1df2af41498dd
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 20:58:47 2024 +0800

    chore: 修改首页地址

commit ef93ffc03d6f06f21b996f873b2b36e625c58a89
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 20:52:27 2024 +0800

    feat: 个人中心

commit 476a436f8654b8cd72fefd2105df2ecca7d517f7
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 19:53:41 2024 +0800

    wip: 个人中心

commit e5704dc3ad9dea20af45457dedc72ba65eb9d732
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 19:36:55 2024 +0800

    feat: 用户管理

commit e22200135c29ddbb17cbaae2d857c46478a9d9f7
Author: lipenghui <mrkezhi@163.com>
Date:   Thu Apr 11 17:42:25 2024 +0800

    fix: 部门删除接口将ids改为id

commit bbf42e206fad8a91a23edbbd68a3e2ce2d5ed59f
Author: lipenghui <mrkezhi@163.com>
Date:   Thu Apr 11 17:40:20 2024 +0800

    feat: 角色管理;

commit c7e7e89bfe9100641811823846d1a0f48cd5f584
Author: lipenghui <mrkezhi@163.com>
Date:   Thu Apr 11 14:11:35 2024 +0800

    feat: 岗位管理

commit 355d77d3cfc286664bd78bffbaca2766597e11c1
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 11:12:33 2024 +0800

    chore: 调整行政区划操作按钮位置

commit 9f7927b94625f7b1ffe74a3b396849631cea5bea
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 10:45:24 2024 +0800

    wip: 用户管理

commit 87b423b14f5bf795b4f99d120feac6a06f3ac332
Author: K <1175047471@qq.com>
Date:   Thu Apr 11 09:16:25 2024 +0800

    feat: 菜单管理 - 新增子项

commit f2fa565d939802896539e1e07cdf1bd3bfa1bb0e
Author: lipenghui <mrkezhi@163.com>
Date:   Wed Apr 10 21:12:04 2024 +0800

    chore: 菜单路由处理动态参数

commit 3a2c15b69ecd8243f6ca9237b1ff19dbb6d374fe
Author: lipenghui <mrkezhi@163.com>
Date:   Wed Apr 10 21:08:23 2024 +0800

    Revert "chore: 菜单路由处理动态参数"

    This reverts commit 3018cb399cd58e8e80285b83fc53ff9bdbd10e14.

commit 9724ddc926c5efedc8f72ced29d31d61995b12a5
Author: lipenghui <mrkezhi@163.com>
Date:   Wed Apr 10 21:05:52 2024 +0800

    chore: 菜单路由处理动态参数

commit 5bca711522df26d884669edd50eb53ce35c7ebd5
Author: lipenghui <mrkezhi@163.com>
Date:   Wed Apr 10 20:40:12 2024 +0800

    fix: 删掉测试路由

commit 753306a1e4b7fdf17ac6337ca029a6f411baec1c
Author: lipenghui <mrkezhi@163.com>
Date:   Wed Apr 10 20:11:03 2024 +0800

    feat: 业务字典

commit 0f551cf23dc20f2750b823ff49864cfa1c55910e
Author: lipenghui <mrkezhi@163.com>
Date:   Wed Apr 10 18:29:17 2024 +0800

    fix: 系统字典-字典配置修改

commit 040923dd0eb3eb3846ddcf17efee9aa9fb2ca2da
Author: lipenghui <mrkezhi@163.com>
Date:   Wed Apr 10 18:23:51 2024 +0800

    feat: 系统字典

commit 1dfe6a3b221863fa792b8fc7432707c5fe009da2
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 21:06:30 2024 +0800

    fix: 菜单类型显示错误

commit c16cbdf807d7b8d44e2b0ff1b1310109c24c2d5d
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 20:57:36 2024 +0800

    fix: 菜单无法跳转

commit f95c62d7faac99aa0700f14d46291fa34eafa0e2
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 20:26:34 2024 +0800

    feat: ImportModal 增加提示

commit 1e0af86b01d54e5ee8b89a5b48c8f2fb5276386f
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 20:21:37 2024 +0800

    fix: 批量删除错误

commit 47a807a76e6cb006fb12ef8bf8e6a065f9e293b8
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 20:19:00 2024 +0800

    fix: 批量删除错误

commit 82248511dcb058a6885e64472a47df10bd43218d
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 20:07:28 2024 +0800

    chore: cleanup

commit fae351c1d131151738e42f82fbd0e98dd21f79bd
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 20:06:58 2024 +0800

    feat: 行政区划

commit 9d370465faff6adac276f96e099a42ea015c4e4a
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 11:18:16 2024 +0800

    fix: 菜单 icon 不应该是必选项

commit 68734b32e416ff6db4fe0e2748148e6867cd236a
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 10:59:32 2024 +0800

    feat: 参数管理 - 批量删除

commit ff3a985a70f97db5ff169b64b2bfe997ce60360d
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 10:51:09 2024 +0800

    feat: 参数管理

commit 6b1ec43e7a598a0140452eaae47223302cb6b345
Author: K <1175047471@qq.com>
Date:   Wed Apr 10 10:24:59 2024 +0800

    wip: 菜单管理

commit 19ad299806d14f780ab52fc6d890224b63796750
Author: K <1175047471@qq.com>
Date:   Tue Apr 9 18:31:16 2024 +0800

    wip: 系统管理

commit 8a02a8a64b90206a109d14ebdfc082b0ba1fcbdc
Author: K <1175047471@qq.com>
Date:   Tue Apr 9 18:03:13 2024 +0800

    wip: 系统管理
main
刘凯 1 year ago
parent
commit
d8e7544d3f
  1. 2
      .env.development
  2. 2
      .env.production
  3. 22
      src/api/base/user/index.ts
  4. 1
      src/api/base/user/types.ts
  5. 10
      src/api/system/dept/index.ts
  6. 48
      src/api/system/dict/index.ts
  7. 36
      src/api/system/dict/types.ts
  8. 48
      src/api/system/dictbiz/index.ts
  9. 37
      src/api/system/dictbiz/types.ts
  10. 14
      src/api/system/menu/index.ts
  11. 10
      src/api/system/param/index.ts
  12. 42
      src/api/system/post/index.ts
  13. 21
      src/api/system/post/types.ts
  14. 37
      src/api/system/region/index.ts
  15. 15
      src/api/system/region/types.ts
  16. 29
      src/api/system/role/index.ts
  17. 9
      src/api/system/role/types.ts
  18. 23
      src/api/system/tenant/index.ts
  19. 13
      src/api/system/tenant/types.ts
  20. 29
      src/api/system/user/index.ts
  21. 1
      src/api/system/user/types.ts
  22. 22
      src/components/Form/src/components/FileUpload.vue
  23. 1
      src/components/ImportModal/index.ts
  24. 58
      src/components/ImportModal/src/ImportModal.vue
  25. 10
      src/components/Tree/src/BasicTree.vue
  26. 1
      src/components/Tree/src/components/TreeHeader.vue
  27. 2
      src/components/Tree/style/index.less
  28. 2
      src/enums/pageEnum.ts
  29. 13
      src/layouts/default/header/components/user-dropdown/index.vue
  30. 34
      src/layouts/default/header/index.vue
  31. 2
      src/router/helper/routeHelper.ts
  32. 13
      src/router/routes/index.ts
  33. 40
      src/router/routes/modules/dashboard.ts
  34. 28
      src/router/routes/modules/profile.ts
  35. 6
      src/store/modules/permission.ts
  36. 10
      src/store/modules/user.ts
  37. 1
      src/types/axios.d.ts
  38. 96
      src/utils/http/axios/Axios.ts
  39. 9
      src/utils/http/axios/index.ts
  40. 6
      src/views/base/login/Login.vue
  41. 4
      src/views/base/login/LoginForm.vue
  42. 75
      src/views/base/profile/components/UpdatePassword.vue
  43. 94
      src/views/base/profile/components/UserInfo.vue
  44. 2
      src/views/base/profile/components/index.ts
  45. 19
      src/views/base/profile/index.vue
  46. 43
      src/views/system/dept/data.ts
  47. 62
      src/views/system/dept/index.vue
  48. 57
      src/views/system/dict/DictFormModal.vue
  49. 126
      src/views/system/dict/data.ts
  50. 130
      src/views/system/dict/index.vue
  51. 61
      src/views/system/dict/setting/SettingFormModal.vue
  52. 132
      src/views/system/dict/setting/data.ts
  53. 120
      src/views/system/dict/setting/index.vue
  54. 57
      src/views/system/dictbiz/DictbizFormModal.vue
  55. 133
      src/views/system/dictbiz/data.ts
  56. 130
      src/views/system/dictbiz/index.vue
  57. 61
      src/views/system/dictbiz/setting/SettingFormModal.vue
  58. 139
      src/views/system/dictbiz/setting/data.ts
  59. 120
      src/views/system/dictbiz/setting/index.vue
  60. 5
      src/views/system/menu/MenuFormModal.vue
  61. 124
      src/views/system/menu/data.ts
  62. 74
      src/views/system/menu/index.vue
  63. 12
      src/views/system/param/data.ts
  64. 64
      src/views/system/param/index.vue
  65. 54
      src/views/system/post/DedailModal.vue
  66. 57
      src/views/system/post/PostFormModal.vue
  67. 121
      src/views/system/post/data.ts
  68. 142
      src/views/system/post/index.vue
  69. 38
      src/views/system/region/composables/useRegionActions.ts
  70. 126
      src/views/system/region/composables/useRegionForm.ts
  71. 39
      src/views/system/region/composables/useRegionList.ts
  72. 165
      src/views/system/region/index.vue
  73. 37
      src/views/system/role/RoleMenuModal.vue
  74. 12
      src/views/system/role/data.ts
  75. 41
      src/views/system/role/index.vue
  76. 50
      src/views/system/tenant/AuthSettingModal.vue
  77. 77
      src/views/system/tenant/DedailModal.vue
  78. 84
      src/views/system/tenant/data.ts
  79. 44
      src/views/system/tenant/index.vue
  80. 21
      src/views/system/user/UserFormModal.vue
  81. 19
      src/views/system/user/components/DeptTree.vue
  82. 1
      src/views/system/user/components/index.ts
  83. 106
      src/views/system/user/composables/useUserActions.ts
  84. 259
      src/views/system/user/data.ts
  85. 160
      src/views/system/user/index.vue

2
.env.development

@ -17,7 +17,7 @@ VITE_DROP_CONSOLE = false
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
# 文件上传接口 可选 # 文件上传接口 可选
VITE_GLOB_UPLOAD_URL = /upload VITE_GLOB_UPLOAD_URL = /api/baymax-resource/oss/endpoint/put-file
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换 # 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
VITE_GLOB_API_URL_PREFIX = /api VITE_GLOB_API_URL_PREFIX = /api

2
.env.production

@ -16,7 +16,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
VITE_GLOB_API_URL = VITE_GLOB_API_URL =
# 文件上传地址 可以由nginx做转发或者直接写实际地址 # 文件上传地址 可以由nginx做转发或者直接写实际地址
VITE_GLOB_UPLOAD_URL = /upload VITE_GLOB_UPLOAD_URL = /baymax-resource/oss/endpoint/put-file
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换 # 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
VITE_GLOB_API_URL_PREFIX = VITE_GLOB_API_URL_PREFIX =

22
src/api/base/user/index.ts

@ -26,9 +26,7 @@ export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal')
export function getUserInfo() { export function getUserInfo() {
return defHttp.get<UserInfo>({ return defHttp.get<UserInfo>({
url: '/system/permission/get-permission-info', url: '/baymax-system/user/info',
}, {
errorMessageMode: 'none',
}) })
} }
@ -43,7 +41,9 @@ export interface __MenuItem {
sort: number sort: number
category: number category: number
action: number action: number
isOpen: 1 | 2
children?: __MenuItem[] children?: __MenuItem[]
visible: BooleanFlag
} }
export function getUserRouters() { export function getUserRouters() {
@ -60,7 +60,7 @@ export function getUserButtons() {
export function doLogout() { export function doLogout() {
return defHttp.post({ return defHttp.post({
url: '/auth/logout', url: '/baymax-auth/oauth/logout',
}) })
} }
@ -72,3 +72,17 @@ export function getLoginCaptcha() {
withoutAuth: true, withoutAuth: true,
}) })
} }
export function updateUserInfo(data: Partial<UserInfo>) {
return defHttp.post({
url: '/baymax-system/user/update-info',
data,
})
}
export function updatePassword(params: { oldPassword: string, newPassword: string, newPassword1: string }) {
return defHttp.post({
url: '/baymax-system/user/update-password',
params,
}, { joinParamsToUrl: true })
}

1
src/api/base/user/types.ts

@ -29,4 +29,5 @@ export interface User {
avatar?: string avatar?: string
role_id: string role_id: string
role_name: string role_name: string
phone: string
} }

10
src/api/system/dept/index.ts

@ -3,34 +3,34 @@ import { defHttp } from '@/utils/http/axios'
export function lazyGetDeptList(params?: LazyGetDeptListParams) { export function lazyGetDeptList(params?: LazyGetDeptListParams) {
return defHttp.get<Department[]>({ return defHttp.get<Department[]>({
url: '/system/dept/lazy-list', url: '/baymax-system/dept/lazy-list',
params, params,
}) })
} }
export function createDept(data: Partial<Department>) { export function createDept(data: Partial<Department>) {
return defHttp.post({ return defHttp.post({
url: '/system/dept/save', url: '/baymax-system/dept/submit',
data, data,
}) })
} }
export function updateDept(data: Partial<Department>) { export function updateDept(data: Partial<Department>) {
return defHttp.post({ return defHttp.post({
url: '/system/dept/update', url: '/baymax-system/dept/submit',
data, data,
}) })
} }
export function deleteDept(id: string) { export function deleteDept(id: string) {
return defHttp.post({ return defHttp.post({
url: `/system/dept/delete?id=${id}`, url: `/baymax-system/dept/remove?id=${id}`,
}) })
} }
export function getDeptTree(params?: { tenantId: string }) { export function getDeptTree(params?: { tenantId: string }) {
return defHttp.get<{ id: string, title: string }[]>({ return defHttp.get<{ id: string, title: string }[]>({
url: '/system/dept/tree', url: '/baymax-system/dept/tree',
params, params,
}) })
} }

48
src/api/system/dict/index.ts

@ -0,0 +1,48 @@
import type { GetDictChildListParams, GetDictListParams, SystemDict, SystemDictChild, SystemDictTree } from './types'
import { defHttp } from '@/utils/http/axios'
export function getDictList(params: GetDictListParams) {
return defHttp.get<PageResult<SystemDict>>({
url: '/baymax-system/dict/parent-list',
params,
})
}
export function detailDict(id: string) {
return defHttp.get<SystemDict>({
url: `/baymax-system/dict/detail?id=${id}`,
})
}
export function createDict(data: Partial<SystemDict>) {
return defHttp.post({
url: '/baymax-system/dict/submit',
data,
})
}
export function updateDict(data: Partial<SystemDict>) {
return defHttp.post({
url: '/baymax-system/dict/submit',
data,
})
}
export function deleteDict(ids: string[]) {
return defHttp.post({
url: `/baymax-system/dict/remove?ids=${ids}`,
})
}
export function dictTree(code: string) {
return defHttp.get<SystemDictTree>({
url: `/baymax-system/dict/tree?code=${code}`,
})
}
export function getDictChildList(params: GetDictChildListParams) {
return defHttp.get<PageResult<SystemDictChild>>({
url: '/baymax-system/dict/child-list',
params,
})
}

36
src/api/system/dict/types.ts

@ -0,0 +1,36 @@
export interface GetDictListParams extends PageParam {
}
export interface GetDictChildListParams extends PageParam {
parentId: string
}
export interface SystemDict {
id: string
code: string
dictKey: string
dictValue: string
hasChildren: boolean
isDeleted: number
isSealed: number
parentId: string
parentName: string
remark: string
sort: number
}
export interface SystemDictChild extends SystemDict {
}
export interface SystemDictTree extends SystemDictTreeItem {
children: SystemDictTree[]
}
export interface SystemDictTreeItem {
id: string
hasChildren: boolean
key: string
parentId: string
title: string
value: string
}

48
src/api/system/dictbiz/index.ts

@ -0,0 +1,48 @@
import type { GetDictbizChildListParams, GetDictbizListParams, SystemDictbiz, SystemDictbizChild, SystemDictbizTree } from './types'
import { defHttp } from '@/utils/http/axios'
export function getDictbizList(params: GetDictbizListParams) {
return defHttp.get<PageResult<SystemDictbiz>>({
url: '/baymax-system/dict-biz/parent-list',
params,
})
}
export function detailDictbiz(id: string) {
return defHttp.get<SystemDictbiz>({
url: `/baymax-system/dict-biz/detail?id=${id}`,
})
}
export function createDictbiz(data: Partial<SystemDictbiz>) {
return defHttp.post({
url: '/baymax-system/dict-biz/submit',
data,
})
}
export function updateDictbiz(data: Partial<SystemDictbiz>) {
return defHttp.post({
url: '/baymax-system/dict-biz/submit',
data,
})
}
export function deleteDictbiz(ids: string[]) {
return defHttp.post({
url: `/baymax-system/dict-biz/remove?ids=${ids}`,
})
}
export function dictbizTree(code: string) {
return defHttp.get<SystemDictbizTree>({
url: `/baymax-system/dict-biz/tree?code=${code}`,
})
}
export function getDictbizChildList(params: GetDictbizChildListParams) {
return defHttp.get<PageResult<SystemDictbizChild>>({
url: '/baymax-system/dict-biz/child-list',
params,
})
}

37
src/api/system/dictbiz/types.ts

@ -0,0 +1,37 @@
export interface GetDictbizListParams extends PageParam {
}
export interface GetDictbizChildListParams extends PageParam {
parentId: string
}
export interface SystemDictbiz {
id: string
code: string
dictKey: string
dictValue: string
hasChildren: boolean
isDeleted: number
isSealed: number
parentId: string
parentName: string
remark: string
sort: number
tenantId: string
}
export interface SystemDictbizChild extends SystemDictbiz {
}
export interface SystemDictbizTree extends SystemDictbizTreeItem {
children: SystemDictbizTree[]
}
export interface SystemDictbizTreeItem {
id: string
hasChildren: boolean
key: string
parentId: string
title: string
value: string
}

14
src/api/system/menu/index.ts

@ -3,39 +3,39 @@ import { defHttp } from '@/utils/http/axios'
export function getMenuListWithoutButtons() { export function getMenuListWithoutButtons() {
return defHttp.get<MenuItem[]>({ return defHttp.get<MenuItem[]>({
url: '/system/menu/tree', url: '/baymax-system/menu/tree',
}) })
} }
export function getMenuList(params: GetMenuListParams) { export function getMenuList(params: GetMenuListParams) {
return defHttp.get<PageResult<MenuItem>>({ return defHttp.get<PageResult<MenuItem>>({
url: '/system/menu/list', url: '/baymax-system/menu/list',
params, params,
}) })
} }
export function getMenu(id: string) { export function getMenu(id: string) {
return defHttp.get({ return defHttp.get({
url: `/system/menu/get?id=${id}`, url: `/baymax-system/menu/get?id=${id}`,
}) })
} }
export function createMenu(data: Omit<MenuItem, 'id' | 'children'>) { export function createMenu(data: Omit<MenuItem, 'id' | 'children'>) {
return defHttp.post({ return defHttp.post({
url: '/system/menu/save', url: '/baymax-system/menu/submit',
data, data,
}) })
} }
export function updateMenu(data: Omit<MenuItem, 'children'>) { export function updateMenu(data: Omit<MenuItem, 'children'>) {
return defHttp.post({ return defHttp.post({
url: '/system/menu/update', url: '/baymax-system/menu/submit',
data, data,
}) })
} }
export function deleteMenu(id: string) { export function deleteMenu(ids: string[]) {
return defHttp.post({ return defHttp.post({
url: `/system/menu/delete?id=${id}`, url: `/baymax-system/menu/remove?ids=${ids}`,
}) })
} }

10
src/api/system/param/index.ts

@ -3,27 +3,27 @@ import { defHttp } from '@/utils/http/axios'
export function getParamList(params: GetParamListParams) { export function getParamList(params: GetParamListParams) {
return defHttp.get({ return defHttp.get({
url: '/param/page', url: '/baymax-system/param/list',
params, params,
}) })
} }
export function createParam(data: Partial<Param>) { export function createParam(data: Partial<Param>) {
return defHttp.post({ return defHttp.post({
url: '/param/save', url: '/baymax-system/param/submit',
data, data,
}) })
} }
export function updateParam(data: Partial<Param>) { export function updateParam(data: Partial<Param>) {
return defHttp.post({ return defHttp.post({
url: '/param/update', url: '/baymax-system/param/submit',
data, data,
}) })
} }
export function deleteParam(id: string) { export function deleteParam(ids: string[]) {
return defHttp.post({ return defHttp.post({
url: `/param/remove?id=${id}`, url: `/baymax-system/param/remove?ids=${ids}`,
}) })
} }

42
src/api/system/post/index.ts

@ -0,0 +1,42 @@
import type { GetPostListParams, SystemPost } from './types'
import { defHttp } from '@/utils/http/axios'
export function getPostList(params: GetPostListParams) {
return defHttp.get<PageResult<SystemPost>>({
url: '/baymax-system/post/list',
params,
})
}
export function detailPost(id: string) {
return defHttp.get<SystemPost>({
url: `/baymax-system/post/detail?id=${id}`,
})
}
export function createPost(data: Partial<SystemPost>) {
return defHttp.post({
url: '/baymax-system/post/submit',
data,
})
}
export function updatePost(data: Partial<SystemPost>) {
return defHttp.post({
url: '/baymax-system/post/submit',
data,
})
}
export function deletePost(ids: string[]) {
return defHttp.post({
url: `/baymax-system/post/remove?ids=${ids}`,
})
}
export function getPostTree(params: { tenantId: string }) {
return defHttp.get({
url: '/baymax-system/post/select',
params,
})
}

21
src/api/system/post/types.ts

@ -0,0 +1,21 @@
export interface GetPostListParams extends PageParam {
}
export interface SystemPost {
id: string
category: number
categoryName: string
createDept: string
createTime: string
createUser: string
isDeleted: number
postCode: string
postName: string
remark: string
sort: number
status: number
tenantId: string
tenantName?: string
updateTime: string
updateUser: string
}

37
src/api/system/region/index.ts

@ -0,0 +1,37 @@
import type { Region } from './types'
import { defHttp } from '@/utils/http/axios'
export function lazyGetRegionList(parentCode?: string) {
return defHttp.get<Region[]>({
url: '/baymax-system/region/lazy-tree',
params: {
parentCode,
},
})
}
export function getRegionDetail(code: string) {
return defHttp.get<Region>({
url: `baymax-system/region/detail?code=${code}`,
})
}
export function updateRegion(data: Partial<Region>) {
return defHttp.post({
url: '/baymax-system/region/submit',
data,
})
}
export function deleteRegion(id: string) {
return defHttp.post({
url: `/baymax-system/region/remove?id=${id}`,
})
}
export function exportRegion() {
return defHttp.get({
url: '/baymax-system/region/export-region',
responseType: 'blob',
})
}

15
src/api/system/region/types.ts

@ -0,0 +1,15 @@
export interface Region {
id: string
parentCode: string
subCode: string
code: string
name: string
sort: number
remark: string
parentId: string
title: string
value: string
hasChildren: boolean
parentName: string
regionLevel: number | string
}

29
src/api/system/role/index.ts

@ -1,56 +1,55 @@
import type { GetRoleListParams, MenuTreeNode, Role } from './types' import type { GetRoleListParams, GrantParams, MenuTreeNode, Role } from './types'
import { defHttp } from '@/utils/http/axios' import { defHttp } from '@/utils/http/axios'
export function lazyGetRoleList(params: GetRoleListParams) { export function getRoleList(params: GetRoleListParams) {
return defHttp.get<Role[]>({ return defHttp.get<Role[]>({
url: '/system/role/lazy-list', url: '/baymax-system/role/list',
params, params,
}) })
} }
export function createRole(data: Partial<Role>) { export function createRole(data: Partial<Role>) {
return defHttp.post({ return defHttp.post({
url: '/system/role/save', url: '/baymax-system/role/submit',
data, data,
}) })
} }
export function updateRole(data: Partial<Role>) { export function updateRole(data: Partial<Role>) {
return defHttp.post({ return defHttp.post({
url: '/system/role/update', url: '/baymax-system/role/submit',
data, data,
}) })
} }
export function deleteRole(id: string) { export function deleteRole(id: string) {
return defHttp.post({ return defHttp.post({
url: `/system/role/delete?id=${id}`, url: `/baymax-system/role/delete?id=${id}`,
}) })
} }
export function getRoleTree(params?: { tenantId: string }) { export function getRoleTree(params?: { tenantId: string }) {
return defHttp.get<{ id: string, title: string }[]>({ return defHttp.get<{ id: string, title: string }[]>({
url: '/system/role/tree', url: '/baymax-system/role/tree',
params, params,
}) })
} }
export function getMenuTree() { export function getMenuTree() {
return defHttp.get<MenuTreeNode[]>({ return defHttp.get<{ menu: MenuTreeNode[] }>({
url: '/system/menu/grant-tree', url: '/baymax-system/menu/grant-tree',
}) })
} }
export function getMenuIdsByRole(roleId: string) { export function getRoleTreeKeys(roleIds: string) {
return defHttp.get<string[]>({ return defHttp.get<{ menu: string[] }>({
url: '/system/permission/list-role-menus', url: `/baymax-system/menu/role-tree-keys?roleIds=${roleIds}`,
params: { roleId },
}) })
} }
export function assignMenuToRole(data: { roleId: string, menuIds: string[] }) { export function updateGrant(data: GrantParams) {
return defHttp.post({ return defHttp.post({
url: '/system/permission/assign-role-menu', url: `/baymax-system/role/grant`,
data, data,
}) })
} }

9
src/api/system/role/types.ts

@ -25,5 +25,14 @@ export interface MenuTreeNode {
id: string id: string
parentId: string parentId: string
title: string title: string
hasChildren: boolean
children?: MenuTreeNode[] children?: MenuTreeNode[]
} }
export interface GrantParams {
apiScopeIds: string[]
dataScopeIds: string[]
menuIds: string[]
roleIds: string[]
topMenuIds: string[]
}

23
src/api/system/tenant/index.ts

@ -3,33 +3,46 @@ import { defHttp } from '@/utils/http/axios'
export function getTenantList(params: GetTenantListParams) { export function getTenantList(params: GetTenantListParams) {
return defHttp.get<PageResult<Tenant>>({ return defHttp.get<PageResult<Tenant>>({
url: '/system/tenant/page', url: '/baymax-system/tenant/list',
params, params,
}) })
} }
export function updateTenant(data: Tenant) { export function updateTenant(data: Tenant) {
return defHttp.post({ return defHttp.post({
url: '/system/tenant/update', url: '/baymax-system/tenant/submit',
data, data,
}) })
} }
export function createTenant(data: Tenant) { export function createTenant(data: Tenant) {
return defHttp.post({ return defHttp.post({
url: '/system/tenant/save', url: '/baymax-system/tenant/submit',
data, data,
}) })
} }
export function detailTenant(id: string) {
return defHttp.get({
url: `/baymax-system/tenant/detail?id=${id}`,
})
}
export function deleteTenant(id: string) { export function deleteTenant(id: string) {
return defHttp.post({ return defHttp.post({
url: `/system/tenant/remove?id=${id}`, url: `/baymax-system/tenant/remove?id=${id}`,
})
}
export function settingTenant(data: Partial<Tenant>) {
return defHttp.post({
url: `/baymax-system/tenant/setting`,
data,
}) })
} }
export function getAllTenants() { export function getAllTenants() {
return defHttp.get({ return defHttp.get({
url: '/system/tenant/select', url: '/baymax-system/tenant/select',
}) })
} }

13
src/api/system/tenant/types.ts

@ -1,11 +1,14 @@
export interface Tenant { export interface Tenant {
id: string id: string
tenantId?: string tenantId: string
tenantName: string tenantName: string
adminAccount: string createUser: string
contactName: string accountNumber: number
contactMobile: string contactNumber: string
expireTime?: string expireTime: string
domainUrl: string
linkman: string
address: string
} }
export interface GetTenantListParams extends PageParam { export interface GetTenantListParams extends PageParam {

29
src/api/system/user/index.ts

@ -3,27 +3,46 @@ import { defHttp } from '@/utils/http/axios'
export function getUserList(params: GetUserListParams) { export function getUserList(params: GetUserListParams) {
return defHttp.get<PageResult<SystemUser>>({ return defHttp.get<PageResult<SystemUser>>({
url: '/system/user/page', url: '/baymax-system/user/page',
params, params,
}) })
} }
export function createUser(data: Partial<SystemUser>) { export function createUser(data: Partial<SystemUser>) {
return defHttp.post({ return defHttp.post({
url: '/system/user/save', url: '/baymax-system/user/save',
data, data,
}) })
} }
export function updateUser(data: Partial<SystemUser>) { export function updateUser(data: Partial<SystemUser>) {
return defHttp.post({ return defHttp.post({
url: '/system/user/update', url: '/baymax-system/user/update',
data, data,
}) })
} }
export function deleteUser(id: string) { export function deleteUser(ids: string[]) {
return defHttp.post({ return defHttp.post({
url: `/system/user/delete?id=${id}`, url: `/baymax-system/user/remove?ids=${ids}`,
})
}
export function resetPasswordByIds(ids: string[]) {
return defHttp.post({
url: `/baymax-system/user/reset-password?userIds=${ids}`,
})
}
export function unlockUserByIds(ids: string[]) {
return defHttp.post({
url: `/baymax-system/user/unlock?userIds=${ids}`,
})
}
export function exportUsers() {
return defHttp.get({
url: '/baymax-system/user/export-user',
responseType: 'blob',
}) })
} }

1
src/api/system/user/types.ts

@ -20,4 +20,5 @@ export interface SystemUser {
sex: 1 | 2 | 3 sex: 1 | 2 | 3
tenantId: string tenantId: string
tenantName: string tenantName: string
userType: string
} }

22
src/components/Form/src/components/FileUpload.vue

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Upload } from 'ant-design-vue' import { Upload } from 'ant-design-vue'
import { InboxOutlined } from '@ant-design/icons-vue'
import { computed, reactive, ref, unref, watch } from 'vue' import { computed, reactive, ref, unref, watch } from 'vue'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId } from '@/utils/auth'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
@ -10,7 +11,6 @@ import { useDesign } from '@/hooks/web/useDesign'
import { useGlobSetting } from '@/hooks/setting' import { useGlobSetting } from '@/hooks/setting'
defineOptions({ name: 'FileUpload' }) defineOptions({ name: 'FileUpload' })
const props = defineProps({ const props = defineProps({
value: propTypes.oneOfType([propTypes.string, propTypes.array]), value: propTypes.oneOfType([propTypes.string, propTypes.array]),
text: propTypes.string.def('上传'), text: propTypes.string.def('上传'),
@ -37,6 +37,8 @@ const props = defineProps({
removeConfirm: propTypes.bool.def(false), removeConfirm: propTypes.bool.def(false),
beforeUpload: propTypes.func, beforeUpload: propTypes.func,
disabled: propTypes.bool.def(false), disabled: propTypes.bool.def(false),
dragger: propTypes.bool.def(false),
draggerHint: propTypes.string,
}) })
const emit = defineEmits(['change', 'update:value']) const emit = defineEmits(['change', 'update:value'])
const { createMessage, createConfirm } = useMessage() const { createMessage, createConfirm } = useMessage()
@ -272,7 +274,8 @@ function getFileName(path) {
<template> <template>
<div ref="containerRef" :class="`${prefixCls}-container`"> <div ref="containerRef" :class="`${prefixCls}-container`">
<Upload <component
:is="dragger ? Upload.Dragger : Upload"
:headers="headers" :headers="headers"
:multiple="multiple" :multiple="multiple"
:action="uploadUrl" :action="uploadUrl"
@ -283,7 +286,18 @@ function getFileName(path) {
@change="onFileChange" @change="onFileChange"
@preview="onFilePreview" @preview="onFilePreview"
> >
<template v-if="isImageMode"> <template v-if="dragger">
<p class="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p class="ant-upload-text">
点击或拖拽文件到这里进行上传
</p>
<p v-if="draggerHint" class="ant-upload-hint">
{{ draggerHint }}
</p>
</template>
<template v-else-if="isImageMode">
<div v-if="!isMaxCount"> <div v-if="!isMaxCount">
<span class="i-ant-design:plus-outlined" /> <span class="i-ant-design:plus-outlined" />
<div class="ant-upload-text"> <div class="ant-upload-text">
@ -295,7 +309,7 @@ function getFileName(path) {
<span class="i-ant-design:upload-outlined" /> <span class="i-ant-design:upload-outlined" />
<span>{{ text }}</span> <span>{{ text }}</span>
</a-button> </a-button>
</Upload> </component>
</div> </div>
</template> </template>

1
src/components/ImportModal/index.ts

@ -0,0 +1 @@
export { default as ImportModal } from './src/ImportModal.vue'

58
src/components/ImportModal/src/ImportModal.vue

@ -0,0 +1,58 @@
<script setup lang="ts">
import { BasicModal, useModalInner } from '@/components/Modal'
import FileUpload from '@/components/Form/src/components/FileUpload.vue'
import { useGlobSetting } from '@/hooks/setting'
import { useMessage } from '@/hooks/web/useMessage'
import { downloadByData } from '@/utils/file/download'
import { defHttp } from '@/utils/http/axios'
import { noop } from '@/utils'
const props = defineProps<{
title?: string
uploadUrl: string
templateUrl?: string
hint?: string
}>()
const emit = defineEmits(['register', 'success'])
const globSetting = useGlobSetting()
const urlPrefix = globSetting.urlPrefix
const [register, { closeModal }] = useModalInner()
function onUploadChange() {
useMessage().createMessage.success('导入成功')
emit('success')
closeModal()
}
function downloadTemplate() {
defHttp.get({
url: props.templateUrl!,
responseType: 'blob',
}, { isReturnNativeResponse: true })
.then((res) => {
let filename: string = 'export-data.xlsx'
try {
filename = res.headers['content-disposition'].split(';filename=')[1]
filename = decodeURIComponent(filename)
}
catch {
}
downloadByData(res.data, filename)
})
.catch(noop)
}
</script>
<template>
<BasicModal :title="title || '数据导入'" @register="register">
<FileUpload dragger :dragger-hint="hint" :show-upload-list="false" :upload-url="`${urlPrefix}${uploadUrl}`" @change="onUploadChange" />
<div v-if="templateUrl" text="right" mt="10px">
<a-button type="link" @click="downloadTemplate">
下载模版
</a-button>
</div>
<slot />
</BasicModal>
</template>

10
src/components/Tree/src/BasicTree.vue

@ -429,7 +429,7 @@ export default defineComponent({
{extendSlots(slots)} {extendSlots(slots)}
</TreeHeader> </TreeHeader>
)} )}
<Spin wrapperClassName={unref(props.treeWrapperClassName)} spinning={unref(props.loading)} tip="加载中..."> <Spin wrapperClassName={`${unref(props.treeWrapperClassName) || ''} h-full`} spinning={unref(props.loading)} tip="加载中...">
<ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}> <ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}>
<Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value} loadData={getBindValues.value.loadData ? loadData : undefined}> <Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value} loadData={getBindValues.value.loadData ? loadData : undefined}>
{extendSlots(slots, ['title'])} {extendSlots(slots, ['title'])}
@ -443,3 +443,11 @@ export default defineComponent({
}, },
}) })
</script> </script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-tree';
.@{prefix-cls} :deep(.ant-spin-container) {
height: 100%;
}
</style>

1
src/components/Tree/src/components/TreeHeader.vue

@ -177,5 +177,6 @@ watch(
</template> </template>
</Dropdown> </Dropdown>
</div> </div>
<slot v-if="slots.headerAction" name="headerAction" />
</div> </div>
</template> </template>

2
src/components/Tree/style/index.less

@ -48,6 +48,6 @@
} }
&-header { &-header {
border-bottom: 1px solid var(--border-color); // border-bottom: 1px solid var(--border-color);
} }
} }

2
src/enums/pageEnum.ts

@ -2,7 +2,7 @@ export enum PageEnum {
// basic login path // basic login path
BASE_LOGIN = '/login', BASE_LOGIN = '/login',
// basic home path // basic home path
BASE_HOME = '/dashboard', BASE_HOME = '/home',
// error page path // error page path
ERROR_PAGE = '/exception', ERROR_PAGE = '/exception',
// error log page path // error log page path

13
src/layouts/default/header/components/user-dropdown/index.vue

@ -1,10 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Avatar, Dropdown, Menu, MenuDivider } from 'ant-design-vue' import { Avatar, Dropdown, Menu } from 'ant-design-vue'
import { UserOutlined } from '@ant-design/icons-vue' import { UserOutlined } from '@ant-design/icons-vue'
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface' import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
import { computed } from 'vue' import { computed } from 'vue'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
// import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { useModal } from '@/components/Modal' import { useModal } from '@/components/Modal'
@ -28,7 +29,7 @@ const LockAction = createAsyncComponent(() => import('../lock/LockModal.vue'))
const { prefixCls } = useDesign('header-user-dropdown') const { prefixCls } = useDesign('header-user-dropdown')
const { t } = useI18n() const { t } = useI18n()
const { getShowDoc, getUseLockPage } = useHeaderSetting() // const { getShowDoc, getUseLockPage } = useHeaderSetting()
const userStore = useUserStore() const userStore = useUserStore()
const getUserInfo = computed(() => { const getUserInfo = computed(() => {
@ -84,11 +85,11 @@ function handleMenuClick(e: MenuInfo) {
<template #overlay> <template #overlay>
<Menu @click="handleMenuClick"> <Menu @click="handleMenuClick">
<MenuItem key="profile" :text="t('layout.header.accountCenter')" icon="i-ion:person-outline" /> <MenuItem key="profile" :text="t('layout.header.accountCenter')" icon="i-ion:person-outline" />
<MenuDivider v-if="getShowDoc" /> <!-- <MenuDivider v-if="getShowDoc" /> -->
<MenuItem <!-- <MenuItem
v-if="getUseLockPage" key="lock" :text="t('layout.header.tooltipLock')" v-if="getUseLockPage" key="lock" :text="t('layout.header.tooltipLock')"
icon="i-ion:lock-closed-outline" icon="i-ion:lock-closed-outline"
/> /> -->
<MenuItem key="logout" :text="t('layout.header.dropdownItemLoginOut')" icon="i-ion:power-outline" /> <MenuItem key="logout" :text="t('layout.header.dropdownItemLoginOut')" icon="i-ion:power-outline" />
</Menu> </Menu>
</template> </template>

34
src/layouts/default/header/index.vue

@ -7,38 +7,29 @@ import LayoutTrigger from '../trigger/index.vue'
import { ErrorAction, LayoutBreadcrumb, UserDropDown } from './components' import { ErrorAction, LayoutBreadcrumb, UserDropDown } from './components'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { AppLocalePicker, AppLogo, AppSearch } from '@/components/Application' import { AppDarkModeToggle, AppLogo, AppSearch } from '@/components/Application'
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting' import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting' import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { useRootSetting } from '@/hooks/setting/useRootSetting' import { useRootSetting } from '@/hooks/setting/useRootSetting'
import { MenuModeEnum, MenuSplitTyeEnum } from '@/enums/menuEnum' import { MenuModeEnum, MenuSplitTyeEnum } from '@/enums/menuEnum'
import { SettingButtonPositionEnum } from '@/enums/appEnum'
import { useAppInject } from '@/hooks/web/useAppInject' import { useAppInject } from '@/hooks/web/useAppInject'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
import { useLocale } from '@/locales/useLocale'
defineOptions({ name: 'LayoutHeader' }) defineOptions({ name: 'LayoutHeader' })
const props = defineProps({ const props = defineProps({
fixed: propTypes.bool, fixed: propTypes.bool,
}) })
const Header = Layout.Header const Header = Layout.Header
const SettingDrawer = createAsyncComponent(() => import('@/layouts/default/setting/index.vue'), {
loading: true,
})
const { prefixCls } = useDesign('layout-header') const { prefixCls } = useDesign('layout-header')
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting() const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting()
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting() const { getUseErrorHandle } = useRootSetting()
const { getHeaderTheme, getShowContent, getShowBread, getShowHeaderLogo, getShowHeader, getShowSearch } const { getHeaderTheme, getShowContent, getShowBread, getShowHeaderLogo, getShowSearch }
= useHeaderSetting() = useHeaderSetting()
const { getShowLocalePicker } = useLocale()
const { getIsMobile } = useAppInject() const { getIsMobile } = useAppInject()
const getHeaderClass = computed(() => { const getHeaderClass = computed(() => {
@ -53,18 +44,6 @@ const getHeaderClass = computed(() => {
] ]
}) })
const getShowSetting = computed(() => {
if (!unref(getShowSettingButton))
return false
const settingButtonPosition = unref(getSettingButtonPosition)
if (settingButtonPosition === SettingButtonPositionEnum.AUTO)
return unref(getShowHeader)
return settingButtonPosition === SettingButtonPositionEnum.HEADER
})
const getLogoWidth = computed(() => { const getLogoWidth = computed(() => {
if (!unref(getIsMixMode) || unref(getIsMobile)) if (!unref(getIsMixMode) || unref(getIsMobile))
return {} return {}
@ -111,14 +90,9 @@ const getMenuMode = computed(() => {
<ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" /> <ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
<AppLocalePicker <AppDarkModeToggle />
v-if="getShowLocalePicker" :reload="true" :show-text="false"
:class="`${prefixCls}-action__item locale-item`"
/>
<UserDropDown :theme="getHeaderTheme" /> <UserDropDown :theme="getHeaderTheme" />
<SettingDrawer v-if="getShowSetting" :class="`${prefixCls}-action__item`" />
</div> </div>
</Header> </Header>
</template> </template>

2
src/router/helper/routeHelper.ts

@ -84,7 +84,7 @@ export function transformObjToRoute(menuList: MenuItem[]): AppRouteModule[] {
const route = { ...item } as unknown as AppRouteModule const route = { ...item } as unknown as AppRouteModule
if (isHttpUrl(route.path)) if (isHttpUrl(route.path))
route.component = 'IFrame' route.component = 'IFrame'
else if (route.children && route.parentId === '0') else if (route.children && route.children.length && route.parentId === '0')
route.component = 'LAYOUT' route.component = 'LAYOUT'
else if (!route.children) else if (!route.children)
route.component = route.component as string route.component = route.component as string

13
src/router/routes/index.ts

@ -4,7 +4,6 @@ import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes/basic'
import { PageEnum } from '@/enums/pageEnum' import { PageEnum } from '@/enums/pageEnum'
import { t } from '@/hooks/web/useI18n' import { t } from '@/hooks/web/useI18n'
import { LAYOUT } from '@/router/constant'
// import.meta.glob() 直接引入所有的模块 Vite 独有的功能 // import.meta.glob() 直接引入所有的模块 Vite 独有的功能
const modules = import.meta.glob('./modules/**/*.ts', { eager: true }) const modules = import.meta.glob('./modules/**/*.ts', { eager: true })
@ -38,23 +37,11 @@ export const LoginRoute: AppRouteRecordRaw = {
}, },
} }
export const ProfileRoute: AppRouteRecordRaw = {
path: '/profile',
component: LAYOUT,
name: 'Profile',
meta: {
title: t('routes.basic.profile'),
hidden: true,
},
children: [],
}
// Basic routing without permission // Basic routing without permission
// 未经许可的基本路由 // 未经许可的基本路由
export const basicRoutes = [ export const basicRoutes = [
LoginRoute, LoginRoute,
RootRoute, RootRoute,
ProfileRoute,
REDIRECT_ROUTE, REDIRECT_ROUTE,
PAGE_NOT_FOUND_ROUTE, PAGE_NOT_FOUND_ROUTE,
] ]

40
src/router/routes/modules/dashboard.ts

@ -1,40 +0,0 @@
import type { AppRouteModule } from '@/router/types'
import { LAYOUT } from '@/router/constant'
import { t } from '@/hooks/web/useI18n'
const dashboard: AppRouteModule = {
path: '/dashboard',
name: 'Dashboard',
component: LAYOUT,
parentId: '0',
redirect: '/dashboard/analysis',
meta: {
orderNo: 10,
icon: 'i-ant-design:home-outlined',
title: t('routes.dashboard.dashboard'),
},
children: [
{
path: 'analysis',
name: 'Analysis',
component: () => import('@/views/dashboard/analysis/index.vue'),
meta: {
// affix: true,
title: t('routes.dashboard.analysis'),
icon: 'i-ant-design:bar-chart-outlined',
},
},
{
path: 'workbench',
name: 'Workbench',
component: () => import('@/views/dashboard/workbench/index.vue'),
meta: {
title: t('routes.dashboard.workbench'),
icon: 'i-ant-design:appstore-outlined',
},
},
],
}
export default dashboard

28
src/router/routes/modules/profile.ts

@ -0,0 +1,28 @@
import type { AppRouteModule } from '@/router/types'
import { LAYOUT } from '@/router/constant'
import { t } from '@/hooks/web/useI18n'
const profile: AppRouteModule = {
path: '/profile',
name: 'Profile',
component: LAYOUT,
parentId: '0',
redirect: '/profile/index',
meta: {
title: '',
hideMenu: true,
},
children: [
{
path: 'index',
name: 'ProfileIndex',
component: () => import('@/views/base/profile/index.vue'),
meta: {
title: t('routes.basic.profile'),
},
},
],
}
export default profile

6
src/store/modules/permission.ts

@ -4,7 +4,7 @@ import { defineStore } from 'pinia'
import { useUserStore } from './user' import { useUserStore } from './user'
import { store } from '@/store' import { store } from '@/store'
import type { AppRouteRecordRaw, Menu } from '@/router/types' import type { AppRouteRecordRaw, Menu } from '@/router/types'
import dashboard from '@/router/routes/modules/dashboard' import profile from '@/router/routes/modules/profile'
import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic' import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'
import { transformRouteToMenu } from '@/router/helper/menuHelper' import { transformRouteToMenu } from '@/router/helper/menuHelper'
import { flatMultiLevelRoutes, transformObjToRoute } from '@/router/helper/routeHelper' import { flatMultiLevelRoutes, transformObjToRoute } from '@/router/helper/routeHelper'
@ -141,7 +141,7 @@ export const usePermissionStore = defineStore('app-permission', {
let routeList = transformObjToRoute(userInfo.menus) let routeList = transformObjToRoute(userInfo.menus)
// Background routing to menu structure // Background routing to menu structure
const backMenuList = transformRouteToMenu([dashboard, ...routeList]) const backMenuList = transformRouteToMenu([profile, ...routeList])
this.setBackMenuList(backMenuList) this.setBackMenuList(backMenuList)
// remove meta.ignoreRoute item // remove meta.ignoreRoute item
@ -149,7 +149,7 @@ export const usePermissionStore = defineStore('app-permission', {
routeList = routeList.filter(routeRemoveIgnoreFilter) routeList = routeList.filter(routeRemoveIgnoreFilter)
routeList = flatMultiLevelRoutes(routeList) routeList = flatMultiLevelRoutes(routeList)
routes = [PAGE_NOT_FOUND_ROUTE, dashboard, ...routeList] routes = [PAGE_NOT_FOUND_ROUTE, profile, ...routeList]
patchHomeAffix(routes) patchHomeAffix(routes)
return routes return routes
}, },

10
src/store/modules/user.ts

@ -146,6 +146,8 @@ export const useUserStore = defineStore('app-user', {
const menus: MenuItem[] = [] const menus: MenuItem[] = []
for (let i = 0; i < __menus.length; i++) { for (let i = 0; i < __menus.length; i++) {
const __menu = __menus[i] const __menu = __menus[i]
// componentName = /system/menu -> SystemMenu
// /system/menu/index -> SystemMenu
const componentName = __menu.path const componentName = __menu.path
.replace('/index', '') .replace('/index', '')
.split('/') .split('/')
@ -153,11 +155,13 @@ export const useUserStore = defineStore('app-user', {
.map(i => `${i[0].toUpperCase()}${i.slice(1)}`) .map(i => `${i[0].toUpperCase()}${i.slice(1)}`)
.join('') .join('')
const component = __menu.path.replaceAll(/\/:[\s|\S]*/g, '')
menus.push({ menus.push({
id: __menu.id, id: __menu.id,
parentId: __menu.parentId, parentId: __menu.parentId,
name: __menu.name, name: __menu.name,
component: `${__menu.path}${__menu.path.endsWith('index') ? '' : '/index'}.vue`, component: `${component}${component.endsWith('index') ? '' : '/index'}.vue`,
componentName, componentName,
path: __menu.path, path: __menu.path,
icon: __menu.source, icon: __menu.source,
@ -165,8 +169,8 @@ export const useUserStore = defineStore('app-user', {
children: __menu.children ? normalizeMenus(__menu.children) : [], children: __menu.children ? normalizeMenus(__menu.children) : [],
code: __menu.code, code: __menu.code,
type: 1, type: 1,
visible: 1, visible: __menu.visible,
keepAlive: 0, keepAlive: __menu.isOpen === 2 ? 1 : 0,
}) })
} }
return menus return menus

1
src/types/axios.d.ts vendored

@ -40,6 +40,7 @@ export interface RetryRequest {
export interface Result<T = any> { export interface Result<T = any> {
code: number code: number
message: string message: string
msg: string
data: T data: T
} }

96
src/utils/http/axios/Axios.ts

@ -15,15 +15,16 @@ import type { RequestOptions, Result, UploadFileParams } from '@/types/axios'
import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum' import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum'
import { downloadByData } from '@/utils/file/download' import { downloadByData } from '@/utils/file/download'
import { useGlobSetting } from '@/hooks/setting' import { useGlobSetting } from '@/hooks/setting'
import { getRefreshToken, getTenantId, setAccessToken } from '@/utils/auth' import { getRefreshToken, getTenantId } from '@/utils/auth'
import { useUserStoreWithOut } from '@/store/modules/user'
export * from './axiosTransform' export * from './axiosTransform'
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
// 请求队列 // 请求队列
let requestList: any[] = [] // const requestList: any[] = []
// 是否正在刷新中 // 是否正在刷新中
let isRefreshToken = false // const isRefreshToken = false
/** /**
* @description: axios * @description: axios
@ -123,51 +124,54 @@ export class VAxios {
// 响应结果拦截器处理 // 响应结果拦截器处理
this.axiosInstance.interceptors.response.use(async (res: AxiosResponse<any>) => { this.axiosInstance.interceptors.response.use(async (res: AxiosResponse<any>) => {
const config = res.config
if (res.data.code === 401) { if (res.data.code === 401) {
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 useUserStoreWithOut().logout(true)
if (!isRefreshToken) { return res
isRefreshToken = true
// 1. 获取到刷新token
if (getRefreshToken()) {
// 2. 进行刷新访问令牌
try {
const refreshTokenRes = await this.refreshToken()
// 2.1 刷新成功,则回放队列的请求 + 当前请求
const refreshToken = getRefreshToken()
setAccessToken(refreshTokenRes.data.data.accessToken)
;(config as Recordable).headers.Authorization = `Bearer ${refreshToken}`
requestList.forEach((cb: any) => {
cb()
})
requestList = []
return new Promise((resolve) => {
resolve(this.axiosInstance(config))
})
// res = await Promise.all([this.axiosInstance(config)])[0]
}
catch (e) {
requestList.forEach((cb: any) => {
cb()
})
}
finally {
requestList = []
isRefreshToken = false
}
}
}
else {
// 添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => {
const refreshToken = getRefreshToken()
requestList.push(() => {
;(config as Recordable).headers.Authorization = `Bearer ${refreshToken}` // 让每个请求携带自定义token 请根据实际情况自行修改
resolve(this.axiosInstance(config))
})
})
}
} }
// // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
// if (!isRefreshToken) {
// isRefreshToken = true
// // 1. 获取到刷新token
// if (getRefreshToken()) {
// // 2. 进行刷新访问令牌
// try {
// const refreshTokenRes = await this.refreshToken()
// // 2.1 刷新成功,则回放队列的请求 + 当前请求
// const refreshToken = getRefreshToken()
// setAccessToken(refreshTokenRes.data.data.accessToken)
// ;(config as Recordable).headers.Authorization = `Bearer ${refreshToken}`
// requestList.forEach((cb: any) => {
// cb()
// })
// requestList = []
// return new Promise((resolve) => {
// resolve(this.axiosInstance(config))
// })
// // res = await Promise.all([this.axiosInstance(config)])[0]
// }
// catch (e) {
// requestList.forEach((cb: any) => {
// cb()
// })
// }
// finally {
// requestList = []
// isRefreshToken = false
// }
// }
// }
// else {
// // 添加到队列,等待刷新获取到新的令牌
// return new Promise((resolve) => {
// const refreshToken = getRefreshToken()
// requestList.push(() => {
// ;(config as Recordable).headers.Authorization = `Bearer ${refreshToken}` // 让每个请求携带自定义token 请根据实际情况自行修改
// resolve(this.axiosInstance(config))
// })
// })
// }
res && axiosCanceler.removePending(res.config) res && axiosCanceler.removePending(res.config)
if (responseInterceptors && isFunction(responseInterceptors)) if (responseInterceptors && isFunction(responseInterceptors))
res = responseInterceptors(res) res = responseInterceptors(res)

9
src/utils/http/axios/index.ts

@ -40,14 +40,15 @@ const transform: AxiosTransform = {
transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => { transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
const { t } = useI18n() const { t } = useI18n()
const { isTransformResponse, isReturnNativeResponse } = options const { isTransformResponse, isReturnNativeResponse } = options
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer')
return res.data
// 是否返回原生响应头 比如:需要获取响应头时使用该属性 // 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse) if (isReturnNativeResponse)
return res return res
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer')
return res.data
// 不进行任何处理,直接返回 // 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取code,data,message这些信息时开启 // 用于页面代码可能需要直接获取code,data,message这些信息时开启
if (!isTransformResponse) if (!isTransformResponse)
@ -61,7 +62,7 @@ const transform: AxiosTransform = {
throw new Error(t('sys.api.apiRequestFailed')) throw new Error(t('sys.api.apiRequestFailed'))
} }
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 // 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
const { code, data: result, message } = data const { code, data: result, msg: message } = data
// 这里逻辑可以根据项目进行修改 // 这里逻辑可以根据项目进行修改
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS
if (hasSuccess) { if (hasSuccess) {

6
src/views/base/login/Login.vue

@ -3,11 +3,10 @@ import { computed } from 'vue'
import LoginForm from './LoginForm.vue' import LoginForm from './LoginForm.vue'
import ForgetPasswordForm from './ForgetPasswordForm.vue' import ForgetPasswordForm from './ForgetPasswordForm.vue'
import RegisterForm from './RegisterForm.vue' import RegisterForm from './RegisterForm.vue'
import { AppDarkModeToggle, AppLocalePicker, AppLogo } from '@/components/Application' import { AppDarkModeToggle, AppLogo } from '@/components/Application'
import { useGlobSetting } from '@/hooks/setting' import { useGlobSetting } from '@/hooks/setting'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { useLocaleStore } from '@/store/modules/locale'
defineProps({ defineProps({
sessionTimeout: { sessionTimeout: {
@ -18,8 +17,6 @@ defineProps({
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
const { prefixCls } = useDesign('login') const { prefixCls } = useDesign('login')
const { t } = useI18n() const { t } = useI18n()
const localeStore = useLocaleStore()
const showLocale = localeStore.getShowPicker
const title = computed(() => globSetting?.title ?? '') const title = computed(() => globSetting?.title ?? '')
</script> </script>
@ -27,7 +24,6 @@ const title = computed(() => globSetting?.title ?? '')
<div :class="prefixCls" class="relative h-full min-h-full w-full overflow-hidden px-4"> <div :class="prefixCls" class="relative h-full min-h-full w-full overflow-hidden px-4">
<div class="absolute right-4 top-4 flex items-center"> <div class="absolute right-4 top-4 flex items-center">
<AppDarkModeToggle v-if="!sessionTimeout" class="enter-x mr-2" /> <AppDarkModeToggle v-if="!sessionTimeout" class="enter-x mr-2" />
<AppLocalePicker v-if="!sessionTimeout && showLocale" class="enter-x xl:text-gray-600" :show-text="false" />
</div> </div>
<span class="-enter-x xl:hidden"> <span class="-enter-x xl:hidden">

4
src/views/base/login/LoginForm.vue

@ -28,9 +28,9 @@ const formRef = ref()
const loading = ref(false) const loading = ref(false)
const formData = reactive<LoginParams>({ const formData = reactive<LoginParams>({
tenantId: '345618', tenantId: '000000',
username: 'admin', username: 'admin',
password: '&demo8&!', password: '123456',
captchaKey: '', captchaKey: '',
captchaCode: '', captchaCode: '',
}) })

75
src/views/base/profile/components/UpdatePassword.vue

@ -0,0 +1,75 @@
<script lang='ts' setup>
import { Spin } from 'ant-design-vue'
import { ref } from 'vue'
import CryptoJS from 'crypto-js'
import { BasicForm, useForm } from '@/components/Form'
import { updatePassword } from '@/api/base/user'
import { noop } from '@/utils'
import { useMessage } from '@/hooks/web/useMessage'
import { useUserStore } from '@/store/modules/user'
const [register, { resetFields }] = useForm({
schemas: [
{
field: 'oldPassword',
label: '原密码',
required: true,
component: 'InputPassword',
},
{
field: 'newPassword',
label: '新密码',
required: true,
component: 'InputPassword',
},
{
field: 'newPassword1',
label: '确认密码',
required: true,
component: 'InputPassword',
},
],
baseColProps: {
span: 24,
},
actionColOptions: {
span: 24,
style: {
textAlign: 'center',
},
},
labelWidth: 80,
submitButtonOptions: {
text: '保存',
},
})
const { logout } = useUserStore()
const loading = ref(false)
function handleSubmit(data) {
loading.value = true
for (const key of Object.keys(data))
data[key] = CryptoJS.MD5(data[key]).toString()
updatePassword(data)
.then(() => {
resetFields()
useMessage().createMessage.success('保存成功, 请重新登陆')
logout()
})
.catch(noop)
.finally(() => {
loading.value = false
})
}
</script>
<template>
<div w="1/3">
<Spin :spinning="loading">
<BasicForm @register="register" @submit="handleSubmit" />
</Spin>
</div>
</template>

94
src/views/base/profile/components/UserInfo.vue

@ -0,0 +1,94 @@
<script lang='ts' setup>
import { Spin } from 'ant-design-vue'
import { ref } from 'vue'
import { BasicForm, useForm } from '@/components/Form'
import { getUserInfo, updateUserInfo } from '@/api/base/user'
import { noop } from '@/utils'
import { useMessage } from '@/hooks/web/useMessage'
import { useUserStore } from '@/store/modules/user'
const [register, { setFieldsValue }] = useForm({
schemas: [
{
field: 'avatar',
fields: ['id'],
label: '头像',
component: 'FileUpload',
componentProps: {
fileType: 'image',
maxCount: 1,
},
},
{
field: 'realName',
label: '姓名',
component: 'Input',
},
{
field: 'name',
label: '用户名',
component: 'Input',
},
{
field: 'phone',
label: '手机号',
component: 'Input',
},
{
field: 'email',
label: '邮箱',
component: 'Input',
},
],
baseColProps: {
span: 24,
},
actionColOptions: {
span: 24,
style: {
textAlign: 'center',
},
},
labelWidth: 50,
submitButtonOptions: {
text: '保存',
},
})
const loading = ref(true)
getUserInfo()
.then((res) => {
loading.value = false
setFieldsValue(res)
})
.catch(noop)
const { setUserInfo, userInfo } = useUserStore()
function handleSubmit(data) {
loading.value = true
updateUserInfo(data)
.then(() => {
loading.value = false
useMessage().createMessage.success('保存成功')
setUserInfo({
...userInfo!,
user: {
...userInfo!.user,
real_name: data.realName,
nick_name: data.name,
avatar: data.avatar,
phone: data.phone,
},
})
})
.catch(noop)
}
</script>
<template>
<div w="1/3">
<Spin :spinning="loading">
<BasicForm @register="register" @submit="handleSubmit" />
</Spin>
</div>
</template>

2
src/views/base/profile/components/index.ts

@ -0,0 +1,2 @@
export { default as UserInfo } from './UserInfo.vue'
export { default as UpdatePassword } from './UpdatePassword.vue'

19
src/views/base/profile/index.vue

@ -0,0 +1,19 @@
<script lang='ts' setup>
import { Card, Tabs } from 'ant-design-vue'
import { UpdatePassword, UserInfo } from './components'
</script>
<template>
<div p="12px">
<Card>
<Tabs>
<Tabs.TabPane key="1" tab="个人信息">
<UserInfo />
</Tabs.TabPane>
<Tabs.TabPane key="2" tab="修改密码">
<UpdatePassword />
</Tabs.TabPane>
</Tabs>
</Card>
</div>
</template>

43
src/views/system/dept/data.ts

@ -1,10 +1,11 @@
import { getSystemDictionary } from '@/api/base/common'
import { lazyGetDeptList } from '@/api/system/dept' import { lazyGetDeptList } from '@/api/system/dept'
import { getAllTenants } from '@/api/system/tenant' import { getAllTenants } from '@/api/system/tenant'
import type { BasicColumn, FormSchema } from '@/components/Table' import type { BasicColumn, FormSchema } from '@/components/Table'
export const columns: BasicColumn[] = [ export const columns: BasicColumn[] = [
{ {
title: '部门名称', title: '机构名称',
dataIndex: 'deptName', dataIndex: 'deptName',
width: 260, width: 260,
align: 'left', align: 'left',
@ -14,11 +15,21 @@ export const columns: BasicColumn[] = [
dataIndex: 'tenantName', dataIndex: 'tenantName',
width: 120, width: 120,
}, },
{
title: '机构全称',
dataIndex: 'fullName',
width: 260,
},
{
title: '机构类型',
dataIndex: 'deptCategoryName',
width: 260,
},
] ]
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
{ {
label: '部门名称', label: '机构名称',
field: 'deptName', field: 'deptName',
component: 'Input', component: 'Input',
colProps: { span: 6 }, colProps: { span: 6 },
@ -34,6 +45,12 @@ export const searchFormSchema: FormSchema[] = [
}, },
colProps: { span: 6 }, colProps: { span: 6 },
}, },
{
label: '机构全称',
field: 'fullName',
component: 'Input',
colProps: { span: 6 },
},
] ]
export const formSchema: FormSchema[] = [ export const formSchema: FormSchema[] = [
@ -70,11 +87,26 @@ export const formSchema: FormSchema[] = [
}, },
}, },
{ {
label: '部门名称', label: '机构名称',
field: 'deptName', field: 'deptName',
required: true, required: true,
component: 'Input', component: 'Input',
}, },
{
label: '机构全称',
field: 'fullName',
required: true,
component: 'Input',
},
{
label: '机构类型',
field: 'deptCategory',
required: true,
component: 'ApiSelect',
componentProps: {
api: () => getSystemDictionary('org_category'),
},
},
{ {
label: '显示顺序', label: '显示顺序',
field: 'sort', field: 'sort',
@ -82,4 +114,9 @@ export const formSchema: FormSchema[] = [
defaultValue: 0, defaultValue: 0,
component: 'InputNumber', component: 'InputNumber',
}, },
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
},
] ]

62
src/views/system/dept/index.vue

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue' import { PlusOutlined } from '@ant-design/icons-vue'
import DeptFormModal from './DeptFormModal.vue' import DeptFormModal from './DeptFormModal.vue'
import { columns, searchFormSchema } from './data' import { columns, searchFormSchema } from './data'
@ -9,7 +10,8 @@ import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table' import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteDept, lazyGetDeptList } from '@/api/system/dept' import { deleteDept, lazyGetDeptList } from '@/api/system/dept'
import type { Department, LazyGetDeptListParams } from '@/api/system/dept/types' import type { Department, LazyGetDeptListParams } from '@/api/system/dept/types'
import { usePermission } from '@/hooks/web/usePermission'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemDept' }) defineOptions({ name: 'SystemDept' })
@ -17,14 +19,14 @@ const { t } = useI18n()
const [registerModal, { openModal }] = useModal<Department>() const [registerModal, { openModal }] = useModal<Department>()
const { hasPermission } = usePermission() // const { hasPermission } = usePermission()
async function lazyGetDeptListWrap(params: LazyGetDeptListParams) { async function lazyGetDeptListWrap(params: LazyGetDeptListParams) {
try { try {
const list = await lazyGetDeptList(params) const list = await lazyGetDeptList(params)
return list.map(item => ({ return list.map(item => ({
...item, ...item,
children: item.hasChildren ? [] : undefined, // children: item.hasChildren ? [] : undefined,
})) }))
} }
catch { catch {
@ -32,11 +34,12 @@ async function lazyGetDeptListWrap(params: LazyGetDeptListParams) {
} }
} }
const [register, { reload }] = useTable<Department>({ const [register, { reload, clearSelectedRowKeys }] = useTable<Department>({
rowKey: 'id',
api: lazyGetDeptListWrap, api: lazyGetDeptListWrap,
load(record) { // load(record) {
return lazyGetDeptListWrap({ parentId: record.id }) // return lazyGetDeptListWrap({ parentId: record.id })
}, // },
columns, columns,
formConfig: { formConfig: {
labelWidth: 80, labelWidth: 80,
@ -46,33 +49,50 @@ const [register, { reload }] = useTable<Department>({
canResize: false, canResize: false,
useSearchForm: true, useSearchForm: true,
pagination: false, pagination: false,
showIndexColumn: false,
actionColumn: { actionColumn: {
width: 140, width: 140,
title: t('common.action'), title: t('common.action'),
dataIndex: 'action', dataIndex: 'action',
fixed: 'right', fixed: 'right',
auth: ['dept_delete', 'dept_edit'], // auth: ['dept_delete', 'dept_edit'],
}, },
}) })
async function handleDelete(id: string) { const { createMessage, createConfirm } = useMessage()
try { function handleDelete(id: string) {
await deleteDept(id) async function execute() {
useMessage().createMessage.success(t('common.delSuccessText')) try {
reload() await deleteDept(id)
createMessage.success(t('common.delSuccessText'))
await reload()
clearSelectedRowKeys()
}
catch {}
} }
catch {}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的菜单?',
onOk: execute,
})
} }
</script> </script>
<template> <template>
<div> <div>
<BasicTable :api="async () => ([] as Department[])" @register="register"> <BasicTable :api="async () => ([] as Department[])" @register="register">
<template v-if="hasPermission('dept_add')" #tableTitle> <!-- v-if="hasPermission('dept_add')" -->
<a-button type="primary" @click="openModal"> <template #tableTitle>
<PlusOutlined /> <Space>
{{ t('action.create') }} <a-button type="primary" @click="openModal">
</a-button> <PlusOutlined />
{{ t('action.create') }}
</a-button>
</Space>
</template> </template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
@ -82,14 +102,14 @@ async function handleDelete(id: string) {
{ {
icon: IconEnum.EDIT, icon: IconEnum.EDIT,
label: t('action.edit'), label: t('action.edit'),
auth: 'dept_edit', // auth: 'dept_edit',
onClick: () => openModal(true, record), onClick: () => openModal(true, record),
}, },
{ {
icon: IconEnum.DELETE, icon: IconEnum.DELETE,
danger: true, danger: true,
label: t('action.delete'), label: t('action.delete'),
auth: 'dept_delete', // auth: 'dept_delete',
popConfirm: { popConfirm: {
title: t('common.delMessage'), title: t('common.delMessage'),
placement: 'left', placement: 'left',

57
src/views/system/dict/DictFormModal.vue

@ -0,0 +1,57 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { getFormSchema } from './data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createDict, updateDict } from '@/api/system/dict'
import type { SystemDict } from '@/api/system/dict/types'
defineOptions({ name: 'DictFormModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const isUpdate = ref(false)
const [registerForm, { setFieldsValue, validate }] = useForm({
name: 'dict-form',
labelWidth: 100,
baseColProps: { span: 12 },
schemas: getFormSchema(),
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SystemDict) => {
isUpdate.value = true
setFieldsValue({ ...data })
})
async function handleSubmit() {
try {
const values = await validate<SystemDict>()
setModalProps({ confirmLoading: true })
await (isUpdate.value ? updateDict(values) : createDict(values))
closeModal()
emit('success')
useMessage().createMessage.success(t('common.saveSuccessText'))
}
catch {}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
width="55%"
:title="isUpdate ? t('action.edit') : t('action.create')"
:after-close="() => isUpdate = false"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>

126
src/views/system/dict/data.ts

@ -0,0 +1,126 @@
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
import type { BasicColumn, FormSchema } from '@/components/Table'
import type { SystemDict } from '@/api/system/dict/types'
export const columns: BasicColumn<SystemDict>[] = [
{
title: '字典编号',
dataIndex: 'code',
width: 180,
customRender: ({ record }) => {
const { code } = record
return h(Tag, { color: 'blue' }, () => code)
},
},
{
title: '字典名称',
dataIndex: 'dictValue',
width: 180,
},
{
title: '字典排序',
dataIndex: 'sort',
width: 50,
},
{
title: '封存',
dataIndex: 'isSealed',
width: 50,
customRender: ({ record }) => {
const { isSealed } = record
const str = isSealed === 0 ? '否' : '是'
return h(Tag, { color: 'blue' }, () => str)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '字典编号',
field: 'code',
component: 'Input',
colProps: { span: 6 },
},
{
label: '字典名称',
field: 'dictValue',
component: 'Input',
colProps: { span: 6 },
},
]
export function getFormSchema(): FormSchema[] {
return [
{
field: 'id',
show: false,
component: 'Input',
},
{
field: 'dictKey',
show: false,
component: 'Input',
defaultValue: '-1',
},
{
field: 'isDeleted',
show: false,
component: 'Input',
defaultValue: 0,
},
{
field: 'parentId',
show: false,
component: 'Input',
defaultValue: '0',
},
{
field: 'parentName',
show: false,
component: 'Input',
defaultValue: '顶级',
},
{
label: '字典编号',
field: 'code',
required: true,
component: 'Input',
colProps: {
span: 24,
},
},
{
label: '字典名称',
field: 'dictValue',
required: true,
component: 'Input',
},
{
label: '字典排序',
field: 'sort',
required: true,
component: 'InputNumber',
},
{
label: '封存',
field: 'isSealed',
required: true,
component: 'Switch',
defaultValue: 0,
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
checkedValue: 1,
unCheckedValue: 0,
},
},
{
label: '字典备注',
field: 'remark',
required: false,
component: 'Input',
},
]
}

130
src/views/system/dict/index.vue

@ -0,0 +1,130 @@
<script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { useRouter } from 'vue-router'
import { columns, searchFormSchema } from './data'
import DictFormModal from './DictFormModal.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteDict, getDictList } from '@/api/system/dict'
import type { SystemDict } from '@/api/system/dict/types'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemDict' })
const router = useRouter()
const { createMessage, createConfirm } = useMessage()
const [registerModal, { openModal }] = useModal<SystemDict>()
// const { hasPermission } = usePermission()
const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
api(params) {
return getDictList(params)
},
columns,
formConfig: {
labelWidth: 80,
schemas: searchFormSchema,
},
rowKey: 'id',
rowSelection: {},
useSearchForm: true,
bordered: true,
canResize: false,
actionColumn: {
width: 160,
title: '操作',
dataIndex: 'action',
fixed: 'right',
// auth: ['user_edit', 'user_delete'],
},
})
async function handleDelete(id?: string) {
const ids = id ? [id] : (getSelectRowKeys() as string[])
if (!ids.length)
return createMessage.warning('请选择要删除的数据')
async function execute() {
try {
await deleteDict(ids)
createMessage.success('删除成功')
reload()
clearSelectedRowKeys()
}
catch {}
}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的字典?',
onOk: execute,
})
}
function toPath(data: SystemDict) {
router.push({
path: `/system/dict/setting/${data.id}`,
})
}
</script>
<template>
<div>
<BasicTable :api="async () => ([] as SystemDict[])" @register="registerTable">
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新增
</a-button>
<a-button danger @click="handleDelete()">
<DeleteOutlined />
批量删除
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:edit-outlined',
label: '修改',
onClick: () => openModal(true, record),
// auth: 'user_edit',
},
{
icon: 'i-ant-design:setting-outlined',
label: '字典配置',
// auth: 'user_delete',
onClick: () => toPath(record),
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: '删除',
// auth: 'user_delete',
popConfirm: {
title: '确定要删除数据吗?',
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
]"
/>
</template>
</template>
</BasicTable>
<DictFormModal @register="registerModal" @success="reload()" />
</div>
</template>

61
src/views/system/dict/setting/SettingFormModal.vue

@ -0,0 +1,61 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { getFormSchema } from './data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createDict, detailDict, updateDict } from '@/api/system/dict'
import type { SystemDict } from '@/api/system/dict/types'
defineOptions({ name: 'SettingFormModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const isUpdate = ref(false)
const [registerForm, { setFieldsValue, validate }] = useForm({
name: 'dict-setting-form',
labelWidth: 100,
baseColProps: { span: 12 },
schemas: getFormSchema(),
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SystemDict) => {
isUpdate.value = !!data.id
console.log('isUpdate.value', isUpdate.value)
const res = await (isUpdate.value ? detailDict(data.id) : detailDict(data.parentId))
const newData = isUpdate.value ? { ...data, parentName: res.parentName } : { code: res.code, parentName: res.dictValue }
setFieldsValue({ ...newData })
})
async function handleSubmit() {
try {
const values = await validate<SystemDict>()
setModalProps({ confirmLoading: true })
await (isUpdate.value ? updateDict(values) : createDict(values))
closeModal()
emit('success')
useMessage().createMessage.success(t('common.saveSuccessText'))
}
catch {}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
width="55%"
:title="isUpdate ? t('action.edit') : t('action.create')"
:after-close="() => isUpdate = false"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>

132
src/views/system/dict/setting/data.ts

@ -0,0 +1,132 @@
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
import type { BasicColumn, FormSchema } from '@/components/Table'
import type { SystemDict } from '@/api/system/dict/types'
export const columns: BasicColumn<SystemDict>[] = [
{
title: '字典编号',
dataIndex: 'code',
width: 180,
customRender: ({ record }) => {
const { code } = record
return h(Tag, { color: 'blue' }, () => code)
},
},
{
title: '字典名称',
dataIndex: 'dictValue',
width: 180,
},
{
title: '字典键值',
dataIndex: 'dictKey',
width: 50,
},
{
title: '封存',
dataIndex: 'isSealed',
width: 50,
customRender: ({ record }) => {
const { isSealed } = record
const str = isSealed === 0 ? '否' : '是'
return h(Tag, { color: 'blue' }, () => str)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '字典编号',
field: 'code',
component: 'Input',
colProps: { span: 6 },
},
{
label: '字典名称',
field: 'dictValue',
component: 'Input',
colProps: { span: 6 },
},
]
export function getFormSchema(): FormSchema[] {
return [
{
field: 'id',
show: false,
component: 'Input',
},
{
field: 'isDeleted',
show: false,
component: 'Input',
defaultValue: 0,
},
{
field: 'parentId',
show: false,
component: 'Input',
defaultValue: '0',
},
{
label: '字典编号',
field: 'code',
required: true,
component: 'Input',
componentProps: {
disabled: true,
},
colProps: {
span: 24,
},
},
{
label: '字典名称',
field: 'dictValue',
required: true,
component: 'Input',
},
{
label: '上级字典',
field: 'parentName',
required: true,
component: 'Input',
componentProps: {
disabled: true,
},
},
{
label: '字典键值',
field: 'dictKey',
required: true,
component: 'Input',
},
{
label: '字典排序',
field: 'sort',
required: true,
component: 'InputNumber',
},
{
label: '封存',
field: 'isSealed',
required: true,
defaultValue: 0,
component: 'Switch',
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
checkedValue: 1,
unCheckedValue: 0,
},
},
{
label: '字典备注',
field: 'remark',
required: false,
component: 'Input',
},
]
}

120
src/views/system/dict/setting/index.vue

@ -0,0 +1,120 @@
<script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { useRoute } from 'vue-router'
import { columns, searchFormSchema } from './data'
import SettingFormModal from './SettingFormModal.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteDict, getDictChildList } from '@/api/system/dict'
import type { SystemDict } from '@/api/system/dict/types'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemDictSetting' })
const route = useRoute()
const parentId = route.params.id as string
const { createMessage, createConfirm } = useMessage()
const [registerModal, { openModal }] = useModal<Partial<SystemDict>>()
// const { hasPermission } = usePermission()
const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
api(apiParams) {
apiParams.parentId = parentId
return getDictChildList(apiParams)
},
columns,
formConfig: {
labelWidth: 80,
schemas: searchFormSchema,
},
rowKey: 'id',
rowSelection: {},
useSearchForm: true,
bordered: true,
canResize: false,
actionColumn: {
width: 100,
title: '操作',
dataIndex: 'action',
fixed: 'right',
// auth: ['user_edit', 'user_delete'],
},
})
async function handleDelete(id?: string) {
const ids = id ? [id] : (getSelectRowKeys() as string[])
if (!ids.length)
return createMessage.warning('请选择要删除的数据')
async function execute() {
try {
await deleteDict(ids)
createMessage.success('删除成功')
reload()
clearSelectedRowKeys()
}
catch {}
}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的字典?',
onOk: execute,
})
}
</script>
<template>
<div>
<BasicTable :api="async () => ([] as SystemDict[])" @register="registerTable">
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal(true, { parentId })">
<PlusOutlined />
新增
</a-button>
<a-button danger @click="handleDelete()">
<DeleteOutlined />
批量删除
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:edit-outlined',
label: '修改',
onClick: () => openModal(true, record),
// auth: 'user_edit',
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: '删除',
// auth: 'user_delete',
popConfirm: {
title: '确定要删除数据吗?',
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
]"
/>
</template>
</template>
</BasicTable>
<SettingFormModal @register="registerModal" @success="reload()" />
</div>
</template>

57
src/views/system/dictbiz/DictbizFormModal.vue

@ -0,0 +1,57 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { getFormSchema } from './data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createDictbiz, updateDictbiz } from '@/api/system/dictbiz'
import type { SystemDictbiz } from '@/api/system/dictbiz/types'
defineOptions({ name: 'DictbizFormModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const isUpdate = ref(false)
const [registerForm, { setFieldsValue, validate }] = useForm({
name: 'dict-form',
labelWidth: 100,
baseColProps: { span: 12 },
schemas: getFormSchema(),
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SystemDictbiz) => {
isUpdate.value = true
setFieldsValue({ ...data })
})
async function handleSubmit() {
try {
const values = await validate<SystemDictbiz>()
setModalProps({ confirmLoading: true })
await (isUpdate.value ? updateDictbiz(values) : createDictbiz(values))
closeModal()
emit('success')
useMessage().createMessage.success(t('common.saveSuccessText'))
}
catch {}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
width="55%"
:title="isUpdate ? t('action.edit') : t('action.create')"
:after-close="() => isUpdate = false"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>

133
src/views/system/dictbiz/data.ts

@ -0,0 +1,133 @@
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
import type { BasicColumn, FormSchema } from '@/components/Table'
import type { SystemDictbiz } from '@/api/system/dictbiz/types'
import { getTenantId } from '@/utils/auth'
export const columns: BasicColumn<SystemDictbiz>[] = [
{
title: '字典编号',
dataIndex: 'code',
width: 180,
customRender: ({ record }) => {
const { code } = record
return h(Tag, { color: 'blue' }, () => code)
},
},
{
title: '字典名称',
dataIndex: 'dictValue',
width: 180,
},
{
title: '字典排序',
dataIndex: 'sort',
width: 50,
},
{
title: '封存',
dataIndex: 'isSealed',
width: 50,
customRender: ({ record }) => {
const { isSealed } = record
const str = isSealed === 0 ? '否' : '是'
return h(Tag, { color: 'blue' }, () => str)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '字典编号',
field: 'code',
component: 'Input',
colProps: { span: 6 },
},
{
label: '字典名称',
field: 'dictValue',
component: 'Input',
colProps: { span: 6 },
},
]
export function getFormSchema(): FormSchema[] {
return [
{
field: 'tenantId',
show: false,
component: 'Input',
defaultValue: getTenantId(),
},
{
field: 'id',
show: false,
component: 'Input',
},
{
field: 'dictKey',
show: false,
component: 'Input',
defaultValue: '-1',
},
{
field: 'isDeleted',
show: false,
component: 'Input',
defaultValue: 0,
},
{
field: 'parentId',
show: false,
component: 'Input',
defaultValue: '0',
},
{
field: 'parentName',
show: false,
component: 'Input',
defaultValue: '顶级',
},
{
label: '字典编号',
field: 'code',
required: true,
component: 'Input',
colProps: {
span: 24,
},
},
{
label: '字典名称',
field: 'dictValue',
required: true,
component: 'Input',
},
{
label: '字典排序',
field: 'sort',
required: true,
component: 'InputNumber',
},
{
label: '封存',
field: 'isSealed',
required: true,
component: 'Switch',
defaultValue: 0,
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
checkedValue: 1,
unCheckedValue: 0,
},
},
{
label: '字典备注',
field: 'remark',
required: false,
component: 'Input',
},
]
}

130
src/views/system/dictbiz/index.vue

@ -0,0 +1,130 @@
<script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { useRouter } from 'vue-router'
import { columns, searchFormSchema } from './data'
import DictbizFormModal from './DictbizFormModal.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteDictbiz, getDictbizList } from '@/api/system/dictbiz'
import type { SystemDictbiz } from '@/api/system/dictbiz/types'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemDictbiz' })
const router = useRouter()
const { createMessage, createConfirm } = useMessage()
const [registerModal, { openModal }] = useModal<SystemDictbiz>()
// const { hasPermission } = usePermission()
const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
api(params) {
return getDictbizList(params)
},
columns,
formConfig: {
labelWidth: 80,
schemas: searchFormSchema,
},
rowKey: 'id',
rowSelection: {},
useSearchForm: true,
bordered: true,
canResize: false,
actionColumn: {
width: 160,
title: '操作',
dataIndex: 'action',
fixed: 'right',
// auth: ['user_edit', 'user_delete'],
},
})
async function handleDelete(id?: string) {
const ids = id ? [id] : (getSelectRowKeys() as string[])
if (!ids.length)
return createMessage.warning('请选择要删除的数据')
async function execute() {
try {
await deleteDictbiz(ids)
createMessage.success('删除成功')
reload()
clearSelectedRowKeys()
}
catch {}
}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的字典?',
onOk: execute,
})
}
function toPath(data: SystemDictbiz) {
router.push({
path: `/system/dictbiz/setting/${data.id}`,
})
}
</script>
<template>
<div>
<BasicTable :api="async () => ([] as SystemDictbiz[])" @register="registerTable">
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新增
</a-button>
<a-button danger @click="handleDelete()">
<DeleteOutlined />
批量删除
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:edit-outlined',
label: '修改',
onClick: () => openModal(true, record),
// auth: 'user_edit',
},
{
icon: 'i-ant-design:setting-outlined',
label: '字典配置',
// auth: 'user_delete',
onClick: () => toPath(record),
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: '删除',
// auth: 'user_delete',
popConfirm: {
title: '确定要删除数据吗?',
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
]"
/>
</template>
</template>
</BasicTable>
<DictbizFormModal @register="registerModal" @success="reload()" />
</div>
</template>

61
src/views/system/dictbiz/setting/SettingFormModal.vue

@ -0,0 +1,61 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { getFormSchema } from './data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createDictbiz, detailDictbiz, updateDictbiz } from '@/api/system/dictbiz'
import type { SystemDict } from '@/api/system/dict/types'
defineOptions({ name: 'SettingFormModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const isUpdate = ref(false)
const [registerForm, { setFieldsValue, validate }] = useForm({
name: 'dict-setting-form',
labelWidth: 100,
baseColProps: { span: 12 },
schemas: getFormSchema(),
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SystemDict) => {
isUpdate.value = !!data.id
console.log('isUpdate.value', isUpdate.value)
const res = await (isUpdate.value ? detailDictbiz(data.id) : detailDictbiz(data.parentId))
const newData = isUpdate.value ? { ...data, parentName: res.parentName } : { code: res.code, parentName: res.dictValue }
setFieldsValue({ ...newData })
})
async function handleSubmit() {
try {
const values = await validate<SystemDict>()
setModalProps({ confirmLoading: true })
await (isUpdate.value ? updateDictbiz(values) : createDictbiz(values))
closeModal()
emit('success')
useMessage().createMessage.success(t('common.saveSuccessText'))
}
catch {}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
width="55%"
:title="isUpdate ? t('action.edit') : t('action.create')"
:after-close="() => isUpdate = false"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>

139
src/views/system/dictbiz/setting/data.ts

@ -0,0 +1,139 @@
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
import type { BasicColumn, FormSchema } from '@/components/Table'
import type { SystemDictbiz } from '@/api/system/dictbiz/types'
import { getTenantId } from '@/utils/auth'
export const columns: BasicColumn<SystemDictbiz>[] = [
{
title: '字典编号',
dataIndex: 'code',
width: 180,
customRender: ({ record }) => {
const { code } = record
return h(Tag, { color: 'blue' }, () => code)
},
},
{
title: '字典名称',
dataIndex: 'dictValue',
width: 180,
},
{
title: '字典键值',
dataIndex: 'dictKey',
width: 50,
},
{
title: '封存',
dataIndex: 'isSealed',
width: 50,
customRender: ({ record }) => {
const { isSealed } = record
const str = isSealed === 0 ? '否' : '是'
return h(Tag, { color: 'blue' }, () => str)
},
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '字典编号',
field: 'code',
component: 'Input',
colProps: { span: 6 },
},
{
label: '字典名称',
field: 'dictValue',
component: 'Input',
colProps: { span: 6 },
},
]
export function getFormSchema(): FormSchema[] {
return [
{
field: 'tenantId',
show: false,
component: 'Input',
defaultValue: getTenantId(),
},
{
field: 'id',
show: false,
component: 'Input',
},
{
field: 'isDeleted',
show: false,
component: 'Input',
defaultValue: 0,
},
{
field: 'parentId',
show: false,
component: 'Input',
defaultValue: '0',
},
{
label: '字典编号',
field: 'code',
required: true,
component: 'Input',
componentProps: {
disabled: true,
},
colProps: {
span: 24,
},
},
{
label: '字典名称',
field: 'dictValue',
required: true,
component: 'Input',
},
{
label: '上级字典',
field: 'parentName',
required: true,
component: 'Input',
componentProps: {
disabled: true,
},
},
{
label: '字典键值',
field: 'dictKey',
required: true,
component: 'Input',
},
{
label: '字典排序',
field: 'sort',
required: true,
component: 'InputNumber',
},
{
label: '封存',
field: 'isSealed',
required: true,
defaultValue: 0,
component: 'Switch',
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
checkedValue: 1,
unCheckedValue: 0,
},
},
{
label: '字典备注',
field: 'remark',
required: false,
component: 'Input',
},
]
}

120
src/views/system/dictbiz/setting/index.vue

@ -0,0 +1,120 @@
<script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { useRoute } from 'vue-router'
import { columns, searchFormSchema } from './data'
import SettingFormModal from './SettingFormModal.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteDictbiz, getDictbizChildList } from '@/api/system/dictbiz'
import type { SystemDictbiz } from '@/api/system/dictbiz/types'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemDictbizSetting' })
const route = useRoute()
const parentId = route.params.id as string
const { createMessage, createConfirm } = useMessage()
const [registerModal, { openModal }] = useModal<Partial<SystemDictbiz>>()
// const { hasPermission } = usePermission()
const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
api(apiParams) {
apiParams.parentId = parentId
return getDictbizChildList(apiParams)
},
columns,
formConfig: {
labelWidth: 80,
schemas: searchFormSchema,
},
rowKey: 'id',
rowSelection: {},
useSearchForm: true,
bordered: true,
canResize: false,
actionColumn: {
width: 100,
title: '操作',
dataIndex: 'action',
fixed: 'right',
// auth: ['user_edit', 'user_delete'],
},
})
async function handleDelete(id?: string) {
const ids = id ? [id] : (getSelectRowKeys() as string[])
if (!ids.length)
return createMessage.warning('请选择要删除的数据')
async function execute() {
try {
await deleteDictbiz(ids)
createMessage.success('删除成功')
reload()
clearSelectedRowKeys()
}
catch {}
}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的字典?',
onOk: execute,
})
}
</script>
<template>
<div>
<BasicTable :api="async () => ([] as SystemDictbiz[])" @register="registerTable">
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal(true, { parentId })">
<PlusOutlined />
新增
</a-button>
<a-button danger @click="handleDelete()">
<DeleteOutlined />
批量删除
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:edit-outlined',
label: '修改',
onClick: () => openModal(true, record),
// auth: 'user_edit',
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: '删除',
// auth: 'user_delete',
popConfirm: {
title: '确定要删除数据吗?',
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
]"
/>
</template>
</template>
</BasicTable>
<SettingFormModal @register="registerModal" @success="reload()" />
</div>
</template>

5
src/views/system/menu/MenuFormModal.vue

@ -19,8 +19,9 @@ const [registerForm, { setFieldsValue, validate }] = useForm({
}) })
const isUpdate = ref(false) const isUpdate = ref(false)
const [registerModal, { setModalProps, closeModal }] = useModalInner((data: MenuItem) => { const [registerModal, { setModalProps, closeModal }] = useModalInner((data: Partial<MenuItem>) => {
isUpdate.value = true isUpdate.value = !!data.id
console.log(data)
setFieldsValue({ setFieldsValue({
...data, ...data,
parentId: data.parentId === '0' ? undefined : data.parentId, parentId: data.parentId === '0' ? undefined : data.parentId,

124
src/views/system/menu/data.tsx → src/views/system/menu/data.ts

@ -1,3 +1,4 @@
import { h } from 'vue'
import { Tag } from 'ant-design-vue' import { Tag } from 'ant-design-vue'
import { getMenuListWithoutButtons } from '@/api/system/menu' import { getMenuListWithoutButtons } from '@/api/system/menu'
import type { BasicColumn, FormSchema } from '@/components/Table' import type { BasicColumn, FormSchema } from '@/components/Table'
@ -7,17 +8,17 @@ export const columns: BasicColumn[] = [
{ {
title: '菜单名称', title: '菜单名称',
dataIndex: 'name', dataIndex: 'name',
width: 250,
align: 'left', align: 'left',
}, },
{ {
title: '菜单类型', title: '菜单类型',
dataIndex: 'type', dataIndex: 'category',
width: 80, width: 80,
customRender: ({ record }) => { customRender: ({ record }) => {
const { type } = record const { category } = record
let name = '' let name = ''
switch (type) {
switch (category) {
case SystemMenuTypeEnum.DIR: case SystemMenuTypeEnum.DIR:
name = '目录' name = '目录'
break break
@ -28,15 +29,15 @@ export const columns: BasicColumn[] = [
name = '按钮' name = '按钮'
} }
return <Tag>{ name }</Tag> return h(Tag, () => name)
}, },
}, },
{ {
title: '图标', title: '图标',
dataIndex: 'icon', dataIndex: 'source',
width: 60, width: 60,
customRender: ({ record }) => { customRender: ({ record }) => {
return record.icon && <span class={record.icon}></span> return record.source && h('span', { class: record.source })
}, },
}, },
{ {
@ -45,20 +46,22 @@ export const columns: BasicColumn[] = [
width: 60, width: 60,
}, },
{ {
title: '权限标识', title: '菜单编号',
dataIndex: 'code', dataIndex: 'code',
width: 140,
}, },
{ {
title: '路由路径', title: '菜单别名',
dataIndex: 'path', dataIndex: 'alias',
width: 140,
}, },
{ {
title: '组件路径', title: '路由路径',
dataIndex: 'component', dataIndex: 'path',
width: 140,
}, },
// {
// title: '组件路径',
// dataIndex: 'component',
// width: 140,
// },
] ]
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
@ -66,13 +69,19 @@ export const searchFormSchema: FormSchema[] = [
label: '菜单名称', label: '菜单名称',
field: 'name', field: 'name',
component: 'Input', component: 'Input',
colProps: { span: 8 }, colProps: { span: 6 },
}, },
{ {
label: '权限标识', label: '菜单编号',
field: 'code', field: 'code',
component: 'Input', component: 'Input',
colProps: { span: 8 }, colProps: { span: 6 },
},
{
label: '菜单别名',
field: 'alias',
component: 'Input',
colProps: { span: 6 },
}, },
] ]
@ -94,16 +103,16 @@ export const formSchema: FormSchema[] = [
}, },
{ {
label: '菜单类型', label: '菜单类型',
field: 'type', field: 'category',
required: true, required: true,
defaultValue: 1, defaultValue: 1,
component: 'RadioButtonGroup', component: 'RadioButtonGroup',
componentProps: { componentProps: {
options: [ options: [
{ // {
label: '目录', // label: '目录',
value: SystemMenuTypeEnum.DIR, // value: SystemMenuTypeEnum.DIR,
}, // },
{ {
label: '菜单', label: '菜单',
value: SystemMenuTypeEnum.MENU, value: SystemMenuTypeEnum.MENU,
@ -122,19 +131,6 @@ export const formSchema: FormSchema[] = [
required: true, required: true,
component: 'Input', component: 'Input',
}, },
{
label: '菜单图标',
field: 'icon',
component: 'IconPicker',
ifShow: ({ values }) => values.type !== SystemMenuTypeEnum.BUTTON,
},
{
label: '显示排序',
field: 'sort',
required: true,
component: 'InputNumber',
defaultValue: 0,
},
{ {
label: '路由地址', label: '路由地址',
field: 'path', field: 'path',
@ -144,28 +140,48 @@ export const formSchema: FormSchema[] = [
ifShow: ({ values }) => values.type !== SystemMenuTypeEnum.BUTTON, ifShow: ({ values }) => values.type !== SystemMenuTypeEnum.BUTTON,
}, },
{ {
label: '组件路径', label: '菜单图标',
field: 'component', field: 'source',
// required: true,
component: 'IconPicker',
ifShow: ({ values }) => values.type !== SystemMenuTypeEnum.BUTTON,
},
{
label: '菜单编号',
field: 'code',
required: true, required: true,
component: 'Input', component: 'Input',
helpMessage: '例如:system/user/index,不定义时使用 path 字段', helpMessage: '输入一个不重复的标识',
ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
}, },
{ {
label: '组件名称', label: '菜单别名',
field: 'componentName', field: 'alias',
required: ({ values }) => values.keepAlive, required: true,
component: 'Input', component: 'Input',
helpMessage: '例如:SystemName,当开启缓存时它是必传的',
ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
}, },
{ {
label: '权限标识', label: '显示排序',
field: 'code', field: 'sort',
required: true, required: true,
component: 'Input', component: 'InputNumber',
helpMessage: '输入一个不重复的标识', defaultValue: 0,
}, },
// {
// label: '组件路径',
// field: 'component',
// required: true,
// component: 'Input',
// helpMessage: '例如:system/user/index,不定义时使用 path 字段',
// ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
// },
// {
// label: '组件名称',
// field: 'componentName',
// required: ({ values }) => values.keepAlive,
// component: 'Input',
// helpMessage: '例如:SystemName,当开启缓存时它是必传的',
// ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
// },
{ {
label: '是否在菜单栏显示', label: '是否在菜单栏显示',
field: 'visible', field: 'visible',
@ -182,15 +198,15 @@ export const formSchema: FormSchema[] = [
}, },
{ {
label: '是否缓存', label: '是否缓存',
field: 'keepAlive', field: 'isOpen',
component: 'Switch', component: 'Switch',
componentProps: { componentProps: {
checkedChildren: '缓存', checkedChildren: '缓存',
unCheckedChildren: '不缓存', unCheckedChildren: '不缓存',
checkedValue: 1, checkedValue: 2,
unCheckedValue: 0, unCheckedValue: 1,
}, },
helpMessage: '选择缓存时,则会被 `keep-alive` 缓存,同时必须指定组件的 Name 值', // helpMessage: '选择缓存时,则会被 `keep-alive` 缓存,同时必须指定组件的 Name 值',
ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU, // ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
}, },
] ]

74
src/views/system/menu/index.vue

@ -1,21 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue' import { Space } from 'ant-design-vue'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import MenuFormModal from './MenuFormModal.vue' import MenuFormModal from './MenuFormModal.vue'
import { columns, searchFormSchema } from './data' import { columns, searchFormSchema } from './data'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal' import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table' import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteMenu, getMenuList } from '@/api/system/menu' import { deleteMenu, getMenuList } from '@/api/system/menu'
import { usePermission } from '@/hooks/web/usePermission'
// import { usePermission } from '@/hooks/web/usePermission'
import type { MenuItem } from '@/api/system/menu/types' import type { MenuItem } from '@/api/system/menu/types'
defineOptions({ name: 'SystemMenu' }) defineOptions({ name: 'SystemMenu' })
const [registerModal, { openModal }] = useModal<MenuItem>() const [registerModal, { openModal }] = useModal<Partial<MenuItem>>()
const { hasPermission } = usePermission() // const { hasPermission } = usePermission()
const [register, { reload }] = useTable<MenuItem>({ const [register, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable<MenuItem>({
columns, columns,
api: getMenuList, api: getMenuList,
rowKey: 'id', rowKey: 'id',
@ -29,34 +31,59 @@ const [register, { reload }] = useTable<MenuItem>({
canResize: false, canResize: false,
isTreeTable: true, isTreeTable: true,
pagination: false, pagination: false,
rowSelection: {},
actionColumn: { actionColumn: {
width: 140, width: 240,
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
fixed: 'right', fixed: 'right',
auth: ['menu_edit', 'menu_delete'], // auth: ['menu_edit', 'menu_delete'],
}, },
}) })
const { createMessage } = useMessage() const { createMessage, createConfirm } = useMessage()
async function handleDelete(id: string) { function handleDelete(id?: string) {
try { const ids = id ? [id] : (getSelectRowKeys() as string[])
await deleteMenu(id)
createMessage.success('删除成功') if (!ids.length)
reload() return createMessage.warning('请先选择数据')
async function execute() {
try {
await deleteMenu(ids)
createMessage.success('删除成功')
await reload()
clearSelectedRowKeys()
}
catch {}
} }
catch {}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的菜单?',
onOk: execute,
})
} }
</script> </script>
<template> <template>
<div> <div>
<BasicTable :api="async () => ([] as MenuItem[])" @register="register"> <BasicTable :api="async () => ([] as MenuItem[])" @register="register">
<template v-if="hasPermission('menu_add')" #tableTitle> <!-- v-if="hasPermission('menu_add')" -->
<a-button type="primary" @click="openModal"> <template #tableTitle>
<PlusOutlined /> <Space>
新增 <a-button type="primary" @click="openModal">
</a-button> <PlusOutlined />
新增
</a-button>
<a-button danger @click="handleDelete()">
<DeleteOutlined />
批量删除
</a-button>
</Space>
</template> </template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
@ -66,20 +93,25 @@ async function handleDelete(id: string) {
{ {
icon: 'i-ant-design:edit-outlined', icon: 'i-ant-design:edit-outlined',
label: '修改', label: '修改',
auth: 'menu_edit', // auth: 'menu_edit',
onClick: () => openModal(true, record), onClick: () => openModal(true, record),
}, },
{ {
icon: 'i-ant-design:delete-outlined', icon: 'i-ant-design:delete-outlined',
label: '删除', label: '删除',
danger: true, danger: true,
auth: 'menu_delete', // auth: 'menu_delete',
popConfirm: { popConfirm: {
title: '确定要删除数据吗?', title: '确定要删除数据吗?',
placement: 'left', placement: 'left',
confirm: () => handleDelete(record.id), confirm: () => handleDelete(record.id),
}, },
}, },
{
icon: 'i-ant-design:plus-outlined',
label: '新增子项',
onClick: () => openModal(true, { parentId: record.id }),
},
]" ]"
/> />
</template> </template>

12
src/views/system/param/data.ts

@ -13,6 +13,10 @@ export const columns: BasicColumn[] = [
title: '参数键值', title: '参数键值',
dataIndex: 'paramValue', dataIndex: 'paramValue',
}, },
{
title: '备注',
dataIndex: 'remark',
},
] ]
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
@ -57,4 +61,12 @@ export const formSchema: FormSchema[] = [
rows: 8, rows: 8,
}, },
}, },
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
componentProps: {
rows: 8,
},
},
] ]

64
src/views/system/param/index.vue

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue' import { PlusOutlined } from '@ant-design/icons-vue'
import ParamFormModal from './ParamFormModal.vue' import ParamFormModal from './ParamFormModal.vue'
import { columns, searchFormSchema } from './data' import { columns, searchFormSchema } from './data'
@ -7,52 +8,79 @@ import { deleteParam, getParamList } from '@/api/system/param'
import type { Param } from '@/api/system/param/types' import type { Param } from '@/api/system/param/types'
import { useModal } from '@/components/Modal' import { useModal } from '@/components/Modal'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { usePermission } from '@/hooks/web/usePermission'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemParams' }) defineOptions({ name: 'SystemParams' })
const [registerModal, { openModal }] = useModal<Param>() const [registerModal, { openModal }] = useModal<Param>()
const [registerTable, { reload }] = useTable({ const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable<Param>({
api: getParamList, api: getParamList,
rowKey: 'id',
columns, columns,
formConfig: { formConfig: {
labelWidth: 100, labelWidth: 100,
schemas: searchFormSchema, schemas: searchFormSchema,
actionColOptions: { span: 4 }, actionColOptions: { span: 12 },
}, },
bordered: true, bordered: true,
canResize: false, canResize: false,
useSearchForm: true, useSearchForm: true,
rowSelection: {},
actionColumn: { actionColumn: {
width: 160, width: 160,
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
fixed: 'right', fixed: 'right',
auth: ['param_delete', 'param_edit'], // auth: ['param_delete', 'param_edit'],
}, },
}) })
async function handleDelete(id: string) { const { createMessage, createConfirm } = useMessage()
try { async function handleDelete(id?: string) {
await deleteParam(id) const ids = id ? [id] : (getSelectRowKeys() as string[])
useMessage().createMessage.success('删除成功!')
reload() if (!ids.length)
return createMessage.warning('请先选择数据')
async function execute() {
try {
await deleteParam(ids)
createMessage.success('删除成功!')
await reload()
clearSelectedRowKeys()
}
catch {}
} }
catch {}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的菜单?',
onOk: execute,
})
} }
const { hasPermission } = usePermission() // const { hasPermission } = usePermission()
</script> </script>
<template> <template>
<div> <div>
<BasicTable :api="async () => ([] as Param[])" @register="registerTable"> <BasicTable :api="async () => ([] as Param[])" @register="registerTable">
<template v-if="hasPermission('param_add')" #tableTitle> <!-- v-if="hasPermission('param_add')" -->
<a-button type="primary" @click="openModal"> <template #tableTitle>
<PlusOutlined /> <Space>
新建 <a-button type="primary" @click="openModal">
</a-button> <PlusOutlined />
新建
</a-button>
<a-button danger @click="handleDelete()">
批量删除
</a-button>
</Space>
</template> </template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
@ -62,14 +90,14 @@ const { hasPermission } = usePermission()
{ {
icon: 'i-ant-design:edit-outlined', icon: 'i-ant-design:edit-outlined',
label: '编辑', label: '编辑',
auth: 'param_edit', // auth: 'param_edit',
onClick: () => openModal(true, record), onClick: () => openModal(true, record),
}, },
{ {
icon: 'i-ant-design:delete-outlined', icon: 'i-ant-design:delete-outlined',
label: '删除', label: '删除',
danger: true, danger: true,
auth: 'param_delete', // auth: 'param_delete',
popConfirm: { popConfirm: {
title: '确定要删除数据吗?', title: '确定要删除数据吗?',
placement: 'left', placement: 'left',

54
src/views/system/post/DedailModal.vue

@ -0,0 +1,54 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { BasicModal, useModalInner } from '@/components/Modal'
import { Description } from '@/components/Description'
import type { DescItem } from '@/components/Description'
import type { SystemPost } from '@/api/system/post/types'
defineOptions({ name: 'DetailModal' })
const baseDetail = ref({})
const [registerModal] = useModalInner(async (data: SystemPost) => {
baseDetail.value = data
})
const baseSchema: DescItem[] = [
{
field: 'tenantName',
label: '所属租户',
span: 24,
},
{
field: 'categoryName',
label: '岗位类型',
},
{
field: 'postCode',
label: '岗位编号',
},
{
field: 'postName',
label: '岗位名称',
},
{
field: 'sort',
label: '岗位排序',
},
{
field: 'remark',
label: '岗位描述',
},
]
</script>
<template>
<BasicModal
width="900px"
title="查看"
:show-cancel-btn="false"
:show-ok-btn="false"
@register="registerModal"
>
<Description :data="baseDetail" :schema="baseSchema" :column="2" />
</BasicModal>
</template>

57
src/views/system/post/PostFormModal.vue

@ -0,0 +1,57 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { getFormSchema } from './data'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { createPost, updatePost } from '@/api/system/post'
import type { SystemPost } from '@/api/system/post/types'
defineOptions({ name: 'DictFormModal' })
const emit = defineEmits(['success', 'register'])
const { t } = useI18n()
const isUpdate = ref(false)
const [registerForm, { setFieldsValue, validate }] = useForm({
name: 'post-form',
labelWidth: 100,
baseColProps: { span: 12 },
schemas: getFormSchema(),
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SystemPost) => {
isUpdate.value = true
setFieldsValue({ ...data })
})
async function handleSubmit() {
try {
const values = await validate<SystemPost>()
setModalProps({ confirmLoading: true })
await (isUpdate.value ? updatePost(values) : createPost(values))
closeModal()
emit('success')
useMessage().createMessage.success(t('common.saveSuccessText'))
}
catch {}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
width="55%"
:title="isUpdate ? t('action.edit') : t('action.create')"
:after-close="() => isUpdate = false"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>

121
src/views/system/post/data.ts

@ -0,0 +1,121 @@
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
import type { BasicColumn, FormSchema } from '@/components/Table'
import type { SystemPost } from '@/api/system/post/types'
import { getSystemDictionary } from '@/api/base/common'
import { getTenantId } from '@/utils/auth'
export const columns: BasicColumn<SystemPost>[] = [
{
title: '所属租户',
dataIndex: 'tenantName',
},
{
title: '岗位类型',
dataIndex: 'categoryName',
customRender: ({ record }) => {
const { categoryName } = record
return h(Tag, { color: 'blue' }, () => categoryName)
},
},
{
title: '岗位编号',
dataIndex: 'postCode',
},
{
title: '岗位名称',
dataIndex: 'postName',
},
{
title: '岗位排序',
dataIndex: 'sort',
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '岗位类型',
field: 'category',
component: 'ApiSelect',
componentProps: {
api: () => getSystemDictionary('post_category'),
},
colProps: { span: 6 },
},
{
label: '岗位编号',
field: 'postCode',
component: 'Input',
colProps: { span: 6 },
},
{
label: '岗位名称',
field: 'postName',
component: 'Input',
colProps: { span: 6 },
},
]
export function getFormSchema(): FormSchema[] {
return [
{
field: 'tenantId',
show: false,
component: 'Input',
defaultValue: getTenantId(),
},
{
field: 'id',
show: false,
component: 'Input',
},
//
{
label: '岗位类型',
field: 'category',
required: true,
component: 'ApiSelect',
componentProps: {
api: async () => {
const res = await getSystemDictionary('post_category')
res.forEach((item) => {
item.dictKey = Number(item.dictKey)
})
return res
},
valueField: 'dictKey',
labelField: 'dictValue',
},
},
{
label: '岗位编号',
field: 'postCode',
required: true,
component: 'Input',
},
{
label: '岗位名称',
field: 'postName',
required: true,
component: 'Input',
},
{
label: '岗位排序',
field: 'sort',
required: true,
component: 'InputNumber',
},
{
label: '岗位描述',
field: 'remark',
required: false,
component: 'InputTextArea',
colProps: {
span: 24,
},
},
]
}

142
src/views/system/post/index.vue

@ -0,0 +1,142 @@
<script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { columns, searchFormSchema } from './data'
import PostFormModal from './PostFormModal.vue'
import DedailModal from './DedailModal.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deletePost, getPostList } from '@/api/system/post'
import type { SystemPost } from '@/api/system/post/types'
import { getAllTenants } from '@/api/system/tenant'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemPost' })
const { createMessage, createConfirm } = useMessage()
const [registerModal, { openModal }] = useModal<SystemPost>()
const [registerDetailModal, { openModal: openDetailModal }] = useModal<SystemPost>()
// const { hasPermission } = usePermission()
const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
async api(params) {
const res = await getAllTenants()
const list = await getPostList(params)
list.records = list.records.map((item) => {
const obj = res.find((tenant: SystemPost) => tenant.tenantId === item.tenantId)
return {
...item,
tenantName: obj?.tenantName,
}
})
return list
},
beforeFetch: (formData) => {
for (const key in formData) {
if (formData[key] === '')
formData[key] = undefined
}
return formData
},
columns,
formConfig: {
labelWidth: 80,
schemas: searchFormSchema,
},
rowKey: 'id',
rowSelection: {},
useSearchForm: true,
bordered: true,
canResize: false,
actionColumn: {
width: 220,
title: '操作',
dataIndex: 'action',
fixed: 'right',
// auth: ['user_edit', 'user_delete'],
},
})
async function handleDelete(id?: string) {
const ids = id ? [id] : (getSelectRowKeys() as string[])
if (!ids.length)
return createMessage.warning('请选择要删除的数据')
async function execute() {
try {
await deletePost(ids)
createMessage.success('删除成功')
reload()
clearSelectedRowKeys()
}
catch {}
}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的字典?',
onOk: execute,
})
}
</script>
<template>
<div>
<BasicTable :api="async () => ([] as SystemPost[])" @register="registerTable">
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新增
</a-button>
<a-button danger @click="handleDelete()">
<DeleteOutlined />
批量删除
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:eye-outlined',
label: '查看',
// auth: 'user_delete',
onClick: () => openDetailModal(true, record),
},
{
icon: 'i-ant-design:edit-outlined',
label: '修改',
onClick: () => openModal(true, record),
// auth: 'user_edit',
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: '删除',
// auth: 'user_delete',
popConfirm: {
title: '确定要删除数据吗?',
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
]"
/>
</template>
</template>
</BasicTable>
<PostFormModal @register="registerModal" @success="reload()" />
<DedailModal @register="registerDetailModal" @success="reload()" />
</div>
</template>

38
src/views/system/region/composables/useRegionActions.ts

@ -0,0 +1,38 @@
import { computed, ref } from 'vue'
import { exportRegion } from '@/api/system/region'
import { useModal } from '@/components/Modal'
import { useMessage } from '@/hooks/web/useMessage'
import { noop } from '@/utils'
import { downloadByData } from '@/utils/file/download'
export function useRegionActions() {
function handleExport() {
useMessage().createConfirm({
title: '确定要导出行政区划数据吗?',
iconType: 'warning',
onOk() {
exportRegion()
.then((res) => {
downloadByData(res, '行政区划.xlsx')
})
.catch(noop)
},
})
}
const [registerImportModal, { openModal: openImportModal }] = useModal()
const isCovered = ref(0)
const importTemplateUrl = '/baymax-system/region/export-template'
const importUrl = computed(() => {
return `/baymax-system/region/import-region?isCovered=${isCovered.value}`
})
return {
handleExport,
registerImportModal,
openImportModal,
importUrl,
importTemplateUrl,
isCovered,
}
}

126
src/views/system/region/composables/useRegionForm.ts

@ -0,0 +1,126 @@
import { getSystemDictionary } from '@/api/base/common'
import { deleteRegion, updateRegion } from '@/api/system/region'
import type { Region } from '@/api/system/region/types'
import { useForm } from '@/components/Form'
import { useMessage } from '@/hooks/web/useMessage'
import { noop } from '@/utils'
export function useRegionForm() {
const [registerForm, methods] = useForm({
schemas: [
{
field: 'parentCode',
fields: [
'id',
'parentId',
'code',
'ancestors',
'cityCode',
'cityName',
'districtCode',
'districtName',
'parentName',
'provinceCode',
'provinceName',
'townCode',
'townName',
'villageCode',
'villageName',
],
label: '父区划编号',
component: 'Input',
render({ values }) {
return values.parentCode
},
},
{
field: 'parentName',
label: '父区划名称',
component: 'Input',
render({ values }) {
return values.parentName
},
},
{
field: 'subCode',
label: '区划编号',
required: true,
component: 'Input',
componentProps({ formModel }) {
return {
addonBefore: formModel.parentCode,
}
},
},
{
field: 'name',
label: '区划名称',
required: true,
component: 'Input',
},
{
field: 'regionLevel',
label: '区划等级',
required: true,
component: 'ApiRadioGroup',
componentProps: {
api: () => getSystemDictionary('region'),
},
},
{
field: 'sort',
label: '区划排序',
required: true,
component: 'InputNumber',
},
{
field: 'remark',
label: '区划备注',
component: 'InputTextArea',
},
],
labelWidth: 80,
baseColProps: {
span: 24,
style: {
textAlign: 'left',
},
},
actionColOptions: {
span: 24,
style: {
textAlign: 'center',
},
},
submitButtonOptions: {
text: '保存',
},
})
function handleSubmit(data: Region) {
data = { ...data }
const TOP_CODE = '00'
data.parentCode = data.parentCode === TOP_CODE ? '' : data.parentCode
data.code = data.parentCode + data.subCode
updateRegion(data)
.then(() => {
useMessage().createMessage.success('保存成功')
})
.catch(noop)
}
function handleDelete(id: string) {
deleteRegion(id)
.then(() => {
useMessage().createMessage.success('删除成功')
})
.catch(noop)
}
return {
registerForm,
...methods,
handleSubmit,
handleDelete,
}
}

39
src/views/system/region/composables/useRegionList.ts

@ -0,0 +1,39 @@
import { ref } from 'vue'
import { lazyGetRegionList } from '@/api/system/region'
import type { Region } from '@/api/system/region/types'
import type { TreeItem } from '@/components/Tree'
export function useRegionList() {
const regionList = ref<Region[]>([])
async function lazyRegionList(node: TreeItem) {
return lazyGetRegionList(node.key.toString())
.then((res) => {
return res.map((item) => {
return {
...item,
isLeaf: !item.hasChildren,
}
})
})
.catch(() => [])
}
const TOP_KEY = '00'
lazyRegionList({ key: TOP_KEY })
.then(res => regionList.value = res)
const selectedRegionKey = ref<string>()
return {
lazyRegionList,
regionList,
selectedRegionKey,
setSelectedRegion(key?: string) {
selectedRegionKey.value = key
},
reload() {
lazyRegionList({ key: TOP_KEY })
},
}
}

165
src/views/system/region/index.vue

@ -0,0 +1,165 @@
<script setup lang="ts">
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons-vue'
import { Card, Empty, Popconfirm, Space, Switch } from 'ant-design-vue'
import { ref } from 'vue'
import { useRegionList } from './composables/useRegionList'
import { useRegionForm } from './composables/useRegionForm'
import { useRegionActions } from './composables/useRegionActions'
import { BasicTree } from '@/components/Tree'
import { BasicForm } from '@/components/Form'
import { getRegionDetail } from '@/api/system/region'
import { ImportModal } from '@/components/ImportModal'
import { noop } from '@/utils'
const {
regionList,
selectedRegionKey,
lazyRegionList,
setSelectedRegion,
reload,
} = useRegionList()
const {
registerForm,
setFieldsValue,
clearValidate,
handleSubmit,
handleDelete,
} = useRegionForm()
const cardTitle = ref('')
function onSelectRegion(_, { nativeEvent, node }: { nativeEvent: PointerEvent, node: any }) {
// + Icon nodeName span
const isCreateChild = (nativeEvent.target as HTMLElement).nodeName === 'SPAN'
cardTitle.value = isCreateChild ? '新增子节点' : '编辑节点'
const key = node.key
setSelectedRegion(key)
if (!key)
return
getRegionDetail(key)
.then((res) => {
let value
if (isCreateChild) {
value = {
...res,
parentCode: res.code,
parentName: res.name,
code: undefined,
name: undefined,
sort: undefined,
remark: undefined,
subCode: undefined,
regionLevel: +res.regionLevel === 5 ? 5 : (+res.regionLevel + 1).toString(),
}
}
else {
value = {
...res,
subCode: res.code.replace(res.parentCode, ''),
regionLevel: res.regionLevel.toString(),
}
}
setFieldsValue(value)
clearValidate()
})
.catch(noop)
}
const {
handleExport,
registerImportModal,
openImportModal,
importUrl,
importTemplateUrl,
isCovered,
} = useRegionActions()
</script>
<template>
<div p="12px" flex="~ gap-12px">
<Card min-w="20%">
<div h="[calc(100vh-155px)]">
<BasicTree
:selected-keys="selectedRegionKey ? [selectedRegionKey] : []"
search
:tree-data="regionList"
:load-data="lazyRegionList"
@select="onSelectRegion"
>
<template #headerAction>
<Space>
<a-button shape="circle" title="导入" @click="openImportModal">
<UploadOutlined />
</a-button>
<a-button shape="circle" title="导出" @click="handleExport">
<DownloadOutlined />
</a-button>
</Space>
</template>
<template #title="{ title, hasChildren, key }">
<div flex="~ items-center justify-between" py="8px" px="5px" box-border w-full>
<div flex="1" width="0" truncate :title="title">
{{ title }}
</div>
<Space>
<span
class="i-ant-design:plus-outlined"
title="添加子节点"
data-create-child
/>
<Popconfirm
title="是否要删除数据?"
:class="[hasChildren ? 'text-gray-300 cursor-not-allowed' : '']"
:disabled="hasChildren"
@click.stop
@confirm="handleDelete(key)"
>
<span class="i-ant-design:delete-outlined" :title="hasChildren ? '存在子节点, 无法删除' : '删除数据'" />
</Popconfirm>
</Space>
</div>
</template>
</BasicTree>
</div>
</Card>
<div w-0 flex-1>
<Card :title="cardTitle">
<Empty v-if="!selectedRegionKey" description="请先在左侧选择一个节点" />
<BasicForm v-else @register="registerForm" @submit="handleSubmit" />
</Card>
</div>
<ImportModal
:upload-url="importUrl"
:template-url="importTemplateUrl"
hint="请上传 .xls, .xlsx 标准格式文件"
@register="registerImportModal"
@success="reload"
>
<div>
<span mr="10px">数据覆盖</span>
<Switch v-model:checked="isCovered" :checked-value="1" :un-checked-value="0" />
</div>
</ImportModal>
</div>
</template>
<style scoped lang="less">
:deep(.ant-tree-treenode) {
width: 100%;
height: 40px;
}
:deep(.ant-tree-node-content-wrapper) {
flex: 1;
width: 0;
height: 40px;
}
:deep(.ant-tree-switcher-icon) {
margin-top: 15px;
}
</style>

37
src/views/system/role/RoleMenuModal.vue

@ -1,33 +1,42 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import { useAsyncState } from '@vueuse/core'
import { BasicModal, useModalInner } from '@/components/Modal' import { BasicModal, useModalInner } from '@/components/Modal'
import { BasicTree } from '@/components/Tree' import { BasicTree } from '@/components/Tree'
import { assignMenuToRole, getMenuIdsByRole, getMenuTree } from '@/api/system/role' import { getMenuTree, getRoleTreeKeys, updateGrant } from '@/api/system/role'
import type { MenuTreeNode } from '@/api/system/role/types'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
defineOptions({ name: 'RoleMenuModal' }) defineOptions({ name: 'RoleMenuModal' })
const emit = defineEmits(['success', 'register']) const emit = defineEmits(['success', 'register'])
const checkedIds = ref<string[] | { checked: string[], halfChecked: string[] }>([]) const checkedIds = ref<string[] | { checked: string[], halfChecked: string[] }>([])
const { state, execute } = useAsyncState(getMenuTree, [], { immediate: false }) const menuTree = ref<MenuTreeNode[]>([])
const roleIds = ref<string[]>([])
let roleId: string
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (id: string) => { const [registerModal, { setModalProps, closeModal }] = useModalInner(async (id: string) => {
try { try {
if (!state.value.length) const grantTree = await getMenuTree()
await execute() const roleTreeKeys = await getRoleTreeKeys(id)
menuTree.value = grantTree.menu
checkedIds.value = await getMenuIdsByRole(roleId = id) checkedIds.value = roleTreeKeys.menu
roleIds.value = [id]
} }
catch {} catch {}
}) })
function handleSubmit() { function handleSubmit() {
const menuIds: string[] = []
if (Array.isArray(checkedIds.value))
menuIds.push(...checkedIds.value)
else
menuIds.push(...checkedIds.value.checked)
setModalProps({ confirmLoading: true }) setModalProps({ confirmLoading: true })
assignMenuToRole({ updateGrant({
roleId, apiScopeIds: [],
menuIds: Array.isArray(checkedIds.value) ? checkedIds.value : checkedIds.value.checked, dataScopeIds: [],
menuIds,
roleIds: roleIds.value,
topMenuIds: [],
}).then(() => { }).then(() => {
closeModal() closeModal()
emit('success') emit('success')
@ -42,12 +51,10 @@ function handleSubmit() {
<template> <template>
<BasicModal title="菜单权限配置" width="20%" @register="registerModal" @ok="handleSubmit"> <BasicModal title="菜单权限配置" width="20%" @register="registerModal" @ok="handleSubmit">
<BasicTree <BasicTree
v-if="state.length"
v-model:value="checkedIds" v-model:value="checkedIds"
checkable checkable
check-strictly check-strictly
default-expand-all :tree-data="menuTree"
:tree-data="state"
:selectable="false" :selectable="false"
:field-names="{ key: 'id' }" :field-names="{ key: 'id' }"
/> />

12
src/views/system/role/data.ts

@ -31,7 +31,7 @@ export const searchFormSchema: FormSchema[] = [
label: '角色名称', label: '角色名称',
field: 'roleName', field: 'roleName',
component: 'Input', component: 'Input',
colProps: { span: 7 }, colProps: { span: 6 },
}, },
{ {
label: '所属租户', label: '所属租户',
@ -42,17 +42,23 @@ export const searchFormSchema: FormSchema[] = [
valueField: 'tenantId', valueField: 'tenantId',
labelField: 'tenantName', labelField: 'tenantName',
}, },
colProps: { span: 7 }, colProps: { span: 6 },
}, },
{ {
label: '角色别名', label: '角色别名',
field: 'roleAlias', field: 'roleAlias',
component: 'Input', component: 'Input',
colProps: { span: 7 }, colProps: { span: 6 },
}, },
] ]
export const formSchema: FormSchema[] = [ export const formSchema: FormSchema[] = [
{
field: 'tenantId',
show: false,
component: 'Input',
defaultValue: getTenantId(),
},
{ {
field: 'id', field: 'id',
show: false, show: false,

41
src/views/system/role/index.vue

@ -6,9 +6,11 @@ import { columns, searchFormSchema } from './data'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal' import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table' import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteRole, lazyGetRoleList } from '@/api/system/role' import { deleteRole, getRoleList } from '@/api/system/role'
import type { GetRoleListParams, Role } from '@/api/system/role/types' import type { GetRoleListParams, Role } from '@/api/system/role/types'
import { usePermission } from '@/hooks/web/usePermission' import { getAllTenants } from '@/api/system/tenant'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemRole' }) defineOptions({ name: 'SystemRole' })
@ -16,15 +18,20 @@ const { createMessage } = useMessage()
const [registerFormModal, { openModal: openFormModal }] = useModal<Role>() const [registerFormModal, { openModal: openFormModal }] = useModal<Role>()
const [registerMenuModal, { openModal: openMenuModal }] = useModal<string>() const [registerMenuModal, { openModal: openMenuModal }] = useModal<string>()
const { hasPermission } = usePermission() // const { hasPermission } = usePermission()
async function lazyGetRoleListWrap(params: GetRoleListParams) { async function getRoleListWrap(params: GetRoleListParams) {
try { try {
const list = await lazyGetRoleList(params) const tenantList = await getAllTenants()
return list.map(item => ({ const roleList = await getRoleList(params)
...item, return roleList.map((item) => {
children: item.hasChildren ? [] : undefined, const obj = tenantList.find((tenant: Role) => tenant.tenantId === item.tenantId)
})) return {
...item,
children: item.hasChildren ? [] : undefined,
tenantName: obj?.tenantName,
}
})
} }
catch { catch {
return [] return []
@ -32,9 +39,9 @@ async function lazyGetRoleListWrap(params: GetRoleListParams) {
} }
const [registerTable, { reload }] = useTable<Role>({ const [registerTable, { reload }] = useTable<Role>({
api: lazyGetRoleListWrap, api: getRoleListWrap,
load(record) { load(record) {
return lazyGetRoleListWrap({ parentId: record.id }) return getRoleListWrap({ parentId: record.id })
}, },
columns, columns,
formConfig: { formConfig: {
@ -47,11 +54,11 @@ const [registerTable, { reload }] = useTable<Role>({
useSearchForm: true, useSearchForm: true,
pagination: false, pagination: false,
actionColumn: { actionColumn: {
width: 140, width: 180,
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
fixed: 'right', fixed: 'right',
auth: ['edit', 'menu', 'delete'].map(code => `role_${code}`), // auth: ['edit', 'menu', 'delete'].map(code => `role_${code}`),
}, },
}) })
@ -68,7 +75,7 @@ async function handleDelete(id: string) {
<template> <template>
<div> <div>
<BasicTable :api="async () => ([] as Role[])" @register="registerTable"> <BasicTable :api="async () => ([] as Role[])" @register="registerTable">
<template v-if="hasPermission('role_add')" #tableTitle> <template #tableTitle>
<a-button type="primary" @click="openFormModal"> <a-button type="primary" @click="openFormModal">
<PlusOutlined /> <PlusOutlined />
新建 新建
@ -82,20 +89,20 @@ async function handleDelete(id: string) {
{ {
icon: 'i-ant-design:edit-outlined', icon: 'i-ant-design:edit-outlined',
label: '编辑', label: '编辑',
auth: 'role_edit', // auth: 'role_edit',
onClick: () => openFormModal(true, record), onClick: () => openFormModal(true, record),
}, },
{ {
icon: 'i-ant-design:appstore-outlined', icon: 'i-ant-design:appstore-outlined',
label: '菜单权限', label: '菜单权限',
auth: 'role_menu', // auth: 'role_menu',
onClick: () => openMenuModal(true, record.id), onClick: () => openMenuModal(true, record.id),
}, },
{ {
icon: 'i-ant-design:delete-outlined', icon: 'i-ant-design:delete-outlined',
label: '删除', label: '删除',
danger: true, danger: true,
auth: 'role_delete', // auth: 'role_delete',
popConfirm: { popConfirm: {
title: '确定要删除数据吗?', title: '确定要删除数据吗?',
placement: 'left', placement: 'left',

50
src/views/system/tenant/AuthSettingModal.vue

@ -0,0 +1,50 @@
<script lang="ts" setup>
import { authSettingSchema } from './data'
import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { settingTenant } from '@/api/system/tenant'
import type { Tenant } from '@/api/system/tenant/types'
defineOptions({ name: 'AuthSettingModal' })
const emit = defineEmits(['success', 'register'])
const [registerForm, { setFieldsValue, validate }] = useForm({
labelWidth: 120,
baseColProps: { span: 24 },
schemas: authSettingSchema,
showActionButtonGroup: false,
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Tenant) => {
setFieldsValue(data)
})
async function handleSubmit() {
try {
const values = await validate<Tenant>()
console.log(values)
setModalProps({ confirmLoading: true })
await settingTenant(values)
closeModal()
emit('success')
useMessage().createMessage.success('保存成功')
}
catch {}
finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<template>
<BasicModal
title="租户授权配置"
@register="registerModal"
@ok="handleSubmit"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>

77
src/views/system/tenant/DedailModal.vue

@ -0,0 +1,77 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { BasicModal, useModalInner } from '@/components/Modal'
import { Description } from '@/components/Description'
import type { DescItem } from '@/components/Description'
import type { Tenant } from '@/api/system/tenant/types'
defineOptions({ name: 'DetailModal' })
const baseDetail = ref({})
const [registerModal] = useModalInner(async (data: Tenant) => {
baseDetail.value = data
})
const baseSchema: DescItem[] = [
{
field: 'tenantId',
label: '租户ID',
span: 24,
},
{
field: 'tenantName',
label: '租户名称',
},
{
field: 'linkman',
label: '联系人',
},
{
field: 'contactNumber',
label: '联系电话',
render: (record) => {
return record || '-'
},
},
{
field: 'address',
label: '联系地址',
render: (record) => {
return record || '-'
},
},
{
field: 'accountNumber',
label: '账号额度',
render: (record) => {
return record || '-'
},
},
{
field: 'expireTime',
label: '过期时间',
render: (record) => {
return record || '无限制'
},
},
{
field: 'domainUrl',
label: '绑定域名',
render: (record) => {
return record || '-'
},
},
]
</script>
<template>
<BasicModal
width="900px"
title="查看"
:show-cancel-btn="false"
:show-ok-btn="false"
@register="registerModal"
>
<Description :data="baseDetail" :schema="baseSchema" :column="2" />
</BasicModal>
</template>

84
src/views/system/tenant/data.ts

@ -9,24 +9,22 @@ export const columns: BasicColumn[] = [
width: 150, width: 150,
}, },
{ {
title: '租户名称', title: '联系人',
dataIndex: 'tenantName', dataIndex: 'linkman',
width: 150, width: 150,
}, },
{ {
title: '登录账号', title: '联系电话',
dataIndex: 'adminAccount', dataIndex: 'contactNumber',
width: 150, width: 150,
}, },
{ {
title: '联系人名称', title: '账号额度',
dataIndex: 'contactName', dataIndex: 'accountNumber',
width: 150,
},
{
title: '联系人手机',
dataIndex: 'contactMobile',
width: 150, width: 150,
customRender({ value }) {
return h(Tag, { color: value ? 'blue' : 'default' }, () => value)
},
}, },
{ {
title: '过期时间', title: '过期时间',
@ -53,13 +51,7 @@ export const searchFormSchema: FormSchema[] = [
}, },
{ {
label: '联系人名称', label: '联系人名称',
field: 'contactName', field: 'linkman',
component: 'Input',
colProps: { span: 5 },
},
{
label: '联系人手机',
field: 'contactMobile',
component: 'Input', component: 'Input',
colProps: { span: 5 }, colProps: { span: 5 },
}, },
@ -72,35 +64,63 @@ export const formSchema: FormSchema[] = [
component: 'Input', component: 'Input',
}, },
{ {
label: '租户名', label: '租户名',
field: 'tenantName', field: 'tenantName',
required: true, required: true,
component: 'Input', component: 'Input',
}, },
{ {
label: '登录账号', label: '联系人名称',
field: 'adminAccount', field: 'linkman',
required: true, required: true,
component: 'Input', component: 'Input',
rules: [
{ min: 6, max: 30, message: '登陆账号长度为6-30' },
],
// cannot edit
show: ({ values }) => !values.id,
}, },
{ {
label: '联系人名称', label: '联系人电话',
field: 'contactName', field: 'contactNumber',
required: true, component: 'InputNumber',
componentProps: {
controls: false,
precision: 0,
},
},
{
label: '联系人地址',
field: 'address',
component: 'InputTextArea',
},
{
label: '绑定域名',
field: 'domainUrl',
component: 'Input', component: 'Input',
}, },
]
export const authSettingSchema: FormSchema[] = [
{ {
label: '联系人手机', field: 'id',
field: 'contactMobile', show: false,
component: 'Input',
},
{
label: '账号额度',
field: 'accountNumber',
component: 'InputNumber', component: 'InputNumber',
componentProps: { componentProps: {
controls: false, controls: true,
precision: 0, precision: 0,
}, },
}, },
{
label: '过期时间',
field: 'expireTime',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD 00:00:00',
valueFormat: 'YYYY-MM-DD 00:00:00',
style: {
width: '100%',
},
},
},
] ]

44
src/views/system/tenant/index.vue

@ -1,19 +1,25 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue' import { PlusOutlined } from '@ant-design/icons-vue'
import DedailModal from './DedailModal.vue'
import TenantModal from './TenantFormModal.vue' import TenantModal from './TenantFormModal.vue'
import AuthSettingModal from './AuthSettingModal.vue'
import { columns, searchFormSchema } from './data' import { columns, searchFormSchema } from './data'
import { BasicTable, TableAction, useTable } from '@/components/Table' import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteTenant, getTenantList } from '@/api/system/tenant' import { deleteTenant, getTenantList } from '@/api/system/tenant'
import type { Tenant } from '@/api/system/tenant/types' import type { Tenant } from '@/api/system/tenant/types'
import { useModal } from '@/components/Modal' import { useModal } from '@/components/Modal'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { usePermission } from '@/hooks/web/usePermission'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemTenant' }) defineOptions({ name: 'SystemTenant' })
const [registerModal, { openModal }] = useModal<Tenant>() const [registerModal, { openModal }] = useModal<Tenant>()
const [registerDetailModal, { openModal: openDetailModal }] = useModal<Tenant>()
const [registerAuthSettingModal, { openModal: openAuthSettingModal }] = useModal<Tenant>()
const { hasPermission } = usePermission() // const { hasPermission } = usePermission()
const [registerTable, { reload }] = useTable({ const [registerTable, { reload }] = useTable({
api: getTenantList, api: getTenantList,
@ -27,11 +33,11 @@ const [registerTable, { reload }] = useTable({
canResize: false, canResize: false,
useSearchForm: true, useSearchForm: true,
actionColumn: { actionColumn: {
width: 140, width: 300,
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
fixed: 'right', fixed: 'right',
auth: ['tenant_delete', 'tenant_edit'], // auth: ['tenant_delete', 'tenant_edit'],
}, },
}) })
@ -48,28 +54,42 @@ async function handleDelete(id: string) {
<template> <template>
<div> <div>
<BasicTable :api="async () => ([] as Tenant[])" @register="registerTable"> <BasicTable :api="async () => ([] as Tenant[])" @register="registerTable">
<template v-if="hasPermission('tenant_add')" #tableTitle> <template #tableTitle>
<a-button type="primary" @click="openModal"> <Space>
<PlusOutlined /> <a-button type="primary" @click="openModal">
新建 <PlusOutlined />
</a-button> 新建
</a-button>
</Space>
</template> </template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<TableAction <TableAction
:actions="[ :actions="[
{
icon: 'i-ant-design:eye-outlined',
label: '查看',
// auth: 'tenant_edit',
onClick: () => openDetailModal(true, record),
},
{ {
icon: 'i-ant-design:edit-outlined', icon: 'i-ant-design:edit-outlined',
label: '编辑', label: '编辑',
auth: 'tenant_edit', // auth: 'tenant_edit',
onClick: () => openModal(true, record), onClick: () => openModal(true, record),
}, },
{
icon: 'i-ant-design:setting-outlined',
label: '授权配置',
// auth: 'tenant_edit',
onClick: () => openAuthSettingModal(true, record),
},
{ {
icon: 'i-ant-design:delete-outlined', icon: 'i-ant-design:delete-outlined',
label: '删除', label: '删除',
danger: true, danger: true,
auth: 'tenant_delete', // auth: 'tenant_delete',
popConfirm: { popConfirm: {
title: '确定要删除数据吗?', title: '确定要删除数据吗?',
placement: 'left', placement: 'left',
@ -83,5 +103,7 @@ async function handleDelete(id: string) {
</BasicTable> </BasicTable>
<TenantModal @register="registerModal" @success="reload()" /> <TenantModal @register="registerModal" @success="reload()" />
<DedailModal @register="registerDetailModal" @success="reload()" />
<AuthSettingModal @register="registerAuthSettingModal" @success="reload()" />
</div> </div>
</template> </template>

21
src/views/system/user/UserFormModal.vue

@ -6,7 +6,6 @@ import { useMessage } from '@/hooks/web/useMessage'
import { BasicForm, useForm } from '@/components/Form' import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal' import { BasicModal, useModalInner } from '@/components/Modal'
import { createUser, updateUser } from '@/api/system/user' import { createUser, updateUser } from '@/api/system/user'
import type { SystemUser } from '@/api/system/user/types'
defineOptions({ name: 'UserFormModal' }) defineOptions({ name: 'UserFormModal' })
@ -16,21 +15,30 @@ const { t } = useI18n()
const isUpdate = ref(false) const isUpdate = ref(false)
const [registerForm, { setFieldsValue, validate }] = useForm({ const [registerForm, { setFieldsValue, validate }] = useForm({
name: 'user-form', name: 'user-form',
labelWidth: 120, labelWidth: 100,
baseColProps: { span: 24 }, baseColProps: { span: 12 },
schemas: getFormSchema(isUpdate), schemas: getFormSchema(isUpdate),
showActionButtonGroup: false, showActionButtonGroup: false,
actionColOptions: { span: 23 }, actionColOptions: { span: 23 },
}) })
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SystemUser) => { const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
isUpdate.value = true isUpdate.value = true
setFieldsValue({ ...data }) setFieldsValue({
...data,
userType: data.userType.toString(),
deptId: data.deptId.split(','),
postId: data.postId.split(','),
roleId: data.roleId.split(','),
})
}) })
async function handleSubmit() { async function handleSubmit() {
try { try {
const values = await validate<SystemUser>() const values = await validate()
values.deptId = values.deptId.join(',')
values.postId = values.postId.join(',')
values.roleId = values.roleId.join(',')
setModalProps({ confirmLoading: true }) setModalProps({ confirmLoading: true })
await (isUpdate.value ? updateUser(values) : createUser(values)) await (isUpdate.value ? updateUser(values) : createUser(values))
closeModal() closeModal()
@ -46,6 +54,7 @@ async function handleSubmit() {
<template> <template>
<BasicModal <BasicModal
width="55%"
:title="isUpdate ? t('action.edit') : t('action.create')" :title="isUpdate ? t('action.edit') : t('action.create')"
:after-close="() => isUpdate = false" :after-close="() => isUpdate = false"
@register="registerModal" @register="registerModal"

19
src/views/system/user/components/DeptTree.vue

@ -0,0 +1,19 @@
<script setup lang="ts">
import { useAsyncState } from '@vueuse/core'
import { lazyGetDeptList } from '@/api/system/dept'
import { BasicTree } from '@/components/Tree'
const emit = defineEmits(['select'])
const { state: deptList } = useAsyncState(lazyGetDeptList, [])
function onSelect(ids: string) {
emit('select', ids[0])
}
</script>
<template>
<div rounded="6px" px="10px" pt="12px" pb="6px" border-box bg="white dark:[var(--component-background)]" min-w="300px">
<BasicTree :tree-data="deptList" search :field-names="{ title: 'deptName', key: 'id' }" @select="onSelect" />
</div>
</template>

1
src/views/system/user/components/index.ts

@ -0,0 +1 @@
export { default as DeptTree } from './DeptTree.vue'

106
src/views/system/user/composables/useUserActions.ts

@ -0,0 +1,106 @@
import { computed, ref } from 'vue'
import { deleteUser, exportUsers, resetPasswordByIds, unlockUserByIds } from '@/api/system/user'
import type { TableActionType } from '@/components/Table'
import { useMessage } from '@/hooks/web/useMessage'
import { downloadByData } from '@/utils/file/download'
import { useModal } from '@/components/Modal'
import { noop } from '@/utils'
export function useUserActions(tableMethods: TableActionType) {
const { createMessage, createConfirm } = useMessage()
function handleDelete(id?: string) {
const { getSelectRowKeys, clearSelectedRowKeys, reload } = tableMethods
const ids = id ? [id] : (getSelectRowKeys() as string[])
if (!ids.length)
return createMessage.warning('请先选择数据')
async function execute() {
try {
await deleteUser(ids)
createMessage.success('删除成功')
await reload()
clearSelectedRowKeys()
}
catch {}
}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的用户?',
onOk: execute,
})
}
function resetPassword() {
const ids = tableMethods.getSelectRowKeys() as string[]
if (!ids.length)
return createMessage.warning('请先选择数据')
createConfirm({
title: '确定将选择账号密码重置为 123456?',
iconType: 'warning',
onOk() {
resetPasswordByIds(ids)
.then(() => {
createMessage.success('重置成功')
})
.catch(noop)
},
})
}
function unlockUser() {
const ids = tableMethods.getSelectRowKeys() as string[]
if (!ids.length)
return createMessage.warning('请先选择数据')
createConfirm({
title: '确定将选择账号解封?',
iconType: 'warning',
onOk() {
unlockUserByIds(ids)
.then(() => {
createMessage.success('解封成功')
})
.catch(noop)
},
})
}
function handleExportUsers() {
exportUsers()
.then((blob) => {
downloadByData(blob, '用户数据.xlsx')
})
.catch(noop)
}
const [registerImportModal, { openModal: openImportModal }] = useModal()
const isImportCovered = ref(0)
const importModalProps = computed(() => {
return {
title: '用户数据导入',
hint: '请上传 .xls,.xlsx 标准格式文件',
uploadUrl: `/baymax-system/user/import-user?isCovered=${isImportCovered.value}`,
templateUrl: '/baymax-system/user/export-template',
}
})
return {
handleDelete,
resetPassword,
unlockUser,
handleExportUsers,
registerImportModal,
openImportModal,
importModalProps,
isImportCovered,
}
}

259
src/views/system/user/data.ts

@ -4,7 +4,9 @@ import { Avatar } from 'ant-design-vue'
import { getRoleTree } from '@/api/system/role' import { getRoleTree } from '@/api/system/role'
import type { BasicColumn, FormSchema } from '@/components/Table' import type { BasicColumn, FormSchema } from '@/components/Table'
import { getAllTenants } from '@/api/system/tenant' import { getAllTenants } from '@/api/system/tenant'
import { getDeptTree, lazyGetDeptList } from '@/api/system/dept' import { getDeptTree } from '@/api/system/dept'
import { getSystemDictionary } from '@/api/base/common'
import { getPostTree } from '@/api/system/post'
export const columns: BasicColumn[] = [ export const columns: BasicColumn[] = [
{ {
@ -20,7 +22,6 @@ export const columns: BasicColumn[] = [
{ {
title: '用户姓名', title: '用户姓名',
dataIndex: 'realName', dataIndex: 'realName',
width: 120,
customRender({ record, value }) { customRender({ record, value }) {
return h('div', [ return h('div', [
h( h(
@ -38,7 +39,7 @@ export const columns: BasicColumn[] = [
}, },
{ {
title: '手机号码', title: '手机号码',
dataIndex: 'mobile', dataIndex: 'phone',
width: 120, width: 120,
}, },
{ {
@ -56,50 +57,32 @@ export const columns: BasicColumn[] = [
dataIndex: 'deptName', dataIndex: 'deptName',
width: 120, width: 120,
}, },
{
title: '用户平台',
dataIndex: 'userTypeName',
width: 120,
},
] ]
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
{ {
label: '账号', label: '登陆账号',
field: 'account', field: 'account',
component: 'Input', component: 'Input',
colProps: { span: 6 }, colProps: { span: 6 },
}, },
{ {
label: '手机号码', label: '用户姓名',
field: 'mobile', field: 'realName',
component: 'InputNumber', component: 'Input',
componentProps: {
controls: false,
precision: 0,
},
colProps: { span: 6 }, colProps: { span: 6 },
}, },
{ {
label: '所属部门', label: '用户平台',
field: 'tenantId', field: 'userType',
component: 'ApiTreeSelect', component: 'ApiSelect',
componentProps: { componentProps: {
async api() { api: () => getSystemDictionary('user_type'),
try {
const res = await lazyGetDeptList()
return res.map(item => ({ ...item, isLeaf: !item.hasChildren }))
}
catch {
return []
}
},
async loadData(treeNode) {
try {
const res = await lazyGetDeptList({ parentId: treeNode.id })
return res.map(item => ({ ...item, isLeaf: !item.hasChildren }))
}
catch {
return []
}
},
valueField: 'id',
labelField: 'deptName',
}, },
colProps: { span: 6 }, colProps: { span: 6 },
}, },
@ -119,40 +102,8 @@ export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] {
componentProps: { componentProps: {
plain: false, plain: false,
}, },
}, colProps: {
{ span: 24,
label: '账号',
field: 'account',
required: true,
component: 'Input',
ifShow: () => !isUpdate.value,
},
{
label: '密码',
field: 'password',
required: true,
component: 'InputPassword',
ifShow: () => !isUpdate.value,
},
{
label: '确认密码',
field: 'confirmPassword',
required: true,
component: 'InputPassword',
ifShow: () => !isUpdate.value,
dynamicRules: ({ values }) => {
return [
{
required: true,
validator: (_, value) => {
if (value !== values.password)
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject('两次输入的密码不一致!')
return Promise.resolve()
},
},
]
}, },
}, },
{ {
@ -190,10 +141,22 @@ export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] {
}, },
}) })
} }
function updatePost(treeData: { id: string, postName: string }[]) {
if (!isUpdate)
formModel.postId && formActionType.setFieldsValue({ postId: undefined })
formActionType.updateSchema({
field: 'postId',
componentProps: {
treeData,
},
})
}
if (!value) { if (!value) {
updateRole([]) updateRole([])
updateDept([]) updateDept([])
updatePost([])
return return
} }
@ -206,58 +169,84 @@ export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] {
.then((res) => { .then((res) => {
updateDept(res) updateDept(res)
}) })
getPostTree({ tenantId: value })
.then((res) => {
updatePost(res)
})
}, },
} }
}, },
colProps: {
span: 24,
},
}, },
{ {
label: '所属部门', label: '用户平台',
field: 'deptId', field: 'userType',
component: 'ApiSelect',
required: true, required: true,
component: 'TreeSelect',
componentProps: { componentProps: {
treeData: [], api: () => getSystemDictionary('user_type'),
fieldNames: {
label: 'title',
value: 'id',
},
}, },
}, },
{ {
label: '所属角色', label: '账号',
field: 'roleId', field: 'account',
required: true, required: true,
component: 'TreeSelect', component: 'Input',
componentProps: { ifShow: () => !isUpdate.value,
treeData: [], },
fieldNames: { {
label: 'title', label: '密码',
value: 'id', field: 'password',
}, required: true,
component: 'InputPassword',
ifShow: () => !isUpdate.value,
},
{
label: '确认密码',
field: 'confirmPassword',
required: true,
component: 'InputPassword',
ifShow: () => !isUpdate.value,
dynamicRules: ({ values }) => {
return [
{
required: true,
validator: (_, value) => {
if (value !== values.password)
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject('两次输入的密码不一致!')
return Promise.resolve()
},
},
]
}, },
}, },
{ {
field: 'UserInfo', field: 'MoreInfo',
label: '用户信息', label: '详细信息',
component: 'Divider', component: 'Divider',
componentProps: { componentProps: {
plain: false, plain: false,
}, },
colProps: {
span: 24,
},
}, },
{ {
label: '用户姓名', label: '用户昵称',
field: 'realName', field: 'name',
component: 'Input', component: 'Input',
required: true, required: true,
}, },
{ {
label: '用户头像', label: '用户姓名',
field: 'avatar', field: 'realName',
component: 'FileUpload', component: 'Input',
componentProps: { required: true,
maxCount: 1,
fileType: 'image',
},
}, },
{ {
label: '用户邮箱', label: '用户邮箱',
@ -287,23 +276,89 @@ export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] {
options: [ options: [
{ {
label: '男', label: '男',
value: 0, value: 1,
}, },
{ {
label: '女', label: '女',
value: 1, value: 0,
}, },
{ {
label: '未知', label: '未知',
value: 2, value: -1,
}, },
], ],
}, },
}, },
{ {
label: '备注', label: '用户生日',
field: 'remark', field: 'birthday',
component: 'InputTextArea', component: 'DatePicker',
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'UserInfo',
label: '职责信息',
component: 'Divider',
componentProps: {
plain: false,
},
colProps: {
span: 24,
},
},
{
label: '用户编号',
field: 'code',
component: 'Input',
},
{
label: '所属部门',
field: 'deptId',
required: true,
component: 'TreeSelect',
componentProps: {
treeData: [],
fieldNames: {
label: 'title',
value: 'id',
},
multiple: true,
treeCheckStrictly: false,
},
},
{
label: '所属角色',
field: 'roleId',
required: true,
component: 'TreeSelect',
componentProps: {
treeData: [],
fieldNames: {
label: 'title',
value: 'id',
},
multiple: true,
treeCheckStrictly: false,
},
},
{
label: '所属岗位',
field: 'postId',
required: true,
component: 'TreeSelect',
componentProps: {
treeData: [],
fieldNames: {
label: 'postName',
value: 'id',
},
multiple: true,
treeCheckStrictly: false,
},
}, },
] ]
} }

160
src/views/system/user/index.vue

@ -1,92 +1,152 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue' import { Dropdown, Menu, Space, Switch } from 'ant-design-vue'
import { DeleteOutlined, DownOutlined, PlusOutlined } from '@ant-design/icons-vue'
import UserFormModal from './UserFormModal.vue' import UserFormModal from './UserFormModal.vue'
import { columns, searchFormSchema } from './data' import { columns, searchFormSchema } from './data'
import { DeptTree } from './components'
import { useUserActions } from './composables/useUserActions'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal' import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table' import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteUser, getUserList } from '@/api/system/user' import { getUserList } from '@/api/system/user'
import type { SystemUser } from '@/api/system/user/types' import type { SystemUser } from '@/api/system/user/types'
import { usePermission } from '@/hooks/web/usePermission' import { ImportModal } from '@/components/ImportModal'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemUser' }) defineOptions({ name: 'SystemUser' })
const { t } = useI18n() const { t } = useI18n()
const { createMessage } = useMessage()
const [registerModal, { openModal }] = useModal<SystemUser>() const [registerModal, { openModal }] = useModal<SystemUser>()
const { hasPermission } = usePermission() // const { hasPermission } = usePermission()
const [registerTable, { reload }] = useTable({ const [registerTable, tableMethods] = useTable({
api(params) { api(params) {
return getUserList(params) return getUserList({
...params,
deptId: selectedDeptId,
})
}, },
rowKey: 'id',
columns, columns,
formConfig: { formConfig: {
labelWidth: 80, labelWidth: 80,
schemas: searchFormSchema, schemas: searchFormSchema,
autoSubmitOnEnter: true,
}, },
useSearchForm: true, useSearchForm: true,
bordered: true, bordered: true,
canResize: false, canResize: false,
rowSelection: {},
actionColumn: { actionColumn: {
width: 140, width: 140,
title: t('common.action'), title: t('common.action'),
dataIndex: 'action', dataIndex: 'action',
fixed: 'right', fixed: 'right',
auth: ['user_edit', 'user_delete'], // auth: ['user_edit', 'user_delete'],
}, },
}) })
async function handleDelete(id: string) { let selectedDeptId: string
try { function onSelectedDept(id: string) {
await deleteUser(id) selectedDeptId = id
createMessage.success(t('common.delSuccessText')) tableMethods.setPagination({ current: 1 })
reload() tableMethods.reload()
}
catch {}
} }
const {
handleDelete,
resetPassword,
unlockUser,
handleExportUsers,
registerImportModal,
importModalProps,
isImportCovered,
openImportModal,
} = useUserActions(tableMethods)
</script> </script>
<template> <template>
<div> <div flex="~">
<BasicTable :api="async () => ([] as SystemUser[])" @register="registerTable"> <DeptTree my="12px" ml="12px" @select="onSelectedDept" />
<template v-if="hasPermission('user_add')" #tableTitle> <div flex="1" w-0>
<a-button type="primary" @click="openModal"> <BasicTable :api="async () => ([] as SystemUser[])" @register="registerTable">
<PlusOutlined /> <!-- v-if="hasPermission('user_add')" -->
{{ t('action.create') }} <template #tableTitle>
</a-button> <Space>
</template> <a-button type="primary" @click="openModal">
<PlusOutlined />
{{ t('action.create') }}
</a-button>
<a-button danger @click="handleDelete()">
<DeleteOutlined />
批量删除
</a-button>
<Dropdown>
<template #overlay>
<Menu>
<!-- <Menu.Item>
角色配置
</Menu.Item> -->
<Menu.Item @click="resetPassword">
密码重置
</Menu.Item>
<!-- <Menu.Item>
平台配置
</Menu.Item> -->
<Menu.Item @click="unlockUser">
帐号解封
</Menu.Item>
<Menu.Item @click="openImportModal()">
数据导入
</Menu.Item>
<Menu.Item @click="handleExportUsers">
数据导出
</Menu.Item>
</Menu>
</template>
<a-button>
更多操作
<DownOutlined />
</a-button>
</Dropdown>
</Space>
</template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<TableAction <TableAction
:actions="[ :actions="[
{ {
icon: 'i-ant-design:edit-outlined', icon: 'i-ant-design:edit-outlined',
label: t('action.edit'), label: t('action.edit'),
onClick: () => openModal(true, record), onClick: () => openModal(true, record),
auth: 'user_edit', // auth: 'user_edit',
}, },
{ {
icon: 'i-ant-design:delete-outlined', icon: 'i-ant-design:delete-outlined',
danger: true, danger: true,
label: t('action.delete'), label: t('action.delete'),
auth: 'user_delete', // auth: 'user_delete',
popConfirm: { popConfirm: {
title: t('common.delMessage'), title: t('common.delMessage'),
placement: 'left', placement: 'left',
confirm: () => handleDelete(record.id), confirm: () => handleDelete(record.id),
},
}, },
}, ]"
]" />
/> </template>
</template> </template>
</template> </BasicTable>
</BasicTable> </div>
<UserFormModal @register="registerModal" @success="reload()" /> <UserFormModal @register="registerModal" @success="tableMethods.reload()" />
<ImportModal v-bind="importModalProps" @register="registerImportModal">
<div>
<span mr="10px">数据覆盖</span>
<Switch v-model:checked="isImportCovered" :checked-value="1" :un-checked-value="0" />
</div>
</ImportModal>
</div> </div>
</template> </template>

Loading…
Cancel
Save