diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index a5b5a6f92802a0606466a55139f848d8227302ce..5efbeecc960902caf9386a25f0d31deb4f50fe05 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 0000000000000000000000000000000000000000..dd0408a85302f6f6a43ddd6f4a859d4a3818300f --- /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 0000000000000000000000000000000000000000..f634d949b6bbe825007a21a9588e2801e6a45f13 --- /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 0000000000000000000000000000000000000000..0d46d228aba8eacd462f57c9583e7c253b9636ff --- /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 0000000000000000000000000000000000000000..84d6f09024c2caab4c068e123f9fabef0937e4f4 --- /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 0000000000000000000000000000000000000000..5175ee0144be130871744a4739d8bc0ad838bb16 --- /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 0000000000000000000000000000000000000000..f2e8d0b97a8d63c34dc150b0e06f4cfabc188f59 --- /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 1f5c6104f846cc7b9bfd58ee427a8f0481bb51c0..9b4bf88b42138ba0c4447a21cd10cb8faa9aff37 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 19e8124263ddc615ea94edf599db8ede7bb10b29..0eae1a990a95d0c857d329a9832bd2a0882fedc2 100644 --- a/web/src/views/system/role/index.vue +++ b/web/src/views/system/role/index.vue @@ -2,25 +2,31 @@ +