refactor(system): 重构消息中心功能

- 移除 WebSocket相关代码
- 新增 SSE (Server-Sent Events) 实现消息推送
- 优化消息中心未读数量展示和更新逻辑- 调整消息中心相关 API 和前端展示
This commit is contained in:
李小涛
2025-06-22 13:09:49 +08:00
parent fa063a8611
commit e8212501e2
18 changed files with 200 additions and 447 deletions

View File

@@ -8,9 +8,7 @@ https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
@@ -18,15 +16,6 @@ os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
http_application = get_asgi_application()
from application.routing import websocket_urlpatterns
application = ProtocolTypeRouter({
"http": http_application,
'websocket': AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
websocket_urlpatterns # 指明路由文件是devops/routing.py
)
)
),
})

View File

@@ -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 是该路由的消费者
]

View 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

View File

@@ -24,6 +24,7 @@ from rest_framework_simplejwt.views import (
from application import dispatch
from application import settings
from application.sse_views import sse_view
from dvadmin.system.views.dictionary import InitDictionaryViewSet
from dvadmin.system.views.login import (
LoginView,
@@ -40,6 +41,7 @@ dispatch.init_system_config()
dispatch.init_dictionary()
# =========== 初始化系统配置 =================
permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ]
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
@@ -50,7 +52,7 @@ schema_view = get_schema_view(
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(permissions.IsAuthenticated,),
permission_classes=permission_classes,
generator_class=CustomOpenAPISchemaGenerator,
)
# 前端页面映射
@@ -115,6 +117,8 @@ urlpatterns = (
# 前端页面映射
path('web/', web_view, name='web_view'),
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.STATIC_URL, document_root=settings.STATIC_URL)

View File

@@ -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}
}
)

View File

@@ -4,3 +4,7 @@ from django.apps import AppConfig
class SystemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'dvadmin.system'
def ready(self):
# 注册信号
import dvadmin.system.signals # 确保路径正确

View File

@@ -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()
detail_init_complete = Signal()
@@ -10,3 +16,12 @@ post_tenants_init_complete = Signal()
post_tenants_all_init_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) # 设置永不超时的键值对

View File

@@ -44,6 +44,11 @@ class DownloadCenterViewSet(CustomModelViewSet):
extra_filter_class = []
def get_queryset(self):
# 判断是否是 Swagger 文档生成阶段,防止报错
if getattr(self, 'swagger_fake_view', False):
return self.queryset.model.objects.none()
# 正常请求下的逻辑
if self.request.user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(creator=self.request.user)

View File

@@ -36,7 +36,7 @@ class MessageCenterSerializer(CustomModelSerializer):
return serializer.data
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 []
users = instance.target_user.all()
# You can do what ever you want in here
@@ -108,7 +108,7 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
return serializer.data
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 []
users = instance.target_user.all()
# You can do what ever you want in here
@@ -139,21 +139,6 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
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):
"""
消息中心-新增-序列化器
@@ -182,10 +167,6 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
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, message={"sender": 'system', "contentType": 'SYSTEM',
"content": '您有一条新消息~', "unread": unread_count})
return data
class Meta:
@@ -225,10 +206,6 @@ class MessageCenterViewSet(CustomModelViewSet):
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, message={"sender": 'system', "contentType": 'TEXT',
"content": '您查看了一条消息~', "unread": unread_count})
return DetailResponse(data=serializer.data, msg="获取成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])

View File

@@ -7,25 +7,24 @@ djangorestframework==3.15.2
django-restql==0.15.4
django-simple-captcha==0.6.0
django-timezone-field==7.0
djangorestframework-simplejwt==5.3.1
djangorestframework_simplejwt==5.4.0
drf-yasg==1.21.7
mysqlclient==2.2.0
pypinyin==0.51.0
ua-parser==0.18.0
pyparsing==3.1.2
openpyxl==3.1.5
requests==2.32.3
requests==2.32.4
typing-extensions==4.12.2
tzlocal==5.2
channels==4.1.0
channels-redis==4.2.0
websockets==11.0.3
user-agents==2.2.0
six==1.16.0
whitenoise==6.7.0
psycopg2==2.9.9
uvicorn==0.30.3
gunicorn==22.0.0
gunicorn==23.0.0
gevent==24.2.1
Pillow==10.4.0
pyinstaller==6.9.0

View File

@@ -2,7 +2,7 @@
ENV = 'development'
# 本地环境接口地址
VITE_API_URL = 'http://127.0.0.1:8001'
VITE_API_URL = 'http://127.0.0.1:8000'
# 是否启用按钮权限
VITE_PM_ENABLED = true

View File

@@ -35,7 +35,6 @@ const route = useRoute();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
import websocket from '/@/utils/websocket';
const core = useCore();
const router = useRouter();
// 获取版本号
@@ -92,63 +91,5 @@ onMounted(() => {
onUnmounted(() => {
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>

View File

@@ -4,7 +4,6 @@
class="tableSelector"
multiple
:collapseTags="props.tableConfig.collapseTags"
@remove-tag="removeTag"
v-model="data"
placeholder="请选择"
@visible-change="visibleChange"
@@ -59,34 +58,36 @@
</template>
<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 { request } from '/@/utils/service';
const props = defineProps({
modelValue: {
type: Array || String || Number,
default: () => []
default: () => [],
},
tableConfig: {
type: Object,
default:{
default: {
url: null,
label: null, //显示值
value: null, //数据值
isTree: false,
lazy: true,
size:'default',
size: 'default',
load: () => {},
data: [], //默认数据
isMultiple: false, //是否多选
collapseTags:false,
collapseTags: false,
treeProps: { children: 'children', hasChildren: 'hasChildren' },
columns: [], //每一项对应的列表项
},
},
displayLabel: {},
} as any);
console.log(props.tableConfig);
const emit = defineEmits(['update:modelValue']);
// tableRef
const tableRef = ref();
@@ -137,6 +138,8 @@ const handleCurrentChange = (val: any) => {
*/
const getDict = async () => {
const url = props.tableConfig.url;
console.log(url);
const params = {
page: pageConfig.page,
limit: pageConfig.limit,
@@ -162,29 +165,25 @@ const getDict = async () => {
// 获取节点值
const getNodeValues = () => {
console.log(props.tableConfig.url);
request({
url:props.tableConfig.valueUrl,
method:'post',
data:{ids:props.modelValue}
}).then(res=>{
if(res.data.length>0){
data.value = res.data.map((item:any)=>{
return item[props.tableConfig.label]
})
url: props.tableConfig.url,
method: 'post',
data: { ids: props.modelValue },
}).then((res) => {
if (res.data.length > 0) {
data.value = res.data.map((item: any) => {
return item[props.tableConfig.label];
});
tableRef.value!.clearSelection()
tableRef.value!.clearSelection();
res.data.forEach((row) => {
tableRef.value!.toggleRowSelection(
row,
true,
false
)
})
tableRef.value!.toggleRowSelection(row, true, false);
});
}
})
}
});
};
/**
* 下拉框展开/关闭
@@ -205,12 +204,11 @@ const handlePageChange = (page: any) => {
getDict();
};
onMounted(()=>{
setTimeout(()=>{
getNodeValues()
},1000)
})
onMounted(() => {
// setTimeout(() => {
// getNodeValues();
// }, 1000);
});
</script>
<style scoped>

View File

@@ -57,30 +57,11 @@
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</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>
<div></div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link">
<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" />
</el-badge>
</span>
@@ -115,8 +96,7 @@ import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
import { Session, Local } from '/@/utils/storage';
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 Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
@@ -148,17 +128,6 @@ const layoutUserFlexNum = computed(() => {
// 定义变量内容
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 = () => {
if (!screenfull.isEnabled) {
@@ -237,8 +206,10 @@ const onLanguageChange = (lang: string) => {
initI18nOrSize('globalI18n', 'disabledI18n');
};
// 初始化组件大小/i18n
const initI18nOrSize = (value: string, attr: string) => {
state[attr] = Local.get('themeConfig')[value];
const initI18nOrSize = (value: string, attr: keyof typeof state) => {
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(() => {
@@ -246,12 +217,32 @@ onMounted(() => {
initI18nOrSize('globalComponentSize', 'disabledSize');
initI18nOrSize('globalI18n', 'disabledI18n');
}
getMessageCenterCount();
});
//消息中心的未读数量
import { messageCenterStore } from '/@/stores/messageCenter';
import {getBaseURL} from "/@/utils/baseUrl";
import { getBaseURL } from '/@/utils/baseUrl';
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>
<style scoped lang="scss">
@@ -298,7 +289,7 @@ const messageCenter = messageCenterStore();
:deep(.el-badge__content.is-fixed) {
top: 12px;
}
.online-status{
.online-status {
cursor: pointer;
:deep(.el-badge__content.is-fixed) {
top: 30px;
@@ -310,7 +301,7 @@ const messageCenter = messageCenterStore();
background-color: #18bc9c;
}
}
.online-down{
.online-down {
cursor: pointer;
:deep(.el-badge__content.is-fixed) {
top: 30px;

View File

@@ -2,7 +2,8 @@
<div class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<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 class="content-box">
<template v-if="state.newsList.length > 0">
@@ -21,7 +22,7 @@
</template>
<script setup lang="ts" name="layoutBreadcrumbUserNews">
import { reactive,onBeforeMount,ref,onMounted } from 'vue';
import { reactive, onBeforeMount, ref, onMounted } from 'vue';
// 定义变量内容
const state = reactive({
@@ -33,27 +34,28 @@ const onAllReadClick = () => {
state.newsList = [];
};
// 前往通知中心点击
import {useRouter } from "vue-router";
const route = useRouter()
import { useRouter } from 'vue-router';
const route = useRouter();
const onGoToGiteeClick = () => {
route.push('/messageCenter')
route.push('/messageCenter');
};
//获取最新消息
import { request } from "/@/utils/service";
const getLastMsg= ()=>{
import { request } from '/@/utils/service';
const getLastMsg = () => {
request({
url: '/api/system/message_center/get_newest_msg/',
method: 'get',
params: {}
}).then((res:any) => {
const { data } = res
state.newsList= [data]
})
}
onMounted(()=>{
getLastMsg()
})
params: {},
}).then((res: any) => {
const { data } = res;
console.log(data);
state.newsList = [data];
});
};
onMounted(() => {
getLastMsg();
});
</script>
<style scoped lang="scss">

View File

@@ -72,9 +72,6 @@ export const useUserInfo = defineStore('userInfo', {
Session.set('userInfo', this.userInfos);
}
},
async setWebSocketState(socketState: boolean) {
this.isSocketOpen = socketState;
},
async getApiUserInfo() {
return request({
url: '/api/system/user/user_info/',

View File

@@ -131,10 +131,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
data: [
{ value: 0, label: '按用户' },
{ value: 1, label: '按角色' },
{
value: 2,
label: '按部门',
},
{ value: 2, label: '按部门' },
{ value: 3, label: '通知公告' },
],
}),
@@ -142,14 +139,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
component: {
optionName: 'el-radio-button',
},
rules: [
{
required: true,
message: '必选项',
// @ts-ignore
trigger: ['blur', 'change'],
},
],
rules: [{ required: true, message: '必选项', trigger: ['blur', 'change'] }],
},
},
target_user: {
@@ -191,10 +181,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}),
rules: [
// 表单校验规则
{
required: true,
message: '必填项',
},
{ required: true, message: '必填项' },
],
},
column: {

View File

@@ -11,6 +11,7 @@
highlight-current
show-checkbox
default-expand-all
:check-on-click-leaf="false"
>
</el-tree>
</template>