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 @@
+
+
+
+
+
+
+
+
+
+
+
+ 设备授权将于 {{ days }} 天后到期,到期后将锁定全部作业功能,请及时续期以免影响正常作业
+
+
+
+
+
+ {{ deviceName }}
+
+
+ 设备ID: {{ deviceIdText }}
+
+
+
+
+
+ 授权到期日期
+
+
+ {{ endDateText }}
+
+
+
+
+
+
+
+
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(() => {
+
+