diff --git a/backend/application/asgi.py b/backend/application/asgi.py index b707dca..5a5c987 100644 --- a/backend/application/asgi.py +++ b/backend/application/asgi.py @@ -14,10 +14,10 @@ from channels.routing import ProtocolTypeRouter, URLRouter os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" -from application.routing import websocket_urlpatterns + http_application = get_asgi_application() - +from application.routing import websocket_urlpatterns application = ProtocolTypeRouter({ "http":http_application, 'websocket': AuthMiddlewareStack( diff --git a/backend/application/dispatch.py b/backend/application/dispatch.py index 6843c75..3bd364c 100644 --- a/backend/application/dispatch.py +++ b/backend/application/dispatch.py @@ -48,6 +48,16 @@ def _get_all_system_config(): value = system_config.get("value", "") if value and system_config.get("form_item_type") == 7: value = value[0].get("url") + if value and system_config.get("form_item_type") == 11: + new_value = [] + for ele in value: + new_value.append({ + "key": ele.get('key'), + "title": ele.get('title'), + "value": ele.get('value'), + }) + new_value.sort(key=lambda s: s["key"]) + value = new_value data[f"{system_config.get('parent__key')}.{system_config.get('key')}"] = value return data diff --git a/backend/application/websocketConfig.py b/backend/application/websocketConfig.py index b3c3845..80515f4 100644 --- a/backend/application/websocketConfig.py +++ b/backend/application/websocketConfig.py @@ -8,34 +8,43 @@ import json from channels.layers import get_channel_layer from jwt import InvalidSignatureError +from rest_framework.request import Request from application import settings +from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser +from dvadmin.system.views.message_center import MessageCenterTargetUserSerializer +from dvadmin.utils.serializers import CustomModelSerializer send_dict = {} + # 发送消息结构体 -def set_message(sender, msg_type, msg): +def set_message(sender, msg_type, msg, unread=0): text = { 'sender': sender, 'contentType': msg_type, 'content': msg, + 'unread': unread } return text -#异步获取消息中心的目标用户 + +# 异步获取消息中心的目标用户 @database_sync_to_async def _get_message_center_instance(message_id): from dvadmin.system.models import MessageCenter - _MessageCenter = MessageCenter.objects.filter(id=message_id).values_list('target_user',flat=True) + _MessageCenter = MessageCenter.objects.filter(id=message_id).values_list('target_user', flat=True) if _MessageCenter: return _MessageCenter else: return [] + @database_sync_to_async def _get_message_unread(user_id): + """获取用户的未读消息数量""" from dvadmin.system.models import MessageCenterTargetUser - count = MessageCenterTargetUser.objects.filter(users=user_id,is_read=False).count() + count = MessageCenterTargetUser.objects.filter(users=user_id, is_read=False).count() return count or 0 @@ -44,6 +53,7 @@ def request_data(scope): qs = urllib.parse.parse_qs(query_string) return qs + class DvadminWebSocket(AsyncJsonWebsocketConsumer): async def connect(self): try: @@ -52,27 +62,33 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer): decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"]) if decoded_result: self.user_id = decoded_result.get('user_id') - self.chat_group_name = "user_"+str(self.user_id) - #收到连接时候处理, + self.chat_group_name = "user_" + str(self.user_id) + # 收到连接时候处理, await self.channel_layer.group_add( self.chat_group_name, self.channel_name ) await self.accept() - # 发送连接成功 - await self.send_json(set_message('system', 'SYSTEM', '连接成功')) # 主动推送消息 unread_count = await _get_message_unread(self.user_id) - await self.send_json(set_message('system', 'TEXT', {"model":'message_center',"unread":unread_count})) + if unread_count == 0: + # 发送连接成功 + await self.send_json(set_message('system', 'SYSTEM', '连接成功')) + else: + await self.send_json( + set_message('system', 'SYSTEM', "请查看您的未读消息~", + unread=unread_count)) except InvalidSignatureError: await self.disconnect(None) - async def disconnect(self, close_code): # Leave room group await self.channel_layer.group_discard(self.chat_group_name, self.channel_name) print("连接关闭") - await self.close(close_code) + try: + await self.close(close_code) + except Exception: + pass class MegCenter(DvadminWebSocket): @@ -92,21 +108,74 @@ class MegCenter(DvadminWebSocket): ) async def push_message(self, event): + """消息发送""" message = event['json'] await self.send(text_data=json.dumps(message)) -def websocket_push(user_id, message): +class MessageCreateSerializer(CustomModelSerializer): """ - 主动推送消息 + 消息中心-新增-序列化器 """ - username = "user_"+str(user_id) - print(103,message) + class Meta: + model = MessageCenter + fields = "__all__" + read_only_fields = ["id"] + +def websocket_push(user_id,message): + username = "user_" + str(user_id) channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)( - username, - { - "type": "push.message", - "json": message - } + username, + { + "type": "push.message", + "json": message + } ) + +def create_message_push(title: str, content: str, target_type: int=0, target_user: list=[], target_dept=None, target_role=None, + message: dict = {'contentType': 'INFO', 'content': '测试~'}, request= Request): + if message is None: + message = {"contentType": "INFO", "content": None} + if target_role is None: + target_role = [] + if target_dept is None: + target_dept = [] + data = { + "title": title, + "content": content, + "target_type": target_type, + "target_user":target_user, + "target_dept":target_dept, + "target_role":target_role + } + message_center_instance = MessageCreateSerializer(data=data,request=request) + message_center_instance.is_valid(raise_exception=True) + message_center_instance.save() + users = target_user or [] + if target_type in [1]: # 按角色 + users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True) + if target_type in [2]: # 按部门 + users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True) + if target_type in [3]: # 系统通知 + users = Users.objects.values_list('id', flat=True) + targetuser_data = [] + for user in users: + targetuser_data.append({ + "messagecenter": message_center_instance.instance.id, + "users": user + }) + targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=request) + targetuser_instance.is_valid(raise_exception=True) + targetuser_instance.save() + for user in users: + username = "user_" + str(user) + unread_count = async_to_sync(_get_message_unread)(user) + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + username, + { + "type": "push.message", + "json": {**message,'unread':unread_count} + } + ) diff --git a/backend/docker_start.sh b/backend/docker_start.sh index 2654bc1..6042fe6 100755 --- a/backend/docker_start.sh +++ b/backend/docker_start.sh @@ -2,4 +2,4 @@ # python manage.py makemigrations # python manage.py migrate # python manage.py init -y -daphne -b 0.0.0.0 -p 8000 application.asgi:application +gunicorn -c gunicorn.py application.asgi:application diff --git a/backend/dvadmin/system/models.py b/backend/dvadmin/system/models.py index bb59d12..d471438 100644 --- a/backend/dvadmin/system/models.py +++ b/backend/dvadmin/system/models.py @@ -120,6 +120,25 @@ class Dept(CoreModel): help_text="上级部门", ) + @classmethod + def recursion_dept_info(cls, dept_id: int, dept_all_list=None, dept_list=None): + """ + 递归获取部门的所有下级部门 + :param dept_id: 需要获取的id + :param dept_all_list: 所有列表 + :param dept_list: 递归list + :return: + """ + if not dept_all_list: + dept_all_list = Dept.objects.values("id", "parent") + if dept_list is None: + dept_list = [dept_id] + for ele in dept_all_list: + if ele.get("parent") == dept_id: + dept_list.append(ele.get("id")) + cls.recursion_dept_info(ele.get("id"), dept_all_list, dept_list) + return list(set(dept_list)) + class Meta: db_table = table_prefix + "system_dept" verbose_name = "部门表" diff --git a/backend/dvadmin/system/views/dept.py b/backend/dvadmin/system/views/dept.py index 8239c85..18c5324 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: @@ -99,6 +106,9 @@ class DeptCreateUpdateSerializer(CustomModelSerializer): """ def create(self, validated_data): + value = validated_data.get('parent',None) + if value is None: + validated_data['parent'] = self.request.user.dept instance = super().create(validated_data) instance.dept_belong_id = instance.id instance.save() @@ -133,46 +143,47 @@ 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') - queryset = self.filter_queryset(self.get_queryset()) - if not parent: - if self.request.user.is_superuser: - queryset = queryset.filter(parent__isnull=True) - 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="获取成功") + is_superuser = request.user.is_superuser + if is_superuser: + queryset = Dept.objects.values('id', 'name', 'parent') + else: + data_range = request.user.role.values_list('data_range', flat=True) + user_dept_id = request.user.dept.id + dept_list = [user_dept_id] + data_range_list = list(set(data_range)) + for item in data_range_list: + if item in [0,2]: + dept_list = [user_dept_id] + elif item == 1: + dept_list = Dept.recursion_dept_info(dept_id=user_dept_id) + elif item == 3: + dept_list = Dept.objects.values_list('id',flat=True) + elif item == 4: + dept_list = request.user.role.values_list('dept',flat=True) + else: + dept_list = [] + queryset = Dept.objects.filter(id__in=dept_list).values('id', 'name', 'parent') + return DetailResponse(data=queryset, 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/menu.py b/backend/dvadmin/system/views/menu.py index 3f2309c..16d60f4 100644 --- a/backend/dvadmin/system/views/menu.py +++ b/backend/dvadmin/system/views/menu.py @@ -10,7 +10,7 @@ from rest_framework import serializers from rest_framework.decorators import action from dvadmin.system.models import Menu, MenuButton -from dvadmin.system.views.menu_button import MenuButtonSerializer +from dvadmin.system.views.menu_button import MenuButtonInitSerializer from dvadmin.utils.json_response import SuccessResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.viewset import CustomModelViewSet @@ -106,7 +106,7 @@ class MenuInitSerializer(CustomModelSerializer): "value": menu_button_data['value'] } instance_obj = MenuButton.objects.filter(**filter_data).first() - serializer = MenuButtonSerializer(instance_obj, data=menu_button_data, request=self.request) + serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request) serializer.is_valid(raise_exception=True) serializer.save() return instance @@ -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/backend/dvadmin/system/views/menu_button.py b/backend/dvadmin/system/views/menu_button.py index 4c4d2eb..1f79dc1 100644 --- a/backend/dvadmin/system/views/menu_button.py +++ b/backend/dvadmin/system/views/menu_button.py @@ -16,6 +16,27 @@ class MenuButtonSerializer(CustomModelSerializer): 菜单按钮-序列化器 """ + class Meta: + model = MenuButton + fields = ['id', 'name', 'value', 'api', 'method'] + read_only_fields = ["id"] + + +class MenuButtonInitSerializer(CustomModelSerializer): + """ + 初始化菜单按钮-序列化器 + """ + + class Meta: + model = MenuButton + fields = ['id', 'name', 'value', 'api', 'method', 'menu'] + read_only_fields = ["id"] + +class MenuButtonCreateUpdateSerializer(CustomModelSerializer): + """ + 初始化菜单按钮-序列化器 + """ + class Meta: model = MenuButton fields = "__all__" @@ -33,4 +54,6 @@ class MenuButtonViewSet(CustomModelViewSet): """ queryset = MenuButton.objects.all() serializer_class = MenuButtonSerializer + create_serializer_class = MenuButtonCreateUpdateSerializer + update_serializer_class = MenuButtonCreateUpdateSerializer extra_filter_backends = [] diff --git a/backend/dvadmin/system/views/message_center.py b/backend/dvadmin/system/views/message_center.py index 5d933c3..072db85 100644 --- a/backend/dvadmin/system/views/message_center.py +++ b/backend/dvadmin/system/views/message_center.py @@ -1,15 +1,14 @@ # -*- coding: utf-8 -*- import json + +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer from django_restql.fields import DynamicSerializerMethodField from rest_framework import serializers from rest_framework.decorators import action, permission_classes from rest_framework.permissions import IsAuthenticated, AllowAny -from application.websocketConfig import websocket_push from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser -from dvadmin.system.views.dept import DeptSerializer -from dvadmin.system.views.role import RoleSerializer -from dvadmin.system.views.user import UserSerializer from dvadmin.utils.json_response import SuccessResponse, DetailResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.viewset import CustomModelViewSet @@ -28,6 +27,7 @@ class MessageCenterSerializer(CustomModelSerializer): roles = instance.target_role.all() # You can do what ever you want in here # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.role import RoleSerializer serializer = RoleSerializer( roles, many=True, @@ -39,6 +39,7 @@ class MessageCenterSerializer(CustomModelSerializer): users = instance.target_user.all() # You can do what ever you want in here # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.user import UserSerializer serializer = UserSerializer( users, many=True, @@ -50,6 +51,7 @@ class MessageCenterSerializer(CustomModelSerializer): dept = instance.target_dept.all() # You can do what ever you want in here # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.dept import DeptSerializer serializer = DeptSerializer( dept, many=True, @@ -78,20 +80,33 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer): """ 目标用户序列化器-序列化器 """ + is_read = serializers.SerializerMethodField() + + def get_is_read(self, instance): + user_id = self.request.user.id + message_center_id = instance.id + queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id,users_id=user_id).first() + return queryset.is_read class Meta: - model = MessageCenterTargetUser + model = MessageCenter fields = "__all__" read_only_fields = ["id"] - def to_representation(self, instance): - data = super().to_representation(instance) - data['title'] = instance.messagecenter.title - data['content'] = instance.messagecenter.content - data['target_type'] = instance.messagecenter.target_type - data['id'] = instance.messagecenter.id - return data - +def websocket_push(user_id, message): + """ + 主动推送消息 + """ + username = "user_"+str(user_id) + print(103,message) + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + username, + { + "type": "push.message", + "json": message + } + ) class MessageCenterCreateSerializer(CustomModelSerializer): """ @@ -105,10 +120,10 @@ class MessageCenterCreateSerializer(CustomModelSerializer): # 在保存之前,根据目标类型,把目标用户查询出来并保存 users = initial_data.get('target_user', []) if target_type in [1]: # 按角色 - target_role = initial_data.get('target_role') + target_role = initial_data.get('target_role',[]) users = Users.objects.exclude(is_deleted=True).filter(role__id__in=target_role).values_list('id', flat=True) if target_type in [2]: # 按部门 - target_dept = initial_data.get('target_dept') + target_dept = initial_data.get('target_dept',[]) users = Users.objects.exclude(is_deleted=True).filter(dept__id__in=target_dept).values_list('id', flat=True) if target_type in [3]: # 系统通知 users = Users.objects.exclude(is_deleted=True).values_list('id', flat=True) @@ -123,8 +138,8 @@ class MessageCenterCreateSerializer(CustomModelSerializer): targetuser_instance.save() for user in users: unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count() - websocket_push(user, {"sender": 'system', "contentType": 'TEXT', - "content": {"model": 'message_center', "unread": unread_count}}) + websocket_push(user, message={"sender": 'system', "contentType": 'SYSTEM', + "content": '您有一条新消息~', "unread": unread_count}) return data class Meta: @@ -166,8 +181,8 @@ class MessageCenterViewSet(CustomModelViewSet): serializer = self.get_serializer(instance) # 主动推送消息 unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count() - websocket_push(user_id, {"sender": 'system', "contentType": 'TEXT', - "content": {"model": 'message_center', "unread": unread_count}}) + websocket_push(user_id, message={"sender": 'system', "contentType": 'TEXT', + "content": '您查看了一条消息~', "unread": unread_count}) return DetailResponse(data=serializer.data, msg="获取成功") @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) @@ -176,7 +191,9 @@ class MessageCenterViewSet(CustomModelViewSet): 获取接收到的消息 """ self_user_id = self.request.user.id - queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('-create_datetime') + # queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('-create_datetime') + queryset = MessageCenter.objects.filter(target_user__id=self_user_id) + print(queryset) # queryset = self.filter_queryset(queryset) page = self.paginate_queryset(queryset) if page is not None: diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index 66aac90..727e355 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.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 -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 @@ -61,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', [])) @@ -76,11 +80,21 @@ 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 - fields = '__all__' + fields = ['id', 'parent', 'name', 'menuPermission'] class RoleViewSet(CustomModelViewSet): @@ -96,12 +110,108 @@ 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): - """通过角色id获取该角色用于的菜单""" - # instance = self.get_object() - # queryset = instance.menu.all() - queryset = Menu.objects.filter(status=1).all() - serializer = MenuPermissonSerializer(queryset, many=True) - return SuccessResponse(data=serializer.data) + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def role_get_menu(self, request): + """根据当前用户的角色返回角色拥有的菜单""" + is_superuser = request.user.is_superuser + 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: + 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,request=request) + return DetailResponse(data=serializer.data) + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def data_scope(self, request): + is_superuser = request.user.is_superuser + role_queryset = Role.objects.filter(users__id=request.user.id).values_list('data_range', flat=True) + if is_superuser: + data = [ + { + "value": 0, + "label": '仅本人数据权限' + }, + { + "value": 1, + "label": '本部门及以下数据权限' + }, + { + "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) + + @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/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index 151f05c..046c2b6 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -231,7 +231,6 @@ class UserProfileImportSerializer(CustomModelSerializer): class Meta: model = Users exclude = ( - "password", "post", "user_permissions", "groups", diff --git a/backend/dvadmin/utils/exception.py b/backend/dvadmin/utils/exception.py index b9d2d51..a6996df 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,16 @@ 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 + 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 = "删除失败:该条数据与其他数据有相关绑定" @@ -45,9 +53,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/import_export.py b/backend/dvadmin/utils/import_export.py index 882ff4c..2bd6e1e 100644 --- a/backend/dvadmin/utils/import_export.py +++ b/backend/dvadmin/utils/import_export.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- import os import re +from datetime import datetime import openpyxl from django.conf import settings +from dvadmin.utils.validator import CustomValidationError + def import_to_data(file_url, field_data, m2m_fields=None): """ @@ -18,6 +21,10 @@ def import_to_data(file_url, field_data, m2m_fields=None): file_path_dir = os.path.join(settings.BASE_DIR, file_url) workbook = openpyxl.load_workbook(file_path_dir) table = workbook[workbook.sheetnames[0]] + theader = tuple(table.values)[0] #Excel的表头 + is_update = '更新主键(勿改)' in theader #是否导入更新 + if is_update is False: #不是更新时,删除id列 + field_data.pop('id') # 获取参数映射 validation_data_dict = {} for key, value in field_data.items(): @@ -40,15 +47,30 @@ def import_to_data(file_url, field_data, m2m_fields=None): if i == 0: continue array = {} - for index, key in enumerate(field_data.keys()): + for index, item in enumerate(field_data.items()): + items = list(item) + key = items[0] + values = items[1] + value_type = 'str' + if isinstance(values, dict): + value_type = values.get('type','str') cell_value = table.cell(row=row + 1, column=index + 2).value - # 由于excel导入数字类型后,会出现数字加 .0 的,进行处理 - if type(cell_value) is float and str(cell_value).split(".")[1] == "0": - cell_value = int(str(cell_value).split(".")[0]) - if type(cell_value) is str: - cell_value = cell_value.strip(" \t\n\r") - if cell_value is None: + if cell_value is None or cell_value=='': continue + elif value_type == 'date': + print(61, datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S').date()) + try: + cell_value = datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S').date() + except: + raise CustomValidationError('日期格式不正确') + elif value_type == 'datetime': + cell_value = datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S') + else: + # 由于excel导入数字类型后,会出现数字加 .0 的,进行处理 + if type(cell_value) is float and str(cell_value).split(".")[1] == "0": + cell_value = int(str(cell_value).split(".")[0]) + elif type(cell_value) is str: + cell_value = cell_value.strip(" \t\n\r") if key in validation_data_dict: array[key] = validation_data_dict.get(key, {}).get(cell_value, None) if key in m2m_fields: diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index 39913fb..44f51cc 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -134,35 +134,116 @@ 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(156,ele) + 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 - ] - 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])} + 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() + ws1 = wb.create_sheet("data", 1) + ws1.sheet_state = "hidden" + ws = wb.active + import_field_dict = {} + header_data = ["序号","更新主键(勿改)"] + hidden_header = ["#","id"] + #----设置选项---- + validation_data_dict = {} + for index, item in enumerate(self.import_field_dict.items()): + items = list(item) + key = items[0] + value = items[1] + if isinstance(value, dict): + header_data.append(value.get("title")) + hidden_header.append(value.get('display')) + choices = value.get("choices", {}) + if choices.get("data"): + data_list = [] + data_list.extend(choices.get("data").keys()) + validation_data_dict[value.get("title")] = data_list + elif choices.get("queryset") and choices.get("values_name"): + data_list = choices.get("queryset").values_list(choices.get("values_name"), flat=True) + validation_data_dict[value.get("title")] = list(data_list) + else: + continue + column_letter = get_column_letter(len(validation_data_dict)) + dv = DataValidation( + type="list", + formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[value.get('title')]) + 1}", + allow_blank=True, + ) + ws.add_data_validation(dv) + dv.add(f"{get_column_letter(index + 3)}2:{get_column_letter(index + 3)}1048576") else: - filter_dic = {i: ele.get(i) for i in list(set(unique_list)) if ele.get(i) is not None} - 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"导入成功!") + header_data.append(value) + hidden_header.append(key) + # 添加数据列 + ws1.append(list(validation_data_dict.keys())) + for index, validation_data in enumerate(validation_data_dict.values()): + for inx, ele in enumerate(validation_data): + ws1[f"{get_column_letter(index + 1)}{inx + 2}"] = ele + #-------- + 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("") + elif isinstance(val,list): + results_list.append(str(val)) + else: + results_list.append(val) + # 计算最大列宽度 + if isinstance(val,str): + 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: @@ -207,6 +288,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): """ 导出功能 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) diff --git a/backend/gunicorn.py b/backend/gunicorn.py new file mode 100644 index 0000000..1772fbc --- /dev/null +++ b/backend/gunicorn.py @@ -0,0 +1,48 @@ +# gunicorn.conf +# coding:utf-8 +# 启动命令:gunicorn -c gunicorn.py application.asgi:application +import multiprocessing +# 并行工作进程数, int,cpu数量*2+1 推荐进程数 +workers = multiprocessing.cpu_count() * 2 + 1 +# 指定每个进程开启的线程数 +threads = 3 +# 绑定的ip与端口 +bind = '0.0.0.0:8000' +# 设置守护进程,将进程交给第三方管理 +daemon = 'false' +# 工作模式协程,默认的是sync模式,推荐使用 gevent,此处使用与uvicorn配合使用 uvicorn.workers.UvicornWorker +worker_class = 'uvicorn.workers.UvicornWorker' +# 设置最大并发量(每个worker处理请求的工作线程数,正整数,默认为1) +worker_connections = 10000 +# 最大客户端并发数量,默认情况下这个值为1000。此设置将影响gevent和eventlet工作模式 +# 每个工作进程将在处理max_requests请求后自动重新启动该进程 +max_requests = 10000 +max_requests_jitter = 200 +# 设置进程文件目录 +pidfile = './gunicorn.pid' +# 日志级别,这个日志级别指的是错误日志的级别,而访问日志的级别无法设置 +loglevel = 'info' +# 设置gunicorn访问日志格式,错误日志无法设置 +access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"' +# 监听队列 +backlog = 512 +#进程名 +proc_name = 'gunicorn_process' +# 设置超时时间120s,默认为30s。按自己的需求进行设置timeout = 120 +timeout = 120 +# 超时重启 +graceful_timeout = 300 +# 在keep-alive连接上等待请求的秒数,默认情况下值为2。一般设定在1~5秒之间。 +keepalive = 3 +# HTTP请求行的最大大小,此参数用于限制HTTP请求行的允许大小,默认情况下,这个值为4094。 +# 值是0~8190的数字。此参数可以防止任何DDOS攻击 +limit_request_line = 5120 +# 限制HTTP请求中请求头字段的数量。 +# 此字段用于限制请求头字段的数量以防止DDOS攻击,与limit-request-field-size一起使用可以提高安全性。 +# 默认情况下,这个值为100,这个值不能超过32768 +limit_request_fields = 101 +# 限制HTTP请求中请求头的大小,默认情况下这个值为8190。 +# 值是一个整数或者0,当该值为0时,表示将对请求头大小不做限制 +limit_request_field_size = 0 +# 记录到标准输出 +accesslog = '-' diff --git a/backend/requirements.txt b/backend/requirements.txt index 1aa39d9..fb07b24 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -43,3 +43,7 @@ whitenoise==5.3.0 openpyxl==3.0.9 channels==3.0.5 channels-redis==3.4.1 +uvicorn==0.20.0 +gunicorn==20.1.0 +gevent==22.10.2 +websockets==10.4 diff --git a/docker_env/django/DockerfileBuild b/docker_env/django/DockerfileBuild index 4c590d2..b5d4ac3 100644 --- a/docker_env/django/DockerfileBuild +++ b/docker_env/django/DockerfileBuild @@ -2,7 +2,7 @@ FROM python:3.8-alpine RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories RUN apk update && apk add bash bash-doc bash-completion git freetds-dev jpeg-dev linux-headers mysql-client mariadb-dev build-base libffi-dev openssl-dev zlib-dev bzip2-dev pcre-dev ncurses-dev readline-dev tk-dev postgresql-dev WORKDIR /backend -COPY ./backend/requirements.txt / -COPY ./docker_env/requirements-all.txt / +COPY ./backend/requirements.txt . +COPY ./docker_env/requirements-all.txt . RUN python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /requirements.txt RUN python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /requirements-all.txt \ No newline at end of file diff --git a/docker_env/web/DockerfileBuild b/docker_env/web/DockerfileBuild index d2e687d..b123505 100644 --- a/docker_env/web/DockerfileBuild +++ b/docker_env/web/DockerfileBuild @@ -1,3 +1,4 @@ FROM node:14-alpine -COPY ./web/package.json / +WORKDIR /web/ +COPY ./web/package.json . RUN npm install --registry=https://registry.npm.taobao.org diff --git a/web/package.json b/web/package.json index 484ca41..336ae62 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "django-vue-admin", - "version": "2.0.7", + "version": "2.0.8", "scripts": { "serve": "vue-cli-service serve --open", "start": "npm run serve", diff --git a/web/src/api/service.js b/web/src/api/service.js index 749385e..e5ab0f8 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,8 @@ export const downloadFile = function ({ url, params, method, filename }) { responseType: 'blob' // headers: {Accept: 'application/vnd.openxmlformats-officedocument'} }).then(res => { - const fileName = (filename === undefined ? false : filename + '.xls') || decodeURI(res.headers['content-disposition'].split('=')[1]) || '文件导出.xls' + const xlsxName = window.decodeURI(res.headers['content-disposition'].split('=')[1]) + const fileName = xlsxName || `${filename}.xlsx` if (res) { const blob = new Blob([res.data], { type: 'charset=utf-8' }) const elink = document.createElement('a') diff --git a/web/src/api/websocket.js b/web/src/api/websocket.js index c9a47ec..83a7e9d 100644 --- a/web/src/api/websocket.js +++ b/web/src/api/websocket.js @@ -29,9 +29,11 @@ function webSocketOnError (e) { */ function webSocketOnMessage (e) { const data = JSON.parse(e.data) + const { unread } = data + store.dispatch('d2admin/messagecenter/setUnread', unread || 0) if (data.contentType === 'SYSTEM') { ElementUI.Notification({ - title: 'websocket', + title: '系统消息', message: data.content, type: 'success', position: 'bottom-right', @@ -54,11 +56,13 @@ function webSocketOnMessage (e) { duration: 0 }) } else { - const { content } = data - if (content.model === 'message_center') { - const unread = content.unread - store.dispatch('d2admin/messagecenter/setUnread', unread) - } + ElementUI.Notification({ + title: '温馨提示', + message: data.content, + type: 'info', + position: 'bottom-right', + duration: 3000 + }) } } // 关闭websiocket diff --git a/web/src/components/importExcel/index.vue b/web/src/components/importExcel/index.vue index 118e77b..1f5241e 100644 --- a/web/src/components/importExcel/index.vue +++ b/web/src/components/importExcel/index.vue @@ -3,36 +3,37 @@ 导入 - - - -
- 将文件拖到此处,或 - 点击上传 + +
+ + +
+ 将文件拖到此处,或 + 点击上传 +
+
提示:仅允许导入“xls”或“xlsx”格式文件!
+
+
+ 下载导入模板 + 批量更新模板 +
-
- - + -
提示:仅允许导入“xls”或“xlsx”格式文件!
- -
下载模板
- - +
@@ -63,11 +64,22 @@ export default { } } }, - importApi: { // 导入接口地址 + api: { // 导入接口地址 type: String, default () { return undefined } + }, + fieldOptions: { + type: Array, + default () { + return [] + } + } + }, + data () { + return { + loading: false } }, methods: { @@ -78,7 +90,17 @@ export default { /** 下载模板操作 */ importTemplate () { downloadFile({ - url: util.baseURL() + this.importApi, + url: this.api + 'import_data/', + params: {}, + method: 'get' + }) + }, + /*** + * 批量更新模板 + */ + updateTemplate () { + downloadFile({ + url: this.api + 'update_template/', params: {}, method: 'get' }) @@ -92,17 +114,17 @@ export default { const that = this // that.upload.open = false that.upload.isUploading = false + that.loading = true that.$refs.upload.clearFiles() // 是否更新已经存在的用户数据 return request({ - url: that.importApi, + url: util.baseURL() + that.api + 'import_data/', method: 'post', data: { - url: response.data.url, - updateSupport: that.upload.updateSupport + url: response.data.url } }).then(response => { - // this.$alert("导入成功!", "导入结果", { dangerouslyUseHTMLString: true }); + that.loading = false that.$alert('导入成功', '导入完成', { confirmButtonText: '确定', callback: action => { diff --git a/web/src/layout/header-aside/components/header-message/components/msg-list/index.vue b/web/src/layout/header-aside/components/header-message/components/msg-list/index.vue index 5b542d5..eceed84 100644 --- a/web/src/layout/header-aside/components/header-message/components/msg-list/index.vue +++ b/web/src/layout/header-aside/components/header-message/components/msg-list/index.vue @@ -3,7 +3,7 @@ 消息中心

{{msgObj.title}}

-
{{msgObj.content}}
+
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/dept/api.js b/web/src/views/system/dept/api.js index 362d5d0..dbce636 100644 --- a/web/src/views/system/dept/api.js +++ b/web/src/views/system/dept/api.js @@ -1,6 +1,6 @@ import { request } from '@/api/service' +import XEUtils from 'xe-utils' export const urlPrefix = '/api/system/dept/' - /** * 列表查询 */ @@ -62,5 +62,7 @@ export function DeptLazy (query) { url: '/api/system/dept_lazy_tree/', method: 'get', params: query + }).then(res => { + return XEUtils.toArrayTree(res.data, { parentKey: 'parent' }) }) } diff --git a/web/src/views/system/dept/crud.js b/web/src/views/system/dept/crud.js index b3ff85c..be9afcf 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 }) }, @@ -105,32 +108,15 @@ export const crudOptions = (vm) => { value: 'id', cache: false, getData: (url, dict, { form, component }) => { // 配置此参数会覆盖全局的getRemoteDictFunc - return api.DeptLazy().then(ret => { return ret.data }) - }, - getNodes (values, data) { - // 配置行展示远程获取nodes - return new Promise((resolve, reject) => { - const row = vm.getEditRow() - resolve(row.parent !== null ? [{ name: row.parent_name, id: row.parent }] : []) - }) + return api.DeptLazy().then(ret => { return ret }) } }, form: { - helper: '默认留空为根节点', + helper: '默认留空为创建者的部门', component: { span: 12, props: { - multiple: false, - elProps: { - lazy: true, - hasChild: 'has_children', - load (node, resolve) { - // 懒加载 - api.DeptLazy({ parent: node.data.id }).then((data) => { - resolve(data.data) - }) - } - } + multiple: false } } } diff --git a/web/src/views/system/dept/index.vue b/web/src/views/system/dept/index.vue index b479d48..e8b5063 100644 --- a/web/src/views/system/dept/index.vue +++ b/web/src/views/system/dept/index.vue @@ -17,7 +17,7 @@ > 新增 导入 diff --git a/web/src/views/system/messageCenter/index.vue b/web/src/views/system/messageCenter/index.vue index ec1fdcf..8832409 100644 --- a/web/src/views/system/messageCenter/index.vue +++ b/web/src/views/system/messageCenter/index.vue @@ -6,6 +6,7 @@ v-bind="_crudProps" v-on="_crudListeners" @onView="onView" + @doDialogClosed="doDialogClosed" >
@@ -39,7 +40,6 @@ export default { tabActivted: 'send' } }, - computed: { }, methods: { @@ -78,12 +78,15 @@ export default { template: viewTemplate }) this.infoRequest(row) - this.doRefresh() }, onTabClick (tab) { const { name } = tab this.tabActivted = name this.doRefresh() + }, + // 关闭事件 + doDialogClosed (context) { + this.doRefresh() } } } diff --git a/web/src/views/system/role/crud.js b/web/src/views/system/role/crud.js index 6e2169c..e756f22 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,13 @@ export const crudOptions = (vm) => { form: { value: false, component: { - placeholder: '请选择是否管理员' + placeholder: '请选择是否管理员', + show (context) { + return vm.info.is_superuser + } } } }, - { 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 2b6eb6b..8e9d842 100644 --- a/web/src/views/system/rolePermission/api.js +++ b/web/src/views/system/rolePermission/api.js @@ -47,11 +47,38 @@ 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 => { // 将列表数据转换为树形数据 - return res.data.data + return res.data + }) +} + +/** + * 获取数据权限 + * @param obj + * @returns {*} + * @constructor + */ +export function GetDataScope () { + return request({ + url: '/api/system/role/data_scope/', + method: 'get', + 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 e47e53d..135f396 100644 --- a/web/src/views/system/rolePermission/index.vue +++ b/web/src/views/system/rolePermission/index.vue @@ -119,7 +119,6 @@