Files
one-pipe-system/src/views/notifications/index.vue
luo cc6fc9243e
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m43s
feat: 注意事项
2026-07-25 14:37:27 +08:00

278 lines
8.8 KiB
Vue

<template>
<ArtTableFullScreen>
<div class="notification-center-page" id="table-full-screen">
<ElCard shadow="never" class="art-table-card">
<template #header>
<div class="page-header">
<div>
<h2>通知中心</h2>
<p>查看余额预警临期提醒审批结果和系统告警</p>
</div>
<ElButton
v-if="hasAuth(NOTIFICATION_PERMISSIONS.readAll)"
:loading="markingAllRead"
@click="handleMarkAllRead"
>
全部已读
</ElButton>
</div>
</template>
<div class="filters">
<ElSelect v-model="filters.category" clearable placeholder="通知分类" @change="reload">
<ElOption label="临期提醒" value="expiry" />
<ElOption label="审批" value="approval" />
<ElOption label="同步" value="sync" />
<ElOption label="系统" value="system" />
</ElSelect>
<ElSelect v-model="filters.type" clearable placeholder="通知类型" @change="reload">
<ElOption v-for="type in typeOptions" :key="type" :label="type" :value="type" />
</ElSelect>
<ElSelect v-model="filters.severity" clearable placeholder="严重级别" @change="reload">
<ElOption label="提示" value="info" />
<ElOption label="警告" value="warning" />
<ElOption label="严重" value="error" />
<ElOption label="严重" value="critical" />
</ElSelect>
<ElSelect v-model="filters.is_read" clearable placeholder="已读状态" @change="reload">
<ElOption label="未读" :value="false" />
<ElOption label="已读" :value="true" />
</ElSelect>
<ElButton @click="loadNotifications">刷新</ElButton>
</div>
<ElTable v-loading="loading" :data="notifications" row-key="id" class="notification-table">
<ElTableColumn label="状态" width="90">
<template #default="scope">
<span :class="['read-dot', { unread: !isRead(scope.row) }]" />
{{ isRead(scope.row) ? '已读' : '未读' }}
</template>
</ElTableColumn>
<ElTableColumn prop="title" label="通知标题" min-width="200" show-overflow-tooltip />
<ElTableColumn label="分类" width="120">
<template #default="scope">{{ getCategoryLabel(scope.row.category) }}</template>
</ElTableColumn>
<ElTableColumn label="严重级别" width="110">
<template #default="scope">
<ElTag :type="getSeverityType(scope.row.severity)">
{{ getSeverityLabel(scope.row.severity) }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="body" label="通知内容" min-width="300" show-overflow-tooltip />
<ElTableColumn prop="read_at" label="已读时间" width="180" />
<ElTableColumn prop="created_at" label="时间" width="180" />
<ElTableColumn label="操作" width="100" fixed="right">
<template #default="scope">
<ElButton
v-if="hasAuth(NOTIFICATION_PERMISSIONS.read)"
link
type="primary"
@click="handleNotificationClick(scope.row)"
>
查看
</ElButton>
<span v-else>-</span>
</template>
</ElTableColumn>
</ElTable>
<div class="pagination-wrapper">
<ElPagination
v-model:current-page="page"
v-model:page-size="pageSize"
layout="total, sizes, prev, pager, next"
:total="total"
@current-change="loadNotifications"
@size-change="reload"
/>
</div>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { NotificationService } from '@/api/modules'
import type { NotificationItem, NotificationQueryParams } from '@/types/api'
import { useNotificationStore } from '@/store/modules/notification'
import { useAuth } from '@/composables/useAuth'
import { NOTIFICATION_PERMISSIONS } from '@/config/constants/notification'
import { navigateNotificationTarget } from '@/utils/business/notificationNavigation'
defineOptions({ name: 'NotificationCenter' })
const router = useRouter()
const notificationStore = useNotificationStore()
const { hasAuth } = useAuth()
const notifications = ref<NotificationItem[]>([])
const loading = ref(false)
const markingAllRead = ref(false)
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const typeOptions = ref<string[]>([])
const filters = reactive<NotificationQueryParams>({
category: undefined,
type: undefined,
severity: undefined,
is_read: undefined
})
const isRead = (item: NotificationItem) => item.is_read
const getCategoryLabel = (category?: string | null) =>
({
balance: '余额预警',
expiry: '临期提醒',
expiring: '临期提醒',
approval: '审批',
sync: '同步/系统',
system: '同步/系统'
})[category || ''] ||
category ||
'-'
const getSeverityLabel = (severity?: string | null) =>
({ info: '提示', warning: '警告', error: '严重', critical: '严重' })[severity || ''] ||
severity ||
'-'
const getSeverityType = (severity?: string | null) => {
if (severity === 'error') return 'danger'
if (severity === 'warning') return 'warning'
if (severity === 'critical') return 'danger'
return 'info'
}
const loadNotifications = async () => {
loading.value = true
try {
const response = await NotificationService.getNotifications({
page: page.value,
page_size: pageSize.value,
...filters
})
if (response.code !== 0 || !response.data) {
ElMessage.error(response.msg || '获取通知列表失败')
return
}
notifications.value = response.data.items || []
total.value = response.data.total || 0
typeOptions.value = Array.from(
new Set(
notifications.value
.map((item) => item.type)
.filter((type): type is string => Boolean(type))
)
)
await notificationStore.refreshUnreadCount()
} catch (error: any) {
ElMessage.error(error?.message || '获取通知列表失败')
} finally {
loading.value = false
}
}
const reload = () => {
page.value = 1
void loadNotifications()
}
const handleMarkAllRead = async () => {
markingAllRead.value = true
try {
const response = await notificationStore.markAllRead()
if (response.code !== 0) ElMessage.error(response.msg || '全部已读失败')
else await loadNotifications()
} catch (error: any) {
ElMessage.error(error?.message || '全部已读失败')
} finally {
markingAllRead.value = false
}
}
const handleNotificationClick = async (item: NotificationItem) => {
try {
const targetResponse = await NotificationService.getTarget(item.id)
const readResponse = await notificationStore.markRead(item.id)
if (readResponse.code !== 0) {
ElMessage.error(readResponse.msg || '标记已读失败')
return
}
if (targetResponse.code !== 0 || !targetResponse.data) {
ElMessage.info(item.body || '该通知暂无可跳转目标')
await loadNotifications()
return
}
if (!navigateNotificationTarget(router, targetResponse.data)) {
ElMessage.info(item.body || '该通知暂无可跳转目标')
}
await loadNotifications()
} catch (error: any) {
ElMessage.error(error?.message || '处理通知失败')
}
}
onMounted(() => {
void loadNotifications()
})
</script>
<style scoped lang="scss">
.notification-center-page {
.page-header,
.filters {
display: flex;
gap: 12px;
align-items: center;
}
.page-header {
justify-content: space-between;
h2 {
margin: 0;
font-size: 20px;
}
p {
margin: 8px 0 0;
color: var(--el-text-color-secondary);
}
}
.filters {
flex-wrap: wrap;
margin-bottom: 16px;
.el-select {
width: 160px;
}
}
.read-dot {
display: inline-block;
width: 8px;
height: 8px;
margin-right: 6px;
vertical-align: middle;
background: var(--el-border-color);
border-radius: 50%;
&.unread {
background: var(--el-color-danger);
}
}
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
</style>