176 lines
5.4 KiB
TypeScript
176 lines
5.4 KiB
TypeScript
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<string, number>
|
|
|
|
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<SystemBannerNotification[]> {
|
|
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))
|
|
}
|