fix: 优化小程序家庭邀请和设备详情

This commit is contained in:
stu2not
2026-08-19 18:41:27 +08:00
parent e9f89841a0
commit 806097751e
6 changed files with 118 additions and 55 deletions
+15 -6
View File
@@ -14,10 +14,13 @@ import {
} from '@/services/binding'
import {
Child,
CHILD_NAME_RULE_MESSAGE,
createChild,
getChildren,
sanitizeChildNameInput,
resolveChildSelection,
setSelectedChildId as setStoredSelectedChildId,
validateChildNameInput,
} from '@/services/child'
import { useSystemBanner } from '@/components/system-banner/use-system-banner'
@@ -444,6 +447,10 @@ export default function Bind() {
const childName = newChildName.trim()
if (!childName) return null
const childNameError = validateChildNameInput(childName)
if (childNameError) {
throw new Error(childNameError)
}
const child = await createChild({ child_name: childName })
setChildren((currentChildren) => [child, ...currentChildren])
@@ -500,7 +507,7 @@ export default function Bind() {
if (isPendingBinding) {
if (!childId) {
throw new Error('请先选择宝贝或填写新的宝贝昵称')
throw new Error('请先选择宝贝或填写新的宝贝姓名')
}
await setBindingChild(pendingDeviceId!, { child_id: childId })
setSelectedBindingDeviceId(pendingDeviceId)
@@ -658,18 +665,20 @@ 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 ? '不选已有宝贝时,可填写2-5位简体中文姓名(暂不支持数字和英文)' : CHILD_NAME_RULE_MESSAGE
: children.length > 0 ? '不选已有宝贝时,可填写2-5位简体中文姓名(暂不支持数字和英文)' : '可不填,稍后再关联宝贝'
}
maxlength={5}
onInput={(event) => {
setNewChildName(event.detail.value)
if (event.detail.value) {
const nextChildName = sanitizeChildNameInput(event.detail.value)
setNewChildName(nextChildName)
if (nextChildName) {
setSelectedChildId(null)
setStoredSelectedChildId(null)
}
+30 -2
View File
@@ -672,8 +672,9 @@
.detail-row {
display: flex;
align-items: center;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
padding: 20px 0;
border-bottom: 1px solid #F3F3F3;
@@ -682,14 +683,41 @@
}
}
.detail-row-highlight {
display: block;
padding-top: 22px;
padding-bottom: 24px;
.detail-label {
display: block;
}
.detail-value {
display: block;
max-width: 100%;
margin-top: 10px;
font-size: 30px;
font-weight: 700;
line-height: 1.45;
text-align: left;
color: #FF8C42;
}
}
.detail-label {
flex-shrink: 0;
width: 180px;
font-size: 28px;
line-height: 1.45;
color: #666666;
}
.detail-value {
max-width: 60%;
flex: 1;
min-width: 0;
max-width: none;
font-size: 28px;
line-height: 1.45;
text-align: right;
word-break: break-all;
color: #1A1A1A;
+21 -17
View File
@@ -342,7 +342,7 @@ export default function Device() {
<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>
@@ -620,30 +620,34 @@ export default function Device() {
{isDetailExpanded && (
<View className='detail-body'>
<View className='detail-row detail-row-highlight'>
<Text className='detail-label'>当前坐标</Text>
<Text className='detail-value'>{coordinateLabel}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>位置上报时间</Text>
<Text className='detail-value'>{formatTime(deviceStatus?.location_updated_at)}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>服务端时间</Text>
<Text className='detail-value'>{formatTime(deviceStatus?.server_time)}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>设备状态时间</Text>
<Text className='detail-value'>{formatTime(deviceStatus?.settings_updated_at)}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>设备 ID</Text>
<Text className='detail-value'>{binding.device_id}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>当前宝贝</Text>
<Text className='detail-value'>{childName}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>当前设备</Text>
<Text className='detail-value'>{binding.device_id}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>绑定时间</Text>
<Text className='detail-value'>{formatTime(binding.bound_at)}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>当前坐标</Text>
<Text className='detail-value'>{coordinateLabel}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>状态更新时间</Text>
<Text className='detail-value'>{formatTime(deviceStatus?.settings_updated_at)}</Text>
</View>
<View className='detail-row'>
<Text className='detail-label'>定位更新时间</Text>
<Text className='detail-value'>{formatTime(deviceStatus?.location_updated_at)}</Text>
</View>
</View>
)}
</View>
-9
View File
@@ -1043,15 +1043,6 @@
color: #94A3B8;
}
.family-copy-action {
display: block;
margin-top: 14px;
font-size: 26px;
color: #FF8C42;
font-weight: 600;
text-align: center;
}
.family-danger-action {
background: #FFF1F2;
}
+17 -19
View File
@@ -15,7 +15,16 @@ import {
updateBindingCardName,
unbindDevice,
} from '@/services/binding'
import { Child, clearSelectedChildId, deleteChild, setSelectedChildId, updateChild } from '@/services/child'
import {
Child,
CHILD_NAME_RULE_MESSAGE,
clearSelectedChildId,
deleteChild,
sanitizeChildNameInput,
setSelectedChildId,
updateChild,
validateChildNameInput,
} from '@/services/child'
import {
createFamilyInvitation,
FamilyMember,
@@ -427,16 +436,6 @@ export default function Sleep() {
}
}
const handleCopyFamilyInvite = () => {
if (!familyInvitePath) return
Taro.setClipboardData({
data: familyInvitePath,
success: () => {
Taro.showToast({ title: '邀请路径已复制', icon: 'success' })
},
})
}
const handleRemoveFamilyMember = (member: FamilyMember) => {
if (!binding?.device_id || member.is_owner || isUpdatingFamily) return
@@ -657,8 +656,9 @@ export default function Sleep() {
const handleSubmitModal = async () => {
const normalizedName = childName.trim()
if (!normalizedName) {
Taro.showToast({ title: '请输入宝贝昵称', icon: 'none' })
const childNameError = validateChildNameInput(normalizedName)
if (childNameError) {
Taro.showToast({ title: childNameError, icon: 'none' })
return
}
@@ -1442,14 +1442,15 @@ export default function Sleep() {
{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={CHILD_NAME_RULE_MESSAGE}
focus
onInput={(event) => setChildName(event.detail.value)}
maxlength={5}
onInput={(event) => setChildName(sanitizeChildNameInput(event.detail.value))}
/>
</View>
<View className='modal-actions'>
@@ -1615,9 +1616,6 @@ export default function Sleep() {
<Text className='family-invite-tip'>
邀请有效期至 {familyInviteExpiresAt ? familyInviteExpiresAt.replace('T', ' ').slice(0, 16) : '24 小时内'}
</Text>
<Text className='family-copy-action' onClick={handleCopyFamilyInvite}>
复制邀请路径
</Text>
</>
) : (
<View
+35 -2
View File
@@ -2,6 +2,9 @@ import Taro from '@tarojs/taro'
import { request } from './api'
const SELECTED_CHILD_ID_KEY = 'selectedChildId'
const CHILD_NAME_PATTERN = /^[\u4e00-\u9fa5]{2,5}$/
export const CHILD_NAME_RULE_MESSAGE = '请输入2-5位简体中文姓名(暂不支持数字和英文)'
function buildQuery(params: Record<string, string | number | undefined | null>): string {
return Object.entries(params)
@@ -10,6 +13,21 @@ function buildQuery(params: Record<string, string | number | undefined | null>):
.join('&')
}
export function normalizeChildNameInput(value?: string | null): string {
return String(value || '').trim()
}
export function sanitizeChildNameInput(value?: string | null): string {
return normalizeChildNameInput(value).replace(/[^\u4e00-\u9fa5]/g, '').slice(0, 5)
}
export function validateChildNameInput(value?: string | null): string {
const childName = normalizeChildNameInput(value)
if (!childName) return '请输入宝贝姓名'
if (!CHILD_NAME_PATTERN.test(childName)) return CHILD_NAME_RULE_MESSAGE
return ''
}
export interface Child {
child_id: number
child_name: string
@@ -61,9 +79,16 @@ export async function createChild(data: {
child_gender?: number
child_birthday?: string
}): Promise<Child> {
const childName = normalizeChildNameInput(data.child_name)
const childNameError = validateChildNameInput(childName)
if (childNameError) throw new Error(childNameError)
return request<Child>('/banban/children', {
method: 'POST',
data,
data: {
...data,
child_name: childName,
},
})
}
@@ -83,9 +108,17 @@ export async function updateChild(
childId: number,
data: { child_name?: string; child_gender?: number; child_birthday?: string }
): Promise<Child> {
const nextData = { ...data }
if (nextData.child_name !== undefined) {
const childName = normalizeChildNameInput(nextData.child_name)
const childNameError = validateChildNameInput(childName)
if (childNameError) throw new Error(childNameError)
nextData.child_name = childName
}
return request<Child>(`/banban/children/${childId}`, {
method: 'PATCH',
data,
data: nextData,
})
}