Refine device-first baby management

This commit is contained in:
stu2not
2026-07-02 11:43:09 +08:00
parent 998401f8c9
commit b4fef6d2e0
11 changed files with 275 additions and 201 deletions

View File

@@ -201,7 +201,7 @@ export default function Bind() {
const isExpiredBind = bindStatus === SESSION_STATUS_EXPIRED
const canEditDeviceFields = !isPendingBinding && !isPollingBind
const canSubmit = isPendingBinding ? hasSelectedOrNewChild : isExpiredBind ? true : !isPollingBind
const pageTitle = isPendingBinding ? '补全绑定' : isAddingCard ? '添加设备卡' : '扫码贴卡绑定设备'
const pageTitle = isPendingBinding ? '补全绑定' : isAddingCard ? '添加设备卡' : '扫码贴卡绑定设备'
const goDevicePage = () => {
Taro.reLaunch({ url: '/pages/device/index' })
@@ -286,7 +286,7 @@ export default function Bind() {
if (parsed.serialNumber) setSerialNumber(parsed.serialNumber)
setBindHint(
parsed.serialNumber
? '已读取设备二维码信息,请选择儿童后发送绑卡指令'
? '已读取设备二维码信息,请选择宝贝后发送绑卡指令'
: '已读取设备号,请补充设备序列号'
)
return true
@@ -364,7 +364,7 @@ export default function Bind() {
? session.card_uuid ? `新卡添加完成,卡号 ${session.card_uuid}` : '新卡添加完成'
: session.child_id
? session.card_uuid ? `绑定完成,卡号 ${session.card_uuid}` : '绑定完成'
: session.card_uuid ? `绑定完成,卡号 ${session.card_uuid},稍后可关联孩子` : '绑定完成,稍后可关联孩子'
: session.card_uuid ? `绑定完成,卡号 ${session.card_uuid},稍后可关联宝贝` : '绑定完成,稍后可关联宝贝'
)
Taro.showToast({ title: isAddingCard ? '新卡添加成功' : '设备绑定成功', icon: 'success' })
setTimeout(() => {
@@ -500,7 +500,7 @@ export default function Bind() {
if (isPendingBinding) {
if (!childId) {
throw new Error('请先选择儿童或填写新的儿童昵称')
throw new Error('请先选择宝贝或填写新的宝贝昵称')
}
await setBindingChild(pendingDeviceId!, { child_id: childId })
setSelectedBindingDeviceId(pendingDeviceId)
@@ -585,7 +585,7 @@ export default function Bind() {
<View className='icon-bg orange'>
<Image className='control-icon-img' src={require('../../assets/tab-icons/rings.png')} mode='aspectFit' />
</View>
<Text className='pending-device-title'></Text>
<Text className='pending-device-title'></Text>
<Text className='pending-device-id'>{deviceId}</Text>
<Text className='pending-device-desc'></Text>
</View>
@@ -596,7 +596,7 @@ export default function Bind() {
</View>
<Text className='pending-device-title'></Text>
<Text className='pending-device-id'>{pendingDeviceId}</Text>
<Text className='pending-device-desc'></Text>
<Text className='pending-device-desc'></Text>
</View>
) : (
<View className='scan-area'>
@@ -622,7 +622,7 @@ export default function Bind() {
<View className='bind-form'>
{!isAddingCard && (
<View className='form-card'>
<Text className='form-title'></Text>
<Text className='form-title'></Text>
{children.length > 0 && (
<View className='child-list'>
@@ -635,7 +635,7 @@ export default function Bind() {
setNewChildName('')
}}
>
<Text className='child-item-text'></Text>
<Text className='child-item-text'></Text>
</View>
)}
{children.map((child) => (
@@ -658,14 +658,14 @@ export default function Bind() {
)}
<View className='field-item'>
<Text className='field-label'>{isPendingBinding ? '新建儿童昵称' : '新建儿童昵称(可选)'}</Text>
<Text className='field-label'>{isPendingBinding ? '新建宝贝昵称' : '新建宝贝昵称(可选)'}</Text>
<Input
className='field-input'
value={newChildName}
placeholder={
isPendingBinding
? children.length > 0 ? '不选已有儿童时,可填写新昵称' : '请输入儿童昵称'
: children.length > 0 ? '不选已有儿童时,可填写新昵称' : '可不填,稍后再关联孩子'
? children.length > 0 ? '不选已有宝贝时,可填写新昵称' : '请输入宝贝昵称'
: children.length > 0 ? '不选已有宝贝时,可填写新昵称' : '可不填,稍后再关联宝贝'
}
onInput={(event) => {
setNewChildName(event.detail.value)
@@ -680,11 +680,11 @@ export default function Bind() {
<Text className='field-hint'>
{isPendingBinding
? children.length > 0
? '可以选已有儿童,也可以新建一个儿童后关联当前设备'
: '当前还没有儿童资料,请先创建一个儿童资料后关联设备'
? '可以选已有宝贝,也可以新建一个宝贝后关联当前设备'
: '当前还没有宝贝资料,请先创建一个宝贝资料后关联设备'
: children.length > 0
? '儿童资料可选;不选择时,设备会先绑定到账号,稍后可关联孩子'
: '也可以先不创建儿童,直接绑定设备,稍后再关联孩子'}
? '宝贝资料可选;不选择时,设备会先绑定到账号,稍后可关联宝贝'
: '也可以先不创建宝贝,直接绑定设备,稍后再关联宝贝'}
</Text>
</View>
)}
@@ -742,7 +742,7 @@ export default function Bind() {
onClick={isExpiredBind && !isPendingBinding && !isAddingCard ? handleScanCode : handleSubmit}
>
{isPendingBinding
? '完成儿童关联'
? '完成宝贝关联'
: isPollingBind
? '等待贴卡确认'
: isExpiredBind
@@ -755,15 +755,15 @@ export default function Bind() {
<Text className='tips-title'></Text>
<View className='tip-item'>
<Text className='tip-number'>1</Text>
<Text className='tip-text'>{isAddingCard ? '确认当前设备号正确' : isPendingBinding ? '先选择已有儿童,或者在本页新建一个儿童' : '儿童资料可选,不选择时设备会先进入待关联状态'}</Text>
<Text className='tip-text'>{isAddingCard ? '确认当前设备号正确' : isPendingBinding ? '先选择已有宝贝,或者在本页新建一个宝贝' : '宝贝资料可选,不选择时设备会先进入待关联状态'}</Text>
</View>
<View className='tip-item'>
<Text className='tip-number'>2</Text>
<Text className='tip-text'>{isAddingCard ? '点击发送加卡指令后,在设备上贴新卡' : isPendingBinding ? '确认当前待关联设备,再选择要关联的孩子' : '扫描设备二维码,确认设备号和序列号正确'}</Text>
<Text className='tip-text'>{isAddingCard ? '点击发送加卡指令后,在设备上贴新卡' : isPendingBinding ? '确认当前待关联设备,再选择要关联的宝贝' : '扫描设备二维码,确认设备号和序列号正确'}</Text>
</View>
<View className='tip-item'>
<Text className='tip-number'>3</Text>
<Text className='tip-text'>{isAddingCard ? '加卡成功后,新卡和原来的卡可以执行相同功能' : isPendingBinding ? '点击完成儿童关联,设备会出现在该孩子下面' : '点击发送绑卡指令,然后在 10 分钟内去设备上贴自己的卡完成确认'}</Text>
<Text className='tip-text'>{isAddingCard ? '加卡成功后,新卡和原来的卡可以执行相同功能' : isPendingBinding ? '点击完成宝贝关联,设备会出现在该宝贝下面' : '点击发送绑卡指令,然后在 10 分钟内去设备上贴自己的卡完成确认'}</Text>
</View>
</View>
{systemBanner}

View File

@@ -37,7 +37,7 @@ function formatParticipantLabel(message: ChatMessage): string {
return name === '我' ? '我' : name || '家长'
}
if (senderType === 'child') {
return name || `儿童ID ${senderId}`
return name || `宝贝ID ${senderId}`
}
if (senderType === 'device') {
return name || `设备ID ${senderId}`

View File

@@ -74,7 +74,7 @@ export default function Chat() {
<View className='guest-card'>
<Text className='guest-card-icon'>💬</Text>
<Text className='guest-card-title'></Text>
<Text className='guest-card-desc'> AI </Text>
<Text className='guest-card-desc'> AI </Text>
<View className='guest-card-btn' onClick={handleGuestLogin}>
<Text className='guest-card-btn-text'></Text>
</View>
@@ -85,7 +85,7 @@ export default function Chat() {
</View>
) : conversations.length === 0 ? (
<View className='empty-card'>
<Text className='empty-title'></Text>
<Text className='empty-title'></Text>
</View>
) : (
<ScrollView className='conversation-list' scrollY>

View File

@@ -93,8 +93,8 @@ function getAlarmChildLabel(alarm: DeviceAlarmItem | null, fallbackChildName?: s
const fallbackName = String(fallbackChildName || '').trim()
if (fallbackName) return fallbackName
if (alarm?.child_id) return `儿童 ${alarm.child_id}`
return '未关联儿童'
if (alarm?.child_id) return `宝贝 ${alarm.child_id}`
return '未关联宝贝'
}
function formatAlarmCoordinates(alarm: DeviceAlarmItem | null): string {
@@ -287,7 +287,7 @@ export default function Device() {
<View className='guest-device-icon'>
<Image className='robot-img' src={require('../../assets/tab-icons/robot.png')} mode='aspectFit' />
</View>
<Text className='guest-title'></Text>
<Text className='guest-title'></Text>
<Text className='guest-subtitle'></Text>
</View>
@@ -305,18 +305,18 @@ export default function Device() {
<View className='guest-feature-card'>
<Image className='guest-feature-icon' src={require('../../assets/tab-icons/chat-icon.png')} mode='aspectFit' />
<Text className='guest-feature-title'></Text>
<Text className='guest-feature-desc'></Text>
<Text className='guest-feature-desc'></Text>
</View>
<View className='guest-feature-card'>
<Image className='guest-feature-icon' src={require('../../assets/tab-icons/manage-icon.png')} mode='aspectFit' />
<Text className='guest-feature-title'></Text>
<Text className='guest-feature-desc'></Text>
<Text className='guest-feature-desc'></Text>
</View>
</View>
<View className='guest-action-card'>
<Text className='guest-action-title'></Text>
<Text className='guest-action-desc'></Text>
<Text className='guest-action-desc'></Text>
<View className='guest-primary-btn' onClick={() => goLoginWithRedirect('/pages/device/index')}>
<Text className='guest-primary-btn-text'></Text>
</View>
@@ -338,14 +338,14 @@ export default function Device() {
<View className='empty-robot'>
<Image className='robot-img' src={require('../../assets/tab-icons/robot.png')} mode='aspectFit' />
</View>
<Text className='empty-title'>{hasPendingDevice ? '设备已绑定,待关联孩子' : '先绑定一台伴伴设备'}</Text>
<Text className='empty-title'>{hasPendingDevice ? '设备已绑定,待关联宝贝' : '先绑定一台伴伴设备'}</Text>
<Text className='empty-subtitle'>
{hasPendingDevice
? '这台设备还没有关联孩子,关联后即可查看定位、聊天和设备状态'
: '绑定时可以选择已有孩子,或直接新建孩子昵称'}
? '这台设备还没有关联宝贝,关联后即可查看定位、聊天和设备状态'
: '绑定时可以选择已有宝贝,或直接新建宝贝昵称'}
</Text>
<View className='empty-btn' onClick={() => Taro.navigateTo({ url: hasPendingDevice ? '/pages/bind/index?mode=assign' : '/pages/bind/index' })}>
<Text className='empty-btn-text'>{hasPendingDevice ? '关联孩子' : '添加设备'}</Text>
<Text className='empty-btn-text'>{hasPendingDevice ? '关联宝贝' : '添加设备'}</Text>
</View>
</View>
{systemBanner}
@@ -367,7 +367,7 @@ export default function Device() {
</View>
<View className='child-focus-info'>
<Text className='child-focus-name'>{child.child_name}</Text>
<Text className='child-focus-desc'></Text>
<Text className='child-focus-desc'></Text>
</View>
</View>
@@ -375,14 +375,14 @@ export default function Device() {
<View className='empty-robot'>
<Image className='robot-img' src={require('../../assets/tab-icons/robot.png')} mode='aspectFit' />
</View>
<Text className='empty-title'>{hasPendingDevice ? '有设备待关联' : '给当前孩子绑定一台设备'}</Text>
<Text className='empty-title'>{hasPendingDevice ? '有设备待关联' : '给当前宝贝绑定一台设备'}</Text>
<Text className='empty-subtitle'>
{hasPendingDevice
? `设备 ${pendingBinding?.device_id} 还没有关联孩子,可以关联到当前孩子`
: '绑定后,这个孩子的聊天记录、定位和设备状态都会显示在首页'}
? `设备 ${pendingBinding?.device_id} 还没有关联宝贝,可以关联到当前宝贝`
: '绑定后,这个宝贝的聊天记录、定位和设备状态都会显示在首页'}
</Text>
<View className='empty-btn' onClick={() => Taro.navigateTo({ url: hasPendingDevice ? '/pages/bind/index?mode=assign' : '/pages/bind/index' })}>
<Text className='empty-btn-text'>{hasPendingDevice ? '关联到当前孩子' : '去绑定设备'}</Text>
<Text className='empty-btn-text'>{hasPendingDevice ? '关联到当前宝贝' : '去绑定设备'}</Text>
</View>
</View>
{systemBanner}
@@ -550,7 +550,7 @@ export default function Device() {
<Text className='alarm-value'>{formatAlarmTime(latestAlarm.created_at)}</Text>
</View>
<View className='alarm-row'>
<Text className='alarm-label'></Text>
<Text className='alarm-label'></Text>
<Text className='alarm-value'>{latestAlarmChildName}</Text>
</View>
<View className='alarm-row'>
@@ -585,7 +585,7 @@ export default function Device() {
</View>
<View className='alarm-history-meta'>
<Text className='alarm-history-child'>
{getAlarmChildLabel(alarm, childName)}
{getAlarmChildLabel(alarm, childName)}
</Text>
<Text className='alarm-history-device'>{alarm.device_id}</Text>
</View>
@@ -621,7 +621,7 @@ export default function Device() {
{isDetailExpanded && (
<View className='detail-body'>
<View className='detail-row'>
<Text className='detail-label'></Text>
<Text className='detail-label'></Text>
<Text className='detail-value'>{childName}</Text>
</View>
<View className='detail-row'>

View File

@@ -66,7 +66,7 @@ function buildLocationDetailLines(
const lines: string[] = []
if (options?.includeIdentity) {
lines.push(`设备 ${point.device_id} · ${point.child_name || '未命名儿童'}`)
lines.push(`设备 ${point.device_id} · ${point.child_name || '未命名宝贝'}`)
}
lines.push(`上报时间 ${formatTime(point.device_time)}`)
@@ -177,10 +177,10 @@ export default function Location() {
setSelectedPoint(null)
setCoordinates(DEFAULT_COORDINATES)
setEmptyState({
title: '还没有当前孩子',
desc: '请先去设置与管理页创建或选择一个孩子,再查看定位。',
actionText: '去设置与管理',
actionUrl: '/pages/sleep/index',
title: '还没有当前宝贝',
desc: '请先绑定设备,并在绑定流程中创建或选择宝贝,再查看定位。',
actionText: '去绑定设备',
actionUrl: '/pages/bind/index',
})
return
}
@@ -192,8 +192,8 @@ export default function Location() {
setSelectedPoint(null)
setCoordinates(DEFAULT_COORDINATES)
setEmptyState({
title: '当前孩子还没有绑定设备',
desc: '先给当前孩子绑定一台设备,定位和轨迹页才会有内容。',
title: '当前宝贝还没有绑定设备',
desc: '先给当前宝贝绑定一台设备,定位和轨迹页才会有内容。',
actionText: '去绑定设备',
actionUrl: '/pages/bind/index',
})
@@ -397,7 +397,7 @@ export default function Location() {
<View className='location-empty-shell'>
<View className='location-empty-card'>
<Text className='location-empty-title'></Text>
<Text className='location-empty-desc'></Text>
<Text className='location-empty-desc'></Text>
<View
className='location-empty-action'
onClick={() => {
@@ -501,7 +501,7 @@ export default function Location() {
</View>
) : (
<View className='location-empty'>
<Text className='location-empty-title'>{locationNotice || '当前孩子暂无设备位置'}</Text>
<Text className='location-empty-title'>{locationNotice || '当前宝贝暂无设备位置'}</Text>
{locationNotice && <Text className='location-empty-desc'></Text>}
</View>
)
@@ -545,7 +545,7 @@ export default function Location() {
<View className='location-empty'>
<Text className='location-empty-title'></Text>
<Text className='location-empty-desc'>
{trajectoryMode === 'today' ? '当前孩子今天还没有新的历史点位。' : '当前孩子最近没有新的历史点位。'}
{trajectoryMode === 'today' ? '当前宝贝今天还没有新的历史点位。' : '当前宝贝最近没有新的历史点位。'}
</Text>
</View>
)}

View File

@@ -153,7 +153,7 @@ export default function Login() {
<View className='login-info-card'>
<Text className='login-info-title'></Text>
<Text className='login-info-desc'></Text>
<Text className='login-info-desc'></Text>
</View>
<View className='login-btn-wrapper'>

View File

@@ -121,11 +121,11 @@ export default function SleepSchedule() {
<View className='sleep-page'>
<View className='page-header'>
<Text className='page-title'></Text>
<Text className='page-subtitle'></Text>
<Text className='page-subtitle'></Text>
</View>
<View className='empty-card'>
<Text className='empty-title'></Text>
<Text className='empty-desc'></Text>
<Text className='empty-title'></Text>
<Text className='empty-desc'></Text>
</View>
{systemBanner}
</View>
@@ -153,7 +153,7 @@ export default function SleepSchedule() {
const currentRange = formatSleepRange(deviceStatus)
const timezoneText = deviceStatus?.timezone || 'Asia/Shanghai'
const childName = child.child_name || '当前孩子'
const childName = child.child_name || '当前宝贝'
return (
<View className='sleep-page'>

View File

@@ -628,6 +628,10 @@
max-height: 74vh;
}
.cards-modal-card {
max-height: 76vh;
}
.family-card {
max-height: 76vh;
}
@@ -677,6 +681,46 @@
overflow-y: auto;
}
.cards-modal-summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
padding: 18px 20px;
border-radius: 16px;
background: #F8FAFC;
margin-bottom: 18px;
}
.cards-modal-device {
min-width: 0;
font-size: 26px;
color: #1F2937;
font-weight: 600;
word-break: break-all;
}
.cards-modal-list {
max-height: 42vh;
overflow-y: auto;
}
.cards-modal-add {
height: 84px;
margin-top: 22px;
border-radius: 18px;
background: #FFF2E7;
display: flex;
align-items: center;
justify-content: center;
}
.cards-modal-add-text {
font-size: 28px;
color: #FF8C42;
font-weight: 700;
}
.device-switch-item {
padding: 24px;
border-radius: 18px;
@@ -790,6 +834,16 @@
flex-shrink: 0;
}
.device-switch-delete {
padding: 6px 14px;
border-radius: 999px;
background: #FFFFFF;
border: 2px solid #E5484D;
font-size: 22px;
color: #E5484D;
flex-shrink: 0;
}
.device-switch-name {
font-size: 26px;
color: #666666;

View File

@@ -90,11 +90,11 @@ export default function Sleep() {
const [familyInviteExpiresAt, setFamilyInviteExpiresAt] = useState('')
const [showModal, setShowModal] = useState(false)
const [showChildModal, setShowChildModal] = useState(false)
const [showDeleteChildModal, setShowDeleteChildModal] = useState(false)
const [showRoleModal, setShowRoleModal] = useState(false)
const [showFamilyModal, setShowFamilyModal] = useState(false)
const [showFamilyNameModal, setShowFamilyNameModal] = useState(false)
const [showAssignDeviceModal, setShowAssignDeviceModal] = useState(false)
const [showCardsModal, setShowCardsModal] = useState(false)
const [showCardNameModal, setShowCardNameModal] = useState(false)
const [childName, setChildName] = useState('')
const [editingChildId, setEditingChildId] = useState<number | null>(null)
@@ -126,7 +126,7 @@ export default function Sleep() {
}, [])
useShareAppMessage(() => {
const targetName = currentChild?.child_name || binding?.device_id || '孩子'
const targetName = currentChild?.child_name || binding?.device_id || '宝贝'
return {
title: `邀请你加入${targetName}的家庭共享`,
path: familyInvitePath || '/pages/device/index',
@@ -567,7 +567,7 @@ export default function Sleep() {
void loadCurrentDeviceCards(nextBinding?.device_id)
setShowChildModal(false)
Taro.showToast({
title: '已切换当前孩子',
title: '已切换当前宝贝',
icon: 'success',
})
}
@@ -621,16 +621,50 @@ export default function Sleep() {
Taro.navigateTo({ url: '/pages/bind/index?mode=assign' })
}
const handleDeleteUnassignedDevice = (targetBinding: BindingListItem) => {
if (!targetBinding?.device_id) return
Taro.showModal({
title: '删除设备',
content: `确定要从当前账号删除设备 ${targetBinding.device_id} 吗?删除后需要重新扫码绑定。`,
confirmText: '删除',
confirmColor: '#E5484D',
success: async (result) => {
if (!result.confirm) return
try {
await unbindDevice(targetBinding.device_id)
if (binding?.device_id === targetBinding.device_id) {
clearSelectedBindingDeviceId()
}
Taro.showToast({ title: '已删除设备', icon: 'success' })
await loadData()
} catch (error: any) {
Taro.showToast({
title: error?.message || '删除设备失败,请重试',
icon: 'none',
})
}
},
})
}
const handleOpenAddCard = () => {
if (!binding?.device_id) return
setShowCardsModal(false)
Taro.navigateTo({ url: `/pages/bind/index?mode=card&device_id=${encodeURIComponent(binding.device_id)}` })
}
const handleSubmitModal = async () => {
const normalizedName = childName.trim()
if (!normalizedName) {
Taro.showToast({ title: '请输入儿童昵称', icon: 'none' })
Taro.showToast({ title: '请输入宝贝昵称', icon: 'none' })
return
}
try {
if (!editingChildId) {
Taro.showToast({ title: '当前没有可修改的孩子', icon: 'none' })
Taro.showToast({ title: '当前没有可修改的宝贝', icon: 'none' })
return
}
@@ -651,11 +685,11 @@ export default function Sleep() {
const childBinding = bindings.find((item) => item.child_id === child.child_id) || null
const isCurrentChild = currentChild?.child_id === child.child_id
const content = childBinding?.device_id
? '删除后,' + child.child_name + ' 将不再显示,设备 ' + childBinding.device_id + ' 会保留但不再分配给该孩子。确定删除吗?'
? '删除后,' + child.child_name + ' 将不再显示,设备 ' + childBinding.device_id + ' 会保留但不再分配给该宝贝。确定删除吗?'
: '删除后,' + child.child_name + ' 将不再显示。确定删除吗?'
Taro.showModal({
title: '删除孩子',
title: '删除宝贝',
content,
confirmText: '删除',
confirmColor: '#E5484D',
@@ -670,7 +704,7 @@ export default function Sleep() {
if (isCurrentChild && childBinding?.device_id && binding?.device_id === childBinding.device_id) {
clearSelectedBindingDeviceId()
}
setShowDeleteChildModal(false)
setShowChildModal(false)
Taro.showToast({ title: '已删除', icon: 'success' })
await loadData()
} catch (error: any) {
@@ -885,16 +919,11 @@ export default function Sleep() {
const handleMenuClick = (item: MenuItem) => {
if (item.disabled) return
if (item.name === '当前孩子') {
if (item.name === '当前宝贝') {
setShowChildModal(true)
return
}
if (item.name === '删除孩子') {
setShowDeleteChildModal(true)
return
}
if (item.name === '关联现有设备') {
setShowAssignDeviceModal(true)
return
@@ -905,9 +934,8 @@ export default function Sleep() {
return
}
if (item.name === '添加设备卡') {
if (!binding?.device_id) return
Taro.navigateTo({ url: `/pages/bind/index?mode=card&device_id=${encodeURIComponent(binding.device_id)}` })
if (item.name === '我的卡片') {
setShowCardsModal(true)
return
}
@@ -951,8 +979,8 @@ export default function Sleep() {
<View className='guest-manage-card'>
<Text className='guest-manage-icon'></Text>
<Text className='guest-manage-title'></Text>
<Text className='guest-manage-desc'></Text>
<Text className='guest-manage-title'></Text>
<Text className='guest-manage-desc'></Text>
<View
className='guest-manage-btn'
onClick={() => {
@@ -966,8 +994,8 @@ export default function Sleep() {
<View className='guest-manage-list'>
<View className='guest-manage-item'>
<Text className='guest-manage-item-title'></Text>
<Text className='guest-manage-item-desc'></Text>
<Text className='guest-manage-item-title'></Text>
<Text className='guest-manage-item-desc'></Text>
</View>
<View className='guest-manage-item'>
<Text className='guest-manage-item-title'></Text>
@@ -1044,22 +1072,19 @@ export default function Sleep() {
: firmwareStatus?.update_available
? '发现可更新版本'
: '当前设备版本状态'
const cardValue = !binding?.device_id
? '未绑定'
: isLoadingCards
? '查询中'
: `${currentCardGroup?.card_count || 0}`
const menuItems: MenuItem[] = [
{
icon: require('../../assets/tab-icons/orange-robot.png'),
iconBgClass: 'orange',
name: '当前孩子',
name: '当前宝贝',
value: currentChildName,
arrow: true,
},
{
icon: require('../../assets/tab-icons/broken-rings.png'),
iconBgClass: 'red',
name: '删除孩子',
value: children.length > 0 ? `${children.length} 个可选` : '未添加',
arrow: true,
disabled: children.length === 0,
},
...(unassignedBindings.length > 0
? [{
icon: require('../../assets/tab-icons/rings.png'),
@@ -1079,8 +1104,8 @@ export default function Sleep() {
{
icon: require('../../assets/tab-icons/rings.png'),
iconBgClass: 'green',
name: '添加设备卡',
value: binding?.device_id ? '给当前设备加卡' : '未绑定',
name: '我的卡片',
value: cardValue,
arrow: true,
disabled: !binding,
},
@@ -1155,11 +1180,11 @@ export default function Sleep() {
<View className='summary-card'>
<View className='summary-row'>
<Text className='summary-label'></Text>
<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-label'></Text>
<Text className='summary-value'>{currentChildName}</Text>
</View>
<View className='summary-row'>
@@ -1197,50 +1222,6 @@ export default function Sleep() {
</View>
</View>
<View className={`bound-cards-card ${binding?.device_id ? '' : 'disabled'}`}>
<View className='bound-cards-header'>
<View className='bound-cards-heading'>
<Text className='bound-cards-title'></Text>
<Text className='bound-cards-subtitle'>{binding?.device_id || '当前未绑定设备'}</Text>
</View>
<Text className='bound-cards-count'>
{isLoadingCards ? '查询中' : `${currentCardGroup?.card_count || 0}`}
</Text>
</View>
{!binding?.device_id ? (
<View className='bound-cards-empty'>
<Text className='bound-cards-empty-text'> UUID</Text>
</View>
) : isLoadingCards ? (
<View className='bound-cards-empty'>
<Text className='bound-cards-empty-text'>...</Text>
</View>
) : currentCardGroup?.cards.length ? (
<View className='bound-card-list'>
{currentCardGroup.cards.map((card) => (
<View className='bound-card-item' key={`${currentCardGroup.device_id}-${card.card_id}`}>
<View className='bound-card-main'>
<Text className={`bound-card-name ${card.card_name ? '' : 'empty'}`}>
{card.card_name || '未命名卡片'}
</Text>
<Text className='bound-card-uuid'>{card.card_uuid}</Text>
</View>
{canEditCardName && (
<Text className='bound-card-edit' onClick={() => handleOpenCardNameModal(card)}>
</Text>
)}
</View>
))}
</View>
) : (
<View className='bound-cards-empty'>
<Text className='bound-cards-empty-text'></Text>
</View>
)}
</View>
<View className='menu-card'>
{menuItems.map((item, index) => (
<View key={index}>
@@ -1279,9 +1260,9 @@ export default function Sleep() {
event.stopPropagation()
}}
>
<Text className='modal-title'></Text>
<Text className='modal-title'></Text>
{children.length === 0 ? (
<Text className='device-switch-empty'></Text>
<Text className='device-switch-empty'></Text>
) : (
<View className='device-switch-list'>
{children.map((item) => {
@@ -1308,6 +1289,15 @@ export default function Sleep() {
</Text>
)}
<Text
className='device-switch-delete'
onClick={(event) => {
event.stopPropagation()
handleDeleteChild(item)
}}
>
</Text>
</View>
</View>
<Text className='device-switch-name'>{itemBinding?.device_id || '未绑定设备'}</Text>
@@ -1325,50 +1315,6 @@ export default function Sleep() {
</View>
)}
{showDeleteChildModal && (
<View className='modal-mask' onClick={() => setShowDeleteChildModal(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 delete-child-item'
onClick={() => handleDeleteChild(item)}
>
<View className='device-switch-head'>
<Text className='device-switch-id'>{item.child_name}</Text>
<View className='device-switch-tags'>
{isActive && <Text className='device-switch-tag'></Text>}
<Text className='device-switch-tag danger'></Text>
</View>
</View>
<Text className='device-switch-name'>{itemBinding?.device_id || '未绑定设备'}</Text>
</View>
)
})}
</View>
)}
<View className='modal-actions'>
<Text className='modal-action cancel' onClick={() => setShowDeleteChildModal(false)}>
</Text>
</View>
</View>
</View>
)}
{showAssignDeviceModal && (
<View className='modal-mask' onClick={() => setShowAssignDeviceModal(false)}>
<View
@@ -1390,9 +1336,20 @@ export default function Sleep() {
>
<View className='device-switch-head'>
<Text className='device-switch-id'>{item.device_id}</Text>
<View className='device-switch-tags'>
<Text className='device-switch-tag'></Text>
<Text
className='device-switch-delete'
onClick={(event) => {
event.stopPropagation()
handleDeleteUnassignedDevice(item)
}}
>
</Text>
</View>
<Text className='device-switch-name'></Text>
</View>
<Text className='device-switch-name'></Text>
</View>
))}
</View>
@@ -1406,15 +1363,78 @@ export default function Sleep() {
</View>
)}
{showCardsModal && (
<View className='modal-mask' onClick={() => setShowCardsModal(false)}>
<View
className='modal-card cards-modal-card'
onClick={(event) => {
event.stopPropagation()
}}
>
<Text className='modal-title'></Text>
<View className='cards-modal-summary'>
<Text className='cards-modal-device'>{binding?.device_id || '当前未绑定设备'}</Text>
<Text className='bound-cards-count'>
{isLoadingCards ? '查询中' : `${currentCardGroup?.card_count || 0}`}
</Text>
</View>
{!binding?.device_id ? (
<View className='bound-cards-empty'>
<Text className='bound-cards-empty-text'> UUID</Text>
</View>
) : isLoadingCards ? (
<View className='bound-cards-empty'>
<Text className='bound-cards-empty-text'>...</Text>
</View>
) : currentCardGroup?.cards.length ? (
<View className='bound-card-list cards-modal-list'>
{currentCardGroup.cards.map((card) => (
<View className='bound-card-item' key={`${currentCardGroup.device_id}-${card.card_id}`}>
<View className='bound-card-main'>
<Text className={`bound-card-name ${card.card_name ? '' : 'empty'}`}>
{card.card_name || '未命名卡片'}
</Text>
<Text className='bound-card-uuid'>{card.card_uuid}</Text>
</View>
{canEditCardName && (
<Text className='bound-card-edit' onClick={() => handleOpenCardNameModal(card)}>
</Text>
)}
</View>
))}
</View>
) : (
<View className='bound-cards-empty'>
<Text className='bound-cards-empty-text'></Text>
</View>
)}
{binding?.device_id && (
<View className='cards-modal-add' onClick={handleOpenAddCard}>
<Text className='cards-modal-add-text'></Text>
</View>
)}
<View className='modal-actions'>
<Text className='modal-action cancel' onClick={() => setShowCardsModal(false)}>
</Text>
</View>
</View>
</View>
)}
{showModal && (
<View className='modal-mask'>
<View className='modal-card'>
<Text className='modal-title'></Text>
<Text className='modal-title'></Text>
<View className='modal-input-wrap'>
<Input
className='modal-input'
value={childName}
placeholder='请输入儿童昵称'
placeholder='请输入宝贝昵称'
focus
onInput={(event) => setChildName(event.detail.value)}
/>

View File

@@ -178,13 +178,13 @@ interface BindingContext {
}
const PEER_META: Record<PeerKind, { icon: string; label: string; description: string }> = {
child: { icon: '🧒', label: '儿童', description: '孩子正在交流的儿童玩伴' },
parent: { icon: '👨', label: '家长', description: '家长通过微信小程序和孩子沟通' },
ai: { icon: '🤖', label: 'AI', description: '孩子与 AI 的历史交流' },
child: { icon: '🧒', label: '宝贝', description: '宝贝正在交流的玩伴' },
parent: { icon: '👨', label: '家长', description: '家长通过微信小程序和宝贝沟通' },
ai: { icon: '🤖', label: 'AI', description: '宝贝与 AI 的历史交流' },
}
const AI_ROLE_META: Record<string, { name: string; icon: string; description: string }> = {
assistant: { name: 'AI', icon: '🤖', description: '孩子与 AI 的历史交流' },
assistant: { name: 'AI', icon: '🤖', description: '宝贝与 AI 的历史交流' },
}
type AiMeta = { name: string; icon: string; description: string }
@@ -260,14 +260,14 @@ function getAiMeta(roleKey?: string, roleName?: string | null, roleDescription?:
return {
name: normalizedRoleName,
icon: '🤖',
description: normalizedRoleDescription || `孩子${normalizedRoleName} 的历史交流`,
description: normalizedRoleDescription || `宝贝${normalizedRoleName} 的历史交流`,
}
}
if (roleKey && AI_ROLE_META[roleKey]) return AI_ROLE_META[roleKey]
return {
name: roleKey || 'AI',
icon: '🤖',
description: `孩子${roleKey || 'AI'} 的历史交流`,
description: `宝贝${roleKey || 'AI'} 的历史交流`,
}
}
@@ -288,7 +288,7 @@ async function getRoleMetaMap(): Promise<Record<string, AiMeta>> {
function getParticipantDisplayName(type: string, id?: string, name?: string | null): string {
if (name && name.trim()) return name.trim()
if (type === 'child') return id ? `儿童 ${id}` : '儿童'
if (type === 'child') return id ? `宝贝 ${id}` : '宝贝'
if (type === 'parent') return id ? `家长 ${id}` : '家长'
return id || 'AI'
}
@@ -400,8 +400,8 @@ function toImConversation(item: ChildConversationItem, context: BindingContext):
typeLabel: isParentConversation ? (canSend ? '家长沟通' : '留言记录') : meta.label,
description: isParentConversation
? canSend
? '家长通过微信小程序和孩子直接沟通'
: '其他家长和孩子的留言记录'
? '家长通过微信小程序和宝贝直接沟通'
: '其他家长和宝贝的留言记录'
: meta.description,
lastMessage: item.last_message_preview || (isParentConversation ? (canSend ? '还没有消息,点进去发送第一条' : '暂无留言记录') : '暂无消息'),
time: formatListTime(sortAt),
@@ -457,7 +457,7 @@ function createSyntheticParentConversation(context: BindingContext): ChatConvers
name: '家长沟通',
avatar: PEER_META.parent.icon,
typeLabel: '家长沟通',
description: '家长通过微信小程序和孩子直接沟通',
description: '家长通过微信小程序和宝贝直接沟通',
lastMessage: '还没有留言,点进去发送第一条',
time: '',
sortAt: '',
@@ -533,8 +533,8 @@ export async function getMessages(
senderId: item.is_user ? context.deviceId : item.role_key,
receiverType: item.is_user ? 'ai' : 'device',
receiverId: item.is_user ? item.role_key : context.deviceId,
senderName: item.is_user ? context.childName || '孩子设备' : meta.name,
receiverName: item.is_user ? meta.name : context.childName || '孩子设备',
senderName: item.is_user ? context.childName || '宝贝设备' : meta.name,
receiverName: item.is_user ? meta.name : context.childName || '宝贝设备',
channelLabel: 'AI',
}
})
@@ -569,13 +569,13 @@ export async function getMessages(
item.sender_type === 'parent' && item.sender_id === String(context.currentUserId)
? '我'
: item.sender_type === 'child' && item.sender_id === String(context.childId || '')
? context.childName || item.sender_name_snapshot || '孩子'
? context.childName || item.sender_name_snapshot || '宝贝'
: getParticipantDisplayName(item.sender_type, item.sender_id, item.sender_name_snapshot),
receiverName:
item.receiver_type === 'parent' && item.receiver_id === String(context.currentUserId)
? '我'
: item.receiver_type === 'child' && item.receiver_id === String(context.childId || '')
? context.childName || item.receiver_name_snapshot || '孩子'
? context.childName || item.receiver_name_snapshot || '宝贝'
: getParticipantDisplayName(item.receiver_type, item.receiver_id, item.receiver_name_snapshot),
channelLabel:
isParentConversation && (item.sender_type === 'parent' || item.receiver_type === 'parent')
@@ -644,7 +644,7 @@ export async function getCharacter(options: {
return {
id: conversationId,
name: normalizedName,
description: canSendParentMessage ? '微信小程序 <-> 儿童设备' : '其他家长和孩子的留言记录',
description: canSendParentMessage ? '微信小程序 <-> 宝贝设备' : '其他家长和宝贝的留言记录',
icon: PEER_META.parent.icon,
peerKind: 'parent',
source,
@@ -663,7 +663,7 @@ export async function getCharacter(options: {
return {
id: conversationId,
name: getPeerDisplayName(peerKind, peerId, name),
description: peerKind === 'child' ? '儿童设备间的聊天记录' : meta.description,
description: peerKind === 'child' ? '宝贝设备间的聊天记录' : meta.description,
icon: meta.icon,
peerKind,
source,

View File

@@ -53,8 +53,8 @@ function getAlarmChildLabel(alarm: DeviceAlarmItem, fallbackChildName?: string |
const fallbackName = String(fallbackChildName || '').trim()
if (fallbackName) return fallbackName
if (alarm.child_id) return `儿童 ${alarm.child_id}`
return '当前孩子'
if (alarm.child_id) return `宝贝 ${alarm.child_id}`
return '当前宝贝'
}
function getAlarmTypeLabel(sourceMsgId?: string | null): string {