@@ -8,9 +8,7 @@ https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from channels.auth import AuthMiddlewareStack
|
from channels.routing import ProtocolTypeRouter
|
||||||
from channels.security.websocket import AllowedHostsOriginValidator
|
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||||
@@ -18,15 +16,6 @@ os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
|||||||
|
|
||||||
http_application = get_asgi_application()
|
http_application = get_asgi_application()
|
||||||
|
|
||||||
from application.routing import websocket_urlpatterns
|
|
||||||
|
|
||||||
application = ProtocolTypeRouter({
|
application = ProtocolTypeRouter({
|
||||||
"http": http_application,
|
"http": http_application,
|
||||||
'websocket': AllowedHostsOriginValidator(
|
|
||||||
AuthMiddlewareStack(
|
|
||||||
URLRouter(
|
|
||||||
websocket_urlpatterns # 指明路由文件是devops/routing.py
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from django.urls import path
|
|
||||||
from application.websocketConfig import MegCenter
|
|
||||||
|
|
||||||
websocket_urlpatterns = [
|
|
||||||
path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者
|
|
||||||
]
|
|
||||||
@@ -399,8 +399,12 @@ DICTIONARY_CONFIG = {}
|
|||||||
# ================================================= #
|
# ================================================= #
|
||||||
# 租户共享app
|
# 租户共享app
|
||||||
TENANT_SHARED_APPS = []
|
TENANT_SHARED_APPS = []
|
||||||
|
# 普通租户独有app
|
||||||
|
TENANT_EXCLUSIVE_APPS = []
|
||||||
# 插件 urlpatterns
|
# 插件 urlpatterns
|
||||||
PLUGINS_URL_PATTERNS = []
|
PLUGINS_URL_PATTERNS = []
|
||||||
|
# 所有模式有的
|
||||||
|
SHARED_APPS = []
|
||||||
# ********** 一键导入插件配置开始 **********
|
# ********** 一键导入插件配置开始 **********
|
||||||
# 例如:
|
# 例如:
|
||||||
# from dvadmin_upgrade_center.settings import * # 升级中心
|
# from dvadmin_upgrade_center.settings import * # 升级中心
|
||||||
|
|||||||
33
backend/application/sse_views.py
Normal file
33
backend/application/sse_views.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# views.py
|
||||||
|
import time
|
||||||
|
|
||||||
|
import jwt
|
||||||
|
from django.http import StreamingHttpResponse
|
||||||
|
|
||||||
|
from application import settings
|
||||||
|
from dvadmin.system.models import MessageCenterTargetUser
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
|
||||||
|
def event_stream(user_id):
|
||||||
|
last_sent_time = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# 从 Redis 中获取最后数据库变更时间
|
||||||
|
last_db_change_time = cache.get('last_db_change_time', 0)
|
||||||
|
# 只有当数据库发生变化时才检查总数
|
||||||
|
if last_db_change_time and last_db_change_time > last_sent_time:
|
||||||
|
count = MessageCenterTargetUser.objects.filter(users=user_id, is_read=False).count()
|
||||||
|
yield f"data: {count}\n\n"
|
||||||
|
last_sent_time = time.time()
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def sse_view(request):
|
||||||
|
token = request.GET.get('token')
|
||||||
|
decoded = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
|
||||||
|
user_id = decoded.get('user_id')
|
||||||
|
response = StreamingHttpResponse(event_stream(user_id), content_type='text/event-stream')
|
||||||
|
response['Cache-Control'] = 'no-cache'
|
||||||
|
return response
|
||||||
@@ -24,6 +24,7 @@ from rest_framework_simplejwt.views import (
|
|||||||
|
|
||||||
from application import dispatch
|
from application import dispatch
|
||||||
from application import settings
|
from application import settings
|
||||||
|
from application.sse_views import sse_view
|
||||||
from dvadmin.system.views.dictionary import InitDictionaryViewSet
|
from dvadmin.system.views.dictionary import InitDictionaryViewSet
|
||||||
from dvadmin.system.views.login import (
|
from dvadmin.system.views.login import (
|
||||||
LoginView,
|
LoginView,
|
||||||
@@ -40,6 +41,7 @@ dispatch.init_system_config()
|
|||||||
dispatch.init_dictionary()
|
dispatch.init_dictionary()
|
||||||
# =========== 初始化系统配置 =================
|
# =========== 初始化系统配置 =================
|
||||||
|
|
||||||
|
permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ]
|
||||||
schema_view = get_schema_view(
|
schema_view = get_schema_view(
|
||||||
openapi.Info(
|
openapi.Info(
|
||||||
title="Snippets API",
|
title="Snippets API",
|
||||||
@@ -50,7 +52,7 @@ schema_view = get_schema_view(
|
|||||||
license=openapi.License(name="BSD License"),
|
license=openapi.License(name="BSD License"),
|
||||||
),
|
),
|
||||||
public=True,
|
public=True,
|
||||||
permission_classes=(permissions.IsAuthenticated,),
|
permission_classes=permission_classes,
|
||||||
generator_class=CustomOpenAPISchemaGenerator,
|
generator_class=CustomOpenAPISchemaGenerator,
|
||||||
)
|
)
|
||||||
# 前端页面映射
|
# 前端页面映射
|
||||||
@@ -115,6 +117,8 @@ urlpatterns = (
|
|||||||
# 前端页面映射
|
# 前端页面映射
|
||||||
path('web/', web_view, name='web_view'),
|
path('web/', web_view, name='web_view'),
|
||||||
path('web/<path:filename>', serve_web_files, name='serve_web_files'),
|
path('web/<path:filename>', serve_web_files, name='serve_web_files'),
|
||||||
|
# sse
|
||||||
|
path('sse/', sse_view, name='sse'),
|
||||||
]
|
]
|
||||||
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
|
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
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 rest_framework.request import Request
|
|
||||||
|
|
||||||
from application import settings
|
|
||||||
from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser
|
|
||||||
from dvadmin.system.views.message_center import MessageCenterTargetUserSerializer
|
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
|
||||||
|
|
||||||
send_dict = {}
|
|
||||||
|
|
||||||
|
|
||||||
# 发送消息结构体
|
|
||||||
def set_message(sender, msg_type, msg, unread=0):
|
|
||||||
text = {
|
|
||||||
'sender': sender,
|
|
||||||
'contentType': msg_type,
|
|
||||||
'content': msg,
|
|
||||||
'unread': unread
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
# 异步获取消息中心的目标用户
|
|
||||||
@database_sync_to_async
|
|
||||||
def _get_message_center_instance(message_id):
|
|
||||||
from dvadmin.system.models import MessageCenter
|
|
||||||
_MessageCenter = MessageCenter.objects.filter(id=message_id).values_list('target_user', flat=True)
|
|
||||||
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):
|
|
||||||
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"]
|
|
||||||
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()
|
|
||||||
# 主动推送消息
|
|
||||||
unread_count = await _get_message_unread(self.user_id)
|
|
||||||
if unread_count == 0:
|
|
||||||
# 发送连接成功
|
|
||||||
await self.send_json(set_message('system', 'SYSTEM', '您已上线'))
|
|
||||||
else:
|
|
||||||
await self.send_json(
|
|
||||||
set_message('system', 'SYSTEM', "请查看您的未读消息~",
|
|
||||||
unread=unread_count))
|
|
||||||
except InvalidSignatureError:
|
|
||||||
await self.disconnect(None)
|
|
||||||
|
|
||||||
async def disconnect(self, close_code):
|
|
||||||
# Leave room group
|
|
||||||
await self.channel_layer.group_discard(self.chat_group_name, self.channel_name)
|
|
||||||
print("连接关闭")
|
|
||||||
try:
|
|
||||||
await self.close(close_code)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MegCenter(DvadminWebSocket):
|
|
||||||
"""
|
|
||||||
消息中心
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def receive(self, text_data):
|
|
||||||
# 接受客户端的信息,你处理的函数
|
|
||||||
text_data_json = json.loads(text_data)
|
|
||||||
message_id = text_data_json.get('message_id', None)
|
|
||||||
user_list = await _get_message_center_instance(message_id)
|
|
||||||
for send_user in user_list:
|
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
class MessageCreateSerializer(CustomModelSerializer):
|
|
||||||
"""
|
|
||||||
消息中心-新增-序列化器
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
model = MessageCenter
|
|
||||||
fields = "__all__"
|
|
||||||
read_only_fields = ["id"]
|
|
||||||
|
|
||||||
|
|
||||||
def websocket_push(user_id, message):
|
|
||||||
username = "user_" + str(user_id)
|
|
||||||
channel_layer = get_channel_layer()
|
|
||||||
async_to_sync(channel_layer.group_send)(
|
|
||||||
username,
|
|
||||||
{
|
|
||||||
"type": "push.message",
|
|
||||||
"json": message
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_message_push(title: str, content: str, target_type: int = 0, target_user: list = None, target_dept=None,
|
|
||||||
target_role=None, message: dict = None, request=Request):
|
|
||||||
if message is None:
|
|
||||||
message = {"contentType": "INFO", "content": None}
|
|
||||||
if target_role is None:
|
|
||||||
target_role = []
|
|
||||||
if target_dept is None:
|
|
||||||
target_dept = []
|
|
||||||
data = {
|
|
||||||
"title": title,
|
|
||||||
"content": content,
|
|
||||||
"target_type": target_type,
|
|
||||||
"target_user": target_user,
|
|
||||||
"target_dept": target_dept,
|
|
||||||
"target_role": target_role
|
|
||||||
}
|
|
||||||
message_center_instance = MessageCreateSerializer(data=data, request=request)
|
|
||||||
message_center_instance.is_valid(raise_exception=True)
|
|
||||||
message_center_instance.save()
|
|
||||||
users = target_user or []
|
|
||||||
if target_type in [1]: # 按角色
|
|
||||||
users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True)
|
|
||||||
if target_type in [2]: # 按部门
|
|
||||||
users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True)
|
|
||||||
if target_type in [3]: # 系统通知
|
|
||||||
users = Users.objects.values_list('id', flat=True)
|
|
||||||
targetuser_data = []
|
|
||||||
for user in users:
|
|
||||||
targetuser_data.append({
|
|
||||||
"messagecenter": message_center_instance.instance.id,
|
|
||||||
"users": user
|
|
||||||
})
|
|
||||||
targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=request)
|
|
||||||
targetuser_instance.is_valid(raise_exception=True)
|
|
||||||
targetuser_instance.save()
|
|
||||||
for user in users:
|
|
||||||
username = "user_" + str(user)
|
|
||||||
unread_count = async_to_sync(_get_message_unread)(user)
|
|
||||||
channel_layer = get_channel_layer()
|
|
||||||
async_to_sync(channel_layer.group_send)(
|
|
||||||
username,
|
|
||||||
{
|
|
||||||
"type": "push.message",
|
|
||||||
"json": {**message, 'unread': unread_count}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
exclude = ["venv"] # 需要排除的文件目录
|
exclude = ["venv", ".venv"] # 需要排除的文件目录
|
||||||
for root, dirs, files in os.walk('.'):
|
for root, dirs, files in os.walk('.'):
|
||||||
dirs[:] = list(set(dirs) - set(exclude))
|
dirs[:] = list(set(dirs) - set(exclude))
|
||||||
if 'migrations' in dirs:
|
if 'migrations' in dirs:
|
||||||
|
|||||||
@@ -4,3 +4,7 @@ from django.apps import AppConfig
|
|||||||
class SystemConfig(AppConfig):
|
class SystemConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'dvadmin.system'
|
name = 'dvadmin.system'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
# 注册信号
|
||||||
|
import dvadmin.system.signals # 确保路径正确
|
||||||
|
|||||||
@@ -546,5 +546,50 @@
|
|||||||
"children": []
|
"children": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "文件存储引擎",
|
||||||
|
"value": "file_engine",
|
||||||
|
"type": 0,
|
||||||
|
"color": null,
|
||||||
|
"is_value": false,
|
||||||
|
"status": true,
|
||||||
|
"sort": 9,
|
||||||
|
"remark": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"label": "本地",
|
||||||
|
"value": "local",
|
||||||
|
"type": 0,
|
||||||
|
"color": "primary",
|
||||||
|
"is_value": true,
|
||||||
|
"status": true,
|
||||||
|
"sort": 1,
|
||||||
|
"remark": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "阿里云oss",
|
||||||
|
"value": "oss",
|
||||||
|
"type": 0,
|
||||||
|
"color": "success",
|
||||||
|
"is_value": true,
|
||||||
|
"status": true,
|
||||||
|
"sort": 2,
|
||||||
|
"remark": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "腾讯cos",
|
||||||
|
"value": "cos",
|
||||||
|
"type": 0,
|
||||||
|
"color": "warning",
|
||||||
|
"is_value": true,
|
||||||
|
"status": true,
|
||||||
|
"sort": 3,
|
||||||
|
"remark": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -235,5 +235,252 @@
|
|||||||
"children": []
|
"children": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"title": "文件存储配置",
|
||||||
|
"key": "file_storage",
|
||||||
|
"value": null,
|
||||||
|
"sort": 0,
|
||||||
|
"status": true,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": null,
|
||||||
|
"placeholder": null,
|
||||||
|
"setting": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"title": "存储引擎",
|
||||||
|
"key": "file_engine",
|
||||||
|
"value": "local",
|
||||||
|
"sort": 1,
|
||||||
|
"status": true,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 4,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请选择存储引擎",
|
||||||
|
"setting": "file_engine",
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "文件是否备份",
|
||||||
|
"key": "file_backup",
|
||||||
|
"value": false,
|
||||||
|
"sort": 2,
|
||||||
|
"status": true,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 9,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "启用云存储时,文件是否备份到本地",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-AccessKey",
|
||||||
|
"key": "aliyun_access_key",
|
||||||
|
"value": null,
|
||||||
|
"sort": 3,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入AccessKey",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-Secret",
|
||||||
|
"key": "aliyun_access_secret",
|
||||||
|
"value": null,
|
||||||
|
"sort": 4,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Secret",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-Endpoint",
|
||||||
|
"key": "aliyun_endpoint",
|
||||||
|
"value": null,
|
||||||
|
"sort": 5,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Endpoint",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-上传路径",
|
||||||
|
"key": "aliyun_path",
|
||||||
|
"value": "/media/",
|
||||||
|
"sort": 5,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入上传路径",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-Bucket",
|
||||||
|
"key": "aliyun_bucket",
|
||||||
|
"value": null,
|
||||||
|
"sort": 7,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Bucket",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},{
|
||||||
|
"title": "阿里云-cdn地址",
|
||||||
|
"key": "aliyun_cdn_url",
|
||||||
|
"value": null,
|
||||||
|
"sort": 7,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入cdn地址",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-SecretId",
|
||||||
|
"key": "tencent_secret_id",
|
||||||
|
"value": null,
|
||||||
|
"sort": 8,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入SecretId",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-SecretKey",
|
||||||
|
"key": "tencent_secret_key",
|
||||||
|
"value": null,
|
||||||
|
"sort": 9,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入SecretKey",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-Region",
|
||||||
|
"key": "tencent_region",
|
||||||
|
"value": null,
|
||||||
|
"sort": 10,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Region",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-Bucket",
|
||||||
|
"key": "tencent_bucket",
|
||||||
|
"value": null,
|
||||||
|
"sort": 11,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Bucket",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-上传路径",
|
||||||
|
"key": "tencent_path",
|
||||||
|
"value": "/media/",
|
||||||
|
"sort": 12,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入上传路径",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
@@ -29,7 +29,7 @@ class Command(BaseCommand):
|
|||||||
def serializer_data(self, serializer, query_set: QuerySet):
|
def serializer_data(self, serializer, query_set: QuerySet):
|
||||||
serializer = serializer(query_set, many=True)
|
serializer = serializer(query_set, many=True)
|
||||||
data = json.loads(json.dumps(serializer.data, ensure_ascii=False))
|
data = json.loads(json.dumps(serializer.data, ensure_ascii=False))
|
||||||
with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w') as f:
|
with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w',encoding='utf-8') as f:
|
||||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|||||||
from application import dispatch
|
from application import dispatch
|
||||||
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
|
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
|
||||||
|
|
||||||
from dvadmin3_flow.base_model import FlowBaseModel
|
|
||||||
class Role(CoreModel,FlowBaseModel):
|
class Role(CoreModel):
|
||||||
name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称")
|
name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称")
|
||||||
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
|
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
|
||||||
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
|
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
|
||||||
@@ -77,7 +77,13 @@ class Users(CoreModel, AbstractUser):
|
|||||||
objects = CustomUserManager()
|
objects = CustomUserManager()
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
|
if raw_password:
|
||||||
|
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.name == "":
|
||||||
|
self.name = self.username
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "system_users"
|
db_table = table_prefix + "system_users"
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
from django.dispatch import Signal
|
import time
|
||||||
|
|
||||||
|
from django.db.models.signals import post_save, post_delete
|
||||||
|
from django.dispatch import Signal, receiver
|
||||||
|
from django.core.cache import cache
|
||||||
|
from dvadmin.system.models import MessageCenterTargetUser
|
||||||
|
|
||||||
# 初始化信号
|
# 初始化信号
|
||||||
pre_init_complete = Signal()
|
pre_init_complete = Signal()
|
||||||
detail_init_complete = Signal()
|
detail_init_complete = Signal()
|
||||||
@@ -10,3 +16,12 @@ post_tenants_init_complete = Signal()
|
|||||||
post_tenants_all_init_complete = Signal()
|
post_tenants_all_init_complete = Signal()
|
||||||
# 租户创建完成信号
|
# 租户创建完成信号
|
||||||
tenants_create_complete = Signal()
|
tenants_create_complete = Signal()
|
||||||
|
|
||||||
|
# 全局变量用于标记最后修改时间
|
||||||
|
last_db_change_time = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=MessageCenterTargetUser)
|
||||||
|
@receiver(post_delete, sender=MessageCenterTargetUser)
|
||||||
|
def update_last_change_time(sender, **kwargs):
|
||||||
|
cache.set('last_db_change_time', time.time(), timeout=None) # 设置永不超时的键值对
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ class DownloadCenterViewSet(CustomModelViewSet):
|
|||||||
extra_filter_class = []
|
extra_filter_class = []
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
# 判断是否是 Swagger 文档生成阶段,防止报错
|
||||||
|
if getattr(self, 'swagger_fake_view', False):
|
||||||
|
return self.queryset.model.objects.none()
|
||||||
|
|
||||||
|
# 正常请求下的逻辑
|
||||||
if self.request.user.is_superuser:
|
if self.request.user.is_superuser:
|
||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
return super().get_queryset().filter(creator=self.request.user)
|
return super().get_queryset().filter(creator=self.request.user)
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ class FileSerializer(CustomModelSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local'
|
file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
|
||||||
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")
|
file_backup = dispatch.get_system_config_values("file_storage.file_backup")
|
||||||
file = self.initial_data.get('file')
|
file = self.initial_data.get('file')
|
||||||
file_size = file.size
|
file_size = file.size
|
||||||
validated_data['name'] = str(file)
|
validated_data['name'] = str(file)
|
||||||
@@ -52,15 +52,15 @@ class FileSerializer(CustomModelSerializer):
|
|||||||
if file_backup:
|
if file_backup:
|
||||||
validated_data['url'] = file
|
validated_data['url'] = file
|
||||||
if file_engine == 'oss':
|
if file_engine == 'oss':
|
||||||
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
|
from dvadmin.utils.aliyunoss import ali_oss_upload
|
||||||
file_path = ali_oss_upload(file)
|
file_path = ali_oss_upload(file, file_name=validated_data['name'])
|
||||||
if file_path:
|
if file_path:
|
||||||
validated_data['file_url'] = file_path
|
validated_data['file_url'] = file_path
|
||||||
else:
|
else:
|
||||||
raise ValueError("上传失败")
|
raise ValueError("上传失败")
|
||||||
elif file_engine == 'cos':
|
elif file_engine == 'cos':
|
||||||
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
|
from dvadmin.utils.tencentcos import tencent_cos_upload
|
||||||
file_path = tencent_cos_upload(file)
|
file_path = tencent_cos_upload(file, file_name=validated_data['name'])
|
||||||
if file_path:
|
if file_path:
|
||||||
validated_data['file_url'] = file_path
|
validated_data['file_url'] = file_path
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class MessageCenterSerializer(CustomModelSerializer):
|
|||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
def get_user_info(self, instance, parsed_query):
|
def get_user_info(self, instance, parsed_query):
|
||||||
if instance.target_type in (1,2,3):
|
if instance.target_type in (1, 2, 3):
|
||||||
return []
|
return []
|
||||||
users = instance.target_user.all()
|
users = instance.target_user.all()
|
||||||
# You can do what ever you want in here
|
# You can do what ever you want in here
|
||||||
@@ -108,7 +108,7 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
|||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
def get_user_info(self, instance, parsed_query):
|
def get_user_info(self, instance, parsed_query):
|
||||||
if instance.target_type in (1,2,3):
|
if instance.target_type in (1, 2, 3):
|
||||||
return []
|
return []
|
||||||
users = instance.target_user.all()
|
users = instance.target_user.all()
|
||||||
# You can do what ever you want in here
|
# You can do what ever you want in here
|
||||||
@@ -139,21 +139,6 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
|||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
def websocket_push(user_id, message):
|
|
||||||
"""
|
|
||||||
主动推送消息
|
|
||||||
"""
|
|
||||||
username = "user_" + str(user_id)
|
|
||||||
channel_layer = get_channel_layer()
|
|
||||||
async_to_sync(channel_layer.group_send)(
|
|
||||||
username,
|
|
||||||
{
|
|
||||||
"type": "push.message",
|
|
||||||
"json": message
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MessageCenterCreateSerializer(CustomModelSerializer):
|
class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
消息中心-新增-序列化器
|
消息中心-新增-序列化器
|
||||||
@@ -182,10 +167,6 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
|
|||||||
targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=self.request)
|
targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=self.request)
|
||||||
targetuser_instance.is_valid(raise_exception=True)
|
targetuser_instance.is_valid(raise_exception=True)
|
||||||
targetuser_instance.save()
|
targetuser_instance.save()
|
||||||
for user in users:
|
|
||||||
unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count()
|
|
||||||
websocket_push(user, message={"sender": 'system', "contentType": 'SYSTEM',
|
|
||||||
"content": '您有一条新消息~', "unread": unread_count})
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -225,10 +206,6 @@ class MessageCenterViewSet(CustomModelViewSet):
|
|||||||
queryset.save()
|
queryset.save()
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
serializer = self.get_serializer(instance)
|
serializer = self.get_serializer(instance)
|
||||||
# 主动推送消息
|
|
||||||
unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count()
|
|
||||||
websocket_push(user_id, message={"sender": 'system', "contentType": 'TEXT',
|
|
||||||
"content": '您查看了一条消息~', "unread": unread_count})
|
|
||||||
return DetailResponse(data=serializer.data, msg="获取成功")
|
return DetailResponse(data=serializer.data, msg="获取成功")
|
||||||
|
|
||||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
|||||||
62
backend/dvadmin/utils/aliyunoss.py
Normal file
62
backend/dvadmin/utils/aliyunoss.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import oss2
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from application import dispatch
|
||||||
|
|
||||||
|
|
||||||
|
# 进度条
|
||||||
|
# 当无法确定待上传的数据长度时,total_bytes的值为None。
|
||||||
|
def percentage(consumed_bytes, total_bytes):
|
||||||
|
if total_bytes:
|
||||||
|
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
|
||||||
|
print('\r{0}% '.format(rate), end='')
|
||||||
|
|
||||||
|
|
||||||
|
def ali_oss_upload(file, file_name):
|
||||||
|
"""
|
||||||
|
阿里云OSS上传
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
file.seek(0)
|
||||||
|
file_read = file.read()
|
||||||
|
except Exception as e:
|
||||||
|
file_read = file
|
||||||
|
if not file:
|
||||||
|
raise ValidationError('请上传文件')
|
||||||
|
# 转存到oss
|
||||||
|
path_prefix = dispatch.get_system_config_values("file_storage.aliyun_path")
|
||||||
|
if not path_prefix.endswith('/'):
|
||||||
|
path_prefix = path_prefix + '/'
|
||||||
|
if path_prefix.startswith('/'):
|
||||||
|
path_prefix = path_prefix[1:]
|
||||||
|
base_fil_name = f'{path_prefix}{file_name}'
|
||||||
|
# 获取OSS配置
|
||||||
|
# 获取的AccessKey
|
||||||
|
access_key_id = dispatch.get_system_config_values("file_storage.aliyun_access_key")
|
||||||
|
access_key_secret = dispatch.get_system_config_values("file_storage.aliyun_access_secret")
|
||||||
|
auth = oss2.Auth(access_key_id, access_key_secret)
|
||||||
|
# 这个是需要用特定的地址,不同地域的服务器地址不同,不要弄错了
|
||||||
|
# 参考官网给的地址配置https://www.alibabacloud.com/help/zh/object-storage-service/latest/regions-and-endpoints#concept-zt4-cvy-5db
|
||||||
|
endpoint = dispatch.get_system_config_values("file_storage.aliyun_endpoint")
|
||||||
|
bucket_name = dispatch.get_system_config_values("file_storage.aliyun_bucket")
|
||||||
|
if bucket_name.endswith(endpoint):
|
||||||
|
bucket_name = bucket_name.replace(f'.{endpoint}', '')
|
||||||
|
# 你的项目名称,类似于不同的项目上传的图片前缀url不同
|
||||||
|
bucket = oss2.Bucket(auth, endpoint, bucket_name) # 项目名称
|
||||||
|
# 生成外网访问的文件路径
|
||||||
|
aliyun_cdn_url = dispatch.get_system_config_values("file_storage.aliyun_cdn_url")
|
||||||
|
if aliyun_cdn_url:
|
||||||
|
if aliyun_cdn_url.endswith('/'):
|
||||||
|
aliyun_cdn_url = aliyun_cdn_url[1:]
|
||||||
|
file_path = f"{aliyun_cdn_url}/{base_fil_name}"
|
||||||
|
else:
|
||||||
|
file_path = f"https://{bucket_name}.{endpoint}/{base_fil_name}"
|
||||||
|
# 这个是阿里提供的SDK方法
|
||||||
|
res = bucket.put_object(base_fil_name, file_read, progress_callback=percentage)
|
||||||
|
# 如果上传状态是200 代表成功 返回文件外网访问路径
|
||||||
|
if res.status == 200:
|
||||||
|
return file_path
|
||||||
|
else:
|
||||||
|
return None
|
||||||
@@ -81,6 +81,26 @@ class SoftDeleteModel(models.Model):
|
|||||||
super().delete(using=using, *args, **kwargs)
|
super().delete(using=using, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreModelManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
is_deleted = getattr(self.model, 'is_soft_delete', False)
|
||||||
|
flow_work_status = getattr(self.model, 'flow_work_status', False)
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
if flow_work_status:
|
||||||
|
queryset = queryset.filter(flow_work_status=1)
|
||||||
|
if is_deleted:
|
||||||
|
queryset = queryset.filter(is_deleted=False)
|
||||||
|
return queryset
|
||||||
|
def create(self,request: Request=None, **kwargs):
|
||||||
|
data = {**kwargs}
|
||||||
|
if request:
|
||||||
|
request_user = request.user
|
||||||
|
data["creator"] = request_user
|
||||||
|
data["modifier"] = request_user.id
|
||||||
|
data["dept_belong_id"] = request_user.dept_id
|
||||||
|
# 调用父类的create方法执行实际的创建操作
|
||||||
|
return super().create(**data)
|
||||||
|
|
||||||
class CoreModel(models.Model):
|
class CoreModel(models.Model):
|
||||||
"""
|
"""
|
||||||
核心标准抽象模型模型,可直接继承使用
|
核心标准抽象模型模型,可直接继承使用
|
||||||
@@ -98,7 +118,8 @@ class CoreModel(models.Model):
|
|||||||
verbose_name="修改时间")
|
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="创建时间")
|
||||||
|
objects = CoreModelManager()
|
||||||
|
all_objects = models.Manager()
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
verbose_name = '核心模型'
|
verbose_name = '核心模型'
|
||||||
|
|||||||
56
backend/dvadmin/utils/tencentcos.py
Normal file
56
backend/dvadmin/utils/tencentcos.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from application import dispatch
|
||||||
|
from qcloud_cos import CosConfig
|
||||||
|
from qcloud_cos import CosS3Client
|
||||||
|
|
||||||
|
|
||||||
|
# 进度条
|
||||||
|
# 当无法确定待上传的数据长度时,total_bytes的值为None。
|
||||||
|
def percentage(consumed_bytes, total_bytes):
|
||||||
|
if total_bytes:
|
||||||
|
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
|
||||||
|
print('\r{0}% '.format(rate), end='')
|
||||||
|
|
||||||
|
def tencent_cos_upload(file, file_name):
|
||||||
|
try:
|
||||||
|
file.seek(0)
|
||||||
|
file_read = file.read()
|
||||||
|
except Exception as e:
|
||||||
|
file_read = file
|
||||||
|
if not file:
|
||||||
|
raise ValidationError('请上传文件')
|
||||||
|
# 生成文件名
|
||||||
|
path_prefix = dispatch.get_system_config_values("file_storage.tencent_path")
|
||||||
|
if not path_prefix.endswith('/'):
|
||||||
|
path_prefix = path_prefix + '/'
|
||||||
|
if path_prefix.startswith('/'):
|
||||||
|
path_prefix = path_prefix[1:]
|
||||||
|
base_fil_name = f'{path_prefix}{file_name}'
|
||||||
|
# 获取cos配置
|
||||||
|
# 1. 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成
|
||||||
|
secret_id = dispatch.get_system_config_values("file_storage.tencent_secret_id") # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
|
||||||
|
secret_key = dispatch.get_system_config_values("file_storage.tencent_secret_key") # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
|
||||||
|
region = dispatch.get_system_config_values("file_storage.tencent_region") # 替换为用户的 region,已创建桶归属的 region 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket # COS 支持的所有 region 列表参见https://cloud.tencent.com/document/product/436/6224
|
||||||
|
bucket = dispatch.get_system_config_values("file_storage.tencent_bucket") # 要访问的桶名称
|
||||||
|
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
|
||||||
|
client = CosS3Client(config)
|
||||||
|
# 访问地址
|
||||||
|
base_file_url = f'https://{bucket}.cos.{region}.myqcloud.com'
|
||||||
|
# 生成外网访问的文件路径
|
||||||
|
if base_file_url.endswith('/'):
|
||||||
|
file_path = base_file_url + base_fil_name
|
||||||
|
else:
|
||||||
|
file_path = f'{base_file_url}/{base_fil_name}'
|
||||||
|
# 这个是阿里提供的SDK方法 bucket是调用的4.1中配置的变量名
|
||||||
|
try:
|
||||||
|
response = client.put_object(
|
||||||
|
Bucket=bucket,
|
||||||
|
Body=file_read,
|
||||||
|
Key=base_fil_name,
|
||||||
|
EnableMD5=False
|
||||||
|
)
|
||||||
|
return file_path
|
||||||
|
except:
|
||||||
|
return None
|
||||||
@@ -7,26 +7,27 @@ djangorestframework==3.15.2
|
|||||||
django-restql==0.15.4
|
django-restql==0.15.4
|
||||||
django-simple-captcha==0.6.0
|
django-simple-captcha==0.6.0
|
||||||
django-timezone-field==7.0
|
django-timezone-field==7.0
|
||||||
djangorestframework-simplejwt==5.3.1
|
djangorestframework_simplejwt==5.4.0
|
||||||
drf-yasg==1.21.7
|
drf-yasg==1.21.7
|
||||||
mysqlclient==2.2.0
|
mysqlclient==2.2.0
|
||||||
pypinyin==0.51.0
|
pypinyin==0.51.0
|
||||||
ua-parser==0.18.0
|
ua-parser==0.18.0
|
||||||
pyparsing==3.1.2
|
pyparsing==3.1.2
|
||||||
openpyxl==3.1.5
|
openpyxl==3.1.5
|
||||||
requests==2.32.3
|
requests==2.32.4
|
||||||
typing-extensions==4.12.2
|
typing-extensions==4.12.2
|
||||||
tzlocal==5.2
|
tzlocal==5.2
|
||||||
channels==4.1.0
|
channels==4.1.0
|
||||||
channels-redis==4.2.0
|
channels-redis==4.2.0
|
||||||
websockets==11.0.3
|
|
||||||
user-agents==2.2.0
|
user-agents==2.2.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
whitenoise==6.7.0
|
whitenoise==6.7.0
|
||||||
psycopg2==2.9.9
|
psycopg2==2.9.9
|
||||||
uvicorn==0.30.3
|
uvicorn==0.30.3
|
||||||
gunicorn==22.0.0
|
gunicorn==23.0.0
|
||||||
gevent==24.2.1
|
gevent==24.2.1
|
||||||
Pillow==10.4.0
|
Pillow==10.4.0
|
||||||
pyinstaller==6.9.0
|
pyinstaller==6.9.0
|
||||||
dvadmin3-celery==3.1.6
|
dvadmin3-celery==3.1.6
|
||||||
|
oss2==2.19.1
|
||||||
|
cos-python-sdk-v5==1.9.37
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
ENV = 'development'
|
ENV = 'development'
|
||||||
|
|
||||||
# 本地环境接口地址
|
# 本地环境接口地址
|
||||||
VITE_API_URL = 'http://127.0.0.1:8001'
|
VITE_API_URL = 'http://127.0.0.1:8000'
|
||||||
|
|
||||||
# 是否启用按钮权限
|
# 是否启用按钮权限
|
||||||
VITE_PM_ENABLED = true
|
VITE_PM_ENABLED = true
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ const route = useRoute();
|
|||||||
const stores = useTagsViewRoutes();
|
const stores = useTagsViewRoutes();
|
||||||
const storesThemeConfig = useThemeConfig();
|
const storesThemeConfig = useThemeConfig();
|
||||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||||
import websocket from '/@/utils/websocket';
|
|
||||||
const core = useCore();
|
const core = useCore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// 获取版本号
|
// 获取版本号
|
||||||
@@ -92,63 +91,5 @@ onMounted(() => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
mittBus.off('openSetingsDrawer', () => {});
|
mittBus.off('openSetingsDrawer', () => {});
|
||||||
});
|
});
|
||||||
// 监听路由的变化,设置网站标题
|
|
||||||
watch(
|
|
||||||
() => route.path,
|
|
||||||
() => {
|
|
||||||
other.useTitle();
|
|
||||||
other.useFavicon();
|
|
||||||
if (!websocket.websocket) {
|
|
||||||
//websockt 模块
|
|
||||||
try {
|
|
||||||
websocket.init(wsReceive)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('websocket错误');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// websocket相关代码
|
|
||||||
import { messageCenterStore } from '/@/stores/messageCenter';
|
|
||||||
const wsReceive = (message: any) => {
|
|
||||||
const data = JSON.parse(message.data);
|
|
||||||
const { unread } = data;
|
|
||||||
const messageCenter = messageCenterStore();
|
|
||||||
messageCenter.setUnread(unread);
|
|
||||||
if (data.contentType === 'SYSTEM') {
|
|
||||||
ElNotification({
|
|
||||||
title: '系统消息',
|
|
||||||
message: data.content,
|
|
||||||
type: 'success',
|
|
||||||
position: 'bottom-right',
|
|
||||||
duration: 5000,
|
|
||||||
});
|
|
||||||
} else if (data.contentType === 'Content') {
|
|
||||||
ElMessageBox.confirm(data.content, data.notificationTitle, {
|
|
||||||
confirmButtonText: data.notificationButton,
|
|
||||||
dangerouslyUseHTMLString: true,
|
|
||||||
cancelButtonText: '关闭',
|
|
||||||
type: 'info',
|
|
||||||
closeOnClickModal: false,
|
|
||||||
}).then(() => {
|
|
||||||
ElMessageBox.close();
|
|
||||||
const path = data.path;
|
|
||||||
if (route.path === path) {
|
|
||||||
core.bus.emit('onNewTask', { name: 'onNewTask' });
|
|
||||||
} else {
|
|
||||||
router.push({ path});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
// 关闭连接
|
|
||||||
websocket.close();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
class="tableSelector"
|
class="tableSelector"
|
||||||
multiple
|
multiple
|
||||||
:collapseTags="props.tableConfig.collapseTags"
|
:collapseTags="props.tableConfig.collapseTags"
|
||||||
@remove-tag="removeTag"
|
|
||||||
v-model="data"
|
v-model="data"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
@visible-change="visibleChange"
|
@visible-change="visibleChange"
|
||||||
@@ -29,9 +28,9 @@
|
|||||||
max-height="200"
|
max-height="200"
|
||||||
height="200"
|
height="200"
|
||||||
:highlight-current-row="!props.tableConfig.isMultiple"
|
:highlight-current-row="!props.tableConfig.isMultiple"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@select="handleSelectionChange"
|
@select="handleSelectionChange"
|
||||||
@selectAll="handleSelectionChange"
|
@selectAll="handleSelectionChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
<el-table-column v-if="props.tableConfig.isMultiple" fixed type="selection" reserve-selection width="55" />
|
<el-table-column v-if="props.tableConfig.isMultiple" fixed type="selection" reserve-selection width="55" />
|
||||||
@@ -59,34 +58,36 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, defineProps, onMounted, reactive, ref, watch} from 'vue';
|
import { computed, defineProps, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import XEUtils from 'xe-utils';
|
import XEUtils from 'xe-utils';
|
||||||
import { request } from '/@/utils/service';
|
import { request } from '/@/utils/service';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array || String || Number,
|
type: Array || String || Number,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
tableConfig: {
|
tableConfig: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default:{
|
default: {
|
||||||
url: null,
|
url: null,
|
||||||
label: null, //显示值
|
label: null, //显示值
|
||||||
value: null, //数据值
|
value: null, //数据值
|
||||||
isTree: false,
|
isTree: false,
|
||||||
lazy: true,
|
lazy: true,
|
||||||
size:'default',
|
size: 'default',
|
||||||
load: () => {},
|
load: () => {},
|
||||||
data: [], //默认数据
|
data: [], //默认数据
|
||||||
isMultiple: false, //是否多选
|
isMultiple: false, //是否多选
|
||||||
collapseTags:false,
|
collapseTags: false,
|
||||||
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
||||||
columns: [], //每一项对应的列表项
|
columns: [], //每一项对应的列表项
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
displayLabel: {},
|
displayLabel: {},
|
||||||
} as any);
|
} as any);
|
||||||
|
console.log(props.tableConfig);
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
// tableRef
|
// tableRef
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
@@ -137,6 +138,8 @@ const handleCurrentChange = (val: any) => {
|
|||||||
*/
|
*/
|
||||||
const getDict = async () => {
|
const getDict = async () => {
|
||||||
const url = props.tableConfig.url;
|
const url = props.tableConfig.url;
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
page: pageConfig.page,
|
page: pageConfig.page,
|
||||||
limit: pageConfig.limit,
|
limit: pageConfig.limit,
|
||||||
@@ -162,29 +165,25 @@ const getDict = async () => {
|
|||||||
|
|
||||||
// 获取节点值
|
// 获取节点值
|
||||||
const getNodeValues = () => {
|
const getNodeValues = () => {
|
||||||
|
console.log(props.tableConfig.url);
|
||||||
|
|
||||||
request({
|
request({
|
||||||
url:props.tableConfig.valueUrl,
|
url: props.tableConfig.url,
|
||||||
method:'post',
|
method: 'post',
|
||||||
data:{ids:props.modelValue}
|
data: { ids: props.modelValue },
|
||||||
}).then(res=>{
|
}).then((res) => {
|
||||||
if(res.data.length>0){
|
if (res.data.length > 0) {
|
||||||
data.value = res.data.map((item:any)=>{
|
data.value = res.data.map((item: any) => {
|
||||||
return item[props.tableConfig.label]
|
return item[props.tableConfig.label];
|
||||||
})
|
});
|
||||||
|
|
||||||
tableRef.value!.clearSelection()
|
|
||||||
res.data.forEach((row) => {
|
|
||||||
tableRef.value!.toggleRowSelection(
|
|
||||||
row,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
tableRef.value!.clearSelection();
|
||||||
|
res.data.forEach((row) => {
|
||||||
|
tableRef.value!.toggleRowSelection(row, true, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下拉框展开/关闭
|
* 下拉框展开/关闭
|
||||||
@@ -205,12 +204,11 @@ const handlePageChange = (page: any) => {
|
|||||||
getDict();
|
getDict();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(()=>{
|
onMounted(() => {
|
||||||
setTimeout(()=>{
|
// setTimeout(() => {
|
||||||
getNodeValues()
|
// getNodeValues();
|
||||||
},1000)
|
// }, 1000);
|
||||||
})
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -57,33 +57,14 @@
|
|||||||
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
|
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div></div>
|
||||||
<span v-if="!isSocketOpen" class="online-status-span">
|
|
||||||
<el-popconfirm
|
|
||||||
width="250"
|
|
||||||
ref="onlinePopoverRef"
|
|
||||||
:confirm-button-text="$t('message.user.retry')"
|
|
||||||
:icon="InfoFilled"
|
|
||||||
trigger="hover"
|
|
||||||
icon-color="#626AEF"
|
|
||||||
:title="$t('message.user.onlinePrompt')"
|
|
||||||
@confirm="onlineConfirmEvent"
|
|
||||||
>
|
|
||||||
<template #reference>
|
|
||||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
|
||||||
<img :src="getBaseURL(userInfos.avatar) || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
|
||||||
</el-badge>
|
|
||||||
</template>
|
|
||||||
</el-popconfirm>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||||
<span class="layout-navbars-breadcrumb-user-link">
|
<span class="layout-navbars-breadcrumb-user-link">
|
||||||
<span v-if="isSocketOpen">
|
<span v-if="isSocketOpen">
|
||||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
<el-badge is-dot class="item" :class="{ 'online-status': isSocketOpen, 'online-down': !isSocketOpen }">
|
||||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||||
</el-badge>
|
</el-badge>
|
||||||
</span>
|
</span>
|
||||||
{{ userInfos.username === '' ? 'common' : userInfos.username }}
|
{{ userInfos.username === '' ? 'common' : userInfos.username }}
|
||||||
<el-icon class="el-icon--right">
|
<el-icon class="el-icon--right">
|
||||||
<ele-ArrowDown />
|
<ele-ArrowDown />
|
||||||
@@ -115,8 +96,7 @@ import other from '/@/utils/other';
|
|||||||
import mittBus from '/@/utils/mitt';
|
import mittBus from '/@/utils/mitt';
|
||||||
import { Session, Local } from '/@/utils/storage';
|
import { Session, Local } from '/@/utils/storage';
|
||||||
import headerImage from '/@/assets/img/headerImage.png';
|
import headerImage from '/@/assets/img/headerImage.png';
|
||||||
import websocket from '/@/utils/websocket';
|
import { InfoFilled } from '@element-plus/icons-vue';
|
||||||
import { InfoFilled } from '@element-plus/icons-vue'
|
|
||||||
// 引入组件
|
// 引入组件
|
||||||
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
|
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
|
||||||
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
||||||
@@ -148,17 +128,6 @@ const layoutUserFlexNum = computed(() => {
|
|||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const { isSocketOpen } = storeToRefs(useUserInfo());
|
const { isSocketOpen } = storeToRefs(useUserInfo());
|
||||||
|
|
||||||
// websocket状态
|
|
||||||
const onlinePopoverRef = ref()
|
|
||||||
const onlineConfirmEvent = () => {
|
|
||||||
if (!isSocketOpen.value) {
|
|
||||||
websocket.is_reonnect = true
|
|
||||||
websocket.reconnect_current = 1
|
|
||||||
websocket.reconnect()
|
|
||||||
}
|
|
||||||
// 手动隐藏弹出
|
|
||||||
unref(onlinePopoverRef).popperRef?.delayHide?.()
|
|
||||||
}
|
|
||||||
// 全屏点击时
|
// 全屏点击时
|
||||||
const onScreenfullClick = () => {
|
const onScreenfullClick = () => {
|
||||||
if (!screenfull.isEnabled) {
|
if (!screenfull.isEnabled) {
|
||||||
@@ -237,8 +206,10 @@ const onLanguageChange = (lang: string) => {
|
|||||||
initI18nOrSize('globalI18n', 'disabledI18n');
|
initI18nOrSize('globalI18n', 'disabledI18n');
|
||||||
};
|
};
|
||||||
// 初始化组件大小/i18n
|
// 初始化组件大小/i18n
|
||||||
const initI18nOrSize = (value: string, attr: string) => {
|
const initI18nOrSize = (value: string, attr: keyof typeof state) => {
|
||||||
state[attr] = Local.get('themeConfig')[value];
|
const themeConfig = Local.get('themeConfig') as { [key: string]: any } | null;
|
||||||
|
const configValue = ((themeConfig && themeConfig[value]) as string) || '';
|
||||||
|
state[attr] = configValue as unknown as never;
|
||||||
};
|
};
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -246,12 +217,32 @@ onMounted(() => {
|
|||||||
initI18nOrSize('globalComponentSize', 'disabledSize');
|
initI18nOrSize('globalComponentSize', 'disabledSize');
|
||||||
initI18nOrSize('globalI18n', 'disabledI18n');
|
initI18nOrSize('globalI18n', 'disabledI18n');
|
||||||
}
|
}
|
||||||
|
getMessageCenterCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
//消息中心的未读数量
|
//消息中心的未读数量
|
||||||
import { messageCenterStore } from '/@/stores/messageCenter';
|
import { messageCenterStore } from '/@/stores/messageCenter';
|
||||||
import {getBaseURL} from "/@/utils/baseUrl";
|
import { getBaseURL } from '/@/utils/baseUrl';
|
||||||
const messageCenter = messageCenterStore();
|
const messageCenter = messageCenterStore();
|
||||||
|
let eventSource: EventSource | null = null; // 存储 EventSource 实例
|
||||||
|
const token = Session.get('token');
|
||||||
|
const getMessageCenterCount = () => {
|
||||||
|
// 创建 EventSource 实例并连接到后端 SSE 端点
|
||||||
|
eventSource = new EventSource(`${getBaseURL()}/sse/?token=${token}`); // 替换为你的后端地址
|
||||||
|
|
||||||
|
// 监听消息事件
|
||||||
|
eventSource.onmessage = function (event) {
|
||||||
|
messageCenter.setUnread(+event.data); // 更新总记录数
|
||||||
|
};
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
eventSource.onerror = function (err) {
|
||||||
|
console.error('SSE 错误:', err);
|
||||||
|
if (eventSource !== null && eventSource.readyState === EventSource.CLOSED) {
|
||||||
|
console.log('连接已关闭');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -298,29 +289,29 @@ const messageCenter = messageCenterStore();
|
|||||||
:deep(.el-badge__content.is-fixed) {
|
:deep(.el-badge__content.is-fixed) {
|
||||||
top: 12px;
|
top: 12px;
|
||||||
}
|
}
|
||||||
.online-status{
|
.online-status {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
:deep(.el-badge__content.is-fixed) {
|
:deep(.el-badge__content.is-fixed) {
|
||||||
top: 30px;
|
top: 30px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
left: 5px;
|
left: 5px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #18bc9c;
|
background-color: #18bc9c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.online-down{
|
.online-down {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
:deep(.el-badge__content.is-fixed) {
|
:deep(.el-badge__content.is-fixed) {
|
||||||
top: 30px;
|
top: 30px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
left: 5px;
|
left: 5px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #979b9c;
|
background-color: #979b9c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
<div class="layout-navbars-breadcrumb-user-news">
|
<div class="layout-navbars-breadcrumb-user-news">
|
||||||
<div class="head-box">
|
<div class="head-box">
|
||||||
<div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
|
<div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
|
||||||
<!-- <div class="head-box-btn" v-if="state.newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>-->
|
|
||||||
|
<!-- <div class="head-box-btn" v-if="state.newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="content-box">
|
<div class="content-box">
|
||||||
<template v-if="state.newsList.length > 0">
|
<template v-if="state.newsList.length > 0">
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="layoutBreadcrumbUserNews">
|
<script setup lang="ts" name="layoutBreadcrumbUserNews">
|
||||||
import { reactive,onBeforeMount,ref,onMounted } from 'vue';
|
import { reactive, onBeforeMount, ref, onMounted } from 'vue';
|
||||||
|
|
||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
@@ -33,27 +34,28 @@ const onAllReadClick = () => {
|
|||||||
state.newsList = [];
|
state.newsList = [];
|
||||||
};
|
};
|
||||||
// 前往通知中心点击
|
// 前往通知中心点击
|
||||||
import {useRouter } from "vue-router";
|
import { useRouter } from 'vue-router';
|
||||||
const route = useRouter()
|
const route = useRouter();
|
||||||
const onGoToGiteeClick = () => {
|
const onGoToGiteeClick = () => {
|
||||||
route.push('/messageCenter')
|
route.push('/messageCenter');
|
||||||
};
|
};
|
||||||
//获取最新消息
|
//获取最新消息
|
||||||
import { request } from "/@/utils/service";
|
import { request } from '/@/utils/service';
|
||||||
const getLastMsg= ()=>{
|
const getLastMsg = () => {
|
||||||
request({
|
request({
|
||||||
url: '/api/system/message_center/get_newest_msg/',
|
url: '/api/system/message_center/get_newest_msg/',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {}
|
params: {},
|
||||||
}).then((res:any) => {
|
}).then((res: any) => {
|
||||||
const { data } = res
|
const { data } = res;
|
||||||
state.newsList= [data]
|
console.log(data);
|
||||||
})
|
|
||||||
}
|
|
||||||
onMounted(()=>{
|
|
||||||
getLastMsg()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
state.newsList = [data];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
getLastMsg();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const initElMenuOffsetLeft = () => {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
let els = <HTMLElement>document.querySelector('.el-menu.el-menu--horizontal li.is-active');
|
let els = <HTMLElement>document.querySelector('.el-menu.el-menu--horizontal li.is-active');
|
||||||
if (!els) return false;
|
if (!els) return false;
|
||||||
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = els.offsetLeft;
|
// elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = els.offsetLeft;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 路由过滤递归函数
|
// 路由过滤递归函数
|
||||||
|
|||||||
@@ -98,10 +98,22 @@ export function formatTwoStageRoutes(arr: any) {
|
|||||||
|
|
||||||
const frameOutRoutes = staticRoutes.map(item => item.path)
|
const frameOutRoutes = staticRoutes.map(item => item.path)
|
||||||
|
|
||||||
|
const checkToken = ()=>{
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const _oauth2_token = urlParams.get('_oauth2_token');
|
||||||
|
if (_oauth2_token) {
|
||||||
|
Session.set('token', _oauth2_token);
|
||||||
|
const cleanUrl = window.location.href.split('?')[0];
|
||||||
|
window.history.replaceState({}, '', cleanUrl);
|
||||||
|
useUserInfo(pinia).setUserInfos();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
// 路由加载前
|
// 路由加载前
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
// 检查浏览器本地版本与线上版本是否一致,判断是否需要刷新页面进行更新
|
// 检查浏览器本地版本与线上版本是否一致,判断是否需要刷新页面进行更新
|
||||||
await checkVersion()
|
await checkVersion()
|
||||||
|
checkToken()
|
||||||
NProgress.configure({showSpinner: false});
|
NProgress.configure({showSpinner: false});
|
||||||
if (to.meta.title) NProgress.start();
|
if (to.meta.title) NProgress.start();
|
||||||
const token = Session.get('token');
|
const token = Session.get('token');
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
* 定义接口来定义对象的类型
|
* 定义接口来定义对象的类型
|
||||||
* `stores` 全部类型定义在这里
|
* `stores` 全部类型定义在这里
|
||||||
*/
|
*/
|
||||||
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
|
import { useFrontendMenuStore } from "/@/stores/frontendMenu";
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
export interface UserInfosState {
|
export interface UserInfosState {
|
||||||
|
id: '',
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
is_superuser: boolean,
|
||||||
username: string;
|
username: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
mobile: string;
|
mobile: string;
|
||||||
gender: string;
|
gender: string;
|
||||||
pwd_change_count:null|number;
|
pwd_change_count: null | number;
|
||||||
dept_info: {
|
dept_info: {
|
||||||
dept_id: number;
|
dept_id: number;
|
||||||
dept_name: string;
|
dept_name: string;
|
||||||
@@ -107,9 +109,9 @@ export interface ConfigStates {
|
|||||||
|
|
||||||
export interface FrontendMenu {
|
export interface FrontendMenu {
|
||||||
arrayRouter: Array<any>;
|
arrayRouter: Array<any>;
|
||||||
treeRouter:Array<any>;
|
treeRouter: Array<any>;
|
||||||
|
|
||||||
frameOutRoutes:Array<any>;
|
frameOutRoutes: Array<any>;
|
||||||
|
|
||||||
frameInRoutes:Array<any>;
|
frameInRoutes: Array<any>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import headerImage from '/@/assets/img/headerImage.png';
|
|||||||
export const useUserInfo = defineStore('userInfo', {
|
export const useUserInfo = defineStore('userInfo', {
|
||||||
state: (): UserInfosStates => ({
|
state: (): UserInfosStates => ({
|
||||||
userInfos: {
|
userInfos: {
|
||||||
|
id:'',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
username: '',
|
username: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -19,6 +20,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
mobile: '',
|
mobile: '',
|
||||||
gender: '',
|
gender: '',
|
||||||
pwd_change_count:null,
|
pwd_change_count:null,
|
||||||
|
is_superuser: false,
|
||||||
dept_info: {
|
dept_info: {
|
||||||
dept_id: 0,
|
dept_id: 0,
|
||||||
dept_name: '',
|
dept_name: '',
|
||||||
@@ -37,6 +39,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
this.userInfos.pwd_change_count = count;
|
this.userInfos.pwd_change_count = count;
|
||||||
},
|
},
|
||||||
async updateUserInfos(userInfos:any) {
|
async updateUserInfos(userInfos:any) {
|
||||||
|
this.userInfos.id = userInfos.id;
|
||||||
this.userInfos.username = userInfos.name;
|
this.userInfos.username = userInfos.name;
|
||||||
this.userInfos.avatar = userInfos.avatar;
|
this.userInfos.avatar = userInfos.avatar;
|
||||||
this.userInfos.name = userInfos.name;
|
this.userInfos.name = userInfos.name;
|
||||||
@@ -46,6 +49,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
this.userInfos.dept_info = userInfos.dept_info;
|
this.userInfos.dept_info = userInfos.dept_info;
|
||||||
this.userInfos.role_info = userInfos.role_info;
|
this.userInfos.role_info = userInfos.role_info;
|
||||||
this.userInfos.pwd_change_count = userInfos.pwd_change_count;
|
this.userInfos.pwd_change_count = userInfos.pwd_change_count;
|
||||||
|
this.userInfos.is_superuser = userInfos.is_superuser;
|
||||||
Session.set('userInfo', this.userInfos);
|
Session.set('userInfo', this.userInfos);
|
||||||
},
|
},
|
||||||
async setUserInfos() {
|
async setUserInfos() {
|
||||||
@@ -54,6 +58,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
this.userInfos = Session.get('userInfo');
|
this.userInfos = Session.get('userInfo');
|
||||||
} else {
|
} else {
|
||||||
let userInfos: any = await this.getApiUserInfo();
|
let userInfos: any = await this.getApiUserInfo();
|
||||||
|
this.userInfos.id = userInfos.id;
|
||||||
this.userInfos.username = userInfos.data.name;
|
this.userInfos.username = userInfos.data.name;
|
||||||
this.userInfos.avatar = userInfos.data.avatar;
|
this.userInfos.avatar = userInfos.data.avatar;
|
||||||
this.userInfos.name = userInfos.data.name;
|
this.userInfos.name = userInfos.data.name;
|
||||||
@@ -63,17 +68,16 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
this.userInfos.dept_info = userInfos.data.dept_info;
|
this.userInfos.dept_info = userInfos.data.dept_info;
|
||||||
this.userInfos.role_info = userInfos.data.role_info;
|
this.userInfos.role_info = userInfos.data.role_info;
|
||||||
this.userInfos.pwd_change_count = userInfos.data.pwd_change_count;
|
this.userInfos.pwd_change_count = userInfos.data.pwd_change_count;
|
||||||
|
this.userInfos.is_superuser = userInfos.data.is_superuser;
|
||||||
Session.set('userInfo', this.userInfos);
|
Session.set('userInfo', this.userInfos);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setWebSocketState(socketState: boolean) {
|
|
||||||
this.isSocketOpen = socketState;
|
|
||||||
},
|
|
||||||
async getApiUserInfo() {
|
async getApiUserInfo() {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/system/user/user_info/',
|
url: '/api/system/user/user_info/',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
}).then((res:any)=>{
|
}).then((res:any)=>{
|
||||||
|
this.userInfos.id = res.data.id;
|
||||||
this.userInfos.username = res.data.name;
|
this.userInfos.username = res.data.name;
|
||||||
this.userInfos.avatar = (res.data.avatar && getBaseURL(res.data.avatar)) || headerImage;
|
this.userInfos.avatar = (res.data.avatar && getBaseURL(res.data.avatar)) || headerImage;
|
||||||
this.userInfos.name = res.data.name;
|
this.userInfos.name = res.data.name;
|
||||||
@@ -83,6 +87,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
this.userInfos.dept_info = res.data.dept_info;
|
this.userInfos.dept_info = res.data.dept_info;
|
||||||
this.userInfos.role_info = res.data.role_info;
|
this.userInfos.role_info = res.data.role_info;
|
||||||
this.userInfos.pwd_change_count = res.data.pwd_change_count;
|
this.userInfos.pwd_change_count = res.data.pwd_change_count;
|
||||||
|
this.userInfos.is_superuser = res.data.is_superuser;
|
||||||
Session.set('userInfo', this.userInfos);
|
Session.set('userInfo', this.userInfos);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
.icon-selector-warp-title {
|
.icon-selector-warp-title {
|
||||||
position: absolute;
|
position: relative;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
|
|||||||
@@ -28,3 +28,10 @@ export function getUserInfo() {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getBackends() {
|
||||||
|
return request({
|
||||||
|
url: '/api/dvadmin3_social_oauth2/backend/get_login_backend/',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
139
web/src/views/system/login/component/oauth2.vue
Normal file
139
web/src/views/system/login/component/oauth2.vue
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<div class="other-fast-way" v-if="backends.length">
|
||||||
|
<div class="fast-title"><span>其他快速方式登录</span></div>
|
||||||
|
<ul class="fast-list">
|
||||||
|
<li v-for="(v, k) in backends" :key="v">
|
||||||
|
<a @click.once="handleOAuth2LoginClick(v)" style="width: 50px;color: #18bc9c">
|
||||||
|
<img :src="v.icon" :alt="v.app_name" />
|
||||||
|
<p>{{ v.app_name }}</p>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
|
||||||
|
import * as loginApi from '../api';
|
||||||
|
import { OAuth2Backend } from '/@/views/system/login/types';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'loginOAuth2',
|
||||||
|
setup() {
|
||||||
|
const handleOAuth2LoginClick = (backend: OAuth2Backend) => {
|
||||||
|
history.replaceState(null, '', location.pathname + location.search);
|
||||||
|
window.location.href = backend.authentication_url + '?next=' + window.location.href;
|
||||||
|
};
|
||||||
|
const state = reactive({
|
||||||
|
handleOAuth2LoginClick: handleOAuth2LoginClick,
|
||||||
|
backends: [],
|
||||||
|
});
|
||||||
|
const getBackends = async () => {
|
||||||
|
loginApi.getBackends().then((ret: any) => {
|
||||||
|
state.backends = ret.data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// const handleTreeClick = (record: MenuTreeItemType) => {
|
||||||
|
// menuButtonRef.value?.handleRefreshTable(record);
|
||||||
|
// menuFieldRef.value?.handleRefreshTable(record)
|
||||||
|
// };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getBackends();
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.login-content-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
@for $i from 1 through 4 {
|
||||||
|
.login-animation#{$i} {
|
||||||
|
opacity: 0;
|
||||||
|
animation-name: error-num;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-delay: calc($i/10) + s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-content-code {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-content-submit {
|
||||||
|
width: 100%;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-msg {
|
||||||
|
color: var(--el-text-color-placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.other-fast-way {
|
||||||
|
//height: 240px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
z-index: 1;
|
||||||
|
//display: flex;
|
||||||
|
//align-items: center;
|
||||||
|
//justify-content: center;
|
||||||
|
.fast-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fast-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
li {
|
||||||
|
margin-left: 20px;
|
||||||
|
opacity: 0;
|
||||||
|
animation-name: error-num;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-delay: 0.1s;
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
img {
|
||||||
|
width: 35px;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -34,7 +34,9 @@
|
|||||||
</el-tab-pane> -->
|
</el-tab-pane> -->
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
<!-- <Scan v-if="state.isScan" />-->
|
<OAuth2 />
|
||||||
|
|
||||||
|
<!-- <Scan v-if="state.isScan" />-->
|
||||||
<!-- <div class="login-content-main-sacn" @click="state.isScan = !state.isScan">-->
|
<!-- <div class="login-content-main-sacn" @click="state.isScan = !state.isScan">-->
|
||||||
<!-- <i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>-->
|
<!-- <i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>-->
|
||||||
<!-- <div class="login-content-main-sacn-delta"></div>-->
|
<!-- <div class="login-content-main-sacn-delta"></div>-->
|
||||||
@@ -81,6 +83,8 @@ const Account = defineAsyncComponent(() => import('/@/views/system/login/compone
|
|||||||
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
||||||
const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue'));
|
const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue'));
|
||||||
const ChangePwd = defineAsyncComponent(() => import('/@/views/system/login/component/changePwd.vue'));
|
const ChangePwd = defineAsyncComponent(() => import('/@/views/system/login/component/changePwd.vue'));
|
||||||
|
const OAuth2 = defineAsyncComponent(() => import('/@/views/system/login/component/oauth2.vue'));
|
||||||
|
|
||||||
import _ from "lodash-es";
|
import _ from "lodash-es";
|
||||||
import {useUserInfo} from "/@/stores/userInfo";
|
import {useUserInfo} from "/@/stores/userInfo";
|
||||||
const { userInfos } = storeToRefs(useUserInfo());
|
const { userInfos } = storeToRefs(useUserInfo());
|
||||||
|
|||||||
8
web/src/views/system/login/types.ts
Normal file
8
web/src/views/system/login/types.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
export interface OAuth2Backend {
|
||||||
|
app_name: string;
|
||||||
|
backend_name: string;
|
||||||
|
icon: string;
|
||||||
|
authentication_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -254,6 +254,9 @@ const handleSubmit = () => {
|
|||||||
let res;
|
let res;
|
||||||
menuBtnLoading.value = true;
|
menuBtnLoading.value = true;
|
||||||
if (menuFormData.id) {
|
if (menuFormData.id) {
|
||||||
|
if (menuFormData.parent == undefined) {
|
||||||
|
menuFormData.parent = null
|
||||||
|
}
|
||||||
res = await UpdateObj(menuFormData);
|
res = await UpdateObj(menuFormData);
|
||||||
} else {
|
} else {
|
||||||
res = await AddObj(menuFormData);
|
res = await AddObj(menuFormData);
|
||||||
|
|||||||
@@ -131,10 +131,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
data: [
|
data: [
|
||||||
{ value: 0, label: '按用户' },
|
{ value: 0, label: '按用户' },
|
||||||
{ value: 1, label: '按角色' },
|
{ value: 1, label: '按角色' },
|
||||||
{
|
{ value: 2, label: '按部门' },
|
||||||
value: 2,
|
|
||||||
label: '按部门',
|
|
||||||
},
|
|
||||||
{ value: 3, label: '通知公告' },
|
{ value: 3, label: '通知公告' },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -142,14 +139,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
component: {
|
component: {
|
||||||
optionName: 'el-radio-button',
|
optionName: 'el-radio-button',
|
||||||
},
|
},
|
||||||
rules: [
|
rules: [{ required: true, message: '必选项', trigger: ['blur', 'change'] }],
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '必选项',
|
|
||||||
// @ts-ignore
|
|
||||||
trigger: ['blur', 'change'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
target_user: {
|
target_user: {
|
||||||
@@ -191,10 +181,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
}),
|
}),
|
||||||
rules: [
|
rules: [
|
||||||
// 表单校验规则
|
// 表单校验规则
|
||||||
{
|
{ required: true, message: '必填项' },
|
||||||
required: true,
|
|
||||||
message: '必填项',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
highlight-current
|
highlight-current
|
||||||
show-checkbox
|
show-checkbox
|
||||||
default-expand-all
|
default-expand-all
|
||||||
|
:check-on-click-leaf="false"
|
||||||
>
|
>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dict: dict({
|
dict: dict({
|
||||||
value: dictionary('button_status_bool'),
|
data: dictionary('button_status_bool'),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { computed } from "vue";
|
|||||||
import { Md5 } from 'ts-md5';
|
import { Md5 } from 'ts-md5';
|
||||||
import { commonCrudConfig } from "/@/utils/commonCrud";
|
import { commonCrudConfig } from "/@/utils/commonCrud";
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
import {exportData} from "./api";
|
||||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
@@ -81,9 +82,9 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
title: "导出",//鼠标停留显示的信息
|
title: "导出",//鼠标停留显示的信息
|
||||||
show: auth('user:Export'),
|
show: auth('user:Export'),
|
||||||
click: (ctx: any) => ElMessageBox.confirm(
|
click: (ctx: any) => ElMessageBox.confirm(
|
||||||
'确定重设密码吗?', '提示',
|
'确定导出数据吗?', '提示',
|
||||||
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||||
).then(() => resetToDefaultPasswordRequest(ctx.row))
|
).then(() => exportData(ctx.row))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user