!79 正式发布2.0.7版本
1. 功能:websocket重构 2. 修复:用户管理中部门下拉无数据问题 3. 修复:filter_fields使用icontains导致条件查询失效 4. 修复:文件上传显示问题 5. 优化:软删除重构 6. 优化:菜单管理改为懒加载pull/80/MERGE v2.0.7
commit
7ea806603f
|
@ -8,20 +8,21 @@ https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
|||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||
|
||||
from application.websocketConfig import websocket_application
|
||||
from application.routing import websocket_urlpatterns
|
||||
|
||||
http_application = get_asgi_application()
|
||||
|
||||
async def application(scope,receive,send):
|
||||
if scope['type'] == 'http':
|
||||
await http_application(scope, receive, send)
|
||||
elif scope['type'] == 'websocket':
|
||||
await websocket_application(scope, receive, send)
|
||||
else:
|
||||
raise Exception("未知的scope类型,"+ scope['type'])
|
||||
application = ProtocolTypeRouter({
|
||||
"http":http_application,
|
||||
'websocket': AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
websocket_urlpatterns #指明路由文件是devops/routing.py
|
||||
)
|
||||
),
|
||||
})
|
|
@ -1,13 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
from dvadmin.system import routing as dvadminRouting
|
||||
from django.urls import path
|
||||
from application.websocketConfig import MegCenter
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/<str:service_uid>/', MegCenter.as_asgi()), #consumers.DvadminWebSocket 是该路由的消费者
|
||||
]
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
'websocket': AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
dvadminRouting.websocket_urlpatterns# 指明路由文件是devops/routing.py
|
||||
)
|
||||
),
|
||||
})
|
|
@ -93,6 +93,7 @@ TEMPLATES = [
|
|||
|
||||
WSGI_APPLICATION = "application.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
|
||||
|
@ -168,15 +169,21 @@ CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持
|
|||
# ================================================= #
|
||||
# ********************* channels配置 ******************* #
|
||||
# ================================================= #
|
||||
ASGI_APPLICATION = 'application.routing.application'
|
||||
ASGI_APPLICATION = 'application.asgi.application'
|
||||
CHANNEL_LAYERS = {
|
||||
'default': {
|
||||
'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
'CONFIG': {
|
||||
"hosts": [('127.0.0.1', 6379)], #需修改
|
||||
},
|
||||
},
|
||||
"default": {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
# CHANNEL_LAYERS = {
|
||||
# 'default': {
|
||||
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
# 'CONFIG': {
|
||||
# "hosts": [('127.0.0.1', 6379)], #需修改
|
||||
# },
|
||||
# },
|
||||
# }
|
||||
|
||||
|
||||
# ================================================= #
|
||||
# ********************* 日志配置 ******************* #
|
||||
|
|
|
@ -1,15 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
import json
|
||||
import urllib
|
||||
|
||||
#处理websocket传参
|
||||
from asgiref.sync import sync_to_async, async_to_sync
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer, AsyncWebsocketConsumer
|
||||
import json
|
||||
|
||||
from channels.layers import get_channel_layer
|
||||
from jwt import InvalidSignatureError
|
||||
|
||||
from application import settings
|
||||
from dvadmin.system.models import MessageCenter
|
||||
|
||||
send_dict = {}
|
||||
|
||||
# 发送消息结构体
|
||||
def set_message(sender, msg_type, msg):
|
||||
text = {
|
||||
'sender': sender,
|
||||
'contentType': msg_type,
|
||||
'content': msg,
|
||||
}
|
||||
return text
|
||||
|
||||
#异步获取消息中心的目标用户
|
||||
@database_sync_to_async
|
||||
def _get_message_center_instance(message_id):
|
||||
from dvadmin.system.models import MessageCenter
|
||||
_MessageCenter = MessageCenter.objects.filter(id=message_id).values_list('target_user',flat=True)
|
||||
if _MessageCenter:
|
||||
return _MessageCenter
|
||||
else:
|
||||
return []
|
||||
|
||||
@database_sync_to_async
|
||||
def _get_message_unread(user_id):
|
||||
from dvadmin.system.models import MessageCenterTargetUser
|
||||
count = MessageCenterTargetUser.objects.filter(users=user_id,is_read=False).count()
|
||||
return count or 0
|
||||
|
||||
|
||||
def request_data(scope):
|
||||
|
@ -17,98 +44,69 @@ def request_data(scope):
|
|||
qs = urllib.parse.parse_qs(query_string)
|
||||
return qs
|
||||
|
||||
|
||||
# 全部的websocket sender
|
||||
CONNECTIONS = {}
|
||||
|
||||
|
||||
# 判断用户是否已经连接
|
||||
def check_connection(key):
|
||||
return key in CONNECTIONS
|
||||
|
||||
|
||||
# 发送消息结构体
|
||||
def message(sender, msg_type, msg):
|
||||
text = json.dumps({
|
||||
'sender': sender,
|
||||
'contentType': msg_type,
|
||||
'content': msg,
|
||||
})
|
||||
return {
|
||||
'type': 'websocket.send',
|
||||
'text': text
|
||||
}
|
||||
|
||||
|
||||
async def websocket_application(scope, receive, send):
|
||||
while True:
|
||||
event = await receive()
|
||||
# print('[event] ', event)
|
||||
qs = request_data(scope)
|
||||
print(1,qs)
|
||||
auth = qs.get('auth', [''])[0]
|
||||
user_id = None
|
||||
# 收到建立WebSocket连接的消息
|
||||
if event['type'] == 'websocket.connect':
|
||||
# 昵称验证
|
||||
if not auth:
|
||||
break
|
||||
else:
|
||||
class DvadminWebSocket(AsyncJsonWebsocketConsumer):
|
||||
async def connect(self):
|
||||
try:
|
||||
import jwt
|
||||
decoded_result = jwt.decode(auth, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
self.service_uid = self.scope["url_route"]["kwargs"]["service_uid"]
|
||||
decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
if decoded_result:
|
||||
user_id = decoded_result.get('user_id')
|
||||
# 记录
|
||||
CONNECTIONS[user_id] = send
|
||||
self.user_id = decoded_result.get('user_id')
|
||||
self.chat_group_name = "user_"+str(self.user_id)
|
||||
#收到连接时候处理,
|
||||
await self.channel_layer.group_add(
|
||||
self.chat_group_name,
|
||||
self.channel_name
|
||||
)
|
||||
await self.accept()
|
||||
# 发送连接成功
|
||||
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
|
||||
# 主动推送消息
|
||||
unread_count = await _get_message_unread(self.user_id)
|
||||
await self.send_json(set_message('system', 'TEXT', {"model":'message_center',"unread":unread_count}))
|
||||
except InvalidSignatureError:
|
||||
break
|
||||
if auth in CONNECTIONS:
|
||||
break
|
||||
|
||||
await send({'type': 'websocket.accept'})
|
||||
await send(message('system', 'INFO', '连接成功'))
|
||||
# # 发送好友列表
|
||||
# friends_list = list(CONNECTIONS.keys())
|
||||
# await send(message('system', 'INFO', friends_list))
|
||||
#
|
||||
# # 向其他人群发消息, 有人登录了
|
||||
# for other in CONNECTIONS.values():
|
||||
# await other(message('system', 'addFriend', auth))
|
||||
await self.disconnect(None)
|
||||
|
||||
|
||||
# 收到中断WebSocket连接的消息
|
||||
elif event['type'] == 'websocket.disconnect':
|
||||
# 移除记录
|
||||
if user_id in CONNECTIONS:
|
||||
CONNECTIONS.pop(user_id)
|
||||
async def disconnect(self, close_code):
|
||||
# Leave room group
|
||||
await self.channel_layer.group_discard(self.chat_group_name, self.channel_name)
|
||||
print("连接关闭")
|
||||
await self.close(close_code)
|
||||
|
||||
# # 向其他人群发消息, 有人离线了
|
||||
# for other in CONNECTIONS.values():
|
||||
# await other(message('system', 'removeFriend', user_id))
|
||||
|
||||
# 其他情况,正常的WebSocket消息
|
||||
elif event['type'] == 'websocket.receive':
|
||||
print(11,event)
|
||||
if event['text'] == 'ping':
|
||||
await send(message('system', 'text', 'pong!'))
|
||||
else:
|
||||
receive_msg = json.loads(event['text'])
|
||||
message_id = receive_msg.get('message_id', None)
|
||||
_MessageCenter = MessageCenter.objects.filter(id=message_id).first()
|
||||
if _MessageCenter:
|
||||
user_list = _MessageCenter.target_user.values_list('id',flat=True)
|
||||
class MegCenter(DvadminWebSocket):
|
||||
"""
|
||||
消息中心
|
||||
"""
|
||||
|
||||
async def receive(self, text_data):
|
||||
# 接受客户端的信息,你处理的函数
|
||||
text_data_json = json.loads(text_data)
|
||||
message_id = text_data_json.get('message_id', None)
|
||||
user_list = await _get_message_center_instance(message_id)
|
||||
for send_user in user_list:
|
||||
if send_user in CONNECTIONS:
|
||||
content_type = receive_msg.get('contentType', 'TEXT')
|
||||
content = receive_msg.get('content', '')
|
||||
msg = message(user_id, content_type, content)
|
||||
await CONNECTIONS[send_user](msg)
|
||||
else:
|
||||
msg = message('system', 'text', '对方已下线或不存在')
|
||||
await send(msg)
|
||||
else:
|
||||
print('a1a1a1')
|
||||
pass
|
||||
await self.channel_layer.group_send(
|
||||
"user_" + str(send_user),
|
||||
{'type': 'push.message', 'json': text_data_json}
|
||||
)
|
||||
|
||||
print('[disconnect]')
|
||||
async def push_message(self, event):
|
||||
message = event['json']
|
||||
await self.send(text_data=json.dumps(message))
|
||||
|
||||
|
||||
def websocket_push(user_id, message):
|
||||
"""
|
||||
主动推送消息
|
||||
"""
|
||||
username = "user_"+str(user_id)
|
||||
print(103,message)
|
||||
channel_layer = get_channel_layer()
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
username,
|
||||
{
|
||||
"type": "push.message",
|
||||
"json": message
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/bin/bash
|
||||
# python manage.py makemigrations
|
||||
# python manage.py migrate
|
||||
# python manage.py init -y
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import urllib
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer, AsyncWebsocketConsumer
|
||||
import json
|
||||
|
||||
from jwt import InvalidSignatureError
|
||||
|
||||
from application import settings
|
||||
from dvadmin.system.models import MessageCenter
|
||||
|
||||
send_dict = {}
|
||||
|
||||
# 发送消息结构体
|
||||
def message(sender, msg_type, msg):
|
||||
text = {
|
||||
'sender': sender,
|
||||
'contentType': msg_type,
|
||||
'content': msg,
|
||||
}
|
||||
return text
|
||||
|
||||
#异步获取消息中心的目标用户
|
||||
@database_sync_to_async
|
||||
def _get_message_center_instance(message_id):
|
||||
_MessageCenter = MessageCenter.objects.filter(id=message_id).values_list('target_user',flat=True)
|
||||
if _MessageCenter:
|
||||
return _MessageCenter
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def request_data(scope):
|
||||
query_string = scope.get('query_string', b'').decode('utf-8')
|
||||
qs = urllib.parse.parse_qs(query_string)
|
||||
return qs
|
||||
|
||||
class DvadminWebSocket(AsyncJsonWebsocketConsumer):
|
||||
async def connect(self):
|
||||
try:
|
||||
import jwt
|
||||
self.service_uid = self.scope["url_route"]["kwargs"]["service_uid"]
|
||||
params = request_data(self.scope)
|
||||
room = params.get('room')[0]
|
||||
decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
if decoded_result:
|
||||
self.user_id = decoded_result.get('user_id')
|
||||
self.chat_group_name = room
|
||||
#收到连接时候处理,
|
||||
await self.channel_layer.group_add(
|
||||
self.chat_group_name,
|
||||
self.channel_name
|
||||
)
|
||||
# 将该客户端的信息发送函数与客户端的唯一身份标识绑定,保存至自定义的字典中
|
||||
if len(send_dict)==0:
|
||||
send_dict.setdefault(self.chat_group_name, {})
|
||||
for room in send_dict.keys():
|
||||
if room == self.chat_group_name:
|
||||
send_dict[self.chat_group_name][self.user_id] = self.send
|
||||
else:
|
||||
send_dict.setdefault(self.chat_group_name,{})
|
||||
await self.accept()
|
||||
await self.send_json(message('system', 'INFO', '连接成功'))
|
||||
except InvalidSignatureError:
|
||||
await self.disconnect(None)
|
||||
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
# 删除 send_dict 中对应的信息
|
||||
del send_dict[self.chat_group_name][self.user_id]
|
||||
# Leave room group
|
||||
await self.channel_layer.group_discard(self.chat_group_name, self.channel_name)
|
||||
print("连接关闭")
|
||||
await self.close(close_code)
|
||||
|
||||
async def receive(self, text_data=None, byte_text_data=None):
|
||||
print(text_data)
|
||||
try:
|
||||
text_data_json = json.loads(text_data)
|
||||
except Exception as e:
|
||||
print('数据无法被json格式化', e)
|
||||
await self.disconnect(400)
|
||||
else:
|
||||
print(123,text_data_json)
|
||||
# 获取将要推送信息的目标身份标识,调用保存在 send_dict中的信息发送函数
|
||||
message_id = text_data_json.get('message_id', None)
|
||||
user_list = await _get_message_center_instance(message_id)
|
||||
for send_user in user_list:
|
||||
await send_dict[self.chat_group_name][send_user](text_data=json.dumps(text_data_json))
|
|
@ -387,7 +387,7 @@ class SystemConfig(CoreModel):
|
|||
|
||||
|
||||
class LoginLog(CoreModel):
|
||||
LOGIN_TYPE_CHOICES = ((1, "普通登录"),)
|
||||
LOGIN_TYPE_CHOICES = ((1, "普通登录"), (2, "微信扫码登录"),)
|
||||
username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名")
|
||||
ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip")
|
||||
agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息")
|
||||
|
@ -417,15 +417,24 @@ class MessageCenter(CoreModel):
|
|||
title = models.CharField(max_length=100,verbose_name="标题",help_text="标题")
|
||||
content = models.TextField(verbose_name="内容",help_text="内容")
|
||||
target_type=models.IntegerField(default=0,verbose_name="目标类型",help_text="目标类型")
|
||||
target_user = models.ManyToManyField(to=Users,related_name="target_user",blank=True,db_constraint=False,verbose_name="目标用户",help_text="目标用户")
|
||||
target_user = models.ManyToManyField(to=Users,related_name='user',through='MessageCenterTargetUser', through_fields=('messagecenter','users'),blank=True,verbose_name="目标用户",help_text="目标用户")
|
||||
target_dept = models.ManyToManyField(to=Dept, blank=True, db_constraint=False,
|
||||
verbose_name="目标部门", help_text="目标部门")
|
||||
target_role = models.ManyToManyField(to=Role, blank=True, db_constraint=False,
|
||||
verbose_name="目标角色", help_text="目标角色")
|
||||
is_read=models.BooleanField(default=False,blank=True,verbose_name="是否已读",help_text="是否已读")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "message_center"
|
||||
verbose_name = "消息中心"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
class MessageCenterTargetUser(CoreModel):
|
||||
users = models.ForeignKey(Users,related_name="target_user", on_delete=models.CASCADE,db_constraint=False,verbose_name="关联用户表",help_text="关联用户表")
|
||||
messagecenter = models.ForeignKey(MessageCenter, on_delete=models.CASCADE,db_constraint=False,verbose_name="关联消息中心表",help_text="关联消息中心表")
|
||||
is_read = models.BooleanField(default=False,blank=True,null=True,verbose_name="是否已读",help_text="是否已读")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "message_center_target_user"
|
||||
verbose_name = "消息中心目标用户表"
|
||||
verbose_name_plural = verbose_name
|
|
@ -1,7 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.urls import path
|
||||
from . import consumers
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/<str:service_uid>/', consumers.DvadminWebSocket.as_asgi()), #consumers.DvadminWebSocket 是该路由的消费者
|
||||
]
|
|
@ -37,6 +37,17 @@ class DeptSerializer(CustomModelSerializer):
|
|||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class DeptImportSerializer(CustomModelSerializer):
|
||||
"""
|
||||
部门-导入-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
fields = '__all__'
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class DeptInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
|
@ -113,8 +124,12 @@ class DeptViewSet(CustomModelViewSet):
|
|||
update_serializer_class = DeptCreateUpdateSerializer
|
||||
filter_fields = ['name', 'id', 'parent']
|
||||
search_fields = []
|
||||
|
||||
# extra_filter_backends = []
|
||||
import_serializer_class = DeptImportSerializer
|
||||
import_field_dict = {
|
||||
"name": "部门名称",
|
||||
"key": "部门标识",
|
||||
}
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
# 如果懒加载,则只返回父级
|
||||
|
|
|
@ -21,6 +21,7 @@ class MenuSerializer(CustomModelSerializer):
|
|||
菜单表的简单序列化器
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField(read_only=True)
|
||||
hasChild = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
queryset = instance.menuPermission.order_by('-name').values_list('name', flat=True)
|
||||
|
@ -29,6 +30,12 @@ class MenuSerializer(CustomModelSerializer):
|
|||
else:
|
||||
return None
|
||||
|
||||
def get_hasChild(self, instance):
|
||||
hasChild = Menu.objects.filter(parent=instance.id)
|
||||
if hasChild:
|
||||
return True
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = "__all__"
|
||||
|
@ -171,3 +178,21 @@ class MenuViewSet(CustomModelViewSet):
|
|||
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data, total=len(data), msg="获取成功")
|
||||
|
||||
def list(self,request):
|
||||
"""
|
||||
懒加载
|
||||
"""
|
||||
params = request.query_params
|
||||
parent = params.get('parent', None)
|
||||
if params:
|
||||
if parent:
|
||||
queryset = self.queryset.filter(status=1, parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter(status=1)
|
||||
else:
|
||||
queryset = self.queryset.filter(status=1, parent__isnull=True)
|
||||
queryset = self.filter_queryset(queryset)
|
||||
serializer = MenuSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data)
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from itertools import chain
|
||||
|
||||
import json
|
||||
from django_restql.fields import DynamicSerializerMethodField
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
|
||||
from dvadmin.system.models import MessageCenter, Users
|
||||
from application.websocketConfig import websocket_push
|
||||
from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser
|
||||
from dvadmin.system.views.dept import DeptSerializer
|
||||
from dvadmin.system.views.role import RoleSerializer
|
||||
from dvadmin.system.views.user import UserSerializer
|
||||
from dvadmin.utils.json_response import SuccessResponse
|
||||
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
@ -19,8 +21,11 @@ class MessageCenterSerializer(CustomModelSerializer):
|
|||
"""
|
||||
role_info = DynamicSerializerMethodField()
|
||||
user_info = DynamicSerializerMethodField()
|
||||
dept_info = DynamicSerializerMethodField()
|
||||
is_read = serializers.BooleanField(read_only=True, source='target_user__is_read')
|
||||
|
||||
def get_role_info(self, instance, parsed_query):
|
||||
roles =instance.target_role.all()
|
||||
roles = instance.target_role.all()
|
||||
# You can do what ever you want in here
|
||||
# `parsed_query` param is passed to BookSerializer to allow further querying
|
||||
serializer = RoleSerializer(
|
||||
|
@ -41,12 +46,53 @@ class MessageCenterSerializer(CustomModelSerializer):
|
|||
)
|
||||
return serializer.data
|
||||
|
||||
def get_dept_info(self, instance, parsed_query):
|
||||
dept = instance.target_dept.all()
|
||||
# You can do what ever you want in here
|
||||
# `parsed_query` param is passed to BookSerializer to allow further querying
|
||||
serializer = DeptSerializer(
|
||||
dept,
|
||||
many=True,
|
||||
parsed_query=parsed_query
|
||||
)
|
||||
return serializer.data
|
||||
|
||||
class Meta:
|
||||
model = MessageCenter
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MessageCenterTargetUserSerializer(CustomModelSerializer):
|
||||
"""
|
||||
目标用户序列化器-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MessageCenterTargetUser
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
||||
"""
|
||||
目标用户序列化器-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MessageCenterTargetUser
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
data['title'] = instance.messagecenter.title
|
||||
data['content'] = instance.messagecenter.content
|
||||
data['target_type'] = instance.messagecenter.target_type
|
||||
data['id'] = instance.messagecenter.id
|
||||
return data
|
||||
|
||||
|
||||
class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
消息中心-新增-序列化器
|
||||
|
@ -57,15 +103,28 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
|
|||
initial_data = self.initial_data
|
||||
target_type = initial_data.get('target_type')
|
||||
# 在保存之前,根据目标类型,把目标用户查询出来并保存
|
||||
users = initial_data.get('target_user',[])
|
||||
if target_type in [1]:
|
||||
users = initial_data.get('target_user', [])
|
||||
if target_type in [1]: # 按角色
|
||||
target_role = initial_data.get('target_role')
|
||||
users = Users.objects.exclude(is_deleted=True).filter(role__id__in=target_role).values_list('id',flat=True)
|
||||
if target_type in [2]:
|
||||
users = Users.objects.exclude(is_deleted=True).filter(role__id__in=target_role).values_list('id', flat=True)
|
||||
if target_type in [2]: # 按部门
|
||||
target_dept = initial_data.get('target_dept')
|
||||
users = Users.objects.exclude(is_deleted=True).filter(dept__id__in=target_dept).values_list('id',flat=True)
|
||||
data.save()
|
||||
data.target_user.set(users)
|
||||
users = Users.objects.exclude(is_deleted=True).filter(dept__id__in=target_dept).values_list('id', flat=True)
|
||||
if target_type in [3]: # 系统通知
|
||||
users = Users.objects.exclude(is_deleted=True).values_list('id', flat=True)
|
||||
targetuser_data = []
|
||||
for user in users:
|
||||
targetuser_data.append({
|
||||
"messagecenter": data.id,
|
||||
"users": user
|
||||
})
|
||||
targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=self.request)
|
||||
targetuser_instance.is_valid(raise_exception=True)
|
||||
targetuser_instance.save()
|
||||
for user in users:
|
||||
unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count()
|
||||
websocket_push(user, {"sender": 'system', "contentType": 'TEXT',
|
||||
"content": {"model": 'message_center', "unread": unread_count}})
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
|
@ -74,8 +133,6 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
|
|||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
|
||||
class MessageCenterViewSet(CustomModelViewSet):
|
||||
"""
|
||||
消息中心接口
|
||||
|
@ -85,21 +142,58 @@ class MessageCenterViewSet(CustomModelViewSet):
|
|||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = MessageCenter.objects.all()
|
||||
queryset = MessageCenter.objects.order_by('create_datetime')
|
||||
serializer_class = MessageCenterSerializer
|
||||
create_serializer_class = MessageCenterCreateSerializer
|
||||
extra_filter_backends = []
|
||||
|
||||
@action(methods=['GET'],detail=False,permission_classes=[IsAuthenticated])
|
||||
def get_self_receive(self,request):
|
||||
def get_queryset(self):
|
||||
if self.action == 'list':
|
||||
return MessageCenter.objects.filter(creator=self.request.user.id).all()
|
||||
return MessageCenter.objects.all()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""
|
||||
重写查看
|
||||
"""
|
||||
pk = kwargs.get('pk')
|
||||
user_id = self.request.user.id
|
||||
queryset = MessageCenterTargetUser.objects.filter(users__id=user_id, messagecenter__id=pk).first()
|
||||
if queryset:
|
||||
queryset.is_read = True
|
||||
queryset.save()
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
# 主动推送消息
|
||||
unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count()
|
||||
websocket_push(user_id, {"sender": 'system', "contentType": 'TEXT',
|
||||
"content": {"model": 'message_center', "unread": unread_count}})
|
||||
return DetailResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def get_self_receive(self, request):
|
||||
"""
|
||||
获取接收到的消息
|
||||
"""
|
||||
self_user_id = self.request.user.id
|
||||
queryset = MessageCenter.objects.filter(target_user__id=self_user_id)
|
||||
queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('-create_datetime')
|
||||
# queryset = self.filter_queryset(queryset)
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True, request=request)
|
||||
serializer = MessageCenterTargetUserListSerializer(page, many=True, request=request)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
serializer = MessageCenterTargetUserListSerializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def get_newest_msg(self, request):
|
||||
"""
|
||||
获取最新的一条消息
|
||||
"""
|
||||
self_user_id = self.request.user.id
|
||||
queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('create_datetime').last()
|
||||
data = None
|
||||
if queryset:
|
||||
serializer = MessageCenterTargetUserListSerializer(queryset, many=False, request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data, msg="获取成功")
|
||||
|
|
|
@ -108,10 +108,8 @@ class SystemConfigChinldernSerializer(CustomModelSerializer):
|
|||
|
||||
def get_chinldern(self, instance):
|
||||
queryset = SystemConfig.objects.filter(parent=instance)
|
||||
if queryset:
|
||||
serializer = SystemConfigSerializer(queryset, many=True)
|
||||
return serializer.data
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
|
|
|
@ -231,7 +231,8 @@ class UserViewSet(CustomModelViewSet):
|
|||
update_serializer_class = UserUpdateSerializer
|
||||
# filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"]
|
||||
filter_fields = {
|
||||
"name": ["icontains"],
|
||||
"name": ["exact"],
|
||||
"mobile": ["exact"],
|
||||
"username": ["exact"],
|
||||
"gender": ["icontains"],
|
||||
"is_active": ["icontains"],
|
||||
|
@ -288,7 +289,7 @@ class UserViewSet(CustomModelViewSet):
|
|||
"gender": user.gender,
|
||||
"email": user.email,
|
||||
"avatar": user.avatar,
|
||||
"dept": user.dept.id,
|
||||
"dept": user.dept_id,
|
||||
"is_superuser": user.is_superuser,
|
||||
"role": user.role.values_list('id', flat=True),
|
||||
}
|
||||
|
|
|
@ -292,11 +292,6 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
|||
)
|
||||
else:
|
||||
orm_lookups.append(search_field)
|
||||
orm_lookups = (
|
||||
orm_lookups
|
||||
if isinstance(filterset.__class__._meta.fields, (list, tuple))
|
||||
else filterset.filters.keys()
|
||||
)
|
||||
conditions = []
|
||||
queries = []
|
||||
for search_term_key in filterset.data.keys():
|
||||
|
|
|
@ -7,6 +7,7 @@ from openpyxl import Workbook
|
|||
from openpyxl.worksheet.datavalidation import DataValidation
|
||||
from openpyxl.utils import get_column_letter, quote_sheetname
|
||||
from openpyxl.worksheet.table import Table, TableStyleInfo
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.request import Request
|
||||
|
||||
from dvadmin.utils.import_export import import_to_data
|
||||
|
@ -56,6 +57,7 @@ class ImportSerializerMixin:
|
|||
length += 2.1 if ord(char) > 256 else 1
|
||||
return round(length, 1) if length <= self.export_column_width else self.export_column_width
|
||||
|
||||
@action(methods=['get','post'],detail=False)
|
||||
@transaction.atomic # Django 事务,防止出错
|
||||
def import_data(self, request: Request, *args, **kwargs):
|
||||
"""
|
||||
|
@ -151,13 +153,13 @@ class ImportSerializerMixin:
|
|||
if queryset.model._meta.unique_together: # 判断是否存在联合主键
|
||||
filter_dic = {i: ele.get(i) for i in list(queryset.model._meta.unique_together[0])}
|
||||
else:
|
||||
filter_dic = {i: ele.get(i) for i in list(set(self.import_field_dict.keys()) & set(unique_list))}
|
||||
filter_dic = {i: ele.get(i) for i in list(set(unique_list)) if ele.get(i) is not None}
|
||||
instance = filter_dic and queryset.filter(**filter_dic).first()
|
||||
if instance and not updateSupport:
|
||||
continue
|
||||
if not filter_dic:
|
||||
instance = None
|
||||
serializer = self.import_serializer_class(instance, data=ele)
|
||||
serializer = self.import_serializer_class(instance, data=ele, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return DetailResponse(msg=f"导入成功!")
|
||||
|
@ -216,7 +218,7 @@ class ExportSerializerMixin:
|
|||
queryset = self.filter_queryset(self.get_queryset())
|
||||
assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__
|
||||
assert self.export_serializer_class, "'%s' 请配置对应的导出序列化器。" % self.__class__.__name__
|
||||
data = self.export_serializer_class(queryset, many=True).data
|
||||
data = self.export_serializer_class(queryset, many=True, request=request).data
|
||||
# 导出excel 表
|
||||
response = HttpResponse(content_type="application/msexcel")
|
||||
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
||||
|
|
|
@ -17,17 +17,8 @@ table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
|||
|
||||
|
||||
class SoftDeleteQuerySet(QuerySet):
|
||||
def delete(self,soft_delete=True):
|
||||
"""
|
||||
重写删除方法
|
||||
当soft_delete为True时表示软删除,则修改删除时间为当前时间,否则直接删除
|
||||
:param soft: Boolean 是否软删除,默认是
|
||||
:return: Tuple eg.(3, {'lqModel.Test': 3})
|
||||
"""
|
||||
if soft_delete:
|
||||
return self.update(is_deleted=True)
|
||||
else:
|
||||
return super(SoftDeleteQuerySet, self).delete()
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -53,6 +44,27 @@ class SoftDeleteManager(models.Manager):
|
|||
return SoftDeleteQuerySet(self.model).get(username=name)
|
||||
|
||||
|
||||
class SoftDeleteModel(models.Model):
|
||||
"""
|
||||
软删除模型
|
||||
一旦继承,就将开启软删除
|
||||
"""
|
||||
is_deleted = models.BooleanField(verbose_name="是否软删除", help_text='是否软删除', default=False, db_index=True)
|
||||
objects = SoftDeleteManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = '软删除模型'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def delete(self, using=None, soft_delete=True, *args, **kwargs):
|
||||
"""
|
||||
重写删除方法,直接开启软删除
|
||||
"""
|
||||
self.is_deleted = True
|
||||
self.save(using=using)
|
||||
|
||||
|
||||
class CoreModel(models.Model):
|
||||
"""
|
||||
核心标准抽象模型模型,可直接继承使用
|
||||
|
@ -67,25 +79,12 @@ class CoreModel(models.Model):
|
|||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
|
||||
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||
verbose_name="创建时间")
|
||||
is_deleted = models.BooleanField(verbose_name="是否软删除",help_text='是否软删除', default=False, db_index=True)
|
||||
objects = SoftDeleteManager()
|
||||
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = '核心模型'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def delete(self, using=None, soft_delete=True, *args, **kwargs):
|
||||
"""
|
||||
Soft delete object (set its ``is_deleted`` field to True).
|
||||
Actually delete object if setting ``soft`` to False.
|
||||
"""
|
||||
if soft_delete:
|
||||
self.is_deleted = True
|
||||
self.save(using=using)
|
||||
else:
|
||||
return super(CoreModel, self).delete(using=using, *args, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ def get_verbose_name(queryset=None, view=None, model=None):
|
|||
:return:
|
||||
"""
|
||||
try:
|
||||
if queryset and hasattr(queryset, 'model'):
|
||||
if queryset is not None and hasattr(queryset, 'model'):
|
||||
model = queryset.model
|
||||
elif view and hasattr(view.get_queryset(), 'model'):
|
||||
model = view.get_queryset().model
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"""
|
||||
import uuid
|
||||
|
||||
from django.db import transaction
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
|
@ -50,6 +51,7 @@ class CustomModelViewSet(ModelViewSet,ImportSerializerMixin,ExportSerializerMixi
|
|||
return self.values_queryset
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
def get_serializer_class(self):
|
||||
action_serializer_name = f"{self.action}_serializer_class"
|
||||
action_serializer_class = getattr(self, action_serializer_name, None)
|
||||
|
@ -57,6 +59,16 @@ class CustomModelViewSet(ModelViewSet,ImportSerializerMixin,ExportSerializerMixi
|
|||
return action_serializer_class
|
||||
return super().get_serializer_class()
|
||||
|
||||
# 通过many=True直接改造原有的API,使其可以批量创建
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
serializer_class = self.get_serializer_class()
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
if isinstance(self.request.data, list):
|
||||
with transaction.atomic():
|
||||
return serializer_class(many=True, *args, **kwargs)
|
||||
else:
|
||||
return serializer_class(*args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
@ -92,13 +104,7 @@ class CustomModelViewSet(ModelViewSet,ImportSerializerMixin,ExportSerializerMixi
|
|||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
request_data = request.data
|
||||
soft_delete = request_data.get('soft_delete',True)
|
||||
if soft_delete:
|
||||
instance.is_deleted = True
|
||||
instance.save()
|
||||
else:
|
||||
self.perform_destroy(instance)
|
||||
instance.delete()
|
||||
return DetailResponse(data=[], msg="删除成功")
|
||||
|
||||
|
||||
|
|
|
@ -91,6 +91,27 @@ services:
|
|||
# network:
|
||||
# ipv4_address: 177.8.0.14
|
||||
|
||||
|
||||
# dvadmin-redis:
|
||||
# image: redis:6.2.6-alpine # 指定服务镜像,最好是与之前下载的redis配置文件保持一致
|
||||
# container_name: dvadmin-redis # 容器名称
|
||||
# restart: on-failure # 重启方式
|
||||
# environment:
|
||||
# - TZ=Asia/Shanghai # 设置时区
|
||||
# volumes: # 配置数据卷
|
||||
# - ./docker_env/redis/data:/data
|
||||
# - ./docker_env/redis/redis.conf:/etc/redis/redis.conf
|
||||
# ports: # 映射端口
|
||||
# - "6379:6379"
|
||||
# sysctls: # 设置容器中的内核参数
|
||||
# - net.core.somaxconn=1024
|
||||
# command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes" # 指定配置文件并开启持久化
|
||||
# privileged: true # 使用该参数,container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限
|
||||
# networks:
|
||||
# network:
|
||||
# ipv4_address: 177.8.0.15
|
||||
|
||||
|
||||
networks:
|
||||
network:
|
||||
ipam:
|
||||
|
|
|
@ -3,4 +3,4 @@ WORKDIR /backend
|
|||
COPY ./backend/ .
|
||||
RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }'
|
||||
RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt
|
||||
CMD ["daphne","-b","0.0.0.0","-p","8000","application.asgi:application"]
|
||||
CMD ["/backend/docker_start.sh"]
|
||||
|
|
|
@ -14,11 +14,18 @@ server {
|
|||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Nginx-Proxy true;
|
||||
set_real_ip_from 0.0.0.0/0;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_connect_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
real_ip_header X-Forwarded-For;
|
||||
rewrite ^/api/(.*)$ /$1 break; #重写
|
||||
proxy_pass http://177.8.0.12:8000/; # 设置代理服务器的协议和地址
|
||||
|
|
4
web/.env
4
web/.env
|
@ -1,13 +1,11 @@
|
|||
# 所有环境默认
|
||||
|
||||
# 页面 title 前缀
|
||||
VUE_APP_TITLE=D2Admin
|
||||
VUE_APP_TITLE=DvAdmin
|
||||
|
||||
# 网络请求公用地址
|
||||
VUE_APP_API=/api/
|
||||
|
||||
# websocket地址
|
||||
VUE_APP_WEBSOCKET=""
|
||||
|
||||
# 仓库地址
|
||||
VUE_APP_REPO=https://github.com/d2-projects/d2-admin-start-kit
|
||||
|
|
|
@ -6,5 +6,4 @@ VUE_APP_TITLE=企业级后台管理系统
|
|||
VUE_APP_PM_ENABLED = true
|
||||
# 后端接口地址及端口(域名)
|
||||
VUE_APP_API = "http://127.0.0.1:8000"
|
||||
VUE_APP_WEBSOCKET = "ws://127.0.0.1:8000"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "django-vue-admin",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.7",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --open",
|
||||
"start": "npm run serve",
|
||||
|
@ -41,10 +41,10 @@
|
|||
"screenfull": "^5.0.2",
|
||||
"sortablejs": "^1.10.1",
|
||||
"ua-parser-js": "^0.7.20",
|
||||
"vue": "^2.6.11",
|
||||
"vue": "^2.7.10",
|
||||
"vue-i18n": "^8.15.1",
|
||||
"vue-infinite-scroll": "^2.0.2",
|
||||
"vue-router": "^3.1.3",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-splitpane": "^1.0.6",
|
||||
"vuex": "^3.1.2",
|
||||
"vxe-table": "^3.3.2",
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import ElementUI from 'element-ui'
|
||||
import util from '@/libs/util'
|
||||
import store from '@/store'
|
||||
function initWebSocket (e) {
|
||||
const token = util.cookies.get('token')
|
||||
if (token) {
|
||||
const wsUri = process.env.VUE_APP_WEBSOCKET + '/ws/' + token + '/?room=message_center'
|
||||
const wsUri = util.wsBaseURL() + 'ws/' + token + '/'
|
||||
this.socket = new WebSocket(wsUri)// 这里面的this都指向vue
|
||||
this.socket.onerror = webSocketOnError
|
||||
this.socket.onmessage = webSocketOnMessage
|
||||
|
@ -20,9 +21,15 @@ function webSocketOnError (e) {
|
|||
duration: 3000
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收消息
|
||||
* @param e
|
||||
* @returns {any}
|
||||
*/
|
||||
function webSocketOnMessage (e) {
|
||||
const data = JSON.parse(e.data)
|
||||
if (data.contentType === 'INFO') {
|
||||
if (data.contentType === 'SYSTEM') {
|
||||
ElementUI.Notification({
|
||||
title: 'websocket',
|
||||
message: data.content,
|
||||
|
@ -38,7 +45,7 @@ function webSocketOnMessage (e) {
|
|||
position: 'bottom-right',
|
||||
duration: 0
|
||||
})
|
||||
} else if (data.contentType === 'TEXT') {
|
||||
} else if (data.contentType === 'INFO') {
|
||||
ElementUI.Notification({
|
||||
title: '温馨提示',
|
||||
message: data.content,
|
||||
|
@ -47,16 +54,29 @@ function webSocketOnMessage (e) {
|
|||
duration: 0
|
||||
})
|
||||
} else {
|
||||
console.log(data.content)
|
||||
const { content } = data
|
||||
if (content.model === 'message_center') {
|
||||
const unread = content.unread
|
||||
store.dispatch('d2admin/messagecenter/setUnread', unread)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 关闭websiocket
|
||||
function closeWebsocket () {
|
||||
console.log('连接已关闭...')
|
||||
// close()
|
||||
this.socket.close()
|
||||
ElementUI.Notification({
|
||||
title: 'websocket',
|
||||
message: '连接已关闭...',
|
||||
type: 'danger',
|
||||
position: 'bottom-right',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param message
|
||||
*/
|
||||
function webSocketSend (message) {
|
||||
this.socket.send(JSON.stringify(message))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<el-tag :type="color">{{ currentValue }}</el-tag>
|
||||
<div>
|
||||
<el-tag v-if="currentValue" :type="color">{{ currentValue }}</el-tag>
|
||||
<span v-else></span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 行展示组件进阶版
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<!--
|
||||
* @创建文件时间: 2021-11-09 15:41:29
|
||||
* @Auther: 猿小天
|
||||
* @最后修改人: 猿小天
|
||||
* @最后修改时间: 2021-12-08 14:41:20
|
||||
* 联系Qq:1638245306
|
||||
* @文件介绍:
|
||||
-->
|
||||
# 表格选择框配置说明
|
||||
|
||||
## crud.js
|
||||
```
|
||||
{
|
||||
title: '单选本地',
|
||||
key: 'select1',
|
||||
sortable: true,
|
||||
search: {
|
||||
disabled: true
|
||||
},
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
url: '/api/system/user/',
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'name', // 数据字典中label字段的属性名
|
||||
getData: (url, dict, { form, component }) => {
|
||||
return request({ url: url, params: { page: 1, limit: 1 } }).then(ret => {
|
||||
component._elProps.page = ret.data.page
|
||||
component._elProps.limit = ret.data.limit
|
||||
component._elProps.total = ret.data.total
|
||||
return ret.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
span: 12,
|
||||
props: { multiple: true },
|
||||
elProps: {
|
||||
pagination: true,
|
||||
columns: [
|
||||
{
|
||||
field: "name",
|
||||
title: "名称",
|
||||
},
|
||||
{
|
||||
field: "username",
|
||||
title: "账号",
|
||||
},
|
||||
{
|
||||
field: "role",
|
||||
title: "角色Id",
|
||||
},
|
||||
{
|
||||
field: "dept",
|
||||
title: "部门Id",
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
```
|
||||
详细文档:
|
||||
1.http://d2-crud-plus.docmirror.cn/d2-crud-plus/guide/dict.html
|
||||
2.https://xuliangzhan_admin.gitee.io/vxe-table/#/grid/api
|
||||
```
|
||||
|
||||
| Name | Description | Type | Required | Default |
|
||||
| ---------- | ---------------- | ------- | -------- | -------------- |
|
||||
| type | 字段所使用的组件 | String | true | table-selector |
|
||||
| dict | 字典的配置 | Object | true | {} |
|
||||
| multiple | 是否多选 | Boolean | false | false |
|
||||
| pagination | 是否分页 | Boolean | false | false |
|
||||
| columns | 表格的列配置 | Array | true | [] |
|
||||
| field | 字段 | String | true | '' |
|
||||
| title | 字段名称 | String | true | '' |
|
||||
___
|
||||
|
||||
## events
|
||||
| Name | Description | Params |
|
||||
|------|-------------|--------|
|
||||
| radioChange| 表格行单选的点击事件| row,rowIndex|
|
||||
|
||||
|
||||
```
|
||||
form:{
|
||||
component:{
|
||||
on: { //单选事件监听
|
||||
radioChange({ event, scope }) {
|
||||
scope.form.channel_number = event.row.channel_number
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
export default {
|
||||
// 字段类型配置,注册之后即可在crud.js中使用了
|
||||
'table-list-selector': {
|
||||
// 表单组件配置
|
||||
form: { component: { name: 'table-list-selector-input', props: { color: 'danger' } } },
|
||||
// 行组件配置
|
||||
component: { name: 'values-format', props: {} },
|
||||
// 行展示时居中
|
||||
align: 'center'
|
||||
// 您还可以写更多默认配置
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* @创建文件时间: 2021-08-02 23:56:15
|
||||
* @Auther: 猿小天
|
||||
* @最后修改人: 猿小天
|
||||
* @最后修改时间: 2021-08-09 22:15:56
|
||||
* 联系Qq:1638245306
|
||||
* @文件介绍:
|
||||
*/
|
||||
import { d2CrudPlus } from 'd2-crud-plus'
|
||||
import group from './group'
|
||||
|
||||
function install (Vue, options) {
|
||||
Vue.component('table-list-selector-input', () => import('./table-list-selector'))
|
||||
// Vue.component('d2p-row-format', () => import('./row'))
|
||||
if (d2CrudPlus != null) {
|
||||
// 注册字段类型`demo-extend`
|
||||
d2CrudPlus.util.columnResolve.addTypes(group)
|
||||
}
|
||||
}
|
||||
|
||||
// 导出install, 通过`vue.use(D2pDemoExtend)`安装后 ,`demo-extend` 就可以在`crud.js`中使用了
|
||||
export default {
|
||||
install
|
||||
}
|
|
@ -0,0 +1,653 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-button-group>
|
||||
<el-button size="mini" type="success" round @click="openDialog">添加</el-button>
|
||||
</el-button-group>
|
||||
<el-dialog
|
||||
custom-class="d2p-tree-selector-dialog"
|
||||
:title="dialogTitle"
|
||||
:visible.sync="dialogVisible"
|
||||
width="50%"
|
||||
append-to-body
|
||||
>
|
||||
<div>
|
||||
<div v-if="treeFilter" class="filter-bar" style="padding-bottom: 20px">
|
||||
<el-input
|
||||
prefix-icon="el-icon-search"
|
||||
:placeholder="filterPlaceholder"
|
||||
v-model="filterText"
|
||||
size="small"
|
||||
>
|
||||
</el-input>
|
||||
</div>
|
||||
<vxe-grid
|
||||
v-bind="_elProps"
|
||||
:data="_options"
|
||||
ref="elTree"
|
||||
:auto-resize="true"
|
||||
@radio-change="radioChange"
|
||||
@checkbox-change="checkboxChange"
|
||||
>
|
||||
<template #pager>
|
||||
<vxe-pager
|
||||
v-if="pagination"
|
||||
style="margin-top: 10px"
|
||||
:layouts="[
|
||||
'Sizes',
|
||||
'PrevJump',
|
||||
'PrevPage',
|
||||
'Number',
|
||||
'NextPage',
|
||||
'NextJump',
|
||||
'FullJump',
|
||||
'Total',
|
||||
]"
|
||||
:current-page.sync="_elProps.page"
|
||||
:page-size.sync="_elProps.limit"
|
||||
:total.sync="_elProps.total"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
</vxe-pager>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">{{ cancelText }}</el-button>
|
||||
<el-button type="primary" @click="selectSubmit">{{
|
||||
confirmText
|
||||
}}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
height="280"
|
||||
align="center"
|
||||
size="small">
|
||||
<el-table-column
|
||||
type="index"
|
||||
width="40"
|
||||
label="#">
|
||||
</el-table-column>
|
||||
<template v-for="(item,index) in gridOptions.columns">
|
||||
<el-table-column
|
||||
v-if="item.types && item.types=='img'"
|
||||
:key="index"
|
||||
:label="item.title"
|
||||
width="120">
|
||||
<template slot-scope="scope">
|
||||
<img :src="scope.row.images" style='width: 30px' />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-else-if="item.types && item.types=='dict'"
|
||||
:key="index"
|
||||
:label="item.title"
|
||||
width="120">
|
||||
<template slot-scope="scope">
|
||||
<span v-for="(data,index) in item.dictData" :key="index">
|
||||
<span v-if="data.value===scope.row[item.field]">{{data.label}}</span>
|
||||
<span v-else></span>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-else
|
||||
:key="index"
|
||||
:prop="item.field"
|
||||
:label="item.title"
|
||||
width="120">
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
fixed="right"
|
||||
v-show="colButtons.show"
|
||||
:width="colButtons.width">
|
||||
<template slot-scope="scopes">
|
||||
<el-button style="padding: 0" :disabled="item.disabled?item.disabled({...scopes,tableData}):false" type="text"
|
||||
size="small" :circle="item.circle?item.circle:false" v-for="(item,index) in colButtons.btns"
|
||||
:icon="item.icon?item.icon:''" :key="index" @click="item.click({...scopes,tableData})">
|
||||
{{ item.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import lodash from 'lodash'
|
||||
import { d2CrudPlus } from 'd2-crud-plus'
|
||||
import { request } from '@/api/service'
|
||||
import XEUtils from 'xe-utils'
|
||||
// 表格选择组件
|
||||
export default {
|
||||
name: 'table-list-selector-input',
|
||||
mixins: [d2CrudPlus.input, d2CrudPlus.inputDict],
|
||||
props: {
|
||||
// 值
|
||||
value: {
|
||||
type: [Number, String, Boolean, Array, Object]
|
||||
},
|
||||
// 过滤,value中的nodes过滤方法 参数为nodes
|
||||
filter: {
|
||||
type: Function,
|
||||
require: false
|
||||
},
|
||||
// 过滤的placeholder
|
||||
filterPlaceholder: {
|
||||
type: String,
|
||||
default: '输入关键字进行过滤'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
dialogTitle: {
|
||||
type: String,
|
||||
default: '选择'
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
// 树形组件节点过滤,可以配置elProps.filterNodeMethod ,覆盖默认的过滤方法
|
||||
treeFilter: {
|
||||
type: Boolean,
|
||||
require: false,
|
||||
default: true
|
||||
},
|
||||
// 是否多选,传入false为单选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否忽略选中节点的子节点
|
||||
ignoreFullCheckedChildren: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否只返回叶子节点
|
||||
leafOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否包含半选节点
|
||||
includeHalfChecked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹框表的配置
|
||||
elProps: {
|
||||
type: Object
|
||||
},
|
||||
// 显示表的操作按钮配置
|
||||
colButtons: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
width: 150,
|
||||
show: true,
|
||||
btn: []
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 是否可以清除
|
||||
*/
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 数据字典配置
|
||||
dict: {
|
||||
type: Object,
|
||||
require: false
|
||||
},
|
||||
// 是否开启分页
|
||||
pagination: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
currentValue: undefined,
|
||||
collapseTags: false,
|
||||
selected: [],
|
||||
dialogVisible: false,
|
||||
filterText: undefined,
|
||||
requestUrl: null,
|
||||
gridOptions: undefined,
|
||||
tableData: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// if (this.dict) {
|
||||
// this.dict = d2CrudPlus.util.dict.mergeDefault(this.dict, true)
|
||||
// }
|
||||
// this.initData()
|
||||
},
|
||||
computed: {
|
||||
_elProps () {
|
||||
const defaultElProps = {
|
||||
// showCheckbox: this.multiple,
|
||||
highlightCurrent: !this.multiple,
|
||||
props: {},
|
||||
columns: [],
|
||||
border: true,
|
||||
resizable: true
|
||||
}
|
||||
|
||||
if (this.dict != null) {
|
||||
if (this.dict.label != null) {
|
||||
defaultElProps.props.label = this.dict.label
|
||||
}
|
||||
if (this.dict.value != null) {
|
||||
defaultElProps.props.value = this.dict.value
|
||||
}
|
||||
if (this.dict.children != null) {
|
||||
defaultElProps.props.children = this.dict.children
|
||||
}
|
||||
// 加上树形的配置
|
||||
if (this.dict.isTree) {
|
||||
defaultElProps.treeConfig = this.elProps.treeConfig
|
||||
}
|
||||
}
|
||||
defaultElProps.nodeKey = defaultElProps.props.value
|
||||
lodash.merge(defaultElProps, this.elProps)
|
||||
|
||||
// 对显示列表增加操作列
|
||||
const gridProps = JSON.parse(JSON.stringify(defaultElProps))
|
||||
// gridProps.columns = [...gridProps.columns,{ title: '操作', width: this.colButtons.width, slots: { default: 'operate' } }]
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.gridOptions = gridProps
|
||||
|
||||
if (this.multiple) {
|
||||
defaultElProps.checkboxConfig = this.elProps.checkboxConfig
|
||||
? this.elProps.checkboxConfig
|
||||
: {}
|
||||
defaultElProps.columns = [
|
||||
{
|
||||
type: 'checkbox',
|
||||
width: 60
|
||||
},
|
||||
...defaultElProps.columns
|
||||
]
|
||||
} else {
|
||||
defaultElProps.radioConfig = this.elProps
|
||||
? this.elProps.radioConfig
|
||||
: {}
|
||||
defaultElProps.columns = [
|
||||
{
|
||||
type: 'radio',
|
||||
width: 60
|
||||
},
|
||||
...defaultElProps.columns
|
||||
]
|
||||
}
|
||||
|
||||
return defaultElProps
|
||||
},
|
||||
collapseTagSize () {
|
||||
return ['small', 'mini'].indexOf(this.selectSize) > -1 ? 'mini' : 'small'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterText (val) {
|
||||
// this.$refs.elTree.filter(val);
|
||||
this.searchTableData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// initData () {
|
||||
// d2CrudPlus.util.dict.get(this.dict).then(ret => {
|
||||
// this.$set(this, 'data', ret)
|
||||
// this.setValue(this.value)
|
||||
// })
|
||||
// },
|
||||
onDictLoaded () {
|
||||
// log.danger("onDictLoaded", this.dict, this.value);
|
||||
// this.setValue(this.value)
|
||||
this.tableData = this.value
|
||||
},
|
||||
setValue (value) {
|
||||
// log.danger("setValue:", this.currentValue, this.value, this._options);
|
||||
if (this.currentValue === this.value) {
|
||||
return
|
||||
}
|
||||
let arrValue = value
|
||||
if (value == null) {
|
||||
this.selected = []
|
||||
}
|
||||
|
||||
if (!(arrValue instanceof Array)) {
|
||||
arrValue = [arrValue]
|
||||
}
|
||||
if (this.dict && this.dict.getNodes) {
|
||||
// log.danger("getNodes:", arrValue);
|
||||
this.dict.getNodes(arrValue).then((nodes) => {
|
||||
this.selectedNodes(nodes, value)
|
||||
})
|
||||
} else {
|
||||
const nodes = []
|
||||
if (this._options == null || this._options.length === 0) {
|
||||
return
|
||||
}
|
||||
for (const item of arrValue) {
|
||||
const data = this._options
|
||||
const node = d2CrudPlus.util.dict.getByValue(item, data, this.dict)
|
||||
if (node != null) {
|
||||
nodes.push(node)
|
||||
}
|
||||
}
|
||||
this.selectedNodes(nodes, value)
|
||||
}
|
||||
},
|
||||
selectedNodes (nodes, value) {
|
||||
const selected = []
|
||||
for (const node of nodes) {
|
||||
node.id = node[this.dict.value]
|
||||
selected.push(node)
|
||||
}
|
||||
// log.danger("selected:", selected);
|
||||
this.$set(this, 'selected', selected)
|
||||
this.resetInputHeight()
|
||||
},
|
||||
handleCheckChange (event) {
|
||||
this.$emit('check-change', event)
|
||||
},
|
||||
handleCurrentChange (event) {
|
||||
this.$emit('current-change', event)
|
||||
},
|
||||
// 打开选择框
|
||||
openDialog () {
|
||||
const that = this
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
this.dialogVisible = true
|
||||
setTimeout(() => {
|
||||
if (that._options.length > 0) {
|
||||
that._options.map(
|
||||
(item) => item[that._elProps.props.value]
|
||||
)
|
||||
// ids.forEach((id) => {
|
||||
// console.log(111, id)
|
||||
// const current = that.$refs.elTree.store.nodesMap[id]
|
||||
// console.log(22, current)
|
||||
// if (current != null) {
|
||||
// this.doExpandParent(current)
|
||||
// }
|
||||
// })
|
||||
|
||||
// this.$nextTick(() => {
|
||||
// if (that.multiple) {
|
||||
// // this.$refs.elTree.setCheckedKeys(ids, this.leafOnly);
|
||||
// that.$refs.elTree.setCheckboxRow(that.tableData, true)
|
||||
// } else if (ids.length > 0) {
|
||||
// // this.$refs.elTree.setCurrentKey(ids[0]);
|
||||
// that.$refs.elTree.setRadioRow(that.tableData[0], true)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}, 1)
|
||||
},
|
||||
doExpandParent (node) {
|
||||
if (node.parent != null) {
|
||||
this.doExpandParent(node.parent)
|
||||
}
|
||||
node.expanded = true
|
||||
},
|
||||
// 处理value,是否为原生value还是自定义value
|
||||
getValueKey (item) {
|
||||
if (this._elProps.props.value != null) {
|
||||
return item[this._elProps.props.value]
|
||||
} else {
|
||||
return item.value
|
||||
}
|
||||
},
|
||||
// 处理label,是否为原生label还是自定义label
|
||||
getValueLabel (item) {
|
||||
if (this._elProps.props.label != null) {
|
||||
return item[this._elProps.props.label]
|
||||
} else {
|
||||
return item.label
|
||||
}
|
||||
},
|
||||
// 处理children,是否为原生children还是自定义children
|
||||
getValueChildren (item) {
|
||||
let children = 'children'
|
||||
if (this._elProps.props.children != null) {
|
||||
children = this._elProps.props.children
|
||||
}
|
||||
return item[children]
|
||||
},
|
||||
// 确定按钮事件
|
||||
selectSubmit () {
|
||||
const that = this
|
||||
const nodes = this.refreshSelected()
|
||||
if (that.tableData === undefined) {
|
||||
that.tableData = nodes
|
||||
} else {
|
||||
that.tableData = this.tableData.concat(nodes) // 为显示表格赋值
|
||||
}
|
||||
this.tableData = XEUtils.uniq(this.tableData) // 将数组去重
|
||||
that.dialogVisible = false
|
||||
that.doValueInputChanged(this.tableData)
|
||||
},
|
||||
// 将值传出去
|
||||
doValueInputChanged (nodes) {
|
||||
// let values = this.formatValue(nodes)
|
||||
let values = nodes
|
||||
this.resetInputHeight()
|
||||
if (!this.multiple) {
|
||||
values = values && values.length > 0 ? values[0] : undefined
|
||||
}
|
||||
this.currentValue = values
|
||||
if (this.dispatch) {
|
||||
this.dispatch('ElFormItem', 'el.form.blur')
|
||||
}
|
||||
this.$emit('input', values)
|
||||
},
|
||||
itemClosed (item) {
|
||||
const newNodes = lodash.without(this.selected, item)
|
||||
// console.log("new value", item, newNodes);
|
||||
this.$set(this, 'selected', newNodes)
|
||||
this.doValueInputChanged(newNodes)
|
||||
},
|
||||
// 获取选中的行数据
|
||||
refreshSelected () {
|
||||
let nodes = null
|
||||
if (this.multiple) {
|
||||
nodes = this.$refs.elTree.getCheckboxRecords()
|
||||
} else {
|
||||
const node = this.$refs.elTree.getRadioRecord()
|
||||
if (node == null) {
|
||||
nodes = []
|
||||
} else {
|
||||
nodes = [node]
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ignoreFullCheckedChildren) {
|
||||
nodes = this.filterFullCheckedChildren(nodes)
|
||||
}
|
||||
if (this.filter != null) {
|
||||
nodes = this.filter(nodes)
|
||||
}
|
||||
// log.danger("selected", this.selected);
|
||||
this.$set(this, 'selected', nodes)
|
||||
return nodes
|
||||
},
|
||||
resetInputHeight () {
|
||||
if (this.collapseTags && !this.filterable) return
|
||||
this.$nextTick(() => {
|
||||
if (!this.$refs.reference) return
|
||||
const inputChildNodes = this.$refs.reference.$el.childNodes
|
||||
const input = [].filter.call(
|
||||
inputChildNodes,
|
||||
(item) => item.tagName === 'INPUT'
|
||||
)[0]
|
||||
const tags = this.$refs.tags
|
||||
const sizeInMap = this.initialInputHeight || 40
|
||||
const height =
|
||||
this.selected.length === 0
|
||||
? sizeInMap + 'px'
|
||||
: Math.max(
|
||||
tags
|
||||
? tags.clientHeight + (tags.clientHeight > sizeInMap ? 6 : 0)
|
||||
: 0,
|
||||
sizeInMap
|
||||
) + 'px'
|
||||
input.style.height = height
|
||||
if (this.visible && this.emptyText !== false) {
|
||||
this.broadcast('ElSelectDropdown', 'updatePopper')
|
||||
}
|
||||
})
|
||||
},
|
||||
// 过滤叶子节点
|
||||
filterFullCheckedChildren (nodes) {
|
||||
const ignored = new Set()
|
||||
for (const item of nodes) {
|
||||
const children = this.getValueChildren(item)
|
||||
if (children != null) {
|
||||
for (const child of children) {
|
||||
ignored.add(this.getValueKey(child))
|
||||
}
|
||||
}
|
||||
}
|
||||
const values = []
|
||||
for (const item of nodes) {
|
||||
const key = this.getValueKey(item)
|
||||
if (!ignored.has(key)) {
|
||||
values.push(item)
|
||||
}
|
||||
}
|
||||
return values
|
||||
},
|
||||
formatValue (nodes) {
|
||||
const values = []
|
||||
for (const item of nodes) {
|
||||
values.push(this.getValueKey(item))
|
||||
}
|
||||
return values
|
||||
},
|
||||
filterNode (value, data) {
|
||||
if (!value) return true
|
||||
return this.getValueLabel(data).indexOf(value) !== -1
|
||||
},
|
||||
onChange (value) {
|
||||
this.$emit('change', value)
|
||||
|
||||
if (this.dispatch) {
|
||||
this.dispatch('ElFormItem', 'el.form.blur')
|
||||
}
|
||||
},
|
||||
// 分页事件
|
||||
handlePageChange ({
|
||||
currentPage,
|
||||
pageSize
|
||||
}) {
|
||||
const that = this
|
||||
that._elProps.page = currentPage
|
||||
that._elProps.limit = pageSize
|
||||
that.searchTableData()
|
||||
},
|
||||
// 获取数据事件
|
||||
searchTableData () {
|
||||
const that = this
|
||||
let params
|
||||
|
||||
if (that.pagination) {
|
||||
params = {
|
||||
page: that._elProps.page,
|
||||
limit: that._elProps.limit,
|
||||
search: that.filterText
|
||||
}
|
||||
} else {
|
||||
params = {
|
||||
search: that.filterText
|
||||
}
|
||||
}
|
||||
let url
|
||||
if (typeof that.dict.url === 'function') {
|
||||
const form = that.d2CrudContext.getForm()
|
||||
url = that.dict.url(that.dict, { form })
|
||||
} else {
|
||||
url = that.dict.url
|
||||
}
|
||||
request({
|
||||
url: url,
|
||||
params: params
|
||||
}).then((ret) => {
|
||||
const { data } = ret
|
||||
that._elProps.page = data.page
|
||||
that._elProps.limit = data.limit
|
||||
that._elProps.total = data.total
|
||||
that.$set(that, 'dictOptions', data.data)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 表格单选事件
|
||||
*/
|
||||
radioChange ({ checked, row, rowIndex, $rowIndex, column, columnIndex, $columnIndex, $event }) {
|
||||
this.$emit('radioChange', {
|
||||
row,
|
||||
rowIndex
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 表格多选事件
|
||||
*/
|
||||
checkboxChange ({ checked, row, rowIndex, $rowIndex, column, columnIndex, $columnIndex, $event }) {
|
||||
this.$emit('checkboxChange', {
|
||||
checked, row, rowIndex, $rowIndex, column, columnIndex, $columnIndex, $event
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.d2p-tree-selector {
|
||||
width: 100%;
|
||||
|
||||
.el-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.is-disabled .el-tag__close.el-icon-close {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.d2p-tree-selector-dialog {
|
||||
&.el-dialog {
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 20px 20px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 10px 20px 10px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -70,3 +70,23 @@
|
|||
| columns | 表格的列配置 | Array | true | [] |
|
||||
| field | 字段 | String | true | '' |
|
||||
| title | 字段名称 | String | true | '' |
|
||||
___
|
||||
|
||||
## events
|
||||
| Name | Description | Params |
|
||||
|------|-------------|--------|
|
||||
| radioChange| 表格行单选的点击事件| row,rowIndex|
|
||||
|
||||
|
||||
```
|
||||
form:{
|
||||
component:{
|
||||
on: { //单选事件监听
|
||||
radioChange({ event, scope }) {
|
||||
scope.form.channel_number = event.row.channel_number
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -201,6 +201,7 @@ export default {
|
|||
// this.dict = d2CrudPlus.util.dict.mergeDefault(this.dict, true)
|
||||
// }
|
||||
// this.initData()
|
||||
// console.log(this)
|
||||
this.searchTableData()
|
||||
},
|
||||
computed: {
|
||||
|
@ -337,7 +338,6 @@ export default {
|
|||
that.$nextTick(() => {
|
||||
const refs = Object.assign({}, that.$refs)
|
||||
const { elTree } = refs
|
||||
console.log(elTree)
|
||||
if (that.multiple) {
|
||||
elTree.setCheckboxRow(that.selected, true)
|
||||
} else {
|
||||
|
|
|
@ -29,7 +29,9 @@ export default {
|
|||
row[col.key] = value.split(',')
|
||||
// 进行组装地址,纠正地址
|
||||
row[col.key].map((val, index) => {
|
||||
if (val.startsWith('/')) {
|
||||
if (val.startsWith('/api')) {
|
||||
row[col.key][index] = val
|
||||
} else if (val.startsWith('/')) {
|
||||
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||
} else {
|
||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
||||
|
@ -67,7 +69,9 @@ export default {
|
|||
row[col.key] = value.split(',')
|
||||
// 进行组装地址,纠正地址
|
||||
row[col.key].map((val, index) => {
|
||||
if (val.startsWith('/')) {
|
||||
if (val.startsWith('/api')) {
|
||||
row[col.key][index] = val
|
||||
} else if (val.startsWith('/')) {
|
||||
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||
} else {
|
||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
||||
|
@ -101,7 +105,9 @@ export default {
|
|||
row[col.key] = value.split(',')
|
||||
// 进行组装地址,纠正地址
|
||||
row[col.key].map((val, index) => {
|
||||
if (val.startsWith('/')) {
|
||||
if (val.startsWith('/api')) {
|
||||
row[col.key][index] = val
|
||||
} else if (val.startsWith('/')) {
|
||||
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||
} else {
|
||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
||||
|
@ -139,7 +145,9 @@ export default {
|
|||
row[col.key] = value.split(',')
|
||||
// 进行组装地址,纠正地址
|
||||
row[col.key].map((val, index) => {
|
||||
if (val.startsWith('/')) {
|
||||
if (val.startsWith('/api')) {
|
||||
row[col.key][index] = val
|
||||
} else if (val.startsWith('/')) {
|
||||
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||
} else {
|
||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
||||
|
|
|
@ -252,6 +252,10 @@ Vue.prototype.commonEndColumns = function (param = {}) {
|
|||
showForm: (param.update_datetime && param.update_datetime.showForm) !== undefined ? param.update_datetime.showForm : false,
|
||||
showTable: (param.update_datetime && param.update_datetime.showTable) !== undefined ? param.update_datetime.showTable : true
|
||||
},
|
||||
creator_name: {
|
||||
showForm: (param.creator_name && param.creator_name.showForm) !== undefined ? param.creator_name.showForm : false,
|
||||
showTable: (param.creator_name && param.creator_name.showTable) !== undefined ? param.creator_name.showTable : false
|
||||
},
|
||||
create_datetime: {
|
||||
showForm: (param.create_datetime && param.create_datetime.showForm) !== undefined ? param.create_datetime.showForm : false,
|
||||
showTable: (param.create_datetime && param.create_datetime.showTable) !== undefined ? param.create_datetime.showTable : true
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-divider content-position="left">消息中心</el-divider>
|
||||
<div v-if="msgObj">
|
||||
<h3>{{msgObj.title}}</h3>
|
||||
<div class="content-style">{{msgObj.content}}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-empty :image-size="100"></el-empty>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div style="text-align: center">
|
||||
<el-button type="text" @click="toPage">前往通知中心</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions} from 'vuex'
|
||||
export default {
|
||||
name: 'msgList',
|
||||
props: {
|
||||
msgObj: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('d2admin/page', [
|
||||
'open'
|
||||
]),
|
||||
toPage () {
|
||||
// this.open({name:'messageCenter'})
|
||||
this.$router.push({
|
||||
name:'messageCenter'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.msg-list{
|
||||
padding: 5px 0px;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
.content-style{
|
||||
width: 370px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3; /*3表示只显示3行*/
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="通知"
|
||||
placement="bottom">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
width="400"
|
||||
trigger="click">
|
||||
<msg-list :msgObj="msgObj"></msg-list>
|
||||
<el-button
|
||||
class="d2-ml-0 d2-mr btn-text can-hover"
|
||||
type="text"
|
||||
slot="reference" @click="getList">
|
||||
<el-badge
|
||||
v-if="unread > 0"
|
||||
:max="99"
|
||||
:value="unread"
|
||||
:is-dot="unread === 0"
|
||||
>
|
||||
<d2-icon
|
||||
:name="unread === 0 ? 'dot-circle-o' : 'bell-o'"
|
||||
style="font-size: 20px"
|
||||
/>
|
||||
</el-badge>
|
||||
<d2-icon
|
||||
v-else
|
||||
name="bell-o"
|
||||
style="font-size: 16px"/>
|
||||
</el-button>
|
||||
</el-popover>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import msgList from './components/msg-list'
|
||||
import { request } from '@/api/service'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters('d2admin', {
|
||||
unread: 'messagecenter/unread'
|
||||
})
|
||||
},
|
||||
components: {
|
||||
msgList
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
msgObj: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList () {
|
||||
request({
|
||||
url: '/api/system/message_center/get_newest_msg/',
|
||||
method: 'get',
|
||||
params: {}
|
||||
}).then(res => {
|
||||
const { data } = res
|
||||
this.msgObj = data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -41,6 +41,7 @@
|
|||
<d2-header-log />
|
||||
<d2-header-fullscreen />
|
||||
<d2-header-theme />
|
||||
<d2-header-message />
|
||||
<d2-header-size />
|
||||
<d2-header-locales />
|
||||
<d2-header-color />
|
||||
|
@ -111,6 +112,7 @@ import d2HeaderTheme from './components/header-theme'
|
|||
import d2HeaderUser from './components/header-user'
|
||||
import d2HeaderLog from './components/header-log'
|
||||
import d2HeaderColor from './components/header-color'
|
||||
import d2HeaderMessage from './components/header-message'
|
||||
import { mapState, mapGetters, mapActions } from 'vuex'
|
||||
import mixinSearch from './mixins/search'
|
||||
export default {
|
||||
|
@ -127,7 +129,8 @@ export default {
|
|||
d2HeaderTheme,
|
||||
d2HeaderUser,
|
||||
d2HeaderLog,
|
||||
d2HeaderColor
|
||||
d2HeaderColor,
|
||||
d2HeaderMessage
|
||||
},
|
||||
provide () {
|
||||
return {
|
||||
|
|
|
@ -62,6 +62,38 @@ util.baseURL = function () {
|
|||
}
|
||||
return baseURL
|
||||
}
|
||||
|
||||
util.wsBaseURL = function () {
|
||||
var baseURL = process.env.VUE_APP_API
|
||||
var param = baseURL.split('/')[3] || ''
|
||||
if (window.pluginsAll && window.pluginsAll.indexOf('dvadmin-tenant-web') !== -1 && (!param || baseURL.startsWith('/'))) {
|
||||
// 1.把127.0.0.1 替换成和前端一样域名
|
||||
// 2.把 ip 地址替换成和前端一样域名
|
||||
// 3.把 /api 或其他类似的替换成和前端一样域名
|
||||
// document.domain
|
||||
var host = baseURL.split('/')[2]
|
||||
if (host) {
|
||||
var prot = baseURL.split(':')[2] || 80
|
||||
if (prot === 80 || prot === 443) {
|
||||
host = document.domain
|
||||
} else {
|
||||
host = document.domain + ':' + prot
|
||||
}
|
||||
baseURL = baseURL.split('/')[0] + '//' + baseURL.split('/')[1] + host + '/' + param
|
||||
} else {
|
||||
baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' : '') + location.port + baseURL
|
||||
}
|
||||
} else if (param !== '' || baseURL.startsWith('/')) {
|
||||
baseURL = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.hostname + (location.port ? ':' : '') + location.port + baseURL
|
||||
}
|
||||
if (!baseURL.endsWith('/')) {
|
||||
baseURL += '/'
|
||||
}
|
||||
if (baseURL.startsWith('http')) { // https 也默认会被替换成 wss
|
||||
baseURL = baseURL.replace('http', 'ws')
|
||||
}
|
||||
return baseURL
|
||||
}
|
||||
/**
|
||||
* 自动生成ID
|
||||
*/
|
||||
|
|
|
@ -53,7 +53,7 @@ export const checkRouter = function (menuData) {
|
|||
for (const item of menuData) {
|
||||
try {
|
||||
if (item.path !== '' && item.component) {
|
||||
(item.component && item.component.indexOf('@great-dream/') !== -1) ? pluginImport(item.component.replace('@great-dream/', '')) : _import(item.component)
|
||||
(item.component && item.component.substr(0, 8) === 'plugins/') ? pluginImport(item.component.replace('plugins/', '')) : _import(item.component)
|
||||
}
|
||||
result.push(item)
|
||||
} catch (err) {
|
||||
|
@ -72,7 +72,7 @@ export const handleRouter = function (menuData) {
|
|||
const obj = {
|
||||
path: item.path,
|
||||
name: item.component_name,
|
||||
component: (item.component && item.component.indexOf('@great-dream/') !== -1) ? pluginImport(item.component.replace('@great-dream/', '')) : _import(item.component),
|
||||
component: (item.component && item.component.substr(0, 8) === 'plugins/') ? pluginImport(item.component.replace('plugins/', '')) : _import(item.component),
|
||||
meta: {
|
||||
title: item.name,
|
||||
auth: true,
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
// 未读消息
|
||||
unread: 0
|
||||
},
|
||||
getters: {
|
||||
unread (state) {
|
||||
return state.unread
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* @description 添加一个日志
|
||||
* @param {Object} context
|
||||
* @param {String} param message {String} 信息
|
||||
* @param {String} param type {String} 类型
|
||||
* @param {Object} payload meta {Object} 附带的信息
|
||||
*/
|
||||
async setUnread ({ state,commit },number) {
|
||||
commit('set',number)
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
/**
|
||||
* 设置未读消息
|
||||
* @param state
|
||||
* @param number
|
||||
*/
|
||||
async set (state, number) {
|
||||
state.unread = number
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,9 @@ import { request } from '@/api/service'
|
|||
export const urlPrefix = '/api/system/area/'
|
||||
|
||||
export function GetList (query) {
|
||||
if (query.pcode === undefined || query.pcode === null || query.pcode.length === 0) {
|
||||
if ((!query.pcode || query.pcode.length === 0) && !query.name && !query.code) {
|
||||
query.level = 1
|
||||
delete query.pcode
|
||||
}
|
||||
return request({
|
||||
url: urlPrefix,
|
||||
|
|
|
@ -70,24 +70,6 @@ export const crudOptions = (vm) => {
|
|||
width: 100
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '关键词',
|
||||
key: 'search',
|
||||
show: false,
|
||||
disabled: true,
|
||||
search: {
|
||||
disabled: false
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入关键词'
|
||||
}
|
||||
},
|
||||
view: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
|
|
|
@ -13,6 +13,14 @@ export function GetList (query) {
|
|||
})
|
||||
}
|
||||
|
||||
export function GetListAll (query) {
|
||||
return request({
|
||||
url: urlPrefix + 'all_dept/',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
@click="addRow"
|
||||
><i class="el-icon-plus" /> 新增</el-button
|
||||
>
|
||||
<importExcel
|
||||
importApi="api/system/dept/import_data/"
|
||||
v-permission="'Import'"
|
||||
>导入
|
||||
</importExcel>
|
||||
</el-button-group>
|
||||
<crud-toolbar
|
||||
:search.sync="crud.searchOptions.show"
|
||||
|
|
|
@ -240,7 +240,7 @@ export const crudOptions = (vm) => {
|
|||
disabled: false
|
||||
},
|
||||
dict: {
|
||||
data: [{ label: '普通登录', value: 1 }]
|
||||
data: [{ label: '普通登录', value: 1 }, { label: '微信扫码登录', value: 2 }]
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
|
|
|
@ -14,14 +14,12 @@ export const urlPrefix = '/api/system/menu/'
|
|||
* 列表查询
|
||||
*/
|
||||
export function GetList (query) {
|
||||
query.limit = 999
|
||||
return request({
|
||||
url: urlPrefix,
|
||||
method: 'get',
|
||||
params: { ...query, limit: 999 }
|
||||
params: { ...query }
|
||||
}).then(res => {
|
||||
// 将列表数据转换为树形数据
|
||||
// res.data.data = XEUtils.toArrayTree(res.data.data, { parentKey: 'parent', strict: false })
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
|
|
@ -34,7 +34,10 @@ export const crudOptions = (vm) => {
|
|||
transform: true,
|
||||
rowField: 'id',
|
||||
parentField: 'parent',
|
||||
expandAll: true
|
||||
expandAll: true,
|
||||
hasChild: 'hasChild',
|
||||
lazy: true,
|
||||
loadMethod: vm.loadContentMethod
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
|
|
|
@ -86,6 +86,18 @@ export default {
|
|||
})
|
||||
})
|
||||
return result
|
||||
},
|
||||
/**
|
||||
* 懒加载
|
||||
* @param row
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
loadContentMethod ({ row }) {
|
||||
return new Promise(resolve => {
|
||||
api.GetList({ parent: row.id }).then(res => {
|
||||
resolve(res.data.data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,3 +52,5 @@ export function DelObj (id) {
|
|||
data: { id }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,13 +3,52 @@ import { request } from '@/api/service'
|
|||
export const crudOptions = (vm) => {
|
||||
return {
|
||||
indexRow: { // 或者直接传true,不显示title,不居中
|
||||
width: 60,
|
||||
title: '序号',
|
||||
align: 'center'
|
||||
},
|
||||
options: {
|
||||
tableType: 'vxe-table',
|
||||
rowKey: true, // 必须设置,true or false
|
||||
height: '100%' // 表格高度100%, 使用toolbar必须设置
|
||||
},
|
||||
viewOptions: {
|
||||
rowHandle: {
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
view: false,
|
||||
edit: {
|
||||
thin: true,
|
||||
text: '',
|
||||
show () {
|
||||
return vm.tabActivted !== 'receive'
|
||||
},
|
||||
disabled () {
|
||||
return !vm.hasPermissions('Update')
|
||||
}
|
||||
},
|
||||
remove: {
|
||||
thin: true,
|
||||
text: '',
|
||||
show () {
|
||||
return vm.tabActivted !== 'receive'
|
||||
},
|
||||
disabled () {
|
||||
return !vm.hasPermissions('Delete')
|
||||
}
|
||||
},
|
||||
custom: [
|
||||
{
|
||||
thin: true,
|
||||
text: null,
|
||||
icon: 'el-icon-view',
|
||||
size: 'small',
|
||||
disabled () {
|
||||
return !vm.hasPermissions('Retrieve')
|
||||
},
|
||||
order: 1,
|
||||
emit: 'onView'
|
||||
}
|
||||
]
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
|
@ -24,7 +63,7 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: false
|
||||
},
|
||||
width: 400,
|
||||
width: 200,
|
||||
form: {
|
||||
rules: [ // 表单校验规则
|
||||
{
|
||||
|
@ -35,12 +74,40 @@ export const crudOptions = (vm) => {
|
|||
component: { span: 24, placeholder: '请输入标题' }
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否已读',
|
||||
key: 'is_read',
|
||||
type: 'select',
|
||||
width: 100,
|
||||
show () {
|
||||
return vm.tabActivted === 'receive'
|
||||
},
|
||||
dict: {
|
||||
data: [
|
||||
{ label: '已读', value: true, color: 'success' },
|
||||
{ label: '未读', value: false, color: 'danger' }
|
||||
]
|
||||
},
|
||||
form: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '目标类型',
|
||||
key: 'target_type',
|
||||
type: 'radio',
|
||||
dict: { data: [{ value: 0, label: '按用户' }, { value: 1, label: '按角色' }, { value: 2, label: '按部门' }] },
|
||||
width: 120,
|
||||
show () {
|
||||
return vm.tabActivted === 'send'
|
||||
},
|
||||
dict: { data: [{ value: 0, label: '按用户' }, { value: 1, label: '按角色' }, { value: 2, label: '按部门' }, { value: 3, label: '通知公告' }] },
|
||||
form: {
|
||||
component: {
|
||||
span: 24,
|
||||
props: {
|
||||
type: 'el-radio-button'
|
||||
}
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
|
@ -56,8 +123,9 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 130,
|
||||
width: 130,
|
||||
type: 'table-selector',
|
||||
disabled: true,
|
||||
dict: {
|
||||
cache: false,
|
||||
url: '/api/system/user/',
|
||||
|
@ -124,8 +192,8 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: true
|
||||
},
|
||||
|
||||
minWidth: 130,
|
||||
disabled: true,
|
||||
width: 130,
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
cache: false,
|
||||
|
@ -193,11 +261,11 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 130,
|
||||
width: 130,
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
cache: false,
|
||||
url: '/api/system/dept/',
|
||||
url: '/api/system/dept/all_dept/',
|
||||
isTree: true,
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'name', // 数据字典中label字段的属性名
|
||||
|
@ -207,16 +275,13 @@ export const crudOptions = (vm) => {
|
|||
component
|
||||
}) => {
|
||||
return request({
|
||||
url: url,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 999
|
||||
}
|
||||
url: url
|
||||
}).then(ret => {
|
||||
return ret.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
disabled: true,
|
||||
form: {
|
||||
rules: [ // 表单校验规则
|
||||
{
|
||||
|
@ -269,7 +334,7 @@ export const crudOptions = (vm) => {
|
|||
{
|
||||
title: '内容',
|
||||
key: 'content',
|
||||
width: 300,
|
||||
minWidth: 300,
|
||||
type: 'editor-quill', // 富文本图片上传依赖file-uploader,请先配置好file-uploader
|
||||
form: {
|
||||
rules: [ // 表单校验规则
|
||||
|
@ -295,6 +360,9 @@ export const crudOptions = (vm) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
].concat(vm.commonEndColumns({
|
||||
create_datetime: { showTable: true },
|
||||
update_datetime: { showTable: false }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
ref="d2Crud"
|
||||
v-bind="_crudProps"
|
||||
v-on="_crudListeners"
|
||||
@form-component-ready="handleFormComponentReady"
|
||||
@onView="onView"
|
||||
>
|
||||
<div slot="header">
|
||||
<crud-search ref="search" :options="crud.searchOptions" @submit="handleSearch" />
|
||||
|
@ -29,6 +29,7 @@
|
|||
import { AddObj, GetObj, GetList, UpdateObj, DelObj, GetSelfReceive } from './api'
|
||||
import { crudOptions } from './crud'
|
||||
import { d2CrudPlus } from 'd2-crud-plus'
|
||||
import viewTemplate from './viewTemplate.js'
|
||||
export default {
|
||||
name: 'messageCenter',
|
||||
components: {},
|
||||
|
@ -38,10 +39,7 @@ export default {
|
|||
tabActivted: 'send'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// 配置编辑前获取详情
|
||||
this.crud.options.fetchDetail = this.fetchDetail
|
||||
},
|
||||
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
|
@ -61,7 +59,7 @@ export default {
|
|||
return AddObj(row).then(res => {
|
||||
const message = {
|
||||
message_id: res.data.id,
|
||||
contentType: 'TEXT',
|
||||
contentType: 'INFO',
|
||||
content: '您有新的消息,请到消息中心查看~'
|
||||
}
|
||||
this.$websocket.webSocketSend(message)
|
||||
|
@ -73,20 +71,18 @@ export default {
|
|||
delRequest (row) {
|
||||
return DelObj(row.id)
|
||||
},
|
||||
// 编辑对话框打开前获取详情
|
||||
fetchDetail (index, row) {
|
||||
if (index == null) {
|
||||
// 添加
|
||||
return {}
|
||||
}
|
||||
return GetObj(row).then(res => {
|
||||
return res.data
|
||||
onView ({ row, index }) {
|
||||
this.getD2Crud().showDialog({
|
||||
mode: 'view',
|
||||
rowIndex: index,
|
||||
template: viewTemplate
|
||||
})
|
||||
this.infoRequest(row)
|
||||
this.doRefresh()
|
||||
},
|
||||
handleFormComponentReady (event, key, form) {
|
||||
// console.log('form component ready:', event, key, form)
|
||||
},
|
||||
onTabClick (obj) {
|
||||
onTabClick (tab) {
|
||||
const { name } = tab
|
||||
this.tabActivted = name
|
||||
this.doRefresh()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
export default {
|
||||
title: {
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
component: {
|
||||
span: 24,
|
||||
placeholder: '请输入标题',
|
||||
disabled: true
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项'
|
||||
}
|
||||
],
|
||||
order: 10
|
||||
},
|
||||
content: {
|
||||
title: '内容',
|
||||
key: 'content',
|
||||
component: {
|
||||
name: 'd2p-quill',
|
||||
span: 24,
|
||||
disabled: true,
|
||||
props: {
|
||||
uploader: {
|
||||
type: 'form'
|
||||
}
|
||||
},
|
||||
events: {}
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项'
|
||||
}
|
||||
],
|
||||
order: 10
|
||||
}
|
||||
}
|
|
@ -163,7 +163,7 @@ export default {
|
|||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '自定数据权限'
|
||||
label: '自定义数据权限'
|
||||
}
|
||||
],
|
||||
dataAuthorizationTips: '授权用户可操作的数据范围',
|
||||
|
@ -212,8 +212,8 @@ export default {
|
|||
},
|
||||
// 获取部门数据
|
||||
getDeptData () {
|
||||
deptApi.GetList({ status: 1 }).then(ret => {
|
||||
this.deptOptions = ret.data.data
|
||||
deptApi.GetListAll().then(ret => {
|
||||
this.deptOptions = XEUtils.toArrayTree(ret.data, { parentKey: 'parent', strict: false })
|
||||
})
|
||||
},
|
||||
// 获取菜单数据
|
||||
|
|
|
@ -206,11 +206,76 @@ export const crudOptions = (vm) => {
|
|||
valueBinding: 'dept_name'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
key: 'role',
|
||||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 130,
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
cache: false,
|
||||
url: '/api/system/role/',
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'name', // 数据字典中label字段的属性名
|
||||
getData: (url, dict, {
|
||||
form,
|
||||
component
|
||||
}) => {
|
||||
return request({
|
||||
url: url,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
}).then(ret => {
|
||||
component._elProps.page = ret.data.page
|
||||
component._elProps.limit = ret.data.limit
|
||||
component._elProps.total = ret.data.total
|
||||
return ret.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
form: {
|
||||
rules: [ // 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项'
|
||||
}
|
||||
],
|
||||
itemProps: {
|
||||
class: { yxtInput: true }
|
||||
},
|
||||
component: {
|
||||
span: 12,
|
||||
pagination: true,
|
||||
props: { multiple: true },
|
||||
elProps: {
|
||||
columns: [
|
||||
{
|
||||
field: 'name',
|
||||
title: '角色名称'
|
||||
},
|
||||
{
|
||||
field: 'key',
|
||||
title: '权限标识'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
component: {
|
||||
name: 'manyToMany',
|
||||
valueBinding: 'role_info',
|
||||
children: 'name'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
key: 'mobile',
|
||||
search: {
|
||||
disabled: true
|
||||
disabled: false
|
||||
},
|
||||
minWidth: 110,
|
||||
type: 'input',
|
||||
|
@ -320,71 +385,6 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
helper: '限制文件大小不能超过500k'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
key: 'role',
|
||||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 130,
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
cache: false,
|
||||
url: '/api/system/role/',
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'name', // 数据字典中label字段的属性名
|
||||
getData: (url, dict, {
|
||||
form,
|
||||
component
|
||||
}) => {
|
||||
return request({
|
||||
url: url,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
}).then(ret => {
|
||||
component._elProps.page = ret.data.page
|
||||
component._elProps.limit = ret.data.limit
|
||||
component._elProps.total = ret.data.total
|
||||
return ret.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
form: {
|
||||
rules: [ // 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项'
|
||||
}
|
||||
],
|
||||
itemProps: {
|
||||
class: { yxtInput: true }
|
||||
},
|
||||
component: {
|
||||
span: 12,
|
||||
pagination: true,
|
||||
props: { multiple: true },
|
||||
elProps: {
|
||||
columns: [
|
||||
{
|
||||
field: 'name',
|
||||
title: '角色名称'
|
||||
},
|
||||
{
|
||||
field: 'key',
|
||||
title: '权限标识'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
component: {
|
||||
name: 'manyToMany',
|
||||
valueBinding: 'role_info',
|
||||
children: 'name'
|
||||
}
|
||||
}
|
||||
].concat(vm.commonEndColumns({
|
||||
create_datetime: { showTable: false },
|
||||
|
|
Loading…
Reference in New Issue