diff --git a/banban-mini/src/pages/bind/index.tsx b/banban-mini/src/pages/bind/index.tsx index 0d787be..fface75 100644 --- a/banban-mini/src/pages/bind/index.tsx +++ b/banban-mini/src/pages/bind/index.tsx @@ -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() { )} - {isPendingBinding ? '新建宝贝昵称' : '新建宝贝昵称(可选)'} + {isPendingBinding ? '新建宝贝姓名' : '新建宝贝姓名(可选)'} 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) } diff --git a/banban-mini/src/pages/device/index.scss b/banban-mini/src/pages/device/index.scss index f392573..6462320 100644 --- a/banban-mini/src/pages/device/index.scss +++ b/banban-mini/src/pages/device/index.scss @@ -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; diff --git a/banban-mini/src/pages/device/index.tsx b/banban-mini/src/pages/device/index.tsx index 6ebea12..aba9c7c 100644 --- a/banban-mini/src/pages/device/index.tsx +++ b/banban-mini/src/pages/device/index.tsx @@ -342,7 +342,7 @@ export default function Device() { {hasPendingDevice ? '这台设备还没有关联宝贝,关联后即可查看定位、聊天和设备状态' - : '绑定时可以选择已有宝贝,或直接新建宝贝昵称'} + : '绑定时可以选择已有宝贝,或直接新建宝贝姓名'} Taro.navigateTo({ url: hasPendingDevice ? '/pages/bind/index?mode=assign' : '/pages/bind/index' })}> {hasPendingDevice ? '关联宝贝' : '添加设备'} @@ -620,30 +620,34 @@ export default function Device() { {isDetailExpanded && ( + + 当前坐标 + {coordinateLabel} + + + 位置上报时间 + {formatTime(deviceStatus?.location_updated_at)} + + + 服务端时间 + {formatTime(deviceStatus?.server_time)} + + + 设备状态时间 + {formatTime(deviceStatus?.settings_updated_at)} + + + 设备 ID + {binding.device_id} + 当前宝贝 {childName} - - 当前设备 - {binding.device_id} - 绑定时间 {formatTime(binding.bound_at)} - - 当前坐标 - {coordinateLabel} - - - 状态更新时间 - {formatTime(deviceStatus?.settings_updated_at)} - - - 定位更新时间 - {formatTime(deviceStatus?.location_updated_at)} - )} diff --git a/banban-mini/src/pages/sleep/index.scss b/banban-mini/src/pages/sleep/index.scss index 55554d7..2e7309a 100644 --- a/banban-mini/src/pages/sleep/index.scss +++ b/banban-mini/src/pages/sleep/index.scss @@ -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; } diff --git a/banban-mini/src/pages/sleep/index.tsx b/banban-mini/src/pages/sleep/index.tsx index 53aca79..55955db 100644 --- a/banban-mini/src/pages/sleep/index.tsx +++ b/banban-mini/src/pages/sleep/index.tsx @@ -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 && ( - 修改宝贝昵称 + 修改宝贝姓名 setChildName(event.detail.value)} + maxlength={5} + onInput={(event) => setChildName(sanitizeChildNameInput(event.detail.value))} /> @@ -1615,9 +1616,6 @@ export default function Sleep() { 邀请有效期至 {familyInviteExpiresAt ? familyInviteExpiresAt.replace('T', ' ').slice(0, 16) : '24 小时内'} - - 复制邀请路径 - ) : ( ): string { return Object.entries(params) @@ -10,6 +13,21 @@ function buildQuery(params: Record): .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 { + const childName = normalizeChildNameInput(data.child_name) + const childNameError = validateChildNameInput(childName) + if (childNameError) throw new Error(childNameError) + return request('/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 { + 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(`/banban/children/${childId}`, { method: 'PATCH', - data, + data: nextData, }) }