Files
wenzi/frontend/admin/src/views/NotificationsView.vue
Your Name 91a0b77f7a test(cache): 修复CacheConfigTest边界值测试
- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl
- 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE
- 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查
- 所有1266个测试用例通过
- 覆盖率: 指令81.89%, 行88.48%, 分支51.55%

docs: 添加项目状态报告
- 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态
- 包含质量指标、已完成功能、待办事项和技术债务
2026-03-02 13:31:54 +08:00

139 lines
4.4 KiB
Vue

<template>
<section class="space-y-6">
<ListSection :page="page" :total-pages="totalPages" @prev="page--" @next="page++">
<template #title>通知中心</template>
<template #subtitle>查看系统告警与运营提醒</template>
<template #filters>
<input class="mos-input !py-1 !px-2 !text-xs w-56" v-model="query" placeholder="搜索通知标题" />
<select class="mos-input !py-1 !px-2 !text-xs" v-model="readFilter">
<option value="">全部状态</option>
<option value="unread">未读</option>
<option value="read">已读</option>
</select>
</template>
<template #actions>
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="selectAll">
{{ allSelected ? '取消全选' : '全选' }}
</button>
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="batchRead">批量标记已读</button>
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="markAllRead">全部标记已读</button>
</template>
<template #default>
<div class="space-y-3">
<div v-for="notice in pagedNotifications" :key="notice.id" class="flex items-center justify-between rounded-xl border border-mosquito-line px-4 py-3">
<div class="flex items-center gap-3">
<input
type="checkbox"
class="h-4 w-4"
:checked="selectedIds.includes(notice.id)"
@click.stop
@change.stop="toggleSelect(notice.id)"
/>
<div>
<div class="text-sm font-semibold text-mosquito-ink">{{ notice.title }}</div>
<div class="mos-muted text-xs">{{ notice.detail }}</div>
</div>
</div>
<div class="text-xs text-mosquito-ink/70">
<div>{{ notice.read ? '已读' : '未读' }}</div>
<div class="mos-muted">{{ formatDate(notice.createdAt) }}</div>
</div>
</div>
</div>
</template>
</ListSection>
</section>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { useDataService } from '../services'
import { useAuditStore } from '../stores/audit'
import ListSection from '../components/ListSection.vue'
type NoticeItem = {
id: string
title: string
detail: string
read: boolean
createdAt: string
}
const notifications = ref<NoticeItem[]>([])
const service = useDataService()
const auditStore = useAuditStore()
const query = ref('')
const readFilter = ref('')
const selectedIds = ref<string[]>([])
const page = ref(0)
const pageSize = 8
const formatDate = (value: string) => new Date(value).toLocaleString('zh-CN')
onMounted(async () => {
notifications.value = await service.getNotifications()
})
const markAllRead = () => {
notifications.value = notifications.value.map((item) => ({
...item,
read: true
}))
auditStore.addLog('标记通知已读', '通知中心')
}
const filteredNotifications = computed(() => {
return notifications.value.filter((item) => {
const matchesQuery = item.title.includes(query.value.trim())
const matchesRead = readFilter.value
? (readFilter.value === 'read' ? item.read : !item.read)
: true
return matchesQuery && matchesRead
})
})
const totalPages = computed(() => Math.max(1, Math.ceil(filteredNotifications.value.length / pageSize)))
const pagedNotifications = computed(() => {
const start = page.value * pageSize
return filteredNotifications.value.slice(start, start + pageSize)
})
watch([query, readFilter], () => {
page.value = 0
})
const allSelected = computed(() => {
return (
filteredNotifications.value.length > 0 &&
filteredNotifications.value.every((item) => selectedIds.value.includes(item.id))
)
})
const toggleSelect = (id: string) => {
if (selectedIds.value.includes(id)) {
selectedIds.value = selectedIds.value.filter((item) => item !== id)
} else {
selectedIds.value = [...selectedIds.value, id]
}
}
const selectAll = () => {
if (allSelected.value) {
selectedIds.value = []
} else {
selectedIds.value = filteredNotifications.value.map((item) => item.id)
}
}
const batchRead = () => {
filteredNotifications.value
.filter((item) => selectedIds.value.includes(item.id))
.forEach((item) => {
item.read = true
})
auditStore.addLog('批量标记通知已读', '通知中心')
selectedIds.value = []
}
</script>