# Conflicts:
#	web/src/api/service.js
pull/86/head v2.0.8
李强 2023-01-13 23:06:46 +08:00
commit a9d6612e9d
34 changed files with 718 additions and 216 deletions

View File

@ -14,10 +14,10 @@ from channels.routing import ProtocolTypeRouter, URLRouter
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
from application.routing import websocket_urlpatterns
http_application = get_asgi_application() http_application = get_asgi_application()
from application.routing import websocket_urlpatterns
application = ProtocolTypeRouter({ application = ProtocolTypeRouter({
"http":http_application, "http":http_application,
'websocket': AuthMiddlewareStack( 'websocket': AuthMiddlewareStack(

View File

@ -48,6 +48,16 @@ def _get_all_system_config():
value = system_config.get("value", "") value = system_config.get("value", "")
if value and system_config.get("form_item_type") == 7: if value and system_config.get("form_item_type") == 7:
value = value[0].get("url") 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 data[f"{system_config.get('parent__key')}.{system_config.get('key')}"] = value
return data return data

View File

@ -8,34 +8,43 @@ import json
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from jwt import InvalidSignatureError from jwt import InvalidSignatureError
from rest_framework.request import Request
from application import settings 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 = {} send_dict = {}
# 发送消息结构体 # 发送消息结构体
def set_message(sender, msg_type, msg): def set_message(sender, msg_type, msg, unread=0):
text = { text = {
'sender': sender, 'sender': sender,
'contentType': msg_type, 'contentType': msg_type,
'content': msg, 'content': msg,
'unread': unread
} }
return text return text
#异步获取消息中心的目标用户
# 异步获取消息中心的目标用户
@database_sync_to_async @database_sync_to_async
def _get_message_center_instance(message_id): def _get_message_center_instance(message_id):
from dvadmin.system.models import MessageCenter 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: if _MessageCenter:
return _MessageCenter return _MessageCenter
else: else:
return [] return []
@database_sync_to_async @database_sync_to_async
def _get_message_unread(user_id): def _get_message_unread(user_id):
"""获取用户的未读消息数量"""
from dvadmin.system.models import MessageCenterTargetUser 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 return count or 0
@ -44,6 +53,7 @@ def request_data(scope):
qs = urllib.parse.parse_qs(query_string) qs = urllib.parse.parse_qs(query_string)
return qs return qs
class DvadminWebSocket(AsyncJsonWebsocketConsumer): class DvadminWebSocket(AsyncJsonWebsocketConsumer):
async def connect(self): async def connect(self):
try: try:
@ -52,27 +62,33 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"]) decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"])
if decoded_result: if decoded_result:
self.user_id = decoded_result.get('user_id') 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( await self.channel_layer.group_add(
self.chat_group_name, self.chat_group_name,
self.channel_name self.channel_name
) )
await self.accept() await self.accept()
# 发送连接成功
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
# 主动推送消息 # 主动推送消息
unread_count = await _get_message_unread(self.user_id) 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: except InvalidSignatureError:
await self.disconnect(None) await self.disconnect(None)
async def disconnect(self, close_code): async def disconnect(self, close_code):
# Leave room group # Leave room group
await self.channel_layer.group_discard(self.chat_group_name, self.channel_name) await self.channel_layer.group_discard(self.chat_group_name, self.channel_name)
print("连接关闭") print("连接关闭")
await self.close(close_code) try:
await self.close(close_code)
except Exception:
pass
class MegCenter(DvadminWebSocket): class MegCenter(DvadminWebSocket):
@ -92,21 +108,74 @@ class MegCenter(DvadminWebSocket):
) )
async def push_message(self, event): async def push_message(self, event):
"""消息发送"""
message = event['json'] message = event['json']
await self.send(text_data=json.dumps(message)) await self.send(text_data=json.dumps(message))
def websocket_push(user_id, message): class MessageCreateSerializer(CustomModelSerializer):
""" """
主动推送消息 消息中心-新增-序列化器
""" """
username = "user_"+str(user_id) class Meta:
print(103,message) model = MessageCenter
fields = "__all__"
read_only_fields = ["id"]
def websocket_push(user_id,message):
username = "user_" + str(user_id)
channel_layer = get_channel_layer() channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)( async_to_sync(channel_layer.group_send)(
username, username,
{ {
"type": "push.message", "type": "push.message",
"json": 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}
}
)

View File

@ -2,4 +2,4 @@
# python manage.py makemigrations # python manage.py makemigrations
# python manage.py migrate # python manage.py migrate
# python manage.py init -y # python manage.py init -y
daphne -b 0.0.0.0 -p 8000 application.asgi:application gunicorn -c gunicorn.py application.asgi:application

View File

@ -120,6 +120,25 @@ class Dept(CoreModel):
help_text="上级部门", 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: class Meta:
db_table = table_prefix + "system_dept" db_table = table_prefix + "system_dept"
verbose_name = "部门表" verbose_name = "部门表"

View File

@ -22,6 +22,13 @@ class DeptSerializer(CustomModelSerializer):
parent_name = serializers.CharField(read_only=True, source='parent.name') parent_name = serializers.CharField(read_only=True, source='parent.name')
status_label = serializers.SerializerMethodField() status_label = serializers.SerializerMethodField()
has_children = 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): def get_status_label(self, obj: Dept):
if obj.status: if obj.status:
@ -99,6 +106,9 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
""" """
def create(self, validated_data): 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 = super().create(validated_data)
instance.dept_belong_id = instance.id instance.dept_belong_id = instance.id
instance.save() instance.save()
@ -133,46 +143,47 @@ class DeptViewSet(CustomModelViewSet):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
# 如果懒加载,则只返回父级 # 如果懒加载,则只返回父级
queryset = self.filter_queryset(self.get_queryset()) params = request.query_params
lazy = self.request.query_params.get('lazy') parent = params.get('parent', None)
parent = self.request.query_params.get('parent') if params:
if lazy: if parent:
# 如果懒加载模式,返回全部 queryset = self.queryset.filter(status=True, parent=parent)
if not parent: else:
role_list = request.user.role.filter(status=1).values("admin", "data_range") queryset = self.queryset.filter(status=True)
is_admin = False else:
for ele in role_list: queryset = self.queryset.filter(status=True, parent__isnull=True)
if 3 == ele.get("data_range") or ele.get("admin") == True: queryset = self.filter_queryset(queryset)
is_admin = True serializer = DeptSerializer(queryset, many=True, request=request)
break data = serializer.data
if self.request.user.is_superuser or is_admin: return SuccessResponse(data=data)
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="获取成功")
def dept_lazy_tree(self, request, *args, **kwargs): def dept_lazy_tree(self, request, *args, **kwargs):
parent = self.request.query_params.get('parent') parent = self.request.query_params.get('parent')
queryset = self.filter_queryset(self.get_queryset()) is_superuser = request.user.is_superuser
if not parent: if is_superuser:
if self.request.user.is_superuser: queryset = Dept.objects.values('id', 'name', 'parent')
queryset = queryset.filter(parent__isnull=True) else:
else: data_range = request.user.role.values_list('data_range', flat=True)
queryset = queryset.filter(id=self.request.user.dept_id) user_dept_id = request.user.dept.id
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent') dept_list = [user_dept_id]
return DetailResponse(data=data, msg="获取成功") 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]) @action(methods=["GET"], detail=False, permission_classes=[AnonymousUserPermission])
def all_dept(self, request, *args, **kwargs): def all_dept(self, request, *args, **kwargs):
self.extra_filter_backends = []
queryset = self.filter_queryset(self.get_queryset()) queryset = self.filter_queryset(self.get_queryset())
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent') data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
return DetailResponse(data=data, msg="获取成功") return DetailResponse(data=data, msg="获取成功")

View File

@ -10,7 +10,7 @@ from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from dvadmin.system.models import Menu, MenuButton 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.json_response import SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -106,7 +106,7 @@ class MenuInitSerializer(CustomModelSerializer):
"value": menu_button_data['value'] "value": menu_button_data['value']
} }
instance_obj = MenuButton.objects.filter(**filter_data).first() 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.is_valid(raise_exception=True)
serializer.save() serializer.save()
return instance return instance
@ -180,9 +180,7 @@ class MenuViewSet(CustomModelViewSet):
return SuccessResponse(data=data, total=len(data), msg="获取成功") return SuccessResponse(data=data, total=len(data), msg="获取成功")
def list(self,request): def list(self,request):
""" """懒加载"""
懒加载
"""
params = request.query_params params = request.query_params
parent = params.get('parent', None) parent = params.get('parent', None)
if params: if params:

View File

@ -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: class Meta:
model = MenuButton model = MenuButton
fields = "__all__" fields = "__all__"
@ -33,4 +54,6 @@ class MenuButtonViewSet(CustomModelViewSet):
""" """
queryset = MenuButton.objects.all() queryset = MenuButton.objects.all()
serializer_class = MenuButtonSerializer serializer_class = MenuButtonSerializer
create_serializer_class = MenuButtonCreateUpdateSerializer
update_serializer_class = MenuButtonCreateUpdateSerializer
extra_filter_backends = [] extra_filter_backends = []

View File

@ -1,15 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import json import json
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django_restql.fields import DynamicSerializerMethodField from django_restql.fields import DynamicSerializerMethodField
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action, permission_classes from rest_framework.decorators import action, permission_classes
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.permissions import IsAuthenticated, AllowAny
from application.websocketConfig import websocket_push
from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser 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.json_response import SuccessResponse, DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -28,6 +27,7 @@ class MessageCenterSerializer(CustomModelSerializer):
roles = instance.target_role.all() roles = instance.target_role.all()
# You can do what ever you want in here # You can do what ever you want in here
# `parsed_query` param is passed to BookSerializer to allow further querying # `parsed_query` param is passed to BookSerializer to allow further querying
from dvadmin.system.views.role import RoleSerializer
serializer = RoleSerializer( serializer = RoleSerializer(
roles, roles,
many=True, many=True,
@ -39,6 +39,7 @@ class MessageCenterSerializer(CustomModelSerializer):
users = instance.target_user.all() users = instance.target_user.all()
# You can do what ever you want in here # You can do what ever you want in here
# `parsed_query` param is passed to BookSerializer to allow further querying # `parsed_query` param is passed to BookSerializer to allow further querying
from dvadmin.system.views.user import UserSerializer
serializer = UserSerializer( serializer = UserSerializer(
users, users,
many=True, many=True,
@ -50,6 +51,7 @@ class MessageCenterSerializer(CustomModelSerializer):
dept = instance.target_dept.all() dept = instance.target_dept.all()
# You can do what ever you want in here # You can do what ever you want in here
# `parsed_query` param is passed to BookSerializer to allow further querying # `parsed_query` param is passed to BookSerializer to allow further querying
from dvadmin.system.views.dept import DeptSerializer
serializer = DeptSerializer( serializer = DeptSerializer(
dept, dept,
many=True, 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: class Meta:
model = MessageCenterTargetUser model = MessageCenter
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
def to_representation(self, instance): def websocket_push(user_id, message):
data = super().to_representation(instance) """
data['title'] = instance.messagecenter.title 主动推送消息
data['content'] = instance.messagecenter.content """
data['target_type'] = instance.messagecenter.target_type username = "user_"+str(user_id)
data['id'] = instance.messagecenter.id print(103,message)
return data channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
username,
{
"type": "push.message",
"json": message
}
)
class MessageCenterCreateSerializer(CustomModelSerializer): class MessageCenterCreateSerializer(CustomModelSerializer):
""" """
@ -105,10 +120,10 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
# 在保存之前,根据目标类型,把目标用户查询出来并保存 # 在保存之前,根据目标类型,把目标用户查询出来并保存
users = initial_data.get('target_user', []) users = initial_data.get('target_user', [])
if target_type in [1]: # 按角色 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) users = Users.objects.exclude(is_deleted=True).filter(role__id__in=target_role).values_list('id', flat=True)
if target_type in [2]: # 按部门 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) users = Users.objects.exclude(is_deleted=True).filter(dept__id__in=target_dept).values_list('id', flat=True)
if target_type in [3]: # 系统通知 if target_type in [3]: # 系统通知
users = Users.objects.exclude(is_deleted=True).values_list('id', flat=True) users = Users.objects.exclude(is_deleted=True).values_list('id', flat=True)
@ -123,8 +138,8 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
targetuser_instance.save() targetuser_instance.save()
for user in users: for user in users:
unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count() unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count()
websocket_push(user, {"sender": 'system', "contentType": 'TEXT', websocket_push(user, message={"sender": 'system', "contentType": 'SYSTEM',
"content": {"model": 'message_center', "unread": unread_count}}) "content": '您有一条新消息~', "unread": unread_count})
return data return data
class Meta: class Meta:
@ -166,8 +181,8 @@ class MessageCenterViewSet(CustomModelViewSet):
serializer = self.get_serializer(instance) serializer = self.get_serializer(instance)
# 主动推送消息 # 主动推送消息
unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count() unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count()
websocket_push(user_id, {"sender": 'system', "contentType": 'TEXT', websocket_push(user_id, message={"sender": 'system', "contentType": 'TEXT',
"content": {"model": 'message_center', "unread": unread_count}}) "content": '您查看了一条消息~', "unread": unread_count})
return DetailResponse(data=serializer.data, msg="获取成功") return DetailResponse(data=serializer.data, msg="获取成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
@ -176,7 +191,9 @@ class MessageCenterViewSet(CustomModelViewSet):
获取接收到的消息 获取接收到的消息
""" """
self_user_id = self.request.user.id 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) # queryset = self.filter_queryset(queryset)
page = self.paginate_queryset(queryset) page = self.paginate_queryset(queryset)
if page is not None: if page is not None:

View File

@ -8,12 +8,13 @@
""" """
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action 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.dept import DeptSerializer
from dvadmin.system.views.menu import MenuSerializer from dvadmin.system.views.menu import MenuSerializer
from dvadmin.system.views.menu_button import MenuButtonSerializer 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.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomUniqueValidator from dvadmin.utils.validator import CustomUniqueValidator
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -61,6 +62,9 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
return super().validate(attrs) return super().validate(attrs)
def save(self, **kwargs): 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 = super().save(**kwargs)
data.dept.set(self.initial_data.get('dept', [])) data.dept.set(self.initial_data.get('dept', []))
data.menu.set(self.initial_data.get('menu', [])) 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: class Meta:
model = Menu model = Menu
fields = '__all__' fields = ['id', 'parent', 'name', 'menuPermission']
class RoleViewSet(CustomModelViewSet): class RoleViewSet(CustomModelViewSet):
@ -96,12 +110,108 @@ class RoleViewSet(CustomModelViewSet):
serializer_class = RoleSerializer serializer_class = RoleSerializer
create_serializer_class = RoleCreateUpdateSerializer create_serializer_class = RoleCreateUpdateSerializer
update_serializer_class = RoleCreateUpdateSerializer update_serializer_class = RoleCreateUpdateSerializer
search_fields = ['name', 'key']
@action(methods=['GET'], detail=True, permission_classes=[]) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def roleId_get_menu(self, request, *args, **kwargs): def role_get_menu(self, request):
"""通过角色id获取该角色用于的菜单""" """根据当前用户的角色返回角色拥有的菜单"""
# instance = self.get_object() is_superuser = request.user.is_superuser
# queryset = instance.menu.all() is_admin = request.user.role.values_list('admin',flat=True)
queryset = Menu.objects.filter(status=1).all() if is_superuser or True in is_admin:
serializer = MenuPermissonSerializer(queryset, many=True) queryset = Menu.objects.filter(status=1).all()
return SuccessResponse(data=serializer.data) 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)

View File

@ -231,7 +231,6 @@ class UserProfileImportSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Users model = Users
exclude = ( exclude = (
"password",
"post", "post",
"user_permissions", "user_permissions",
"groups", "groups",

View File

@ -10,6 +10,7 @@ import logging
import traceback import traceback
from django.db.models import ProtectedError from django.db.models import ProtectedError
from django.http import Http404
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed
from rest_framework.views import set_rollback from rest_framework.views import set_rollback
@ -33,9 +34,16 @@ def CustomExceptionHandler(ex, context):
if isinstance(ex, AuthenticationFailed): if isinstance(ex, AuthenticationFailed):
code = 401 code = 401
msg = ex.detail msg = ex.detail
elif isinstance(ex,Http404):
code = 400
msg = "接口地址不正确"
elif isinstance(ex, DRFAPIException): elif isinstance(ex, DRFAPIException):
set_rollback() set_rollback()
msg = ex.detail 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): elif isinstance(ex, ProtectedError):
set_rollback() set_rollback()
msg = "删除失败:该条数据与其他数据有相关绑定" msg = "删除失败:该条数据与其他数据有相关绑定"
@ -45,9 +53,4 @@ def CustomExceptionHandler(ex, context):
elif isinstance(ex, Exception): elif isinstance(ex, Exception):
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
msg = str(ex) msg = str(ex)
# errorMsg = msg
# for key in errorMsg:
# msg = errorMsg[key][0]
return ErrorResponse(msg=msg, code=code) return ErrorResponse(msg=msg, code=code)

View File

@ -1,10 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import re import re
from datetime import datetime
import openpyxl import openpyxl
from django.conf import settings from django.conf import settings
from dvadmin.utils.validator import CustomValidationError
def import_to_data(file_url, field_data, m2m_fields=None): 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) file_path_dir = os.path.join(settings.BASE_DIR, file_url)
workbook = openpyxl.load_workbook(file_path_dir) workbook = openpyxl.load_workbook(file_path_dir)
table = workbook[workbook.sheetnames[0]] 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 = {} validation_data_dict = {}
for key, value in field_data.items(): 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: if i == 0:
continue continue
array = {} 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 cell_value = table.cell(row=row + 1, column=index + 2).value
# 由于excel导入数字类型后会出现数字加 .0 的,进行处理 if cell_value is None or cell_value=='':
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:
continue 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: if key in validation_data_dict:
array[key] = validation_data_dict.get(key, {}).get(cell_value, None) array[key] = validation_data_dict.get(key, {}).get(cell_value, None)
if key in m2m_fields: if key in m2m_fields:

View File

@ -134,35 +134,116 @@ class ImportSerializerMixin:
ws.add_table(tab) ws.add_table(tab)
wb.save(response) wb.save(response)
return 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") @action(methods=['get'],detail=False)
# 从excel中组织对应的数据结构然后使用序列化器保存 def update_template(self,request):
queryset = self.filter_queryset(self.get_queryset()) queryset = self.filter_queryset(self.get_queryset())
# 获取多对多字段 assert self.import_field_dict, "'%s' 请配置对应的导入模板字段。" % self.__class__.__name__
m2m_fields = [ assert self.import_serializer_class, "'%s' 请配置对应的导入序列化器。" % self.__class__.__name__
ele.name data = self.import_serializer_class(queryset, many=True, request=request).data
for ele in queryset.model._meta.get_fields() # 导出excel 表
if hasattr(ele, "many_to_many") and ele.many_to_many == True response = HttpResponse(content_type="application/msexcel")
] response["Access-Control-Expose-Headers"] = f"Content-Disposition"
data = import_to_data(request.data.get("url"), self.import_field_dict, m2m_fields) response["content-disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}'
unique_list = [ wb = Workbook()
ele.name for ele in queryset.model._meta.get_fields() if hasattr(ele, "unique") and ele.unique == True ws1 = wb.create_sheet("data", 1)
] ws1.sheet_state = "hidden"
for ele in data: ws = wb.active
# 获取 unique 字段 import_field_dict = {}
if queryset.model._meta.unique_together: # 判断是否存在联合主键 header_data = ["序号","更新主键(勿改)"]
filter_dic = {i: ele.get(i) for i in list(queryset.model._meta.unique_together[0])} 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: else:
filter_dic = {i: ele.get(i) for i in list(set(unique_list)) if ele.get(i) is not None} header_data.append(value)
instance = filter_dic and queryset.filter(**filter_dic).first() hidden_header.append(key)
if instance and not updateSupport: # 添加数据列
continue ws1.append(list(validation_data_dict.keys()))
if not filter_dic: for index, validation_data in enumerate(validation_data_dict.values()):
instance = None for inx, ele in enumerate(validation_data):
serializer = self.import_serializer_class(instance, data=ele, request=request) ws1[f"{get_column_letter(index + 1)}{inx + 2}"] = ele
serializer.is_valid(raise_exception=True) #--------
serializer.save() df_len_max = [self.get_string_len(ele) for ele in header_data]
return DetailResponse(msg=f"导入成功!") 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: class ExportSerializerMixin:
@ -207,6 +288,7 @@ class ExportSerializerMixin:
length += 2.1 if ord(char) > 256 else 1 length += 2.1 if ord(char) > 256 else 1
return round(length, 1) if length <= self.export_column_width else self.export_column_width 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): def export_data(self, request: Request, *args, **kwargs):
""" """
导出功能 导出功能

View File

@ -104,6 +104,24 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
return getattr(self.request.user, "id", None) return getattr(self.request.user, "id", None)
return 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 # @cached_property
# def fields(self): # def fields(self):
# fields = BindingDict(self) # fields = BindingDict(self)

48
backend/gunicorn.py Normal file
View File

@ -0,0 +1,48 @@
# gunicorn.conf
# coding:utf-8
# 启动命令gunicorn -c gunicorn.py application.asgi:application
import multiprocessing
# 并行工作进程数, intcpu数量*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 = '-'

View File

@ -43,3 +43,7 @@ whitenoise==5.3.0
openpyxl==3.0.9 openpyxl==3.0.9
channels==3.0.5 channels==3.0.5
channels-redis==3.4.1 channels-redis==3.4.1
uvicorn==0.20.0
gunicorn==20.1.0
gevent==22.10.2
websockets==10.4

View File

@ -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 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 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 WORKDIR /backend
COPY ./backend/requirements.txt / COPY ./backend/requirements.txt .
COPY ./docker_env/requirements-all.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.txt
RUN python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /requirements-all.txt RUN python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /requirements-all.txt

View File

@ -1,3 +1,4 @@
FROM node:14-alpine FROM node:14-alpine
COPY ./web/package.json / WORKDIR /web/
COPY ./web/package.json .
RUN npm install --registry=https://registry.npm.taobao.org RUN npm install --registry=https://registry.npm.taobao.org

View File

@ -1,6 +1,6 @@
{ {
"name": "django-vue-admin", "name": "django-vue-admin",
"version": "2.0.7", "version": "2.0.8",
"scripts": { "scripts": {
"serve": "vue-cli-service serve --open", "serve": "vue-cli-service serve --open",
"start": "npm run serve", "start": "npm run serve",

View File

@ -216,7 +216,7 @@ const refreshTken = function () {
* @param method * @param method
* @param filename * @param filename
*/ */
export const downloadFile = function ({ url, params, method, filename }) { export const downloadFile = function ({ url, params, method, filename = '文件导出' }) {
request({ request({
url: url, url: url,
method: method, method: method,
@ -224,7 +224,8 @@ export const downloadFile = function ({ url, params, method, filename }) {
responseType: 'blob' responseType: 'blob'
// headers: {Accept: 'application/vnd.openxmlformats-officedocument'} // headers: {Accept: 'application/vnd.openxmlformats-officedocument'}
}).then(res => { }).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) { if (res) {
const blob = new Blob([res.data], { type: 'charset=utf-8' }) const blob = new Blob([res.data], { type: 'charset=utf-8' })
const elink = document.createElement('a') const elink = document.createElement('a')

View File

@ -29,9 +29,11 @@ function webSocketOnError (e) {
*/ */
function webSocketOnMessage (e) { function webSocketOnMessage (e) {
const data = JSON.parse(e.data) const data = JSON.parse(e.data)
const { unread } = data
store.dispatch('d2admin/messagecenter/setUnread', unread || 0)
if (data.contentType === 'SYSTEM') { if (data.contentType === 'SYSTEM') {
ElementUI.Notification({ ElementUI.Notification({
title: 'websocket', title: '系统消息',
message: data.content, message: data.content,
type: 'success', type: 'success',
position: 'bottom-right', position: 'bottom-right',
@ -54,11 +56,13 @@ function webSocketOnMessage (e) {
duration: 0 duration: 0
}) })
} else { } else {
const { content } = data ElementUI.Notification({
if (content.model === 'message_center') { title: '温馨提示',
const unread = content.unread message: data.content,
store.dispatch('d2admin/messagecenter/setUnread', unread) type: 'info',
} position: 'bottom-right',
duration: 3000
})
} }
} }
// 关闭websiocket // 关闭websiocket

View File

@ -3,36 +3,37 @@
<el-button size="small" type="success" icon="el-icon-upload" @click="handleImport"> <el-button size="small" type="success" icon="el-icon-upload" @click="handleImport">
<slot>导入</slot> <slot>导入</slot>
</el-button> </el-button>
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload <div v-loading="loading">
ref="upload" <el-upload
:limit="1" ref="upload"
accept=".xlsx, .xls" :limit="1"
:headers="upload.headers" accept=".xlsx, .xls"
:action="upload.url" :headers="upload.headers"
:disabled="upload.isUploading" :action="upload.url"
:on-progress="handleFileUploadProgress" :disabled="upload.isUploading"
:on-success="handleFileSuccess" :on-progress="handleFileUploadProgress"
:auto-upload="false" :on-success="handleFileSuccess"
drag :auto-upload="false"
> drag
<i class="el-icon-upload" /> >
<div class="el-upload__text"> <i class="el-icon-upload"/>
将文件拖到此处 <div class="el-upload__text">
<em>点击上传</em> 将文件拖到此处
<em>点击上传</em>
</div>
<div slot="tip" class="el-upload__tip" style="color:red">提示仅允许导入xlsxlsx格式文件</div>
</el-upload>
<div>
<el-button type="warning" style="font-size:14px;margin-top: 20px" @click="importTemplate"></el-button>
<el-button type="warning" style="font-size:14px;margin-top: 20px" @click="updateTemplate"></el-button>
</div>
</div> </div>
<div slot="tip" class="el-upload__tip"> <div slot="footer" class="dialog-footer">
<el-checkbox size="medium" label="是否更新已经存在的数据" border v-model="upload.updateSupport" /> <el-button type="primary" :disabled="loading" @click="submitFileForm"> </el-button>
<el-button :disabled="loading" @click="upload.open = false"> </el-button>
</div> </div>
<div slot="tip" class="el-upload__tip" style="color:red">提示仅允许导入xlsxlsx格式文件</div> </el-dialog>
</el-upload>
<div><el-link type="primary" style="font-size:14px;margin-top: 20px" @click="importTemplate"></el-link></div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
@ -63,11 +64,22 @@ export default {
} }
} }
}, },
importApi: { // api: { //
type: String, type: String,
default () { default () {
return undefined return undefined
} }
},
fieldOptions: {
type: Array,
default () {
return []
}
}
},
data () {
return {
loading: false
} }
}, },
methods: { methods: {
@ -78,7 +90,17 @@ export default {
/** 下载模板操作 */ /** 下载模板操作 */
importTemplate () { importTemplate () {
downloadFile({ downloadFile({
url: util.baseURL() + this.importApi, url: this.api + 'import_data/',
params: {},
method: 'get'
})
},
/***
* 批量更新模板
*/
updateTemplate () {
downloadFile({
url: this.api + 'update_template/',
params: {}, params: {},
method: 'get' method: 'get'
}) })
@ -92,17 +114,17 @@ export default {
const that = this const that = this
// that.upload.open = false // that.upload.open = false
that.upload.isUploading = false that.upload.isUploading = false
that.loading = true
that.$refs.upload.clearFiles() that.$refs.upload.clearFiles()
// //
return request({ return request({
url: that.importApi, url: util.baseURL() + that.api + 'import_data/',
method: 'post', method: 'post',
data: { data: {
url: response.data.url, url: response.data.url
updateSupport: that.upload.updateSupport
} }
}).then(response => { }).then(response => {
// this.$alert("", "", { dangerouslyUseHTMLString: true }); that.loading = false
that.$alert('导入成功', '导入完成', { that.$alert('导入成功', '导入完成', {
confirmButtonText: '确定', confirmButtonText: '确定',
callback: action => { callback: action => {

View File

@ -3,7 +3,7 @@
<el-divider content-position="left">消息中心</el-divider> <el-divider content-position="left">消息中心</el-divider>
<div v-if="msgObj"> <div v-if="msgObj">
<h3>{{msgObj.title}}</h3> <h3>{{msgObj.title}}</h3>
<div class="content-style">{{msgObj.content}}</div> <div class="content-style" v-html="msgObj.content"></div>
</div> </div>
<div v-else> <div v-else>
<el-empty :image-size="100"></el-empty> <el-empty :image-size="100"></el-empty>

View File

@ -279,7 +279,6 @@ export default {
* @param file * @param file
*/ */
handleAvatarSuccess (res, file) { handleAvatarSuccess (res, file) {
console.log(11, res)
this.userInfo.avatar = res this.userInfo.avatar = res
} }
} }

View File

@ -1,6 +1,6 @@
import { request } from '@/api/service' import { request } from '@/api/service'
import XEUtils from 'xe-utils'
export const urlPrefix = '/api/system/dept/' export const urlPrefix = '/api/system/dept/'
/** /**
* 列表查询 * 列表查询
*/ */
@ -62,5 +62,7 @@ export function DeptLazy (query) {
url: '/api/system/dept_lazy_tree/', url: '/api/system/dept_lazy_tree/',
method: 'get', method: 'get',
params: query params: query
}).then(res => {
return XEUtils.toArrayTree(res.data, { parentKey: 'parent' })
}) })
} }

View File

@ -1,10 +1,10 @@
import * as api from './api' import * as api from './api'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
// pagination: false,
pageOptions: { pageOptions: {
compact: true compact: true
}, },
pagination: false,
options: { options: {
tableType: 'vxe-table', tableType: 'vxe-table',
stripe: false, stripe: false,
@ -14,10 +14,13 @@ export const crudOptions = (vm) => {
highlightCurrentRow: false, highlightCurrentRow: false,
defaultExpandAll: true, defaultExpandAll: true,
treeConfig: { treeConfig: {
transform: true,
rowField: 'id',
parentField: 'parent',
hasChild: 'hasChild',
lazy: true, lazy: true,
hasChild: 'has_children',
loadMethod: ({ row }) => { loadMethod: ({ row }) => {
return api.GetList({ parent: row.id, lazy: true }).then(ret => { return api.GetList({ parent: row.id }).then(ret => {
return ret.data.data return ret.data.data
}) })
}, },
@ -105,32 +108,15 @@ export const crudOptions = (vm) => {
value: 'id', value: 'id',
cache: false, cache: false,
getData: (url, dict, { form, component }) => { // 配置此参数会覆盖全局的getRemoteDictFunc getData: (url, dict, { form, component }) => { // 配置此参数会覆盖全局的getRemoteDictFunc
return api.DeptLazy().then(ret => { return ret.data }) return api.DeptLazy().then(ret => { return ret })
},
getNodes (values, data) {
// 配置行展示远程获取nodes
return new Promise((resolve, reject) => {
const row = vm.getEditRow()
resolve(row.parent !== null ? [{ name: row.parent_name, id: row.parent }] : [])
})
} }
}, },
form: { form: {
helper: '默认留空为根节点', helper: '默认留空为创建者的部门',
component: { component: {
span: 12, span: 12,
props: { props: {
multiple: false, multiple: false
elProps: {
lazy: true,
hasChild: 'has_children',
load (node, resolve) {
// 懒加载
api.DeptLazy({ parent: node.data.id }).then((data) => {
resolve(data.data)
})
}
}
} }
} }
} }

View File

@ -17,7 +17,7 @@
><i class="el-icon-plus" /> 新增</el-button ><i class="el-icon-plus" /> 新增</el-button
> >
<importExcel <importExcel
importApi="api/system/dept/import_data/" api="api/system/dept/"
v-permission="'Import'" v-permission="'Import'"
>导入 >导入
</importExcel> </importExcel>

View File

@ -6,6 +6,7 @@
v-bind="_crudProps" v-bind="_crudProps"
v-on="_crudListeners" v-on="_crudListeners"
@onView="onView" @onView="onView"
@doDialogClosed="doDialogClosed"
> >
<div slot="header"> <div slot="header">
<crud-search ref="search" :options="crud.searchOptions" @submit="handleSearch" /> <crud-search ref="search" :options="crud.searchOptions" @submit="handleSearch" />
@ -39,7 +40,6 @@ export default {
tabActivted: 'send' tabActivted: 'send'
} }
}, },
computed: { computed: {
}, },
methods: { methods: {
@ -78,12 +78,15 @@ export default {
template: viewTemplate template: viewTemplate
}) })
this.infoRequest(row) this.infoRequest(row)
this.doRefresh()
}, },
onTabClick (tab) { onTabClick (tab) {
const { name } = tab const { name } = tab
this.tabActivted = name this.tabActivted = name
this.doRefresh() this.doRefresh()
},
//
doDialogClosed (context) {
this.doRefresh()
} }
} }
} }

View File

@ -156,7 +156,6 @@ export const crudOptions = (vm) => {
title: '是否管理员', title: '是否管理员',
key: 'admin', key: 'admin',
sortable: true, sortable: true,
type: 'radio', type: 'radio',
dict: { dict: {
data: vm.dictionary('button_whether_bool') data: vm.dictionary('button_whether_bool')
@ -164,11 +163,13 @@ export const crudOptions = (vm) => {
form: { form: {
value: false, value: false,
component: { component: {
placeholder: '请选择是否管理员' placeholder: '请选择是否管理员',
show (context) {
return vm.info.is_superuser
}
} }
} }
}, },
{ {
title: '状态', title: '状态',
key: 'status', key: 'status',

View File

@ -64,12 +64,17 @@ import * as api from './api'
import { crudOptions } from './crud' import { crudOptions } from './crud'
import { d2CrudPlus } from 'd2-crud-plus' import { d2CrudPlus } from 'd2-crud-plus'
import rolePermission from '../rolePermission' import rolePermission from '../rolePermission'
import { mapState } from 'vuex'
export default { export default {
name: 'role', name: 'role',
mixins: [d2CrudPlus.crud], mixins: [d2CrudPlus.crud],
components: { components: {
rolePermission rolePermission
}, },
computed: {
...mapState('d2admin/user', ['info'])
},
data () { data () {
return { return {
rolePermissionShow: false, rolePermissionShow: false,

View File

@ -47,11 +47,38 @@ export function DelObj (id) {
// 通过角色id,获取菜单数据 // 通过角色id,获取菜单数据
export function GetMenuData (obj) { export function GetMenuData (obj) {
return request({ return request({
url: '/api/system/role/' + obj.id + '/roleId_get_menu/', url: '/api/system/role/role_get_menu/',
method: 'get', method: 'get',
params: {} params: {}
}).then(res => { }).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: {}
}) })
} }

View File

@ -119,7 +119,6 @@
<script> <script>
import * as api from './api' import * as api from './api'
import * as deptApi from '../dept/api'
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
export default { export default {
@ -202,6 +201,7 @@ export default {
this.menuCheckedKeys = this.roleObj.menu // this.menuCheckedKeys = this.roleObj.menu //
this.menuCheckStrictly = true // this.menuCheckStrictly = true //
this.deptCheckedKeys = this.roleObj.dept this.deptCheckedKeys = this.roleObj.dept
this.GetDataScope()
}, },
addRequest (row) { addRequest (row) {
return api.createObj(row) return api.createObj(row)
@ -214,7 +214,7 @@ export default {
}, },
// //
getDeptData () { getDeptData () {
deptApi.GetListAll().then(ret => { api.GetDataScopeDept().then(ret => {
this.deptOptions = XEUtils.toArrayTree(ret.data, { parentKey: 'parent', strict: false }) this.deptOptions = XEUtils.toArrayTree(ret.data, { parentKey: 'parent', strict: false })
}) })
}, },
@ -238,6 +238,12 @@ export default {
}) })
}) })
}, },
//
GetDataScope () {
api.GetDataScope().then(res => {
this.dataScopeOptions = res.data
})
},
// //
getMenuAllCheckedKeys () { getMenuAllCheckedKeys () {
// //

View File

@ -32,7 +32,7 @@
><i class="el-icon-download" /> 导出 ><i class="el-icon-download" /> 导出
</el-button> </el-button>
<importExcel <importExcel
importApi="api/system/user/import/" api="api/system/user/"
v-permission="'Import'" v-permission="'Import'"
>导入 >导入
</importExcel> </importExcel>
@ -198,6 +198,18 @@ export default {
that.$message.error('表单校验失败,请检查') 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)
})
} }
} }
} }