feat(mini): align companion pages and show OTA progress
This commit is contained in:
@@ -383,3 +383,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bind-result { padding: 24px 48px 60px; text-align: center; }
|
||||
.bind-result-title { display: block; color: #895731; font-size: 46px; font-weight: 700; line-height: 1.35; }
|
||||
.bind-result-art { display: block; width: 310px; height: 290px; margin: 12px auto 20px; }
|
||||
.bind-result-description { display: block; margin-top: 18px; color: #a68d74; font-size: 27px; line-height: 1.65; word-break: break-word; }
|
||||
.bind-countdown-ring { width: 220px; height: 220px; border: 12px solid #f9d96c; border-top-color: #ffa24f; border-radius: 50%; margin: 44px auto; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #fffdf7; }
|
||||
.bind-countdown-value { font-size: 62px; font-weight: 700; color: #ec913e; }
|
||||
.bind-countdown-label { margin-top: 12px; font-size: 22px; color: #aa8154; }
|
||||
.bind-instruction { padding: 28px; border: 2px solid #d2b48c; border-radius: 46px; background: #fffdf7; }
|
||||
.bind-instruction-title { font-size: 34px; font-weight: 600; color: #895731; }
|
||||
.bind-result-button { min-height: 92px; margin-top: 28px; border-radius: 46px; display: flex; align-items: center; justify-content: center; font-size: 30px; font-weight: 600; box-sizing: border-box; }
|
||||
.bind-result-button::after { display: none; }
|
||||
.bind-result-button.primary { background: linear-gradient(#ffce8d, #efa05a); border: 2px solid #cf935a; color: #895731; }
|
||||
.bind-result-button.secondary { background: #fffdf7; border: 2px solid #cf935a; color: #ad8054; }
|
||||
.bind-result-button.waiting { background: linear-gradient(#eeede9, #c9c7c1); border: 2px solid #aaa69c; color: #777168; }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Image, Input, Button } from '@tarojs/components'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Taro, { useDidShow, useDidHide, useRouter } from '@tarojs/taro'
|
||||
@@ -175,6 +176,7 @@ export default function Bind() {
|
||||
const [bindStatus, setBindStatus] = useState<number | null>(null)
|
||||
const [cardUUID, setCardUUID] = useState('')
|
||||
const [bindHint, setBindHint] = useState('')
|
||||
const [bindResultDestination, setBindResultDestination] = useState('/pages/device/index')
|
||||
const [bindExpiresAt, setBindExpiresAt] = useState('')
|
||||
const [remainingBindSeconds, setRemainingBindSeconds] = useState(0)
|
||||
const pollingRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
@@ -370,13 +372,7 @@ export default function Bind() {
|
||||
: session.card_uuid ? `绑定完成,卡号 ${session.card_uuid},稍后可关联宝贝` : '绑定完成,稍后可关联宝贝'
|
||||
)
|
||||
Taro.showToast({ title: isAddingCard ? '新卡添加成功' : '设备绑定成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
if (isAddingCard || session.child_id) {
|
||||
goDevicePage()
|
||||
} else {
|
||||
goManagePage()
|
||||
}
|
||||
}, 500)
|
||||
setBindResultDestination(isAddingCard || session.child_id ? '/pages/device/index' : '/pages/sleep/index')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -568,7 +564,7 @@ export default function Bind() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View className='bind-page'>
|
||||
<View className='bind-page' style={getPageStyle(true)}>
|
||||
<View className='loading'>
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
@@ -577,8 +573,68 @@ export default function Bind() {
|
||||
)
|
||||
}
|
||||
|
||||
const sessionVisible = bindStatus !== null && [
|
||||
SESSION_STATUS_PENDING, SESSION_STATUS_COMPLETED, SESSION_STATUS_EXPIRED,
|
||||
SESSION_STATUS_FAILED, SESSION_STATUS_CANCELLED,
|
||||
].includes(bindStatus)
|
||||
|
||||
if (sessionVisible) {
|
||||
const completed = bindStatus === SESSION_STATUS_COMPLETED
|
||||
const pending = bindStatus === SESSION_STATUS_PENDING
|
||||
const title = pending ? '请在设备上贴卡' : completed
|
||||
? isAddingCard ? '新卡添加完成' : '设备绑定成功'
|
||||
: isExpiredBind ? '贴卡时间已过' : '贴卡未完成'
|
||||
return (
|
||||
<View className='bind-page bind-state-page' style={getPageStyle(true)}>
|
||||
<View className='bind-header'>
|
||||
<View className='back-btn' onClick={handleBack}>
|
||||
<Image className='back-icon' src={require('../../assets/tab-icons/arrow-left.png')} mode='aspectFit' />
|
||||
<Text className='back-text'>返回</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className='bind-result'>
|
||||
{pending ? (
|
||||
<>
|
||||
<Text className='bind-result-title'>{title}</Text>
|
||||
<View className='bind-countdown-ring'>
|
||||
<Text className='bind-countdown-value'>{formatRemainingTime(remainingBindSeconds)}</Text>
|
||||
<Text className='bind-countdown-label'>剩余贴卡时间</Text>
|
||||
</View>
|
||||
<View className='bind-instruction'>
|
||||
<Text className='bind-instruction-title'>{isAddingCard ? '加卡指令已发送' : '绑卡指令已发送'}</Text>
|
||||
<Text className='bind-result-description'>{bindHint}</Text>
|
||||
</View>
|
||||
<Button className='bind-result-button waiting' disabled>等待设备确认</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Image className='bind-result-art' mode='aspectFit'
|
||||
src={completed ? require('../../assets/ui/dog-success.png') : require('../../assets/ui/dog-failure.png')} />
|
||||
<Text className='bind-result-title'>{title}</Text>
|
||||
<Text className='bind-result-description'>{bindHint}</Text>
|
||||
{completed ? (
|
||||
<Button className='bind-result-button primary' onClick={() => Taro.reLaunch({ url: bindResultDestination })}>
|
||||
{bindResultDestination.includes('/sleep/') ? '去关联宝贝' : '返回首页'}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button className='bind-result-button primary' onClick={() => {
|
||||
resetBindSessionState()
|
||||
if (isExpiredBind && !isAddingCard) handleScanCode()
|
||||
}}>重新开始</Button>
|
||||
<Button className='bind-result-button secondary' onClick={goDevicePage}>返回首页</Button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
{systemBanner}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='bind-page'>
|
||||
<View className='bind-page' style={getPageStyle(true)}>
|
||||
<View className='bind-header'>
|
||||
<View className='back-btn' onClick={handleBack}>
|
||||
<Image className='back-icon' src={require('../../assets/tab-icons/arrow-left.png')} mode='aspectFit' />
|
||||
@@ -587,16 +643,6 @@ export default function Bind() {
|
||||
<Text className='title'>{pageTitle}</Text>
|
||||
</View>
|
||||
|
||||
{bindStatus === SESSION_STATUS_PENDING && (
|
||||
<Image className='bind-state-mascot pending' src={require('../../assets/ui/dog-running.png')} mode='aspectFit' />
|
||||
)}
|
||||
{bindStatus === SESSION_STATUS_COMPLETED && (
|
||||
<Image className='bind-state-mascot success' src={require('../../assets/ui/dog-success.png')} mode='aspectFit' />
|
||||
)}
|
||||
{(bindStatus === SESSION_STATUS_EXPIRED || bindStatus === SESSION_STATUS_FAILED || bindStatus === SESSION_STATUS_CANCELLED) && (
|
||||
<Image className='bind-state-mascot failure' src={require('../../assets/ui/dog-failure.png')} mode='aspectFit' />
|
||||
)}
|
||||
|
||||
{isAddingCard ? (
|
||||
<View className='pending-device-card'>
|
||||
<View className='icon-bg orange'>
|
||||
|
||||
@@ -92,7 +92,8 @@
|
||||
align-items: flex-start;
|
||||
|
||||
&.user {
|
||||
flex-direction: row-reverse;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
.message-main {
|
||||
align-items: flex-end;
|
||||
@@ -251,3 +252,7 @@
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.chat-avatar-art { width: 100%; height: 100%; }
|
||||
.user-avatar { margin-left: 14px; }
|
||||
.message-item.user .message-bubble { margin: 0; }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { getParticipantArtwork } from '@/utils/artwork'
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, ScrollView, Image } from '@tarojs/components'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Taro, { useRouter } from '@tarojs/taro'
|
||||
@@ -747,14 +749,14 @@ export default function ChatDetail() {
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='chat-detail-page'>
|
||||
<View className='chat-detail-page' style={getPageStyle(true)}>
|
||||
<View className='chat-header'>
|
||||
<View className='back-btn' onClick={handleBack}>
|
||||
<Image src={require('../../../assets/tab-icons/arrow-left.png')} className='back-arrow-img' />
|
||||
</View>
|
||||
<View className='character-info'>
|
||||
<View className='character-avatar'>
|
||||
<Text>{character.icon}</Text>
|
||||
<Image className='chat-avatar-art' src={getParticipantArtwork(character.peerKind, decodedRoleKey)} mode='aspectFit' />
|
||||
</View>
|
||||
<View className='character-text'>
|
||||
<Text className='character-name'>{character.name}</Text>
|
||||
@@ -797,13 +799,13 @@ export default function ChatDetail() {
|
||||
</View>
|
||||
</View>
|
||||
<View className='user-avatar'>
|
||||
<Text className='avatar-img'>{msg.senderType === 'parent' ? '👨' : '👦'}</Text>
|
||||
<Image className='chat-avatar-art' src={getParticipantArtwork(msg.senderType)} mode='aspectFit' />
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View className='message-item peer'>
|
||||
<View className='peer-avatar'>
|
||||
<Text className='robot-emoji'>{msg.senderType === 'child' ? '🧒' : character.icon}</Text>
|
||||
<Image className='chat-avatar-art' src={getParticipantArtwork(msg.senderType || character.peerKind, decodedRoleKey)} mode='aspectFit' />
|
||||
</View>
|
||||
<View className='message-main'>
|
||||
<Text className='message-meta'>{formatParticipantLabel(msg)}</Text>
|
||||
|
||||
@@ -368,3 +368,6 @@
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
.conversation-avatar-art { width: 100%; height: 100%; }
|
||||
.conversation-avatar { background: #fff1d4; padding: 8px; box-sizing: border-box; }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { View, Text, ScrollView } from '@tarojs/components'
|
||||
import { getParticipantArtwork } from '@/utils/artwork'
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, ScrollView, Image } from '@tarojs/components'
|
||||
import { useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { getToken } from '@/services/auth'
|
||||
@@ -74,7 +76,7 @@ export default function Chat() {
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='chat-page'>
|
||||
<View className='chat-page' style={getPageStyle(true)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>玩伴</Text>
|
||||
</View>
|
||||
@@ -108,7 +110,7 @@ export default function Chat() {
|
||||
)}
|
||||
>
|
||||
<View className='guest-demo-avatar'>
|
||||
<Text>伴</Text>
|
||||
<Image className='conversation-avatar-art' src={getParticipantArtwork('ai')} mode='aspectFit' />
|
||||
</View>
|
||||
<View className='guest-demo-content'>
|
||||
<Text className='guest-demo-name'>AI 玩伴</Text>
|
||||
@@ -132,7 +134,7 @@ export default function Chat() {
|
||||
)}
|
||||
>
|
||||
<View className='guest-demo-avatar parent'>
|
||||
<Text>家</Text>
|
||||
<Image className='conversation-avatar-art' src={getParticipantArtwork('parent')} mode='aspectFit' />
|
||||
</View>
|
||||
<View className='guest-demo-content'>
|
||||
<Text className='guest-demo-name'>家长留言</Text>
|
||||
@@ -175,7 +177,7 @@ export default function Chat() {
|
||||
onClick={() => handleConversationClick(conversation)}
|
||||
>
|
||||
<View className='conversation-avatar'>
|
||||
<Text className='avatar-icon'>{conversation.avatar}</Text>
|
||||
<Image className='conversation-avatar-art' src={getParticipantArtwork(conversation.peerKind, conversation.roleKey)} mode='aspectFit' />
|
||||
</View>
|
||||
<View className='conversation-content'>
|
||||
<View className='conversation-header'>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Slider, Image } from '@tarojs/components'
|
||||
import { useRef, useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
@@ -271,7 +272,7 @@ export default function Device() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='device-page' style={getPageStyle(true)}>
|
||||
<View className='loading'>
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
@@ -282,7 +283,7 @@ export default function Device() {
|
||||
|
||||
if (isGuest) {
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='device-page' style={getPageStyle(true)}>
|
||||
<View className='guest-hero'>
|
||||
<View className='guest-device-icon'>
|
||||
<Image className='guest-mascot' src={require('../../assets/ui/dog-happy.png')} mode='aspectFit' />
|
||||
@@ -334,7 +335,7 @@ export default function Device() {
|
||||
if (!child) {
|
||||
const hasPendingDevice = Boolean(pendingBinding?.device_id)
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='device-page' style={getPageStyle(true)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>设备首页</Text>
|
||||
</View>
|
||||
@@ -362,7 +363,7 @@ export default function Device() {
|
||||
if (!binding) {
|
||||
const hasPendingDevice = Boolean(pendingBinding?.device_id)
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='device-page' style={getPageStyle(true)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>{child.child_name} 的伴伴</Text>
|
||||
</View>
|
||||
@@ -421,7 +422,7 @@ export default function Device() {
|
||||
const latestAlarmChildName = getAlarmChildLabel(latestAlarm, childName)
|
||||
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='device-page' style={getPageStyle(true)}>
|
||||
<View className='page-header bound-device-header'>
|
||||
<Text className='page-title'>{pageTitle}</Text>
|
||||
</View>
|
||||
@@ -460,7 +461,7 @@ export default function Device() {
|
||||
<View className='control-item sleep-item' onClick={handleOpenSleepSchedule}>
|
||||
<View className='control-left'>
|
||||
<View className='icon-bg purple'>
|
||||
<Image className='control-icon-img' src={require('../../assets/tab-icons/moon.png')} mode='aspectFit' />
|
||||
<Image className='control-icon-img' src={require('../../assets/ui/menu-sleep.png')} mode='aspectFit' />
|
||||
</View>
|
||||
<View className='control-info'>
|
||||
<Text className='control-name'>定时休眠</Text>
|
||||
@@ -476,7 +477,7 @@ export default function Device() {
|
||||
<View className='quick-sleep-header'>
|
||||
<View className='control-left'>
|
||||
<View className='icon-bg purple'>
|
||||
<Image className='control-icon-img' src={require('../../assets/tab-icons/moon.png')} mode='aspectFit' />
|
||||
<Image className='control-icon-img' src={require('../../assets/ui/menu-sleep.png')} mode='aspectFit' />
|
||||
</View>
|
||||
<View className='control-info'>
|
||||
<Text className='control-name'>立即休眠/唤醒</Text>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import Taro, { useDidShow, useRouter } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
@@ -74,7 +75,7 @@ export default function FamilyInvite() {
|
||||
const statusText = invitation?.status === 1 ? '等待确认加入' : '邀请已失效'
|
||||
|
||||
return (
|
||||
<View className='family-invite-page'>
|
||||
<View className='family-invite-page' style={getPageStyle(false)}>
|
||||
<Image className='invite-mascot' src={require('../../assets/ui/dog-running.png')} mode='aspectFit' />
|
||||
<View className='invite-panel'>
|
||||
<Text className='invite-title'>家庭共享邀请</Text>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Map } from '@tarojs/components'
|
||||
import { useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
@@ -376,7 +377,7 @@ export default function Location() {
|
||||
: '最近点位'
|
||||
|
||||
return (
|
||||
<View className='location-page'>
|
||||
<View className='location-page' style={getPageStyle(true)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>设备定位</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Button, Checkbox, CheckboxGroup, Image } from '@tarojs/components'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
@@ -131,7 +132,7 @@ export default function Login() {
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='login-page'>
|
||||
<View className='login-page' style={getPageStyle(false)}>
|
||||
<View className='login-header'>
|
||||
<View className='logo'>
|
||||
<Image
|
||||
@@ -147,15 +148,15 @@ export default function Login() {
|
||||
<View className='login-content'>
|
||||
<View className='features'>
|
||||
<View className='feature-item'>
|
||||
<Image className='feature-icon-image' src={require('../../assets/tab-icons/chat-icon.png')} mode='aspectFit' />
|
||||
<Image className='feature-icon-image' src={require('../../assets/ui/feature-chat.png')} mode='aspectFit' />
|
||||
<Text className='feature-text'>AI 智能对话</Text>
|
||||
</View>
|
||||
<View className='feature-item'>
|
||||
<Image className='feature-icon-image' src={require('../../assets/tab-icons/location-icon.png')} mode='aspectFit' />
|
||||
<Image className='feature-icon-image' src={require('../../assets/ui/feature-location.png')} mode='aspectFit' />
|
||||
<Text className='feature-text'>实时定位追踪</Text>
|
||||
</View>
|
||||
<View className='feature-item'>
|
||||
<Image className='feature-icon-image' src={require('../../assets/tab-icons/device-icon.png')} mode='aspectFit' />
|
||||
<Image className='feature-icon-image' src={require('../../assets/ui/feature-status.png')} mode='aspectFit' />
|
||||
<Text className='feature-text'>设备状态监控</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Taro, { useRouter } from '@tarojs/taro'
|
||||
@@ -49,7 +50,7 @@ export default function NotificationEntry() {
|
||||
}, [token])
|
||||
|
||||
return (
|
||||
<View className='notification-page'>
|
||||
<View className='notification-page' style={getPageStyle(false)}>
|
||||
<Image className='notification-mascot' src={require('../../assets/ui/dog-question.png')} mode='aspectFit' />
|
||||
<View className='notification-panel'>
|
||||
<Text className='notification-title'>伴伴</Text>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Picker, Image } from '@tarojs/components'
|
||||
import { useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
@@ -107,7 +108,7 @@ export default function SleepSchedule() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View className='sleep-page'>
|
||||
<View className='sleep-page' style={getPageStyle(false)}>
|
||||
<View className='loading'>
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
@@ -118,7 +119,7 @@ export default function SleepSchedule() {
|
||||
|
||||
if (!child) {
|
||||
return (
|
||||
<View className='sleep-page'>
|
||||
<View className='sleep-page' style={getPageStyle(false)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>定时休眠</Text>
|
||||
<Text className='page-subtitle'>先绑定设备并选择宝贝,再为对应设备设置休眠时间</Text>
|
||||
@@ -134,7 +135,7 @@ export default function SleepSchedule() {
|
||||
|
||||
if (!binding) {
|
||||
return (
|
||||
<View className='sleep-page'>
|
||||
<View className='sleep-page' style={getPageStyle(false)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>定时休眠</Text>
|
||||
<Text className='page-subtitle'>{child.child_name} 还没有绑定设备</Text>
|
||||
@@ -156,7 +157,7 @@ export default function SleepSchedule() {
|
||||
const childName = child.child_name || '当前宝贝'
|
||||
|
||||
return (
|
||||
<View className='sleep-page'>
|
||||
<View className='sleep-page' style={getPageStyle(false)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>定时休眠</Text>
|
||||
<Text className='page-subtitle'>每天在设定时段内,设备将进入不可启动状态</Text>
|
||||
|
||||
@@ -1058,3 +1058,6 @@
|
||||
color: #E5484D;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.system-update-title-row { display: flex; align-items: center; gap: 12px; }
|
||||
.system-update-icon { width: 54px; height: 54px; }
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import FirmwareProgress from '@/components/firmware-progress'
|
||||
import { getRoleArtwork } from '@/utils/artwork'
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { View, Text, Image, Input, Button } from '@tarojs/components'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Taro, { useDidHide, useDidShow, useShareAppMessage } from '@tarojs/taro'
|
||||
@@ -82,6 +85,10 @@ export default function Sleep() {
|
||||
const [binding, setBinding] = useState<BindingListItem | null>(null)
|
||||
const [currentChild, setCurrentChild] = useState<Child | null>(null)
|
||||
const [firmwareStatus, setFirmwareStatus] = useState<DeviceFirmwareStatus | null>(null)
|
||||
const [showFirmwareProgress, setShowFirmwareProgress] = useState(false)
|
||||
const [firmwareRefreshError, setFirmwareRefreshError] = useState(false)
|
||||
const firmwareLoadGenerationRef = useRef(0)
|
||||
const firmwareDeviceIdRef = useRef('')
|
||||
const [deviceRole, setDeviceRole] = useState<DeviceCurrentRole | null>(null)
|
||||
const [roles, setRoles] = useState<DeviceRoleSummary[]>([])
|
||||
const [currentCardGroup, setCurrentCardGroup] = useState<BindingCardGroupItem | null>(null)
|
||||
@@ -269,6 +276,7 @@ export default function Sleep() {
|
||||
if (firmwareStatusPollDeviceIdRef.current !== normalizedDeviceId) return
|
||||
|
||||
setFirmwareStatus(nextFirmwareStatus)
|
||||
setFirmwareRefreshError(false)
|
||||
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status)) {
|
||||
scheduleFirmwareStatusPoll(normalizedDeviceId)
|
||||
return
|
||||
@@ -283,6 +291,7 @@ export default function Sleep() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[manage] firmware poll failed:', error)
|
||||
if (firmwareStatusPollDeviceIdRef.current === normalizedDeviceId) setFirmwareRefreshError(true)
|
||||
if (firmwareStatusPollDeviceIdRef.current === normalizedDeviceId) {
|
||||
scheduleFirmwareStatusPoll(normalizedDeviceId)
|
||||
}
|
||||
@@ -293,6 +302,13 @@ export default function Sleep() {
|
||||
|
||||
const loadFirmwareStatus = async (deviceId?: string | null) => {
|
||||
const normalizedDeviceId = String(deviceId || '').trim()
|
||||
const generation = ++firmwareLoadGenerationRef.current
|
||||
if (firmwareDeviceIdRef.current !== normalizedDeviceId) {
|
||||
setShowFirmwareProgress(false)
|
||||
setFirmwareStatus(null)
|
||||
stopFirmwareStatusPolling()
|
||||
}
|
||||
firmwareDeviceIdRef.current = normalizedDeviceId
|
||||
if (!normalizedDeviceId) {
|
||||
setFirmwareStatus(null)
|
||||
setIsLoadingFirmware(false)
|
||||
@@ -303,9 +319,13 @@ export default function Sleep() {
|
||||
setIsLoadingFirmware(true)
|
||||
try {
|
||||
const nextFirmwareStatus = await getDeviceFirmwareStatus(normalizedDeviceId)
|
||||
if (generation !== firmwareLoadGenerationRef.current) return
|
||||
setFirmwareStatus(nextFirmwareStatus)
|
||||
setFirmwareRefreshError(false)
|
||||
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status)) setShowFirmwareProgress(true)
|
||||
syncFirmwareStatusPolling(normalizedDeviceId, nextFirmwareStatus)
|
||||
} catch (error: any) {
|
||||
if (generation !== firmwareLoadGenerationRef.current) return
|
||||
console.error('[manage] firmware load failed:', error)
|
||||
setFirmwareStatus(null)
|
||||
if (firmwareStatusPollDeviceIdRef.current === normalizedDeviceId) {
|
||||
@@ -316,7 +336,7 @@ export default function Sleep() {
|
||||
icon: 'none',
|
||||
})
|
||||
} finally {
|
||||
setIsLoadingFirmware(false)
|
||||
if (generation === firmwareLoadGenerationRef.current) setIsLoadingFirmware(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,6 +769,9 @@ export default function Sleep() {
|
||||
}
|
||||
|
||||
const nextFirmwareStatus = await startDeviceFirmwareUpdate(binding.device_id)
|
||||
if (firmwareDeviceIdRef.current !== binding.device_id) return
|
||||
setShowFirmwareProgress(true)
|
||||
setFirmwareRefreshError(false)
|
||||
setFirmwareStatus(nextFirmwareStatus)
|
||||
syncFirmwareStatusPolling(binding.device_id, nextFirmwareStatus)
|
||||
Taro.showToast({
|
||||
@@ -961,7 +984,7 @@ export default function Sleep() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View className='manage-page'>
|
||||
<View className='manage-page' style={getPageStyle(true)}>
|
||||
<View className='loading'>
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
@@ -972,7 +995,7 @@ export default function Sleep() {
|
||||
|
||||
if (isGuest) {
|
||||
return (
|
||||
<View className='manage-page'>
|
||||
<View className='manage-page' style={getPageStyle(true)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>设置与管理</Text>
|
||||
</View>
|
||||
@@ -1054,7 +1077,7 @@ export default function Sleep() {
|
||||
: isUpdatingFirmware
|
||||
? '发送中'
|
||||
: isFirmwareUpdatingByStatus
|
||||
? '更新中'
|
||||
? '查看进度'
|
||||
: firmwareStatus?.update_available
|
||||
? '更新'
|
||||
: '已最新'
|
||||
@@ -1093,7 +1116,7 @@ export default function Sleep() {
|
||||
: `${currentCardGroup?.card_count || 0} 张`
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
icon: require('../../assets/tab-icons/orange-robot.png'),
|
||||
icon: require('../../assets/ui/menu-role.png'),
|
||||
iconBgClass: 'orange',
|
||||
name: '当前宝贝',
|
||||
value: currentChildName,
|
||||
@@ -1101,7 +1124,7 @@ export default function Sleep() {
|
||||
},
|
||||
...(unassignedBindings.length > 0
|
||||
? [{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
icon: require('../../assets/ui/menu-binding.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '关联现有设备',
|
||||
value: `${unassignedBindings.length} 台待关联`,
|
||||
@@ -1109,14 +1132,14 @@ export default function Sleep() {
|
||||
}]
|
||||
: []),
|
||||
{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
icon: require('../../assets/ui/menu-binding.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '绑定设备',
|
||||
value: binding?.device_id ? `当前: ${binding.device_id}` : '未绑定',
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
icon: require('../../assets/ui/menu-cards.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '我的卡片',
|
||||
value: cardValue,
|
||||
@@ -1124,7 +1147,7 @@ export default function Sleep() {
|
||||
disabled: !binding,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/orange-robot.png'),
|
||||
icon: require('../../assets/ui/menu-role.png'),
|
||||
iconBgClass: 'green',
|
||||
name: 'AI 角色',
|
||||
value: roleValue,
|
||||
@@ -1132,7 +1155,7 @@ export default function Sleep() {
|
||||
disabled: !binding,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
icon: require('../../assets/ui/menu-family.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '家庭成员',
|
||||
value: familyValue,
|
||||
@@ -1140,7 +1163,7 @@ export default function Sleep() {
|
||||
disabled: !binding,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
icon: require('../../assets/ui/menu-notice.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '服务号通知',
|
||||
value: wechatMpValue,
|
||||
@@ -1148,7 +1171,7 @@ export default function Sleep() {
|
||||
disabled: isStartingWechatMpBind,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/broken-rings.png'),
|
||||
icon: require('../../assets/ui/menu-unbind.png'),
|
||||
iconBgClass: 'red',
|
||||
name: '解除设备绑定',
|
||||
value: binding?.device_id ? `当前: ${binding.device_id}` : '',
|
||||
@@ -1158,7 +1181,7 @@ export default function Sleep() {
|
||||
]
|
||||
|
||||
return (
|
||||
<View className='manage-page'>
|
||||
<View className='manage-page' style={getPageStyle(true)}>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>设置与管理</Text>
|
||||
</View>
|
||||
@@ -1210,12 +1233,15 @@ export default function Sleep() {
|
||||
<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>
|
||||
<View className='system-update-title-row'>
|
||||
<Image className='system-update-icon' src={require('../../assets/ui/menu-update.png')} mode='aspectFit' />
|
||||
<Text className='system-update-title'>系统更新</Text>
|
||||
</View>
|
||||
<Text className='system-update-subtitle'>{firmwareSubtitle}</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`system-update-action ${canUpdateFirmware ? '' : 'disabled'}`}
|
||||
onClick={canUpdateFirmware ? handleStartFirmwareUpdate : undefined}
|
||||
className={`system-update-action ${canUpdateFirmware || isFirmwareUpdatingByStatus ? '' : 'disabled'}`}
|
||||
onClick={isFirmwareUpdatingByStatus ? () => setShowFirmwareProgress(true) : canUpdateFirmware ? handleStartFirmwareUpdate : undefined}
|
||||
>
|
||||
<Text className='system-update-action-text'>{firmwareActionText}</Text>
|
||||
</View>
|
||||
@@ -1266,6 +1292,12 @@ export default function Sleep() {
|
||||
<Text className='version-text'>伴伴 Companion V1.1.0</Text>
|
||||
</View>
|
||||
|
||||
{showFirmwareProgress && firmwareStatus?.device_id === binding?.device_id && (
|
||||
<FirmwareProgress status={firmwareStatus} refreshError={firmwareRefreshError}
|
||||
onClose={() => setShowFirmwareProgress(false)}
|
||||
onRetry={canUpdateFirmware ? () => { setShowFirmwareProgress(false); handleStartFirmwareUpdate() } : undefined} />
|
||||
)}
|
||||
|
||||
{showChildModal && (
|
||||
<View className='modal-mask' onClick={() => setShowChildModal(false)}>
|
||||
<View
|
||||
@@ -1443,7 +1475,8 @@ export default function Sleep() {
|
||||
{showModal && (
|
||||
<View className='modal-mask'>
|
||||
<View className='modal-card'>
|
||||
<Text className='modal-title'>修改宝贝姓名</Text>
|
||||
<Text className='modal-title'>编辑宝贝资料</Text>
|
||||
<Text className='modal-field-label'>宝贝名称</Text>
|
||||
<View className='modal-input-wrap'>
|
||||
<Input
|
||||
className='modal-input'
|
||||
@@ -1459,7 +1492,7 @@ export default function Sleep() {
|
||||
取消
|
||||
</Text>
|
||||
<Text className='modal-action confirm' onClick={handleSubmitModal}>
|
||||
确认
|
||||
保存
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -1520,6 +1553,7 @@ export default function Sleep() {
|
||||
className={`role-switch-item ${isActive ? 'active' : ''} ${isUpdatingRole ? 'disabled' : ''}`}
|
||||
onClick={isUpdatingRole ? undefined : () => handleSelectRole(item)}
|
||||
>
|
||||
<Image className='role-switch-art' src={getRoleArtwork(item.role_key)} mode='aspectFit' />
|
||||
<View className='role-switch-head'>
|
||||
<Text className='role-switch-name'>{item.name || item.role_key}</Text>
|
||||
{isActive && <Text className='device-switch-tag'>当前</Text>}
|
||||
|
||||
Reference in New Issue
Block a user