You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

175 lines
5.4 KiB

<template>
<div class="notification-page">
<el-card>
<template #header>
<div class="card-header">
<span>通知中心</span>
<div>
<el-tag style="margin-right: 12px" :type="wsConnected ? 'success' : 'info'">
{{ wsConnected ? '已连接' : '未连接' }}
</el-tag>
<el-button size="small" @click="wsConnected ? disconnectWs() : connectWs()">
{{ wsConnected ? '断开' : '连接' }}WebSocket
</el-button>
</div>
</div>
</template>
<el-tabs v-model="activeTab">
<el-tab-pane label="实时消息" name="messages">
<div class="message-list" ref="msgListRef">
<div v-for="(msg, i) in messages" :key="i" :class="['msg-item', 'msg-' + (msg.type || 'info')]">
<div class="msg-header">
<strong>{{ msg.title || '通知' }}</strong>
<span class="msg-time">{{ msg.ts ? new Date(msg.ts).toLocaleTimeString() : '' }}</span>
</div>
<div class="msg-body">{{ msg.message }}</div>
</div>
<el-empty v-if="!messages.length" description="暂无消息,请连接WebSocket" />
</div>
</el-tab-pane>
<el-tab-pane label="发送通知" name="send">
<el-form :model="sendForm" label-width="80px" style="max-width: 500px">
<el-form-item label="标题">
<el-input v-model="sendForm.title" placeholder="通知标题" />
</el-form-item>
<el-form-item label="内容">
<el-input v-model="sendForm.message" type="textarea" :rows="4" placeholder="通知内容" />
</el-form-item>
<el-form-item label="目标用户">
<el-input v-model="sendForm.user_id" placeholder="用户ID,留空广播" />
</el-form-item>
<el-form-item label="企微推送">
<el-switch v-model="sendForm.push_to_wecom" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="sendNotification" :loading="sending">发送</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="模板管理" name="templates">
<el-table :data="templates">
<el-table-column prop="name" label="名称" />
<el-table-column prop="code" label="编码" />
<el-table-column prop="channel" label="渠道" width="100" />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button size="small" type="danger" :disabled="row.is_system" @click="deleteTemplate(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { notificationApi } from '@/api'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const activeTab = ref('messages')
const wsConnected = ref(false)
const messages = ref<any[]>([])
const sending = ref(false)
const templates = ref<any[]>([])
const msgListRef = ref<HTMLElement>()
let ws: WebSocket | null = null
const sendForm = reactive({
title: '系统通知',
message: '',
user_id: '',
push_to_wecom: false,
})
function connectWs() {
const userId = userStore.user?.id || 'anonymous'
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
const wsUrl = `${protocol}//${location.host}/api/notification/ws/${userId}`
ws = new WebSocket(wsUrl)
ws.onopen = () => { wsConnected.value = true }
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
messages.value.push(data)
nextTick(() => {
if (msgListRef.value) msgListRef.value.scrollTop = msgListRef.value.scrollHeight
})
} catch { /**/ }
}
ws.onclose = () => { wsConnected.value = false }
ws.onerror = () => { wsConnected.value = false }
}
function disconnectWs() {
ws?.close()
ws = null
}
async function sendNotification() {
sending.value = true
try {
await notificationApi.send(sendForm)
ElMessage.success('通知已发送')
sendForm.message = ''
} finally {
sending.value = false
}
}
async function loadTemplates() {
const res: any = await notificationApi.getTemplates()
templates.value = res?.data || res || []
}
async function deleteTemplate(row: any) {
await notificationApi.deleteTemplate(row.id)
ElMessage.success('已删除')
await loadTemplates()
}
onMounted(() => {
connectWs()
loadTemplates()
})
onUnmounted(() => disconnectWs())
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.message-list {
max-height: 400px;
overflow-y: auto;
padding: 8px;
}
.msg-item {
padding: 10px 14px;
margin-bottom: 8px;
border-radius: 6px;
border-left: 4px solid #409EFF;
background: #f5f7fa;
}
.msg-item.msg-error { border-color: #F56C6C; background: #fef0f0; }
.msg-item.msg-success { border-color: #67C23A; background: #f0f9eb; }
.msg-item.msg-warning { border-color: #E6A23C; background: #fdf6ec; }
.msg-header {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
font-size: 13px;
}
.msg-time { color: #909399; font-size: 12px; }
.msg-body { font-size: 14px; color: #606266; }
</style>