From a4afe70d4646291da603619a4f8c69e948299054 Mon Sep 17 00:00:00 2001
From: liangzai <2440983361@qq.com>
Date: Thu, 30 Apr 2026 11:39:57 +0800
Subject: [PATCH] t-4-29
---
src/App.vue | 3 +
src/auto-imports.d.ts | 4 +
src/composables/uni-router.ts | 85 ++++++-----
src/composables/useApiFetch.ts | 19 ++-
src/composables/useGlobalNotification.js | 174 +++++++++++++++++++++++
src/layouts/default.vue | 95 +++++--------
src/layouts/home.vue | 36 +++++
src/pages/agent.vue | 18 +--
src/pages/cancel-account.vue | 96 +++----------
src/pages/me.vue | 2 +-
src/pages/subordinate-detail.vue | 26 ++--
src/static/images/cbgx.svg | 1 +
src/static/images/djgx.svg | 1 +
src/static/images/tgjl.svg | 1 +
src/static/images/txsy.svg | 1 +
src/static/images/zhsvipjl.svg | 1 +
src/static/images/zhvipjl.svg | 1 +
17 files changed, 363 insertions(+), 201 deletions(-)
create mode 100644 src/composables/useGlobalNotification.js
create mode 100644 src/static/images/cbgx.svg
create mode 100644 src/static/images/djgx.svg
create mode 100644 src/static/images/tgjl.svg
create mode 100644 src/static/images/txsy.svg
create mode 100644 src/static/images/zhsvipjl.svg
create mode 100644 src/static/images/zhvipjl.svg
diff --git a/src/App.vue b/src/App.vue
index 163a9af..01d21eb 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,7 +1,10 @@
diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts
index 62acd5b..b6cb3ab 100644
--- a/src/auto-imports.d.ts
+++ b/src/auto-imports.d.ts
@@ -56,6 +56,7 @@ declare global {
const getToken: typeof import('./utils/storage')['getToken']
const getUserInfo: typeof import('./utils/storage')['getUserInfo']
const getWebPathForNotification: typeof import('./composables/uni-router')['getWebPathForNotification']
+ const getWebPathsForNotification: typeof import('./composables/uni-router')['getWebPathsForNotification']
const h: typeof import('vue')['h']
const handlePosterRenderMergeDone: typeof import('./utils/posterRenderMergeBridge')['handlePosterRenderMergeDone']
const handlePosterRenderMergeFailed: typeof import('./utils/posterRenderMergeBridge')['handlePosterRenderMergeFailed']
@@ -250,6 +251,7 @@ declare global {
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
+ const useGlobalNotification: typeof import('./composables/useGlobalNotification.js')['useGlobalNotification']
const useHotUpdate: typeof import('./composables/useHotUpdate')['useHotUpdate']
const useHttp: typeof import('./composables/useHttp.js')['useHttp']
const useId: typeof import('vue')['useId']
@@ -449,6 +451,7 @@ declare module 'vue' {
readonly getToken: UnwrapRef
readonly getUserInfo: UnwrapRef
readonly getWebPathForNotification: UnwrapRef
+ readonly getWebPathsForNotification: UnwrapRef
readonly h: UnwrapRef
readonly handlePosterRenderMergeDone: UnwrapRef
readonly handlePosterRenderMergeFailed: UnwrapRef
@@ -640,6 +643,7 @@ declare module 'vue' {
readonly useFullscreen: UnwrapRef
readonly useGamepad: UnwrapRef
readonly useGeolocation: UnwrapRef
+ readonly useGlobalNotification: UnwrapRef
readonly useHotUpdate: UnwrapRef
readonly useId: UnwrapRef
readonly useIdle: UnwrapRef
diff --git a/src/composables/uni-router.ts b/src/composables/uni-router.ts
index 2503509..7cd2bba 100644
--- a/src/composables/uni-router.ts
+++ b/src/composables/uni-router.ts
@@ -155,52 +155,67 @@ export function getLayoutPageTitle(): string {
}
/**
- * 与 webview vue-router path 对齐,用于全局通知 notificationPage 匹配
+ * 与 webview vue-router path 对齐,用于全局通知 notificationPage 匹配。
+ * 一个 uni 页面可对应多个 web 路径(如 webview 中 /withdraw 和 /agent/withdraw 是同一页面)。
*/
-const UNI_TO_WEB_NOTIFY_PATH: Record = {
- 'pages/index': '/',
- 'pages/agent': '/agent',
- 'pages/me': '/me',
- 'pages/promote': '/agent/promote',
- 'pages/history-query': '/historyQuery',
- 'pages/help': '/help',
- 'pages/help-detail': '/help/detail',
- 'pages/help-guide': '/help/guide',
- 'pages/withdraw': '/withdraw',
- 'pages/report-result-webview': '/app/report',
- 'pages/report-example-webview': '/app/example',
- 'pages/privacy-policy': '/privacyPolicy',
- 'pages/user-agreement': '/userAgreement',
- 'pages/agent-manage-agreement': '/agentManageAgreement',
- 'pages/agent-service-agreement': '/agentSerivceAgreement',
- 'pages/authorization': '/authorization',
- 'pages/payment-result': '/payment/result',
- 'pages/inquire': '/inquire',
- 'pages/login': '/login',
- 'pages/invitation': '/agent/invitation',
- 'pages/agent-promote-details': '/agent/promoteDetails',
- 'pages/agent-rewards-details': '/agent/rewardsDetails',
- 'pages/agent-vip': '/agent/agentVip',
- 'pages/agent-vip-apply': '/agent/vipApply',
- 'pages/agent-vip-config': '/agent/vipConfig',
- 'pages/withdraw-details': '/agent/withdrawDetails',
- 'pages/subordinate-list': '/agent/subordinateList',
+const UNI_TO_WEB_NOTIFY_PATHS: Record = {
+ 'pages/index': ['/'],
+ 'pages/agent': ['/agent'],
+ 'pages/me': ['/me'],
+ 'pages/promote': ['/agent/promote'],
+ 'pages/history-query': ['/historyQuery'],
+ 'pages/help': ['/help'],
+ 'pages/help-detail': ['/help/detail'],
+ 'pages/help-guide': ['/help/guide'],
+ 'pages/withdraw': ['/withdraw', '/agent/withdraw'],
+ 'pages/report-result-webview': ['/app/report', '/report'],
+ 'pages/report-example-webview': ['/app/example', '/example'],
+ 'pages/privacy-policy': ['/privacyPolicy'],
+ 'pages/user-agreement': ['/userAgreement'],
+ 'pages/agent-manage-agreement': ['/agentManageAgreement'],
+ 'pages/agent-service-agreement': ['/agentSerivceAgreement'],
+ 'pages/authorization': ['/authorization'],
+ 'pages/payment-result': ['/payment/result'],
+ 'pages/inquire': ['/inquire'],
+ 'pages/login': ['/login'],
+ 'pages/invitation': ['/agent/invitation'],
+ 'pages/agent-promote-details': ['/agent/promoteDetails'],
+ 'pages/agent-rewards-details': ['/agent/rewardsDetails'],
+ 'pages/agent-vip': ['/agent/agentVip'],
+ 'pages/agent-vip-apply': ['/agent/vipApply'],
+ 'pages/agent-vip-config': ['/agent/vipConfig'],
+ 'pages/withdraw-details': ['/agent/withdrawDetails'],
+ 'pages/subordinate-list': ['/agent/subordinateList'],
+ 'pages/cancel-account': ['/cancelAccount'],
+ 'pages/subordinate-detail': ['/agent/subordinateDetail'],
+ 'pages/invitation-agent-apply': ['/agent/invitationAgentApply'],
+ 'pages/report-share': ['/report/share'],
}
-export function getWebPathForNotification(): string {
+/**
+ * 获取当前 uni 页面在 web 端可能的所有路径(用于通知匹配)
+ */
+export function getWebPathsForNotification(): string[] {
const r = getCurrentUniRoute()
const pages = getCurrentPages()
const page = pages[pages.length - 1] as any
const q: Record = { ...(page?.options || {}) }
+
+ // 动态路由:带参数的路径
if (r === 'pages/inquire' && q.feature)
- return `/inquire/${q.feature}`
+ return [`/inquire/${q.feature}`]
if (r === 'pages/subordinate-detail' && q.id)
- return `/agent/subordinateDetail/${q.id}`
+ return [`/agent/subordinateDetail/${q.id}`]
if (r === 'pages/invitation-agent-apply' && q.linkIdentifier)
- return `/agent/invitationAgentApply/${q.linkIdentifier}`
+ return [`/agent/invitationAgentApply/${q.linkIdentifier}`]
if (r === 'pages/report-share' && q.linkIdentifier)
- return `/report/share/${q.linkIdentifier}`
- return UNI_TO_WEB_NOTIFY_PATH[r] || '/'
+ return [`/report/share/${q.linkIdentifier}`]
+
+ return UNI_TO_WEB_NOTIFY_PATHS[r] || ['/']
+}
+
+export function getWebPathForNotification(): string {
+ return getWebPathsForNotification()[0] ?? '/'
}
const uniRouteToName: Record = {
diff --git a/src/composables/useApiFetch.ts b/src/composables/useApiFetch.ts
index e4a5aab..28544e3 100644
--- a/src/composables/useApiFetch.ts
+++ b/src/composables/useApiFetch.ts
@@ -94,7 +94,7 @@ function uniRequest(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
url: string,
data?: unknown,
-): Promise<{ statusCode: number, data: ApiEnvelope }> {
+): Promise<{ statusCode: number, data: ApiEnvelope, header: Record }> {
return new Promise((resolve, reject) => {
if (!hasAcceptedPrivacyPolicy()) {
reject(new Error('用户未同意隐私政策,禁止请求'))
@@ -114,6 +114,7 @@ function uniRequest(
resolve({
statusCode: res.statusCode || 0,
data: res.data as ApiEnvelope,
+ header: (res.header || {}) as Record,
})
},
fail: (err) => {
@@ -134,10 +135,24 @@ async function executeJson(
if (!silent)
showLoading()
try {
- const { statusCode, data: body } = await uniRequest(method, url, data)
+ const { statusCode, data: body, header: respHeader } = await uniRequest(method, url, data)
if (!silent)
hideLoading()
+ // 检测会员过期响应头,自动刷新代理信息
+ console.log('[MembershipExpired] respHeader:', JSON.stringify(respHeader))
+ if (respHeader && (respHeader['X-Membership-Expired'] === 'true' || respHeader['x-membership-expired'] === 'true')) {
+ console.log('[MembershipExpired] 检测到过期响应头')
+ const agentStore = useAgentStore()
+ console.log('[MembershipExpired] 当前 level:', agentStore.level)
+ if (agentStore.level !== 'normal') {
+ console.log('[MembershipExpired] 触发 fetchAgentStatus')
+ agentStore.fetchAgentStatus()
+ } else {
+ console.log('[MembershipExpired] 已是 normal,跳过刷新')
+ }
+ }
+
if (statusCode === 401) {
clearAuthStorage()
navigateLogin()
diff --git a/src/composables/useGlobalNotification.js b/src/composables/useGlobalNotification.js
new file mode 100644
index 0000000..dc9db04
--- /dev/null
+++ b/src/composables/useGlobalNotification.js
@@ -0,0 +1,174 @@
+import { ref } from 'vue'
+import { getWebPathForNotification, getWebPathsForNotification } from '@/composables/uni-router'
+import useApiFetch from '@/composables/useApiFetch'
+
+const SESSION_KEY = 'bdrp_app_shown_notifications'
+
+const showPopup = ref(false)
+const currentNotify = ref(null)
+const notify = ref([])
+const pendingNotifyQueue = ref([])
+const shownNotificationKeys = ref(new Set())
+
+function buildNotificationKey(n) {
+ return [
+ n.title ?? '',
+ n.content ?? '',
+ n.notificationPage ?? '',
+ n.startDate ?? '',
+ n.endDate ?? '',
+ n.startTime ?? '',
+ n.endTime ?? '',
+ ].join('|')
+}
+
+function loadShownNotificationKeys() {
+ try {
+ const raw = uni.getStorageSync(SESSION_KEY)
+ if (raw) {
+ const parsed = JSON.parse(raw)
+ if (Array.isArray(parsed))
+ shownNotificationKeys.value = new Set(parsed)
+ }
+ }
+ catch {
+ shownNotificationKeys.value = new Set()
+ }
+}
+
+function saveShownNotificationKeys() {
+ uni.setStorageSync(SESSION_KEY, JSON.stringify([...shownNotificationKeys.value]))
+}
+
+function hasShownNotification(n) {
+ return shownNotificationKeys.value.has(buildNotificationKey(n))
+}
+
+function markNotificationShown(n) {
+ shownNotificationKeys.value.add(buildNotificationKey(n))
+ saveShownNotificationKeys()
+}
+
+function showNextNotification() {
+ const next = pendingNotifyQueue.value.shift()
+ if (!next) {
+ currentNotify.value = null
+ showPopup.value = false
+ console.log('[通知DEBUG] showNextNotification: 队列为空,关闭弹窗')
+ return
+ }
+ currentNotify.value = next
+ showPopup.value = true
+ markNotificationShown(next)
+ console.log('[通知DEBUG] showNextNotification: 展示通知, title=', next.title)
+}
+
+function isWithinTimeRange(startTime, endTime) {
+ const s = (startTime || '').trim()
+ const e = (endTime || '').trim()
+ if (!s && !e)
+ return true
+ if (s === e)
+ return true
+
+ const now = new Date()
+ const currentMinutes = now.getHours() * 60 + now.getMinutes()
+ const toMinutes = (t) => {
+ const parts = t.split(':').map(Number)
+ return (parts[0] ?? 0) * 60 + (parts[1] ?? 0)
+ }
+ const startMinutes = toMinutes(s)
+ const endMinutes = toMinutes(e)
+
+ if (endMinutes < startMinutes)
+ return currentMinutes >= startMinutes || currentMinutes <= endMinutes
+ return currentMinutes >= startMinutes && currentMinutes <= endMinutes
+}
+
+function matchesNotificationPage(page) {
+ const p = (page || '').trim()
+ const paths = getWebPathsForNotification()
+ // 当前页面的所有可能 web 路径中,有一个与配置路径匹配即可
+ for (const cur of paths) {
+ if (p === cur)
+ return true
+ if (p === '/' && (cur === '/' || cur === ''))
+ return true
+ // 兼容:/report 与 /app/report
+ if (cur.startsWith('/app/') && p === cur.replace('/app/', '/'))
+ return true
+ }
+ return false
+}
+
+function checkNotification() {
+ if (showPopup.value) {
+ console.log('[通知DEBUG] checkNotification: 弹窗已显示,跳过')
+ return
+ }
+
+ const curPath = getWebPathForNotification()
+ console.log('[通知DEBUG] checkNotification: 当前页面路径=', curPath, '通知数量=', notify.value.length)
+
+ const matched = []
+ for (const notification of notify.value) {
+ const isTimeValid = isWithinTimeRange(notification.startTime, notification.endTime)
+ const isPageValid = matchesNotificationPage(notification.notificationPage)
+ const isShown = hasShownNotification(notification)
+ console.log('[通知DEBUG] 通知项:', JSON.stringify({
+ title: notification.title,
+ page: notification.notificationPage,
+ curPath,
+ startTime: notification.startTime,
+ endTime: notification.endTime,
+ isTimeValid,
+ isPageValid,
+ isShown,
+ }))
+
+ if (isTimeValid && isPageValid && !isShown)
+ matched.push(notification)
+ }
+
+ console.log('[通知DEBUG] 匹配到的通知数量:', matched.length)
+ pendingNotifyQueue.value = matched
+ showNextNotification()
+}
+
+async function getGlobalNotify() {
+ console.log('[通知DEBUG] getGlobalNotify 开始请求')
+ const { data, error } = await useApiFetch('/notification/list', { silent: true }).get().json()
+ console.log('[通知DEBUG] 请求完成, data:', JSON.stringify(data.value), 'error:', error.value)
+ if (!data.value || error.value) {
+ console.log('[通知DEBUG] 数据为空或出错,退出')
+ return
+ }
+ if (data.value.code !== 200) {
+ console.log('[通知DEBUG] code 不为 200,code:', data.value.code)
+ return
+ }
+ const raw = data.value.data
+ console.log('[通知DEBUG] raw data:', JSON.stringify(raw))
+ const list = raw && typeof raw === 'object' && 'notifications' in raw
+ ? raw.notifications
+ : []
+ notify.value = Array.isArray(list) ? list : []
+ console.log('[通知DEBUG] notify list 长度:', notify.value.length, '内容:', JSON.stringify(notify.value))
+ checkNotification()
+}
+
+function onClosePopup() {
+ showNextNotification()
+}
+
+export function useGlobalNotification() {
+ return {
+ showPopup,
+ currentNotify,
+ notify,
+ loadShownNotificationKeys,
+ getGlobalNotify,
+ checkNotification,
+ onClosePopup,
+ }
+}
diff --git a/src/layouts/default.vue b/src/layouts/default.vue
index 6fc7828..7539b1b 100644
--- a/src/layouts/default.vue
+++ b/src/layouts/default.vue
@@ -4,21 +4,26 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
import {
getCurrentUniRoute,
getLayoutPageTitle,
- getWebPathForNotification,
useRouter,
} from '@/composables/uni-router'
-import useApiFetch from '@/composables/useApiFetch'
import { ensurePageAccessByUrl } from '@/composables/useNavigationAuthGuard'
+import { useGlobalNotification } from '@/composables/useGlobalNotification'
const router = useRouter()
-const showPopup = ref(false)
-const notify = ref([])
-const currentNotify = ref(null)
const pageTitle = ref('赤眉')
const currentRoute = ref('')
const safeAreaTop = ref(0)
const immersiveNavbarSolid = ref(false)
+const {
+ showPopup,
+ currentNotify,
+ loadShownNotificationKeys,
+ getGlobalNotify,
+ checkNotification,
+ onClosePopup,
+} = useGlobalNotification()
+
const immersiveRoutes = new Set([
'pages/inquire',
])
@@ -63,55 +68,8 @@ function getSafeAreaTop() {
})
}
-onMounted(() => {
- getSafeAreaTop()
- syncTitle()
- getGlobalNotify()
-})
-
-onShow(() => {
- syncTitle()
- ensureAuthIfNeeded()
- checkNotification()
-})
-
-async function getGlobalNotify() {
- const { data, error } = await useApiFetch('/notification/list', { silent: true }).get().json()
- if (!data.value || error.value)
- return
- if (data.value.code !== 200)
- return
- const raw = data.value.data
- const list = raw && typeof raw === 'object' && 'notifications' in raw
- ? raw.notifications
- : []
- notify.value = Array.isArray(list) ? list : []
- checkNotification()
-}
-
-function isWithinTimeRange(startTime, endTime) {
- const now = new Date()
- const currentMinutes = now.getHours() * 60 + now.getMinutes()
- const startParts = startTime.split(':').map(Number)
- const endParts = endTime.split(':').map(Number)
- const startMinutes = startParts[0] * 60 + startParts[1]
- const endMinutes = endParts[0] * 60 + endParts[1]
- if (endMinutes < startMinutes)
- return currentMinutes >= startMinutes || currentMinutes < endMinutes
- return currentMinutes >= startMinutes && currentMinutes <= endMinutes
-}
-
-function checkNotification() {
- const webPath = getWebPathForNotification()
- showPopup.value = false
- for (const notification of notify.value) {
- const isTimeValid = isWithinTimeRange(notification.startTime, notification.endTime)
- if (isTimeValid && notification.notificationPage === webPath) {
- currentNotify.value = notification
- showPopup.value = true
- break
- }
- }
+function onClickOverlay() {
+ onClosePopup()
}
function onClickLeft() {
@@ -124,6 +82,21 @@ function onImmersiveNavbarChange(payload) {
immersiveNavbarSolid.value = Boolean(payload.solid)
}
+onMounted(() => {
+ getSafeAreaTop()
+ syncTitle()
+ loadShownNotificationKeys()
+ console.log('[通知DEBUG] default layout onMounted, 即将请求通知')
+ getGlobalNotify()
+})
+
+onShow(() => {
+ syncTitle()
+ ensureAuthIfNeeded()
+ console.log('[通知DEBUG] default layout onShow')
+ checkNotification()
+})
+
onMounted(() => {
uni.$on('immersive-navbar-change', onImmersiveNavbarChange)
})
@@ -150,11 +123,14 @@ onUnmounted(() => {
/>
-
-
-
+ @click="selectedTeamDate = item.value">
{{ item.label }}
diff --git a/src/pages/cancel-account.vue b/src/pages/cancel-account.vue
index 5dfc92b..83850af 100644
--- a/src/pages/cancel-account.vue
+++ b/src/pages/cancel-account.vue
@@ -206,34 +206,24 @@ async function submitCancelAccount() {
-
+
-
-
+
+
钱包提示
- 检测到您为代理且账户仍有余额(¥{{ (revenueData?.balance ?? 0).toFixed(2) }})或待结账金额(¥{{ (revenueData?.frozen_balance ?? 0).toFixed(2) }})。注销后将无法通过本账号提现,请确认已了解风险。
+ 检测到您为代理且账户仍有余额(¥{{ (revenueData?.balance ?? 0).toFixed(2) }})或冻结余额(¥{{ (revenueData?.frozen_balance ??
+ 0).toFixed(2) }})。注销后将无法通过本账号提现,请确认已了解风险。
-
+
会员提示
@@ -246,22 +236,10 @@ async function submitCancelAccount() {
-
+
-
+