Files
one-pipe-system/src/components/core/layouts/art-notification/index.vue
luo 9289a6e940
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m5s
feat: 完善接口19-顶部通知铃铛与站内通知中心
2026-07-25 10:20:53 +08:00

171 lines
5.5 KiB
Vue

<template>
<div v-if="visible" class="notice-overlay" @click="close">
<div class="notice" @click.stop>
<div class="header">
<span class="text">通知</span>
<button
v-if="hasAuth(NOTIFICATION_PERMISSIONS.readAll)"
class="read-all"
type="button"
@click="handleMarkAllRead"
>
全部已读
</button>
</div>
<div class="bar">
<button
v-for="tab in tabs"
:key="tab.value"
type="button"
:class="{ active: activeCategory === tab.value }"
@click="activeCategory = tab.value"
>
{{ tab.label }}<span v-if="tab.count"> ({{ tab.count }})</span>
</button>
</div>
<div class="content">
<div v-loading="notificationStore.loading" class="scroll">
<button
v-for="item in filteredItems"
:key="item.id"
type="button"
:class="['notification-item', { unread: !isRead(item) }]"
@click="handleNotificationClick(item)"
>
<span :class="['icon', `severity-${item.severity || 'info'}`]">
<i class="iconfont-sys">&#xe6c2;</i>
</span>
<span class="text">
<strong>{{ item.title }}</strong>
<small>{{ item.body }}</small>
<time>{{ item.created_at || '-' }}</time>
</span>
<span v-if="!isRead(item)" class="unread-dot" />
</button>
<div v-if="!filteredItems.length" class="empty-tips">
<i class="iconfont-sys">&#xe8d7;</i>
<p>暂无通知</p>
</div>
</div>
<div class="btn-wrapper">
<ElButton class="view-all" @click="handleViewAll">进入通知中心</ElButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { NotificationService } from '@/api/modules'
import type { NotificationCategory, NotificationItem } 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'
const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
const router = useRouter()
const notificationStore = useNotificationStore()
const { hasAuth } = useAuth()
const activeCategory = ref<'all' | NotificationCategory>('all')
const visible = computed(() => props.modelValue)
const categoryMatches = (item: NotificationItem, category: string) => {
if (category === 'all') return true
if (category === 'system') return item.category === 'sync' || item.category === 'system'
if (category === 'expiry') return item.category === 'expiry' || item.category === 'expiring'
return item.category === category
}
const isRead = (item: NotificationItem) => item.is_read
const filteredItems = computed(() =>
notificationStore.recentNotifications.filter((item) =>
categoryMatches(item, activeCategory.value)
)
)
const tabs = computed(() => {
return [
{ value: 'all', label: '全部', count: notificationStore.summary.total },
{ value: 'approval', label: '审批', count: notificationStore.summary.approval },
{ value: 'expiry', label: '临期', count: notificationStore.summary.expiry },
{
value: 'system',
label: '同步/系统',
count: notificationStore.summary.sync + notificationStore.summary.system
}
]
})
const close = () => emit('update:modelValue', false)
const handleViewAll = () => {
close()
void router.push('/notifications')
}
const handleMarkAllRead = async () => {
try {
const response = await notificationStore.markAllRead()
if (response.code !== 0) ElMessage.error(response.msg || '全部已读失败')
} catch (error: any) {
ElMessage.error(error?.message || '全部已读失败')
}
}
const handleNotificationClick = async (item: NotificationItem) => {
if (!hasAuth(NOTIFICATION_PERMISSIONS.read)) {
ElMessage.warning('您没有标记通知已读的权限')
return
}
try {
const readResponse = await notificationStore.markRead(item.id)
if (readResponse.code !== 0) {
ElMessage.error(readResponse.msg || '标记已读失败')
return
}
const targetResponse = await NotificationService.getTarget(item.id)
if (targetResponse.code !== 0 || !targetResponse.data) {
ElMessage.info(item.body || '该通知暂无可跳转目标')
return
}
if (!navigateNotificationTarget(router, targetResponse.data)) {
ElMessage.info(item.body || '该通知暂无可跳转目标')
} else {
close()
}
} catch (error: any) {
ElMessage.error(error?.message || '处理通知失败')
}
}
watch(
() => props.modelValue,
(open) => {
if (open) {
activeCategory.value = 'all'
void notificationStore.refreshSummary().catch((error) => {
ElMessage.error(error?.message || '获取通知失败')
})
}
}
)
onMounted(() => {
void notificationStore.refreshUnreadCount().catch(() => undefined)
})
</script>
<style lang="scss" scoped>
@use './style';
</style>