feat(小程序): 支持多设备绑定与管理
This commit is contained in:
@@ -161,149 +161,198 @@
|
||||
}
|
||||
}
|
||||
|
||||
.add-input-box {
|
||||
.image-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20px;
|
||||
margin: 0 24px 24px;
|
||||
padding: 24px;
|
||||
padding: 10px 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
.summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #F2F2F2;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
.name-input {
|
||||
background: #F5F7FA;
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
font-size: 30px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.summary-label {
|
||||
font-size: 28px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.input-hint {
|
||||
font-size: 24px;
|
||||
color: #999999;
|
||||
margin-top: 12px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.input-btns {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 24px;
|
||||
|
||||
.confirm-btn {
|
||||
font-size: 30px;
|
||||
color: #FF8C42;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
font-size: 30px;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
.summary-value {
|
||||
max-width: 60%;
|
||||
font-size: 28px;
|
||||
color: #1A1A1A;
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.menu-item.disabled {
|
||||
opacity: 0.5;
|
||||
|
||||
&:active {
|
||||
background: transparent;
|
||||
}
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.logout-section {
|
||||
margin: 24px;
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
height: 92px;
|
||||
border-radius: 18px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20px;
|
||||
padding: 26px 24px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.logout-text {
|
||||
font-size: 30px;
|
||||
color: #FF3B30;
|
||||
font-weight: 500;
|
||||
}
|
||||
.logout-text {
|
||||
font-size: 30px;
|
||||
color: #FF6B35;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.42);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 0 48px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
.modal-card {
|
||||
width: 100%;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20px;
|
||||
width: 600px;
|
||||
margin: 0 48px;
|
||||
overflow: hidden;
|
||||
border-radius: 24px;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 32px 32px 16px;
|
||||
text-align: center;
|
||||
.modal-title {
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 34px;
|
||||
.modal-input-wrap {
|
||||
background: #F7F8FA;
|
||||
border-radius: 16px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.modal-input {
|
||||
height: 88px;
|
||||
width: 100%;
|
||||
font-size: 28px;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.modal-action {
|
||||
font-size: 30px;
|
||||
margin-left: 32px;
|
||||
|
||||
&.cancel {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
color: #FF8C42;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px 32px 32px;
|
||||
.device-switch-card {
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.device-switch-empty {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.device-switch-list {
|
||||
max-height: 52vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.device-switch-item {
|
||||
padding: 24px;
|
||||
border-radius: 18px;
|
||||
background: #F7F8FA;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #FFF2E7;
|
||||
border: 2px solid #FFBE94;
|
||||
}
|
||||
}
|
||||
|
||||
.device-switch-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.modal-input {
|
||||
background: #F5F7FA;
|
||||
border-radius: 12px;
|
||||
padding: 0 24px;
|
||||
font-size: 34px;
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
.device-switch-id {
|
||||
font-size: 28px;
|
||||
color: #1A1A1A;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.device-switch-tag {
|
||||
margin-left: 16px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
background: #FF8C42;
|
||||
font-size: 22px;
|
||||
color: #FFFFFF;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.device-switch-name {
|
||||
font-size: 26px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.loading {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
border-top: 1px solid #F0F0F0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.modal-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
text {
|
||||
font-size: 32px;
|
||||
|
||||
&.cancel {
|
||||
color: #666666;
|
||||
border-right: 1px solid #F0F0F0;
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
color: #FF8C42;
|
||||
font-weight: 500;
|
||||
}
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,210 +1,270 @@
|
||||
import { View, Text, Image, Input } from '@tarojs/components'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { getToken, getCurrentUserId } from '@/services/api'
|
||||
import { getChildren, createChild, updateChild } from '@/services/child'
|
||||
import { getCurrentBinding, unbindDevice } from '@/services/binding'
|
||||
import { useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { clearToken, getCurrentUserId, getToken } from '@/services/auth'
|
||||
import {
|
||||
BindingListItem,
|
||||
clearSelectedBindingDeviceId,
|
||||
getBindings,
|
||||
resolveBindingSelection,
|
||||
setBindingChild,
|
||||
setSelectedBindingDeviceId,
|
||||
unbindDevice,
|
||||
} from '@/services/binding'
|
||||
import { Child, createChild, getChildren, updateChild } from '@/services/child'
|
||||
import './index.scss'
|
||||
|
||||
interface Child {
|
||||
child_id: number
|
||||
child_name: string
|
||||
child_gender: number
|
||||
status: number
|
||||
}
|
||||
|
||||
interface Binding {
|
||||
device_id: string
|
||||
child_id: number | null
|
||||
status: number
|
||||
}
|
||||
|
||||
interface MenuItem {
|
||||
icon: string
|
||||
iconBgClass: string
|
||||
name: string
|
||||
value?: string
|
||||
value: string
|
||||
arrow?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function Sleep() {
|
||||
const [children, setChildren] = useState<Child[]>([])
|
||||
const [binding, setBinding] = useState<Binding | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [children, setChildren] = useState<Child[]>([])
|
||||
const [bindings, setBindings] = useState<BindingListItem[]>([])
|
||||
const [binding, setBinding] = useState<BindingListItem | null>(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [showDeviceModal, setShowDeviceModal] = 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 }>({})
|
||||
|
||||
const token = getToken()
|
||||
const userId = getCurrentUserId()
|
||||
useDidShow(() => {
|
||||
void loadData()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
const loadData = async () => {
|
||||
if (!getToken()) {
|
||||
Taro.reLaunch({ url: '/pages/login/index' })
|
||||
return
|
||||
}
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
// Get parent info
|
||||
const userInfo = Taro.getStorageSync('userInfo') || {}
|
||||
setParentInfo(userInfo)
|
||||
|
||||
// Get children
|
||||
const childRes = await getChildren()
|
||||
setChildren(childRes.items || [])
|
||||
const [childResponse, bindingResponse] = await Promise.all([getChildren(), getBindings(undefined, 100)])
|
||||
const bindingItems = bindingResponse.items || []
|
||||
const activeBinding = resolveBindingSelection(bindingItems)
|
||||
|
||||
// Get binding
|
||||
try {
|
||||
const bindRes = await getCurrentBinding()
|
||||
setBinding(bindRes)
|
||||
} catch (err: any) {
|
||||
if (err.status === 404) {
|
||||
setBinding(null)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Load failed:', err)
|
||||
setChildren(childResponse.items || [])
|
||||
setBindings(bindingItems)
|
||||
setBinding(activeBinding)
|
||||
setParentInfo(Taro.getStorageSync('userInfo') || {})
|
||||
} catch (error: any) {
|
||||
console.error('[manage] load failed:', error)
|
||||
Taro.showToast({
|
||||
title: error?.message || '加载失败,请稍后重试',
|
||||
icon: 'none',
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const currentChild = binding?.child_id ? children.find((item) => item.child_id === binding.child_id) || null : null
|
||||
const currentChildName = currentChild?.child_name || binding?.child_name || (binding && !binding.child_id ? '待关联' : '未设置')
|
||||
|
||||
const handleOpenModal = (type: 'add' | 'edit', child?: Child) => {
|
||||
setModalType(type)
|
||||
if (type === 'edit' && child) {
|
||||
setChildName(child.child_name)
|
||||
setEditingChildId(child.child_id)
|
||||
} else {
|
||||
setChildName('')
|
||||
setEditingChildId(null)
|
||||
}
|
||||
setChildName(child?.child_name || '')
|
||||
setEditingChildId(child?.child_id || null)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const handleModalConfirm = async () => {
|
||||
if (!childName.trim()) {
|
||||
Taro.showToast({ title: '请输入昵称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (modalType === 'add') {
|
||||
await createChild({ child_name: childName.trim() })
|
||||
Taro.showToast({ title: '添加成功', icon: 'success' })
|
||||
} else if (editingChildId) {
|
||||
await updateChild(editingChildId, { child_name: childName.trim() })
|
||||
Taro.showToast({ title: '修改成功', icon: 'success' })
|
||||
}
|
||||
setShowModal(false)
|
||||
setChildName('')
|
||||
setEditingChildId(null)
|
||||
loadData()
|
||||
} catch (err) {
|
||||
Taro.showToast({ title: '操作失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
const handleCloseModal = () => {
|
||||
setShowModal(false)
|
||||
setChildName('')
|
||||
setEditingChildId(null)
|
||||
}
|
||||
|
||||
const handleUnbind = () => {
|
||||
if (!binding) return
|
||||
Taro.showModal({
|
||||
title: '解除设备绑定',
|
||||
content: '确定要解除当前设备的绑定吗?',
|
||||
confirmColor: '#FF8C42',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await unbindDevice(binding.device_id)
|
||||
Taro.showToast({ title: '已解绑', icon: 'success' })
|
||||
setBinding(null)
|
||||
loadData()
|
||||
} catch (err) {
|
||||
Taro.showToast({ title: '解绑失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleSelectBinding = (targetBinding: BindingListItem) => {
|
||||
setSelectedBindingDeviceId(targetBinding.device_id)
|
||||
setBinding(targetBinding)
|
||||
setShowDeviceModal(false)
|
||||
Taro.showToast({
|
||||
title: '已切换设备',
|
||||
icon: 'success',
|
||||
})
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
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 })
|
||||
if (binding && !binding.child_id) {
|
||||
await setBindingChild(binding.device_id, { child_id: createdChild.child_id })
|
||||
Taro.showToast({ title: '已创建并关联', icon: 'success' })
|
||||
} else {
|
||||
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 handleChildMenu = () => {
|
||||
if (binding && !binding.child_id && children.length > 0) {
|
||||
Taro.showActionSheet({
|
||||
itemList: [...children.map((item) => item.child_name), '新建儿童资料'],
|
||||
success: async (result) => {
|
||||
if (result.tapIndex === children.length) {
|
||||
handleOpenModal('add')
|
||||
return
|
||||
}
|
||||
|
||||
const targetChild = children[result.tapIndex]
|
||||
if (!targetChild) return
|
||||
|
||||
try {
|
||||
await setBindingChild(binding.device_id, { child_id: targetChild.child_id })
|
||||
Taro.showToast({ title: '关联成功', icon: 'success' })
|
||||
await loadData()
|
||||
} catch (error: any) {
|
||||
Taro.showToast({
|
||||
title: error?.message || '关联失败,请重试',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (currentChild) {
|
||||
handleOpenModal('edit', currentChild)
|
||||
return
|
||||
}
|
||||
|
||||
handleOpenModal('add')
|
||||
}
|
||||
|
||||
const handleUnbind = () => {
|
||||
if (!binding) return
|
||||
|
||||
Taro.showModal({
|
||||
title: '解除设备绑定',
|
||||
content: '确定要解除当前设备绑定吗?',
|
||||
confirmColor: '#FF8C42',
|
||||
success: async (result) => {
|
||||
if (!result.confirm) return
|
||||
|
||||
try {
|
||||
await unbindDevice(binding.device_id)
|
||||
Taro.showToast({ title: '已解绑', icon: 'success' })
|
||||
await loadData()
|
||||
} catch (error: any) {
|
||||
Taro.showToast({
|
||||
title: error?.message || '解绑失败,请重试',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
Taro.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出登录吗?',
|
||||
content: '确定要退出当前账号吗?',
|
||||
confirmColor: '#FF8C42',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
Taro.removeStorageSync('token')
|
||||
Taro.removeStorageSync('user_id')
|
||||
Taro.removeStorageSync('userInfo')
|
||||
Taro.reLaunch({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
success: (result) => {
|
||||
if (!result.confirm) return
|
||||
clearToken()
|
||||
clearSelectedBindingDeviceId()
|
||||
Taro.removeStorageSync('userInfo')
|
||||
Taro.reLaunch({ url: '/pages/login/index' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleMenuClick = (item: MenuItem) => {
|
||||
if (item.disabled) return
|
||||
|
||||
|
||||
if (item.name === '儿童资料 (用于称呼)') {
|
||||
handleChildMenu()
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '绑定新设备') {
|
||||
if (binding) {
|
||||
Taro.showToast({ title: '已有设备绑定', icon: 'none' })
|
||||
} else {
|
||||
Taro.navigateTo({ url: '/pages/bind/index' })
|
||||
}
|
||||
} else if (item.name === '解除设备绑定') {
|
||||
Taro.navigateTo({ url: '/pages/bind/index' })
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '切换设备') {
|
||||
setShowDeviceModal(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '解除设备绑定') {
|
||||
handleUnbind()
|
||||
} else if (item.name.includes('儿童资料')) {
|
||||
if (children.length > 0) {
|
||||
handleOpenModal('edit', children[0])
|
||||
} else {
|
||||
handleOpenModal('add')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!token) return null
|
||||
if (loading) {
|
||||
return (
|
||||
<View className='manage-page'>
|
||||
<View className='loading'>
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const userId = getCurrentUserId()
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
icon: require('../../assets/tab-icons/orange-robot.png'),
|
||||
iconBgClass: 'orange',
|
||||
name: '儿童资料 (用于称呼)',
|
||||
value: children.length > 0 ? children[0].child_name : '未设置',
|
||||
arrow: true
|
||||
value: currentChildName,
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '切换设备',
|
||||
value: bindings.length > 0 ? `共 ${bindings.length} 台` : '暂无设备',
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/orange-robot.png'),
|
||||
iconBgClass: 'orange',
|
||||
name: '绑定新设备',
|
||||
value: '',
|
||||
arrow: true
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/broken-rings.png'),
|
||||
iconBgClass: 'red',
|
||||
name: '解除设备绑定',
|
||||
value: '',
|
||||
value: binding?.device_id ? `当前: ${binding.device_id}` : '',
|
||||
arrow: true,
|
||||
disabled: !binding
|
||||
}
|
||||
disabled: !binding,
|
||||
},
|
||||
]
|
||||
|
||||
const firstChild = children[0]
|
||||
const childDisplayValue = firstChild ? firstChild.child_name : ''
|
||||
|
||||
return (
|
||||
<View className='manage-page'>
|
||||
<View className='page-header'>
|
||||
@@ -214,24 +274,39 @@ export default function Sleep() {
|
||||
<View className='user-card'>
|
||||
<View className='user-avatar'>
|
||||
{parentInfo.avatar_url ? (
|
||||
<Image className='avatar-img' src={parentInfo.avatar_url} mode='aspectFill' />
|
||||
<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'>{parentInfo.nickname || '家长用户'}</Text>
|
||||
<Text className='user-role'>ID: {userId}</Text>
|
||||
<Text className='user-role'>用户 ID: {userId || '--'}</Text>
|
||||
</View>
|
||||
<View className='verified-badge'>
|
||||
<Text>{bindings.length > 0 ? `已绑定 ${bindings.length} 台` : '未绑定设备'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='summary-card'>
|
||||
<View className='summary-row'>
|
||||
<Text className='summary-label'>已绑定设备数</Text>
|
||||
<Text className='summary-value'>{bindings.length} 台</Text>
|
||||
</View>
|
||||
<View className='summary-row'>
|
||||
<Text className='summary-label'>当前设备</Text>
|
||||
<Text className='summary-value'>{binding?.device_id || '未绑定'}</Text>
|
||||
</View>
|
||||
<View className='summary-row'>
|
||||
<Text className='summary-label'>当前儿童</Text>
|
||||
<Text className='summary-value'>{currentChildName}</Text>
|
||||
</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-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' />
|
||||
@@ -239,9 +314,7 @@ export default function Sleep() {
|
||||
<Text className='menu-name'>{item.name}</Text>
|
||||
</View>
|
||||
<View className='menu-right'>
|
||||
{item.name.includes('儿童资料') && (
|
||||
<Text className='menu-value'>{childDisplayValue}</Text>
|
||||
)}
|
||||
{item.value && <Text className='menu-value'>{item.value}</Text>}
|
||||
{item.arrow && <Text className='arrow'>›</Text>}
|
||||
</View>
|
||||
</View>
|
||||
@@ -260,25 +333,66 @@ export default function Sleep() {
|
||||
<Text className='version-text'>伴伴 Companion V1.1.0</Text>
|
||||
</View>
|
||||
|
||||
{/* Modal */}
|
||||
{showDeviceModal && (
|
||||
<View className='modal-mask' onClick={() => setShowDeviceModal(false)}>
|
||||
<View
|
||||
className='modal-card device-switch-card'
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<Text className='modal-title'>切换设备</Text>
|
||||
{bindings.length === 0 ? (
|
||||
<Text className='device-switch-empty'>当前还没有已绑定设备</Text>
|
||||
) : (
|
||||
<View className='device-switch-list'>
|
||||
{bindings.map((item) => {
|
||||
const isActive = binding?.device_id === item.device_id
|
||||
return (
|
||||
<View
|
||||
key={item.device_id}
|
||||
className={`device-switch-item ${isActive ? 'active' : ''}`}
|
||||
onClick={() => handleSelectBinding(item)}
|
||||
>
|
||||
<View className='device-switch-head'>
|
||||
<Text className='device-switch-id'>{item.device_id}</Text>
|
||||
{isActive && <Text className='device-switch-tag'>当前</Text>}
|
||||
</View>
|
||||
<Text className='device-switch-name'>{item.child_name || '待关联儿童'}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
<View className='modal-actions'>
|
||||
<Text className='modal-action cancel' onClick={() => setShowDeviceModal(false)}>
|
||||
关闭
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{showModal && (
|
||||
<View className='modal-mask'>
|
||||
<View className='modal-content'>
|
||||
<View className='modal-header'>
|
||||
<Text className='modal-title'>{modalType === 'add' ? '添加儿童' : '修改昵称'}</Text>
|
||||
</View>
|
||||
<View className='modal-body'>
|
||||
<Input
|
||||
<View className='modal-card'>
|
||||
<Text className='modal-title'>{modalType === 'add' ? '新建儿童资料' : '修改儿童昵称'}</Text>
|
||||
<View className='modal-input-wrap'>
|
||||
<Input
|
||||
className='modal-input'
|
||||
placeholder='请输入儿童昵称'
|
||||
value={childName}
|
||||
onInput={(e) => setChildName(e.detail.value)}
|
||||
focus={true}
|
||||
placeholder='请输入儿童昵称'
|
||||
focus
|
||||
onInput={(event) => setChildName(event.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<View className='modal-footer'>
|
||||
<Text className='modal-btn cancel' onClick={handleModalCancel}>取消</Text>
|
||||
<Text className='modal-btn confirm' onClick={handleModalConfirm}>确认</Text>
|
||||
<View className='modal-actions'>
|
||||
<Text className='modal-action cancel' onClick={handleCloseModal}>
|
||||
取消
|
||||
</Text>
|
||||
<Text className='modal-action confirm' onClick={handleSubmitModal}>
|
||||
确认
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user