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
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>
|