feat(event): 首页/消息中心接入告警列表真实接口,移除事件 mock 层
This commit is contained in:
parent
7bef729e93
commit
5761a5fed2
|
|
@ -1,26 +0,0 @@
|
||||||
import { request } from '@/http'
|
|
||||||
|
|
||||||
const USE_MOCK = true
|
|
||||||
|
|
||||||
export interface EventItem {
|
|
||||||
id: number
|
|
||||||
sceneName: string
|
|
||||||
deviceName: string
|
|
||||||
status: number
|
|
||||||
time: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventQuery {
|
|
||||||
sceneId: number
|
|
||||||
status: number
|
|
||||||
startDate: string
|
|
||||||
endDate: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 事件列表接口未定:mock 返回空数组,页面保持空态
|
|
||||||
export function getEventList(_query: EventQuery): Promise<EventItem[]> {
|
|
||||||
if (USE_MOCK) {
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
|
||||||
return request.get<EventItem[]>('/event/list', { ..._query })
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +1,30 @@
|
||||||
import { request } from '@/http'
|
import { request } from '@/http'
|
||||||
|
|
||||||
|
// 入参对齐 apifox YlHomeAlarmListReq(homeId 必填,其余可选筛选)
|
||||||
|
export interface AlarmListReq {
|
||||||
|
homeId: string
|
||||||
|
roomId?: string // 传入则只查该房间告警
|
||||||
|
status?: number // 告警状态,不传则不筛选
|
||||||
|
beginTime?: string // yyyy-MM-dd HH:mm:ss
|
||||||
|
endTime?: string
|
||||||
|
pageIndex?: number
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
// 骨架:本期仅锁定接口路径,字段类型联调时修正(spec D7)
|
// 骨架:本期仅锁定接口路径,字段类型联调时修正(spec D7)
|
||||||
export interface HomeAlarm {
|
export interface HomeAlarm {
|
||||||
alarmId: string
|
alarmId: string
|
||||||
|
homeId: string
|
||||||
|
roomId: string
|
||||||
deviceId: string
|
deviceId: string
|
||||||
|
deviceName: string
|
||||||
alarmType: string
|
alarmType: string
|
||||||
alarmTime: string
|
alarmTime: string
|
||||||
handled: boolean
|
status: number // 告警状态:0-未处理 1-已处理 2-已忽略(待实测确认枚举)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAlarmList(params: Record<string, unknown>): Promise<HomeAlarm[]> {
|
export function getAlarmList(params: AlarmListReq): Promise<HomeAlarm[]> {
|
||||||
return request.get('/yanglao/app/ylHomeAlarm/getList', params)
|
return request.post('/yanglao/app/ylHomeAlarm/getList', params as unknown as Record<string, unknown>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAlarmDetail(alarmId: string): Promise<HomeAlarm> {
|
export function getAlarmDetail(alarmId: string): Promise<HomeAlarm> {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ export interface HomeRoom {
|
||||||
roomName: string
|
roomName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文档路径为 POST + body { homeId }(YlHomeRoomListReq)
|
||||||
export function getRoomList(homeId: string): Promise<HomeRoom[]> {
|
export function getRoomList(homeId: string): Promise<HomeRoom[]> {
|
||||||
return request.get('/yanglao/app/ylHomeRoom/getList', { homeId })
|
return request.post('/yanglao/app/ylHomeRoom/getList', { homeId })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addRoom(params: Record<string, unknown>): Promise<void> {
|
export function addRoom(params: Record<string, unknown>): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,78 @@
|
||||||
color: $color-text-tertiary;
|
color: $color-text-tertiary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 告警卡片列表 */
|
||||||
|
.alarm-list {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-card {
|
||||||
|
background: $color-bg-white;
|
||||||
|
border-radius: $radius-lg;
|
||||||
|
box-shadow: $shadow-card;
|
||||||
|
padding: 28px 32px;
|
||||||
|
|
||||||
|
& + & { margin-top: 20px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-type {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $color-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-tag {
|
||||||
|
font-size: 22px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
border-radius: $radius-pill;
|
||||||
|
color: $color-text-secondary;
|
||||||
|
background: $color-bg-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态枚举待实测,先按未处理红色、已处理绿色、已忽略灰的常见语义 */
|
||||||
|
.tag1 {
|
||||||
|
color: $color-danger;
|
||||||
|
background: $color-danger-light-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag2 {
|
||||||
|
color: $color-primary-deep;
|
||||||
|
background: $color-primary-light-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag3 {
|
||||||
|
color: $color-text-tertiary;
|
||||||
|
background: $color-bg-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-label {
|
||||||
|
width: 120px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 26px;
|
||||||
|
color: $color-text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-value {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 26px;
|
||||||
|
color: $color-text;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
/* 数据层:三抽屉的定位锚点 */
|
/* 数据层:三抽屉的定位锚点 */
|
||||||
.content-area {
|
.content-area {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,112 @@
|
||||||
import { useState, useCallback, useEffect } from 'react'
|
import { useState, useCallback, useEffect } from 'react'
|
||||||
|
import { useDidShow } from '@tarojs/taro'
|
||||||
import { View, Text } from '@tarojs/components'
|
import { View, Text } from '@tarojs/components'
|
||||||
import { Navbar } from '@/components/navbar'
|
import { Navbar } from '@/components/navbar'
|
||||||
import { Empty } from '@/components/empty'
|
import { Empty } from '@/components/empty'
|
||||||
import { getSceneList } from '@/api/scene'
|
import { getHomeList, type HomeInfo } from '@/api/home'
|
||||||
import { getEventList, type EventItem } from '@/api/event'
|
import { getRoomList, type HomeRoom } from '@/api/homeRoom'
|
||||||
|
import { getAlarmList, type HomeAlarm } from '@/api/homeAlarm'
|
||||||
import { CalendarDrawer } from './calendar-drawer'
|
import { CalendarDrawer } from './calendar-drawer'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
id: number
|
id: string // '' 表示全部
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 告警状态筛选枚举(后端 integer,待实测确认:1-未处理 2-已处理 3-已忽略)
|
||||||
|
const ALARM_STATUS: Record<string, string> = {
|
||||||
|
'1': '未处理',
|
||||||
|
'2': '已处理',
|
||||||
|
'3': '已忽略'
|
||||||
|
}
|
||||||
|
|
||||||
export default function Event() {
|
export default function Event() {
|
||||||
// '' | 'scene' | 'status' | 'time'
|
// '' | 'room' | 'status' | 'time'
|
||||||
const [drawer, setDrawer] = useState<'' | 'scene' | 'status' | 'time'>('')
|
const [drawer, setDrawer] = useState<'' | 'room' | 'status' | 'time'>('')
|
||||||
// 事件列表:后端接口定型前 mock 返回空数组 → 空态
|
const [alarmList, setAlarmList] = useState<HomeAlarm[]>([])
|
||||||
const [eventList, setEventList] = useState<EventItem[]>([])
|
const [loading, setLoading] = useState(true)
|
||||||
const [filters, setFilters] = useState({
|
const [filters, setFilters] = useState({
|
||||||
sceneId: 0, // 0 表示全部
|
roomId: '',
|
||||||
status: 0,
|
status: '',
|
||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: '',
|
endDate: '',
|
||||||
timeText: ''
|
timeText: ''
|
||||||
})
|
})
|
||||||
const [sceneOptions, setSceneOptions] = useState<FilterOption[]>([{ id: 0, name: '全部' }])
|
const [home, setHome] = useState<HomeInfo | null>(null)
|
||||||
|
const [roomOptions, setRoomOptions] = useState<FilterOption[]>([{ id: '', name: '全部' }])
|
||||||
const statusOptions: FilterOption[] = [
|
const statusOptions: FilterOption[] = [
|
||||||
{ id: 0, name: '全部' },
|
{ id: '', name: '全部' },
|
||||||
{ id: 1, name: '未处理' },
|
{ id: '1', name: '未处理' },
|
||||||
{ id: 2, name: '已处理' },
|
{ id: '2', name: '已处理' },
|
||||||
{ id: 3, name: '已忽略' }
|
{ id: '3', name: '已忽略' }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 场所列表:复用场景接口;失败时保留"全部"不阻塞筛选
|
// 默认家庭 + 其房间列表:失败时保留"全部"选项不阻塞筛选
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getSceneList()
|
getHomeList()
|
||||||
.then((data) => {
|
.then(async (list) => {
|
||||||
setSceneOptions([{ id: 0, name: '全部' }, ...(data ?? [])])
|
const first = list[0] ?? null
|
||||||
|
setHome(first)
|
||||||
|
if (!first) return
|
||||||
|
const rooms = await getRoomList(first.homeId)
|
||||||
|
setRoomOptions([{ id: '', name: '全部' }, ...rooms.map(toRoomOption)])
|
||||||
})
|
})
|
||||||
.catch(() => setSceneOptions([{ id: 0, name: '全部' }]))
|
.catch(() => setRoomOptions([{ id: '', name: '全部' }]))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
// 进页首拉:home 就绪与否都调一次,home 为空时后端会报错并保持空态
|
||||||
|
if (home) loadAlarms(filters)
|
||||||
|
})
|
||||||
|
|
||||||
|
const toRoomOption = (r: HomeRoom): FilterOption => ({ id: r.roomId, name: r.roomName })
|
||||||
|
|
||||||
const closeDrawer = useCallback(() => setDrawer(''), [])
|
const closeDrawer = useCallback(() => setDrawer(''), [])
|
||||||
|
|
||||||
const sceneLabel = sceneOptions.find((o) => o.id === filters.sceneId)?.name ?? '全部场所'
|
const roomLabel = roomOptions.find((o) => o.id === filters.roomId)?.name ?? '全部场所'
|
||||||
const statusLabel = statusOptions.find((o) => o.id === filters.status)?.name ?? '全部状态'
|
const statusLabel = statusOptions.find((o) => o.id === filters.status)?.name ?? '全部状态'
|
||||||
|
|
||||||
// 筛选变化:拉取事件列表(当前 mock 空,保持空态展示)
|
// 拉取告警列表:home 未就绪时跳过(useEffect 到达后由 loadAlarms 首拉兜底)
|
||||||
const handleFilter = useCallback(async (next: typeof filters) => {
|
const loadAlarms = useCallback(async (next: typeof filters, homeId?: string) => {
|
||||||
|
const targetHomeId = homeId ?? home?.homeId
|
||||||
|
if (!targetHomeId) return
|
||||||
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const list = await getEventList({
|
const list = await getAlarmList({
|
||||||
sceneId: next.sceneId,
|
homeId: targetHomeId,
|
||||||
status: next.status,
|
roomId: next.roomId || undefined,
|
||||||
startDate: next.startDate,
|
status: next.status ? Number(next.status) : undefined,
|
||||||
endDate: next.endDate
|
// 接口要求完整时分秒:起止日拼边界值覆盖整天
|
||||||
|
beginTime: next.startDate ? `${next.startDate} 00:00:00` : undefined,
|
||||||
|
endTime: next.endDate ? `${next.endDate} 23:59:59` : undefined
|
||||||
})
|
})
|
||||||
setEventList(list)
|
setAlarmList(list ?? [])
|
||||||
} catch {
|
} catch {
|
||||||
// 失败保持现有列表,toast 由请求封装统一处理
|
// 失败保持现有列表,toast 由请求封装统一处理
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [home?.homeId])
|
||||||
|
|
||||||
const selectScene = (id: number) => {
|
// home 就绪后的首拉(初始 useDidShow 时 home 可能还没到)
|
||||||
const next = { ...filters, sceneId: id }
|
useEffect(() => {
|
||||||
|
if (home) loadAlarms(filters, home.homeId)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [home?.homeId])
|
||||||
|
|
||||||
|
const applyFilter = (next: typeof filters) => {
|
||||||
setFilters(next)
|
setFilters(next)
|
||||||
closeDrawer()
|
closeDrawer()
|
||||||
handleFilter(next)
|
loadAlarms(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectStatus = (id: number) => {
|
const renderOptionDrawer = (kind: 'room' | 'status') => {
|
||||||
const next = { ...filters, status: id }
|
const opts = kind === 'room' ? roomOptions : statusOptions
|
||||||
setFilters(next)
|
const currentId = kind === 'room' ? filters.roomId : filters.status
|
||||||
closeDrawer()
|
const title = kind === 'room' ? '选择场所' : '选择状态'
|
||||||
handleFilter(next)
|
const onSelect = (id: string) =>
|
||||||
}
|
applyFilter(kind === 'room' ? { ...filters, roomId: id } : { ...filters, status: id })
|
||||||
|
|
||||||
const onTimeConfirm = (start: string, end: string) => {
|
|
||||||
const next = { ...filters, startDate: start, endDate: end, timeText: `${start} 至 ${end}` }
|
|
||||||
setFilters(next)
|
|
||||||
closeDrawer()
|
|
||||||
handleFilter(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderOptionDrawer = (kind: 'scene' | 'status') => {
|
|
||||||
const opts = kind === 'scene' ? sceneOptions : statusOptions
|
|
||||||
const currentId = kind === 'scene' ? filters.sceneId : filters.status
|
|
||||||
const title = kind === 'scene' ? '选择场所' : '选择状态'
|
|
||||||
const onSelect = kind === 'scene' ? selectScene : selectStatus
|
|
||||||
return (
|
return (
|
||||||
<View className={styles.dropPanelOptions}>
|
<View className={styles.dropPanelOptions}>
|
||||||
<View className={styles.drawerHeader}>
|
<View className={styles.drawerHeader}>
|
||||||
|
|
@ -111,20 +131,29 @@ export default function Event() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statusTagOf = (alarm: HomeAlarm) => ALARM_STATUS[String(alarm.status)] ?? '未知'
|
||||||
|
|
||||||
|
// CSS Modules 动态 key 映射对象(避免索引签名问题)
|
||||||
|
const tagClsMap: Record<string, string> = {
|
||||||
|
'1': styles.tag1,
|
||||||
|
'2': styles.tag2,
|
||||||
|
'3': styles.tag3
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={styles.page}>
|
<View className={styles.page}>
|
||||||
<Navbar title="消息中心" autoBack={false} variant="hero" />
|
<Navbar title="消息中心" autoBack={false} variant="hero" />
|
||||||
|
|
||||||
{/* 筛选栏:三格等分 */}
|
{/* 筛选栏:三格等分 */}
|
||||||
<View className={styles.filters}>
|
<View className={styles.filters}>
|
||||||
<View className={styles.filterCell} onClick={() => setDrawer(drawer === 'scene' ? '' : 'scene')}>
|
<View className={styles.filterCell} onClick={() => setDrawer(drawer === 'room' ? '' : 'room')}>
|
||||||
<View className={`${styles.filterTrigger} ${filters.sceneId !== 0 ? styles.filterTriggerActive : ''}`}>
|
<View className={`${styles.filterTrigger} ${filters.roomId ? styles.filterTriggerActive : ''}`}>
|
||||||
<Text className={styles.filterText}>{sceneLabel}</Text>
|
<Text className={styles.filterText}>{roomLabel}</Text>
|
||||||
<Text className={styles.filterArrow}>▾</Text>
|
<Text className={styles.filterArrow}>▾</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.filterCell} onClick={() => setDrawer(drawer === 'status' ? '' : 'status')}>
|
<View className={styles.filterCell} onClick={() => setDrawer(drawer === 'status' ? '' : 'status')}>
|
||||||
<View className={`${styles.filterTrigger} ${filters.status !== 0 ? styles.filterTriggerActive : ''}`}>
|
<View className={`${styles.filterTrigger} ${filters.status ? styles.filterTriggerActive : ''}`}>
|
||||||
<Text className={styles.filterText}>{statusLabel}</Text>
|
<Text className={styles.filterText}>{statusLabel}</Text>
|
||||||
<Text className={styles.filterArrow}>▾</Text>
|
<Text className={styles.filterArrow}>▾</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -140,32 +169,49 @@ export default function Event() {
|
||||||
{/* 数据层:抽屉锚点(筛选栏下缘展开,遮罩盖住本层全部) */}
|
{/* 数据层:抽屉锚点(筛选栏下缘展开,遮罩盖住本层全部) */}
|
||||||
<View className={styles.contentArea}>
|
<View className={styles.contentArea}>
|
||||||
<View className={styles.contentBody}>
|
<View className={styles.contentBody}>
|
||||||
{eventList.length === 0 ? (
|
{alarmList.length === 0 ? (
|
||||||
<>
|
<>
|
||||||
<View className={styles.emptyArea}>
|
<View className={styles.emptyArea}>
|
||||||
<Empty text="暂无事件数据" mode="message" />
|
<Empty text="暂无告警数据" mode="message" />
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.emptyHint}>
|
<View className={styles.emptyHint}>
|
||||||
<Text>调整筛选条件后的事件列表将在此展示</Text>
|
<Text>调整筛选条件后的告警列表将在此展示</Text>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<View className={styles.eventList}>
|
<View className={styles.alarmList}>
|
||||||
{/* 事件卡片列表:布局等后端接口定型后补(Task 5.1.c 说明) */}
|
{alarmList.map((alarm) => (
|
||||||
|
<View key={alarm.alarmId} className={styles.alarmCard}>
|
||||||
|
<View className={styles.alarmHead}>
|
||||||
|
<Text className={styles.alarmType}>{alarm.alarmType}</Text>
|
||||||
|
<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.deviceName || alarm.deviceId}</Text>
|
||||||
|
</View>
|
||||||
|
<View className={styles.alarmRow}>
|
||||||
|
<Text className={styles.alarmLabel}>时间</Text>
|
||||||
|
<Text className={styles.alarmValue}>{alarm.alarmTime}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{drawer !== '' && <View className={styles.contentMask} onClick={closeDrawer} catchMove />}
|
{drawer !== '' && <View className={styles.contentMask} onClick={closeDrawer} catchMove />}
|
||||||
|
|
||||||
{drawer === 'scene' && renderOptionDrawer('scene')}
|
{drawer === 'room' && renderOptionDrawer('room')}
|
||||||
{drawer === 'status' && renderOptionDrawer('status')}
|
{drawer === 'status' && renderOptionDrawer('status')}
|
||||||
{drawer === 'time' && (
|
{drawer === 'time' && (
|
||||||
<CalendarDrawer
|
<CalendarDrawer
|
||||||
initialStart={filters.startDate}
|
initialStart={filters.startDate}
|
||||||
initialEnd={filters.endDate}
|
initialEnd={filters.endDate}
|
||||||
onClose={closeDrawer}
|
onClose={closeDrawer}
|
||||||
onConfirm={onTimeConfirm}
|
onConfirm={(start, end) =>
|
||||||
|
applyFilter({ ...filters, startDate: start, endDate: end, timeText: `${start} 至 ${end}` })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import { Navbar } from '@/components/navbar'
|
||||||
import { Loading } from '@/components/loading'
|
import { Loading } from '@/components/loading'
|
||||||
import { Empty } from '@/components/empty'
|
import { Empty } from '@/components/empty'
|
||||||
import { StateView } from '@/components/state-view'
|
import { StateView } from '@/components/state-view'
|
||||||
import { getEventList, type EventItem } from '@/api/event'
|
import { getHomeList, type HomeInfo } from '@/api/home'
|
||||||
|
import { getAlarmList, type HomeAlarm } from '@/api/homeAlarm'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
import { storage } from '@/utils/storage'
|
import { storage } from '@/utils/storage'
|
||||||
import { ROUTES } from '@/constants/routes'
|
import { ROUTES } from '@/constants/routes'
|
||||||
|
|
@ -15,17 +16,23 @@ import { splitAlerts } from './splitAlerts'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const [alertList, setAlertList] = useState<EventItem[]>([])
|
const [alertList, setAlertList] = useState<HomeAlarm[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [loadError, setLoadError] = useState(false)
|
const [loadError, setLoadError] = useState(false)
|
||||||
|
|
||||||
// 每次进入页面重拉全量告警(与事件页"全部"筛选同参数)
|
// 每次进入页面重拉默认家庭的全部告警(与事件页"全部"筛选同参数)
|
||||||
const loadAlerts = useCallback(async () => {
|
const loadAlerts = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setLoadError(false)
|
setLoadError(false)
|
||||||
try {
|
try {
|
||||||
const list = await getEventList({ sceneId: 0, status: 0, startDate: '', endDate: '' })
|
const homes = await getHomeList()
|
||||||
setAlertList(list)
|
const first: HomeInfo | undefined = homes[0]
|
||||||
|
if (!first) {
|
||||||
|
setAlertList([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const list = await getAlarmList({ homeId: first.homeId })
|
||||||
|
setAlertList(list ?? [])
|
||||||
} catch {
|
} catch {
|
||||||
setLoadError(true)
|
setLoadError(true)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -66,7 +73,7 @@ export default function Index() {
|
||||||
{messages.length > 0 && (
|
{messages.length > 0 && (
|
||||||
<View className={styles.messageList}>
|
<View className={styles.messageList}>
|
||||||
{messages.map((alert) => (
|
{messages.map((alert) => (
|
||||||
<AlertMessageItem key={alert.id} alert={alert} onClick={goAlertDetail} />
|
<AlertMessageItem key={alert.alarmId} alert={alert} onClick={goAlertDetail} />
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
@ -94,33 +101,33 @@ function FamilyEntry({ onClick }: { onClick: () => void }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function UnhandledAlert({ alert, onClick }: { alert: EventItem; onClick: () => void }) {
|
function UnhandledAlert({ alert, onClick }: { alert: HomeAlarm; onClick: () => void }) {
|
||||||
return (
|
return (
|
||||||
<View className={styles.unhandledCard} onClick={onClick}>
|
<View className={styles.unhandledCard} onClick={onClick}>
|
||||||
<View className={styles.unhandledHead}>
|
<View className={styles.unhandledHead}>
|
||||||
<View className={styles.unhandledIcon}>
|
<View className={styles.unhandledIcon}>
|
||||||
<Text className={styles.unhandledIconText}>!</Text>
|
<Text className={styles.unhandledIconText}>!</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className={styles.unhandledTitle}>{alert.deviceName || '未知设备'}</Text>
|
<Text className={styles.unhandledTitle}>{alert.deviceName || alert.deviceId || '未知设备'}</Text>
|
||||||
<View className={styles.unhandledTag}>
|
<View className={styles.unhandledTag}>
|
||||||
<Text className={styles.unhandledTagText}>未处理</Text>
|
<Text className={styles.unhandledTagText}>未处理</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{alert.sceneName && (
|
{alert.alarmType && (
|
||||||
<View className={styles.unhandledMeta}>
|
<View className={styles.unhandledMeta}>
|
||||||
<Text className={styles.unhandledMetaText}>{alert.sceneName}</Text>
|
<Text className={styles.unhandledMetaText}>{alert.alarmType}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{alert.time && (
|
{alert.alarmTime && (
|
||||||
<View className={styles.unhandledMeta}>
|
<View className={styles.unhandledMeta}>
|
||||||
<Text className={styles.unhandledMetaText}>{alert.time}</Text>
|
<Text className={styles.unhandledMetaText}>{alert.alarmTime}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertMessageItem({ alert, onClick }: { alert: EventItem; onClick: () => void }) {
|
function AlertMessageItem({ alert, onClick }: { alert: HomeAlarm; onClick: () => void }) {
|
||||||
return (
|
return (
|
||||||
<View className={styles.messageCard} onClick={onClick}>
|
<View className={styles.messageCard} onClick={onClick}>
|
||||||
<View className={styles.messageIcon}>
|
<View className={styles.messageIcon}>
|
||||||
|
|
@ -128,8 +135,8 @@ function AlertMessageItem({ alert, onClick }: { alert: EventItem; onClick: () =>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.messageBody}>
|
<View className={styles.messageBody}>
|
||||||
<View className={styles.messageHead}>
|
<View className={styles.messageHead}>
|
||||||
<Text className={styles.messageTitle}>告警消息</Text>
|
<Text className={styles.messageTitle}>{alert.alarmType || '告警消息'}</Text>
|
||||||
{alert.time && <Text className={styles.messageTime}>{alert.time}</Text>}
|
{alert.alarmTime && <Text className={styles.messageTime}>{alert.alarmTime}</Text>}
|
||||||
</View>
|
</View>
|
||||||
{alert.deviceName && (
|
{alert.deviceName && (
|
||||||
<Text className={styles.messageDevice}>{alert.deviceName}</Text>
|
<Text className={styles.messageDevice}>{alert.deviceName}</Text>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import type { EventItem } from '@/api/event'
|
import type { HomeAlarm } from '@/api/homeAlarm'
|
||||||
|
|
||||||
// 首页告警分流:status=1 未处理(首页只展示最新一条),其余全部进消息列表
|
// 首页告警分流:status=1 未处理(首页只展示最新一条),其余全部进消息列表
|
||||||
export function splitAlerts(list: EventItem[]): {
|
export function splitAlerts(list: HomeAlarm[]): {
|
||||||
unhandled: EventItem[]
|
unhandled: HomeAlarm[]
|
||||||
messages: EventItem[]
|
messages: HomeAlarm[]
|
||||||
} {
|
} {
|
||||||
const unhandled: EventItem[] = []
|
const unhandled: HomeAlarm[] = []
|
||||||
const messages: EventItem[] = []
|
const messages: HomeAlarm[] = []
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
if (item.status === 1) unhandled.push(item)
|
if (item.status === 1) unhandled.push(item)
|
||||||
else messages.push(item)
|
else messages.push(item)
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,36 @@
|
||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest'
|
||||||
import { splitAlerts } from '@/pages/index/splitAlerts'
|
import { splitAlerts } from '@/pages/index/splitAlerts'
|
||||||
import type { EventItem } from '@/api/event'
|
import type { HomeAlarm } from '@/api/homeAlarm'
|
||||||
|
|
||||||
const item = (over: Partial<EventItem>): EventItem => ({
|
const item = (over: Partial<HomeAlarm>): HomeAlarm => ({
|
||||||
id: 1,
|
alarmId: '1',
|
||||||
sceneName: '客厅',
|
homeId: 'h1',
|
||||||
|
roomId: 'r1',
|
||||||
|
deviceId: 'd1',
|
||||||
deviceName: '体征监测仪',
|
deviceName: '体征监测仪',
|
||||||
|
alarmType: '心率异常',
|
||||||
|
alarmTime: '2026-09-07 21:22:12',
|
||||||
status: 2,
|
status: 2,
|
||||||
time: '2026-09-07 21:22:12',
|
|
||||||
...over
|
...over
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('splitAlerts', () => {
|
describe('splitAlerts', () => {
|
||||||
it('status=1 进入未处理组,其余进入消息组', () => {
|
it('status=1 进入未处理组,其余进入消息组', () => {
|
||||||
const list = [
|
const list = [
|
||||||
item({ id: 1, status: 1 }),
|
item({ alarmId: '1', status: 1 }),
|
||||||
item({ id: 2, status: 2 }),
|
item({ alarmId: '2', status: 2 }),
|
||||||
item({ id: 3, status: 3 })
|
item({ alarmId: '3', status: 3 })
|
||||||
]
|
]
|
||||||
const { unhandled, messages } = splitAlerts(list)
|
const { unhandled, messages } = splitAlerts(list)
|
||||||
expect(unhandled.map((i) => i.id)).toEqual([1])
|
expect(unhandled.map((i) => i.alarmId)).toEqual(['1'])
|
||||||
expect(messages.map((i) => i.id)).toEqual([2, 3])
|
expect(messages.map((i) => i.alarmId)).toEqual(['2', '3'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('status 异常值(如 0、99)落入消息组不丢弃', () => {
|
it('status 异常值(如 0、99)落入消息组不丢弃', () => {
|
||||||
const list = [item({ id: 1, status: 0 }), item({ id: 2, status: 99 })]
|
const list = [item({ alarmId: '1', status: 0 }), item({ alarmId: '2', status: 99 })]
|
||||||
const { unhandled, messages } = splitAlerts(list)
|
const { unhandled, messages } = splitAlerts(list)
|
||||||
expect(unhandled).toEqual([])
|
expect(unhandled).toEqual([])
|
||||||
expect(messages.map((i) => i.id)).toEqual([1, 2])
|
expect(messages.map((i) => i.alarmId)).toEqual(['1', '2'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('空列表两组均为空', () => {
|
it('空列表两组均为空', () => {
|
||||||
|
|
@ -38,12 +41,12 @@ describe('splitAlerts', () => {
|
||||||
|
|
||||||
it('两组均保持原有相对顺序', () => {
|
it('两组均保持原有相对顺序', () => {
|
||||||
const list = [
|
const list = [
|
||||||
item({ id: 3, status: 2 }),
|
item({ alarmId: '3', status: 2 }),
|
||||||
item({ id: 1, status: 1 }),
|
item({ alarmId: '1', status: 1 }),
|
||||||
item({ id: 2, status: 2 })
|
item({ alarmId: '2', status: 2 })
|
||||||
]
|
]
|
||||||
const { unhandled, messages } = splitAlerts(list)
|
const { unhandled, messages } = splitAlerts(list)
|
||||||
expect(unhandled.map((i) => i.id)).toEqual([1])
|
expect(unhandled.map((i) => i.alarmId)).toEqual(['1'])
|
||||||
expect(messages.map((i) => i.id)).toEqual([3, 2])
|
expect(messages.map((i) => i.alarmId)).toEqual(['3', '2'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue