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(() => { + +