mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 16:16:42 +08:00
add message view
This commit is contained in:
@@ -802,18 +802,34 @@ export interface Context {
|
|||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
export interface User {
|
export interface User {
|
||||||
|
// 用户ID
|
||||||
id: number
|
id: number
|
||||||
|
|
||||||
|
// 用户名称
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
|
// 用户密码
|
||||||
password: string
|
password: string
|
||||||
|
|
||||||
|
// 用户邮箱
|
||||||
email: string
|
email: string
|
||||||
|
|
||||||
|
// 是否激活
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
|
|
||||||
|
// 是否管理员
|
||||||
is_superuser: boolean
|
is_superuser: boolean
|
||||||
|
|
||||||
|
// 头像
|
||||||
avatar: string
|
avatar: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储空间
|
// 存储空间
|
||||||
export interface Storage {
|
export interface Storage {
|
||||||
|
// 总空间
|
||||||
total_storage: number
|
total_storage: number
|
||||||
|
|
||||||
|
// 已使用空间
|
||||||
used_storage: number
|
used_storage: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,45 +934,129 @@ export interface Setting {
|
|||||||
|
|
||||||
// 文件浏览接口
|
// 文件浏览接口
|
||||||
export interface EndPoints {
|
export interface EndPoints {
|
||||||
|
// 文件列表
|
||||||
list: any
|
list: any
|
||||||
|
|
||||||
|
// 创建目录
|
||||||
mkdir: any
|
mkdir: any
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
delete: any
|
delete: any
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
download: any
|
download: any
|
||||||
|
|
||||||
|
// 图片预览
|
||||||
image: any
|
image: any
|
||||||
|
|
||||||
|
// 重命名
|
||||||
rename: any
|
rename: any
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件浏览项目
|
// 文件浏览项目
|
||||||
export interface FileItem {
|
export interface FileItem {
|
||||||
|
// 类型
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
// 文件名
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
|
// 文件名不含扩展名
|
||||||
basename: string
|
basename: string
|
||||||
|
|
||||||
|
// 文件路径
|
||||||
path: string
|
path: string
|
||||||
|
|
||||||
|
// 文件扩展名
|
||||||
extension: string
|
extension: string
|
||||||
|
|
||||||
|
// 文件大小
|
||||||
size: number
|
size: number
|
||||||
|
|
||||||
|
// 文件子元素
|
||||||
children: FileItem[]
|
children: FileItem[]
|
||||||
|
|
||||||
|
// 文件创建时间
|
||||||
modify_time: number
|
modify_time: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 媒体服务器播放条目
|
// 媒体服务器播放条目
|
||||||
export interface MediaServerPlayItem {
|
export interface MediaServerPlayItem {
|
||||||
|
// ID
|
||||||
id?: string | number
|
id?: string | number
|
||||||
|
|
||||||
|
// 标题
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
|
// 副标题
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
|
|
||||||
|
// 类型
|
||||||
type?: string
|
type?: string
|
||||||
|
|
||||||
|
// 海报
|
||||||
image?: string
|
image?: string
|
||||||
|
|
||||||
|
// 链接
|
||||||
link?: string
|
link?: string
|
||||||
|
|
||||||
|
// 播放百分比
|
||||||
percent?: number
|
percent?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 媒体服务器媒体库
|
// 媒体服务器媒体库
|
||||||
export interface MediaServerLibrary {
|
export interface MediaServerLibrary {
|
||||||
|
// 服务器名称
|
||||||
server: string
|
server: string
|
||||||
|
|
||||||
|
// ID
|
||||||
id?: string | number
|
id?: string | number
|
||||||
|
|
||||||
|
// 名称
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
|
// 路径
|
||||||
path?: string
|
path?: string
|
||||||
|
|
||||||
|
// 类型
|
||||||
type?: string
|
type?: string
|
||||||
|
|
||||||
|
// 图片
|
||||||
image?: string
|
image?: string
|
||||||
|
|
||||||
|
// 图片列表
|
||||||
image_list?: string[]
|
image_list?: string[]
|
||||||
|
|
||||||
|
// 链接
|
||||||
link?: 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 LoggingView from '@/views/system/LoggingView.vue'
|
||||||
import RuleTestView from '@/views/system/RuleTestView.vue'
|
import RuleTestView from '@/views/system/RuleTestView.vue'
|
||||||
import ModuleTestView from '@/views/system/ModuleTestView.vue'
|
import ModuleTestView from '@/views/system/ModuleTestView.vue'
|
||||||
|
import MessageView from '@/views/system/MessageView.vue'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
|
|
||||||
// App捷径
|
// App捷径
|
||||||
@@ -24,6 +25,9 @@ const ruleTestDialog = ref(false)
|
|||||||
// 系统健康检查弹窗
|
// 系统健康检查弹窗
|
||||||
const systemTestDialog = ref(false)
|
const systemTestDialog = ref(false)
|
||||||
|
|
||||||
|
// 消息中心弹窗
|
||||||
|
const messageDialog = ref(false)
|
||||||
|
|
||||||
// 拼接全部日志url
|
// 拼接全部日志url
|
||||||
function allLoggingUrl() {
|
function allLoggingUrl() {
|
||||||
const token = store.state.auth.token
|
const token = store.state.auth.token
|
||||||
@@ -124,7 +128,7 @@ function allLoggingUrl() {
|
|||||||
<h6 class="text-base font-weight-medium mt-2 mb-0">
|
<h6 class="text-base font-weight-medium mt-2 mb-0">
|
||||||
日志
|
日志
|
||||||
</h6>
|
</h6>
|
||||||
<span class="text-sm">查看实时日志</span>
|
<span class="text-sm">实时日志</span>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol
|
<VCol
|
||||||
@@ -145,7 +149,7 @@ function allLoggingUrl() {
|
|||||||
<h6 class="text-base font-weight-medium mt-2 mb-0">
|
<h6 class="text-base font-weight-medium mt-2 mb-0">
|
||||||
网络
|
网络
|
||||||
</h6>
|
</h6>
|
||||||
<span class="text-sm">测试网速连通性</span>
|
<span class="text-sm">网速连通性测试</span>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
@@ -168,7 +172,28 @@ function allLoggingUrl() {
|
|||||||
<h6 class="text-base font-weight-medium mt-2 mb-0">
|
<h6 class="text-base font-weight-medium mt-2 mb-0">
|
||||||
系统
|
系统
|
||||||
</h6>
|
</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>
|
</VListItem>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
@@ -249,4 +274,17 @@ function allLoggingUrl() {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
|
<!-- 消息中心弹窗 -->
|
||||||
|
<VDialog
|
||||||
|
v-model="messageDialog"
|
||||||
|
max-width="60rem"
|
||||||
|
scrollable
|
||||||
|
>
|
||||||
|
<VCard title="消息中心">
|
||||||
|
<DialogCloseBtn @click="messageDialog = false" />
|
||||||
|
<VCardText>
|
||||||
|
<MessageView />
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -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