139 lines
4.4 KiB
Vue
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>
|