Commit 0de7fdae76cc81442c2717808013bb496cdfa045
Committed by
Gitee
!244 完善用户权限
Merge pull request !244 from xingyu/master
Showing
13 changed files
with
248 additions
and
78 deletions
README.md
| ... | ... | @@ -204,11 +204,11 @@ ps:核心功能已经实现,正在对接微信小程序中... |
| 204 | 204 | | 框架 | 说明 | 版本 | |
| 205 | 205 | |----------------------------------------------------------------------|------------------|--------| |
| 206 | 206 | | [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.37 | |
| 207 | -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.3 | | |
| 208 | -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.11 | | |
| 207 | +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 | | |
| 208 | +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 | | |
| 209 | 209 | | [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.7.4 | |
| 210 | 210 | | [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.17 | |
| 211 | -| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.1.10 | | |
| 211 | +| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.0 | | |
| 212 | 212 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | |
| 213 | 213 | | [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | |
| 214 | 214 | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java
| ... | ... | @@ -12,8 +12,8 @@ import java.util.Set; |
| 12 | 12 | @Data |
| 13 | 13 | public class PermissionAssignUserRoleReqVO { |
| 14 | 14 | |
| 15 | - @ApiModelProperty(value = "角色编号", required = true, example = "1") | |
| 16 | - @NotNull(message = "角色编号不能为空") | |
| 15 | + @ApiModelProperty(value = "用户编号", required = true, example = "1") | |
| 16 | + @NotNull(message = "用户编号不能为空") | |
| 17 | 17 | private Long userId; |
| 18 | 18 | |
| 19 | 19 | @ApiModelProperty(value = "角色编号列表", example = "1,3,5") | ... | ... |
yudao-ui-admin-vue3/README.md
| ... | ... | @@ -27,15 +27,15 @@ |
| 27 | 27 | | 框架 | 说明 | 版本 | |
| 28 | 28 | | --- | --- |--------| |
| 29 | 29 | | [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 | |
| 30 | -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.3 | | |
| 31 | -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.11 | | |
| 32 | -| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 | | |
| 30 | +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 | | |
| 31 | +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 | | |
| 32 | +| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 | | |
| 33 | 33 | | [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.17 | |
| 34 | -| [vueuse](https://vueuse.org//) | 常用工具集 | 9.0.0 | | |
| 35 | -| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.1.10 | | |
| 36 | -| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.3 | | |
| 37 | -| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | | |
| 38 | -| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | | |
| 34 | +| [vueuse](https://vueuse.org//) | 常用工具集 | 9.0.2 | | |
| 35 | +| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.0 | | |
| 36 | +| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.3 | | |
| 37 | +| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | | |
| 38 | +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | | |
| 39 | 39 | | [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.14 | |
| 40 | 40 | |
| 41 | 41 | ### 推荐 VScode 开发,插件如下 | ... | ... |
yudao-ui-admin-vue3/package.json
| ... | ... | @@ -26,7 +26,7 @@ |
| 26 | 26 | }, |
| 27 | 27 | "dependencies": { |
| 28 | 28 | "@iconify/iconify": "^2.2.1", |
| 29 | - "@vueuse/core": "^9.0.0", | |
| 29 | + "@vueuse/core": "^9.0.2", | |
| 30 | 30 | "@wangeditor/editor": "^5.1.14", |
| 31 | 31 | "@wangeditor/editor-for-vue": "^5.1.10", |
| 32 | 32 | "@zxcvbn-ts/core": "^2.0.3", |
| ... | ... | @@ -35,7 +35,7 @@ |
| 35 | 35 | "dayjs": "^1.11.4", |
| 36 | 36 | "echarts": "^5.3.3", |
| 37 | 37 | "echarts-wordcloud": "^2.0.0", |
| 38 | - "element-plus": "2.2.11", | |
| 38 | + "element-plus": "2.2.12", | |
| 39 | 39 | "intro.js": "^6.0.0", |
| 40 | 40 | "jsencrypt": "^3.2.1", |
| 41 | 41 | "lodash-es": "^4.17.21", |
| ... | ... | @@ -48,20 +48,20 @@ |
| 48 | 48 | "url": "^0.11.0", |
| 49 | 49 | "vue": "3.2.37", |
| 50 | 50 | "vue-cropper": "^1.0.3", |
| 51 | - "vue-i18n": "9.1.10", | |
| 51 | + "vue-i18n": "9.2.0", | |
| 52 | 52 | "vue-router": "^4.1.3", |
| 53 | - "vue-types": "^4.2.0", | |
| 53 | + "vue-types": "^4.2.1", | |
| 54 | 54 | "web-storage-cache": "^1.1.1" |
| 55 | 55 | }, |
| 56 | 56 | "devDependencies": { |
| 57 | 57 | "@commitlint/cli": "^17.0.3", |
| 58 | 58 | "@commitlint/config-conventional": "^17.0.3", |
| 59 | - "@iconify/json": "^2.1.85", | |
| 59 | + "@iconify/json": "^2.1.86", | |
| 60 | 60 | "@intlify/vite-plugin-vue-i18n": "^5.0.1", |
| 61 | 61 | "@purge-icons/generated": "^0.8.1", |
| 62 | 62 | "@types/intro.js": "^5.1.0", |
| 63 | 63 | "@types/lodash-es": "^4.17.6", |
| 64 | - "@types/node": "^18.6.2", | |
| 64 | + "@types/node": "^18.6.3", | |
| 65 | 65 | "@types/nprogress": "^0.2.0", |
| 66 | 66 | "@types/qrcode": "^1.4.2", |
| 67 | 67 | "@types/qs": "^6.9.7", |
| ... | ... | @@ -70,9 +70,9 @@ |
| 70 | 70 | "@vitejs/plugin-vue": "^3.0.1", |
| 71 | 71 | "@vitejs/plugin-vue-jsx": "^2.0.0", |
| 72 | 72 | "autoprefixer": "^10.4.8", |
| 73 | - "eslint": "^8.20.0", | |
| 73 | + "eslint": "^8.21.0", | |
| 74 | 74 | "eslint-config-prettier": "^8.5.0", |
| 75 | - "eslint-define-config": "^1.5.1", | |
| 75 | + "eslint-define-config": "^1.6.0", | |
| 76 | 76 | "eslint-plugin-prettier": "^4.2.1", |
| 77 | 77 | "eslint-plugin-vue": "^9.3.0", |
| 78 | 78 | "less": "^4.1.3", |
| ... | ... | @@ -97,7 +97,7 @@ |
| 97 | 97 | "vite-plugin-eslint": "^1.7.0", |
| 98 | 98 | "vite-plugin-html": "^3.2.0", |
| 99 | 99 | "vite-plugin-purge-icons": "^0.8.2", |
| 100 | - "vite-plugin-style-import": "1.4.1", | |
| 100 | + "vite-plugin-style-import": "^2.0.0", | |
| 101 | 101 | "vite-plugin-svg-icons": "^2.0.1", |
| 102 | 102 | "vite-plugin-windicss": "^1.8.7", |
| 103 | 103 | "vue-tsc": "^0.39.4", | ... | ... |
yudao-ui-admin-vue3/src/api/system/permission/index.ts
0 → 100644
| 1 | +import { useAxios } from '@/hooks/web/useAxios' | |
| 2 | +import type { | |
| 3 | + PermissionAssignRoleDataScopeReqVO, | |
| 4 | + PermissionAssignRoleMenuReqVO, | |
| 5 | + PermissionAssignUserRoleReqVO | |
| 6 | +} from './types' | |
| 7 | + | |
| 8 | +const request = useAxios() | |
| 9 | + | |
| 10 | +// 查询角色拥有的菜单权限 | |
| 11 | +export const listRoleMenusApi = async (roleId: number) => { | |
| 12 | + return await request.get({ url: '/system/permission/list-role-resources?roleId=' + roleId }) | |
| 13 | +} | |
| 14 | + | |
| 15 | +// 赋予角色菜单权限 | |
| 16 | +export const assignRoleMenuApi = async (data: PermissionAssignRoleMenuReqVO) => { | |
| 17 | + return await request.post({ url: '/system/permission/assign-role-menu', data }) | |
| 18 | +} | |
| 19 | + | |
| 20 | +// 赋予角色数据权限 | |
| 21 | +export const assignRoleDataScopeApi = async (data: PermissionAssignRoleDataScopeReqVO) => { | |
| 22 | + return await request.post({ url: '/system/permission/assign-role-data-scope', data }) | |
| 23 | +} | |
| 24 | + | |
| 25 | +// 查询用户拥有的角色数组 | |
| 26 | +export const listUserRolesApi = async (userId: number) => { | |
| 27 | + return await request.get({ url: '/system/permission/list-user-roles?userId=' + userId }) | |
| 28 | +} | |
| 29 | + | |
| 30 | +// 赋予用户角色 | |
| 31 | +export const aassignUserRoleApi = async (data: PermissionAssignUserRoleReqVO) => { | |
| 32 | + return await request.post({ url: '/system/permission/assign-user-role', data }) | |
| 33 | +} | ... | ... |
yudao-ui-admin-vue3/src/api/system/permission/types.ts
0 → 100644
| 1 | +export type PermissionAssignUserRoleReqVO = { | |
| 2 | + userId: number | |
| 3 | + roleIds: number[] | |
| 4 | +} | |
| 5 | + | |
| 6 | +export type PermissionAssignRoleMenuReqVO = { | |
| 7 | + roleId: number | |
| 8 | + menuIds: number[] | |
| 9 | +} | |
| 10 | + | |
| 11 | +export type PermissionAssignRoleDataScopeReqVO = { | |
| 12 | + roleId: number | |
| 13 | + dataScope: number | |
| 14 | + dataScopeDeptIds: number[] | |
| 15 | +} | ... | ... |
yudao-ui-admin-vue3/src/api/system/role/index.ts
| ... | ... | @@ -4,31 +4,36 @@ import type { RoleVO } from './types' |
| 4 | 4 | const request = useAxios() |
| 5 | 5 | |
| 6 | 6 | // 查询角色列表 |
| 7 | -export const getRolePageApi = (params) => { | |
| 8 | - return request.get({ url: '/system/role/page', params }) | |
| 7 | +export const getRolePageApi = async (params) => { | |
| 8 | + return await request.get({ url: '/system/role/page', params }) | |
| 9 | +} | |
| 10 | + | |
| 11 | +// 查询角色(精简)列表 | |
| 12 | +export const listSimpleRolesApi = async () => { | |
| 13 | + return await request.get({ url: '/system/role/list-all-simple' }) | |
| 9 | 14 | } |
| 10 | 15 | |
| 11 | 16 | // 查询角色详情 |
| 12 | -export const getRoleApi = (id: number) => { | |
| 13 | - return request.get({ url: '/system/role/get?id=' + id }) | |
| 17 | +export const getRoleApi = async (id: number) => { | |
| 18 | + return await request.get({ url: '/system/role/get?id=' + id }) | |
| 14 | 19 | } |
| 15 | 20 | |
| 16 | 21 | // 新增角色 |
| 17 | -export const createRoleApi = (data: RoleVO) => { | |
| 18 | - return request.post({ url: '/system/role/create', data }) | |
| 22 | +export const createRoleApi = async (data: RoleVO) => { | |
| 23 | + return await request.post({ url: '/system/role/create', data }) | |
| 19 | 24 | } |
| 20 | 25 | |
| 21 | 26 | // 修改角色 |
| 22 | -export const updateRoleApi = (data: RoleVO) => { | |
| 23 | - return request.put({ url: '/system/role/update', data }) | |
| 27 | +export const updateRoleApi = async (data: RoleVO) => { | |
| 28 | + return await request.put({ url: '/system/role/update', data }) | |
| 24 | 29 | } |
| 25 | 30 | |
| 26 | 31 | // 修改角色状态 |
| 27 | -export const updateRoleStatusApi = (data: RoleVO) => { | |
| 28 | - return request.put({ url: '/system/role/update-status', data }) | |
| 32 | +export const updateRoleStatusApi = async (data: RoleVO) => { | |
| 33 | + return await request.put({ url: '/system/role/update-status', data }) | |
| 29 | 34 | } |
| 30 | 35 | |
| 31 | 36 | // 删除角色 |
| 32 | -export const deleteRoleApi = (id: number) => { | |
| 33 | - return request.delete({ url: '/system/role/delete?id=' + id }) | |
| 37 | +export const deleteRoleApi = async (id: number) => { | |
| 38 | + return await request.delete({ url: '/system/role/delete?id=' + id }) | |
| 34 | 39 | } | ... | ... |
yudao-ui-admin-vue3/src/components/CountTo/src/CountTo.vue
| ... | ... | @@ -157,7 +157,7 @@ const count = (timestamp: number) => { |
| 157 | 157 | } else { |
| 158 | 158 | state.printVal = state.printVal > endVal ? endVal : state.printVal |
| 159 | 159 | } |
| 160 | - state.displayValue = formatNumber(state.printVal) | |
| 160 | + state.displayValue = formatNumber(state.printVal!) | |
| 161 | 161 | if (progress < (state.localDuration as number)) { |
| 162 | 162 | state.rAF = requestAnimationFrame(count) |
| 163 | 163 | } else { | ... | ... |
yudao-ui-admin-vue3/src/views/system/role/index.vue
| 1 | 1 | <script setup lang="ts"> |
| 2 | 2 | import { onMounted, reactive, ref, unref } from 'vue' |
| 3 | 3 | import dayjs from 'dayjs' |
| 4 | -import { DICT_TYPE, getDictOptions } from '@/utils/dict' | |
| 5 | -import { useTable } from '@/hooks/web/useTable' | |
| 6 | -import { useI18n } from '@/hooks/web/useI18n' | |
| 7 | -import { FormExpose } from '@/components/Form' | |
| 8 | -import type { RoleVO } from '@/api/system/role/types' | |
| 9 | -import { rules, allSchemas } from './role.data' | |
| 10 | -import * as RoleApi from '@/api/system/role' | |
| 11 | -import Dialog from '@/components/Dialog/src/Dialog.vue' | |
| 12 | 4 | import { |
| 13 | 5 | ElForm, |
| 14 | 6 | ElFormItem, |
| ... | ... | @@ -20,6 +12,18 @@ import { |
| 20 | 12 | ElCard, |
| 21 | 13 | ElSwitch |
| 22 | 14 | } from 'element-plus' |
| 15 | +import { DICT_TYPE, getDictOptions } from '@/utils/dict' | |
| 16 | +import { useTable } from '@/hooks/web/useTable' | |
| 17 | +import { useI18n } from '@/hooks/web/useI18n' | |
| 18 | +import { FormExpose } from '@/components/Form' | |
| 19 | +import { rules, allSchemas } from './role.data' | |
| 20 | +import type { RoleVO } from '@/api/system/role/types' | |
| 21 | +import type { | |
| 22 | + PermissionAssignRoleDataScopeReqVO, | |
| 23 | + PermissionAssignRoleMenuReqVO | |
| 24 | +} from '@/api/system/permission/types' | |
| 25 | +import * as RoleApi from '@/api/system/role' | |
| 26 | +import * as PermissionApi from '@/api/system/permission' | |
| 23 | 27 | import { listSimpleMenusApi } from '@/api/system/menu' |
| 24 | 28 | import { listSimpleDeptApi } from '@/api/system/dept' |
| 25 | 29 | import { handleTree } from '@/utils/tree' |
| ... | ... | @@ -95,6 +99,7 @@ const handleDetail = async (row: RoleVO) => { |
| 95 | 99 | |
| 96 | 100 | // ========== 数据权限 ========== |
| 97 | 101 | const dataScopeForm = reactive({ |
| 102 | + id: 0, | |
| 98 | 103 | name: '', |
| 99 | 104 | code: '', |
| 100 | 105 | dataScope: 0, |
| ... | ... | @@ -111,33 +116,62 @@ const dialogScopeVisible = ref(false) |
| 111 | 116 | const dialogScopeTitle = ref('数据权限') |
| 112 | 117 | const actionScopeType = ref('') |
| 113 | 118 | const dataScopeDictDatas = ref() |
| 119 | +const defaultCheckedKeys = ref() | |
| 114 | 120 | // 选项 |
| 115 | 121 | const checkStrictly = ref(true) |
| 116 | 122 | const treeNodeAll = ref(false) |
| 123 | +// 全选/全不选 | |
| 124 | +const handleCheckedTreeNodeAll = () => { | |
| 125 | + treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) | |
| 126 | +} | |
| 117 | 127 | // 权限操作 |
| 118 | 128 | const handleScope = async (type: string, row: RoleVO) => { |
| 119 | - checkStrictly.value = true | |
| 120 | - treeNodeAll.value = false | |
| 121 | - dataScopeForm.dataScope = 0 | |
| 129 | + dataScopeForm.id = row.id | |
| 122 | 130 | dataScopeForm.name = row.name |
| 123 | 131 | dataScopeForm.code = row.code |
| 124 | 132 | if (type === 'menu') { |
| 125 | 133 | const menuRes = await listSimpleMenusApi() |
| 126 | 134 | treeOptions.value = handleTree(menuRes) |
| 127 | - } else if (type === 'dept') { | |
| 135 | + const role = await PermissionApi.listRoleMenusApi(row.id) | |
| 136 | + console.info(role) | |
| 137 | + if (role) { | |
| 138 | + // treeRef.value!.setCheckedKeys(role as unknown as Array<number>) | |
| 139 | + defaultCheckedKeys.value = role | |
| 140 | + } | |
| 141 | + } else if (type === 'data') { | |
| 128 | 142 | const deptRes = await listSimpleDeptApi() |
| 129 | 143 | treeOptions.value = handleTree(deptRes) |
| 144 | + const role = await RoleApi.getRoleApi(row.id) | |
| 145 | + console.info(role) | |
| 146 | + dataScopeForm.dataScope = role.dataScope | |
| 147 | + if (role.dataScopeDeptIds) { | |
| 148 | + // treeRef.value!.setCheckedKeys(role.dataScopeDeptIds as unknown as Array<number>, false) | |
| 149 | + defaultCheckedKeys.value = role.dataScopeDeptIds | |
| 150 | + } | |
| 130 | 151 | } |
| 131 | 152 | actionScopeType.value = type |
| 132 | 153 | dialogScopeVisible.value = true |
| 133 | 154 | } |
| 134 | -// 全选/全不选 | |
| 135 | -const handleCheckedTreeNodeAll = () => { | |
| 136 | - treeRef.value?.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) | |
| 137 | -} | |
| 138 | -// TODO:保存 | |
| 139 | -const submitScope = () => { | |
| 140 | - console.info() | |
| 155 | +// 保存权限 | |
| 156 | +const submitScope = async () => { | |
| 157 | + const keys = treeRef.value!.getCheckedKeys(false) as unknown as Array<number> | |
| 158 | + console.info(keys) | |
| 159 | + if ('data' === actionScopeType.value) { | |
| 160 | + const data = ref<PermissionAssignRoleDataScopeReqVO>({ | |
| 161 | + roleId: dataScopeForm.id, | |
| 162 | + dataScope: dataScopeForm.dataScope, | |
| 163 | + dataScopeDeptIds: dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM ? [] : keys | |
| 164 | + }) | |
| 165 | + await PermissionApi.assignRoleDataScopeApi(data.value) | |
| 166 | + } else if ('menu' === actionScopeType.value) { | |
| 167 | + const data = ref<PermissionAssignRoleMenuReqVO>({ | |
| 168 | + roleId: dataScopeForm.id, | |
| 169 | + menuIds: keys | |
| 170 | + }) | |
| 171 | + await PermissionApi.assignRoleMenuApi(data.value) | |
| 172 | + } | |
| 173 | + ElMessage.success(t('common.updateSuccess')) | |
| 174 | + dialogScopeVisible.value = false | |
| 141 | 175 | } |
| 142 | 176 | const init = () => { |
| 143 | 177 | dataScopeDictDatas.value = getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) |
| ... | ... | @@ -308,7 +342,7 @@ onMounted(() => { |
| 308 | 342 | ref="treeRef" |
| 309 | 343 | node-key="id" |
| 310 | 344 | show-checkbox |
| 311 | - default-expand-all | |
| 345 | + :default-checked-keys="defaultCheckedKeys" | |
| 312 | 346 | :check-strictly="!checkStrictly" |
| 313 | 347 | :props="defaultProps" |
| 314 | 348 | :data="treeOptions" | ... | ... |
yudao-ui-admin-vue3/src/views/system/tenantPackage/index.vue
| ... | ... | @@ -7,7 +7,7 @@ import { useTable } from '@/hooks/web/useTable' |
| 7 | 7 | import { useI18n } from '@/hooks/web/useI18n' |
| 8 | 8 | import { FormExpose } from '@/components/Form' |
| 9 | 9 | import { TenantPackageVO } from '@/api/system/tenantPackage/types' |
| 10 | -import { ElMessage, ElCard, ElCheckbox, ElTree } from 'element-plus' | |
| 10 | +import { ElMessage, ElCard, ElSwitch, ElTree } from 'element-plus' | |
| 11 | 11 | import { rules, allSchemas } from './tenantPackage.data' |
| 12 | 12 | import * as TenantPackageApi from '@/api/system/tenantPackage' |
| 13 | 13 | import { listSimpleMenusApi } from '@/api/system/menu' |
| ... | ... | @@ -21,11 +21,15 @@ const defaultProps = { |
| 21 | 21 | // ========== 创建菜单树结构 ========== |
| 22 | 22 | const menuOptions = ref([]) // 树形结构 |
| 23 | 23 | const treeRef = ref<InstanceType<typeof ElTree>>() |
| 24 | +const treeNodeAll = ref(false) | |
| 25 | +// 全选/全不选 | |
| 26 | +const handleCheckedTreeNodeAll = () => { | |
| 27 | + treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) | |
| 28 | +} | |
| 24 | 29 | const getTree = async () => { |
| 25 | 30 | const res = await listSimpleMenusApi() |
| 26 | 31 | menuOptions.value = handleTree(res) |
| 27 | 32 | } |
| 28 | -let menuCheckStrictly = true | |
| 29 | 33 | const menuExpand = ref(false) |
| 30 | 34 | const menuNodeAll = ref(false) |
| 31 | 35 | |
| ... | ... | @@ -42,7 +46,6 @@ const formRef = ref<FormExpose>() // 表单 Ref |
| 42 | 46 | const actionType = ref('') // 操作按钮的类型 |
| 43 | 47 | const dialogVisible = ref(false) // 是否显示弹出层 |
| 44 | 48 | const dialogTitle = ref('edit') // 弹出层标题 |
| 45 | -const menuParentId = ref() | |
| 46 | 49 | |
| 47 | 50 | // 设置标题 |
| 48 | 51 | const setDialogTile = (type: string) => { |
| ... | ... | @@ -58,7 +61,6 @@ const handleCreate = () => { |
| 58 | 61 | unref(formRef)?.getElFormRef()?.resetFields() |
| 59 | 62 | //重置菜单树 |
| 60 | 63 | unref(treeRef)?.setCheckedKeys([]) |
| 61 | - menuCheckStrictly = true | |
| 62 | 64 | menuExpand.value = false |
| 63 | 65 | menuNodeAll.value = false |
| 64 | 66 | } |
| ... | ... | @@ -69,13 +71,8 @@ const handleUpdate = async (row: any) => { |
| 69 | 71 | // 设置数据 |
| 70 | 72 | const res = await TenantPackageApi.getTenantPackageApi(row.id) |
| 71 | 73 | unref(formRef)?.setValues(res) |
| 72 | - // 设置菜单项 | |
| 73 | - // 设置为严格,避免设置父节点自动选中子节点,解决半选中问题 | |
| 74 | - menuCheckStrictly = true | |
| 75 | 74 | // 设置选中 |
| 76 | 75 | unref(treeRef)?.setCheckedKeys(res.menuIds) |
| 77 | - // 设置为非严格,继续使用半选中 | |
| 78 | - menuCheckStrictly = false | |
| 79 | 76 | } |
| 80 | 77 | // 提交按钮 |
| 81 | 78 | const submitForm = async () => { |
| ... | ... | @@ -163,20 +160,23 @@ onMounted(async () => { |
| 163 | 160 | <el-card class="box-card"> |
| 164 | 161 | <template #header> |
| 165 | 162 | <div class="card-header"> |
| 166 | - <el-checkbox>展开/折叠</el-checkbox> | |
| 167 | - <el-checkbox>全选/全不选</el-checkbox> | |
| 163 | + 全选/全不选: | |
| 164 | + <el-switch | |
| 165 | + v-model="treeNodeAll" | |
| 166 | + inline-prompt | |
| 167 | + active-text="是" | |
| 168 | + inactive-text="否" | |
| 169 | + @change="handleCheckedTreeNodeAll()" | |
| 170 | + /> | |
| 168 | 171 | </div> |
| 169 | 172 | </template> |
| 170 | - <!-- default-expand-all--> | |
| 171 | 173 | <el-tree |
| 172 | 174 | ref="treeRef" |
| 173 | 175 | node-key="id" |
| 174 | - v-model="menuParentId" | |
| 176 | + show-checkbox | |
| 175 | 177 | :props="defaultProps" |
| 176 | 178 | :data="menuOptions" |
| 177 | - show-checkbox | |
| 178 | - :check-strictly="menuCheckStrictly" | |
| 179 | - empty-text="加载菜单中..." | |
| 179 | + empty-text="加载中,请稍后" | |
| 180 | 180 | /> |
| 181 | 181 | </el-card> |
| 182 | 182 | </template> | ... | ... |
yudao-ui-admin-vue3/src/views/system/user/index.vue
| 1 | 1 | <script setup lang="ts"> |
| 2 | -import { onMounted, ref, unref, watch } from 'vue' | |
| 2 | +import { onMounted, reactive, ref, unref, watch } from 'vue' | |
| 3 | 3 | import dayjs from 'dayjs' |
| 4 | 4 | import { |
| 5 | + ElTag, | |
| 5 | 6 | ElInput, |
| 6 | 7 | ElCard, |
| 7 | 8 | ElTree, |
| ... | ... | @@ -23,14 +24,19 @@ import { useTable } from '@/hooks/web/useTable' |
| 23 | 24 | import { FormExpose } from '@/components/Form' |
| 24 | 25 | import type { UserVO } from '@/api/system/user/types' |
| 25 | 26 | import type { PostVO } from '@/api/system/post/types' |
| 27 | +import type { PermissionAssignUserRoleReqVO } from '@/api/system/permission/types' | |
| 26 | 28 | import { listSimpleDeptApi } from '@/api/system/dept' |
| 27 | 29 | import { listSimplePostsApi } from '@/api/system/post' |
| 30 | +import { listSimpleRolesApi } from '@/api/system/role' | |
| 31 | +import { aassignUserRoleApi, listUserRolesApi } from '@/api/system/permission' | |
| 28 | 32 | import { rules, allSchemas } from './user.data' |
| 29 | 33 | import * as UserApi from '@/api/system/user' |
| 30 | 34 | import download from '@/utils/download' |
| 35 | +import { useRouter } from 'vue-router' | |
| 31 | 36 | import { CommonStatusEnum } from '@/utils/constants' |
| 32 | 37 | import { getAccessToken, getTenantId } from '@/utils/auth' |
| 33 | 38 | import { useMessage } from '@/hooks/web/useMessage' |
| 39 | + | |
| 34 | 40 | const message = useMessage() |
| 35 | 41 | interface Tree { |
| 36 | 42 | id: number |
| ... | ... | @@ -74,6 +80,10 @@ const handleDeptNodeClick = (data: { [key: string]: any }) => { |
| 74 | 80 | tableTitle.value = data.name |
| 75 | 81 | methods.getList() |
| 76 | 82 | } |
| 83 | +const { push } = useRouter() | |
| 84 | +const handleDeptEdit = () => { | |
| 85 | + push('/system/dept') | |
| 86 | +} | |
| 77 | 87 | watch(filterText, (val) => { |
| 78 | 88 | treeRef.value!.filter(val) |
| 79 | 89 | }) |
| ... | ... | @@ -164,7 +174,37 @@ const handleResetPwd = (row: UserVO) => { |
| 164 | 174 | }) |
| 165 | 175 | }) |
| 166 | 176 | } |
| 167 | - | |
| 177 | +// 分配角色 | |
| 178 | +const roleDialogVisible = ref(false) | |
| 179 | +const roleOptions = ref() | |
| 180 | +const userRole = reactive({ | |
| 181 | + id: 0, | |
| 182 | + username: '', | |
| 183 | + nickname: '', | |
| 184 | + roleIds: [] | |
| 185 | +}) | |
| 186 | +const handleRole = async (row: UserVO) => { | |
| 187 | + userRole.id = row.id | |
| 188 | + userRole.username = row.username | |
| 189 | + userRole.nickname = row.nickname | |
| 190 | + // 获得角色拥有的权限集合 | |
| 191 | + const roles = await listUserRolesApi(row.id) | |
| 192 | + userRole.roleIds = roles | |
| 193 | + // 获取角色列表 | |
| 194 | + const roleOpt = await listSimpleRolesApi() | |
| 195 | + roleOptions.value = roleOpt | |
| 196 | + roleDialogVisible.value = true | |
| 197 | +} | |
| 198 | +// 提交 | |
| 199 | +const submitRole = async () => { | |
| 200 | + const data = ref<PermissionAssignUserRoleReqVO>({ | |
| 201 | + userId: userRole.id, | |
| 202 | + roleIds: userRole.roleIds | |
| 203 | + }) | |
| 204 | + await aassignUserRoleApi(data.value) | |
| 205 | + message.success(t('common.updateSuccess')) | |
| 206 | + roleDialogVisible.value = false | |
| 207 | +} | |
| 168 | 208 | // ========== 详情相关 ========== |
| 169 | 209 | const detailRef = ref() |
| 170 | 210 | |
| ... | ... | @@ -251,6 +291,9 @@ getList() |
| 251 | 291 | <template #header> |
| 252 | 292 | <div class="card-header"> |
| 253 | 293 | <span>部门列表</span> |
| 294 | + <el-button link class="button" type="primary" @click="handleDeptEdit"> | |
| 295 | + 修改部门 | |
| 296 | + </el-button> | |
| 254 | 297 | </div> |
| 255 | 298 | </template> |
| 256 | 299 | <el-input v-model="filterText" placeholder="搜索部门" /> |
| ... | ... | @@ -354,6 +397,14 @@ getList() |
| 354 | 397 | <el-button |
| 355 | 398 | link |
| 356 | 399 | type="primary" |
| 400 | + v-hasPermi="['system:permission:assign-user-role']" | |
| 401 | + @click="handleRole(row)" | |
| 402 | + > | |
| 403 | + <Icon icon="ep:key" class="mr-1px" /> 分配角色 | |
| 404 | + </el-button> | |
| 405 | + <el-button | |
| 406 | + link | |
| 407 | + type="primary" | |
| 357 | 408 | v-hasPermi="['system:user:delete']" |
| 358 | 409 | @click="delList(row.id, false)" |
| 359 | 410 | > |
| ... | ... | @@ -398,10 +449,14 @@ getList() |
| 398 | 449 | :data="detailRef" |
| 399 | 450 | > |
| 400 | 451 | <template #deptId="{ row }"> |
| 401 | - <span>{{ row.dept.name }}</span> | |
| 452 | + <span>{{ row.dept?.name }}</span> | |
| 402 | 453 | </template> |
| 403 | 454 | <template #postIds="{ row }"> |
| 404 | - <span>{{ row.dept.name }}</span> | |
| 455 | + <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> | |
| 456 | + <template v-for="postObj in postOptions"> | |
| 457 | + {{ post === postObj.id ? postObj.name : '' }} | |
| 458 | + </template> | |
| 459 | + </el-tag> | |
| 405 | 460 | </template> |
| 406 | 461 | <template #sex="{ row }"> |
| 407 | 462 | <DictTag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" /> |
| ... | ... | @@ -426,6 +481,34 @@ getList() |
| 426 | 481 | <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button> |
| 427 | 482 | </template> |
| 428 | 483 | </Dialog> |
| 484 | + <!-- 分配用户角色 --> | |
| 485 | + <Dialog v-model="roleDialogVisible" title="分配角色"> | |
| 486 | + <el-form :model="userRole" label-width="80px"> | |
| 487 | + <el-form-item label="用户名称"> | |
| 488 | + <el-input v-model="userRole.username" :disabled="true" /> | |
| 489 | + </el-form-item> | |
| 490 | + <el-form-item label="用户昵称"> | |
| 491 | + <el-input v-model="userRole.nickname" :disabled="true" /> | |
| 492 | + </el-form-item> | |
| 493 | + <el-form-item label="角色"> | |
| 494 | + <el-select v-model="userRole.roleIds" multiple> | |
| 495 | + <el-option | |
| 496 | + v-for="item in roleOptions" | |
| 497 | + :key="parseInt(item.id)" | |
| 498 | + :label="item.name" | |
| 499 | + :value="parseInt(item.id)" | |
| 500 | + /> | |
| 501 | + </el-select> | |
| 502 | + </el-form-item> | |
| 503 | + </el-form> | |
| 504 | + <!-- 操作按钮 --> | |
| 505 | + <template #footer> | |
| 506 | + <el-button type="primary" :loading="loading" @click="submitRole"> | |
| 507 | + {{ t('action.save') }} | |
| 508 | + </el-button> | |
| 509 | + <el-button @click="roleDialogVisible = false">{{ t('dialog.close') }}</el-button> | |
| 510 | + </template> | |
| 511 | + </Dialog> | |
| 429 | 512 | <!-- 导入 --> |
| 430 | 513 | <Dialog |
| 431 | 514 | v-model="importDialogVisible" | ... | ... |
yudao-ui-admin-vue3/src/views/system/user/user.data.ts
yudao-ui-admin-vue3/vite.config.ts
| ... | ... | @@ -6,7 +6,7 @@ import WindiCSS from 'vite-plugin-windicss' |
| 6 | 6 | import VueJsx from '@vitejs/plugin-vue-jsx' |
| 7 | 7 | import EslintPlugin from 'vite-plugin-eslint' |
| 8 | 8 | import VueI18n from '@intlify/vite-plugin-vue-i18n' |
| 9 | -import styleImport, { ElementPlusResolve } from 'vite-plugin-style-import' | |
| 9 | +import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import' | |
| 10 | 10 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' |
| 11 | 11 | import PurgeIcons from 'vite-plugin-purge-icons' |
| 12 | 12 | import DefineOptions from 'unplugin-vue-define-options/vite' |
| ... | ... | @@ -55,7 +55,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { |
| 55 | 55 | Vue(), |
| 56 | 56 | VueJsx(), |
| 57 | 57 | WindiCSS(), |
| 58 | - styleImport({ | |
| 58 | + createStyleImportPlugin({ | |
| 59 | 59 | resolves: [ElementPlusResolve()], |
| 60 | 60 | libs: [{ |
| 61 | 61 | libraryName: 'element-plus', | ... | ... |