305 lines
12 KiB
TypeScript
305 lines
12 KiB
TypeScript
import { useState, useCallback } from 'react'
|
||
import { useDidShow } from '@tarojs/taro'
|
||
import Taro 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 { BottomSheet } from '@/components/bottom-sheet'
|
||
import { store } from '@/store'
|
||
import { storage } from '@/utils/storage'
|
||
import { getHomeDeviceList, type HomeDevice } from '@/api/homeDevice'
|
||
import { getRoomList, type HomeRoom } from '@/api/homeRoom'
|
||
import { getHomeList, type HomeInfo } from '@/api/home'
|
||
import { resolveCurrentHome, setCurrentHomeId } from '@/store/currentHome'
|
||
import { ROUTES } from '@/constants/routes'
|
||
import { navigateTo } from '@/utils/navigation'
|
||
import ed713Icon from '@/assets/device/ed713.png'
|
||
import ed719Icon from '@/assets/device/ed719.png'
|
||
import eb15Icon from '@/assets/device/eb15.jpg'
|
||
import styles from './index.module.scss'
|
||
|
||
const FALLBACK_ICON = '⌂'
|
||
|
||
// 设备型号 → 产品图:sub-device 优先(EB15 是 EP832 网关下的子设备,
|
||
// 在 deviceModel 中没有对应值,需要走 subDeviceType 分支)。后端有时把型号
|
||
// 放在 deviceModel、有时放 subDeviceType,所以两个字段都查一遍
|
||
const pickDeviceIcon = (device: HomeDevice): string | null => {
|
||
const model = device.subDeviceType ?? device.deviceModel
|
||
if (model === 'EB15') return eb15Icon
|
||
if (model === 'ED713') return ed713Icon
|
||
if (model === 'ED719') return ed719Icon
|
||
return null
|
||
}
|
||
|
||
// 设备显示名:优先 name,未命名回落序列号
|
||
const deviceLabel = (device: HomeDevice): string => device.name || device.deviceName
|
||
|
||
export default function DeviceList() {
|
||
const [loading, setLoading] = useState(true)
|
||
const [loadError, setLoadError] = useState(false)
|
||
const [deviceList, setDeviceList] = useState<HomeDevice[]>([])
|
||
|
||
const [families, setFamilies] = useState<HomeInfo[]>([])
|
||
const [currentFamily, setCurrentFamily] = useState<HomeInfo | null>(null)
|
||
const [familySheetOpen, setFamilySheetOpen] = useState(false)
|
||
const [pendingFamily, setPendingFamily] = useState<HomeInfo | null>(null)
|
||
|
||
// 房间筛选:'' 为全部房间
|
||
const [rooms, setRooms] = useState<HomeRoom[]>([])
|
||
const [filterRoomId, setFilterRoomId] = useState('')
|
||
|
||
// 游客浏览:未登录不拉数据,展示引导登录(微信审核要求不强制授权)
|
||
const isGuest = () =>
|
||
!store.getState().auth.token && !storage.get<string>('auth_token')
|
||
const isGuestEmpty = isGuest()
|
||
|
||
// 设备列表随家庭 + 房间筛选联动
|
||
const loadDevices = useCallback(async (homeId?: string, roomId?: string) => {
|
||
const targetHomeId = homeId ?? currentFamily?.homeId
|
||
if (!targetHomeId) {
|
||
setDeviceList([])
|
||
setLoading(false)
|
||
return
|
||
}
|
||
setLoading(true)
|
||
setLoadError(false)
|
||
try {
|
||
const data = await getHomeDeviceList({
|
||
homeId: targetHomeId,
|
||
roomId: roomId || undefined
|
||
})
|
||
setDeviceList(data ?? [])
|
||
} catch {
|
||
setLoadError(true)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}, [currentFamily?.homeId])
|
||
|
||
const loadFamilies = useCallback(async () => {
|
||
try {
|
||
const list = await getHomeList()
|
||
setFamilies(list)
|
||
// 未选中时按持久化的 homeId 恢复,失效则回落第一条
|
||
setCurrentFamily((prev) => {
|
||
const next = prev ?? resolveCurrentHome(list)
|
||
if (next && next.homeId !== prev?.homeId) {
|
||
setCurrentHomeId(next.homeId)
|
||
setFilterRoomId('')
|
||
loadDevices(next.homeId)
|
||
loadRooms(next.homeId)
|
||
}
|
||
return next
|
||
})
|
||
} catch {
|
||
// 静默:家庭圈加载失败不阻断设备列表
|
||
}
|
||
}, [loadDevices])
|
||
|
||
// 房间列表随家庭加载,供筛选条使用;失败静默(仅少了筛选能力)
|
||
const loadRooms = useCallback(async (homeId?: string) => {
|
||
const targetHomeId = homeId ?? currentFamily?.homeId
|
||
if (!targetHomeId) return
|
||
try {
|
||
const list = await getRoomList(targetHomeId)
|
||
setRooms(list ?? [])
|
||
} catch {
|
||
setRooms([])
|
||
}
|
||
}, [currentFamily?.homeId])
|
||
|
||
const selectRoom = (roomId: string) => {
|
||
if (roomId === filterRoomId) return
|
||
setFilterRoomId(roomId)
|
||
loadDevices(undefined, roomId)
|
||
}
|
||
|
||
useDidShow(() => {
|
||
if (isGuest()) {
|
||
setDeviceList([])
|
||
setLoading(false)
|
||
setLoadError(false)
|
||
return
|
||
}
|
||
loadDevices(undefined, filterRoomId)
|
||
loadFamilies()
|
||
loadRooms()
|
||
})
|
||
|
||
// EventChannel 把整个设备对象传给详情页:首屏零延迟渲染体征快照
|
||
const goDetail = (device: HomeDevice) => {
|
||
const name = encodeURIComponent(deviceLabel(device))
|
||
Taro.navigateTo({
|
||
url: `${ROUTES.DEVICE_DETAIL}?id=${device.installId}&name=${name}&model=${device.subDeviceType ?? ''}&online=${device.onlineStatus ? 1 : 0}`,
|
||
success: (res) => res.eventChannel.emit('device', device)
|
||
})
|
||
}
|
||
const goAdd = () => navigateTo(ROUTES.DEVICE_ADD)
|
||
const goRoom = () => navigateTo(ROUTES.SCENE_SELECT)
|
||
const goLogin = () => navigateTo(ROUTES.LOGIN)
|
||
|
||
const openFamilySheet = () => {
|
||
setPendingFamily(currentFamily)
|
||
setFamilySheetOpen(true)
|
||
}
|
||
const closeFamilySheet = () => {
|
||
setFamilySheetOpen(false)
|
||
setPendingFamily(null)
|
||
}
|
||
const confirmFamilySheet = () => {
|
||
if (pendingFamily && pendingFamily.homeId !== currentFamily?.homeId) {
|
||
setCurrentFamily(pendingFamily)
|
||
setCurrentHomeId(pendingFamily.homeId)
|
||
setFilterRoomId('')
|
||
loadDevices(pendingFamily.homeId)
|
||
loadRooms(pendingFamily.homeId)
|
||
}
|
||
closeFamilySheet()
|
||
}
|
||
|
||
return (
|
||
<View className={styles.page}>
|
||
<Navbar title="智能设备" autoBack={false} variant="hero" />
|
||
|
||
{/* 绿色衬底:承接浮卡的上浮区,避免 -40px 压到导航标题 */}
|
||
<View className={styles.heroSpace} />
|
||
|
||
{/* 设备应用家庭信息卡 */}
|
||
<View className={styles.familyCard}>
|
||
<View className={styles.familyRow}>
|
||
<Text className={styles.familyLabel}>设备应用家庭</Text>
|
||
<Text className={styles.familyValue}>{currentFamily?.name ?? '未选择'}</Text>
|
||
</View>
|
||
<View className={styles.familyRow}>
|
||
<Text className={styles.familyLabel}>关爱对象</Text>
|
||
<Text className={styles.familyValue}>
|
||
{currentFamily ? (currentFamily.careMemberCount > 0 ? `${currentFamily.careMemberCount} 人` : '--') : '--'}
|
||
</Text>
|
||
</View>
|
||
<View className={styles.familyRow}>
|
||
<Text className={styles.familyLabel}>关爱地址</Text>
|
||
<Text className={styles.familyValue}>{currentFamily?.address ?? '--'}</Text>
|
||
</View>
|
||
<View className={styles.switchBtn} hoverClass={styles.switchBtnPressed} onClick={openFamilySheet}>
|
||
<Text className={styles.switchBtnText}>切换家庭圈</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 房间管理 + 添加设备 两张并排 */}
|
||
<View className={styles.funcRow}>
|
||
<View className={styles.funcCard} hoverClass={styles.funcCardPressed} onClick={goRoom}>
|
||
<View className={styles.funcIconWrap}>
|
||
<Text className={styles.funcIcon}>⌂</Text>
|
||
</View>
|
||
<Text className={styles.funcText}>房间管理</Text>
|
||
</View>
|
||
<View className={styles.funcCard} hoverClass={styles.funcCardPressed} onClick={goAdd}>
|
||
<View className={styles.funcIconWrap}>
|
||
<Text className={styles.funcIcon}>+</Text>
|
||
</View>
|
||
<Text className={styles.funcText}>添加设备</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 房间筛选条:横向滚动 chip,随当前家庭房间列表渲染 */}
|
||
{rooms.length > 0 && (
|
||
<View className={styles.roomBar}>
|
||
<View
|
||
className={`${styles.roomChip} ${filterRoomId === '' ? styles.roomChipActive : ''}`}
|
||
onClick={() => selectRoom('')}
|
||
>
|
||
<Text className={filterRoomId === '' ? styles.roomChipTextActive : ''}>全部</Text>
|
||
</View>
|
||
{rooms.map((room) => {
|
||
const active = filterRoomId === room.roomId
|
||
return (
|
||
<View
|
||
key={room.roomId}
|
||
className={`${styles.roomChip} ${active ? styles.roomChipActive : ''}`}
|
||
onClick={() => selectRoom(room.roomId)}
|
||
>
|
||
<Text className={active ? styles.roomChipTextActive : ''}>{room.name}</Text>
|
||
</View>
|
||
)
|
||
})}
|
||
</View>
|
||
)}
|
||
|
||
{/* 「我的设备」分区标题:数量随筛选联动 */}
|
||
{!loading && !loadError && !isGuestEmpty && (
|
||
<View className={styles.sectionTitle}>
|
||
<Text>我的设备</Text>
|
||
<Text className={styles.sectionCount}>{deviceList.length}</Text>
|
||
</View>
|
||
)}
|
||
|
||
{/* 设备网格 */}
|
||
{loading ? (
|
||
<View className={styles.stateWrap}><Loading /></View>
|
||
) : isGuestEmpty ? (
|
||
<StateView status="error" mode="list" text="登录后查看你的设备" retryText="去登录" onRetry={goLogin} />
|
||
) : loadError ? (
|
||
<StateView status="error" onRetry={() => loadDevices()} />
|
||
) : deviceList.length === 0 ? (
|
||
<StateView status="error" mode="list" text="暂无设备" retryText="添加设备" onRetry={goAdd} />
|
||
) : (
|
||
<View className={styles.grid}>
|
||
{deviceList.map((device) => (
|
||
<View key={device.installId} className={styles.card}>
|
||
<View className={styles.cardTop} onClick={() => goDetail(device)}>
|
||
<View className={styles.icon}>
|
||
{(() => {
|
||
const src = pickDeviceIcon(device)
|
||
return src ? (
|
||
<Image className={styles.iconImg} src={src} mode="aspectFit" />
|
||
) : (
|
||
<Text className={styles.iconText}>{FALLBACK_ICON}</Text>
|
||
)
|
||
})()}
|
||
</View>
|
||
<Text className={styles.name}>{deviceLabel(device)}</Text>
|
||
<View className={styles.statusRow}>
|
||
<View className={`${styles.dot} ${device.onlineStatus ? styles.dotOn : styles.dotOff}`} />
|
||
<Text className={`${styles.statusText} ${!device.onlineStatus ? styles.statusTextOff : ''}`}>
|
||
{device.onlineStatus ? '在线' : '离线'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
))}
|
||
</View>
|
||
)}
|
||
|
||
{/* 切换家庭圈弹层 */}
|
||
<BottomSheet visible={familySheetOpen} title="切换家庭圈" onClose={closeFamilySheet}>
|
||
<View className={styles.sheetList}>
|
||
{families.map((family) => {
|
||
const active = pendingFamily?.homeId === family.homeId
|
||
return (
|
||
<View
|
||
key={family.homeId}
|
||
className={`${styles.sheetItem} ${active ? styles.sheetItemActive : ''}`}
|
||
onClick={() => setPendingFamily(family)}
|
||
>
|
||
<View className={styles.sheetHead}>
|
||
<Text className={styles.sheetName}>{family.name}</Text>
|
||
</View>
|
||
<View className={styles.sheetMeta}>
|
||
<Text className={styles.sheetMetaItem}>
|
||
关爱对象 {family.careMemberCount > 0 ? `${family.careMemberCount} 人` : '--'}
|
||
</Text>
|
||
<Text className={styles.sheetMetaItem}>设备数量 {family.deviceCount}台</Text>
|
||
</View>
|
||
</View>
|
||
)
|
||
})}
|
||
</View>
|
||
<View className={styles.sheetConfirm} hoverClass={styles.sheetConfirmPressed} onClick={confirmFamilySheet}>
|
||
<Text className={styles.sheetConfirmText}>确定</Text>
|
||
</View>
|
||
</BottomSheet>
|
||
</View>
|
||
)
|
||
}
|