236 lines
9.3 KiB
TypeScript
236 lines
9.3 KiB
TypeScript
import { useState, useCallback, useEffect } from 'react'
|
||
import { useDidShow } from '@tarojs/taro'
|
||
import { View, Text } from '@tarojs/components'
|
||
import { Navbar } from '@/components/navbar'
|
||
import { Empty } from '@/components/empty'
|
||
import { getHomeList, type HomeInfo } from '@/api/home'
|
||
import { resolveCurrentHome } from '@/store/currentHome'
|
||
import { getRoomList, type HomeRoom } from '@/api/homeRoom'
|
||
import { getAlarmList, type HomeAlarm } from '@/api/homeAlarm'
|
||
import { navigateTo } from '@/utils/navigation'
|
||
import { ROUTES } from '@/constants/routes'
|
||
import { CalendarDrawer } from './calendar-drawer'
|
||
import styles from './index.module.scss'
|
||
|
||
interface FilterOption {
|
||
id: string // '' 表示全部
|
||
name: string
|
||
}
|
||
|
||
// 告警状态枚举(2026-09-23 后端确认:0-待处理 1-已忽略 2-已处理 3-自动处理)
|
||
const ALARM_STATUS: Record<string, string> = {
|
||
'0': '待处理',
|
||
'1': '已忽略',
|
||
'2': '已处理',
|
||
'3': '自动处理'
|
||
}
|
||
|
||
export default function Event() {
|
||
// '' | 'room' | 'status' | 'time'
|
||
const [drawer, setDrawer] = useState<'' | 'room' | 'status' | 'time'>('')
|
||
const [alarmList, setAlarmList] = useState<HomeAlarm[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [filters, setFilters] = useState({
|
||
roomId: '',
|
||
status: '',
|
||
startDate: '',
|
||
endDate: '',
|
||
timeText: ''
|
||
})
|
||
const [home, setHome] = useState<HomeInfo | null>(null)
|
||
const [roomOptions, setRoomOptions] = useState<FilterOption[]>([{ id: '', name: '全部' }])
|
||
const statusOptions: FilterOption[] = [
|
||
{ id: '', name: '全部' },
|
||
{ id: '0', name: '待处理' },
|
||
{ id: '1', name: '已忽略' },
|
||
{ id: '2', name: '已处理' },
|
||
{ id: '3', name: '自动处理' }
|
||
]
|
||
|
||
// 当前家庭 + 其房间列表:与智能设备页共享选择(resolveCurrentHome 读持久化),
|
||
// 失败时保留"全部"选项不阻塞筛选
|
||
useEffect(() => {
|
||
getHomeList()
|
||
.then(async (list) => {
|
||
const first = resolveCurrentHome(list)
|
||
setHome(first)
|
||
if (!first) return
|
||
const rooms = await getRoomList(first.homeId)
|
||
setRoomOptions([{ id: '', name: '全部' }, ...rooms.map(toRoomOption)])
|
||
})
|
||
.catch(() => setRoomOptions([{ id: '', name: '全部' }]))
|
||
}, [])
|
||
|
||
useDidShow(() => {
|
||
// 进页首拉:home 就绪与否都调一次,home 为空时后端会报错并保持空态
|
||
if (home) loadAlarms(filters)
|
||
})
|
||
|
||
const toRoomOption = (r: HomeRoom): FilterOption => ({ id: r.roomId, name: r.name })
|
||
|
||
const closeDrawer = useCallback(() => setDrawer(''), [])
|
||
|
||
const roomLabel = roomOptions.find((o) => o.id === filters.roomId)?.name ?? '全部场所'
|
||
const statusLabel = statusOptions.find((o) => o.id === filters.status)?.name ?? '全部状态'
|
||
|
||
// 拉取告警列表:home 未就绪时跳过(useEffect 到达后由 loadAlarms 首拉兜底)
|
||
const loadAlarms = useCallback(async (next: typeof filters, homeId?: string) => {
|
||
const targetHomeId = homeId ?? home?.homeId
|
||
if (!targetHomeId) return
|
||
setLoading(true)
|
||
try {
|
||
const list = await getAlarmList({
|
||
homeId: targetHomeId,
|
||
roomId: next.roomId || undefined,
|
||
status: next.status ? Number(next.status) : undefined,
|
||
// 接口要求完整时分秒:起止日拼边界值覆盖整天
|
||
beginTime: next.startDate ? `${next.startDate} 00:00:00` : undefined,
|
||
endTime: next.endDate ? `${next.endDate} 23:59:59` : undefined
|
||
})
|
||
setAlarmList(list ?? [])
|
||
} catch {
|
||
// 失败保持现有列表,toast 由请求封装统一处理
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}, [home?.homeId])
|
||
|
||
// home 就绪后的首拉(初始 useDidShow 时 home 可能还没到)
|
||
useEffect(() => {
|
||
if (home) loadAlarms(filters, home.homeId)
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [home?.homeId])
|
||
|
||
const applyFilter = (next: typeof filters) => {
|
||
setFilters(next)
|
||
closeDrawer()
|
||
loadAlarms(next)
|
||
}
|
||
|
||
const renderOptionDrawer = (kind: 'room' | 'status') => {
|
||
const opts = kind === 'room' ? roomOptions : statusOptions
|
||
const currentId = kind === 'room' ? filters.roomId : filters.status
|
||
const title = kind === 'room' ? '选择场所' : '选择状态'
|
||
const onSelect = (id: string) =>
|
||
applyFilter(kind === 'room' ? { ...filters, roomId: id } : { ...filters, status: id })
|
||
return (
|
||
<View className={styles.dropPanelOptions}>
|
||
<View className={styles.drawerHeader}>
|
||
<Text className={styles.drawerTitle}>{title}</Text>
|
||
<View className={styles.drawerClose} onClick={closeDrawer}>
|
||
<Text className={styles.closeIcon}>×</Text>
|
||
</View>
|
||
</View>
|
||
<View className={styles.drawerList}>
|
||
{opts.map((opt) => (
|
||
<View
|
||
key={opt.id}
|
||
className={`${styles.drawerItem} ${currentId === opt.id ? styles.drawerItemActive : ''}`}
|
||
onClick={() => onSelect(opt.id)}
|
||
>
|
||
<Text className={styles.drawerItemText}>{opt.name}</Text>
|
||
{currentId === opt.id && <Text className={styles.checkIcon}>✓</Text>}
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
const statusTagOf = (alarm: HomeAlarm) => ALARM_STATUS[String(alarm.status)] ?? '未知'
|
||
|
||
// CSS Modules 动态 key 映射对象(避免索引签名问题)
|
||
const tagClsMap: Record<string, string> = {
|
||
'0': styles.tag1, // 待处理-红
|
||
'1': styles.tag3, // 已忽略-灰
|
||
'2': styles.tag2, // 已处理-绿
|
||
'3': styles.tag3 // 自动处理-灰
|
||
}
|
||
|
||
return (
|
||
<View className={styles.page}>
|
||
<Navbar title="消息中心" autoBack={false} variant="hero" />
|
||
|
||
{/* 筛选栏:三格等分 */}
|
||
<View className={styles.filters}>
|
||
<View className={styles.filterCell} onClick={() => setDrawer(drawer === 'room' ? '' : 'room')}>
|
||
<View className={`${styles.filterTrigger} ${filters.roomId ? styles.filterTriggerActive : ''}`}>
|
||
<Text className={styles.filterText}>{roomLabel}</Text>
|
||
<Text className={styles.filterArrow}>▾</Text>
|
||
</View>
|
||
</View>
|
||
<View className={styles.filterCell} onClick={() => setDrawer(drawer === 'status' ? '' : 'status')}>
|
||
<View className={`${styles.filterTrigger} ${filters.status ? styles.filterTriggerActive : ''}`}>
|
||
<Text className={styles.filterText}>{statusLabel}</Text>
|
||
<Text className={styles.filterArrow}>▾</Text>
|
||
</View>
|
||
</View>
|
||
<View className={styles.filterCell} onClick={() => setDrawer(drawer === 'time' ? '' : 'time')}>
|
||
<View className={`${styles.filterTrigger} ${filters.startDate ? styles.filterTriggerActive : ''}`}>
|
||
<Text className={styles.filterText}>{filters.timeText || '时间'}</Text>
|
||
<Text className={styles.filterArrow}>▾</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 数据层:抽屉锚点(筛选栏下缘展开,遮罩盖住本层全部) */}
|
||
<View className={styles.contentArea}>
|
||
<View className={styles.contentBody}>
|
||
{alarmList.length === 0 ? (
|
||
<>
|
||
<View className={styles.emptyArea}>
|
||
<Empty text="暂无告警数据" mode="message" />
|
||
</View>
|
||
<View className={styles.emptyHint}>
|
||
<Text>调整筛选条件后的告警列表将在此展示</Text>
|
||
</View>
|
||
</>
|
||
) : (
|
||
<View className={styles.alarmList}>
|
||
{alarmList.map((alarm) => (
|
||
<View
|
||
key={alarm.alarmId}
|
||
className={styles.alarmCard}
|
||
hoverClass={styles.alarmCardPressed}
|
||
onClick={() => navigateTo(`${ROUTES.EVENT_DETAIL}?id=${alarm.alarmId}`)}
|
||
>
|
||
<View className={styles.alarmHead}>
|
||
<View className={styles.alarmTypeWrap}>
|
||
<View className={styles.alarmTypeDot} />
|
||
<Text className={styles.alarmType}>{alarm.content || '告警消息'}</Text>
|
||
</View>
|
||
<Text className={`${styles.alarmTag} ${tagClsMap[String(alarm.status)] ?? ''}`}>{statusTagOf(alarm)}</Text>
|
||
</View>
|
||
<View className={styles.alarmRow}>
|
||
<Text className={styles.alarmLabel}>设备</Text>
|
||
<Text className={styles.alarmValue}>{alarm.deviceAlias || alarm.deviceName || alarm.deviceId}</Text>
|
||
</View>
|
||
<View className={styles.alarmRow}>
|
||
<Text className={styles.alarmLabel}>时间</Text>
|
||
<Text className={styles.alarmValue}>{alarm.createTime}</Text>
|
||
</View>
|
||
</View>
|
||
))}
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
{drawer !== '' && <View className={styles.contentMask} onClick={closeDrawer} catchMove />}
|
||
|
||
{drawer === 'room' && renderOptionDrawer('room')}
|
||
{drawer === 'status' && renderOptionDrawer('status')}
|
||
{drawer === 'time' && (
|
||
<CalendarDrawer
|
||
initialStart={filters.startDate}
|
||
initialEnd={filters.endDate}
|
||
onClose={closeDrawer}
|
||
onConfirm={(start, end) =>
|
||
applyFilter({ ...filters, startDate: start, endDate: end, timeText: `${start} 至 ${end}` })
|
||
}
|
||
/>
|
||
)}
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|