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 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) =&gt; {
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(() =&gt; {
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 &#39;@/hooks/web/useTable&#39;
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&lt;FormExpose&gt;() // 表单 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 = () =&gt; {
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) =&gt; {
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 () =&gt; {
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 &#39;@/hooks/web/useTable&#39;
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 }) =&gt; {
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) =&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 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
... ... @@ -152,7 +152,7 @@ const crudSchemas = reactive&lt;CrudSchema[]&gt;([
152 152 },
153 153 {
154 154 field: 'action',
155   - width: '340px',
  155 + width: '400px',
156 156 label: t('table.action'),
157 157 form: {
158 158 show: false
... ...
yudao-ui-admin-vue3/vite.config.ts
... ... @@ -6,7 +6,7 @@ import WindiCSS from &#39;vite-plugin-windicss&#39;
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 =&gt; {
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',
... ...