!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
|
import os
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
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.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||||
|
|
||||||
from application.websocketConfig import websocket_application
|
from application.routing import websocket_urlpatterns
|
||||||
|
|
||||||
http_application = get_asgi_application()
|
http_application = get_asgi_application()
|
||||||
|
|
||||||
async def application(scope,receive,send):
|
application = ProtocolTypeRouter({
|
||||||
if scope['type'] == 'http':
|
"http":http_application,
|
||||||
await http_application(scope, receive, send)
|
'websocket': AuthMiddlewareStack(
|
||||||
elif scope['type'] == 'websocket':
|
URLRouter(
|
||||||
await websocket_application(scope, receive, send)
|
websocket_urlpatterns #指明路由文件是devops/routing.py
|
||||||
else:
|
)
|
||||||
raise Exception("未知的scope类型,"+ scope['type'])
|
),
|
||||||
|
})
|
|
@ -1,13 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from channels.auth import AuthMiddlewareStack
|
from django.urls import path
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
from application.websocketConfig import MegCenter
|
||||||
from dvadmin.system import routing as dvadminRouting
|
|
||||||
|
|
||||||
|
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"
|
WSGI_APPLICATION = "application.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
|
@ -168,15 +169,21 @@ CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ********************* channels配置 ******************* #
|
# ********************* channels配置 ******************* #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
ASGI_APPLICATION = 'application.routing.application'
|
ASGI_APPLICATION = 'application.asgi.application'
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
'default': {
|
"default": {
|
||||||
'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||||
'CONFIG': {
|
}
|
||||||
"hosts": [('127.0.0.1', 6379)], #需修改
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
# CHANNEL_LAYERS = {
|
||||||
|
# 'default': {
|
||||||
|
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||||
|
# 'CONFIG': {
|
||||||
|
# "hosts": [('127.0.0.1', 6379)], #需修改
|
||||||
|
# },
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ********************* 日志配置 ******************* #
|
# ********************* 日志配置 ******************* #
|
||||||
|
|
|
@ -1,15 +1,42 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import django
|
|
||||||
|
|
||||||
django.setup()
|
|
||||||
import json
|
|
||||||
import urllib
|
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 jwt import InvalidSignatureError
|
||||||
|
|
||||||
from application import settings
|
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):
|
def request_data(scope):
|
||||||
|
@ -17,98 +44,69 @@ 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):
|
||||||
# 全部的websocket sender
|
async def connect(self):
|
||||||
CONNECTIONS = {}
|
try:
|
||||||
|
import jwt
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
await self.disconnect(None)
|
||||||
|
|
||||||
|
|
||||||
# 判断用户是否已经连接
|
async def disconnect(self, close_code):
|
||||||
def check_connection(key):
|
# Leave room group
|
||||||
return key in CONNECTIONS
|
await self.channel_layer.group_discard(self.chat_group_name, self.channel_name)
|
||||||
|
print("连接关闭")
|
||||||
|
await self.close(close_code)
|
||||||
|
|
||||||
|
|
||||||
# 发送消息结构体
|
class MegCenter(DvadminWebSocket):
|
||||||
def message(sender, msg_type, msg):
|
"""
|
||||||
text = json.dumps({
|
消息中心
|
||||||
'sender': sender,
|
"""
|
||||||
'contentType': msg_type,
|
|
||||||
'content': msg,
|
async def receive(self, text_data):
|
||||||
})
|
# 接受客户端的信息,你处理的函数
|
||||||
return {
|
text_data_json = json.loads(text_data)
|
||||||
'type': 'websocket.send',
|
message_id = text_data_json.get('message_id', None)
|
||||||
'text': text
|
user_list = await _get_message_center_instance(message_id)
|
||||||
|
for send_user in user_list:
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
"user_" + str(send_user),
|
||||||
|
{'type': 'push.message', 'json': text_data_json}
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
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:
|
|
||||||
try:
|
|
||||||
import jwt
|
|
||||||
decoded_result = jwt.decode(auth, settings.SECRET_KEY, algorithms=["HS256"])
|
|
||||||
if decoded_result:
|
|
||||||
user_id = decoded_result.get('user_id')
|
|
||||||
# 记录
|
|
||||||
CONNECTIONS[user_id] = send
|
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
# 收到中断WebSocket连接的消息
|
|
||||||
elif event['type'] == 'websocket.disconnect':
|
|
||||||
# 移除记录
|
|
||||||
if user_id in CONNECTIONS:
|
|
||||||
CONNECTIONS.pop(user_id)
|
|
||||||
|
|
||||||
# # 向其他人群发消息, 有人离线了
|
|
||||||
# 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)
|
|
||||||
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
|
|
||||||
|
|
||||||
print('[disconnect]')
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
# 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
|
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||||
|
|
|
@ -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):
|
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="登录用户名")
|
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")
|
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信息")
|
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="标题")
|
title = models.CharField(max_length=100,verbose_name="标题",help_text="标题")
|
||||||
content = models.TextField(verbose_name="内容",help_text="内容")
|
content = models.TextField(verbose_name="内容",help_text="内容")
|
||||||
target_type=models.IntegerField(default=0,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,
|
target_dept = models.ManyToManyField(to=Dept, blank=True, db_constraint=False,
|
||||||
verbose_name="目标部门", help_text="目标部门")
|
verbose_name="目标部门", help_text="目标部门")
|
||||||
target_role = models.ManyToManyField(to=Role, blank=True, db_constraint=False,
|
target_role = models.ManyToManyField(to=Role, blank=True, db_constraint=False,
|
||||||
verbose_name="目标角色", help_text="目标角色")
|
verbose_name="目标角色", help_text="目标角色")
|
||||||
is_read=models.BooleanField(default=False,blank=True,verbose_name="是否已读",help_text="是否已读")
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "message_center"
|
db_table = table_prefix + "message_center"
|
||||||
verbose_name = "消息中心"
|
verbose_name = "消息中心"
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
ordering = ("-create_datetime",)
|
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"]
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
class DeptImportSerializer(CustomModelSerializer):
|
||||||
|
"""
|
||||||
|
部门-导入-序列化器
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Dept
|
||||||
|
fields = '__all__'
|
||||||
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
class DeptInitSerializer(CustomModelSerializer):
|
class DeptInitSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
递归深度获取数信息(用于生成初始化json文件)
|
递归深度获取数信息(用于生成初始化json文件)
|
||||||
|
@ -113,8 +124,12 @@ class DeptViewSet(CustomModelViewSet):
|
||||||
update_serializer_class = DeptCreateUpdateSerializer
|
update_serializer_class = DeptCreateUpdateSerializer
|
||||||
filter_fields = ['name', 'id', 'parent']
|
filter_fields = ['name', 'id', 'parent']
|
||||||
search_fields = []
|
search_fields = []
|
||||||
|
|
||||||
# extra_filter_backends = []
|
# extra_filter_backends = []
|
||||||
|
import_serializer_class = DeptImportSerializer
|
||||||
|
import_field_dict = {
|
||||||
|
"name": "部门名称",
|
||||||
|
"key": "部门标识",
|
||||||
|
}
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
# 如果懒加载,则只返回父级
|
# 如果懒加载,则只返回父级
|
||||||
|
|
|
@ -21,6 +21,7 @@ class MenuSerializer(CustomModelSerializer):
|
||||||
菜单表的简单序列化器
|
菜单表的简单序列化器
|
||||||
"""
|
"""
|
||||||
menuPermission = serializers.SerializerMethodField(read_only=True)
|
menuPermission = serializers.SerializerMethodField(read_only=True)
|
||||||
|
hasChild = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_menuPermission(self, instance):
|
def get_menuPermission(self, instance):
|
||||||
queryset = instance.menuPermission.order_by('-name').values_list('name', flat=True)
|
queryset = instance.menuPermission.order_by('-name').values_list('name', flat=True)
|
||||||
|
@ -29,6 +30,12 @@ class MenuSerializer(CustomModelSerializer):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_hasChild(self, instance):
|
||||||
|
hasChild = Menu.objects.filter(parent=instance.id)
|
||||||
|
if hasChild:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Menu
|
model = Menu
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
@ -171,3 +178,21 @@ class MenuViewSet(CustomModelViewSet):
|
||||||
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
return SuccessResponse(data=data, total=len(data), msg="获取成功")
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from itertools import chain
|
import json
|
||||||
|
|
||||||
from django_restql.fields import DynamicSerializerMethodField
|
from django_restql.fields import DynamicSerializerMethodField
|
||||||
|
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 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.role import RoleSerializer
|
||||||
from dvadmin.system.views.user import UserSerializer
|
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.serializers import CustomModelSerializer
|
||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
|
|
||||||
|
@ -19,8 +21,11 @@ class MessageCenterSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
role_info = DynamicSerializerMethodField()
|
role_info = DynamicSerializerMethodField()
|
||||||
user_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):
|
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
|
# 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
|
||||||
serializer = RoleSerializer(
|
serializer = RoleSerializer(
|
||||||
|
@ -41,12 +46,53 @@ class MessageCenterSerializer(CustomModelSerializer):
|
||||||
)
|
)
|
||||||
return serializer.data
|
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:
|
class Meta:
|
||||||
model = MessageCenter
|
model = MessageCenter
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = ["id"]
|
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):
|
class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
消息中心-新增-序列化器
|
消息中心-新增-序列化器
|
||||||
|
@ -57,15 +103,28 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||||
initial_data = self.initial_data
|
initial_data = self.initial_data
|
||||||
target_type = initial_data.get('target_type')
|
target_type = initial_data.get('target_type')
|
||||||
# 在保存之前,根据目标类型,把目标用户查询出来并保存
|
# 在保存之前,根据目标类型,把目标用户查询出来并保存
|
||||||
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)
|
||||||
data.save()
|
if target_type in [3]: # 系统通知
|
||||||
data.target_user.set(users)
|
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
|
return data
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -74,8 +133,6 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MessageCenterViewSet(CustomModelViewSet):
|
class MessageCenterViewSet(CustomModelViewSet):
|
||||||
"""
|
"""
|
||||||
消息中心接口
|
消息中心接口
|
||||||
|
@ -85,21 +142,58 @@ class MessageCenterViewSet(CustomModelViewSet):
|
||||||
retrieve:单例
|
retrieve:单例
|
||||||
destroy:删除
|
destroy:删除
|
||||||
"""
|
"""
|
||||||
queryset = MessageCenter.objects.all()
|
queryset = MessageCenter.objects.order_by('create_datetime')
|
||||||
serializer_class = MessageCenterSerializer
|
serializer_class = MessageCenterSerializer
|
||||||
create_serializer_class = MessageCenterCreateSerializer
|
create_serializer_class = MessageCenterCreateSerializer
|
||||||
extra_filter_backends = []
|
extra_filter_backends = []
|
||||||
|
|
||||||
@action(methods=['GET'],detail=False,permission_classes=[IsAuthenticated])
|
def get_queryset(self):
|
||||||
def get_self_receive(self,request):
|
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
|
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)
|
page = self.paginate_queryset(queryset)
|
||||||
if page is not None:
|
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)
|
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="获取成功")
|
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):
|
def get_chinldern(self, instance):
|
||||||
queryset = SystemConfig.objects.filter(parent=instance)
|
queryset = SystemConfig.objects.filter(parent=instance)
|
||||||
if queryset:
|
serializer = SystemConfigSerializer(queryset, many=True)
|
||||||
serializer = SystemConfigSerializer(queryset, many=True)
|
return serializer.data
|
||||||
return serializer.data
|
|
||||||
return None
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SystemConfig
|
model = SystemConfig
|
||||||
|
|
|
@ -231,7 +231,8 @@ class UserViewSet(CustomModelViewSet):
|
||||||
update_serializer_class = UserUpdateSerializer
|
update_serializer_class = UserUpdateSerializer
|
||||||
# filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"]
|
# filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"]
|
||||||
filter_fields = {
|
filter_fields = {
|
||||||
"name": ["icontains"],
|
"name": ["exact"],
|
||||||
|
"mobile": ["exact"],
|
||||||
"username": ["exact"],
|
"username": ["exact"],
|
||||||
"gender": ["icontains"],
|
"gender": ["icontains"],
|
||||||
"is_active": ["icontains"],
|
"is_active": ["icontains"],
|
||||||
|
@ -288,7 +289,7 @@ class UserViewSet(CustomModelViewSet):
|
||||||
"gender": user.gender,
|
"gender": user.gender,
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"avatar": user.avatar,
|
"avatar": user.avatar,
|
||||||
"dept": user.dept.id,
|
"dept": user.dept_id,
|
||||||
"is_superuser": user.is_superuser,
|
"is_superuser": user.is_superuser,
|
||||||
"role": user.role.values_list('id', flat=True),
|
"role": user.role.values_list('id', flat=True),
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,11 +292,6 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
orm_lookups.append(search_field)
|
orm_lookups.append(search_field)
|
||||||
orm_lookups = (
|
|
||||||
orm_lookups
|
|
||||||
if isinstance(filterset.__class__._meta.fields, (list, tuple))
|
|
||||||
else filterset.filters.keys()
|
|
||||||
)
|
|
||||||
conditions = []
|
conditions = []
|
||||||
queries = []
|
queries = []
|
||||||
for search_term_key in filterset.data.keys():
|
for search_term_key in filterset.data.keys():
|
||||||
|
|
|
@ -7,6 +7,7 @@ from openpyxl import Workbook
|
||||||
from openpyxl.worksheet.datavalidation import DataValidation
|
from openpyxl.worksheet.datavalidation import DataValidation
|
||||||
from openpyxl.utils import get_column_letter, quote_sheetname
|
from openpyxl.utils import get_column_letter, quote_sheetname
|
||||||
from openpyxl.worksheet.table import Table, TableStyleInfo
|
from openpyxl.worksheet.table import Table, TableStyleInfo
|
||||||
|
from rest_framework.decorators import action
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from dvadmin.utils.import_export import import_to_data
|
from dvadmin.utils.import_export import import_to_data
|
||||||
|
@ -56,6 +57,7 @@ class ImportSerializerMixin:
|
||||||
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','post'],detail=False)
|
||||||
@transaction.atomic # Django 事务,防止出错
|
@transaction.atomic # Django 事务,防止出错
|
||||||
def import_data(self, request: Request, *args, **kwargs):
|
def import_data(self, request: Request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -151,13 +153,13 @@ class ImportSerializerMixin:
|
||||||
if queryset.model._meta.unique_together: # 判断是否存在联合主键
|
if queryset.model._meta.unique_together: # 判断是否存在联合主键
|
||||||
filter_dic = {i: ele.get(i) for i in list(queryset.model._meta.unique_together[0])}
|
filter_dic = {i: ele.get(i) for i in list(queryset.model._meta.unique_together[0])}
|
||||||
else:
|
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()
|
instance = filter_dic and queryset.filter(**filter_dic).first()
|
||||||
if instance and not updateSupport:
|
if instance and not updateSupport:
|
||||||
continue
|
continue
|
||||||
if not filter_dic:
|
if not filter_dic:
|
||||||
instance = None
|
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.is_valid(raise_exception=True)
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return DetailResponse(msg=f"导入成功!")
|
return DetailResponse(msg=f"导入成功!")
|
||||||
|
@ -216,7 +218,7 @@ class ExportSerializerMixin:
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__
|
assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__
|
||||||
assert self.export_serializer_class, "'%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 表
|
# 导出excel 表
|
||||||
response = HttpResponse(content_type="application/msexcel")
|
response = HttpResponse(content_type="application/msexcel")
|
||||||
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
||||||
|
|
|
@ -17,17 +17,8 @@ table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
||||||
|
|
||||||
|
|
||||||
class SoftDeleteQuerySet(QuerySet):
|
class SoftDeleteQuerySet(QuerySet):
|
||||||
def delete(self,soft_delete=True):
|
pass
|
||||||
"""
|
|
||||||
重写删除方法
|
|
||||||
当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()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,6 +44,27 @@ class SoftDeleteManager(models.Manager):
|
||||||
return SoftDeleteQuerySet(self.model).get(username=name)
|
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):
|
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="修改时间")
|
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="创建时间",
|
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||||
verbose_name="创建时间")
|
verbose_name="创建时间")
|
||||||
is_deleted = models.BooleanField(verbose_name="是否软删除",help_text='是否软删除', default=False, db_index=True)
|
|
||||||
objects = SoftDeleteManager()
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
verbose_name = '核心模型'
|
verbose_name = '核心模型'
|
||||||
verbose_name_plural = 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:
|
:return:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if queryset and hasattr(queryset, 'model'):
|
if queryset is not None and hasattr(queryset, 'model'):
|
||||||
model = queryset.model
|
model = queryset.model
|
||||||
elif view and hasattr(view.get_queryset(), 'model'):
|
elif view and hasattr(view.get_queryset(), 'model'):
|
||||||
model = view.get_queryset().model
|
model = view.get_queryset().model
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"""
|
"""
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -50,6 +51,7 @@ class CustomModelViewSet(ModelViewSet,ImportSerializerMixin,ExportSerializerMixi
|
||||||
return self.values_queryset
|
return self.values_queryset
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
action_serializer_name = f"{self.action}_serializer_class"
|
action_serializer_name = f"{self.action}_serializer_class"
|
||||||
action_serializer_class = getattr(self, action_serializer_name, None)
|
action_serializer_class = getattr(self, action_serializer_name, None)
|
||||||
|
@ -57,6 +59,16 @@ class CustomModelViewSet(ModelViewSet,ImportSerializerMixin,ExportSerializerMixi
|
||||||
return action_serializer_class
|
return action_serializer_class
|
||||||
return super().get_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):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.data, request=request)
|
serializer = self.get_serializer(data=request.data, request=request)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
@ -92,13 +104,7 @@ class CustomModelViewSet(ModelViewSet,ImportSerializerMixin,ExportSerializerMixi
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
request_data = request.data
|
instance.delete()
|
||||||
soft_delete = request_data.get('soft_delete',True)
|
|
||||||
if soft_delete:
|
|
||||||
instance.is_deleted = True
|
|
||||||
instance.save()
|
|
||||||
else:
|
|
||||||
self.perform_destroy(instance)
|
|
||||||
return DetailResponse(data=[], msg="删除成功")
|
return DetailResponse(data=[], msg="删除成功")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,27 @@ services:
|
||||||
# network:
|
# network:
|
||||||
# ipv4_address: 177.8.0.14
|
# 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:
|
networks:
|
||||||
network:
|
network:
|
||||||
ipam:
|
ipam:
|
||||||
|
|
|
@ -3,4 +3,4 @@ WORKDIR /backend
|
||||||
COPY ./backend/ .
|
COPY ./backend/ .
|
||||||
RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }'
|
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
|
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/ {
|
location /api/ {
|
||||||
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Nginx-Proxy true;
|
||||||
set_real_ip_from 0.0.0.0/0;
|
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;
|
real_ip_header X-Forwarded-For;
|
||||||
rewrite ^/api/(.*)$ /$1 break; #重写
|
rewrite ^/api/(.*)$ /$1 break; #重写
|
||||||
proxy_pass http://177.8.0.12:8000/; # 设置代理服务器的协议和地址
|
proxy_pass http://177.8.0.12:8000/; # 设置代理服务器的协议和地址
|
||||||
|
|
4
web/.env
4
web/.env
|
@ -1,13 +1,11 @@
|
||||||
# 所有环境默认
|
# 所有环境默认
|
||||||
|
|
||||||
# 页面 title 前缀
|
# 页面 title 前缀
|
||||||
VUE_APP_TITLE=D2Admin
|
VUE_APP_TITLE=DvAdmin
|
||||||
|
|
||||||
# 网络请求公用地址
|
# 网络请求公用地址
|
||||||
VUE_APP_API=/api/
|
VUE_APP_API=/api/
|
||||||
|
|
||||||
# websocket地址
|
|
||||||
VUE_APP_WEBSOCKET=""
|
|
||||||
|
|
||||||
# 仓库地址
|
# 仓库地址
|
||||||
VUE_APP_REPO=https://github.com/d2-projects/d2-admin-start-kit
|
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_PM_ENABLED = true
|
||||||
# 后端接口地址及端口(域名)
|
# 后端接口地址及端口(域名)
|
||||||
VUE_APP_API = "http://127.0.0.1:8000"
|
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",
|
"name": "django-vue-admin",
|
||||||
"version": "2.0.6",
|
"version": "2.0.7",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve --open",
|
"serve": "vue-cli-service serve --open",
|
||||||
"start": "npm run serve",
|
"start": "npm run serve",
|
||||||
|
@ -41,10 +41,10 @@
|
||||||
"screenfull": "^5.0.2",
|
"screenfull": "^5.0.2",
|
||||||
"sortablejs": "^1.10.1",
|
"sortablejs": "^1.10.1",
|
||||||
"ua-parser-js": "^0.7.20",
|
"ua-parser-js": "^0.7.20",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.7.10",
|
||||||
"vue-i18n": "^8.15.1",
|
"vue-i18n": "^8.15.1",
|
||||||
"vue-infinite-scroll": "^2.0.2",
|
"vue-infinite-scroll": "^2.0.2",
|
||||||
"vue-router": "^3.1.3",
|
"vue-router": "^3.6.5",
|
||||||
"vue-splitpane": "^1.0.6",
|
"vue-splitpane": "^1.0.6",
|
||||||
"vuex": "^3.1.2",
|
"vuex": "^3.1.2",
|
||||||
"vxe-table": "^3.3.2",
|
"vxe-table": "^3.3.2",
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import ElementUI from 'element-ui'
|
import ElementUI from 'element-ui'
|
||||||
import util from '@/libs/util'
|
import util from '@/libs/util'
|
||||||
|
import store from '@/store'
|
||||||
function initWebSocket (e) {
|
function initWebSocket (e) {
|
||||||
const token = util.cookies.get('token')
|
const token = util.cookies.get('token')
|
||||||
if (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 = new WebSocket(wsUri)// 这里面的this都指向vue
|
||||||
this.socket.onerror = webSocketOnError
|
this.socket.onerror = webSocketOnError
|
||||||
this.socket.onmessage = webSocketOnMessage
|
this.socket.onmessage = webSocketOnMessage
|
||||||
|
@ -20,9 +21,15 @@ function webSocketOnError (e) {
|
||||||
duration: 3000
|
duration: 3000
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收消息
|
||||||
|
* @param e
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
function webSocketOnMessage (e) {
|
function webSocketOnMessage (e) {
|
||||||
const data = JSON.parse(e.data)
|
const data = JSON.parse(e.data)
|
||||||
if (data.contentType === 'INFO') {
|
if (data.contentType === 'SYSTEM') {
|
||||||
ElementUI.Notification({
|
ElementUI.Notification({
|
||||||
title: 'websocket',
|
title: 'websocket',
|
||||||
message: data.content,
|
message: data.content,
|
||||||
|
@ -38,7 +45,7 @@ function webSocketOnMessage (e) {
|
||||||
position: 'bottom-right',
|
position: 'bottom-right',
|
||||||
duration: 0
|
duration: 0
|
||||||
})
|
})
|
||||||
} else if (data.contentType === 'TEXT') {
|
} else if (data.contentType === 'INFO') {
|
||||||
ElementUI.Notification({
|
ElementUI.Notification({
|
||||||
title: '温馨提示',
|
title: '温馨提示',
|
||||||
message: data.content,
|
message: data.content,
|
||||||
|
@ -47,16 +54,29 @@ function webSocketOnMessage (e) {
|
||||||
duration: 0
|
duration: 0
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log(data.content)
|
const { content } = data
|
||||||
|
if (content.model === 'message_center') {
|
||||||
|
const unread = content.unread
|
||||||
|
store.dispatch('d2admin/messagecenter/setUnread', unread)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 关闭websiocket
|
// 关闭websiocket
|
||||||
function closeWebsocket () {
|
function closeWebsocket () {
|
||||||
console.log('连接已关闭...')
|
console.log('连接已关闭...')
|
||||||
// close()
|
ElementUI.Notification({
|
||||||
this.socket.close()
|
title: 'websocket',
|
||||||
|
message: '连接已关闭...',
|
||||||
|
type: 'danger',
|
||||||
|
position: 'bottom-right',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
function webSocketSend (message) {
|
function webSocketSend (message) {
|
||||||
this.socket.send(JSON.stringify(message))
|
this.socket.send(JSON.stringify(message))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<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>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
// 行展示组件进阶版
|
// 行展示组件进阶版
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-tag style="margin-right: 10px" :type="color" v-for="(item,index) in currentValue" :key="index">{{
|
<el-tag style="margin-right: 10px" :type="color" v-for="(item,index) in currentValue" :key="index">{{
|
||||||
item[key]
|
item[key]
|
||||||
}}
|
}}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
|
|
|
@ -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 | [] |
|
| columns | 表格的列配置 | Array | true | [] |
|
||||||
| field | 字段 | String | true | '' |
|
| field | 字段 | String | true | '' |
|
||||||
| title | 字段名称 | 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.dict = d2CrudPlus.util.dict.mergeDefault(this.dict, true)
|
||||||
// }
|
// }
|
||||||
// this.initData()
|
// this.initData()
|
||||||
|
// console.log(this)
|
||||||
this.searchTableData()
|
this.searchTableData()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -337,7 +338,6 @@ export default {
|
||||||
that.$nextTick(() => {
|
that.$nextTick(() => {
|
||||||
const refs = Object.assign({}, that.$refs)
|
const refs = Object.assign({}, that.$refs)
|
||||||
const { elTree } = refs
|
const { elTree } = refs
|
||||||
console.log(elTree)
|
|
||||||
if (that.multiple) {
|
if (that.multiple) {
|
||||||
elTree.setCheckboxRow(that.selected, true)
|
elTree.setCheckboxRow(that.selected, true)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -29,7 +29,9 @@ export default {
|
||||||
row[col.key] = value.split(',')
|
row[col.key] = value.split(',')
|
||||||
// 进行组装地址,纠正地址
|
// 进行组装地址,纠正地址
|
||||||
row[col.key].map((val, index) => {
|
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)
|
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||||
} else {
|
} else {
|
||||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
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] = value.split(',')
|
||||||
// 进行组装地址,纠正地址
|
// 进行组装地址,纠正地址
|
||||||
row[col.key].map((val, index) => {
|
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)
|
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||||
} else {
|
} else {
|
||||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
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] = value.split(',')
|
||||||
// 进行组装地址,纠正地址
|
// 进行组装地址,纠正地址
|
||||||
row[col.key].map((val, index) => {
|
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)
|
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||||
} else {
|
} else {
|
||||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
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] = value.split(',')
|
||||||
// 进行组装地址,纠正地址
|
// 进行组装地址,纠正地址
|
||||||
row[col.key].map((val, index) => {
|
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)
|
row[col.key][index] = util.baseURL() + val.slice(1)
|
||||||
} else {
|
} else {
|
||||||
row[col.key][index] = !val.startsWith('http') ? util.baseURL() + val : val
|
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,
|
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
|
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: {
|
create_datetime: {
|
||||||
showForm: (param.create_datetime && param.create_datetime.showForm) !== undefined ? param.create_datetime.showForm : false,
|
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
|
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-log />
|
||||||
<d2-header-fullscreen />
|
<d2-header-fullscreen />
|
||||||
<d2-header-theme />
|
<d2-header-theme />
|
||||||
|
<d2-header-message />
|
||||||
<d2-header-size />
|
<d2-header-size />
|
||||||
<d2-header-locales />
|
<d2-header-locales />
|
||||||
<d2-header-color />
|
<d2-header-color />
|
||||||
|
@ -111,6 +112,7 @@ import d2HeaderTheme from './components/header-theme'
|
||||||
import d2HeaderUser from './components/header-user'
|
import d2HeaderUser from './components/header-user'
|
||||||
import d2HeaderLog from './components/header-log'
|
import d2HeaderLog from './components/header-log'
|
||||||
import d2HeaderColor from './components/header-color'
|
import d2HeaderColor from './components/header-color'
|
||||||
|
import d2HeaderMessage from './components/header-message'
|
||||||
import { mapState, mapGetters, mapActions } from 'vuex'
|
import { mapState, mapGetters, mapActions } from 'vuex'
|
||||||
import mixinSearch from './mixins/search'
|
import mixinSearch from './mixins/search'
|
||||||
export default {
|
export default {
|
||||||
|
@ -127,7 +129,8 @@ export default {
|
||||||
d2HeaderTheme,
|
d2HeaderTheme,
|
||||||
d2HeaderUser,
|
d2HeaderUser,
|
||||||
d2HeaderLog,
|
d2HeaderLog,
|
||||||
d2HeaderColor
|
d2HeaderColor,
|
||||||
|
d2HeaderMessage
|
||||||
},
|
},
|
||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -62,6 +62,38 @@ util.baseURL = function () {
|
||||||
}
|
}
|
||||||
return baseURL
|
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
|
* 自动生成ID
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -53,7 +53,7 @@ export const checkRouter = function (menuData) {
|
||||||
for (const item of menuData) {
|
for (const item of menuData) {
|
||||||
try {
|
try {
|
||||||
if (item.path !== '' && item.component) {
|
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)
|
result.push(item)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -72,7 +72,7 @@ export const handleRouter = function (menuData) {
|
||||||
const obj = {
|
const obj = {
|
||||||
path: item.path,
|
path: item.path,
|
||||||
name: item.component_name,
|
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: {
|
meta: {
|
||||||
title: item.name,
|
title: item.name,
|
||||||
auth: true,
|
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 const urlPrefix = '/api/system/area/'
|
||||||
|
|
||||||
export function GetList (query) {
|
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
|
query.level = 1
|
||||||
|
delete query.pcode
|
||||||
}
|
}
|
||||||
return request({
|
return request({
|
||||||
url: urlPrefix,
|
url: urlPrefix,
|
||||||
|
|
|
@ -70,24 +70,6 @@ export const crudOptions = (vm) => {
|
||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
{
|
|
||||||
title: '关键词',
|
|
||||||
key: 'search',
|
|
||||||
show: false,
|
|
||||||
disabled: true,
|
|
||||||
search: {
|
|
||||||
disabled: false
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
disabled: true,
|
|
||||||
component: {
|
|
||||||
placeholder: '请输入关键词'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
view: {
|
|
||||||
disabled: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
key: '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"
|
@click="addRow"
|
||||||
><i class="el-icon-plus" /> 新增</el-button
|
><i class="el-icon-plus" /> 新增</el-button
|
||||||
>
|
>
|
||||||
|
<importExcel
|
||||||
|
importApi="api/system/dept/import_data/"
|
||||||
|
v-permission="'Import'"
|
||||||
|
>导入
|
||||||
|
</importExcel>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
<crud-toolbar
|
<crud-toolbar
|
||||||
:search.sync="crud.searchOptions.show"
|
:search.sync="crud.searchOptions.show"
|
||||||
|
|
|
@ -240,7 +240,7 @@ export const crudOptions = (vm) => {
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
dict: {
|
dict: {
|
||||||
data: [{ label: '普通登录', value: 1 }]
|
data: [{ label: '普通登录', value: 1 }, { label: '微信扫码登录', value: 2 }]
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
component: {
|
component: {
|
||||||
|
|
|
@ -14,14 +14,12 @@ export const urlPrefix = '/api/system/menu/'
|
||||||
* 列表查询
|
* 列表查询
|
||||||
*/
|
*/
|
||||||
export function GetList (query) {
|
export function GetList (query) {
|
||||||
query.limit = 999
|
|
||||||
return request({
|
return request({
|
||||||
url: urlPrefix,
|
url: urlPrefix,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { ...query, limit: 999 }
|
params: { ...query }
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
// 将列表数据转换为树形数据
|
// 将列表数据转换为树形数据
|
||||||
// res.data.data = XEUtils.toArrayTree(res.data.data, { parentKey: 'parent', strict: false })
|
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,10 @@ export const crudOptions = (vm) => {
|
||||||
transform: true,
|
transform: true,
|
||||||
rowField: 'id',
|
rowField: 'id',
|
||||||
parentField: 'parent',
|
parentField: 'parent',
|
||||||
expandAll: true
|
expandAll: true,
|
||||||
|
hasChild: 'hasChild',
|
||||||
|
lazy: true,
|
||||||
|
loadMethod: vm.loadContentMethod
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
|
|
|
@ -86,6 +86,18 @@ export default {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return result
|
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 }
|
data: { id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,52 @@ import { request } from '@/api/service'
|
||||||
export const crudOptions = (vm) => {
|
export const crudOptions = (vm) => {
|
||||||
return {
|
return {
|
||||||
indexRow: { // 或者直接传true,不显示title,不居中
|
indexRow: { // 或者直接传true,不显示title,不居中
|
||||||
|
width: 60,
|
||||||
title: '序号',
|
title: '序号',
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
tableType: 'vxe-table',
|
||||||
|
rowKey: true, // 必须设置,true or false
|
||||||
height: '100%' // 表格高度100%, 使用toolbar必须设置
|
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: [
|
columns: [
|
||||||
{
|
{
|
||||||
|
@ -24,7 +63,7 @@ export const crudOptions = (vm) => {
|
||||||
search: {
|
search: {
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
width: 400,
|
width: 200,
|
||||||
form: {
|
form: {
|
||||||
rules: [ // 表单校验规则
|
rules: [ // 表单校验规则
|
||||||
{
|
{
|
||||||
|
@ -35,12 +74,40 @@ export const crudOptions = (vm) => {
|
||||||
component: { span: 24, placeholder: '请输入标题' }
|
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: '目标类型',
|
title: '目标类型',
|
||||||
key: 'target_type',
|
key: 'target_type',
|
||||||
type: 'radio',
|
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: {
|
form: {
|
||||||
|
component: {
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
type: 'el-radio-button'
|
||||||
|
}
|
||||||
|
},
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -56,8 +123,9 @@ export const crudOptions = (vm) => {
|
||||||
search: {
|
search: {
|
||||||
disabled: true
|
disabled: true
|
||||||
},
|
},
|
||||||
minWidth: 130,
|
width: 130,
|
||||||
type: 'table-selector',
|
type: 'table-selector',
|
||||||
|
disabled: true,
|
||||||
dict: {
|
dict: {
|
||||||
cache: false,
|
cache: false,
|
||||||
url: '/api/system/user/',
|
url: '/api/system/user/',
|
||||||
|
@ -124,8 +192,8 @@ export const crudOptions = (vm) => {
|
||||||
search: {
|
search: {
|
||||||
disabled: true
|
disabled: true
|
||||||
},
|
},
|
||||||
|
disabled: true,
|
||||||
minWidth: 130,
|
width: 130,
|
||||||
type: 'table-selector',
|
type: 'table-selector',
|
||||||
dict: {
|
dict: {
|
||||||
cache: false,
|
cache: false,
|
||||||
|
@ -193,11 +261,11 @@ export const crudOptions = (vm) => {
|
||||||
search: {
|
search: {
|
||||||
disabled: true
|
disabled: true
|
||||||
},
|
},
|
||||||
minWidth: 130,
|
width: 130,
|
||||||
type: 'table-selector',
|
type: 'table-selector',
|
||||||
dict: {
|
dict: {
|
||||||
cache: false,
|
cache: false,
|
||||||
url: '/api/system/dept/',
|
url: '/api/system/dept/all_dept/',
|
||||||
isTree: true,
|
isTree: true,
|
||||||
value: 'id', // 数据字典中value字段的属性名
|
value: 'id', // 数据字典中value字段的属性名
|
||||||
label: 'name', // 数据字典中label字段的属性名
|
label: 'name', // 数据字典中label字段的属性名
|
||||||
|
@ -207,16 +275,13 @@ export const crudOptions = (vm) => {
|
||||||
component
|
component
|
||||||
}) => {
|
}) => {
|
||||||
return request({
|
return request({
|
||||||
url: url,
|
url: url
|
||||||
params: {
|
|
||||||
page: 1,
|
|
||||||
limit: 999
|
|
||||||
}
|
|
||||||
}).then(ret => {
|
}).then(ret => {
|
||||||
return ret.data.data
|
return ret.data.data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
disabled: true,
|
||||||
form: {
|
form: {
|
||||||
rules: [ // 表单校验规则
|
rules: [ // 表单校验规则
|
||||||
{
|
{
|
||||||
|
@ -269,7 +334,7 @@ export const crudOptions = (vm) => {
|
||||||
{
|
{
|
||||||
title: '内容',
|
title: '内容',
|
||||||
key: 'content',
|
key: 'content',
|
||||||
width: 300,
|
minWidth: 300,
|
||||||
type: 'editor-quill', // 富文本图片上传依赖file-uploader,请先配置好file-uploader
|
type: 'editor-quill', // 富文本图片上传依赖file-uploader,请先配置好file-uploader
|
||||||
form: {
|
form: {
|
||||||
rules: [ // 表单校验规则
|
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"
|
ref="d2Crud"
|
||||||
v-bind="_crudProps"
|
v-bind="_crudProps"
|
||||||
v-on="_crudListeners"
|
v-on="_crudListeners"
|
||||||
@form-component-ready="handleFormComponentReady"
|
@onView="onView"
|
||||||
>
|
>
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
<crud-search ref="search" :options="crud.searchOptions" @submit="handleSearch" />
|
<crud-search ref="search" :options="crud.searchOptions" @submit="handleSearch" />
|
||||||
|
@ -29,6 +29,7 @@
|
||||||
import { AddObj, GetObj, GetList, UpdateObj, DelObj, GetSelfReceive } from './api'
|
import { AddObj, GetObj, GetList, UpdateObj, DelObj, GetSelfReceive } from './api'
|
||||||
import { crudOptions } from './crud'
|
import { crudOptions } from './crud'
|
||||||
import { d2CrudPlus } from 'd2-crud-plus'
|
import { d2CrudPlus } from 'd2-crud-plus'
|
||||||
|
import viewTemplate from './viewTemplate.js'
|
||||||
export default {
|
export default {
|
||||||
name: 'messageCenter',
|
name: 'messageCenter',
|
||||||
components: {},
|
components: {},
|
||||||
|
@ -38,10 +39,7 @@ export default {
|
||||||
tabActivted: 'send'
|
tabActivted: 'send'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
|
||||||
// 配置编辑前获取详情
|
|
||||||
this.crud.options.fetchDetail = this.fetchDetail
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -61,7 +59,7 @@ export default {
|
||||||
return AddObj(row).then(res => {
|
return AddObj(row).then(res => {
|
||||||
const message = {
|
const message = {
|
||||||
message_id: res.data.id,
|
message_id: res.data.id,
|
||||||
contentType: 'TEXT',
|
contentType: 'INFO',
|
||||||
content: '您有新的消息,请到消息中心查看~'
|
content: '您有新的消息,请到消息中心查看~'
|
||||||
}
|
}
|
||||||
this.$websocket.webSocketSend(message)
|
this.$websocket.webSocketSend(message)
|
||||||
|
@ -73,20 +71,18 @@ export default {
|
||||||
delRequest (row) {
|
delRequest (row) {
|
||||||
return DelObj(row.id)
|
return DelObj(row.id)
|
||||||
},
|
},
|
||||||
// 编辑对话框打开前获取详情
|
onView ({ row, index }) {
|
||||||
fetchDetail (index, row) {
|
this.getD2Crud().showDialog({
|
||||||
if (index == null) {
|
mode: 'view',
|
||||||
// 添加
|
rowIndex: index,
|
||||||
return {}
|
template: viewTemplate
|
||||||
}
|
|
||||||
return GetObj(row).then(res => {
|
|
||||||
return res.data
|
|
||||||
})
|
})
|
||||||
|
this.infoRequest(row)
|
||||||
|
this.doRefresh()
|
||||||
},
|
},
|
||||||
handleFormComponentReady (event, key, form) {
|
onTabClick (tab) {
|
||||||
// console.log('form component ready:', event, key, form)
|
const { name } = tab
|
||||||
},
|
this.tabActivted = name
|
||||||
onTabClick (obj) {
|
|
||||||
this.doRefresh()
|
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,
|
value: 4,
|
||||||
label: '自定数据权限'
|
label: '自定义数据权限'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dataAuthorizationTips: '授权用户可操作的数据范围',
|
dataAuthorizationTips: '授权用户可操作的数据范围',
|
||||||
|
@ -212,8 +212,8 @@ export default {
|
||||||
},
|
},
|
||||||
// 获取部门数据
|
// 获取部门数据
|
||||||
getDeptData () {
|
getDeptData () {
|
||||||
deptApi.GetList({ status: 1 }).then(ret => {
|
deptApi.GetListAll().then(ret => {
|
||||||
this.deptOptions = ret.data.data
|
this.deptOptions = XEUtils.toArrayTree(ret.data, { parentKey: 'parent', strict: false })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 获取菜单数据
|
// 获取菜单数据
|
||||||
|
|
|
@ -206,11 +206,76 @@ export const crudOptions = (vm) => {
|
||||||
valueBinding: 'dept_name'
|
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: '手机号码',
|
title: '手机号码',
|
||||||
key: 'mobile',
|
key: 'mobile',
|
||||||
search: {
|
search: {
|
||||||
disabled: true
|
disabled: false
|
||||||
},
|
},
|
||||||
minWidth: 110,
|
minWidth: 110,
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
@ -320,71 +385,6 @@ export const crudOptions = (vm) => {
|
||||||
},
|
},
|
||||||
helper: '限制文件大小不能超过500k'
|
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({
|
].concat(vm.commonEndColumns({
|
||||||
create_datetime: { showTable: false },
|
create_datetime: { showTable: false },
|
||||||
|
|
Loading…
Reference in New Issue