From c7694df2cc9b3ce2038fa49937489cd35233a300 Mon Sep 17 00:00:00 2001 From: Ironsp <1522278303@qq.com> Date: Thu, 24 Sep 2026 15:12:38 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E8=81=94=E8=B0=83=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=BF=80=E6=B4=BB=E7=A0=81=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components.d.ts | 1 + src/components/expireNotice.vue | 285 +++++++++++++++++++++++++++++++ src/composables/useDeviceAuth.ts | 74 +++++++- src/pages/Ahome/index.vue | 25 ++- 4 files changed, 379 insertions(+), 6 deletions(-) create mode 100644 src/components/expireNotice.vue diff --git a/src/components.d.ts b/src/components.d.ts index 99f6fba..7cd9e30 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -11,6 +11,7 @@ declare module 'vue' { BluetoothConnect: typeof import('./components/bluetooth/connect.vue')['default'] DeviceCard: typeof import('./components/deviceCard.vue')['default'] DeviceInfo: typeof import('./components/deviceInfo.vue')['default'] + ExpireNotice: typeof import('./components/expireNotice.vue')['default'] FullscreenNav: typeof import('./components/FullscreenNav.vue')['default'] HeatmapCanvas: typeof import('./components/heatmap/canvas.vue')['default'] HeatmapLayerPanel: typeof import('./components/heatmap/layerPanel.vue')['default'] diff --git a/src/components/expireNotice.vue b/src/components/expireNotice.vue new file mode 100644 index 0000000..9bda283 --- /dev/null +++ b/src/components/expireNotice.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/composables/useDeviceAuth.ts b/src/composables/useDeviceAuth.ts index 3e50a33..a8a8c57 100644 --- a/src/composables/useDeviceAuth.ts +++ b/src/composables/useDeviceAuth.ts @@ -10,7 +10,24 @@ const AUTH_INFO_KEY = 'device_auth_info' const ACTIVATION_CODE_KEY = 'device_activation_code' function readCachedAuthInfo(): DeviceAuthStatus | null { - return storage.get(AUTH_INFO_KEY) ?? null + const cached = storage.get(AUTH_INFO_KEY) + if (cached && typeof cached === 'object') + return cached + + // 兼容异常写入:直接读原始 storage + try { + const raw = uni.getStorageSync(AUTH_INFO_KEY) + if (!raw) + return null + const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw + const value = parsed?.value ?? parsed + if (value && typeof value === 'object' && 'remainingDays' in value) + return value as DeviceAuthStatus + } + catch { + // ignore + } + return null } function toAuthStatus(value: AuthStatus | string | number | null | undefined): AuthStatus | null { @@ -58,7 +75,8 @@ function createUnauthorizedInfo(): DeviceAuthStatus { authStatus: '0', startDate: '', endDate: '', - remainingDays: 0, + // -1 表示无有效剩余天数,避免误触发到期提醒 + remainingDays: -1, } } @@ -67,6 +85,9 @@ const authInfo = ref(readCachedAuthInfo()) const activationCode = ref(storage.get(ACTIVATION_CODE_KEY, '') ?? '') const loading = ref(false) const authReady = ref(false) +/** 到期提醒弹窗显示状态(App/H5 共用,避免页面 ref 调用失效) */ +const expireNoticeVisible = ref(false) +let lastExpireShownAt = 0 /** * 进入软件:先查本地激活码 → 无码再查授权状态并写入 storage @@ -80,6 +101,49 @@ export function useDeviceAuth(options?: { watchShow?: boolean }) { const needAuthPrompt = computed(() => authStatus.value === '0' || authStatus.value === '2') const hasActivationCode = computed(() => !!activationCode.value.trim()) const deviceId = computed(() => authInfo.value?.deviceId || getDeviceId()) + const remainingDays = computed(() => { + const days = authInfo.value?.remainingDays + return days == null || Number.isNaN(Number(days)) ? null : Number(days) + }) + /** + * 从当前授权信息判断是否需到期提醒: + * storage 中 remainingDays 为 0~7(含)时弹出 + */ + const needExpireNotice = computed(() => { + const days = remainingDays.value + return days != null && days >= 0 && days <= 7 + }) + + /** 每次打开软件:先读 storage,再判断 remainingDays ≤ 7 */ + function shouldShowExpireNoticeFromStorage(): boolean { + const cached = readCachedAuthInfo() + authInfo.value = cached + if (!cached) + return false + const days = Number(cached.remainingDays) + if (Number.isNaN(days)) + return false + return days >= 0 && days <= 7 + } + + /** 打开到期提醒(由首页 onShow/onMounted 调用) */ + function openExpireNoticeIfNeeded() { + if (!shouldShowExpireNoticeFromStorage()) { + console.log('[expireNotice] skip, storage=', authInfo.value) + return false + } + const now = Date.now() + if (now - lastExpireShownAt < 1500) + return false + lastExpireShownAt = now + expireNoticeVisible.value = true + console.log('[expireNotice] open, remainingDays=', authInfo.value?.remainingDays) + return true + } + + function closeExpireNotice() { + expireNoticeVisible.value = false + } /** 将完整授权信息写入内存 + storage */ function saveAuthInfo(info: DeviceAuthStatus) { @@ -206,15 +270,21 @@ export function useDeviceAuth(options?: { watchShow?: boolean }) { activationCode, isActivated, needAuthPrompt, + needExpireNotice, hasActivationCode, deviceId, + remainingDays, loading, authReady, + expireNoticeVisible, saveAuthInfo, setActivationCode, fetchAuthStatus, initAuth, activate, refreshAuthStatus, + shouldShowExpireNoticeFromStorage, + openExpireNoticeIfNeeded, + closeExpireNotice, } } diff --git a/src/pages/Ahome/index.vue b/src/pages/Ahome/index.vue index 4455c7f..200811c 100644 --- a/src/pages/Ahome/index.vue +++ b/src/pages/Ahome/index.vue @@ -1,5 +1,6 @@ @@ -36,6 +51,8 @@ onMounted(() => { + +