add message view

This commit is contained in:
jxxghp
2024-03-15 18:15:31 +08:00
parent 8236d80b42
commit 046c21edf6
4 changed files with 297 additions and 3 deletions

View File

@@ -802,18 +802,34 @@ export interface Context {
// 用户信息
export interface User {
// 用户ID
id: number
// 用户名称
name: string
// 用户密码
password: string
// 用户邮箱
email: string
// 是否激活
is_active: boolean
// 是否管理员
is_superuser: boolean
// 头像
avatar: string
}
// 存储空间
export interface Storage {
// 总空间
total_storage: number
// 已使用空间
used_storage: number
}
@@ -918,45 +934,129 @@ export interface Setting {
// 文件浏览接口
export interface EndPoints {
// 文件列表
list: any
// 创建目录
mkdir: any
// 删除文件
delete: any
// 下载文件
download: any
// 图片预览
image: any
// 重命名
rename: any
}
// 文件浏览项目
export interface FileItem {
// 类型
type: string
// 文件名
name: string
// 文件名不含扩展名
basename: string
// 文件路径
path: string
// 文件扩展名
extension: string
// 文件大小
size: number
// 文件子元素
children: FileItem[]
// 文件创建时间
modify_time: number
}
// 媒体服务器播放条目
export interface MediaServerPlayItem {
// ID
id?: string | number
// 标题
title: string
// 副标题
subtitle?: string
// 类型
type?: string
// 海报
image?: string
// 链接
link?: string
// 播放百分比
percent?: number
}
// 媒体服务器媒体库
export interface MediaServerLibrary {
// 服务器名称
server: string
// ID
id?: string | number
// 名称
name: string
// 路径
path?: string
// 类型
type?: string
// 图片
image?: string
// 图片列表
image_list?: string[]
// 链接
link?: string
}
// 消息通知
export interface Message {
// 消息类型
mtype?: string
// 消息标题
title?: string
// 消息内容
text?: string
// 消息链接
link?: string
// 消息图片
image?: string
// 消息时间
date?: string
// 用户ID
userid?: string
// 用户名称
username?: string
// 消息方向0-接收1-发送
action?: number
}

View File

@@ -0,0 +1,54 @@
<script lang="ts" setup>
import type { Message } from '@/api/types'
// 输入参数
const props = defineProps({
message: Object as PropType<Message>,
width: String,
height: String,
})
// 图片是否加载完成
const isImageLoaded = ref(false)
// 图片是否加载失败
const imageLoadError = ref(false)
// 图片加载完成
async function imageLoaded() {
isImageLoaded.value = true
}
// 链接打开新窗口
function openLink() {
if (props.message?.link)
window.open(props.message.link, '_blank')
}
</script>
<template>
<VCard
:width="props.width"
:height="props.height"
@click="openLink"
>
<div
v-if="props.message?.image"
class="relative text-center card-cover-blurred"
>
<VImg
:src="props.message?.image"
aspect-ratio="4/3"
cover
:class="{ shadow: isImageLoaded }"
@load="imageLoaded"
@error="imageLoadError = true"
/>
</div>
<VCardTitle>{{ props.message?.title }}</VCardTitle>
<VCardText>
{{ props.message?.text }}
</VCardText>
</VCard>
</template>

View File

@@ -4,6 +4,7 @@ import NetTestView from '@/views/system/NetTestView.vue'
import LoggingView from '@/views/system/LoggingView.vue'
import RuleTestView from '@/views/system/RuleTestView.vue'
import ModuleTestView from '@/views/system/ModuleTestView.vue'
import MessageView from '@/views/system/MessageView.vue'
import store from '@/store'
// App捷径
@@ -24,6 +25,9 @@ const ruleTestDialog = ref(false)
// 系统健康检查弹窗
const systemTestDialog = ref(false)
// 消息中心弹窗
const messageDialog = ref(false)
// 拼接全部日志url
function allLoggingUrl() {
const token = store.state.auth.token
@@ -124,7 +128,7 @@ function allLoggingUrl() {
<h6 class="text-base font-weight-medium mt-2 mb-0">
日志
</h6>
<span class="text-sm">查看实时日志</span>
<span class="text-sm">实时日志</span>
</VListItem>
</VCol>
<VCol
@@ -145,7 +149,7 @@ function allLoggingUrl() {
<h6 class="text-base font-weight-medium mt-2 mb-0">
网络
</h6>
<span class="text-sm">测试网速连通性</span>
<span class="text-sm">网速连通性测试</span>
</VListItem>
</VCol>
</VRow>
@@ -168,7 +172,28 @@ function allLoggingUrl() {
<h6 class="text-base font-weight-medium mt-2 mb-0">
系统
</h6>
<span class="text-sm">系统健康检查</span>
<span class="text-sm">健康检查</span>
</VListItem>
</VCol>
<VCol
cols="6"
class="text-center cursor-pointer pa-0 shortcut-icon border-e"
@click="() => {}"
>
<VListItem
class="pa-4"
@click="messageDialog = true"
>
<VAvatar
size="48"
variant="tonal"
>
<VIcon icon="mdi-message-outline" />
</VAvatar>
<h6 class="text-base font-weight-medium mt-2 mb-0">
消息
</h6>
<span class="text-sm">消息中心</span>
</VListItem>
</VCol>
</VRow>
@@ -249,4 +274,17 @@ function allLoggingUrl() {
</VCardText>
</VCard>
</VDialog>
<!-- 消息中心弹窗 -->
<VDialog
v-model="messageDialog"
max-width="60rem"
scrollable
>
<VCard title="消息中心">
<DialogCloseBtn @click="messageDialog = false" />
<VCardText>
<MessageView />
</VCardText>
</VCard>
</VDialog>
</template>

View File

@@ -0,0 +1,102 @@
<script lang="ts" setup>
import store from '@/store'
import MessageCard from '@/components/cards/MessageCard.vue'
import type { Message } from '@/api/types'
import api from '@/api'
// 消息列表
const messages = ref<Message[]>([])
// 是否完成加载
const isLoaded = ref(false)
// 输入消息
const content = ref('')
// 聊天容器
const chatContainer = ref<HTMLDivElement>()
// 滚动到底部
function scrollToEnd() {
if (chatContainer.value)
chatContainer.value.scrollTop = chatContainer.value.scrollHeight
}
// SSE持续获取消息
function startSSEMessager() {
const token = store.state.auth.token
if (token) {
const eventSource = new EventSource(
`${import.meta.env.VITE_API_BASE_URL}system/message?token=${token}&role=user`,
)
eventSource.addEventListener('message', (event) => {
const message = event.data
if (message) {
const object = JSON.parse(message)
messages.value.push(object)
scrollToEnd()
}
isLoaded.value = true
})
onBeforeUnmount(() => {
eventSource.close()
})
}
}
// 调用API加载存量消息
async function loadMessages() {
try {
messages.value = await api.get('message/web')
}
catch (error) {
console.error(error)
}
}
onMounted(() => {
// 加载存量消息
loadMessages()
// 监听新消息
startSSEMessager()
})
</script>
<template>
<div
v-if="!isLoaded"
class="mt-5 w-full text-center flex flex-col items-center"
>
<VProgressCircular
size="48"
indeterminate
color="primary"
/>
<span class="mt-3">正在刷新 ...</span>
</div>
<div
v-if="messages.length === 0"
class="w-full text-center flex flex-col items-center"
>
<span class="mb-3">当前没有消息</span>
</div>
<VContainer ref="chatContainer" fluid style="padding: 0;">
<VRow
v-for="(msg, index) in messages"
:key="index"
>
<VCol
sm="8"
lg="6"
xl="4"
style="position: relative;"
>
<MessageCard
:message="msg"
/>
</VCol>
</VRow>
</VContainer>
</template>