统一通知中心
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { Bell, Check, Refresh } from '@element-plus/icons-vue'
|
||||
import http, { apiErrorMessage } from '../api/http'
|
||||
|
||||
const notifications = ref<any[]>([])
|
||||
const unreadCount = ref(0)
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const unreadOnly = ref(false)
|
||||
|
||||
async function load(reset = false) {
|
||||
if (reset) page.value = 1
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await http.get('/notifications', {
|
||||
params: { page: page.value, pageSize, unreadOnly: unreadOnly.value || undefined },
|
||||
})
|
||||
notifications.value = data.items
|
||||
total.value = data.total
|
||||
unreadCount.value = data.unreadCount
|
||||
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
async function markRead(n: any) {
|
||||
if (n.isRead) return
|
||||
try {
|
||||
await http.post(`/notifications/${n.id}/read`)
|
||||
n.isRead = true
|
||||
unreadCount.value = Math.max(0, unreadCount.value - 1)
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
async function markAllRead() {
|
||||
try {
|
||||
await http.post('/notifications/read-all')
|
||||
notifications.value.forEach(n => n.isRead = true)
|
||||
unreadCount.value = 0
|
||||
ElMessage.success('已全部标为已读')
|
||||
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
|
||||
}
|
||||
|
||||
function goLink(link: string | null) {
|
||||
if (!link) return
|
||||
const router = (window as any).__router__
|
||||
if (router) router.push(link)
|
||||
}
|
||||
|
||||
onMounted(() => load())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-stack notif-page">
|
||||
<section class="page-intro">
|
||||
<div>
|
||||
<span class="section-kicker">NOTIFICATION CENTER</span>
|
||||
<h2>消息通知</h2>
|
||||
<p>审核提示、调课通知、成绩发布等所有系统消息统一汇总于此。</p>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<el-badge :value="unreadCount" :hidden="!unreadCount">
|
||||
<el-button :icon="Bell">未读 {{ unreadCount }}</el-button>
|
||||
</el-badge>
|
||||
<el-button v-if="unreadCount" :icon="Check" @click="markAllRead">全部已读</el-button>
|
||||
<el-button :icon="Refresh" @click="load(true)">刷新</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="notif-toolbar">
|
||||
<el-checkbox v-model="unreadOnly" @change="load(true)">仅显示未读</el-checkbox>
|
||||
</section>
|
||||
|
||||
<section v-loading="loading" class="notif-list">
|
||||
<article
|
||||
v-for="n in notifications"
|
||||
:key="n.id"
|
||||
class="notif-card"
|
||||
:class="{ unread: !n.isRead }"
|
||||
@click="goLink(n.linkUrl); markRead(n)"
|
||||
:style="{ cursor: n.linkUrl ? 'pointer' : 'default' }"
|
||||
>
|
||||
<div class="notif-dot" v-if="!n.isRead" />
|
||||
<div class="notif-body">
|
||||
<div class="notif-head">
|
||||
<b>{{ n.title }}</b>
|
||||
<el-tag v-if="!n.isRead" size="small" type="primary">未读</el-tag>
|
||||
</div>
|
||||
<p>{{ n.content }}</p>
|
||||
<small>{{ new Date(n.createdAt).toLocaleString('zh-CN') }}</small>
|
||||
</div>
|
||||
</article>
|
||||
<el-empty v-if="!notifications.length" description="暂无消息通知" />
|
||||
</section>
|
||||
|
||||
<div class="notif-pager" v-if="total > pageSize">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
@current-change="load()"
|
||||
layout="prev, pager, next, total"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.notif-toolbar { margin-bottom: 16px; }
|
||||
.notif-list { display: grid; gap: 8px; }
|
||||
.notif-card {
|
||||
display: flex; align-items: flex-start; gap: 14px;
|
||||
padding: 16px 20px; background: #fff; border: 1px solid #e4e7ed;
|
||||
border-radius: 10px; transition: box-shadow .15s;
|
||||
}
|
||||
.notif-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,.06); }
|
||||
.notif-card.unread { background: #ecf5ff; border-color: #c6e2ff; }
|
||||
.notif-dot { width: 10px; height: 10px; border-radius: 50%; background: #409eff; flex-shrink: 0; margin-top: 5px; }
|
||||
.notif-body { flex: 1; min-width: 0; }
|
||||
.notif-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 6px; }
|
||||
.notif-head b { font-size: 14px; }
|
||||
.notif-body p { font-size: 13px; color: #606266; margin: 0 0 6px; line-height: 1.5; }
|
||||
.notif-body small { font-size: 11px; color: var(--muted); }
|
||||
.notif-pager { display: flex; justify-content: center; margin-top: 20px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user