186 lines
6.3 KiB
TypeScript
186 lines
6.3 KiB
TypeScript
import { useState, useEffect } from 'react'
|
||
import Taro from '@tarojs/taro'
|
||
import { useDidShow } from '@tarojs/taro'
|
||
import { View, Text, Input } from '@tarojs/components'
|
||
import { Navbar } from '@/components/navbar'
|
||
import { Loading } from '@/components/loading'
|
||
import { StateView } from '@/components/state-view'
|
||
import { addHomeDevice } from '@/api/homeDevice'
|
||
import { getHomeList, type HomeInfo } from '@/api/home'
|
||
import { getRoomList, type HomeRoom } from '@/api/homeRoom'
|
||
import { resolveCurrentHome, setCurrentHomeId } from '@/store/currentHome'
|
||
import { showToast } from '@/utils/toast'
|
||
import { navigateBack } from '@/utils/navigation'
|
||
import styles from './add.module.scss'
|
||
|
||
export default function DeviceAdd() {
|
||
const [deviceId, setDeviceId] = useState('')
|
||
const [submitting, setSubmitting] = useState(false)
|
||
|
||
const [loading, setLoading] = useState(true)
|
||
const [loadError, setLoadError] = useState(false)
|
||
const [home, setHome] = useState<HomeInfo | null>(null)
|
||
const [rooms, setRooms] = useState<HomeRoom[]>([])
|
||
const [roomId, setRoomId] = useState('')
|
||
|
||
// 数据加载体:useDidShow 首次与错误重试共用(state 变化触发 useDidShow 不可行,用计数器驱动 effect)
|
||
const [reload, setReload] = useState(0)
|
||
const [loaded, setLoaded] = useState(false)
|
||
|
||
// 默认家庭 + 房间列表:addHomeDevice 需 homeId + roomId
|
||
useEffect(() => {
|
||
let cancelled = false
|
||
setLoading(true)
|
||
setLoadError(false)
|
||
getHomeList()
|
||
.then(async (list) => {
|
||
const first = resolveCurrentHome(list)
|
||
if (cancelled) return
|
||
setHome(first)
|
||
if (first) setCurrentHomeId(first.homeId)
|
||
if (!first) return
|
||
const roomList = await getRoomList(first.homeId)
|
||
if (cancelled) return
|
||
setRooms(roomList ?? [])
|
||
setRoomId((prev) => prev || roomList[0]?.roomId || '')
|
||
})
|
||
.catch(() => !cancelled && setLoadError(true))
|
||
.finally(() => {
|
||
if (cancelled) return
|
||
setLoading(false)
|
||
setLoaded(true)
|
||
})
|
||
return () => {
|
||
cancelled = true
|
||
}
|
||
}, [reload])
|
||
|
||
// 从房间管理等页面返回时刷新(房间可能已变更)
|
||
useDidShow(() => {
|
||
if (loaded) setReload((n) => n + 1)
|
||
})
|
||
|
||
const submitManual = async () => {
|
||
if (submitting) return
|
||
const code = deviceId.trim()
|
||
if (!code) {
|
||
showToast('请输入设备编号')
|
||
return
|
||
}
|
||
if (!home || !roomId) {
|
||
showToast('请选择安装房间')
|
||
return
|
||
}
|
||
setSubmitting(true)
|
||
try {
|
||
await addHomeDevice({ deviceName: code, homeId: home.homeId, roomId })
|
||
showToast('添加成功')
|
||
navigateBack()
|
||
} catch (e) {
|
||
showToast(e instanceof Error ? e.message : '添加失败')
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
// 点击扫码区拉起相机扫码,识别结果回填到输入框,确认后提交
|
||
const scanQRCode = async () => {
|
||
try {
|
||
const res = await Taro.scanCode({ scanType: ['qrCode', 'barCode'] })
|
||
const code = String(res.result ?? '').trim()
|
||
if (!code) {
|
||
showToast('未识别到有效编号')
|
||
return
|
||
}
|
||
setDeviceId(code)
|
||
showToast('已填入扫码结果')
|
||
} catch {
|
||
showToast('扫码取消')
|
||
}
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<View className={styles.page}>
|
||
<Navbar title="添加设备" autoBack variant="hero" />
|
||
<View className={styles.stateWrap}><Loading /></View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
if (loadError || !home) {
|
||
return (
|
||
<View className={styles.page}>
|
||
<Navbar title="添加设备" autoBack variant="hero" />
|
||
<StateView
|
||
status="error"
|
||
mode="list"
|
||
text={loadError ? '加载失败' : '暂无家庭圈'}
|
||
retryText={loadError ? '重试' : '返回'}
|
||
onRetry={() => (loadError ? setReload((n) => n + 1) : navigateBack())}
|
||
/>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<View className={styles.page}>
|
||
<Navbar title="添加设备" autoBack={true} variant="hero" />
|
||
|
||
<View className={styles.section}>
|
||
<Text className={styles.sectionTitle}>手动输入</Text>
|
||
<Text className={styles.sectionDesc}>
|
||
将智能设备添加至「{home.name}」,可实时查看设备状态和告警信息。
|
||
</Text>
|
||
<Input
|
||
className={styles.input}
|
||
placeholder="请输入设备编号"
|
||
maxlength={32}
|
||
confirmType="done"
|
||
value={deviceId}
|
||
onInput={(e) => setDeviceId(e.detail.value)}
|
||
/>
|
||
{rooms.length > 0 && (
|
||
<View className={styles.roomPicker}>
|
||
<Text className={styles.roomLabel}>安装房间</Text>
|
||
<View className={styles.roomOptions}>
|
||
{rooms.map((room) => (
|
||
<View
|
||
key={room.roomId}
|
||
className={`${styles.roomChip} ${roomId === room.roomId ? styles.roomChipActive : ''}`}
|
||
onClick={() => setRoomId(room.roomId)}
|
||
>
|
||
<Text>{room.name}</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
)}
|
||
<View
|
||
className={`${styles.submitBtn} ${submitting ? styles.submitBtnDisabled : ''}`}
|
||
hoverClass={styles.submitBtnPressed}
|
||
onClick={submitManual}
|
||
>
|
||
<Text className={styles.submitBtnText}>{submitting ? '提交中...' : '提交'}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View className={styles.section}>
|
||
<Text className={styles.sectionTitle}>扫码添加</Text>
|
||
<Text className={styles.sectionDesc}>扫描设备机身或包装盒上的二维码,自动填入编号。</Text>
|
||
<View className={styles.scanArea} hoverClass={styles.scanAreaPressed} onClick={scanQRCode}>
|
||
<View className={styles.scanFrame}>
|
||
<View className={`${styles.scanCorner} ${styles.scanCornerTopLeft}`} />
|
||
<View className={`${styles.scanCorner} ${styles.scanCornerTopRight}`} />
|
||
<View className={`${styles.scanCorner} ${styles.scanCornerBottomLeft}`} />
|
||
<View className={`${styles.scanCorner} ${styles.scanCornerBottomRight}`} />
|
||
<View className={styles.scanLine} />
|
||
</View>
|
||
<Text className={styles.scanText}>点击开始扫码</Text>
|
||
<Text className={styles.scanHint}>支持二维码 / 条形码</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|