Commit 0de7fdae76cc81442c2717808013bb496cdfa045

Authored by 芋道源码
Committed by Gitee
2 parents dcea527b 7d3c59af

!244 完善用户权限

Merge pull request !244 from xingyu/master
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) =&gt; { @@ -95,6 +99,7 @@ const handleDetail = async (row: RoleVO) =&gt; {
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(() =&gt; { @@ -308,7 +342,7 @@ onMounted(() =&gt; {
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 &#39;@/hooks/web/useTable&#39; @@ -7,7 +7,7 @@ import { useTable } from &#39;@/hooks/web/useTable&#39;
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&lt;FormExpose&gt;() // 表单 Ref @@ -42,7 +46,6 @@ const formRef = ref&lt;FormExpose&gt;() // 表单 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 = () =&gt; { @@ -58,7 +61,6 @@ const handleCreate = () =&gt; {
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) =&gt; { @@ -69,13 +71,8 @@ const handleUpdate = async (row: any) =&gt; {
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 () =&gt; { @@ -163,20 +160,23 @@ onMounted(async () =&gt; {
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 &#39;@/hooks/web/useTable&#39; @@ -23,14 +24,19 @@ import { useTable } from &#39;@/hooks/web/useTable&#39;
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 }) =&gt; { @@ -74,6 +80,10 @@ const handleDeptNodeClick = (data: { [key: string]: any }) =&gt; {
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) =&gt; { @@ -164,7 +174,37 @@ const handleResetPwd = (row: UserVO) =&gt; {
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&lt;CrudSchema[]&gt;([ @@ -152,7 +152,7 @@ const crudSchemas = reactive&lt;CrudSchema[]&gt;([
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 &#39;vite-plugin-windicss&#39; @@ -6,7 +6,7 @@ import WindiCSS from &#39;vite-plugin-windicss&#39;
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 =&gt; { @@ -55,7 +55,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; {
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',