feat(小程序): 支持多设备绑定与管理
This commit is contained in:
@@ -1,104 +1,65 @@
|
||||
import { View, Text, Switch, Slider, Image } from '@tarojs/components'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { getCurrentBinding } from '@/services/binding'
|
||||
import { useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { getToken } from '@/services/auth'
|
||||
import { Binding, resolveActiveBinding } from '@/services/binding'
|
||||
import { Child, getChild } from '@/services/child'
|
||||
import './index.scss'
|
||||
|
||||
interface DeviceData {
|
||||
id: string
|
||||
name: string
|
||||
status: 'online' | 'offline'
|
||||
battery: number
|
||||
daysLeft: number
|
||||
sleepEnabled: boolean
|
||||
sleepTime: string
|
||||
volume: number
|
||||
}
|
||||
|
||||
export default function Device() {
|
||||
const [device, setDevice] = useState<DeviceData | null>(null)
|
||||
const [binding, setBinding] = useState<Binding | null>(null)
|
||||
const [child, setChild] = useState<Child | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [hasDevice, setHasDevice] = useState(false)
|
||||
const [sleepEnabled, setSleepEnabled] = useState(true)
|
||||
const [volume, setVolume] = useState(50)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[Device] Checking login status...')
|
||||
const token = Taro.getStorageSync('token')
|
||||
console.log('[Device] Token exists:', !!token)
|
||||
|
||||
if (!token) {
|
||||
console.log('[Device] No token, redirecting to login')
|
||||
Taro.redirectTo({ url: '/pages/login/index' })
|
||||
useDidShow(() => {
|
||||
void loadDeviceInfo()
|
||||
})
|
||||
|
||||
const loadDeviceInfo = async () => {
|
||||
if (!getToken()) {
|
||||
Taro.reLaunch({ url: '/pages/login/index' })
|
||||
return
|
||||
}
|
||||
|
||||
loadDeviceInfo()
|
||||
}, [])
|
||||
|
||||
const loadDeviceInfo = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const binding = await getCurrentBinding()
|
||||
console.log('[Device] Got binding:', binding)
|
||||
|
||||
if (binding) {
|
||||
if (!binding.child_id) {
|
||||
setHasDevice(false)
|
||||
Taro.showToast({ title: 'Please link child profile first', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({ url: '/pages/bind/index' })
|
||||
}, 600)
|
||||
return
|
||||
}
|
||||
const activeBinding = await resolveActiveBinding()
|
||||
setBinding(activeBinding)
|
||||
|
||||
setHasDevice(true)
|
||||
Taro.setStorageSync('hasDevice', '1')
|
||||
Taro.setStorageSync('deviceInfo', {
|
||||
id: binding.device_id,
|
||||
name: '伴伴设备',
|
||||
status: 'online',
|
||||
battery: 82
|
||||
})
|
||||
setDevice({
|
||||
id: binding.device_id,
|
||||
name: '伴伴设备',
|
||||
status: 'online',
|
||||
battery: 82,
|
||||
daysLeft: 4,
|
||||
sleepEnabled: true,
|
||||
sleepTime: '每天 22:00 - 07:00',
|
||||
volume: 50
|
||||
})
|
||||
if (activeBinding?.child_id) {
|
||||
try {
|
||||
const currentChild = await getChild(activeBinding.child_id)
|
||||
setChild(currentChild)
|
||||
} catch (error) {
|
||||
console.error('[device] load child failed:', error)
|
||||
setChild(null)
|
||||
}
|
||||
} else {
|
||||
setHasDevice(false)
|
||||
Taro.removeStorageSync('hasDevice')
|
||||
Taro.removeStorageSync('deviceInfo')
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.log('[Device] No binding or error:', err?.message || err)
|
||||
if (err?.status === 404) {
|
||||
setHasDevice(false)
|
||||
Taro.removeStorageSync('hasDevice')
|
||||
Taro.removeStorageSync('deviceInfo')
|
||||
setChild(null)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[device] load failed:', error)
|
||||
Taro.showToast({
|
||||
title: error?.message || '加载失败,请稍后重试',
|
||||
icon: 'none',
|
||||
})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
const handleSleepToggle = (e: any) => {
|
||||
if (!device) return
|
||||
setDevice({ ...device, sleepEnabled: e.detail.value })
|
||||
setSleepEnabled(e.detail.value)
|
||||
Taro.showToast({
|
||||
title: e.detail.value ? '已开启定时休眠' : '已关闭定时休眠',
|
||||
icon: 'success'
|
||||
title: e.detail.value ? '休眠展示已开启' : '休眠展示已关闭',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
|
||||
const handleVolumeChange = (e: any) => {
|
||||
if (!device) return
|
||||
setDevice({ ...device, volume: e.detail.value })
|
||||
}
|
||||
|
||||
const switchTab = (url: string) => {
|
||||
Taro.switchTab({ url })
|
||||
setVolume(e.detail.value)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
@@ -111,27 +72,48 @@ export default function Device() {
|
||||
)
|
||||
}
|
||||
|
||||
// 未绑定设备,跳转到绑定页面
|
||||
if (!hasDevice || !device) {
|
||||
Taro.redirectTo({ url: '/pages/bind/index' })
|
||||
return null
|
||||
if (!binding) {
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>设备首页</Text>
|
||||
</View>
|
||||
|
||||
<View className='empty-card'>
|
||||
<View className='empty-robot'>
|
||||
<Image className='robot-img' src={require('../../assets/tab-icons/robot.png')} mode='aspectFit' />
|
||||
</View>
|
||||
<Text className='empty-title'>先绑定一台伴伴设备</Text>
|
||||
<View className='empty-btn' onClick={() => Taro.navigateTo({ url: '/pages/bind/index' })}>
|
||||
<Text className='empty-btn-text'>去绑定设备</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const childName = child?.child_name || '未设置'
|
||||
const pageTitle = child?.child_name ? `${child.child_name} 的伴伴` : '伴伴设备'
|
||||
const battery = 82
|
||||
const daysLeft = 4
|
||||
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>{device.name}</Text>
|
||||
<Text className='page-subtitle'>设备运行状态良好</Text>
|
||||
<Text className='page-title'>{pageTitle}</Text>
|
||||
</View>
|
||||
|
||||
<View className='status-card'>
|
||||
<View className='status-badge'>
|
||||
<View className='status-dot'></View>
|
||||
<Text className='status-text'>4G 在线</Text>
|
||||
<Text className='status-text'>{binding.child_id ? '已完成绑定' : '待关联儿童'}</Text>
|
||||
</View>
|
||||
<View className='battery-section'>
|
||||
<Text className='battery-percent'>{device.battery}<Text className='percent'>%</Text></Text>
|
||||
<Text className='battery-label'>剩余电量 (预计续航 {device.daysLeft} 天)</Text>
|
||||
<Text className='battery-percent'>
|
||||
{battery}
|
||||
<Text className='percent'>%</Text>
|
||||
</Text>
|
||||
<Text className='battery-label'>模拟电量展示 (预计续航 {daysLeft} 天)</Text>
|
||||
</View>
|
||||
<View className='device-avatar'>
|
||||
<View className='robot-icon'>
|
||||
@@ -140,6 +122,21 @@ export default function Device() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='detail-card'>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>设备号</Text>
|
||||
<Text className='detail-value'>{binding.device_id}</Text>
|
||||
</View>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>儿童昵称</Text>
|
||||
<Text className='detail-value'>{childName}</Text>
|
||||
</View>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>绑定时间</Text>
|
||||
<Text className='detail-value'>{binding.bound_at || '--'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='section-title'>基础控制</View>
|
||||
<View className='control-card'>
|
||||
<View className='control-item'>
|
||||
@@ -153,14 +150,10 @@ export default function Device() {
|
||||
</View>
|
||||
<View className='control-info'>
|
||||
<Text className='control-name'>定时休眠</Text>
|
||||
<Text className='control-detail'>{device.sleepTime}</Text>
|
||||
<Text className='control-detail'>开启后进入休眠展示</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Switch
|
||||
checked={device.sleepEnabled}
|
||||
onChange={handleSleepToggle}
|
||||
color='#FF8C42'
|
||||
/>
|
||||
<Switch checked={sleepEnabled} onChange={handleSleepToggle} color='#FF8C42' />
|
||||
</View>
|
||||
|
||||
<View className='divider'></View>
|
||||
@@ -188,7 +181,7 @@ export default function Device() {
|
||||
className='slider'
|
||||
min={0}
|
||||
max={100}
|
||||
value={device.volume}
|
||||
value={volume}
|
||||
onChange={handleVolumeChange}
|
||||
activeColor='#FF8C42'
|
||||
backgroundColor='#E5E5E5'
|
||||
@@ -203,6 +196,11 @@ export default function Device() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{!binding.child_id && (
|
||||
<View className='helper-card' onClick={() => Taro.navigateTo({ url: '/pages/bind/index' })}>
|
||||
<Text className='helper-title'>继续完成儿童关联</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user