Merge branch 'v2.x' of https://gitee.com/liqianglog/django-vue-admin into main
# Conflicts: # web/src/api/service.jspull/86/head v2.0.8
commit
a9d6612e9d
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = "部门表"
|
||||||
|
|
|
@ -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="获取成功")
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
导出功能
|
导出功能
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = '-'
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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">提示:仅允许导入“xls”或“xlsx”格式文件!</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">提示:仅允许导入“xls”或“xlsx”格式文件!</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 => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () {
|
||||||
// 目前被选中的菜单节点
|
// 目前被选中的菜单节点
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue