mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-10 17:42:50 +08:00
add message view
This commit is contained in:
100
src/api/types.ts
100
src/api/types.ts
@@ -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
|
||||
}
|
||||
|
||||
54
src/components/cards/MessageCard.vue
Normal file
54
src/components/cards/MessageCard.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
102
src/views/system/MessageView.vue
Normal file
102
src/views/system/MessageView.vue
Normal 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>
|
||||
Reference in New Issue
Block a user