feat(event): 告警列表/详情接真实字段与新增告警详情页
- HomeAlarm 类型按 2026-09-23 联调实测重写:content/createTime/ deviceAlias/otherInfo 扩展等,status 枚举确认 0-待处理 1-已忽略 2-已处理 3-自动处理;splitAlerts 待处理判定改 status===0,单测同步 - 新增 pages/event/feature/detail 告警详情页(getAlarmDetail、状态徽章、 设备/房间/时间/扩展信息行),路由与分包注册,事件卡片与首页告警 点击均跳详情 - 事件页/首页改用 resolveCurrentHome 共享当前家庭,卡片展示 content、 deviceAlias 优先、两行截断与红点行标 - 家庭圈成员行点击改群昵称(showModal editable + updateMemberNickName), 转让/改昵称入口放开给所有成员(成员响应无 userId,isOwner 不可靠, 由后端 400 校验兜底) - ListRow value 推到行尾并支持 valueClass 自定义样式
This commit is contained in:
parent
6ec6fc7138
commit
82aa7d6925
|
|
@ -12,16 +12,46 @@ export interface AlarmListReq {
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 骨架:本期仅锁定接口路径,字段类型联调时修正(spec D7)
|
// 2026-09-23 联调实测字段(.getList 真实响应):
|
||||||
|
// - 无 homeId 字段;alarmType 是告警类型字典 ID(非文案),文案在 content
|
||||||
|
// - 时间字段是 createTime / lastUpdateTime,无 alarmTime
|
||||||
|
// - status: 0-待处理 1-已忽略 2-已处理 3-自动处理(2026-09-23 后端确认)
|
||||||
|
// - level: 5(告警级别,语义待字典确认);category: 'DEVICE'
|
||||||
export interface HomeAlarm {
|
export interface HomeAlarm {
|
||||||
alarmId: string
|
alarmId: string
|
||||||
homeId: string
|
content: string // 告警文案,如「跌倒检测器 No.01于2026-09-23 14:19:08发生确认跌倒」
|
||||||
roomId: string
|
level?: number
|
||||||
|
status: number
|
||||||
|
category?: string
|
||||||
|
alarmType: string // 告警类型字典 ID,前端不直接展示
|
||||||
deviceId: string
|
deviceId: string
|
||||||
deviceName: string
|
deviceName: string // 设备序列号
|
||||||
alarmType: string
|
deviceAlias?: string | null // 设备别名(如「跌倒检测器 No.01」),展示优先于 deviceName
|
||||||
alarmTime: string
|
roomId: string
|
||||||
status: number // 告警状态:0-未处理 1-已处理 2-已忽略(待实测确认枚举)
|
roomName?: string | null
|
||||||
|
productId?: string
|
||||||
|
createTime: string
|
||||||
|
lastUpdateTime?: string
|
||||||
|
// 详情接口附加的告警扩展信息(列表接口为 null)
|
||||||
|
otherInfo?: {
|
||||||
|
appName?: string
|
||||||
|
description?: string
|
||||||
|
alarmName?: string // 告警名称,如「跌倒报警」
|
||||||
|
deviceSn?: string
|
||||||
|
installId?: string
|
||||||
|
deviceName?: string
|
||||||
|
content?: string
|
||||||
|
realName?: string
|
||||||
|
alertTypeName?: string // 告警分类名,如「设备告警消息」
|
||||||
|
systemName?: string
|
||||||
|
cameraId?: string
|
||||||
|
appId?: string
|
||||||
|
alertTime?: string
|
||||||
|
cameraName?: string
|
||||||
|
deviceLocation?: string
|
||||||
|
} | null
|
||||||
|
videoUrl?: string | null
|
||||||
|
capturePicUrl?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAlarmList(params: AlarmListReq): Promise<HomeAlarm[]> {
|
export function getAlarmList(params: AlarmListReq): Promise<HomeAlarm[]> {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ export default {
|
||||||
{
|
{
|
||||||
root: 'pages/family',
|
root: 'pages/family',
|
||||||
pages: ['index', 'detail', 'contact/add', 'contact/detail']
|
pages: ['index', 'detail', 'contact/add', 'contact/detail']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: 'pages/event/feature',
|
||||||
|
pages: ['detail']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
window: {
|
window: {
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
margin-left: 24px;
|
// 推到行尾紧贴箭头:无箭头时贴右边距;否则三元素 space-between 会把 value 挤到中间
|
||||||
|
margin-left: auto;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ import styles from './index.module.scss'
|
||||||
interface ListRowProps {
|
interface ListRowProps {
|
||||||
label: string
|
label: string
|
||||||
value?: string
|
value?: string
|
||||||
|
valueClass?: string
|
||||||
arrow?: boolean
|
arrow?: boolean
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
danger?: boolean
|
danger?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListRow({ label, value, arrow, onClick, danger }: ListRowProps) {
|
export function ListRow({ label, value, valueClass, arrow, onClick, danger }: ListRowProps) {
|
||||||
const cls = [styles.row, danger ? styles.rowDanger : ''].filter(Boolean).join(' ')
|
const cls = [styles.row, danger ? styles.rowDanger : ''].filter(Boolean).join(' ')
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
|
@ -19,7 +20,7 @@ export function ListRow({ label, value, arrow, onClick, danger }: ListRowProps)
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Text className={styles.label}>{label}</Text>
|
<Text className={styles.label}>{label}</Text>
|
||||||
{value && <Text className={styles.value}>{value}</Text>}
|
{value && <Text className={`${styles.value} ${valueClass ?? ''}`.trim()}>{value}</Text>}
|
||||||
{arrow && <Text className={styles.arrow}>›</Text>}
|
{arrow && <Text className={styles.arrow}>›</Text>}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ export const ROUTES = Object.freeze({
|
||||||
LOGIN: '/pages/login/index',
|
LOGIN: '/pages/login/index',
|
||||||
HOME: '/pages/index/index',
|
HOME: '/pages/index/index',
|
||||||
EVENT: '/pages/event/index',
|
EVENT: '/pages/event/index',
|
||||||
|
EVENT_DETAIL: '/pages/event/feature/detail',
|
||||||
MINE: '/pages/mine/index',
|
MINE: '/pages/mine/index',
|
||||||
DEVICE_LIST: '/pages/device/index',
|
DEVICE_LIST: '/pages/device/index',
|
||||||
DEVICE_ADD: '/pages/device/feature/add',
|
DEVICE_ADD: '/pages/device/feature/add',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: $color-bg;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stateWrap {
|
||||||
|
margin-top: 120px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 告警头卡:白底渐变浮卡,左侧语义色竖条强调紧急度
|
||||||
|
.headCard {
|
||||||
|
margin: 24px;
|
||||||
|
padding: 32px;
|
||||||
|
background: $color-bg-white;
|
||||||
|
border-radius: $radius-lg;
|
||||||
|
box-shadow: $shadow-card;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headTop {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarmName {
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $color-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态徽章
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 20px;
|
||||||
|
border-radius: $radius-pill;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 500;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 待处理:红(需关注)
|
||||||
|
.tagPending {
|
||||||
|
background: $color-danger-light-bg;
|
||||||
|
color: $color-danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已处理:绿
|
||||||
|
.tagDone {
|
||||||
|
background: $color-success-light-bg;
|
||||||
|
color: $color-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已忽略/未知:灰
|
||||||
|
.tagIgnored {
|
||||||
|
background: $color-bg-disabled;
|
||||||
|
color: $color-text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
font-size: 28px;
|
||||||
|
color: $color-text;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headMeta {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metaText {
|
||||||
|
font-size: 24px;
|
||||||
|
color: $color-text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metaDot {
|
||||||
|
margin: 0 12px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: $color-text-placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 现场抓拍图
|
||||||
|
.capture {
|
||||||
|
margin-top: 24px;
|
||||||
|
width: 100%;
|
||||||
|
height: 360px;
|
||||||
|
border-radius: $radius-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin: 0 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
display: block;
|
||||||
|
font-size: 26px;
|
||||||
|
color: $color-text-secondary;
|
||||||
|
margin: 0 8px 16px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useRouter } from '@tarojs/taro'
|
||||||
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
|
import { Navbar } from '@/components/navbar'
|
||||||
|
import { Loading } from '@/components/loading'
|
||||||
|
import { StateView } from '@/components/state-view'
|
||||||
|
import { ListRow, ListRowGroup } from '@/components/list-row'
|
||||||
|
import { getAlarmDetail, type HomeAlarm } from '@/api/homeAlarm'
|
||||||
|
import { navigateTo } from '@/utils/navigation'
|
||||||
|
import { ROUTES } from '@/constants/routes'
|
||||||
|
import styles from './detail.module.scss'
|
||||||
|
|
||||||
|
// 状态 → 徽章文案与语义色(2026-09-23 后端确认:0-待处理 1-已忽略 2-已处理 3-自动处理)
|
||||||
|
const STATUS_META: Record<number, { label: string; cls: string }> = {
|
||||||
|
0: { label: '待处理', cls: 'tagPending' },
|
||||||
|
1: { label: '已忽略', cls: 'tagIgnored' },
|
||||||
|
2: { label: '已处理', cls: 'tagDone' },
|
||||||
|
3: { label: '自动处理', cls: 'tagIgnored' }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AlarmDetail() {
|
||||||
|
const router = useRouter()
|
||||||
|
const alarmId = router.params.id ?? ''
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [alarm, setAlarm] = useState<HomeAlarm | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!alarmId) {
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setLoading(true)
|
||||||
|
getAlarmDetail(alarmId)
|
||||||
|
.then(setAlarm)
|
||||||
|
.catch(() => setAlarm(null))
|
||||||
|
.finally(() => setLoading(false))
|
||||||
|
}, [alarmId])
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<View className={styles.page}>
|
||||||
|
<Navbar title="告警详情" variant="hero" />
|
||||||
|
<View className={styles.stateWrap}><Loading /></View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alarm) {
|
||||||
|
return (
|
||||||
|
<View className={styles.page}>
|
||||||
|
<Navbar title="告警详情" variant="hero" />
|
||||||
|
<StateView status="error" mode="list" text="该告警不存在或已删除" />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = STATUS_META[alarm.status] ?? { label: '未知状态', cls: 'tagIgnored' }
|
||||||
|
const deviceLabel = alarm.deviceAlias || alarm.deviceName || alarm.deviceId || '未知设备'
|
||||||
|
const ext = alarm.otherInfo
|
||||||
|
|
||||||
|
const goDevice = () => {
|
||||||
|
if (ext?.installId) navigateTo(`${ROUTES.DEVICE_DETAIL}?id=${ext.installId}&name=${encodeURIComponent(deviceLabel)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className={styles.page}>
|
||||||
|
<Navbar title="告警详情" variant="hero" />
|
||||||
|
|
||||||
|
{/* 告警头卡:名称 + 状态徽章 + 文案 + 时间 */}
|
||||||
|
<View className={styles.headCard}>
|
||||||
|
<View className={styles.headTop}>
|
||||||
|
<Text className={styles.alarmName}>{ext?.alarmName || '设备告警'}</Text>
|
||||||
|
<Text className={`${styles.tag} ${styles[meta.cls]}`}>{meta.label}</Text>
|
||||||
|
</View>
|
||||||
|
<Text className={styles.content}>{alarm.content}</Text>
|
||||||
|
<View className={styles.headMeta}>
|
||||||
|
<Text className={styles.metaText}>{ext?.alertTypeName || '设备告警消息'}</Text>
|
||||||
|
<Text className={styles.metaDot}>·</Text>
|
||||||
|
<Text className={styles.metaText}>{alarm.createTime}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 现场抓拍:capturePicUrl 存在时展示 */}
|
||||||
|
{alarm.capturePicUrl && (
|
||||||
|
<Image className={styles.capture} src={alarm.capturePicUrl} mode="aspectFill" />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 告警信息组 */}
|
||||||
|
<View className={styles.section}>
|
||||||
|
<Text className={styles.sectionTitle}>告警信息</Text>
|
||||||
|
<ListRowGroup>
|
||||||
|
<ListRow label="告警时间" value={ext?.alertTime || alarm.createTime} />
|
||||||
|
<ListRow label="更新时间" value={alarm.lastUpdateTime || alarm.createTime} />
|
||||||
|
<ListRow label="告警设备" value={deviceLabel} arrow={!!ext?.installId} onClick={ext?.installId ? goDevice : undefined} />
|
||||||
|
<ListRow label="所在房间" value={alarm.roomName || '--'} />
|
||||||
|
<ListRow label="设备序列号" value={alarm.deviceName || '--'} />
|
||||||
|
{ext?.deviceLocation && <ListRow label="设备位置" value={ext.deviceLocation} />}
|
||||||
|
</ListRowGroup>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 现场视频:videoUrl 存在时提示跳转(视频播放器暂未接入) */}
|
||||||
|
{alarm.videoUrl && (
|
||||||
|
<View className={styles.section}>
|
||||||
|
<Text className={styles.sectionTitle}>现场记录</Text>
|
||||||
|
<ListRowGroup>
|
||||||
|
<ListRow label="告警录像" value="点击查看" arrow onClick={() => navigateTo(`${ROUTES.DEVICE_DETAIL}?id=${alarm.deviceId}`)} />
|
||||||
|
</ListRowGroup>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -78,6 +78,7 @@
|
||||||
border-radius: $radius-lg;
|
border-radius: $radius-lg;
|
||||||
box-shadow: $shadow-card;
|
box-shadow: $shadow-card;
|
||||||
padding: 28px 32px;
|
padding: 28px 32px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
& + & { margin-top: 20px; }
|
& + & { margin-top: 20px; }
|
||||||
}
|
}
|
||||||
|
|
@ -90,20 +91,52 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.alarm-type {
|
.alarm-type {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
|
// 多行文案截断到两行:跌倒类告警 content 常带完整时间戳较长
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 告警类型左侧色条行标:红点提示设备类告警语义
|
||||||
|
.alarm-type-dot { display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: $radius-circle;
|
||||||
|
background: $color-danger;
|
||||||
|
margin-right: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alarm-tag {
|
.alarm-tag {
|
||||||
|
display: inline-block;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
padding: 4px 16px;
|
padding: 6px 16px;
|
||||||
border-radius: $radius-pill;
|
border-radius: $radius-pill;
|
||||||
color: $color-text-secondary;
|
color: $color-text-secondary;
|
||||||
background: $color-bg-gray;
|
background: $color-bg-gray;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 状态枚举待实测,先按未处理红色、已处理绿色、已忽略灰的常见语义 */
|
// 标题行:红点 + 两行截断文案
|
||||||
|
.alarmTypeWrap {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-right: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarmCardPressed {
|
||||||
|
border-color: $color-primary-light-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态语义色:0-待处理红 2-已处理绿 1/3-已忽略/自动处理灰(2026-09-23 后端确认枚举) */
|
||||||
.tag1 {
|
.tag1 {
|
||||||
color: $color-danger;
|
color: $color-danger;
|
||||||
background: $color-danger-light-bg;
|
background: $color-danger-light-bg;
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@ 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 { getHomeList, type HomeInfo } from '@/api/home'
|
import { getHomeList, type HomeInfo } from '@/api/home'
|
||||||
|
import { resolveCurrentHome } from '@/store/currentHome'
|
||||||
import { getRoomList, type HomeRoom } from '@/api/homeRoom'
|
import { getRoomList, type HomeRoom } from '@/api/homeRoom'
|
||||||
import { getAlarmList, type HomeAlarm } from '@/api/homeAlarm'
|
import { getAlarmList, type HomeAlarm } from '@/api/homeAlarm'
|
||||||
|
import { navigateTo } from '@/utils/navigation'
|
||||||
|
import { ROUTES } from '@/constants/routes'
|
||||||
import { CalendarDrawer } from './calendar-drawer'
|
import { CalendarDrawer } from './calendar-drawer'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
|
|
@ -14,11 +17,12 @@ interface FilterOption {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 告警状态筛选枚举(后端 integer,待实测确认:1-未处理 2-已处理 3-已忽略)
|
// 告警状态枚举(2026-09-23 后端确认:0-待处理 1-已忽略 2-已处理 3-自动处理)
|
||||||
const ALARM_STATUS: Record<string, string> = {
|
const ALARM_STATUS: Record<string, string> = {
|
||||||
'1': '未处理',
|
'0': '待处理',
|
||||||
|
'1': '已忽略',
|
||||||
'2': '已处理',
|
'2': '已处理',
|
||||||
'3': '已忽略'
|
'3': '自动处理'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Event() {
|
export default function Event() {
|
||||||
|
|
@ -37,16 +41,18 @@ export default function Event() {
|
||||||
const [roomOptions, setRoomOptions] = useState<FilterOption[]>([{ id: '', name: '全部' }])
|
const [roomOptions, setRoomOptions] = useState<FilterOption[]>([{ id: '', name: '全部' }])
|
||||||
const statusOptions: FilterOption[] = [
|
const statusOptions: FilterOption[] = [
|
||||||
{ id: '', name: '全部' },
|
{ id: '', name: '全部' },
|
||||||
{ id: '1', name: '未处理' },
|
{ id: '0', name: '待处理' },
|
||||||
|
{ id: '1', name: '已忽略' },
|
||||||
{ id: '2', name: '已处理' },
|
{ id: '2', name: '已处理' },
|
||||||
{ id: '3', name: '已忽略' }
|
{ id: '3', name: '自动处理' }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 默认家庭 + 其房间列表:失败时保留"全部"选项不阻塞筛选
|
// 当前家庭 + 其房间列表:与智能设备页共享选择(resolveCurrentHome 读持久化),
|
||||||
|
// 失败时保留"全部"选项不阻塞筛选
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getHomeList()
|
getHomeList()
|
||||||
.then(async (list) => {
|
.then(async (list) => {
|
||||||
const first = list[0] ?? null
|
const first = resolveCurrentHome(list)
|
||||||
setHome(first)
|
setHome(first)
|
||||||
if (!first) return
|
if (!first) return
|
||||||
const rooms = await getRoomList(first.homeId)
|
const rooms = await getRoomList(first.homeId)
|
||||||
|
|
@ -135,9 +141,10 @@ export default function Event() {
|
||||||
|
|
||||||
// CSS Modules 动态 key 映射对象(避免索引签名问题)
|
// CSS Modules 动态 key 映射对象(避免索引签名问题)
|
||||||
const tagClsMap: Record<string, string> = {
|
const tagClsMap: Record<string, string> = {
|
||||||
'1': styles.tag1,
|
'0': styles.tag1, // 待处理-红
|
||||||
'2': styles.tag2,
|
'1': styles.tag3, // 已忽略-灰
|
||||||
'3': styles.tag3
|
'2': styles.tag2, // 已处理-绿
|
||||||
|
'3': styles.tag3 // 自动处理-灰
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -181,18 +188,26 @@ export default function Event() {
|
||||||
) : (
|
) : (
|
||||||
<View className={styles.alarmList}>
|
<View className={styles.alarmList}>
|
||||||
{alarmList.map((alarm) => (
|
{alarmList.map((alarm) => (
|
||||||
<View key={alarm.alarmId} className={styles.alarmCard}>
|
<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.alarmHead}>
|
||||||
<Text className={styles.alarmType}>{alarm.alarmType}</Text>
|
<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>
|
<Text className={`${styles.alarmTag} ${tagClsMap[String(alarm.status)] ?? ''}`}>{statusTagOf(alarm)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.alarmRow}>
|
<View className={styles.alarmRow}>
|
||||||
<Text className={styles.alarmLabel}>设备</Text>
|
<Text className={styles.alarmLabel}>设备</Text>
|
||||||
<Text className={styles.alarmValue}>{alarm.deviceName || alarm.deviceId}</Text>
|
<Text className={styles.alarmValue}>{alarm.deviceAlias || alarm.deviceName || alarm.deviceId}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.alarmRow}>
|
<View className={styles.alarmRow}>
|
||||||
<Text className={styles.alarmLabel}>时间</Text>
|
<Text className={styles.alarmLabel}>时间</Text>
|
||||||
<Text className={styles.alarmValue}>{alarm.alarmTime}</Text>
|
<Text className={styles.alarmValue}>{alarm.createTime}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,21 @@
|
||||||
color: $color-text-tertiary;
|
color: $color-text-tertiary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 家庭成员行「群主」徽章:浅绿底胶囊,右贴箭头
|
||||||
|
// margin-left:auto 在 flex 行里把它推到最右;Text 需 inline-block 撑高背景
|
||||||
|
.valueTag {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 6px 20px;
|
||||||
|
background: $color-primary-light-bg;
|
||||||
|
border-radius: $radius-pill;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $color-primary-deep;
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 转让群主选人弹层(Modal 内容)
|
// 转让群主选人弹层(Modal 内容)
|
||||||
.transferTitle {
|
.transferTitle {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { StateView } from '@/components/state-view'
|
||||||
import { FamilyCard } from '@/components/family-card'
|
import { FamilyCard } from '@/components/family-card'
|
||||||
import { ListRow, ListRowGroup } from '@/components/list-row'
|
import { ListRow, ListRowGroup } from '@/components/list-row'
|
||||||
import { Modal } from '@/components/modal'
|
import { Modal } from '@/components/modal'
|
||||||
import { getHomeDetail, getHomeMemberList, joinHome, transferOwner, type HomeInfo, type HomeMember } from '@/api/home'
|
import { getHomeDetail, getHomeMemberList, joinHome, transferOwner, updateMemberNickName, type HomeInfo, type HomeMember } from '@/api/home'
|
||||||
import { getCareMemberList, type CareMember } from '@/api/careMember'
|
import { getCareMemberList, type CareMember } from '@/api/careMember'
|
||||||
import { MAX_CARED_PER_FAMILY } from '@/constants/family'
|
import { MAX_CARED_PER_FAMILY } from '@/constants/family'
|
||||||
import { ROUTES } from '@/constants/routes'
|
import { ROUTES } from '@/constants/routes'
|
||||||
|
|
@ -17,8 +17,22 @@ import { navigateBack, navigateTo, reLaunch } from '@/utils/navigation'
|
||||||
import { showToast } from '@/utils/toast'
|
import { showToast } from '@/utils/toast'
|
||||||
import styles from './detail.module.scss'
|
import styles from './detail.module.scss'
|
||||||
|
|
||||||
|
// Taro 类型未跟进 wx.showModal 的 editable 扩展(微信 2.17.1+),运行时真实存在
|
||||||
|
interface EditableModalOption extends Taro.showModal.Option {
|
||||||
|
editable?: boolean
|
||||||
|
placeholderText?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditableModalResult extends Taro.showModal.SuccessCallbackResult {
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const showEditableModal = (option: EditableModalOption) =>
|
||||||
|
Taro.showModal(option as Taro.showModal.Option) as Promise<EditableModalResult>
|
||||||
|
|
||||||
// 成员显示名:家庭内昵称优先,未设置回落微信昵称
|
// 成员显示名:家庭内昵称优先,未设置回落微信昵称
|
||||||
const memberName = (m: HomeMember): string => m.nickName ?? m.userNickName ?? '成员'
|
// 注意 2026-09-23 实测:清空昵称后端返回空串 '' 而非 null,需按空白处理
|
||||||
|
const memberName = (m: HomeMember): string => m.nickName?.trim() || m.userNickName?.trim() || '成员'
|
||||||
|
|
||||||
// 关爱对象联系人摘要:最多显示前 2 位姓名,其余以 +N 概括;未填则不显示
|
// 关爱对象联系人摘要:最多显示前 2 位姓名,其余以 +N 概括;未填则不显示
|
||||||
const contactsSummary = (c: CareMember): string => {
|
const contactsSummary = (c: CareMember): string => {
|
||||||
|
|
@ -45,6 +59,8 @@ export default function FamilyDetail() {
|
||||||
// 转让群主:底部入口仅群主可见,点击后弹选人 Modal
|
// 转让群主:底部入口仅群主可见,点击后弹选人 Modal
|
||||||
const [transferOpen, setTransferOpen] = useState(false)
|
const [transferOpen, setTransferOpen] = useState(false)
|
||||||
const [transferring, setTransferring] = useState(false)
|
const [transferring, setTransferring] = useState(false)
|
||||||
|
// 改昵称:点击成员行触发
|
||||||
|
const [renaming, setRenaming] = useState(false)
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
// 分享卡直达本页且未登录:先去登录,登录后回跳本页
|
// 分享卡直达本页且未登录:先去登录,登录后回跳本页
|
||||||
|
|
@ -114,9 +130,13 @@ export default function FamilyDetail() {
|
||||||
// 我在成员列表里的角色:转让入口只对群主开放
|
// 我在成员列表里的角色:转让入口只对群主开放
|
||||||
const myMember = members.find((m) => m.memberId === myUserId) ?? null
|
const myMember = members.find((m) => m.memberId === myUserId) ?? null
|
||||||
const isOwner = myMember?.role === 'OWNER'
|
const isOwner = myMember?.role === 'OWNER'
|
||||||
|
// 注意:成员响应无 userId 字段(2026-09-23 实测),memberId 是家庭圈成员记录 ID,
|
||||||
|
// 与登录 userId 不同源,isOwner 恒为 false——仅作未来后端补字段后的预留,勿用于门禁
|
||||||
|
|
||||||
|
// 转让/改昵称入口全开:前端无法可靠识别自己,由后端权威校验
|
||||||
|
// (transferOwner 实测已有 400「不能转让给自己」「成员不存在」等完整校验)
|
||||||
const openTransfer = () => {
|
const openTransfer = () => {
|
||||||
if (!isOwner || transferring) return
|
if (transferring) return
|
||||||
setTransferOpen(true)
|
setTransferOpen(true)
|
||||||
}
|
}
|
||||||
const closeTransfer = () => {
|
const closeTransfer = () => {
|
||||||
|
|
@ -138,6 +158,37 @@ export default function FamilyDetail() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 点击成员行改昵称:wx.showModal editable 输入框,预填当前昵称
|
||||||
|
const renameMember = async (target: HomeMember) => {
|
||||||
|
if (renaming) return
|
||||||
|
setRenaming(true)
|
||||||
|
try {
|
||||||
|
const { confirm, content } = await showEditableModal({
|
||||||
|
title: '修改群昵称',
|
||||||
|
editable: true,
|
||||||
|
placeholderText: '请输入新的昵称',
|
||||||
|
content: target.nickName ?? ''
|
||||||
|
})
|
||||||
|
if (!confirm) return
|
||||||
|
const next = (content ?? '').trim()
|
||||||
|
if (!next) {
|
||||||
|
showToast('昵称不能为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (next === (target.nickName ?? '').trim()) {
|
||||||
|
showToast('昵称未变化')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await updateMemberNickName({ memberId: target.memberId, nickName: next })
|
||||||
|
showToast('已修改')
|
||||||
|
await load()
|
||||||
|
} catch (e) {
|
||||||
|
showToast(e instanceof Error ? e.message : '修改失败')
|
||||||
|
} finally {
|
||||||
|
setRenaming(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<View className={styles.page}>
|
<View className={styles.page}>
|
||||||
|
|
@ -219,7 +270,14 @@ export default function FamilyDetail() {
|
||||||
) : (
|
) : (
|
||||||
<ListRowGroup>
|
<ListRowGroup>
|
||||||
{members.map((m) => (
|
{members.map((m) => (
|
||||||
<ListRow key={m.memberId} label={memberName(m)} value={m.role === 'OWNER' ? '群主' : '成员'} />
|
<ListRow
|
||||||
|
key={m.memberId}
|
||||||
|
label={memberName(m)}
|
||||||
|
value={m.role === 'OWNER' ? '群主' : undefined}
|
||||||
|
valueClass={styles.valueTag}
|
||||||
|
arrow
|
||||||
|
onClick={() => renameMember(m)}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</ListRowGroup>
|
</ListRowGroup>
|
||||||
)}
|
)}
|
||||||
|
|
@ -237,34 +295,31 @@ export default function FamilyDetail() {
|
||||||
<Text className={styles.shareLabel}>邀请他人</Text>
|
<Text className={styles.shareLabel}>邀请他人</Text>
|
||||||
<Text className={styles.shareArrow}>›</Text>
|
<Text className={styles.shareArrow}>›</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<ListRow label="转让群主" arrow onClick={isOwner ? openTransfer : stub('仅群主可转让')} />
|
<ListRow label="转让群主" arrow onClick={openTransfer} />
|
||||||
<ListRow label="修改群昵称" arrow onClick={stub('修改昵称功能即将上线')} />
|
|
||||||
</ListRowGroup>
|
</ListRowGroup>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 转让群主选人弹层:列出除自己外的成员,选中即调用 transferOwner */}
|
{/* 转让群主选人弹层:全部成员可选(含自己),后端对「转让给自己」返回 400 由 toast 透出 */}
|
||||||
<Modal visible={transferOpen} onClose={closeTransfer}>
|
<Modal visible={transferOpen} onClose={closeTransfer}>
|
||||||
<Text className={styles.transferTitle}>转让群主给谁?</Text>
|
<Text className={styles.transferTitle}>转让群主给谁?</Text>
|
||||||
<View className={styles.transferList}>
|
<View className={styles.transferList}>
|
||||||
{members.filter((m) => m.memberId !== myUserId).length === 0 ? (
|
{members.length === 0 ? (
|
||||||
<Text className={styles.transferEmpty}>暂无可转让的成员</Text>
|
<Text className={styles.transferEmpty}>暂无可转让的成员</Text>
|
||||||
) : (
|
) : (
|
||||||
members
|
members.map((m) => (
|
||||||
.filter((m) => m.memberId !== myUserId)
|
<View
|
||||||
.map((m) => (
|
key={m.memberId}
|
||||||
<View
|
className={styles.transferItem}
|
||||||
key={m.memberId}
|
hoverClass={styles.transferItemPressed}
|
||||||
className={styles.transferItem}
|
onClick={() => doTransfer(m)}
|
||||||
hoverClass={styles.transferItemPressed}
|
>
|
||||||
onClick={() => doTransfer(m)}
|
<Text className={styles.transferItemName}>{memberName(m)}</Text>
|
||||||
>
|
<Text className={styles.transferItemMeta}>
|
||||||
<Text className={styles.transferItemName}>{memberName(m)}</Text>
|
{m.role === 'OWNER' ? '群主' : '成员'} · 群昵称:{m.nickName || '未设置'}
|
||||||
<Text className={styles.transferItemMeta}>
|
</Text>
|
||||||
{m.role === 'OWNER' ? '群主' : '成员'} · 群昵称:{m.nickName ?? '未设置'}
|
</View>
|
||||||
</Text>
|
))
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.transferCancel} hoverClass={styles.transferCancelPressed} onClick={closeTransfer}>
|
<View className={styles.transferCancel} hoverClass={styles.transferCancelPressed} onClick={closeTransfer}>
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ 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 { getHomeList, type HomeInfo } from '@/api/home'
|
import { getHomeList, type HomeInfo } from '@/api/home'
|
||||||
|
import { resolveCurrentHome } from '@/store/currentHome'
|
||||||
import { getAlarmList, type HomeAlarm } from '@/api/homeAlarm'
|
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'
|
||||||
import { navigateTo, reLaunch } from '@/utils/navigation'
|
import { navigateTo, reLaunch } from '@/utils/navigation'
|
||||||
import { showToast } from '@/utils/toast'
|
|
||||||
import { splitAlerts } from './splitAlerts'
|
import { splitAlerts } from './splitAlerts'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
|
|
@ -26,7 +26,8 @@ export default function Index() {
|
||||||
setLoadError(false)
|
setLoadError(false)
|
||||||
try {
|
try {
|
||||||
const homes = await getHomeList()
|
const homes = await getHomeList()
|
||||||
const first: HomeInfo | undefined = homes[0]
|
// 与智能设备页共享当前家庭:优先持久化选择,失效回落第一条(resolveCurrentHome 内重新落盘)
|
||||||
|
const first: HomeInfo | null = resolveCurrentHome(homes)
|
||||||
if (!first) {
|
if (!first) {
|
||||||
setAlertList([])
|
setAlertList([])
|
||||||
return
|
return
|
||||||
|
|
@ -52,7 +53,7 @@ export default function Index() {
|
||||||
const { unhandled, messages } = useMemo(() => splitAlerts(alertList), [alertList])
|
const { unhandled, messages } = useMemo(() => splitAlerts(alertList), [alertList])
|
||||||
|
|
||||||
const goFamily = () => navigateTo(ROUTES.FAMILY)
|
const goFamily = () => navigateTo(ROUTES.FAMILY)
|
||||||
const goAlertDetail = () => showToast('告警详情即将上线')
|
const goAlertDetail = (alarmId: string) => navigateTo(`${ROUTES.EVENT_DETAIL}?id=${alarmId}`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={styles.page}>
|
<View className={styles.page}>
|
||||||
|
|
@ -67,13 +68,13 @@ export default function Index() {
|
||||||
<FamilyEntry onClick={goFamily} />
|
<FamilyEntry onClick={goFamily} />
|
||||||
|
|
||||||
{unhandled.length > 0 && (
|
{unhandled.length > 0 && (
|
||||||
<UnhandledAlert alert={unhandled[0]} onClick={goAlertDetail} />
|
<UnhandledAlert alert={unhandled[0]} onClick={() => goAlertDetail(unhandled[0].alarmId)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{messages.length > 0 && (
|
{messages.length > 0 && (
|
||||||
<View className={styles.messageList}>
|
<View className={styles.messageList}>
|
||||||
{messages.map((alert) => (
|
{messages.map((alert) => (
|
||||||
<AlertMessageItem key={alert.alarmId} alert={alert} onClick={goAlertDetail} />
|
<AlertMessageItem key={alert.alarmId} alert={alert} onClick={() => goAlertDetail(alert.alarmId)} />
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
@ -108,19 +109,19 @@ function UnhandledAlert({ alert, onClick }: { alert: HomeAlarm; onClick: () => v
|
||||||
<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 || alert.deviceId || '未知设备'}</Text>
|
<Text className={styles.unhandledTitle}>{alert.deviceAlias || 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.alarmType && (
|
{alert.content && (
|
||||||
<View className={styles.unhandledMeta}>
|
<View className={styles.unhandledMeta}>
|
||||||
<Text className={styles.unhandledMetaText}>{alert.alarmType}</Text>
|
<Text className={styles.unhandledMetaText}>{alert.content}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{alert.alarmTime && (
|
{alert.createTime && (
|
||||||
<View className={styles.unhandledMeta}>
|
<View className={styles.unhandledMeta}>
|
||||||
<Text className={styles.unhandledMetaText}>{alert.alarmTime}</Text>
|
<Text className={styles.unhandledMetaText}>{alert.createTime}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -135,11 +136,11 @@ function AlertMessageItem({ alert, onClick }: { alert: HomeAlarm; onClick: () =>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.messageBody}>
|
<View className={styles.messageBody}>
|
||||||
<View className={styles.messageHead}>
|
<View className={styles.messageHead}>
|
||||||
<Text className={styles.messageTitle}>{alert.alarmType || '告警消息'}</Text>
|
<Text className={styles.messageTitle}>{alert.content || '告警消息'}</Text>
|
||||||
{alert.alarmTime && <Text className={styles.messageTime}>{alert.alarmTime}</Text>}
|
{alert.createTime && <Text className={styles.messageTime}>{alert.createTime}</Text>}
|
||||||
</View>
|
</View>
|
||||||
{alert.deviceName && (
|
{(alert.deviceAlias || alert.deviceName) && (
|
||||||
<Text className={styles.messageDevice}>{alert.deviceName}</Text>
|
<Text className={styles.messageDevice}>{alert.deviceAlias || alert.deviceName}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { HomeAlarm } from '@/api/homeAlarm'
|
import type { HomeAlarm } from '@/api/homeAlarm'
|
||||||
|
|
||||||
// 首页告警分流:status=1 未处理(首页只展示最新一条),其余全部进消息列表
|
// 首页告警分流:status=0 待处理(首页只展示最新一条),其余全部进消息列表
|
||||||
|
// (2026-09-23 后端确认:0-待处理 1-已忽略 2-已处理 3-自动处理)
|
||||||
export function splitAlerts(list: HomeAlarm[]): {
|
export function splitAlerts(list: HomeAlarm[]): {
|
||||||
unhandled: HomeAlarm[]
|
unhandled: HomeAlarm[]
|
||||||
messages: HomeAlarm[]
|
messages: HomeAlarm[]
|
||||||
|
|
@ -8,7 +9,7 @@ export function splitAlerts(list: HomeAlarm[]): {
|
||||||
const unhandled: HomeAlarm[] = []
|
const unhandled: HomeAlarm[] = []
|
||||||
const messages: HomeAlarm[] = []
|
const messages: HomeAlarm[] = []
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
if (item.status === 1) unhandled.push(item)
|
if (item.status === 0) unhandled.push(item)
|
||||||
else messages.push(item)
|
else messages.push(item)
|
||||||
}
|
}
|
||||||
return { unhandled, messages }
|
return { unhandled, messages }
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,37 @@ import { describe, it, expect } from 'vitest'
|
||||||
import { splitAlerts } from '@/pages/index/splitAlerts'
|
import { splitAlerts } from '@/pages/index/splitAlerts'
|
||||||
import type { HomeAlarm } from '@/api/homeAlarm'
|
import type { HomeAlarm } from '@/api/homeAlarm'
|
||||||
|
|
||||||
|
// 字段对齐 2026-09-23 实测响应(content/createTime;status 0-待处理 1-已忽略 2-已处理 3-自动处理)
|
||||||
const item = (over: Partial<HomeAlarm>): HomeAlarm => ({
|
const item = (over: Partial<HomeAlarm>): HomeAlarm => ({
|
||||||
alarmId: '1',
|
alarmId: '1',
|
||||||
homeId: 'h1',
|
|
||||||
roomId: 'r1',
|
roomId: 'r1',
|
||||||
deviceId: 'd1',
|
deviceId: 'd1',
|
||||||
deviceName: '体征监测仪',
|
deviceName: '1463937a07a5',
|
||||||
alarmType: '心率异常',
|
deviceAlias: '跌倒检测器 No.01',
|
||||||
alarmTime: '2026-09-07 21:22:12',
|
content: '跌倒检测器 No.01发生确认跌倒',
|
||||||
status: 2,
|
alarmType: '1299830603847491584',
|
||||||
|
createTime: '2026-09-23 14:19:08',
|
||||||
|
status: 1,
|
||||||
...over
|
...over
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('splitAlerts', () => {
|
describe('splitAlerts', () => {
|
||||||
it('status=1 进入未处理组,其余进入消息组', () => {
|
it('status=0 进入待处理组,其余进入消息组', () => {
|
||||||
const list = [
|
const list = [
|
||||||
item({ alarmId: '1', status: 1 }),
|
item({ alarmId: '1', status: 0 }),
|
||||||
item({ alarmId: '2', status: 2 }),
|
item({ alarmId: '2', status: 1 }),
|
||||||
item({ alarmId: '3', status: 3 })
|
item({ alarmId: '3', status: 2 })
|
||||||
]
|
]
|
||||||
const { unhandled, messages } = splitAlerts(list)
|
const { unhandled, messages } = splitAlerts(list)
|
||||||
expect(unhandled.map((i) => i.alarmId)).toEqual(['1'])
|
expect(unhandled.map((i) => i.alarmId)).toEqual(['1'])
|
||||||
expect(messages.map((i) => i.alarmId)).toEqual(['2', '3'])
|
expect(messages.map((i) => i.alarmId)).toEqual(['2', '3'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('status 异常值(如 0、99)落入消息组不丢弃', () => {
|
it('status 异常值(如 99)落入消息组不丢弃', () => {
|
||||||
const list = [item({ alarmId: '1', status: 0 }), item({ alarmId: '2', status: 99 })]
|
const list = [item({ alarmId: '1', status: 99 })]
|
||||||
const { unhandled, messages } = splitAlerts(list)
|
const { unhandled, messages } = splitAlerts(list)
|
||||||
expect(unhandled).toEqual([])
|
expect(unhandled).toEqual([])
|
||||||
expect(messages.map((i) => i.alarmId)).toEqual(['1', '2'])
|
expect(messages.map((i) => i.alarmId)).toEqual(['1'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('空列表两组均为空', () => {
|
it('空列表两组均为空', () => {
|
||||||
|
|
@ -41,9 +43,9 @@ describe('splitAlerts', () => {
|
||||||
|
|
||||||
it('两组均保持原有相对顺序', () => {
|
it('两组均保持原有相对顺序', () => {
|
||||||
const list = [
|
const list = [
|
||||||
item({ alarmId: '3', status: 2 }),
|
item({ alarmId: '3', status: 1 }),
|
||||||
item({ alarmId: '1', status: 1 }),
|
item({ alarmId: '1', status: 0 }),
|
||||||
item({ alarmId: '2', status: 2 })
|
item({ alarmId: '2', status: 1 })
|
||||||
]
|
]
|
||||||
const { unhandled, messages } = splitAlerts(list)
|
const { unhandled, messages } = splitAlerts(list)
|
||||||
expect(unhandled.map((i) => i.alarmId)).toEqual(['1'])
|
expect(unhandled.map((i) => i.alarmId)).toEqual(['1'])
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue