2025-12-16 12:33:02 +08:00
|
|
|
<template>
|
|
|
|
|
<router-view />
|
|
|
|
|
<van-popup v-model:show="showPopup" round @click-overlay="onClickOverlay">
|
|
|
|
|
<div class="popup-content text-center p-8">
|
2026-05-13 14:43:38 +08:00
|
|
|
<div v-if="currentNotify?.title" class="text-lg font-bold mb-4">{{ currentNotify.title }}</div>
|
2025-12-16 12:33:02 +08:00
|
|
|
<div v-html="currentNotify?.content"></div>
|
|
|
|
|
<div class="flex justify-center">
|
2026-05-13 14:43:38 +08:00
|
|
|
<van-button type="primary" @click="onClosePopup" class="w-24">关闭</van-button>
|
2025-12-16 12:33:02 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</van-popup>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, onMounted, watch } from 'vue'
|
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
|
|
|
|
|
|
// 响应式变量
|
|
|
|
|
const showPopup = ref(false)
|
|
|
|
|
const notify = ref([])
|
|
|
|
|
const currentNotify = ref(null)
|
2026-05-13 14:43:38 +08:00
|
|
|
const pendingNotifyQueue = ref([])
|
|
|
|
|
const shownNotificationKeys = ref(new Set())
|
|
|
|
|
const SESSION_KEY = 'qnc_webview_shown_notifications'
|
2025-12-16 12:33:02 +08:00
|
|
|
|
|
|
|
|
// 获取当前页面路径
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
|
|
|
|
// 获取通知数据
|
|
|
|
|
onMounted(() => {
|
2026-05-13 14:43:38 +08:00
|
|
|
loadShownNotificationKeys()
|
2025-12-16 12:33:02 +08:00
|
|
|
getGlobalNotify()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 获取通知数据
|
|
|
|
|
const getGlobalNotify = async () => {
|
|
|
|
|
const { data, error } = await useApiFetch("/notification/list")
|
|
|
|
|
.get()
|
|
|
|
|
.json()
|
|
|
|
|
|
2026-05-13 14:43:38 +08:00
|
|
|
if (data.value && !error.value && data.value.code === 200 && data.value.data) {
|
|
|
|
|
notify.value = data.value.data.notifications ?? []
|
|
|
|
|
checkNotification()
|
2025-12-16 12:33:02 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 14:43:38 +08:00
|
|
|
/** 与后台「展示时间」一致:未配置或起止相同视为「全天」,任意时刻都算在范围内 */
|
2025-12-16 12:33:02 +08:00
|
|
|
const isWithinTimeRange = (startTime, endTime) => {
|
2026-05-13 14:43:38 +08:00
|
|
|
const s = (startTime || '').trim()
|
|
|
|
|
const e = (endTime || '').trim()
|
|
|
|
|
if (!s && !e) return true
|
|
|
|
|
if (s === e) return true
|
2025-12-16 12:33:02 +08:00
|
|
|
|
2026-05-13 14:43:38 +08:00
|
|
|
const now = new Date()
|
2025-12-16 12:33:02 +08:00
|
|
|
const currentMinutes = now.getHours() * 60 + now.getMinutes()
|
2026-05-13 14:43:38 +08:00
|
|
|
const toMinutes = (t) => {
|
|
|
|
|
const parts = t.split(':').map(Number)
|
|
|
|
|
const h = parts[0] ?? 0
|
|
|
|
|
const m = parts[1] ?? 0
|
|
|
|
|
return h * 60 + m
|
|
|
|
|
}
|
|
|
|
|
const startMinutes = toMinutes(s)
|
|
|
|
|
const endMinutes = toMinutes(e)
|
2025-12-16 12:33:02 +08:00
|
|
|
|
|
|
|
|
if (endMinutes < startMinutes) {
|
2026-05-13 14:43:38 +08:00
|
|
|
return currentMinutes >= startMinutes || currentMinutes <= endMinutes
|
2025-12-16 12:33:02 +08:00
|
|
|
}
|
|
|
|
|
return currentMinutes >= startMinutes && currentMinutes <= endMinutes
|
|
|
|
|
}
|
2026-05-13 14:43:38 +08:00
|
|
|
|
|
|
|
|
/** 当前路由是否与通知配置的页面一致 */
|
|
|
|
|
const matchesNotificationPage = (page) => {
|
|
|
|
|
const p = (page || '').trim()
|
|
|
|
|
const cur = route.path || ''
|
|
|
|
|
if (p === cur) return true
|
|
|
|
|
if (p === '/' && (cur === '/' || cur === '')) return true
|
|
|
|
|
if (cur.startsWith('/app/') && p === cur.replace('/app/', '/')) return true
|
|
|
|
|
if (p.startsWith('/app/') && cur === p.replace('/app/', '/')) return true
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildNotificationKey = (notification) => {
|
|
|
|
|
return [
|
|
|
|
|
notification.title ?? '',
|
|
|
|
|
notification.content ?? '',
|
|
|
|
|
notification.notificationPage ?? '',
|
|
|
|
|
notification.startDate ?? '',
|
|
|
|
|
notification.endDate ?? '',
|
|
|
|
|
notification.startTime ?? '',
|
|
|
|
|
notification.endTime ?? ''
|
|
|
|
|
].join('|')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadShownNotificationKeys = () => {
|
|
|
|
|
const raw = sessionStorage.getItem(SESSION_KEY)
|
|
|
|
|
if (!raw) return
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(raw)
|
|
|
|
|
if (Array.isArray(parsed)) {
|
|
|
|
|
shownNotificationKeys.value = new Set(parsed)
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
shownNotificationKeys.value = new Set()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveShownNotificationKeys = () => {
|
|
|
|
|
sessionStorage.setItem(SESSION_KEY, JSON.stringify([...shownNotificationKeys.value]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasShownNotification = (notification) => {
|
|
|
|
|
return shownNotificationKeys.value.has(buildNotificationKey(notification))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const markNotificationShown = (notification) => {
|
|
|
|
|
shownNotificationKeys.value.add(buildNotificationKey(notification))
|
|
|
|
|
saveShownNotificationKeys()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const showNextNotification = () => {
|
|
|
|
|
const next = pendingNotifyQueue.value.shift()
|
|
|
|
|
if (!next) {
|
|
|
|
|
currentNotify.value = null
|
|
|
|
|
showPopup.value = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
currentNotify.value = next
|
|
|
|
|
showPopup.value = true
|
|
|
|
|
markNotificationShown(next)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 12:33:02 +08:00
|
|
|
// 检查通知并更新showPopup
|
|
|
|
|
const checkNotification = () => {
|
2026-05-13 14:43:38 +08:00
|
|
|
if (showPopup.value) return
|
|
|
|
|
|
|
|
|
|
const matchedNotifications = []
|
2025-12-16 12:33:02 +08:00
|
|
|
for (let notification of notify.value) {
|
|
|
|
|
const isTimeValid = isWithinTimeRange(notification.startTime, notification.endTime)
|
2026-05-13 14:43:38 +08:00
|
|
|
const isPageValid = matchesNotificationPage(notification.notificationPage)
|
|
|
|
|
const isShown = hasShownNotification(notification)
|
2025-12-16 12:33:02 +08:00
|
|
|
|
2026-05-13 14:43:38 +08:00
|
|
|
if (isTimeValid && isPageValid && !isShown) {
|
|
|
|
|
matchedNotifications.push(notification)
|
2025-12-16 12:33:02 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-13 14:43:38 +08:00
|
|
|
|
|
|
|
|
pendingNotifyQueue.value = matchedNotifications
|
|
|
|
|
showNextNotification()
|
2025-12-16 12:33:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 监听路由变化
|
|
|
|
|
watch(() => route.path, () => {
|
2026-05-13 14:43:38 +08:00
|
|
|
checkNotification()
|
2025-12-16 12:33:02 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 关闭弹窗
|
2026-05-13 14:43:38 +08:00
|
|
|
const onClosePopup = () => {
|
|
|
|
|
showNextNotification()
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 12:33:02 +08:00
|
|
|
const onClickOverlay = () => {
|
2026-05-13 14:43:38 +08:00
|
|
|
onClosePopup()
|
2025-12-16 12:33:02 +08:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped></style>
|