From d05df2d778886d9714d01db41581f7c480bf6ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 9 Dec 2022 23:55:47 +0800 Subject: [PATCH 01/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E9=94=99=E8=AF=AF=E6=97=B6=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E7=9A=84verbase=5Fname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/utils/exception.py | 12 +++++++----- backend/dvadmin/utils/serializers.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/backend/dvadmin/utils/exception.py b/backend/dvadmin/utils/exception.py index b9d2d51..2cf9150 100644 --- a/backend/dvadmin/utils/exception.py +++ b/backend/dvadmin/utils/exception.py @@ -10,6 +10,7 @@ import logging import traceback from django.db.models import ProtectedError +from django.http import Http404 from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed from rest_framework.views import set_rollback @@ -33,9 +34,15 @@ def CustomExceptionHandler(ex, context): if isinstance(ex, AuthenticationFailed): code = 401 msg = ex.detail + elif isinstance(ex,Http404): + code = 400 + msg = "接口地址不正确" elif isinstance(ex, DRFAPIException): set_rollback() msg = ex.detail + for k, v in msg.items(): + for i in v: + msg = "%s:%s" % (k, i) elif isinstance(ex, ProtectedError): set_rollback() msg = "删除失败:该条数据与其他数据有相关绑定" @@ -45,9 +52,4 @@ def CustomExceptionHandler(ex, context): elif isinstance(ex, Exception): logger.error(traceback.format_exc()) msg = str(ex) - - # errorMsg = msg - # for key in errorMsg: - # msg = errorMsg[key][0] - return ErrorResponse(msg=msg, code=code) diff --git a/backend/dvadmin/utils/serializers.py b/backend/dvadmin/utils/serializers.py index 90a685e..c2b047d 100644 --- a/backend/dvadmin/utils/serializers.py +++ b/backend/dvadmin/utils/serializers.py @@ -104,6 +104,24 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer): return getattr(self.request.user, "id", None) return None + @property + def errors(self): + # get errors + errors = super().errors + verbose_errors = {} + + # fields = { field.name: field.verbose_name } for each field in model + fields = {field.name: field.verbose_name for field in + self.Meta.model._meta.get_fields() if hasattr(field, 'verbose_name')} + + # iterate over errors and replace error key with verbose name if exists + for field_name, error in errors.items(): + if field_name in fields: + verbose_errors[str(fields[field_name])] = error + else: + verbose_errors[field_name] = error + return verbose_errors + # @cached_property # def fields(self): # fields = BindingDict(self) From f6e8fb3d095dd44199f55da1d9395ad4a8fec0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Wed, 28 Dec 2022 15:59:38 +0800 Subject: [PATCH 02/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.权限优化;2.导入更新 --- backend/dvadmin/system/views/menu.py | 4 +- web/src/components/importExcel/index.vue | 96 +++++++++++++------ web/src/views/system/dept/crud.js | 9 +- web/src/views/system/dept/index.vue | 1 + web/src/views/system/rolePermission/api.js | 14 +++ web/src/views/system/rolePermission/index.vue | 7 ++ web/src/views/system/user/index.vue | 12 +++ 7 files changed, 108 insertions(+), 35 deletions(-) diff --git a/backend/dvadmin/system/views/menu.py b/backend/dvadmin/system/views/menu.py index 3f2309c..3078d34 100644 --- a/backend/dvadmin/system/views/menu.py +++ b/backend/dvadmin/system/views/menu.py @@ -180,9 +180,7 @@ class MenuViewSet(CustomModelViewSet): return SuccessResponse(data=data, total=len(data), msg="获取成功") def list(self,request): - """ - 懒加载 - """ + """懒加载""" params = request.query_params parent = params.get('parent', None) if params: diff --git a/web/src/components/importExcel/index.vue b/web/src/components/importExcel/index.vue index 118e77b..2b3de92 100644 --- a/web/src/components/importExcel/index.vue +++ b/web/src/components/importExcel/index.vue @@ -3,36 +3,47 @@ 导入 - - - - - 将文件拖到此处,或 - 点击上传 + + + + + 将文件拖到此处,或 + 点击上传 + + + + + + + + 如果导入时需要更新数据,则请选择一个字段作为更新依据 + + 提示:仅允许导入“xls”或“xlsx”格式文件! + + + 下载模板 - - - + - 提示:仅允许导入“xls”或“xlsx”格式文件! - - 下载模板 - - + @@ -68,6 +79,18 @@ export default { default () { return undefined } + }, + updateFieldApi: { + type: String, + default () { + return undefined + } + }, + fieldOptions: { + type: Array, + default () { + return [] + } } }, methods: { @@ -99,7 +122,8 @@ export default { method: 'post', data: { url: response.data.url, - updateSupport: that.upload.updateSupport + updateSupport: that.upload.updateSupport, + updateField: that.upload.updateField } }).then(response => { // this.$alert("导入成功!", "导入结果", { dangerouslyUseHTMLString: true }); @@ -114,7 +138,21 @@ export default { // 提交上传文件 submitFileForm () { this.$refs.upload.submit() + }, + getUpdateField () { + const that = this + if (that.updateFieldApi) { + return request({ + url: that.updateFieldApi, + method: 'get' + }).then(res => { + that.fieldOptions = res.data + }) + } } + }, + mounted () { + this.getUpdateField() } } diff --git a/web/src/views/system/dept/crud.js b/web/src/views/system/dept/crud.js index b3ff85c..b9dd086 100644 --- a/web/src/views/system/dept/crud.js +++ b/web/src/views/system/dept/crud.js @@ -1,10 +1,10 @@ import * as api from './api' export const crudOptions = (vm) => { return { - // pagination: false, pageOptions: { compact: true }, + pagination: false, options: { tableType: 'vxe-table', stripe: false, @@ -14,10 +14,13 @@ export const crudOptions = (vm) => { highlightCurrentRow: false, defaultExpandAll: true, treeConfig: { + transform: true, + rowField: 'id', + parentField: 'parent', + hasChild: 'hasChild', lazy: true, - hasChild: 'has_children', loadMethod: ({ row }) => { - return api.GetList({ parent: row.id, lazy: true }).then(ret => { + return api.GetList({ parent: row.id }).then(ret => { return ret.data.data }) }, diff --git a/web/src/views/system/dept/index.vue b/web/src/views/system/dept/index.vue index b479d48..6990e64 100644 --- a/web/src/views/system/dept/index.vue +++ b/web/src/views/system/dept/index.vue @@ -18,6 +18,7 @@ > 导入 diff --git a/web/src/views/system/rolePermission/api.js b/web/src/views/system/rolePermission/api.js index 2b6eb6b..57c0391 100644 --- a/web/src/views/system/rolePermission/api.js +++ b/web/src/views/system/rolePermission/api.js @@ -55,3 +55,17 @@ export function GetMenuData (obj) { return res.data.data }) } + +/** + * 获取数据权限 + * @param obj + * @returns {*} + * @constructor + */ +export function GetDataScope () { + return request({ + url: '/api/system/role/data_scope/', + method: 'get', + params: {} + }) +} diff --git a/web/src/views/system/rolePermission/index.vue b/web/src/views/system/rolePermission/index.vue index e47e53d..25555b4 100644 --- a/web/src/views/system/rolePermission/index.vue +++ b/web/src/views/system/rolePermission/index.vue @@ -202,6 +202,7 @@ export default { this.menuCheckedKeys = this.roleObj.menu // 加载已勾选的菜单 this.menuCheckStrictly = true // 父子不相互关联 this.deptCheckedKeys = this.roleObj.dept + this.GetDataScope() }, addRequest (row) { return api.createObj(row) @@ -238,6 +239,12 @@ export default { }) }) }, + //获取权限范围 + GetDataScope(){ + api.GetDataScope().then(res=>{ + this.dataScopeOptions = res.data + }) + }, // 所有勾选菜单节点数据 getMenuAllCheckedKeys () { // 目前被选中的菜单节点 diff --git a/web/src/views/system/user/index.vue b/web/src/views/system/user/index.vue index 21293a0..be8e519 100644 --- a/web/src/views/system/user/index.vue +++ b/web/src/views/system/user/index.vue @@ -198,6 +198,18 @@ export default { that.$message.error('表单校验失败,请检查') } }) + }, + //部门懒加载 + loadChildrenMethod({ row} ){ + return new Promise(resolve => { + setTimeout(() => { + const childs = [ + { id: row.id + 100000, parent: row.id, name: row.name + 'Test45', type: 'mp4', size: null, date: '2021-10-03', hasChild: true }, + { id: row.id + 150000, parent: row.id, name: row.name + 'Test56', type: 'mp3', size: null, date: '2021-07-09', hasChild: false } + ] + resolve(childs) + }, 500) + }) } } } From 43e21c9c64fc75d011ceb7a495f5024c4a406678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Thu, 29 Dec 2022 13:07:47 +0800 Subject: [PATCH 03/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E6=97=B6=EF=BC=8C=E5=90=8C=E6=AD=A5=E5=90=AF?= =?UTF-8?q?=E7=94=A8=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/dept.py | 47 +++++++--------- backend/dvadmin/system/views/role.py | 59 ++++++++++++++++++-- backend/dvadmin/utils/exception.py | 7 ++- backend/dvadmin/utils/import_export_mixin.py | 19 +++++-- 4 files changed, 92 insertions(+), 40 deletions(-) diff --git a/backend/dvadmin/system/views/dept.py b/backend/dvadmin/system/views/dept.py index 8239c85..743fa79 100644 --- a/backend/dvadmin/system/views/dept.py +++ b/backend/dvadmin/system/views/dept.py @@ -22,6 +22,13 @@ class DeptSerializer(CustomModelSerializer): parent_name = serializers.CharField(read_only=True, source='parent.name') status_label = serializers.SerializerMethodField() has_children = serializers.SerializerMethodField() + hasChild = serializers.SerializerMethodField() + + def get_hasChild(self, instance): + hasChild = Dept.objects.filter(parent=instance.id) + if hasChild: + return True + return False def get_status_label(self, obj: Dept): if obj.status: @@ -133,31 +140,19 @@ class DeptViewSet(CustomModelViewSet): def list(self, request, *args, **kwargs): # 如果懒加载,则只返回父级 - queryset = self.filter_queryset(self.get_queryset()) - lazy = self.request.query_params.get('lazy') - parent = self.request.query_params.get('parent') - if lazy: - # 如果懒加载模式,返回全部 - if not parent: - role_list = request.user.role.filter(status=1).values("admin", "data_range") - is_admin = False - for ele in role_list: - if 3 == ele.get("data_range") or ele.get("admin") == True: - is_admin = True - break - if self.request.user.is_superuser or is_admin: - queryset = queryset.filter(parent__isnull=True) - else: - queryset = queryset.filter(id=self.request.user.dept_id) - serializer = self.get_serializer(queryset, many=True, request=request) - return SuccessResponse(data=serializer.data, msg="获取成功") - - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True, request=request) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True, request=request) - return SuccessResponse(data=serializer.data, msg="获取成功") + params = request.query_params + parent = params.get('parent', None) + if params: + if parent: + queryset = self.queryset.filter(status=True, parent=parent) + else: + queryset = self.queryset.filter(status=True) + else: + queryset = self.queryset.filter(status=True, parent__isnull=True) + queryset = self.filter_queryset(queryset) + serializer = DeptSerializer(queryset, many=True, request=request) + data = serializer.data + return SuccessResponse(data=data) def dept_lazy_tree(self, request, *args, **kwargs): parent = self.request.query_params.get('parent') @@ -170,9 +165,9 @@ class DeptViewSet(CustomModelViewSet): data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent') return DetailResponse(data=data, msg="获取成功") + @action(methods=["GET"], detail=False, permission_classes=[AnonymousUserPermission]) def all_dept(self, request, *args, **kwargs): - self.extra_filter_backends = [] queryset = self.filter_queryset(self.get_queryset()) data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent') return DetailResponse(data=data, msg="获取成功") diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index 66aac90..e1acf87 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -8,12 +8,13 @@ """ from rest_framework import serializers from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated from dvadmin.system.models import Role, Menu 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.json_response import SuccessResponse +from dvadmin.utils.json_response import SuccessResponse, DetailResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.validator import CustomUniqueValidator from dvadmin.utils.viewset import CustomModelViewSet @@ -96,12 +97,58 @@ class RoleViewSet(CustomModelViewSet): serializer_class = RoleSerializer create_serializer_class = RoleCreateUpdateSerializer update_serializer_class = RoleCreateUpdateSerializer + search_fields = ['name','key'] - @action(methods=['GET'], detail=True, permission_classes=[]) - def roleId_get_menu(self, request, *args, **kwargs): + @action(methods=['GET'], detail=True, permission_classes=[IsAuthenticated]) + def roleId_get_menu(self, request,pk): """通过角色id获取该角色用于的菜单""" - # instance = self.get_object() - # queryset = instance.menu.all() - queryset = Menu.objects.filter(status=1).all() + instance = Role.objects.filter(id=pk).first() + queryset = instance.menu.all() + # queryset = Menu.objects.filter(status=1).all() + queryset = self.filter_queryset(queryset) serializer = MenuPermissonSerializer(queryset, many=True) return SuccessResponse(data=serializer.data) + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def data_scope(self,request): + is_superuser = request.user.is_superuser + is_admin = Role.objects.filter(users__id=request.user.id,admin=True) + if is_superuser or is_admin: + data = [ + { + "value": 0, + "label": '仅本人数据权限' + }, + { + "value": 1, + "label": '本部门及以下数据权限' + }, + { + "value": 2, + "label": '本部门数据权限' + }, + { + "value": 3, + "label": '全部数据权限' + }, + { + "value": 4, + "label": '自定义数据权限' + } + ] + else: + data = [ + { + "value": 0, + "label": '仅本人数据权限' + }, + { + "value": 1, + "label": '本部门及以下数据权限' + }, + { + "value": 2, + "label": '本部门数据权限' + } + ] + return DetailResponse(data=data) diff --git a/backend/dvadmin/utils/exception.py b/backend/dvadmin/utils/exception.py index 2cf9150..a6996df 100644 --- a/backend/dvadmin/utils/exception.py +++ b/backend/dvadmin/utils/exception.py @@ -40,9 +40,10 @@ def CustomExceptionHandler(ex, context): elif isinstance(ex, DRFAPIException): set_rollback() msg = ex.detail - for k, v in msg.items(): - for i in v: - msg = "%s:%s" % (k, i) + if isinstance(msg,dict): + for k, v in msg.items(): + for i in v: + msg = "%s:%s" % (k, i) elif isinstance(ex, ProtectedError): set_rollback() msg = "删除失败:该条数据与其他数据有相关绑定" diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index 39913fb..45e08db 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -57,6 +57,11 @@ class ImportSerializerMixin: length += 2.1 if ord(char) > 256 else 1 return round(length, 1) if length <= self.export_column_width else self.export_column_width + @action(methods=['get'],detail=False) + def update_field(self,request:Request): + data = [{"label":value,"value":key} for key,value in self.import_field_dict.items()] + return DetailResponse(data=data) + @action(methods=['get','post'],detail=False) @transaction.atomic # Django 事务,防止出错 def import_data(self, request: Request, *args, **kwargs): @@ -148,12 +153,15 @@ class ImportSerializerMixin: unique_list = [ ele.name for ele in queryset.model._meta.get_fields() if hasattr(ele, "unique") and ele.unique == True ] + updateField = request.data.get("updateField") for ele in data: - # 获取 unique 字段 - if queryset.model._meta.unique_together: # 判断是否存在联合主键 - filter_dic = {i: ele.get(i) for i in list(queryset.model._meta.unique_together[0])} - else: - filter_dic = {i: ele.get(i) for i in list(set(unique_list)) if ele.get(i) is not None} + # # 获取 unique 字段 + # if queryset.model._meta.unique_together: # 判断是否存在联合主键 + # filter_dic = {i: ele.get(i) for i in list(queryset.model._meta.unique_together[0])} + # else: + # filter_dic = {i: ele.get(i) for i in list(set(unique_list)) if ele.get(i) is not None} + filter_dic = {updateField:ele.get(updateField)} + print(162,filter_dic) instance = filter_dic and queryset.filter(**filter_dic).first() if instance and not updateSupport: continue @@ -207,6 +215,7 @@ class ExportSerializerMixin: length += 2.1 if ord(char) > 256 else 1 return round(length, 1) if length <= self.export_column_width else self.export_column_width + @action(methods=['get'],detail=False) def export_data(self, request: Request, *args, **kwargs): """ 导出功能 From 8158d2b981d917be6cd5a89b20cd82e7a53e6ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Thu, 29 Dec 2022 14:44:39 +0800 Subject: [PATCH 04/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8E=88=E6=9D=83=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/menu_button.py | 2 +- backend/dvadmin/system/views/role.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/dvadmin/system/views/menu_button.py b/backend/dvadmin/system/views/menu_button.py index 4c4d2eb..f32bcb4 100644 --- a/backend/dvadmin/system/views/menu_button.py +++ b/backend/dvadmin/system/views/menu_button.py @@ -18,7 +18,7 @@ class MenuButtonSerializer(CustomModelSerializer): class Meta: model = MenuButton - fields = "__all__" + fields = ['id','name','value','api','method'] read_only_fields = ["id"] diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index e1acf87..be9a1a2 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -81,7 +81,7 @@ class MenuPermissonSerializer(CustomModelSerializer): class Meta: model = Menu - fields = '__all__' + fields = ['id','parent','name','menuPermission'] class RoleViewSet(CustomModelViewSet): @@ -102,12 +102,18 @@ class RoleViewSet(CustomModelViewSet): @action(methods=['GET'], detail=True, permission_classes=[IsAuthenticated]) def roleId_get_menu(self, request,pk): """通过角色id获取该角色用于的菜单""" - instance = Role.objects.filter(id=pk).first() - queryset = instance.menu.all() - # queryset = Menu.objects.filter(status=1).all() + is_superuser = request.user.is_superuser + is_admin = Role.objects.filter(id=pk, admin=True).first() + if is_superuser or is_admin: + queryset = Menu.objects.filter(status=1).all() + else: + instance = Role.objects.filter(id=pk).first() + queryset = instance.menu.all() + print(111, queryset) queryset = self.filter_queryset(queryset) serializer = MenuPermissonSerializer(queryset, many=True) - return SuccessResponse(data=serializer.data) + print(114, serializer.data) + return DetailResponse(data=serializer.data) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) def data_scope(self,request): From f2e474bf12ee2f3e3e44e442c37ac6071bd2042f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Thu, 29 Dec 2022 15:21:31 +0800 Subject: [PATCH 05/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=201?= =?UTF-8?q?.=E6=9D=83=E9=99=90=E4=BC=98=E5=8C=96;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/role.py | 2 -- web/src/views/system/rolePermission/api.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index be9a1a2..81b1d42 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -109,10 +109,8 @@ class RoleViewSet(CustomModelViewSet): else: instance = Role.objects.filter(id=pk).first() queryset = instance.menu.all() - print(111, queryset) queryset = self.filter_queryset(queryset) serializer = MenuPermissonSerializer(queryset, many=True) - print(114, serializer.data) return DetailResponse(data=serializer.data) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) diff --git a/web/src/views/system/rolePermission/api.js b/web/src/views/system/rolePermission/api.js index 57c0391..d0b4631 100644 --- a/web/src/views/system/rolePermission/api.js +++ b/web/src/views/system/rolePermission/api.js @@ -52,7 +52,7 @@ export function GetMenuData (obj) { params: {} }).then(res => { // 将列表数据转换为树形数据 - return res.data.data + return res.data }) } From 0a3516369ed5ef3fe55731530eacb074c6cdb5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 30 Dec 2022 14:33:06 +0800 Subject: [PATCH 06/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9D=83=E9=99=90=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/role.py | 95 +++++++++++++++++++--------- 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index 81b1d42..3bf3c6e 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -81,7 +81,7 @@ class MenuPermissonSerializer(CustomModelSerializer): class Meta: model = Menu - fields = ['id','parent','name','menuPermission'] + fields = ['id', 'parent', 'name', 'menuPermission'] class RoleViewSet(CustomModelViewSet): @@ -97,14 +97,14 @@ class RoleViewSet(CustomModelViewSet): serializer_class = RoleSerializer create_serializer_class = RoleCreateUpdateSerializer update_serializer_class = RoleCreateUpdateSerializer - search_fields = ['name','key'] + search_fields = ['name', 'key'] @action(methods=['GET'], detail=True, permission_classes=[IsAuthenticated]) - def roleId_get_menu(self, request,pk): + def roleId_get_menu(self, request, pk): """通过角色id获取该角色用于的菜单""" is_superuser = request.user.is_superuser is_admin = Role.objects.filter(id=pk, admin=True).first() - if is_superuser or is_admin: + if is_superuser or is_admin : queryset = Menu.objects.filter(status=1).all() else: instance = Role.objects.filter(id=pk).first() @@ -114,33 +114,10 @@ class RoleViewSet(CustomModelViewSet): return DetailResponse(data=serializer.data) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) - def data_scope(self,request): + def data_scope(self, request): is_superuser = request.user.is_superuser - is_admin = Role.objects.filter(users__id=request.user.id,admin=True) - if is_superuser or is_admin: - data = [ - { - "value": 0, - "label": '仅本人数据权限' - }, - { - "value": 1, - "label": '本部门及以下数据权限' - }, - { - "value": 2, - "label": '本部门数据权限' - }, - { - "value": 3, - "label": '全部数据权限' - }, - { - "value": 4, - "label": '自定义数据权限' - } - ] - else: + role_queryset = Role.objects.filter(users__id=request.user.id).values_list('data_range', flat=True) + if is_superuser: data = [ { "value": 0, @@ -153,6 +130,64 @@ class RoleViewSet(CustomModelViewSet): { "value": 2, "label": '本部门数据权限' + }, + { + "value": 3, + "label": '全部数据权限' + }, + { + "value": 4, + "label": '自定义数据权限' } ] + else: + data = [] + data_range_list = list(set(role_queryset)) + for item in data_range_list: + if item == 0: + data = [{ + "value": 0, + "label": '仅本人数据权限' + }] + elif item == 1: + data = [{ + "value": 0, + "label": '仅本人数据权限' + }, { + "value": 1, + "label": '本部门及以下数据权限' + }, + { + "value": 2, + "label": '本部门数据权限' + }] + elif item == 2: + data = [{ + "value": 0, + "label": '仅本人数据权限' + }, + { + "value": 2, + "label": '本部门数据权限' + }] + elif item == 3: + data = [{ + "value": 0, + "label": '仅本人数据权限' + }, + { + "value": 3, + "label": '全部数据权限' + }, ] + elif item == 4: + data = [{ + "value": 0, + "label": '仅本人数据权限' + }, + { + "value": 4, + "label": '自定义数据权限' + }] + else: + data = [] return DetailResponse(data=data) From 977e75dd5062a0b85c8ef0d995017ba6f585269c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 30 Dec 2022 15:08:51 +0800 Subject: [PATCH 07/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=8F=9C=E5=8D=95=E6=8E=88=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/role.py | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index 3bf3c6e..da4f951 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -10,7 +10,7 @@ from rest_framework import serializers from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated -from dvadmin.system.models import Role, Menu +from dvadmin.system.models import Role, Menu, MenuButton from dvadmin.system.views.dept import DeptSerializer from dvadmin.system.views.menu import MenuSerializer from dvadmin.system.views.menu_button import MenuButtonSerializer @@ -77,7 +77,17 @@ class MenuPermissonSerializer(CustomModelSerializer): """ 菜单的按钮权限 """ - menuPermission = MenuButtonSerializer(many=True, read_only=True) + menuPermission = serializers.SerializerMethodField() + + def get_menuPermission(self, instance): + is_superuser = self.request.user.is_superuser + if is_superuser: + queryset = MenuButton.objects.filter(menu__id=instance.id) + else: + menu_permission_id_list = self.request.user.role.values_list('permission',flat=True) + queryset = MenuButton.objects.filter(id__in=menu_permission_id_list,menu__id=instance.id) + serializer = MenuButtonSerializer(queryset,many=True, read_only=True) + return serializer.data class Meta: model = Menu @@ -99,18 +109,18 @@ class RoleViewSet(CustomModelViewSet): update_serializer_class = RoleCreateUpdateSerializer search_fields = ['name', 'key'] - @action(methods=['GET'], detail=True, permission_classes=[IsAuthenticated]) - def roleId_get_menu(self, request, pk): - """通过角色id获取该角色用于的菜单""" + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def role_get_menu(self, request): + """根据当前用户的角色返回角色拥有的菜单""" is_superuser = request.user.is_superuser - is_admin = Role.objects.filter(id=pk, admin=True).first() - if is_superuser or is_admin : + is_admin = request.user.role.values_list('admin',flat=True) + if is_superuser or True in is_admin: queryset = Menu.objects.filter(status=1).all() else: - instance = Role.objects.filter(id=pk).first() - queryset = instance.menu.all() + menu_id_list = request.user.role.values_list('menu',flat=True) + queryset = Menu.objects.filter(id__in=menu_id_list) queryset = self.filter_queryset(queryset) - serializer = MenuPermissonSerializer(queryset, many=True) + serializer = MenuPermissonSerializer(queryset, many=True,request=request) return DetailResponse(data=serializer.data) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) From 9f523f6eba66ac1fcb70bbe07cfab57927e19689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 30 Dec 2022 16:57:53 +0800 Subject: [PATCH 08/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/dept.py | 19 ++++++++++++------- backend/dvadmin/system/views/role.py | 18 ++++++++++++++++-- .../components/header-user/userinfo.vue | 1 - web/src/views/system/role/crud.js | 8 ++++---- web/src/views/system/role/index.vue | 5 +++++ web/src/views/system/rolePermission/api.js | 15 ++++++++++++++- web/src/views/system/rolePermission/index.vue | 2 +- 7 files changed, 52 insertions(+), 16 deletions(-) diff --git a/backend/dvadmin/system/views/dept.py b/backend/dvadmin/system/views/dept.py index 743fa79..52dc1df 100644 --- a/backend/dvadmin/system/views/dept.py +++ b/backend/dvadmin/system/views/dept.py @@ -156,14 +156,19 @@ class DeptViewSet(CustomModelViewSet): def dept_lazy_tree(self, request, *args, **kwargs): parent = self.request.query_params.get('parent') - queryset = self.filter_queryset(self.get_queryset()) - if not parent: - if self.request.user.is_superuser: - queryset = queryset.filter(parent__isnull=True) + is_superuser = request.user.is_superuser + if is_superuser: + if parent: + queryset = Dept.objects.filter(parent=parent).values('id', 'name', 'parent') else: - queryset = queryset.filter(id=self.request.user.dept_id) - data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent') - return DetailResponse(data=data, msg="获取成功") + queryset = Dept.objects.filter(parent__isnull=True).values('id', 'name', 'parent') + else: + dept_list = request.user.role.values_list('dept', flat=True) + if parent: + queryset = Dept.objects.filter(id__in=dept_list,parent=parent).values('id', 'name', 'parent') + else: + queryset = Dept.objects.filter(id__in=dept_list,parent__isnull=True).values('id', 'name', 'parent') + return DetailResponse(data=queryset, msg="获取成功") @action(methods=["GET"], detail=False, permission_classes=[AnonymousUserPermission]) diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index da4f951..727e355 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -10,7 +10,7 @@ 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 +from dvadmin.system.models import Role, Menu, MenuButton, Dept from dvadmin.system.views.dept import DeptSerializer from dvadmin.system.views.menu import MenuSerializer from dvadmin.system.views.menu_button import MenuButtonSerializer @@ -62,6 +62,9 @@ class RoleCreateUpdateSerializer(CustomModelSerializer): return super().validate(attrs) def save(self, **kwargs): + is_superuser = self.request.user.is_superuser + if not is_superuser: + self.validated_data.pop('admin') data = super().save(**kwargs) data.dept.set(self.initial_data.get('dept', [])) data.menu.set(self.initial_data.get('menu', [])) @@ -119,7 +122,7 @@ class RoleViewSet(CustomModelViewSet): else: menu_id_list = request.user.role.values_list('menu',flat=True) queryset = Menu.objects.filter(id__in=menu_id_list) - queryset = self.filter_queryset(queryset) + # queryset = self.filter_queryset(queryset) serializer = MenuPermissonSerializer(queryset, many=True,request=request) return DetailResponse(data=serializer.data) @@ -201,3 +204,14 @@ class RoleViewSet(CustomModelViewSet): else: data = [] return DetailResponse(data=data) + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def data_scope_dept(self,request): + """根据当前角色获取部门信息""" + is_superuser = request.user.is_superuser + if is_superuser: + queryset = Dept.objects.values('id','name','parent') + else: + dept_list = request.user.role.values_list('dept',flat=True) + queryset = Dept.objects.filter(id__in=dept_list).values('id','name','parent') + return DetailResponse(data=queryset) \ No newline at end of file diff --git a/web/src/layout/header-aside/components/header-user/userinfo.vue b/web/src/layout/header-aside/components/header-user/userinfo.vue index b46522e..d02c51d 100644 --- a/web/src/layout/header-aside/components/header-user/userinfo.vue +++ b/web/src/layout/header-aside/components/header-user/userinfo.vue @@ -279,7 +279,6 @@ export default { * @param file */ handleAvatarSuccess (res, file) { - console.log(11, res) this.userInfo.avatar = res } } diff --git a/web/src/views/system/role/crud.js b/web/src/views/system/role/crud.js index 6e2169c..3468f44 100644 --- a/web/src/views/system/role/crud.js +++ b/web/src/views/system/role/crud.js @@ -156,7 +156,6 @@ export const crudOptions = (vm) => { title: '是否管理员', key: 'admin', sortable: true, - type: 'radio', dict: { data: vm.dictionary('button_whether_bool') @@ -164,11 +163,12 @@ export const crudOptions = (vm) => { form: { value: false, component: { - placeholder: '请选择是否管理员' - } + placeholder: '请选择是否管理员', + show(context){ + return vm.info.is_superuser?true:false} + }, } }, - { title: '状态', key: 'status', diff --git a/web/src/views/system/role/index.vue b/web/src/views/system/role/index.vue index 8ed34d7..72517e8 100644 --- a/web/src/views/system/role/index.vue +++ b/web/src/views/system/role/index.vue @@ -64,12 +64,17 @@ import * as api from './api' import { crudOptions } from './crud' import { d2CrudPlus } from 'd2-crud-plus' import rolePermission from '../rolePermission' +import { mapState } from 'vuex' + export default { name: 'role', mixins: [d2CrudPlus.crud], components: { rolePermission }, + computed: { + ...mapState('d2admin/user', ['info']) + }, data () { return { rolePermissionShow: false, diff --git a/web/src/views/system/rolePermission/api.js b/web/src/views/system/rolePermission/api.js index d0b4631..8e9d842 100644 --- a/web/src/views/system/rolePermission/api.js +++ b/web/src/views/system/rolePermission/api.js @@ -47,7 +47,7 @@ export function DelObj (id) { // 通过角色id,获取菜单数据 export function GetMenuData (obj) { return request({ - url: '/api/system/role/' + obj.id + '/roleId_get_menu/', + url: '/api/system/role/role_get_menu/', method: 'get', params: {} }).then(res => { @@ -69,3 +69,16 @@ export function GetDataScope () { params: {} }) } + +/** + * 获取角色部门 + * @returns {*} + * @constructor + */ +export function GetDataScopeDept () { + return request({ + url: '/api/system/role/data_scope_dept/', + method: 'get', + params: {} + }) +} diff --git a/web/src/views/system/rolePermission/index.vue b/web/src/views/system/rolePermission/index.vue index 25555b4..fa81c32 100644 --- a/web/src/views/system/rolePermission/index.vue +++ b/web/src/views/system/rolePermission/index.vue @@ -215,7 +215,7 @@ export default { }, // 获取部门数据 getDeptData () { - deptApi.GetListAll().then(ret => { + api.GetDataScopeDept().then(ret => { this.deptOptions = XEUtils.toArrayTree(ret.data, { parentKey: 'parent', strict: false }) }) }, From cd1b39e9d34849c358716319fd76d95e0ff6a1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 30 Dec 2022 22:07:43 +0800 Subject: [PATCH 09/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=9B=B4=E6=96=B0=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/utils/import_export_mixin.py | 96 ++++++++++++++------ web/src/api/service.js | 4 +- web/src/views/system/role/crud.js | 7 +- 3 files changed, 72 insertions(+), 35 deletions(-) diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index 45e08db..371a128 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -139,38 +139,74 @@ class ImportSerializerMixin: ws.add_table(tab) wb.save(response) return response + else: + # 从excel中组织对应的数据结构,然后使用序列化器保存 + queryset = self.filter_queryset(self.get_queryset()) + # 获取多对多字段 + m2m_fields = [ + ele.name + for ele in queryset.model._meta.get_fields() + if hasattr(ele, "many_to_many") and ele.many_to_many == True + ] + import_field_dict = {'id':'更新主键(勿改)',**self.import_field_dict} + data = import_to_data(request.data.get("url"), import_field_dict, m2m_fields) + for ele in data: + filter_dic = {'id':ele.get('id')} + instance = filter_dic and queryset.filter(**filter_dic).first() + print(instance) + serializer = self.import_serializer_class(instance, data=ele, request=request) + serializer.is_valid(raise_exception=True) + serializer.save() + return DetailResponse(msg=f"导入成功!") - updateSupport = request.data.get("updateSupport") - # 从excel中组织对应的数据结构,然后使用序列化器保存 + @action(methods=['get'],detail=False) + def update_template(self,request): queryset = self.filter_queryset(self.get_queryset()) - # 获取多对多字段 - m2m_fields = [ - ele.name - for ele in queryset.model._meta.get_fields() - if hasattr(ele, "many_to_many") and ele.many_to_many == True - ] - data = import_to_data(request.data.get("url"), self.import_field_dict, m2m_fields) - unique_list = [ - ele.name for ele in queryset.model._meta.get_fields() if hasattr(ele, "unique") and ele.unique == True - ] - updateField = request.data.get("updateField") - for ele in data: - # # 获取 unique 字段 - # if queryset.model._meta.unique_together: # 判断是否存在联合主键 - # filter_dic = {i: ele.get(i) for i in list(queryset.model._meta.unique_together[0])} - # else: - # filter_dic = {i: ele.get(i) for i in list(set(unique_list)) if ele.get(i) is not None} - filter_dic = {updateField:ele.get(updateField)} - print(162,filter_dic) - instance = filter_dic and queryset.filter(**filter_dic).first() - if instance and not updateSupport: - continue - if not filter_dic: - instance = None - serializer = self.import_serializer_class(instance, data=ele, request=request) - serializer.is_valid(raise_exception=True) - serializer.save() - return DetailResponse(msg=f"导入成功!") + assert self.import_field_dict, "'%s' 请配置对应的导入模板字段。" % self.__class__.__name__ + assert self.import_serializer_class, "'%s' 请配置对应的导入序列化器。" % self.__class__.__name__ + data = self.import_serializer_class(queryset, many=True, request=request).data + # 导出excel 表 + response = HttpResponse(content_type="application/msexcel") + response["Access-Control-Expose-Headers"] = f"Content-Disposition" + response["content-disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}' + wb = Workbook() + ws = wb.active + header_data = ["序号","更新主键(勿改)", *self.import_field_dict.values()] + hidden_header = ["#","id", *self.import_field_dict.keys()] + df_len_max = [self.get_string_len(ele) for ele in header_data] + row = get_column_letter(len(hidden_header) + 1) + column = 1 + ws.append(header_data) + for index, results in enumerate(data): + results_list = [] + for h_index, h_item in enumerate(hidden_header): + for key, val in results.items(): + if key == h_item: + if val is None or val == "": + results_list.append("") + else: + results_list.append(val) + # 计算最大列宽度 + result_column_width = self.get_string_len(val) + if h_index != 0 and result_column_width > df_len_max[h_index]: + df_len_max[h_index] = result_column_width + ws.append([index+1,*results_list]) + column += 1 + # 更新列宽 + for index, width in enumerate(df_len_max): + ws.column_dimensions[get_column_letter(index + 1)].width = width + tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器 + style = TableStyleInfo( + name="TableStyleLight11", + showFirstColumn=True, + showLastColumn=True, + showRowStripes=True, + showColumnStripes=True, + ) + tab.tableStyleInfo = style + ws.add_table(tab) + wb.save(response) + return response class ExportSerializerMixin: diff --git a/web/src/api/service.js b/web/src/api/service.js index e4c9480..fb47712 100644 --- a/web/src/api/service.js +++ b/web/src/api/service.js @@ -216,7 +216,7 @@ const refreshTken = function () { * @param method * @param filename */ -export const downloadFile = function ({ url, params, method, filename }) { +export const downloadFile = function ({ url, params, method, filename="文件导出" }) { request({ url: url, method: method, @@ -224,7 +224,7 @@ export const downloadFile = function ({ url, params, method, filename }) { responseType: 'blob' // headers: {Accept: 'application/vnd.openxmlformats-officedocument'} }).then(res => { - const fileName = window.decodeURI(filename + '.xls' || res.headers['content-disposition'].split('=')[1]) || '文件导出.xls' + const fileName = window.decodeURI(filename + '.xlsx' || res.headers['content-disposition'].split('=')[1]) || '文件导出.xlsx' if (res) { const blob = new Blob([res.data], { type: 'charset=utf-8' }) const elink = document.createElement('a') diff --git a/web/src/views/system/role/crud.js b/web/src/views/system/role/crud.js index 3468f44..79a9568 100644 --- a/web/src/views/system/role/crud.js +++ b/web/src/views/system/role/crud.js @@ -164,9 +164,10 @@ export const crudOptions = (vm) => { value: false, component: { placeholder: '请选择是否管理员', - show(context){ - return vm.info.is_superuser?true:false} - }, + show (context) { + return !!vm.info.is_superuser + } + } } }, { From 5cb38b85bb568f419707e7e0bc6f66e8c3905ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 30 Dec 2022 22:13:05 +0800 Subject: [PATCH 10/31] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=201?= =?UTF-8?q?.=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5=E4=BC=98=E5=8C=96;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/importExcel/index.vue | 39 +++++++------------ .../components/msg-list/index.vue | 2 +- web/src/views/system/dept/index.vue | 3 +- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/web/src/components/importExcel/index.vue b/web/src/components/importExcel/index.vue index 2b3de92..f450664 100644 --- a/web/src/components/importExcel/index.vue +++ b/web/src/components/importExcel/index.vue @@ -21,23 +21,11 @@ 将文件拖到此处,或 点击上传 - - - - - - - 如果导入时需要更新数据,则请选择一个字段作为更新依据 - 提示:仅允许导入“xls”或“xlsx”格式文件! - 下载模板 + 下载导入模板 + 批量更新模板