From 93beea5b57b54373c503190645ef92d50f5e7b54 Mon Sep 17 00:00:00 2001 From: lxy <10179281+lxy0722@user.noreply.gitee.com> Date: Sat, 4 Jan 2025 19:05:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A7=92=E8=89=B2=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=8E=88=E6=9D=83=E7=94=A8=E6=88=B7=20=EF=BC=88cherry?= =?UTF-8?q?=20picked=20commit=20from=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/role.py | 72 ++++++- .../system/role/components/addUsers/api.ts | 30 +++ .../system/role/components/addUsers/crud.tsx | 184 +++++++++++++++++ .../system/role/components/addUsers/index.vue | 91 +++++++++ .../system/role/components/searchUsers/api.ts | 44 ++++ .../role/components/searchUsers/crud.tsx | 193 ++++++++++++++++++ .../role/components/searchUsers/index.vue | 98 +++++++++ web/src/views/system/role/crud.tsx | 21 +- web/src/views/system/role/index.vue | 10 +- .../system/role/stores/RoleUserStores.ts | 25 +++ 10 files changed, 761 insertions(+), 7 deletions(-) create mode 100644 web/src/views/system/role/components/addUsers/api.ts create mode 100644 web/src/views/system/role/components/addUsers/crud.tsx create mode 100644 web/src/views/system/role/components/addUsers/index.vue create mode 100644 web/src/views/system/role/components/searchUsers/api.ts create mode 100644 web/src/views/system/role/components/searchUsers/crud.tsx create mode 100644 web/src/views/system/role/components/searchUsers/index.vue create mode 100644 web/src/views/system/role/stores/RoleUserStores.ts diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index a5b5a6f9..5efbeecc 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -10,16 +10,17 @@ from rest_framework import serializers from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated -from dvadmin.system.models import Role, Menu, MenuButton, Dept +from dvadmin.system.models import Role, Menu, MenuButton, Dept, Users from dvadmin.system.views.dept import DeptSerializer from dvadmin.system.views.menu import MenuSerializer from dvadmin.system.views.menu_button import MenuButtonSerializer from dvadmin.utils.crud_mixin import FastCrudMixin from dvadmin.utils.field_permission import FieldPermissionMixin -from dvadmin.utils.json_response import SuccessResponse, DetailResponse +from dvadmin.utils.json_response import SuccessResponse, DetailResponse, ErrorResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.validator import CustomUniqueValidator from dvadmin.utils.viewset import CustomModelViewSet +from dvadmin.utils.permission import CustomPermission class RoleSerializer(CustomModelSerializer): @@ -101,8 +102,7 @@ class MenuButtonPermissionSerializer(CustomModelSerializer): fields = '__all__' - -class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin): +class RoleViewSet(CustomModelViewSet, FastCrudMixin, FieldPermissionMixin): """ 角色管理接口 list:查询 @@ -116,3 +116,67 @@ class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin): create_serializer_class = RoleCreateUpdateSerializer update_serializer_class = RoleCreateUpdateSerializer search_fields = ['name', 'key'] + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated, CustomPermission]) + def get_role_users(self, request): + """ + 获取角色已授权、未授权的用户 + 已授权的用户:1 + 未授权的用户:0 + """ + role_id = request.query_params.get('role_id', None) + + if not role_id: + return ErrorResponse(msg="请选择角色") + + if request.query_params.get('authorized', 0) == "1": + queryset = Users.objects.filter(role__id=role_id).exclude(is_superuser=True) + else: + queryset = Users.objects.exclude(role__id=role_id).exclude(is_superuser=True) + + if name := request.query_params.get('name', None): + queryset = queryset.filter(name__icontains=name) + + if dept := request.query_params.get('dept', None): + queryset = queryset.filter(dept=dept) + + page = self.paginate_queryset(queryset.values('id', 'name', 'dept__name')) + if page is not None: + return self.get_paginated_response(page) + + return SuccessResponse(data=page) + + @action(methods=['DELETE'], detail=False, permission_classes=[IsAuthenticated, CustomPermission]) + def remove_role_user(self, request): + """ + 删除角色用户 + """ + role_id = request.data.get('role_id', None) + user_id = request.data.get('user_id', None) + + if not role_id: + return ErrorResponse(msg="请选择角色") + + if not user_id: + return ErrorResponse(msg="请选择用户") + + queryset = self.queryset.get(id=role_id) + queryset.users_set.remove(*user_id) + + return SuccessResponse(msg="删除成功") + + @action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated, CustomPermission]) + def add_role_users(self, request): + role_id = request.data.get('role_id', None) + users_id = request.data.get('users_id', None) + + if not role_id: + return ErrorResponse(msg="请选择角色") + + if not users_id: + return ErrorResponse(msg="请选择用户") + + role = self.queryset.get(id=role_id) + role.users_set.add(*users_id) + + return DetailResponse(msg="添加成功") diff --git a/web/src/views/system/role/components/addUsers/api.ts b/web/src/views/system/role/components/addUsers/api.ts new file mode 100644 index 00000000..dd0408a8 --- /dev/null +++ b/web/src/views/system/role/components/addUsers/api.ts @@ -0,0 +1,30 @@ +import { request } from '/@/utils/service'; +import { UserPageQuery} from '@fast-crud/fast-crud'; + +/** + * 当前角色查询未授权的用户 + * @param role_id 角色id + * @param query 查询条件 需要有角色id + * @returns + */ +export function getRoleUsersUnauthorized(query: UserPageQuery) { + query["authorized"] = 0; + return request({ + url: '/api/system/role/get_role_users/', + method: 'get', + params: query, + }); +} +/** + * 当前用户角色添加用户 + * @param role_id 角色id + * @param users_id 用户id数组 + * @returns + */ +export function addRoleUsers(role_id: number, users_id: Array) { + return request({ + url: '/api/system/role/add_role_users/', + method: 'post', + data: {role_id: role_id, users_id: users_id}, + }); +} \ No newline at end of file diff --git a/web/src/views/system/role/components/addUsers/crud.tsx b/web/src/views/system/role/components/addUsers/crud.tsx new file mode 100644 index 00000000..f634d949 --- /dev/null +++ b/web/src/views/system/role/components/addUsers/crud.tsx @@ -0,0 +1,184 @@ +import {getRoleUsersUnauthorized} from './api'; +import { + compute, + dict, + UserPageQuery, + AddReq, + DelReq, + EditReq, + CrudOptions, + CreateCrudOptionsProps, + CreateCrudOptionsRet +} from '@fast-crud/fast-crud'; + +import { ref , nextTick} from 'vue'; +import XEUtils from 'xe-utils'; + +export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const pageRequest = async (query: UserPageQuery) => { + return await getRoleUsersUnauthorized(query); + }; + const editRequest = async ({ form, row }: EditReq) => { + return undefined; + }; + const delRequest = async ({ row }: DelReq) => { + return undefined; + }; + const addRequest = async ({ form }: AddReq) => { + return undefined; + }; + + // 记录选中的行 + const selectedRows = ref([]); + + const onSelectionChange = (changed: any) => { + const tableData = crudExpose.getTableData(); + const unChanged = tableData.filter((row: any) => !changed.includes(row)); + // 添加已选择的行 + XEUtils.arrayEach(changed, (item: any) => { + const ids = XEUtils.pluck(selectedRows.value, 'id'); + if (!ids.includes(item.id)) { + selectedRows.value = XEUtils.union(selectedRows.value, [item]); + } + }); + // 剔除未选择的行 + XEUtils.arrayEach(unChanged, (unItem: any) => { + selectedRows.value = XEUtils.remove(selectedRows.value, (item: any) => item.id !== unItem.id); + }); + }; + const toggleRowSelection = () => { + // 多选后,回显默认勾选 + const tableRef = crudExpose.getBaseTableRef(); + const tableData = crudExpose.getTableData(); + const selected = XEUtils.filter(tableData, (item: any) => { + const ids = XEUtils.pluck(selectedRows.value, 'id'); + return ids.includes(item.id); + }); + + nextTick(() => { + XEUtils.arrayEach(selected, (item) => { + tableRef.toggleRowSelection(item, true); + }); + }); + }; + + return { + selectedRows, + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest, + }, + actionbar: { + show: false, + buttons: { + add: { + show: false, + }, + }, + }, + rowHandle: { + show: false, + //固定右侧 + fixed: 'left', + width: 150, + buttons: { + view: { + show: false, + }, + edit: { + show: false, + }, + remove: { + show: false, + }, + }, + }, + table: { + rowKey: "id", + onSelectionChange, + onRefreshed: () => toggleRowSelection(), + }, + columns: { + $checked: { + title: "选择", + form: { show: false}, + column: { + show: true, + type: "selection", + align: "center", + width: "55px", + columnSetDisabled: true, //禁止在列设置中选择 + } + }, + _index: { + title: '序号', + form: { show: false }, + column: { + //type: 'index', + align: 'center', + width: '70px', + columnSetDisabled: true, //禁止在列设置中选择 + formatter: (context) => { + //计算序号,你可以自定义计算规则,此处为翻页累加 + let index = context.index ?? 1; + let pagination = crudExpose!.crudBinding.value.pagination; + // @ts-ignore + return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1; + }, + }, + }, + name: { + title: '用户名', + search: { + show: true, + component: { + props: { + clearable: true, + }, + }, + }, + type: 'text', + form: { + show: false, + }, + }, + dept: { + title: '部门', + show: true, + type: 'dict-tree', + column: { + name: 'text', + formatter({value,row,index}){ + return row.dept__name + } + }, + search: { + show: true, + disabled: true, + col:{span: 6}, + component: { + multiple: false, + props: { + checkStrictly: true, + clearable: true, + filterable: true, + }, + }, + }, + form: { + show: false + }, + dict: dict({ + isTree: true, + url: '/api/system/dept/all_dept/', + value: 'id', + label: 'name' + }), + }, + }, + }, + }; +}; diff --git a/web/src/views/system/role/components/addUsers/index.vue b/web/src/views/system/role/components/addUsers/index.vue new file mode 100644 index 00000000..0d46d228 --- /dev/null +++ b/web/src/views/system/role/components/addUsers/index.vue @@ -0,0 +1,91 @@ + + + diff --git a/web/src/views/system/role/components/searchUsers/api.ts b/web/src/views/system/role/components/searchUsers/api.ts new file mode 100644 index 00000000..84d6f090 --- /dev/null +++ b/web/src/views/system/role/components/searchUsers/api.ts @@ -0,0 +1,44 @@ +import { request } from '/@/utils/service'; +import { UserPageQuery} from '@fast-crud/fast-crud'; + +/** + * 当前角色查询授权的用户 + * @param query 查询条件 需要有角色id + * @returns + */ +export function getRoleUsersAuthorized(query: UserPageQuery) { + query["authorized"] = 1; + return request({ + url: '/api/system/role/get_role_users/', + method: 'get', + params: query, + }); +} +/** + * 当前角色删除授权的用户 + * @param role_id 角色id + * @param user_id 用户id数组 + * @returns + */ +export function removeRoleUser(role_id: number, user_id: Array) { + return request({ + url: '/api/system/role/remove_role_user/', + method: 'delete', + data: { role_id: role_id , user_id: user_id}, + }); +} + + +/** + * 当前用户角色添加用户 + * @param role_id 角色id + * @param data 用户id数组 + * @returns + */ +export function addRoleUsers(role_id: number, data: Array) { + return request({ + url: '/api/system/role/add_role_users/', + method: 'post', + data: {role_id: role_id, users_id: data}, + }); +} \ No newline at end of file diff --git a/web/src/views/system/role/components/searchUsers/crud.tsx b/web/src/views/system/role/components/searchUsers/crud.tsx new file mode 100644 index 00000000..5175ee01 --- /dev/null +++ b/web/src/views/system/role/components/searchUsers/crud.tsx @@ -0,0 +1,193 @@ +import {getRoleUsersAuthorized, removeRoleUser} from './api'; +import { + compute, + dict, + UserPageQuery, + AddReq, + DelReq, + EditReq, + CrudOptions, + CreateCrudOptionsProps, + CreateCrudOptionsRet +} from '@fast-crud/fast-crud'; + +import {auth} from "/@/utils/authFunction"; +import { ref , nextTick} from 'vue'; +import XEUtils from 'xe-utils'; + +export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const pageRequest = async (query: UserPageQuery) => { + return await getRoleUsersAuthorized(query); + }; + const editRequest = async ({ form, row }: EditReq) => { + return undefined; + }; + const delRequest = async ({ row }: DelReq) => { + return await removeRoleUser(crudExpose.crudRef.value.getSearchFormData().role_id, [row.id]); + }; + const addRequest = async ({ form }: AddReq) => { + return undefined; + }; + + // 记录选中的行 + const selectedRows = ref([]); + + const onSelectionChange = (changed: any) => { + const tableData = crudExpose.getTableData(); + const unChanged = tableData.filter((row: any) => !changed.includes(row)); + // 添加已选择的行 + XEUtils.arrayEach(changed, (item: any) => { + const ids = XEUtils.pluck(selectedRows.value, 'id'); + if (!ids.includes(item.id)) { + selectedRows.value = XEUtils.union(selectedRows.value, [item]); + } + }); + // 剔除未选择的行 + XEUtils.arrayEach(unChanged, (unItem: any) => { + selectedRows.value = XEUtils.remove(selectedRows.value, (item: any) => item.id !== unItem.id); + }); + }; + const toggleRowSelection = () => { + // 多选后,回显默认勾选 + const tableRef = crudExpose.getBaseTableRef(); + const tableData = crudExpose.getTableData(); + const selected = XEUtils.filter(tableData, (item: any) => { + const ids = XEUtils.pluck(selectedRows.value, 'id'); + return ids.includes(item.id); + }); + + nextTick(() => { + XEUtils.arrayEach(selected, (item) => { + tableRef.toggleRowSelection(item, true); + }); + }); + }; + + return { + selectedRows, + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest, + }, + actionbar: { + buttons: { + add: { + show: auth('role:AuthorizedAdd'), + click: (ctx: any) => { + context!.subUserRef.value.dialog = true; + nextTick(() => { + context!.subUserRef.value.setSearchFormData({ form: { role_id: crudExpose.crudRef.value.getSearchFormData().role_id } }); + context!.subUserRef.value.doRefresh(); + }); + }, + }, + }, + + }, + rowHandle: { + //固定右侧 + fixed: 'left', + width: 150, + show: auth('role:AuthorizedDel'), + buttons: { + view: { + show: false, + }, + edit: { + show: false, + }, + remove: { + iconRight: 'Delete', + show: true, + }, + }, + }, + table: { + rowKey: "id", + onSelectionChange, + onRefreshed: () => toggleRowSelection(), + }, + columns: { + $checked: { + title: "选择", + form: { show: false}, + column: { + show: auth('role:AuthorizedDel'), + type: "selection", + align: "center", + width: "55px", + columnSetDisabled: true, //禁止在列设置中选择 + } + }, + _index: { + title: '序号', + form: { show: false }, + column: { + //type: 'index', + align: 'center', + width: '70px', + columnSetDisabled: true, //禁止在列设置中选择 + formatter: (context) => { + //计算序号,你可以自定义计算规则,此处为翻页累加 + let index = context.index ?? 1; + let pagination = crudExpose!.crudBinding.value.pagination; + // @ts-ignore + return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1; + }, + }, + }, + name: { + title: '用户名', + search: { + show: true, + component: { + props: { + clearable: true, + }, + }, + }, + type: 'text', + form: { + show: false, + }, + }, + dept: { + title: '部门', + show: true, + type: 'dict-tree', + column: { + name: 'text', + formatter({value,row,index}){ + return row.dept__name + } + }, + search: { + show: true, + disabled: true, + col:{span: 6}, + component: { + multiple: false, + props: { + checkStrictly: true, + clearable: true, + filterable: true, + }, + }, + }, + form: { + show: false + }, + dict: dict({ + isTree: true, + url: '/api/system/dept/all_dept/', + value: 'id', + label: 'name' + }), + }, + }, + }, + }; +}; diff --git a/web/src/views/system/role/components/searchUsers/index.vue b/web/src/views/system/role/components/searchUsers/index.vue new file mode 100644 index 00000000..f2e8d0b9 --- /dev/null +++ b/web/src/views/system/role/components/searchUsers/index.vue @@ -0,0 +1,98 @@ + + + diff --git a/web/src/views/system/role/crud.tsx b/web/src/views/system/role/crud.tsx index 1f5c6104..9b4bf88b 100644 --- a/web/src/views/system/role/crud.tsx +++ b/web/src/views/system/role/crud.tsx @@ -3,6 +3,7 @@ import * as api from './api'; import { dictionary } from '/@/utils/dictionary'; import { successMessage } from '../../../utils/message'; import { auth } from '/@/utils/authFunction'; +import { nextTick, computed } from 'vue'; /** * @@ -46,7 +47,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp rowHandle: { //固定右侧 fixed: 'right', - width: 320, + width: computed(() => { + if (auth('role:AuthorizedAdd') || auth('role:AuthorizedSearch')){ + return 420; + } + return 320; + }), buttons: { view: { show: true, @@ -57,6 +63,19 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp remove: { show: auth('role:Delete'), }, + assignment: { + type: 'primary', + text: '授权用户', + show: auth('role:AuthorizedAdd') || auth('role:AuthorizedSearch'), + click: (ctx: any) => { + const { row } = ctx; + context!.RoleUserDrawer.handleDrawerOpen(row); + nextTick(() => { + context!.RoleUserRef.value.setSearchFormData({ form: { role_id: row.id } }); + context!.RoleUserRef.value.doRefresh(); + }); + }, + }, permission: { type: 'primary', text: '权限配置', diff --git a/web/src/views/system/role/index.vue b/web/src/views/system/role/index.vue index 19e81242..0eae1a99 100644 --- a/web/src/views/system/role/index.vue +++ b/web/src/views/system/role/index.vue @@ -2,25 +2,31 @@ +