import Taro from '@tarojs/taro' import { Binding, getBindings } from './binding' import { DeviceAlarmItem, getDeviceAlarms } from './device' const LAST_SEEN_ALARM_MAP_KEY = 'systemBannerLastSeenAlarmMap' const DEVICE_ALARM_FETCH_LIMIT = 20 const LAST_POLL_TIME_KEY = 'systemBannerLastPollTime' type LastSeenAlarmMap = Record export interface SystemBannerNotification { key: string title: string summary: string detail: string timeLabel: string route: string alarmId: number deviceId: string } function readLastSeenAlarmMap(): LastSeenAlarmMap { const storedValue = Taro.getStorageSync(LAST_SEEN_ALARM_MAP_KEY) if (!storedValue || typeof storedValue !== 'object') return {} return storedValue as LastSeenAlarmMap } function writeLastSeenAlarmMap(map: LastSeenAlarmMap) { Taro.setStorageSync(LAST_SEEN_ALARM_MAP_KEY, map) } function readLastPollTime(): number { const value = Number(Taro.getStorageSync(LAST_POLL_TIME_KEY) || 0) return Number.isFinite(value) ? value : 0 } function writeLastPollTime(value: number) { Taro.setStorageSync(LAST_POLL_TIME_KEY, value) } function setDeviceAlarmSeen(deviceId: string, alarmId: number, map: LastSeenAlarmMap) { map[deviceId] = alarmId } function hasDeviceAlarmBaseline(deviceId: string, map: LastSeenAlarmMap): boolean { return Object.prototype.hasOwnProperty.call(map, deviceId) } function getAlarmChildLabel(alarm: DeviceAlarmItem, fallbackChildName?: string | null): string { const childName = String(alarm.child_name || '').trim() if (childName) return childName const fallbackName = String(fallbackChildName || '').trim() if (fallbackName) return fallbackName if (alarm.child_id) return `儿童 ${alarm.child_id}` return '当前孩子' } function getAlarmTypeLabel(sourceMsgId?: string | null): string { const normalizedValue = String(sourceMsgId || '').trim() if (normalizedValue === '010') return '紧急告警' if (!normalizedValue) return '设备提醒' return normalizedValue } function formatBannerTime(value?: string | null): string { if (!value) return '' const date = new Date(value) if (Number.isNaN(date.getTime())) return '' const hour = `${date.getHours()}`.padStart(2, '0') const minute = `${date.getMinutes()}`.padStart(2, '0') return `${hour}:${minute}` } function buildDeviceAlarmNotification( alarm: DeviceAlarmItem, fallbackChildName?: string | null ): SystemBannerNotification { const childLabel = getAlarmChildLabel(alarm, fallbackChildName) const alarmTypeLabel = getAlarmTypeLabel(alarm.source_msg_id) return { key: `device-alarm-${alarm.device_id}-${alarm.alarm_id}`, title: '系统消息', summary: `${childLabel} 的伴伴触发了${alarmTypeLabel}`, detail: `设备 ${alarm.device_id}`, timeLabel: formatBannerTime(alarm.created_at), route: '/pages/device/index', alarmId: alarm.alarm_id, deviceId: alarm.device_id, } } function sortAlarmsByCreatedAt(left: DeviceAlarmItem, right: DeviceAlarmItem): number { const leftTime = new Date(left.created_at).getTime() const rightTime = new Date(right.created_at).getTime() return leftTime - rightTime } export async function pollSystemBannerNotifications(): Promise { const bindingResponse = await getBindings(undefined, 100) const bindings = bindingResponse.items || [] if (bindings.length === 0) return [] const lastSeenAlarmMap = readLastSeenAlarmMap() const previousPollTime = readLastPollTime() const currentPollTime = Date.now() let hasMapChanged = false const alarmResults = await Promise.allSettled( bindings.map(async (binding) => ({ binding, response: await getDeviceAlarms(binding.device_id, DEVICE_ALARM_FETCH_LIMIT), })) ) const nextNotifications: Array<{ alarm: DeviceAlarmItem; binding: Binding }> = [] alarmResults.forEach((result) => { if (result.status !== 'fulfilled') return const { binding, response } = result.value const deviceId = String(binding.device_id || '').trim() if (!deviceId) return const alarms = response.items || [] const latestAlarmId = alarms[0]?.alarm_id || 0 const hasBaseline = hasDeviceAlarmBaseline(deviceId, lastSeenAlarmMap) const knownAlarmId = Number(lastSeenAlarmMap[deviceId] || 0) // Establish a baseline on first poll so old history does not flood the user. if (!hasBaseline) { setDeviceAlarmSeen(deviceId, latestAlarmId, lastSeenAlarmMap) hasMapChanged = true return } if (latestAlarmId < knownAlarmId) { setDeviceAlarmSeen(deviceId, latestAlarmId, lastSeenAlarmMap) hasMapChanged = true return } if (latestAlarmId === knownAlarmId) { return } setDeviceAlarmSeen(deviceId, latestAlarmId, lastSeenAlarmMap) hasMapChanged = true alarms .filter((alarm) => { if (alarm.alarm_id <= knownAlarmId) return false if (previousPollTime <= 0) return true const createdAt = new Date(alarm.created_at).getTime() if (Number.isNaN(createdAt)) return true return createdAt >= previousPollTime }) .forEach((alarm) => { nextNotifications.push({ alarm, binding }) }) }) if (hasMapChanged) { writeLastSeenAlarmMap(lastSeenAlarmMap) } writeLastPollTime(currentPollTime) return nextNotifications .sort((left, right) => sortAlarmsByCreatedAt(left.alarm, right.alarm)) .map(({ alarm, binding }) => buildDeviceAlarmNotification(alarm, binding.child_name)) }