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_UPLOAD_URL = /upload
VITE_GLOB_UPLOAD_URL = /api/baymax-resource/oss/endpoint/put-file
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
VITE_GLOB_API_URL_PREFIX = /api

2
.env.production

@ -16,7 +16,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
VITE_GLOB_API_URL =
# 文件上传地址 可以由nginx做转发或者直接写实际地址
VITE_GLOB_UPLOAD_URL = /upload
VITE_GLOB_UPLOAD_URL = /baymax-resource/oss/endpoint/put-file
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
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() {
return defHttp.get<UserInfo>({
url: '/system/permission/get-permission-info',
}, {
errorMessageMode: 'none',
url: '/baymax-system/user/info',
})
}
@ -43,7 +41,9 @@ export interface __MenuItem {
sort: number
category: number
action: number
isOpen: 1 | 2
children?: __MenuItem[]
visible: BooleanFlag
}
export function getUserRouters() {
@ -60,7 +60,7 @@ export function getUserButtons() {
export function doLogout() {
return defHttp.post({
url: '/auth/logout',
url: '/baymax-auth/oauth/logout',
})
}
@ -72,3 +72,17 @@ export function getLoginCaptcha() {
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
role_id: 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) {
return defHttp.get<Department[]>({
url: '/system/dept/lazy-list',
url: '/baymax-system/dept/lazy-list',
params,
})
}
export function createDept(data: Partial<Department>) {
return defHttp.post({
url: '/system/dept/save',
url: '/baymax-system/dept/submit',
data,
})
}
export function updateDept(data: Partial<Department>) {
return defHttp.post({
url: '/system/dept/update',
url: '/baymax-system/dept/submit',
data,
})
}
export function deleteDept(id: string) {
return defHttp.post({
url: `/system/dept/delete?id=${id}`,
url: `/baymax-system/dept/remove?id=${id}`,
})
}
export function getDeptTree(params?: { tenantId: string }) {
return defHttp.get<{ id: string, title: string }[]>({
url: '/system/dept/tree',
url: '/baymax-system/dept/tree',
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() {
return defHttp.get<MenuItem[]>({
url: '/system/menu/tree',
url: '/baymax-system/menu/tree',
})
}
export function getMenuList(params: GetMenuListParams) {
return defHttp.get<PageResult<MenuItem>>({
url: '/system/menu/list',
url: '/baymax-system/menu/list',
params,
})
}
export function getMenu(id: string) {
return defHttp.get({
url: `/system/menu/get?id=${id}`,
url: `/baymax-system/menu/get?id=${id}`,
})
}
export function createMenu(data: Omit<MenuItem, 'id' | 'children'>) {
return defHttp.post({
url: '/system/menu/save',
url: '/baymax-system/menu/submit',
data,
})
}
export function updateMenu(data: Omit<MenuItem, 'children'>) {
return defHttp.post({
url: '/system/menu/update',
url: '/baymax-system/menu/submit',
data,
})
}
export function deleteMenu(id: string) {
export function deleteMenu(ids: string[]) {
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) {
return defHttp.get({
url: '/param/page',
url: '/baymax-system/param/list',
params,
})
}
export function createParam(data: Partial<Param>) {
return defHttp.post({
url: '/param/save',
url: '/baymax-system/param/submit',
data,
})
}
export function updateParam(data: Partial<Param>) {
return defHttp.post({
url: '/param/update',
url: '/baymax-system/param/submit',
data,
})
}
export function deleteParam(id: string) {
export function deleteParam(ids: string[]) {
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'
export function lazyGetRoleList(params: GetRoleListParams) {
export function getRoleList(params: GetRoleListParams) {
return defHttp.get<Role[]>({
url: '/system/role/lazy-list',
url: '/baymax-system/role/list',
params,
})
}
export function createRole(data: Partial<Role>) {
return defHttp.post({
url: '/system/role/save',
url: '/baymax-system/role/submit',
data,
})
}
export function updateRole(data: Partial<Role>) {
return defHttp.post({
url: '/system/role/update',
url: '/baymax-system/role/submit',
data,
})
}
export function deleteRole(id: string) {
return defHttp.post({
url: `/system/role/delete?id=${id}`,
url: `/baymax-system/role/delete?id=${id}`,
})
}
export function getRoleTree(params?: { tenantId: string }) {
return defHttp.get<{ id: string, title: string }[]>({
url: '/system/role/tree',
url: '/baymax-system/role/tree',
params,
})
}
export function getMenuTree() {
return defHttp.get<MenuTreeNode[]>({
url: '/system/menu/grant-tree',
return defHttp.get<{ menu: MenuTreeNode[] }>({
url: '/baymax-system/menu/grant-tree',
})
}
export function getMenuIdsByRole(roleId: string) {
return defHttp.get<string[]>({
url: '/system/permission/list-role-menus',
params: { roleId },
export function getRoleTreeKeys(roleIds: string) {
return defHttp.get<{ menu: string[] }>({
url: `/baymax-system/menu/role-tree-keys?roleIds=${roleIds}`,
})
}
export function assignMenuToRole(data: { roleId: string, menuIds: string[] }) {
export function updateGrant(data: GrantParams) {
return defHttp.post({
url: '/system/permission/assign-role-menu',
url: `/baymax-system/role/grant`,
data,
})
}

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

@ -25,5 +25,14 @@ export interface MenuTreeNode {
id: string
parentId: string
title: string
hasChildren: boolean
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) {
return defHttp.get<PageResult<Tenant>>({
url: '/system/tenant/page',
url: '/baymax-system/tenant/list',
params,
})
}
export function updateTenant(data: Tenant) {
return defHttp.post({
url: '/system/tenant/update',
url: '/baymax-system/tenant/submit',
data,
})
}
export function createTenant(data: Tenant) {
return defHttp.post({
url: '/system/tenant/save',
url: '/baymax-system/tenant/submit',
data,
})
}
export function detailTenant(id: string) {
return defHttp.get({
url: `/baymax-system/tenant/detail?id=${id}`,
})
}
export function deleteTenant(id: string) {
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() {
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 {
id: string
tenantId?: string
tenantId: string
tenantName: string
adminAccount: string
contactName: string
contactMobile: string
expireTime?: string
createUser: string
accountNumber: number
contactNumber: string
expireTime: string
domainUrl: string
linkman: string
address: string
}
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) {
return defHttp.get<PageResult<SystemUser>>({
url: '/system/user/page',
url: '/baymax-system/user/page',
params,
})
}
export function createUser(data: Partial<SystemUser>) {
return defHttp.post({
url: '/system/user/save',
url: '/baymax-system/user/save',
data,
})
}
export function updateUser(data: Partial<SystemUser>) {
return defHttp.post({
url: '/system/user/update',
url: '/baymax-system/user/update',
data,
})
}
export function deleteUser(id: string) {
export function deleteUser(ids: string[]) {
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
tenantId: string
tenantName: string
userType: string
}

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

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { Upload } from 'ant-design-vue'
import { InboxOutlined } from '@ant-design/icons-vue'
import { computed, reactive, ref, unref, watch } from 'vue'
import { getAccessToken, getTenantId } from '@/utils/auth'
import { propTypes } from '@/utils/propTypes'
@ -10,7 +11,6 @@ import { useDesign } from '@/hooks/web/useDesign'
import { useGlobSetting } from '@/hooks/setting'
defineOptions({ name: 'FileUpload' })
const props = defineProps({
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
text: propTypes.string.def('上传'),
@ -37,6 +37,8 @@ const props = defineProps({
removeConfirm: propTypes.bool.def(false),
beforeUpload: propTypes.func,
disabled: propTypes.bool.def(false),
dragger: propTypes.bool.def(false),
draggerHint: propTypes.string,
})
const emit = defineEmits(['change', 'update:value'])
const { createMessage, createConfirm } = useMessage()
@ -272,7 +274,8 @@ function getFileName(path) {
<template>
<div ref="containerRef" :class="`${prefixCls}-container`">
<Upload
<component
:is="dragger ? Upload.Dragger : Upload"
:headers="headers"
:multiple="multiple"
:action="uploadUrl"
@ -283,7 +286,18 @@ function getFileName(path) {
@change="onFileChange"
@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">
<span class="i-ant-design:plus-outlined" />
<div class="ant-upload-text">
@ -295,7 +309,7 @@ function getFileName(path) {
<span class="i-ant-design:upload-outlined" />
<span>{{ text }}</span>
</a-button>
</Upload>
</component>
</div>
</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)}
</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)}>
<Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value} loadData={getBindValues.value.loadData ? loadData : undefined}>
{extendSlots(slots, ['title'])}
@ -443,3 +443,11 @@ export default defineComponent({
},
})
</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>
</Dropdown>
</div>
<slot v-if="slots.headerAction" name="headerAction" />
</div>
</template>

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

@ -48,6 +48,6 @@
}
&-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
BASE_LOGIN = '/login',
// basic home path
BASE_HOME = '/dashboard',
BASE_HOME = '/home',
// error page path
ERROR_PAGE = '/exception',
// error log page path

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

@ -1,10 +1,11 @@
<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 type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
import { computed } from 'vue'
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 { useDesign } from '@/hooks/web/useDesign'
import { useModal } from '@/components/Modal'
@ -28,7 +29,7 @@ const LockAction = createAsyncComponent(() => import('../lock/LockModal.vue'))
const { prefixCls } = useDesign('header-user-dropdown')
const { t } = useI18n()
const { getShowDoc, getUseLockPage } = useHeaderSetting()
// const { getShowDoc, getUseLockPage } = useHeaderSetting()
const userStore = useUserStore()
const getUserInfo = computed(() => {
@ -84,11 +85,11 @@ function handleMenuClick(e: MenuInfo) {
<template #overlay>
<Menu @click="handleMenuClick">
<MenuItem key="profile" :text="t('layout.header.accountCenter')" icon="i-ion:person-outline" />
<MenuDivider v-if="getShowDoc" />
<MenuItem
<!-- <MenuDivider v-if="getShowDoc" /> -->
<!-- <MenuItem
v-if="getUseLockPage" key="lock" :text="t('layout.header.tooltipLock')"
icon="i-ion:lock-closed-outline"
/>
/> -->
<MenuItem key="logout" :text="t('layout.header.dropdownItemLoginOut')" icon="i-ion:power-outline" />
</Menu>
</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 { 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 { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { useRootSetting } from '@/hooks/setting/useRootSetting'
import { MenuModeEnum, MenuSplitTyeEnum } from '@/enums/menuEnum'
import { SettingButtonPositionEnum } from '@/enums/appEnum'
import { useAppInject } from '@/hooks/web/useAppInject'
import { useDesign } from '@/hooks/web/useDesign'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
import { useLocale } from '@/locales/useLocale'
defineOptions({ name: 'LayoutHeader' })
const props = defineProps({
fixed: propTypes.bool,
})
const Header = Layout.Header
const SettingDrawer = createAsyncComponent(() => import('@/layouts/default/setting/index.vue'), {
loading: true,
})
const { prefixCls } = useDesign('layout-header')
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()
const { getShowLocalePicker } = useLocale()
const { getIsMobile } = useAppInject()
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(() => {
if (!unref(getIsMixMode) || unref(getIsMobile))
return {}
@ -111,14 +90,9 @@ const getMenuMode = computed(() => {
<ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
<AppLocalePicker
v-if="getShowLocalePicker" :reload="true" :show-text="false"
:class="`${prefixCls}-action__item locale-item`"
/>
<AppDarkModeToggle />
<UserDropDown :theme="getHeaderTheme" />
<SettingDrawer v-if="getShowSetting" :class="`${prefixCls}-action__item`" />
</div>
</Header>
</template>

2
src/router/helper/routeHelper.ts

@ -84,7 +84,7 @@ export function transformObjToRoute(menuList: MenuItem[]): AppRouteModule[] {
const route = { ...item } as unknown as AppRouteModule
if (isHttpUrl(route.path))
route.component = 'IFrame'
else if (route.children && route.parentId === '0')
else if (route.children && route.children.length && route.parentId === '0')
route.component = 'LAYOUT'
else if (!route.children)
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 { t } from '@/hooks/web/useI18n'
import { LAYOUT } from '@/router/constant'
// import.meta.glob() 直接引入所有的模块 Vite 独有的功能
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
// 未经许可的基本路由
export const basicRoutes = [
LoginRoute,
RootRoute,
ProfileRoute,
REDIRECT_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 { store } from '@/store'
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 { transformRouteToMenu } from '@/router/helper/menuHelper'
import { flatMultiLevelRoutes, transformObjToRoute } from '@/router/helper/routeHelper'
@ -141,7 +141,7 @@ export const usePermissionStore = defineStore('app-permission', {
let routeList = transformObjToRoute(userInfo.menus)
// Background routing to menu structure
const backMenuList = transformRouteToMenu([dashboard, ...routeList])
const backMenuList = transformRouteToMenu([profile, ...routeList])
this.setBackMenuList(backMenuList)
// remove meta.ignoreRoute item
@ -149,7 +149,7 @@ export const usePermissionStore = defineStore('app-permission', {
routeList = routeList.filter(routeRemoveIgnoreFilter)
routeList = flatMultiLevelRoutes(routeList)
routes = [PAGE_NOT_FOUND_ROUTE, dashboard, ...routeList]
routes = [PAGE_NOT_FOUND_ROUTE, profile, ...routeList]
patchHomeAffix(routes)
return routes
},

10
src/store/modules/user.ts

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

1
src/types/axios.d.ts vendored

@ -40,6 +40,7 @@ export interface RetryRequest {
export interface Result<T = any> {
code: number
message: string
msg: string
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 { downloadByData } from '@/utils/file/download'
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'
const globSetting = useGlobSetting()
// 请求队列
let requestList: any[] = []
// const requestList: any[] = []
// 是否正在刷新中
let isRefreshToken = false
// const isRefreshToken = false
/**
* @description: axios
@ -123,51 +124,54 @@ export class VAxios {
// 响应结果拦截器处理
this.axiosInstance.interceptors.response.use(async (res: AxiosResponse<any>) => {
const config = res.config
if (res.data.code === 401) {
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
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))
})
})
}
useUserStoreWithOut().logout(true)
return res
}
// // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
// 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)
if (responseInterceptors && isFunction(responseInterceptors))
res = responseInterceptors(res)

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

@ -40,14 +40,15 @@ const transform: AxiosTransform = {
transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
const { t } = useI18n()
const { isTransformResponse, isReturnNativeResponse } = options
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer')
return res.data
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse)
return res
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer')
return res.data
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
if (!isTransformResponse)
@ -61,7 +62,7 @@ const transform: AxiosTransform = {
throw new Error(t('sys.api.apiRequestFailed'))
}
// 这里 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
if (hasSuccess) {

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

@ -3,11 +3,10 @@ import { computed } from 'vue'
import LoginForm from './LoginForm.vue'
import ForgetPasswordForm from './ForgetPasswordForm.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 { useI18n } from '@/hooks/web/useI18n'
import { useDesign } from '@/hooks/web/useDesign'
import { useLocaleStore } from '@/store/modules/locale'
defineProps({
sessionTimeout: {
@ -18,8 +17,6 @@ defineProps({
const globSetting = useGlobSetting()
const { prefixCls } = useDesign('login')
const { t } = useI18n()
const localeStore = useLocaleStore()
const showLocale = localeStore.getShowPicker
const title = computed(() => globSetting?.title ?? '')
</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="absolute right-4 top-4 flex items-center">
<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>
<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 formData = reactive<LoginParams>({
tenantId: '345618',
tenantId: '000000',
username: 'admin',
password: '&demo8&!',
password: '123456',
captchaKey: '',
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 { getAllTenants } from '@/api/system/tenant'
import type { BasicColumn, FormSchema } from '@/components/Table'
export const columns: BasicColumn[] = [
{
title: '部门名称',
title: '机构名称',
dataIndex: 'deptName',
width: 260,
align: 'left',
@ -14,11 +15,21 @@ export const columns: BasicColumn[] = [
dataIndex: 'tenantName',
width: 120,
},
{
title: '机构全称',
dataIndex: 'fullName',
width: 260,
},
{
title: '机构类型',
dataIndex: 'deptCategoryName',
width: 260,
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '部门名称',
label: '机构名称',
field: 'deptName',
component: 'Input',
colProps: { span: 6 },
@ -34,6 +45,12 @@ export const searchFormSchema: FormSchema[] = [
},
colProps: { span: 6 },
},
{
label: '机构全称',
field: 'fullName',
component: 'Input',
colProps: { span: 6 },
},
]
export const formSchema: FormSchema[] = [
@ -70,11 +87,26 @@ export const formSchema: FormSchema[] = [
},
},
{
label: '部门名称',
label: '机构名称',
field: 'deptName',
required: true,
component: 'Input',
},
{
label: '机构全称',
field: 'fullName',
required: true,
component: 'Input',
},
{
label: '机构类型',
field: 'deptCategory',
required: true,
component: 'ApiSelect',
componentProps: {
api: () => getSystemDictionary('org_category'),
},
},
{
label: '显示顺序',
field: 'sort',
@ -82,4 +114,9 @@ export const formSchema: FormSchema[] = [
defaultValue: 0,
component: 'InputNumber',
},
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
},
]

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

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { Space } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import DeptFormModal from './DeptFormModal.vue'
import { columns, searchFormSchema } from './data'
@ -9,7 +10,8 @@ import { IconEnum } from '@/enums/appEnum'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteDept, lazyGetDeptList } from '@/api/system/dept'
import type { Department, LazyGetDeptListParams } from '@/api/system/dept/types'
import { usePermission } from '@/hooks/web/usePermission'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemDept' })
@ -17,14 +19,14 @@ const { t } = useI18n()
const [registerModal, { openModal }] = useModal<Department>()
const { hasPermission } = usePermission()
// const { hasPermission } = usePermission()
async function lazyGetDeptListWrap(params: LazyGetDeptListParams) {
try {
const list = await lazyGetDeptList(params)
return list.map(item => ({
...item,
children: item.hasChildren ? [] : undefined,
// children: item.hasChildren ? [] : undefined,
}))
}
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,
load(record) {
return lazyGetDeptListWrap({ parentId: record.id })
},
// load(record) {
// return lazyGetDeptListWrap({ parentId: record.id })
// },
columns,
formConfig: {
labelWidth: 80,
@ -46,33 +49,50 @@ const [register, { reload }] = useTable<Department>({
canResize: false,
useSearchForm: true,
pagination: false,
showIndexColumn: false,
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
auth: ['dept_delete', 'dept_edit'],
// auth: ['dept_delete', 'dept_edit'],
},
})
async function handleDelete(id: string) {
try {
await deleteDept(id)
useMessage().createMessage.success(t('common.delSuccessText'))
reload()
const { createMessage, createConfirm } = useMessage()
function handleDelete(id: string) {
async function execute() {
try {
await deleteDept(id)
createMessage.success(t('common.delSuccessText'))
await reload()
clearSelectedRowKeys()
}
catch {}
}
catch {}
if (id)
return execute()
createConfirm({
iconType: 'warning',
title: '是否要删除选择的菜单?',
onOk: execute,
})
}
</script>
<template>
<div>
<BasicTable :api="async () => ([] as Department[])" @register="register">
<template v-if="hasPermission('dept_add')" #tableTitle>
<a-button type="primary" @click="openModal">
<PlusOutlined />
{{ t('action.create') }}
</a-button>
<!-- v-if="hasPermission('dept_add')" -->
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal">
<PlusOutlined />
{{ t('action.create') }}
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
@ -82,14 +102,14 @@ async function handleDelete(id: string) {
{
icon: IconEnum.EDIT,
label: t('action.edit'),
auth: 'dept_edit',
// auth: 'dept_edit',
onClick: () => openModal(true, record),
},
{
icon: IconEnum.DELETE,
danger: true,
label: t('action.delete'),
auth: 'dept_delete',
// auth: 'dept_delete',
popConfirm: {
title: t('common.delMessage'),
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 [registerModal, { setModalProps, closeModal }] = useModalInner((data: MenuItem) => {
isUpdate.value = true
const [registerModal, { setModalProps, closeModal }] = useModalInner((data: Partial<MenuItem>) => {
isUpdate.value = !!data.id
console.log(data)
setFieldsValue({
...data,
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 { getMenuListWithoutButtons } from '@/api/system/menu'
import type { BasicColumn, FormSchema } from '@/components/Table'
@ -7,17 +8,17 @@ export const columns: BasicColumn[] = [
{
title: '菜单名称',
dataIndex: 'name',
width: 250,
align: 'left',
},
{
title: '菜单类型',
dataIndex: 'type',
dataIndex: 'category',
width: 80,
customRender: ({ record }) => {
const { type } = record
const { category } = record
let name = ''
switch (type) {
switch (category) {
case SystemMenuTypeEnum.DIR:
name = '目录'
break
@ -28,15 +29,15 @@ export const columns: BasicColumn[] = [
name = '按钮'
}
return <Tag>{ name }</Tag>
return h(Tag, () => name)
},
},
{
title: '图标',
dataIndex: 'icon',
dataIndex: 'source',
width: 60,
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,
},
{
title: '权限标识',
title: '菜单编号',
dataIndex: 'code',
width: 140,
},
{
title: '路由路径',
dataIndex: 'path',
width: 140,
title: '菜单别名',
dataIndex: 'alias',
},
{
title: '组件路径',
dataIndex: 'component',
width: 140,
title: '路由路径',
dataIndex: 'path',
},
// {
// title: '组件路径',
// dataIndex: 'component',
// width: 140,
// },
]
export const searchFormSchema: FormSchema[] = [
@ -66,13 +69,19 @@ export const searchFormSchema: FormSchema[] = [
label: '菜单名称',
field: 'name',
component: 'Input',
colProps: { span: 8 },
colProps: { span: 6 },
},
{
label: '权限标识',
label: '菜单编号',
field: 'code',
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: '菜单类型',
field: 'type',
field: 'category',
required: true,
defaultValue: 1,
component: 'RadioButtonGroup',
componentProps: {
options: [
{
label: '目录',
value: SystemMenuTypeEnum.DIR,
},
// {
// label: '目录',
// value: SystemMenuTypeEnum.DIR,
// },
{
label: '菜单',
value: SystemMenuTypeEnum.MENU,
@ -122,19 +131,6 @@ export const formSchema: FormSchema[] = [
required: true,
component: 'Input',
},
{
label: '菜单图标',
field: 'icon',
component: 'IconPicker',
ifShow: ({ values }) => values.type !== SystemMenuTypeEnum.BUTTON,
},
{
label: '显示排序',
field: 'sort',
required: true,
component: 'InputNumber',
defaultValue: 0,
},
{
label: '路由地址',
field: 'path',
@ -144,28 +140,48 @@ export const formSchema: FormSchema[] = [
ifShow: ({ values }) => values.type !== SystemMenuTypeEnum.BUTTON,
},
{
label: '组件路径',
field: 'component',
label: '菜单图标',
field: 'source',
// required: true,
component: 'IconPicker',
ifShow: ({ values }) => values.type !== SystemMenuTypeEnum.BUTTON,
},
{
label: '菜单编号',
field: 'code',
required: true,
component: 'Input',
helpMessage: '例如:system/user/index,不定义时使用 path 字段',
ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
helpMessage: '输入一个不重复的标识',
},
{
label: '组件名称',
field: 'componentName',
required: ({ values }) => values.keepAlive,
label: '菜单别名',
field: 'alias',
required: true,
component: 'Input',
helpMessage: '例如:SystemName,当开启缓存时它是必传的',
ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
},
{
label: '权限标识',
field: 'code',
label: '显示排序',
field: 'sort',
required: true,
component: 'Input',
helpMessage: '输入一个不重复的标识',
component: 'InputNumber',
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: '是否在菜单栏显示',
field: 'visible',
@ -182,15 +198,15 @@ export const formSchema: FormSchema[] = [
},
{
label: '是否缓存',
field: 'keepAlive',
field: 'isOpen',
component: 'Switch',
componentProps: {
checkedChildren: '缓存',
unCheckedChildren: '不缓存',
checkedValue: 1,
unCheckedValue: 0,
checkedValue: 2,
unCheckedValue: 1,
},
helpMessage: '选择缓存时,则会被 `keep-alive` 缓存,同时必须指定组件的 Name 值',
ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
// helpMessage: '选择缓存时,则会被 `keep-alive` 缓存,同时必须指定组件的 Name 值',
// ifShow: ({ values }) => values.type === SystemMenuTypeEnum.MENU,
},
]

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

@ -1,21 +1,23 @@
<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 { columns, searchFormSchema } from './data'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
import { BasicTable, TableAction, useTable } from '@/components/Table'
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'
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,
api: getMenuList,
rowKey: 'id',
@ -29,34 +31,59 @@ const [register, { reload }] = useTable<MenuItem>({
canResize: false,
isTreeTable: true,
pagination: false,
rowSelection: {},
actionColumn: {
width: 140,
width: 240,
title: '操作',
dataIndex: 'action',
fixed: 'right',
auth: ['menu_edit', 'menu_delete'],
// auth: ['menu_edit', 'menu_delete'],
},
})
const { createMessage } = useMessage()
async function handleDelete(id: string) {
try {
await deleteMenu(id)
createMessage.success('删除成功')
reload()
const { createMessage, createConfirm } = useMessage()
function handleDelete(id?: string) {
const ids = id ? [id] : (getSelectRowKeys() as string[])
if (!ids.length)
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>
<template>
<div>
<BasicTable :api="async () => ([] as MenuItem[])" @register="register">
<template v-if="hasPermission('menu_add')" #tableTitle>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新增
</a-button>
<!-- v-if="hasPermission('menu_add')" -->
<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 }">
@ -66,20 +93,25 @@ async function handleDelete(id: string) {
{
icon: 'i-ant-design:edit-outlined',
label: '修改',
auth: 'menu_edit',
// auth: 'menu_edit',
onClick: () => openModal(true, record),
},
{
icon: 'i-ant-design:delete-outlined',
label: '删除',
danger: true,
auth: 'menu_delete',
// auth: 'menu_delete',
popConfirm: {
title: '确定要删除数据吗?',
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
{
icon: 'i-ant-design:plus-outlined',
label: '新增子项',
onClick: () => openModal(true, { parentId: record.id }),
},
]"
/>
</template>

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

@ -13,6 +13,10 @@ export const columns: BasicColumn[] = [
title: '参数键值',
dataIndex: 'paramValue',
},
{
title: '备注',
dataIndex: 'remark',
},
]
export const searchFormSchema: FormSchema[] = [
@ -57,4 +61,12 @@ export const formSchema: FormSchema[] = [
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>
import { Space } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import ParamFormModal from './ParamFormModal.vue'
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 { useModal } from '@/components/Modal'
import { useMessage } from '@/hooks/web/useMessage'
import { usePermission } from '@/hooks/web/usePermission'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemParams' })
const [registerModal, { openModal }] = useModal<Param>()
const [registerTable, { reload }] = useTable({
const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable<Param>({
api: getParamList,
rowKey: 'id',
columns,
formConfig: {
labelWidth: 100,
schemas: searchFormSchema,
actionColOptions: { span: 4 },
actionColOptions: { span: 12 },
},
bordered: true,
canResize: false,
useSearchForm: true,
rowSelection: {},
actionColumn: {
width: 160,
title: '操作',
dataIndex: 'action',
fixed: 'right',
auth: ['param_delete', 'param_edit'],
// auth: ['param_delete', 'param_edit'],
},
})
async function handleDelete(id: string) {
try {
await deleteParam(id)
useMessage().createMessage.success('删除成功!')
reload()
const { createMessage, createConfirm } = useMessage()
async function handleDelete(id?: string) {
const ids = id ? [id] : (getSelectRowKeys() as string[])
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>
<template>
<div>
<BasicTable :api="async () => ([] as Param[])" @register="registerTable">
<template v-if="hasPermission('param_add')" #tableTitle>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新建
</a-button>
<!-- v-if="hasPermission('param_add')" -->
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新建
</a-button>
<a-button danger @click="handleDelete()">
批量删除
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
@ -62,14 +90,14 @@ const { hasPermission } = usePermission()
{
icon: 'i-ant-design:edit-outlined',
label: '编辑',
auth: 'param_edit',
// auth: 'param_edit',
onClick: () => openModal(true, record),
},
{
icon: 'i-ant-design:delete-outlined',
label: '删除',
danger: true,
auth: 'param_delete',
// auth: 'param_delete',
popConfirm: {
title: '确定要删除数据吗?',
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>
import { ref } from 'vue'
import { useAsyncState } from '@vueuse/core'
import { BasicModal, useModalInner } from '@/components/Modal'
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'
defineOptions({ name: 'RoleMenuModal' })
const emit = defineEmits(['success', 'register'])
const checkedIds = ref<string[] | { checked: string[], halfChecked: string[] }>([])
const { state, execute } = useAsyncState(getMenuTree, [], { immediate: false })
let roleId: string
const menuTree = ref<MenuTreeNode[]>([])
const roleIds = ref<string[]>([])
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (id: string) => {
try {
if (!state.value.length)
await execute()
checkedIds.value = await getMenuIdsByRole(roleId = id)
const grantTree = await getMenuTree()
const roleTreeKeys = await getRoleTreeKeys(id)
menuTree.value = grantTree.menu
checkedIds.value = roleTreeKeys.menu
roleIds.value = [id]
}
catch {}
})
function handleSubmit() {
const menuIds: string[] = []
if (Array.isArray(checkedIds.value))
menuIds.push(...checkedIds.value)
else
menuIds.push(...checkedIds.value.checked)
setModalProps({ confirmLoading: true })
assignMenuToRole({
roleId,
menuIds: Array.isArray(checkedIds.value) ? checkedIds.value : checkedIds.value.checked,
updateGrant({
apiScopeIds: [],
dataScopeIds: [],
menuIds,
roleIds: roleIds.value,
topMenuIds: [],
}).then(() => {
closeModal()
emit('success')
@ -42,12 +51,10 @@ function handleSubmit() {
<template>
<BasicModal title="菜单权限配置" width="20%" @register="registerModal" @ok="handleSubmit">
<BasicTree
v-if="state.length"
v-model:value="checkedIds"
checkable
check-strictly
default-expand-all
:tree-data="state"
:tree-data="menuTree"
:selectable="false"
:field-names="{ key: 'id' }"
/>

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

@ -31,7 +31,7 @@ export const searchFormSchema: FormSchema[] = [
label: '角色名称',
field: 'roleName',
component: 'Input',
colProps: { span: 7 },
colProps: { span: 6 },
},
{
label: '所属租户',
@ -42,17 +42,23 @@ export const searchFormSchema: FormSchema[] = [
valueField: 'tenantId',
labelField: 'tenantName',
},
colProps: { span: 7 },
colProps: { span: 6 },
},
{
label: '角色别名',
field: 'roleAlias',
component: 'Input',
colProps: { span: 7 },
colProps: { span: 6 },
},
]
export const formSchema: FormSchema[] = [
{
field: 'tenantId',
show: false,
component: 'Input',
defaultValue: getTenantId(),
},
{
field: 'id',
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 { useModal } from '@/components/Modal'
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 { usePermission } from '@/hooks/web/usePermission'
import { getAllTenants } from '@/api/system/tenant'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemRole' })
@ -16,15 +18,20 @@ const { createMessage } = useMessage()
const [registerFormModal, { openModal: openFormModal }] = useModal<Role>()
const [registerMenuModal, { openModal: openMenuModal }] = useModal<string>()
const { hasPermission } = usePermission()
// const { hasPermission } = usePermission()
async function lazyGetRoleListWrap(params: GetRoleListParams) {
async function getRoleListWrap(params: GetRoleListParams) {
try {
const list = await lazyGetRoleList(params)
return list.map(item => ({
...item,
children: item.hasChildren ? [] : undefined,
}))
const tenantList = await getAllTenants()
const roleList = await getRoleList(params)
return roleList.map((item) => {
const obj = tenantList.find((tenant: Role) => tenant.tenantId === item.tenantId)
return {
...item,
children: item.hasChildren ? [] : undefined,
tenantName: obj?.tenantName,
}
})
}
catch {
return []
@ -32,9 +39,9 @@ async function lazyGetRoleListWrap(params: GetRoleListParams) {
}
const [registerTable, { reload }] = useTable<Role>({
api: lazyGetRoleListWrap,
api: getRoleListWrap,
load(record) {
return lazyGetRoleListWrap({ parentId: record.id })
return getRoleListWrap({ parentId: record.id })
},
columns,
formConfig: {
@ -47,11 +54,11 @@ const [registerTable, { reload }] = useTable<Role>({
useSearchForm: true,
pagination: false,
actionColumn: {
width: 140,
width: 180,
title: '操作',
dataIndex: 'action',
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>
<div>
<BasicTable :api="async () => ([] as Role[])" @register="registerTable">
<template v-if="hasPermission('role_add')" #tableTitle>
<template #tableTitle>
<a-button type="primary" @click="openFormModal">
<PlusOutlined />
新建
@ -82,20 +89,20 @@ async function handleDelete(id: string) {
{
icon: 'i-ant-design:edit-outlined',
label: '编辑',
auth: 'role_edit',
// auth: 'role_edit',
onClick: () => openFormModal(true, record),
},
{
icon: 'i-ant-design:appstore-outlined',
label: '菜单权限',
auth: 'role_menu',
// auth: 'role_menu',
onClick: () => openMenuModal(true, record.id),
},
{
icon: 'i-ant-design:delete-outlined',
label: '删除',
danger: true,
auth: 'role_delete',
// auth: 'role_delete',
popConfirm: {
title: '确定要删除数据吗?',
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,
},
{
title: '租户名称',
dataIndex: 'tenantName',
title: '联系人',
dataIndex: 'linkman',
width: 150,
},
{
title: '登录账号',
dataIndex: 'adminAccount',
title: '联系电话',
dataIndex: 'contactNumber',
width: 150,
},
{
title: '联系人名称',
dataIndex: 'contactName',
width: 150,
},
{
title: '联系人手机',
dataIndex: 'contactMobile',
title: '账号额度',
dataIndex: 'accountNumber',
width: 150,
customRender({ value }) {
return h(Tag, { color: value ? 'blue' : 'default' }, () => value)
},
},
{
title: '过期时间',
@ -53,13 +51,7 @@ export const searchFormSchema: FormSchema[] = [
},
{
label: '联系人名称',
field: 'contactName',
component: 'Input',
colProps: { span: 5 },
},
{
label: '联系人手机',
field: 'contactMobile',
field: 'linkman',
component: 'Input',
colProps: { span: 5 },
},
@ -72,35 +64,63 @@ export const formSchema: FormSchema[] = [
component: 'Input',
},
{
label: '租户名',
label: '租户名',
field: 'tenantName',
required: true,
component: 'Input',
},
{
label: '登录账号',
field: 'adminAccount',
label: '联系人名称',
field: 'linkman',
required: true,
component: 'Input',
rules: [
{ min: 6, max: 30, message: '登陆账号长度为6-30' },
],
// cannot edit
show: ({ values }) => !values.id,
},
{
label: '联系人名称',
field: 'contactName',
required: true,
label: '联系人电话',
field: 'contactNumber',
component: 'InputNumber',
componentProps: {
controls: false,
precision: 0,
},
},
{
label: '联系人地址',
field: 'address',
component: 'InputTextArea',
},
{
label: '绑定域名',
field: 'domainUrl',
component: 'Input',
},
]
export const authSettingSchema: FormSchema[] = [
{
label: '联系人手机',
field: 'contactMobile',
field: 'id',
show: false,
component: 'Input',
},
{
label: '账号额度',
field: 'accountNumber',
component: 'InputNumber',
componentProps: {
controls: false,
controls: true,
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>
import { Space } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import DedailModal from './DedailModal.vue'
import TenantModal from './TenantFormModal.vue'
import AuthSettingModal from './AuthSettingModal.vue'
import { columns, searchFormSchema } from './data'
import { BasicTable, TableAction, useTable } from '@/components/Table'
import { deleteTenant, getTenantList } from '@/api/system/tenant'
import type { Tenant } from '@/api/system/tenant/types'
import { useModal } from '@/components/Modal'
import { useMessage } from '@/hooks/web/useMessage'
import { usePermission } from '@/hooks/web/usePermission'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemTenant' })
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({
api: getTenantList,
@ -27,11 +33,11 @@ const [registerTable, { reload }] = useTable({
canResize: false,
useSearchForm: true,
actionColumn: {
width: 140,
width: 300,
title: '操作',
dataIndex: 'action',
fixed: 'right',
auth: ['tenant_delete', 'tenant_edit'],
// auth: ['tenant_delete', 'tenant_edit'],
},
})
@ -48,28 +54,42 @@ async function handleDelete(id: string) {
<template>
<div>
<BasicTable :api="async () => ([] as Tenant[])" @register="registerTable">
<template v-if="hasPermission('tenant_add')" #tableTitle>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新建
</a-button>
<template #tableTitle>
<Space>
<a-button type="primary" @click="openModal">
<PlusOutlined />
新建
</a-button>
</Space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:eye-outlined',
label: '查看',
// auth: 'tenant_edit',
onClick: () => openDetailModal(true, record),
},
{
icon: 'i-ant-design:edit-outlined',
label: '编辑',
auth: 'tenant_edit',
// auth: 'tenant_edit',
onClick: () => openModal(true, record),
},
{
icon: 'i-ant-design:setting-outlined',
label: '授权配置',
// auth: 'tenant_edit',
onClick: () => openAuthSettingModal(true, record),
},
{
icon: 'i-ant-design:delete-outlined',
label: '删除',
danger: true,
auth: 'tenant_delete',
// auth: 'tenant_delete',
popConfirm: {
title: '确定要删除数据吗?',
placement: 'left',
@ -83,5 +103,7 @@ async function handleDelete(id: string) {
</BasicTable>
<TenantModal @register="registerModal" @success="reload()" />
<DedailModal @register="registerDetailModal" @success="reload()" />
<AuthSettingModal @register="registerAuthSettingModal" @success="reload()" />
</div>
</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 { BasicModal, useModalInner } from '@/components/Modal'
import { createUser, updateUser } from '@/api/system/user'
import type { SystemUser } from '@/api/system/user/types'
defineOptions({ name: 'UserFormModal' })
@ -16,21 +15,30 @@ const { t } = useI18n()
const isUpdate = ref(false)
const [registerForm, { setFieldsValue, validate }] = useForm({
name: 'user-form',
labelWidth: 120,
baseColProps: { span: 24 },
labelWidth: 100,
baseColProps: { span: 12 },
schemas: getFormSchema(isUpdate),
showActionButtonGroup: false,
actionColOptions: { span: 23 },
})
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: SystemUser) => {
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
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() {
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 })
await (isUpdate.value ? updateUser(values) : createUser(values))
closeModal()
@ -46,6 +54,7 @@ async function handleSubmit() {
<template>
<BasicModal
width="55%"
:title="isUpdate ? t('action.edit') : t('action.create')"
:after-close="() => isUpdate = false"
@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 type { BasicColumn, FormSchema } from '@/components/Table'
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[] = [
{
@ -20,7 +22,6 @@ export const columns: BasicColumn[] = [
{
title: '用户姓名',
dataIndex: 'realName',
width: 120,
customRender({ record, value }) {
return h('div', [
h(
@ -38,7 +39,7 @@ export const columns: BasicColumn[] = [
},
{
title: '手机号码',
dataIndex: 'mobile',
dataIndex: 'phone',
width: 120,
},
{
@ -56,50 +57,32 @@ export const columns: BasicColumn[] = [
dataIndex: 'deptName',
width: 120,
},
{
title: '用户平台',
dataIndex: 'userTypeName',
width: 120,
},
]
export const searchFormSchema: FormSchema[] = [
{
label: '账号',
label: '登陆账号',
field: 'account',
component: 'Input',
colProps: { span: 6 },
},
{
label: '手机号码',
field: 'mobile',
component: 'InputNumber',
componentProps: {
controls: false,
precision: 0,
},
label: '用户姓名',
field: 'realName',
component: 'Input',
colProps: { span: 6 },
},
{
label: '所属部门',
field: 'tenantId',
component: 'ApiTreeSelect',
label: '用户平台',
field: 'userType',
component: 'ApiSelect',
componentProps: {
async api() {
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',
api: () => getSystemDictionary('user_type'),
},
colProps: { span: 6 },
},
@ -119,40 +102,8 @@ export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] {
componentProps: {
plain: false,
},
},
{
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()
},
},
]
colProps: {
span: 24,
},
},
{
@ -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) {
updateRole([])
updateDept([])
updatePost([])
return
}
@ -206,58 +169,84 @@ export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] {
.then((res) => {
updateDept(res)
})
getPostTree({ tenantId: value })
.then((res) => {
updatePost(res)
})
},
}
},
colProps: {
span: 24,
},
},
{
label: '所属部门',
field: 'deptId',
label: '用户平台',
field: 'userType',
component: 'ApiSelect',
required: true,
component: 'TreeSelect',
componentProps: {
treeData: [],
fieldNames: {
label: 'title',
value: 'id',
},
api: () => getSystemDictionary('user_type'),
},
},
{
label: '所属角色',
field: 'roleId',
label: '账号',
field: 'account',
required: true,
component: 'TreeSelect',
componentProps: {
treeData: [],
fieldNames: {
label: 'title',
value: 'id',
},
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()
},
},
]
},
},
{
field: 'UserInfo',
label: '用户信息',
field: 'MoreInfo',
label: '详细信息',
component: 'Divider',
componentProps: {
plain: false,
},
colProps: {
span: 24,
},
},
{
label: '用户姓名',
field: 'realName',
label: '用户昵称',
field: 'name',
component: 'Input',
required: true,
},
{
label: '用户头像',
field: 'avatar',
component: 'FileUpload',
componentProps: {
maxCount: 1,
fileType: 'image',
},
label: '用户姓名',
field: 'realName',
component: 'Input',
required: true,
},
{
label: '用户邮箱',
@ -287,23 +276,89 @@ export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] {
options: [
{
label: '男',
value: 0,
value: 1,
},
{
label: '女',
value: 1,
value: 0,
},
{
label: '未知',
value: 2,
value: -1,
},
],
},
},
{
label: '备注',
field: 'remark',
component: 'InputTextArea',
label: '用户生日',
field: 'birthday',
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>
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 { columns, searchFormSchema } from './data'
import { DeptTree } from './components'
import { useUserActions } from './composables/useUserActions'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useModal } from '@/components/Modal'
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 { usePermission } from '@/hooks/web/usePermission'
import { ImportModal } from '@/components/ImportModal'
// import { usePermission } from '@/hooks/web/usePermission'
defineOptions({ name: 'SystemUser' })
const { t } = useI18n()
const { createMessage } = useMessage()
const [registerModal, { openModal }] = useModal<SystemUser>()
const { hasPermission } = usePermission()
// const { hasPermission } = usePermission()
const [registerTable, { reload }] = useTable({
const [registerTable, tableMethods] = useTable({
api(params) {
return getUserList(params)
return getUserList({
...params,
deptId: selectedDeptId,
})
},
rowKey: 'id',
columns,
formConfig: {
labelWidth: 80,
schemas: searchFormSchema,
autoSubmitOnEnter: true,
},
useSearchForm: true,
bordered: true,
canResize: false,
rowSelection: {},
actionColumn: {
width: 140,
title: t('common.action'),
dataIndex: 'action',
fixed: 'right',
auth: ['user_edit', 'user_delete'],
// auth: ['user_edit', 'user_delete'],
},
})
async function handleDelete(id: string) {
try {
await deleteUser(id)
createMessage.success(t('common.delSuccessText'))
reload()
}
catch {}
let selectedDeptId: string
function onSelectedDept(id: string) {
selectedDeptId = id
tableMethods.setPagination({ current: 1 })
tableMethods.reload()
}
const {
handleDelete,
resetPassword,
unlockUser,
handleExportUsers,
registerImportModal,
importModalProps,
isImportCovered,
openImportModal,
} = useUserActions(tableMethods)
</script>
<template>
<div>
<BasicTable :api="async () => ([] as SystemUser[])" @register="registerTable">
<template v-if="hasPermission('user_add')" #tableTitle>
<a-button type="primary" @click="openModal">
<PlusOutlined />
{{ t('action.create') }}
</a-button>
</template>
<div flex="~">
<DeptTree my="12px" ml="12px" @select="onSelectedDept" />
<div flex="1" w-0>
<BasicTable :api="async () => ([] as SystemUser[])" @register="registerTable">
<!-- v-if="hasPermission('user_add')" -->
<template #tableTitle>
<Space>
<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 v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:edit-outlined',
label: t('action.edit'),
onClick: () => openModal(true, record),
auth: 'user_edit',
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: t('action.delete'),
auth: 'user_delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: () => handleDelete(record.id),
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'i-ant-design:edit-outlined',
label: t('action.edit'),
onClick: () => openModal(true, record),
// auth: 'user_edit',
},
{
icon: 'i-ant-design:delete-outlined',
danger: true,
label: t('action.delete'),
// auth: 'user_delete',
popConfirm: {
title: t('common.delMessage'),
placement: 'left',
confirm: () => handleDelete(record.id),
},
},
},
]"
/>
]"
/>
</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>
</template>

Loading…
Cancel
Save