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,11 +204,11 @@ ps:核心功能已经实现,正在对接微信小程序中... | ||
| 204 | | 框架 | 说明 | 版本 | | 204 | | 框架 | 说明 | 版本 | |
| 205 | |----------------------------------------------------------------------|------------------|--------| | 205 | |----------------------------------------------------------------------|------------------|--------| |
| 206 | | [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.37 | | 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 | | [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.7.4 | | 209 | | [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.7.4 | |
| 210 | | [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.17 | | 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 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | | 212 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | |
| 213 | | [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | | 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,8 +12,8 @@ import java.util.Set; | ||
| 12 | @Data | 12 | @Data |
| 13 | public class PermissionAssignUserRoleReqVO { | 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 | private Long userId; | 17 | private Long userId; |
| 18 | 18 | ||
| 19 | @ApiModelProperty(value = "角色编号列表", example = "1,3,5") | 19 | @ApiModelProperty(value = "角色编号列表", example = "1,3,5") |
yudao-ui-admin-vue3/README.md
| @@ -27,15 +27,15 @@ | @@ -27,15 +27,15 @@ | ||
| 27 | | 框架 | 说明 | 版本 | | 27 | | 框架 | 说明 | 版本 | |
| 28 | | --- | --- |--------| | 28 | | --- | --- |--------| |
| 29 | | [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 | | 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 | | [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.17 | | 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 | | [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.14 | | 39 | | [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.14 | |
| 40 | 40 | ||
| 41 | ### 推荐 VScode 开发,插件如下 | 41 | ### 推荐 VScode 开发,插件如下 |
yudao-ui-admin-vue3/package.json
| @@ -26,7 +26,7 @@ | @@ -26,7 +26,7 @@ | ||
| 26 | }, | 26 | }, |
| 27 | "dependencies": { | 27 | "dependencies": { |
| 28 | "@iconify/iconify": "^2.2.1", | 28 | "@iconify/iconify": "^2.2.1", |
| 29 | - "@vueuse/core": "^9.0.0", | 29 | + "@vueuse/core": "^9.0.2", |
| 30 | "@wangeditor/editor": "^5.1.14", | 30 | "@wangeditor/editor": "^5.1.14", |
| 31 | "@wangeditor/editor-for-vue": "^5.1.10", | 31 | "@wangeditor/editor-for-vue": "^5.1.10", |
| 32 | "@zxcvbn-ts/core": "^2.0.3", | 32 | "@zxcvbn-ts/core": "^2.0.3", |
| @@ -35,7 +35,7 @@ | @@ -35,7 +35,7 @@ | ||
| 35 | "dayjs": "^1.11.4", | 35 | "dayjs": "^1.11.4", |
| 36 | "echarts": "^5.3.3", | 36 | "echarts": "^5.3.3", |
| 37 | "echarts-wordcloud": "^2.0.0", | 37 | "echarts-wordcloud": "^2.0.0", |
| 38 | - "element-plus": "2.2.11", | 38 | + "element-plus": "2.2.12", |
| 39 | "intro.js": "^6.0.0", | 39 | "intro.js": "^6.0.0", |
| 40 | "jsencrypt": "^3.2.1", | 40 | "jsencrypt": "^3.2.1", |
| 41 | "lodash-es": "^4.17.21", | 41 | "lodash-es": "^4.17.21", |
| @@ -48,20 +48,20 @@ | @@ -48,20 +48,20 @@ | ||
| 48 | "url": "^0.11.0", | 48 | "url": "^0.11.0", |
| 49 | "vue": "3.2.37", | 49 | "vue": "3.2.37", |
| 50 | "vue-cropper": "^1.0.3", | 50 | "vue-cropper": "^1.0.3", |
| 51 | - "vue-i18n": "9.1.10", | 51 | + "vue-i18n": "9.2.0", |
| 52 | "vue-router": "^4.1.3", | 52 | "vue-router": "^4.1.3", |
| 53 | - "vue-types": "^4.2.0", | 53 | + "vue-types": "^4.2.1", |
| 54 | "web-storage-cache": "^1.1.1" | 54 | "web-storage-cache": "^1.1.1" |
| 55 | }, | 55 | }, |
| 56 | "devDependencies": { | 56 | "devDependencies": { |
| 57 | "@commitlint/cli": "^17.0.3", | 57 | "@commitlint/cli": "^17.0.3", |
| 58 | "@commitlint/config-conventional": "^17.0.3", | 58 | "@commitlint/config-conventional": "^17.0.3", |
| 59 | - "@iconify/json": "^2.1.85", | 59 | + "@iconify/json": "^2.1.86", |
| 60 | "@intlify/vite-plugin-vue-i18n": "^5.0.1", | 60 | "@intlify/vite-plugin-vue-i18n": "^5.0.1", |
| 61 | "@purge-icons/generated": "^0.8.1", | 61 | "@purge-icons/generated": "^0.8.1", |
| 62 | "@types/intro.js": "^5.1.0", | 62 | "@types/intro.js": "^5.1.0", |
| 63 | "@types/lodash-es": "^4.17.6", | 63 | "@types/lodash-es": "^4.17.6", |
| 64 | - "@types/node": "^18.6.2", | 64 | + "@types/node": "^18.6.3", |
| 65 | "@types/nprogress": "^0.2.0", | 65 | "@types/nprogress": "^0.2.0", |
| 66 | "@types/qrcode": "^1.4.2", | 66 | "@types/qrcode": "^1.4.2", |
| 67 | "@types/qs": "^6.9.7", | 67 | "@types/qs": "^6.9.7", |
| @@ -70,9 +70,9 @@ | @@ -70,9 +70,9 @@ | ||
| 70 | "@vitejs/plugin-vue": "^3.0.1", | 70 | "@vitejs/plugin-vue": "^3.0.1", |
| 71 | "@vitejs/plugin-vue-jsx": "^2.0.0", | 71 | "@vitejs/plugin-vue-jsx": "^2.0.0", |
| 72 | "autoprefixer": "^10.4.8", | 72 | "autoprefixer": "^10.4.8", |
| 73 | - "eslint": "^8.20.0", | 73 | + "eslint": "^8.21.0", |
| 74 | "eslint-config-prettier": "^8.5.0", | 74 | "eslint-config-prettier": "^8.5.0", |
| 75 | - "eslint-define-config": "^1.5.1", | 75 | + "eslint-define-config": "^1.6.0", |
| 76 | "eslint-plugin-prettier": "^4.2.1", | 76 | "eslint-plugin-prettier": "^4.2.1", |
| 77 | "eslint-plugin-vue": "^9.3.0", | 77 | "eslint-plugin-vue": "^9.3.0", |
| 78 | "less": "^4.1.3", | 78 | "less": "^4.1.3", |
| @@ -97,7 +97,7 @@ | @@ -97,7 +97,7 @@ | ||
| 97 | "vite-plugin-eslint": "^1.7.0", | 97 | "vite-plugin-eslint": "^1.7.0", |
| 98 | "vite-plugin-html": "^3.2.0", | 98 | "vite-plugin-html": "^3.2.0", |
| 99 | "vite-plugin-purge-icons": "^0.8.2", | 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 | "vite-plugin-svg-icons": "^2.0.1", | 101 | "vite-plugin-svg-icons": "^2.0.1", |
| 102 | "vite-plugin-windicss": "^1.8.7", | 102 | "vite-plugin-windicss": "^1.8.7", |
| 103 | "vue-tsc": "^0.39.4", | 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,31 +4,36 @@ import type { RoleVO } from './types' | ||
| 4 | const request = useAxios() | 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,7 +157,7 @@ const count = (timestamp: number) => { | ||
| 157 | } else { | 157 | } else { |
| 158 | state.printVal = state.printVal > endVal ? endVal : state.printVal | 158 | state.printVal = state.printVal > endVal ? endVal : state.printVal |
| 159 | } | 159 | } |
| 160 | - state.displayValue = formatNumber(state.printVal) | 160 | + state.displayValue = formatNumber(state.printVal!) |
| 161 | if (progress < (state.localDuration as number)) { | 161 | if (progress < (state.localDuration as number)) { |
| 162 | state.rAF = requestAnimationFrame(count) | 162 | state.rAF = requestAnimationFrame(count) |
| 163 | } else { | 163 | } else { |
yudao-ui-admin-vue3/src/views/system/role/index.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | import { onMounted, reactive, ref, unref } from 'vue' | 2 | import { onMounted, reactive, ref, unref } from 'vue' |
| 3 | import dayjs from 'dayjs' | 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 | import { | 4 | import { |
| 13 | ElForm, | 5 | ElForm, |
| 14 | ElFormItem, | 6 | ElFormItem, |
| @@ -20,6 +12,18 @@ import { | @@ -20,6 +12,18 @@ import { | ||
| 20 | ElCard, | 12 | ElCard, |
| 21 | ElSwitch | 13 | ElSwitch |
| 22 | } from 'element-plus' | 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 | import { listSimpleMenusApi } from '@/api/system/menu' | 27 | import { listSimpleMenusApi } from '@/api/system/menu' |
| 24 | import { listSimpleDeptApi } from '@/api/system/dept' | 28 | import { listSimpleDeptApi } from '@/api/system/dept' |
| 25 | import { handleTree } from '@/utils/tree' | 29 | import { handleTree } from '@/utils/tree' |
| @@ -95,6 +99,7 @@ const handleDetail = async (row: RoleVO) => { | @@ -95,6 +99,7 @@ const handleDetail = async (row: RoleVO) => { | ||
| 95 | 99 | ||
| 96 | // ========== 数据权限 ========== | 100 | // ========== 数据权限 ========== |
| 97 | const dataScopeForm = reactive({ | 101 | const dataScopeForm = reactive({ |
| 102 | + id: 0, | ||
| 98 | name: '', | 103 | name: '', |
| 99 | code: '', | 104 | code: '', |
| 100 | dataScope: 0, | 105 | dataScope: 0, |
| @@ -111,33 +116,62 @@ const dialogScopeVisible = ref(false) | @@ -111,33 +116,62 @@ const dialogScopeVisible = ref(false) | ||
| 111 | const dialogScopeTitle = ref('数据权限') | 116 | const dialogScopeTitle = ref('数据权限') |
| 112 | const actionScopeType = ref('') | 117 | const actionScopeType = ref('') |
| 113 | const dataScopeDictDatas = ref() | 118 | const dataScopeDictDatas = ref() |
| 119 | +const defaultCheckedKeys = ref() | ||
| 114 | // 选项 | 120 | // 选项 |
| 115 | const checkStrictly = ref(true) | 121 | const checkStrictly = ref(true) |
| 116 | const treeNodeAll = ref(false) | 122 | const treeNodeAll = ref(false) |
| 123 | +// 全选/全不选 | ||
| 124 | +const handleCheckedTreeNodeAll = () => { | ||
| 125 | + treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) | ||
| 126 | +} | ||
| 117 | // 权限操作 | 127 | // 权限操作 |
| 118 | const handleScope = async (type: string, row: RoleVO) => { | 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 | dataScopeForm.name = row.name | 130 | dataScopeForm.name = row.name |
| 123 | dataScopeForm.code = row.code | 131 | dataScopeForm.code = row.code |
| 124 | if (type === 'menu') { | 132 | if (type === 'menu') { |
| 125 | const menuRes = await listSimpleMenusApi() | 133 | const menuRes = await listSimpleMenusApi() |
| 126 | treeOptions.value = handleTree(menuRes) | 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 | const deptRes = await listSimpleDeptApi() | 142 | const deptRes = await listSimpleDeptApi() |
| 129 | treeOptions.value = handleTree(deptRes) | 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 | actionScopeType.value = type | 152 | actionScopeType.value = type |
| 132 | dialogScopeVisible.value = true | 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 | const init = () => { | 176 | const init = () => { |
| 143 | dataScopeDictDatas.value = getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) | 177 | dataScopeDictDatas.value = getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) |
| @@ -308,7 +342,7 @@ onMounted(() => { | @@ -308,7 +342,7 @@ onMounted(() => { | ||
| 308 | ref="treeRef" | 342 | ref="treeRef" |
| 309 | node-key="id" | 343 | node-key="id" |
| 310 | show-checkbox | 344 | show-checkbox |
| 311 | - default-expand-all | 345 | + :default-checked-keys="defaultCheckedKeys" |
| 312 | :check-strictly="!checkStrictly" | 346 | :check-strictly="!checkStrictly" |
| 313 | :props="defaultProps" | 347 | :props="defaultProps" |
| 314 | :data="treeOptions" | 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 +7,7 @@ import { useTable } from '@/hooks/web/useTable' | ||
| 7 | import { useI18n } from '@/hooks/web/useI18n' | 7 | import { useI18n } from '@/hooks/web/useI18n' |
| 8 | import { FormExpose } from '@/components/Form' | 8 | import { FormExpose } from '@/components/Form' |
| 9 | import { TenantPackageVO } from '@/api/system/tenantPackage/types' | 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 | import { rules, allSchemas } from './tenantPackage.data' | 11 | import { rules, allSchemas } from './tenantPackage.data' |
| 12 | import * as TenantPackageApi from '@/api/system/tenantPackage' | 12 | import * as TenantPackageApi from '@/api/system/tenantPackage' |
| 13 | import { listSimpleMenusApi } from '@/api/system/menu' | 13 | import { listSimpleMenusApi } from '@/api/system/menu' |
| @@ -21,11 +21,15 @@ const defaultProps = { | @@ -21,11 +21,15 @@ const defaultProps = { | ||
| 21 | // ========== 创建菜单树结构 ========== | 21 | // ========== 创建菜单树结构 ========== |
| 22 | const menuOptions = ref([]) // 树形结构 | 22 | const menuOptions = ref([]) // 树形结构 |
| 23 | const treeRef = ref<InstanceType<typeof ElTree>>() | 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 | const getTree = async () => { | 29 | const getTree = async () => { |
| 25 | const res = await listSimpleMenusApi() | 30 | const res = await listSimpleMenusApi() |
| 26 | menuOptions.value = handleTree(res) | 31 | menuOptions.value = handleTree(res) |
| 27 | } | 32 | } |
| 28 | -let menuCheckStrictly = true | ||
| 29 | const menuExpand = ref(false) | 33 | const menuExpand = ref(false) |
| 30 | const menuNodeAll = ref(false) | 34 | const menuNodeAll = ref(false) |
| 31 | 35 | ||
| @@ -42,7 +46,6 @@ const formRef = ref<FormExpose>() // 表单 Ref | @@ -42,7 +46,6 @@ const formRef = ref<FormExpose>() // 表单 Ref | ||
| 42 | const actionType = ref('') // 操作按钮的类型 | 46 | const actionType = ref('') // 操作按钮的类型 |
| 43 | const dialogVisible = ref(false) // 是否显示弹出层 | 47 | const dialogVisible = ref(false) // 是否显示弹出层 |
| 44 | const dialogTitle = ref('edit') // 弹出层标题 | 48 | const dialogTitle = ref('edit') // 弹出层标题 |
| 45 | -const menuParentId = ref() | ||
| 46 | 49 | ||
| 47 | // 设置标题 | 50 | // 设置标题 |
| 48 | const setDialogTile = (type: string) => { | 51 | const setDialogTile = (type: string) => { |
| @@ -58,7 +61,6 @@ const handleCreate = () => { | @@ -58,7 +61,6 @@ const handleCreate = () => { | ||
| 58 | unref(formRef)?.getElFormRef()?.resetFields() | 61 | unref(formRef)?.getElFormRef()?.resetFields() |
| 59 | //重置菜单树 | 62 | //重置菜单树 |
| 60 | unref(treeRef)?.setCheckedKeys([]) | 63 | unref(treeRef)?.setCheckedKeys([]) |
| 61 | - menuCheckStrictly = true | ||
| 62 | menuExpand.value = false | 64 | menuExpand.value = false |
| 63 | menuNodeAll.value = false | 65 | menuNodeAll.value = false |
| 64 | } | 66 | } |
| @@ -69,13 +71,8 @@ const handleUpdate = async (row: any) => { | @@ -69,13 +71,8 @@ const handleUpdate = async (row: any) => { | ||
| 69 | // 设置数据 | 71 | // 设置数据 |
| 70 | const res = await TenantPackageApi.getTenantPackageApi(row.id) | 72 | const res = await TenantPackageApi.getTenantPackageApi(row.id) |
| 71 | unref(formRef)?.setValues(res) | 73 | unref(formRef)?.setValues(res) |
| 72 | - // 设置菜单项 | ||
| 73 | - // 设置为严格,避免设置父节点自动选中子节点,解决半选中问题 | ||
| 74 | - menuCheckStrictly = true | ||
| 75 | // 设置选中 | 74 | // 设置选中 |
| 76 | unref(treeRef)?.setCheckedKeys(res.menuIds) | 75 | unref(treeRef)?.setCheckedKeys(res.menuIds) |
| 77 | - // 设置为非严格,继续使用半选中 | ||
| 78 | - menuCheckStrictly = false | ||
| 79 | } | 76 | } |
| 80 | // 提交按钮 | 77 | // 提交按钮 |
| 81 | const submitForm = async () => { | 78 | const submitForm = async () => { |
| @@ -163,20 +160,23 @@ onMounted(async () => { | @@ -163,20 +160,23 @@ onMounted(async () => { | ||
| 163 | <el-card class="box-card"> | 160 | <el-card class="box-card"> |
| 164 | <template #header> | 161 | <template #header> |
| 165 | <div class="card-header"> | 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 | </div> | 171 | </div> |
| 169 | </template> | 172 | </template> |
| 170 | - <!-- default-expand-all--> | ||
| 171 | <el-tree | 173 | <el-tree |
| 172 | ref="treeRef" | 174 | ref="treeRef" |
| 173 | node-key="id" | 175 | node-key="id" |
| 174 | - v-model="menuParentId" | 176 | + show-checkbox |
| 175 | :props="defaultProps" | 177 | :props="defaultProps" |
| 176 | :data="menuOptions" | 178 | :data="menuOptions" |
| 177 | - show-checkbox | ||
| 178 | - :check-strictly="menuCheckStrictly" | ||
| 179 | - empty-text="加载菜单中..." | 179 | + empty-text="加载中,请稍后" |
| 180 | /> | 180 | /> |
| 181 | </el-card> | 181 | </el-card> |
| 182 | </template> | 182 | </template> |
yudao-ui-admin-vue3/src/views/system/user/index.vue
| 1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
| 2 | -import { onMounted, ref, unref, watch } from 'vue' | 2 | +import { onMounted, reactive, ref, unref, watch } from 'vue' |
| 3 | import dayjs from 'dayjs' | 3 | import dayjs from 'dayjs' |
| 4 | import { | 4 | import { |
| 5 | + ElTag, | ||
| 5 | ElInput, | 6 | ElInput, |
| 6 | ElCard, | 7 | ElCard, |
| 7 | ElTree, | 8 | ElTree, |
| @@ -23,14 +24,19 @@ import { useTable } from '@/hooks/web/useTable' | @@ -23,14 +24,19 @@ import { useTable } from '@/hooks/web/useTable' | ||
| 23 | import { FormExpose } from '@/components/Form' | 24 | import { FormExpose } from '@/components/Form' |
| 24 | import type { UserVO } from '@/api/system/user/types' | 25 | import type { UserVO } from '@/api/system/user/types' |
| 25 | import type { PostVO } from '@/api/system/post/types' | 26 | import type { PostVO } from '@/api/system/post/types' |
| 27 | +import type { PermissionAssignUserRoleReqVO } from '@/api/system/permission/types' | ||
| 26 | import { listSimpleDeptApi } from '@/api/system/dept' | 28 | import { listSimpleDeptApi } from '@/api/system/dept' |
| 27 | import { listSimplePostsApi } from '@/api/system/post' | 29 | import { listSimplePostsApi } from '@/api/system/post' |
| 30 | +import { listSimpleRolesApi } from '@/api/system/role' | ||
| 31 | +import { aassignUserRoleApi, listUserRolesApi } from '@/api/system/permission' | ||
| 28 | import { rules, allSchemas } from './user.data' | 32 | import { rules, allSchemas } from './user.data' |
| 29 | import * as UserApi from '@/api/system/user' | 33 | import * as UserApi from '@/api/system/user' |
| 30 | import download from '@/utils/download' | 34 | import download from '@/utils/download' |
| 35 | +import { useRouter } from 'vue-router' | ||
| 31 | import { CommonStatusEnum } from '@/utils/constants' | 36 | import { CommonStatusEnum } from '@/utils/constants' |
| 32 | import { getAccessToken, getTenantId } from '@/utils/auth' | 37 | import { getAccessToken, getTenantId } from '@/utils/auth' |
| 33 | import { useMessage } from '@/hooks/web/useMessage' | 38 | import { useMessage } from '@/hooks/web/useMessage' |
| 39 | + | ||
| 34 | const message = useMessage() | 40 | const message = useMessage() |
| 35 | interface Tree { | 41 | interface Tree { |
| 36 | id: number | 42 | id: number |
| @@ -74,6 +80,10 @@ const handleDeptNodeClick = (data: { [key: string]: any }) => { | @@ -74,6 +80,10 @@ const handleDeptNodeClick = (data: { [key: string]: any }) => { | ||
| 74 | tableTitle.value = data.name | 80 | tableTitle.value = data.name |
| 75 | methods.getList() | 81 | methods.getList() |
| 76 | } | 82 | } |
| 83 | +const { push } = useRouter() | ||
| 84 | +const handleDeptEdit = () => { | ||
| 85 | + push('/system/dept') | ||
| 86 | +} | ||
| 77 | watch(filterText, (val) => { | 87 | watch(filterText, (val) => { |
| 78 | treeRef.value!.filter(val) | 88 | treeRef.value!.filter(val) |
| 79 | }) | 89 | }) |
| @@ -164,7 +174,37 @@ const handleResetPwd = (row: UserVO) => { | @@ -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 | const detailRef = ref() | 209 | const detailRef = ref() |
| 170 | 210 | ||
| @@ -251,6 +291,9 @@ getList() | @@ -251,6 +291,9 @@ getList() | ||
| 251 | <template #header> | 291 | <template #header> |
| 252 | <div class="card-header"> | 292 | <div class="card-header"> |
| 253 | <span>部门列表</span> | 293 | <span>部门列表</span> |
| 294 | + <el-button link class="button" type="primary" @click="handleDeptEdit"> | ||
| 295 | + 修改部门 | ||
| 296 | + </el-button> | ||
| 254 | </div> | 297 | </div> |
| 255 | </template> | 298 | </template> |
| 256 | <el-input v-model="filterText" placeholder="搜索部门" /> | 299 | <el-input v-model="filterText" placeholder="搜索部门" /> |
| @@ -354,6 +397,14 @@ getList() | @@ -354,6 +397,14 @@ getList() | ||
| 354 | <el-button | 397 | <el-button |
| 355 | link | 398 | link |
| 356 | type="primary" | 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 | v-hasPermi="['system:user:delete']" | 408 | v-hasPermi="['system:user:delete']" |
| 358 | @click="delList(row.id, false)" | 409 | @click="delList(row.id, false)" |
| 359 | > | 410 | > |
| @@ -398,10 +449,14 @@ getList() | @@ -398,10 +449,14 @@ getList() | ||
| 398 | :data="detailRef" | 449 | :data="detailRef" |
| 399 | > | 450 | > |
| 400 | <template #deptId="{ row }"> | 451 | <template #deptId="{ row }"> |
| 401 | - <span>{{ row.dept.name }}</span> | 452 | + <span>{{ row.dept?.name }}</span> |
| 402 | </template> | 453 | </template> |
| 403 | <template #postIds="{ row }"> | 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 | </template> | 460 | </template> |
| 406 | <template #sex="{ row }"> | 461 | <template #sex="{ row }"> |
| 407 | <DictTag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" /> | 462 | <DictTag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" /> |
| @@ -426,6 +481,34 @@ getList() | @@ -426,6 +481,34 @@ getList() | ||
| 426 | <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button> | 481 | <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button> |
| 427 | </template> | 482 | </template> |
| 428 | </Dialog> | 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 | <Dialog | 513 | <Dialog |
| 431 | v-model="importDialogVisible" | 514 | v-model="importDialogVisible" |
yudao-ui-admin-vue3/src/views/system/user/user.data.ts
| @@ -152,7 +152,7 @@ const crudSchemas = reactive<CrudSchema[]>([ | @@ -152,7 +152,7 @@ const crudSchemas = reactive<CrudSchema[]>([ | ||
| 152 | }, | 152 | }, |
| 153 | { | 153 | { |
| 154 | field: 'action', | 154 | field: 'action', |
| 155 | - width: '340px', | 155 | + width: '400px', |
| 156 | label: t('table.action'), | 156 | label: t('table.action'), |
| 157 | form: { | 157 | form: { |
| 158 | show: false | 158 | show: false |
yudao-ui-admin-vue3/vite.config.ts
| @@ -6,7 +6,7 @@ import WindiCSS from 'vite-plugin-windicss' | @@ -6,7 +6,7 @@ import WindiCSS from 'vite-plugin-windicss' | ||
| 6 | import VueJsx from '@vitejs/plugin-vue-jsx' | 6 | import VueJsx from '@vitejs/plugin-vue-jsx' |
| 7 | import EslintPlugin from 'vite-plugin-eslint' | 7 | import EslintPlugin from 'vite-plugin-eslint' |
| 8 | import VueI18n from '@intlify/vite-plugin-vue-i18n' | 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 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' | 10 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' |
| 11 | import PurgeIcons from 'vite-plugin-purge-icons' | 11 | import PurgeIcons from 'vite-plugin-purge-icons' |
| 12 | import DefineOptions from 'unplugin-vue-define-options/vite' | 12 | import DefineOptions from 'unplugin-vue-define-options/vite' |
| @@ -55,7 +55,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { | @@ -55,7 +55,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { | ||
| 55 | Vue(), | 55 | Vue(), |
| 56 | VueJsx(), | 56 | VueJsx(), |
| 57 | WindiCSS(), | 57 | WindiCSS(), |
| 58 | - styleImport({ | 58 | + createStyleImportPlugin({ |
| 59 | resolves: [ElementPlusResolve()], | 59 | resolves: [ElementPlusResolve()], |
| 60 | libs: [{ | 60 | libs: [{ |
| 61 | libraryName: 'element-plus', | 61 | libraryName: 'element-plus', |