Files
banban/banban-mini/src/pages/sleep/index.tsx
2026-05-09 10:55:11 +08:00

508 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { View, Text, Image, Input } from '@tarojs/components'
import { useState } from 'react'
import Taro, { useDidShow } from '@tarojs/taro'
import { clearToken, getToken } from '@/services/auth'
import {
BindingListItem,
clearSelectedBindingDeviceId,
loadCurrentChildBindingContext,
setSelectedBindingDeviceId,
unbindDevice,
} from '@/services/binding'
import { Child, clearSelectedChildId, createChild, setSelectedChildId, updateChild } from '@/services/child'
import { DeviceFirmwareStatus, getDeviceFirmwareStatus, startDeviceFirmwareUpdate } from '@/services/device'
import { useSystemBanner } from '@/components/system-banner/use-system-banner'
import './index.scss'
interface MenuItem {
icon: string
iconBgClass: string
name: string
value: string
arrow?: boolean
disabled?: boolean
}
export default function Sleep() {
const systemBanner = useSystemBanner()
const [loading, setLoading] = useState(true)
const [children, setChildren] = useState<Child[]>([])
const [bindings, setBindings] = useState<BindingListItem[]>([])
const [binding, setBinding] = useState<BindingListItem | null>(null)
const [currentChild, setCurrentChild] = useState<Child | null>(null)
const [firmwareStatus, setFirmwareStatus] = useState<DeviceFirmwareStatus | null>(null)
const [isLoadingFirmware, setIsLoadingFirmware] = useState(false)
const [isUpdatingFirmware, setIsUpdatingFirmware] = useState(false)
const [showModal, setShowModal] = useState(false)
const [showChildModal, setShowChildModal] = useState(false)
const [modalType, setModalType] = useState<'add' | 'edit'>('add')
const [childName, setChildName] = useState('')
const [editingChildId, setEditingChildId] = useState<number | null>(null)
const [parentInfo, setParentInfo] = useState<{ nickname?: string; avatar_url?: string }>({})
useDidShow(() => {
void loadData()
})
const loadData = async () => {
if (!getToken()) {
Taro.reLaunch({ url: '/pages/login/index' })
return
}
setLoading(true)
try {
const context = await loadCurrentChildBindingContext()
const nextBinding = (context.currentBinding as BindingListItem | null) || null
setChildren(context.children)
setBindings(context.bindings as BindingListItem[])
setCurrentChild(context.currentChild)
setBinding(nextBinding)
setParentInfo(Taro.getStorageSync('userInfo') || {})
void loadFirmwareStatus(nextBinding?.device_id)
} catch (error: any) {
console.error('[manage] load failed:', error)
Taro.showToast({
title: error?.message || '加载失败,请稍后重试',
icon: 'none',
})
} finally {
setLoading(false)
}
}
const currentChildName = currentChild?.child_name || '未设置'
const loadFirmwareStatus = async (deviceId?: string | null) => {
const normalizedDeviceId = String(deviceId || '').trim()
if (!normalizedDeviceId) {
setFirmwareStatus(null)
setIsLoadingFirmware(false)
return
}
setIsLoadingFirmware(true)
try {
const nextFirmwareStatus = await getDeviceFirmwareStatus(normalizedDeviceId)
setFirmwareStatus(nextFirmwareStatus)
} catch (error: any) {
console.error('[manage] firmware load failed:', error)
setFirmwareStatus(null)
Taro.showToast({
title: error?.message || '系统更新状态加载失败',
icon: 'none',
})
} finally {
setIsLoadingFirmware(false)
}
}
const handleOpenModal = (type: 'add' | 'edit', child?: Child) => {
setModalType(type)
setChildName(child?.child_name || '')
setEditingChildId(child?.child_id || null)
setShowModal(true)
}
const handleCloseModal = () => {
setShowModal(false)
setChildName('')
setEditingChildId(null)
}
const handleSelectChild = (child: Child) => {
setSelectedChildId(child.child_id)
const nextBinding = bindings.find((item) => item.child_id === child.child_id) || null
if (nextBinding?.device_id) {
setSelectedBindingDeviceId(nextBinding.device_id)
} else {
clearSelectedBindingDeviceId()
}
setCurrentChild(child)
setBinding(nextBinding)
void loadFirmwareStatus(nextBinding?.device_id)
setShowChildModal(false)
Taro.showToast({
title: '已切换当前孩子',
icon: 'success',
})
}
const handleOpenAddChildFromSwitch = () => {
setShowChildModal(false)
handleOpenModal('add')
}
const handleSubmitModal = async () => {
const normalizedName = childName.trim()
if (!normalizedName) {
Taro.showToast({ title: '请输入儿童昵称', icon: 'none' })
return
}
try {
if (modalType === 'add') {
const createdChild = await createChild({ child_name: normalizedName })
setSelectedChildId(createdChild.child_id)
Taro.showToast({ title: '创建成功', icon: 'success' })
} else if (editingChildId) {
await updateChild(editingChildId, { child_name: normalizedName })
Taro.showToast({ title: '修改成功', icon: 'success' })
}
handleCloseModal()
await loadData()
} catch (error: any) {
console.error('[manage] save child failed:', error)
Taro.showToast({
title: error?.message || '保存失败,请重试',
icon: 'none',
})
}
}
const handleStartFirmwareUpdate = () => {
if (!binding?.device_id || !firmwareStatus?.can_update || isLoadingFirmware || isUpdatingFirmware) return
Taro.showModal({
title: '系统更新',
content: `确定要更新当前设备 ${binding.device_id} 吗?`,
confirmText: '更新',
confirmColor: '#16A34A',
success: async (result) => {
if (!result.confirm || !binding?.device_id) return
setIsUpdatingFirmware(true)
try {
const nextFirmwareStatus = await startDeviceFirmwareUpdate(binding.device_id)
setFirmwareStatus(nextFirmwareStatus)
Taro.showToast({
title: '更新指令已发送',
icon: 'success',
})
} catch (error: any) {
Taro.showToast({
title: error?.message || '更新失败,请重试',
icon: 'none',
})
} finally {
setIsUpdatingFirmware(false)
}
},
})
}
const handleUnbind = () => {
if (!binding) return
Taro.showModal({
title: '解除设备绑定',
content: `确定要解除 ${currentChildName} 当前绑定的设备吗?`,
confirmColor: '#FF8C42',
success: async (result) => {
if (!result.confirm) return
try {
await unbindDevice(binding.device_id)
clearSelectedBindingDeviceId()
Taro.showToast({ title: '已解绑', icon: 'success' })
await loadData()
} catch (error: any) {
Taro.showToast({
title: error?.message || '解绑失败,请重试',
icon: 'none',
})
}
},
})
}
const handleLogout = () => {
Taro.showModal({
title: '退出登录',
content: '确定要退出当前账号吗?',
confirmColor: '#FF8C42',
success: (result) => {
if (!result.confirm) return
clearToken()
clearSelectedBindingDeviceId()
clearSelectedChildId()
Taro.removeStorageSync('userInfo')
Taro.reLaunch({ url: '/pages/login/index' })
},
})
}
const handleMenuClick = (item: MenuItem) => {
if (item.disabled) return
if (item.name === '当前孩子') {
setShowChildModal(true)
return
}
if (item.name === '编辑当前孩子') {
if (currentChild) {
handleOpenModal('edit', currentChild)
} else {
handleOpenModal('add')
}
return
}
if (item.name === '新增孩子') {
handleOpenModal('add')
return
}
if (item.name === '绑定设备') {
if (!currentChild) {
handleOpenModal('add')
Taro.showToast({
title: '请先添加儿童资料',
icon: 'none',
})
return
}
Taro.navigateTo({ url: '/pages/bind/index' })
return
}
if (item.name === '解除设备绑定') {
handleUnbind()
}
}
if (loading) {
return (
<View className='manage-page'>
<View className='loading'>
<Text>...</Text>
</View>
{systemBanner}
</View>
)
}
const parentDisplayName = parentInfo.nickname?.trim() || '家长'
const currentFirmwareLabel = firmwareStatus?.current_version || '--'
const latestFirmwareLabel = firmwareStatus?.latest_version || '--'
const canUpdateFirmware = Boolean(binding?.device_id && firmwareStatus?.can_update && !isLoadingFirmware && !isUpdatingFirmware)
const firmwareActionText = isLoadingFirmware ? '查询中' : isUpdatingFirmware ? '发送中' : '更新'
const firmwareSubtitle = !binding?.device_id
? '绑定设备后可查看系统版本'
: isLoadingFirmware
? '正在查询当前设备版本'
: firmwareStatus?.update_available
? '发现可更新版本'
: '当前设备版本状态'
const menuItems: MenuItem[] = [
{
icon: require('../../assets/tab-icons/orange-robot.png'),
iconBgClass: 'orange',
name: '当前孩子',
value: currentChildName,
arrow: true,
},
{
icon: require('../../assets/tab-icons/orange-robot.png'),
iconBgClass: 'orange',
name: '新增孩子',
value: '',
arrow: true,
},
{
icon: require('../../assets/tab-icons/orange-robot.png'),
iconBgClass: 'orange',
name: '编辑当前孩子',
value: '',
arrow: true,
},
{
icon: require('../../assets/tab-icons/rings.png'),
iconBgClass: 'green',
name: '绑定设备',
value: binding?.device_id ? `当前: ${binding.device_id}` : '未绑定',
arrow: true,
},
{
icon: require('../../assets/tab-icons/broken-rings.png'),
iconBgClass: 'red',
name: '解除设备绑定',
value: binding?.device_id ? `当前: ${binding.device_id}` : '',
arrow: true,
disabled: !binding,
},
]
return (
<View className='manage-page'>
<View className='page-header'>
<Text className='page-title'></Text>
</View>
<View className='user-card'>
<View className='user-avatar'>
{parentInfo.avatar_url ? (
<Image className='avatar-img image-avatar' src={parentInfo.avatar_url} mode='aspectFill' />
) : (
<Text className='avatar-img'>👤</Text>
)}
</View>
<View className='user-info'>
<Text className='user-name'>{parentDisplayName}</Text>
</View>
<View className='verified-badge'>
<Text>{currentChild ? '已选择当前孩子' : '未选择孩子'}</Text>
</View>
</View>
<View className='summary-card'>
<View className='summary-row'>
<Text className='summary-label'></Text>
<Text className='summary-value'>{children.length} </Text>
</View>
<View className='summary-row'>
<Text className='summary-label'></Text>
<Text className='summary-value'>{currentChildName}</Text>
</View>
<View className='summary-row'>
<Text className='summary-label'></Text>
<Text className='summary-value'>{binding?.device_id || '未绑定'}</Text>
</View>
</View>
<View className={`system-update-card ${binding?.device_id ? '' : 'disabled'}`}>
<View className='system-update-header'>
<View className='system-update-heading'>
<Text className='system-update-title'></Text>
<Text className='system-update-subtitle'>{firmwareSubtitle}</Text>
</View>
<View
className={`system-update-action ${canUpdateFirmware ? '' : 'disabled'}`}
onClick={canUpdateFirmware ? handleStartFirmwareUpdate : undefined}
>
<Text className='system-update-action-text'>{firmwareActionText}</Text>
</View>
</View>
<View className='system-update-body'>
<View className='system-update-row'>
<Text className='system-update-label'></Text>
<Text className='system-update-value'>{binding?.device_id || '未绑定'}</Text>
</View>
<View className='system-update-row'>
<Text className='system-update-label'></Text>
<Text className='system-update-value'>{currentFirmwareLabel}</Text>
</View>
<View className='system-update-row'>
<Text className='system-update-label'></Text>
<Text className='system-update-value highlight'>{latestFirmwareLabel}</Text>
</View>
</View>
</View>
<View className='menu-card'>
{menuItems.map((item, index) => (
<View key={index}>
<View className={`menu-item ${item.disabled ? 'disabled' : ''}`} onClick={() => handleMenuClick(item)}>
<View className='menu-left'>
<View className={`icon-bg ${item.iconBgClass}`}>
<Image className='control-icon-img' src={item.icon} mode='aspectFit' />
</View>
<Text className='menu-name'>{item.name}</Text>
</View>
<View className='menu-right'>
{item.value && <Text className='menu-value'>{item.value}</Text>}
{item.arrow && <Text className='arrow'></Text>}
</View>
</View>
{index < menuItems.length - 1 && <View className='divider'></View>}
</View>
))}
</View>
<View className='logout-section'>
<View className='logout-btn' onClick={handleLogout}>
<Text className='logout-text'>退</Text>
</View>
</View>
<View className='version-info'>
<Text className='version-text'> Companion V1.1.0</Text>
</View>
{showChildModal && (
<View className='modal-mask' onClick={() => setShowChildModal(false)}>
<View
className='modal-card device-switch-card'
onClick={(event) => {
event.stopPropagation()
}}
>
<Text className='modal-title'></Text>
{children.length === 0 ? (
<Text className='device-switch-empty'></Text>
) : (
<View className='device-switch-list'>
{children.map((item) => {
const isActive = currentChild?.child_id === item.child_id
const itemBinding = bindings.find((bindingItem) => bindingItem.child_id === item.child_id) || null
return (
<View
key={item.child_id}
className={`device-switch-item ${isActive ? 'active' : ''}`}
onClick={() => handleSelectChild(item)}
>
<View className='device-switch-head'>
<Text className='device-switch-id'>{item.child_name}</Text>
{isActive && <Text className='device-switch-tag'></Text>}
</View>
<Text className='device-switch-name'>{itemBinding?.device_id || '未绑定设备'}</Text>
</View>
)
})}
</View>
)}
<View className='device-switch-footer'>
<Text className='device-switch-add' onClick={handleOpenAddChildFromSwitch}>
+
</Text>
</View>
<View className='modal-actions'>
<Text className='modal-action cancel' onClick={() => setShowChildModal(false)}>
</Text>
</View>
</View>
</View>
)}
{showModal && (
<View className='modal-mask'>
<View className='modal-card'>
<Text className='modal-title'>{modalType === 'add' ? '新建儿童资料' : '修改儿童昵称'}</Text>
<View className='modal-input-wrap'>
<Input
className='modal-input'
value={childName}
placeholder='请输入儿童昵称'
focus
onInput={(event) => setChildName(event.detail.value)}
/>
</View>
<View className='modal-actions'>
<Text className='modal-action cancel' onClick={handleCloseModal}>
</Text>
<Text className='modal-action confirm' onClick={handleSubmitModal}>
</Text>
</View>
</View>
</View>
)}
{systemBanner}
</View>
)
}