Files
hfb_sys/frontend/src/features/admin/views/AdminNotificationsView.vue
T

195 lines
5.3 KiB
Vue

<script setup lang="ts">
import { Bell, Check, Refresh } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { computed, onMounted, ref } from 'vue'
import {
fetchAdminNotifications,
markAdminNotificationRead,
markAllAdminNotificationsRead,
type AdminNotification,
} from '@/features/admin/api/adminNotifications'
import { adminNotificationUnreadChangedEvent } from '@/features/admin/composables/useAdminNotificationUnreadCount'
import { formatDateTime } from '@/shared/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const loading = ref(false)
const submitting = ref(false)
const notifications = ref<AdminNotification[]>([])
const currentPage = ref(1)
const currentPageSize = ref(20)
const total = ref(0)
const readFilter = ref<'all' | 'unread'>('all')
const unreadInPage = computed(() => notifications.value.filter(item => !item.is_read).length)
onMounted(loadNotifications)
async function loadNotifications() {
loading.value = true
try {
const result = await fetchAdminNotifications({
read: readFilter.value === 'unread' ? 'unread' : undefined,
page: currentPage.value,
page_size: currentPageSize.value,
})
notifications.value = result.items
total.value = result.total
} finally {
loading.value = false
}
}
async function handleFilterChange() {
currentPage.value = 1
await loadNotifications()
}
async function handleMarkRead(row: AdminNotification) {
if (row.is_read) return
await markAdminNotificationRead(row.id)
row.is_read = true
window.dispatchEvent(new Event(adminNotificationUnreadChangedEvent))
ElMessage.success('已标记为已读')
}
async function handleMarkAllRead() {
if (submitting.value) return
submitting.value = true
try {
const result = await markAllAdminNotificationsRead()
window.dispatchEvent(new Event(adminNotificationUnreadChangedEvent))
ElMessage.success(result.read_count > 0 ? `已标记 ${result.read_count} 条通知` : '暂无未读通知')
await loadNotifications()
} finally {
submitting.value = false
}
}
function typeLabel(type: string) {
const map: Record<string, string> = {
system: '系统',
chat: '聊天',
}
return map[type] || type
}
</script>
<template>
<section class="page">
<div class="page-header-row">
<div class="page-header">
<p class="eyebrow">Notifications</p>
<h1>通知中心</h1>
<p>查看库存预警客服协作等后台提醒</p>
</div>
<div class="toolbar-actions">
<el-button :icon="Refresh" :loading="loading" @click="loadNotifications">刷新</el-button>
<el-button
type="primary"
:icon="Check"
:loading="submitting"
:disabled="unreadInPage === 0 && readFilter === 'unread'"
@click="handleMarkAllRead"
>
全部已读
</el-button>
</div>
</div>
<el-segmented
v-model="readFilter"
class="notification-filter"
:options="[
{ label: '全部', value: 'all' },
{ label: '未读', value: 'unread' },
]"
@change="handleFilterChange"
/>
<el-table v-loading="loading" class="table-panel" :data="notifications">
<el-table-column label="状态" width="90">
<template #default="{ row }">
<el-tag :type="row.is_read ? 'info' : 'danger'" effect="plain">
{{ row.is_read ? '已读' : '未读' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="类型" width="100">
<template #default="{ row }">
<el-tag type="primary" effect="plain">{{ typeLabel(row.type) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="内容" min-width="420">
<template #default="{ row }">
<div class="notification-content" :class="{ unread: !row.is_read }">
<el-icon><Bell /></el-icon>
<div>
<strong>{{ row.title }}</strong>
<p>{{ row.content }}</p>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="时间" min-width="180">
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button v-if="!row.is_read" size="small" type="primary" @click="handleMarkRead(row)">
已读
</el-button>
<span v-else class="muted-text">-</span>
</template>
</el-table-column>
</el-table>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="loadNotifications"
/>
</section>
</template>
<style scoped>
.notification-filter {
margin-bottom: 14px;
}
.notification-content {
display: flex;
align-items: flex-start;
gap: 10px;
color: #5b6575;
}
.notification-content.unread {
color: #17233d;
}
.notification-content .el-icon {
margin-top: 2px;
color: #409eff;
}
.notification-content strong {
display: block;
margin-bottom: 4px;
font-size: 14px;
}
.notification-content p {
margin: 0;
color: #6b7280;
line-height: 20px;
}
.muted-text {
color: #9ca3af;
}
</style>