diff --git a/src/api/careMember.ts b/src/api/careMember.ts new file mode 100644 index 0000000..c55df25 --- /dev/null +++ b/src/api/careMember.ts @@ -0,0 +1,46 @@ +import { request } from '@/http' + +// 关爱对象接口(apifox 2026-09-11 文档版) + +// 紧急联系人(文档 YlHomeCareMemberContactItemReq,注意关系字段名为 relationShip) +export interface CareContact { + name: string + phone: string + relationShip: string +} + +// 保存入参(文档 YlHomeCareMemberSaveReq:memberId 空=新增,有值=修改) +export interface CareMemberSaveParams { + memberId?: string + homeId: string + name: string // 最多 10 个字 + idNumber: string + address: string + contacts: CareContact[] // 最多 5 名 +} + +// 列表项(响应字段文档未给出,按保存入参推断,联调修正) +export interface CareMember { + memberId: string + homeId: string + name: string + idNumber: string + address: string + contacts: CareContact[] +} + +export function saveCareMember(params: CareMemberSaveParams): Promise { + return request.post('/yanglao/app/ylHomeCareMember/save', params as unknown as Record) +} + +export function getCareMemberList(homeId: string): Promise { + return request.post('/yanglao/app/ylHomeCareMember/getList', { homeId }) +} + +export function getCareMemberDetail(memberId: string): Promise { + return request.get(`/yanglao/app/ylHomeCareMember/get/${memberId}`) +} + +export function deleteCareMember(memberId: string): Promise { + return request.post('/yanglao/app/ylHomeCareMember/delete', { memberId }) +} diff --git a/src/api/family.ts b/src/api/family.ts deleted file mode 100644 index 8e540df..0000000 --- a/src/api/family.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { storage } from '@/utils/storage' -import { - FAMILY_DATA_STORAGE_KEY, - MAX_CARED_PER_FAMILY, - MOCK_ID_SEED -} from '@/constants/family' - -export interface Family { - id: string - name: string - memberCount: number - caredCount: number - caredLabel: string - address: string - deviceCount: number - role?: '管理员' | '群主' // 当前用户在家庭圈的角色,弹层显示用 -} - -export interface EmergencyContact { - id: string - name: string - phone: string - relation: string -} - -export interface CaredPerson { - id: string - familyId: string - name: string - idNumber: string - address: string - contacts: EmergencyContact[] -} - -export interface FamilyMember { - id: string - familyId: string - name: string - role: 'owner' | 'member' - phone: string - notify: boolean -} - -export interface FamilyDetailData { - family: Family - members: FamilyMember[] - cared: CaredPerson[] -} - -interface FamilyData { - families: Family[] - cared: CaredPerson[] - members: FamilyMember[] - nextId: number -} - -const SEED_DATA: FamilyData = { - families: [ - { id: '846443196293189', name: 'JollyPlace', memberCount: 6, caredCount: 1, caredLabel: 'Jay', address: '--', deviceCount: 24, role: '管理员' }, - { id: '735201884550361', name: '我的家庭圈', memberCount: 4, caredCount: 0, caredLabel: '--', address: '--', deviceCount: 1, role: '群主' }, - { id: '628934071266852', name: '康乐之家', memberCount: 1, caredCount: 0, caredLabel: '--', address: '--', deviceCount: 21, role: '群主' }, - { id: '519027468331740', name: '平安庭院', memberCount: 1, caredCount: 0, caredLabel: '--', address: '--', deviceCount: 0, role: '群主' } - ], - cared: [ - { - id: '900001', - familyId: '846443196293189', - name: 'Jay', - idNumber: '440301195603150000', - address: '深圳市南山区科技园', - contacts: [ - { id: '910001', name: '张女士', phone: '13800138000', relation: '配偶' }, - { id: '910002', name: '李先生', phone: '13900139000', relation: '儿子' } - ] - } - ], - members: [ - { id: '900001', familyId: '846443196293189', name: 'Jay', role: 'owner', phone: '13800138000', notify: true }, - { id: '900002', familyId: '846443196293189', name: '小美', role: 'member', phone: '13900139000', notify: false } - ], - nextId: MOCK_ID_SEED -} - -// 读全量数据;storage 为空时写入样例(首次启动初始化) -function loadData(): FamilyData { - const saved = storage.get(FAMILY_DATA_STORAGE_KEY) - if (saved) { - if (!saved.families || !Array.isArray(saved.families)) { - console.warn('familyData 存储结构异常,回退默认数据') - return resetData() - } - return saved - } - return resetData() -} - -function resetData(): FamilyData { - const data: FamilyData = JSON.parse(JSON.stringify(SEED_DATA)) - storage.set(FAMILY_DATA_STORAGE_KEY, data) - return data -} - -function saveData(data: FamilyData): void { - storage.set(FAMILY_DATA_STORAGE_KEY, data) -} - -// mock 异步语义:与真实 API 调用形式一致 -const delay = (value: T, ms = 100): Promise => - new Promise((resolve) => setTimeout(() => resolve(value), ms)) - -function nextIdOf(data: FamilyData): string { - const id = String(data.nextId) - data.nextId += 1 - return id -} - -export async function getFamilies(): Promise { - return delay(loadData().families) -} - -export async function getFamilyDetail(id: string): Promise { - const data = loadData() - const family = data.families.find((f) => f.id === id) - if (!family) return delay(null) - const members = data.members.filter((m) => m.familyId === id) - const cared = data.cared.filter((c) => c.familyId === id) - return delay({ family, members, cared }) -} - -export async function addFamily(name: string): Promise { - const trimmed = name.trim() - if (!trimmed) throw new Error('请输入家庭名') - const data = loadData() - const family: Family = { - id: nextIdOf(data), - name: trimmed, - memberCount: 1, - caredCount: 0, - caredLabel: '--', - address: '--', - deviceCount: 0, - role: '群主' - } - data.families = [...data.families, family] - saveData(data) - return delay(family) -} - -export async function joinFamily(code: string): Promise { - const trimmed = code.trim() - if (!trimmed) throw new Error('请输入家庭编号') - const data = loadData() - const family: Family = { - id: nextIdOf(data), - name: `加入的 ${trimmed.slice(-4)}`, - memberCount: 2, - caredCount: 0, - caredLabel: '--', - address: '--', - deviceCount: 0, - role: '群主' - } - data.families = [...data.families, family] - saveData(data) - return delay(family) -} - -export async function canAddCared(familyId: string): Promise { - const data = loadData() - const count = data.cared.filter((c) => c.familyId === familyId).length - return count < MAX_CARED_PER_FAMILY -} - -export async function addCared(input: Omit): Promise { - const data = loadData() - const count = data.cared.filter((c) => c.familyId === input.familyId).length - if (count >= MAX_CARED_PER_FAMILY) throw new Error('已达上限') - - const person: CaredPerson = { ...input, id: nextIdOf(data) } - data.cared = [...data.cared, person] - // 同步家庭卡上的关爱对象摘要 - const family = data.families.find((f) => f.id === input.familyId) - if (family) { - family.caredCount = count + 1 - family.caredLabel = person.name - } - saveData(data) - return delay(person) -} - -export async function getCaredByFamily(familyId: string): Promise { - return delay(loadData().cared.filter((c) => c.familyId === familyId)) -} diff --git a/src/api/home.ts b/src/api/home.ts index 4aa8ddf..cd39740 100644 --- a/src/api/home.ts +++ b/src/api/home.ts @@ -1,21 +1,31 @@ import { request } from '@/http' -// 骨架:本期仅锁定接口路径,字段类型按文档目录推断,联调时逐个修正(spec D7) +// 家庭圈接口(apifox 2026-09-11 文档版) +// 响应 Content 字段文档未给出(ResponseData 泛型占位),类型按命名推断,联调时逐个修正 + export interface HomeInfo { homeId: string homeName: string ownerUserId: string memberCount: number + // 以下为列表卡展示推断字段,联调确认 + careName?: string // 关爱对象名(caredLabel) + careCount?: number // 关爱对象数(上限校验用) + address?: string + deviceCount?: number + role?: string // 当前用户角色(群主/成员) } export interface HomeMember { userId: string nickName: string isOwner: boolean + mobile?: string } -export function addHome(params: Record): Promise { - return request.post('/yanglao/app/ylHome/add', params) +export function addHome(name: string): Promise { + // name 可空,空则后端默认「我的家庭圈」(文档 YlHomeAddReq) + return request.post('/yanglao/app/ylHome/add', { name }) } export function updateHome(params: Record): Promise { @@ -27,11 +37,11 @@ export function dissolveHome(params: Record): Promise { } export function getHomeList(): Promise { - return request.get('/yanglao/app/ylHome/getList') + return request.post('/yanglao/app/ylHome/getList') } export function getHomeMemberList(homeId: string): Promise { - return request.get('/yanglao/app/ylHome/getMemberList', { homeId }) + return request.post('/yanglao/app/ylHome/getMemberList', { homeId }) } export function transferOwner(params: Record): Promise { @@ -46,6 +56,7 @@ export function getHomeDetail(homeId: string): Promise { return request.get(`/yanglao/app/ylHome/get/${homeId}`) } -export function joinHome(params: Record): Promise { - return request.post('/yanglao/app/ylHome/join', params) +export function joinHome(code: string): Promise { + // code 为家庭编号(文档 YlHomeJoinReq) + return request.post('/yanglao/app/ylHome/join', { code }) } diff --git a/src/components/family-card/index.tsx b/src/components/family-card/index.tsx index ac407a3..c15c322 100644 --- a/src/components/family-card/index.tsx +++ b/src/components/family-card/index.tsx @@ -1,20 +1,20 @@ import { View, Text } from '@tarojs/components' -import type { Family } from '@/api/family' +import type { HomeInfo } from '@/api/home' import styles from './index.module.scss' interface Props { - family: Family + family: HomeInfo onClick?: (id: string) => void } export function FamilyCard({ family, onClick }: Props) { - const first = family.name.slice(0, 1) + const first = family.homeName.slice(0, 1) return ( onClick?.(family.id)} + onClick={() => onClick?.(family.homeId)} > {/* 右上淡绿圆装饰 */} @@ -24,21 +24,21 @@ export function FamilyCard({ family, onClick }: Props) { - {family.name} + {family.homeName} {family.role && {family.role}} - {family.memberCount} 位成员 · {family.deviceCount} 台设备 + {family.memberCount} 位成员 · {family.deviceCount ?? 0} 台设备 关爱对象 - {family.caredLabel} + {family.careName || '--'} 关爱地址 - {family.address} + {family.address || '--'} ) diff --git a/src/pages/device/index.tsx b/src/pages/device/index.tsx index a7fc6d6..97b193e 100644 --- a/src/pages/device/index.tsx +++ b/src/pages/device/index.tsx @@ -11,7 +11,7 @@ import { getDeviceList } from '@/api/device' import { DEVICE_ICON_MAP, type DeviceModel } from '@/constants/enums' import { ROUTES } from '@/constants/routes' import { navigateTo } from '@/utils/navigation' -import { getFamilies, type Family } from '@/api/family' +import { getHomeList, type HomeInfo } from '@/api/home' import styles from './index.module.scss' const FALLBACK_ICON = '⌂' @@ -24,10 +24,10 @@ export default function DeviceList() { const [loading, setLoading] = useState(true) const [loadError, setLoadError] = useState(false) - const [families, setFamilies] = useState([]) - const [currentFamily, setCurrentFamily] = useState(null) + const [families, setFamilies] = useState([]) + const [currentFamily, setCurrentFamily] = useState(null) const [familySheetOpen, setFamilySheetOpen] = useState(false) - const [pendingFamily, setPendingFamily] = useState(null) + const [pendingFamily, setPendingFamily] = useState(null) // 在线状态需每次进入页面都新鲜:useDidShow 覆盖 // 首次进入、从详情页 navigateBack 返回、tab 切换回三种场景 @@ -46,7 +46,7 @@ export default function DeviceList() { const loadFamilies = useCallback(async () => { try { - const list = await getFamilies() + const list = await getHomeList() setFamilies(list) // 若当前未选中,默认选第一条 setCurrentFamily((prev) => prev ?? list[0] ?? null) @@ -93,11 +93,11 @@ export default function DeviceList() { 设备应用家庭 - {currentFamily?.name ?? scene?.name ?? '未选择'} + {currentFamily?.homeName ?? scene?.name ?? '未选择'} 关爱对象 - {currentFamily?.caredLabel ?? '--'} + {currentFamily?.careName ?? '--'} 关爱地址 @@ -150,15 +150,15 @@ export default function DeviceList() { {families.map((family) => { - const active = pendingFamily?.id === family.id + const active = pendingFamily?.homeId === family.homeId return ( setPendingFamily(family)} > - {family.name} + {family.homeName} {family.role && ( {family.role} @@ -166,8 +166,8 @@ export default function DeviceList() { )} - 关爱对象 {family.caredLabel} - 设备数量 {family.deviceCount}台 + 关爱对象 {family.careName || '--'} + 设备数量 {family.deviceCount ?? 0}台 ) diff --git a/src/pages/family/contact/add.tsx b/src/pages/family/contact/add.tsx index a546e11..268d247 100644 --- a/src/pages/family/contact/add.tsx +++ b/src/pages/family/contact/add.tsx @@ -2,18 +2,19 @@ import { useState } from 'react' import { useRouter } from '@tarojs/taro' import { View, Text, Input } from '@tarojs/components' import { Navbar } from '@/components/navbar' -import { addCared, type EmergencyContact } from '@/api/family' +import { saveCareMember, type CareContact } from '@/api/careMember' import { MAX_EMERGENCY_CONTACTS, MAX_FAMILY_NAME_LEN } from '@/constants/family' import { navigateBack } from '@/utils/navigation' import { showToast } from '@/utils/toast' import { validateContactForm } from './validate' import styles from './add.module.scss' -const emptyContact = (): EmergencyContact => ({ - id: `tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, +let tmpSeq = 0 +const emptyContact = (): (CareContact & { tmpId: string }) => ({ + tmpId: `tmp-${Date.now()}-${(tmpSeq += 1)}`, name: '', phone: '', - relation: '' + relationShip: '' }) export default function ContactAdd() { @@ -23,15 +24,15 @@ export default function ContactAdd() { const [name, setName] = useState('') const [idNumber, setIdNumber] = useState('') const [address, setAddress] = useState('') - const [contacts, setContacts] = useState([]) + const [contacts, setContacts] = useState<(CareContact & { tmpId: string })[]>([]) const [submitting, setSubmitting] = useState(false) - const patchContact = (id: string, key: keyof EmergencyContact, value: string) => { - setContacts((prev) => prev.map((c) => (c.id === id ? { ...c, [key]: value } : c))) + const patchContact = (id: string, key: keyof CareContact, value: string) => { + setContacts((prev) => prev.map((c) => (c.tmpId === id ? { ...c, [key]: value } : c))) } const removeContact = (id: string) => { - setContacts((prev) => prev.filter((c) => c.id !== id)) + setContacts((prev) => prev.filter((c) => c.tmpId !== id)) } const submit = async () => { @@ -44,8 +45,8 @@ export default function ContactAdd() { setSubmitting(true) try { - await addCared({ - familyId, + await saveCareMember({ + homeId: familyId, name: name.trim(), idNumber: idNumber.trim(), address: address.trim(), @@ -107,8 +108,8 @@ export default function ContactAdd() { 最多可添加 {MAX_EMERGENCY_CONTACTS} 位紧急联系人,可不填 {contacts.map((c) => ( - - removeContact(c.id)}> + + removeContact(c.tmpId)}> × @@ -119,7 +120,7 @@ export default function ContactAdd() { className={styles.input} placeholder="联系人姓名" value={c.name} - onInput={(e) => patchContact(c.id, 'name', e.detail.value)} + onInput={(e) => patchContact(c.tmpId, 'name', e.detail.value)} /> @@ -129,7 +130,7 @@ export default function ContactAdd() { type="number" placeholder="联系人电话" value={c.phone} - onInput={(e) => patchContact(c.id, 'phone', e.detail.value)} + onInput={(e) => patchContact(c.tmpId, 'phone', e.detail.value)} /> @@ -137,8 +138,8 @@ export default function ContactAdd() { patchContact(c.id, 'relation', e.detail.value)} + value={c.relationShip} + onInput={(e) => patchContact(c.tmpId, 'relationShip', e.detail.value)} /> diff --git a/src/pages/family/contact/validate.ts b/src/pages/family/contact/validate.ts index 4603d35..e518ee8 100644 --- a/src/pages/family/contact/validate.ts +++ b/src/pages/family/contact/validate.ts @@ -1,14 +1,14 @@ -import type { EmergencyContact } from '@/api/family' +import type { CareContact } from '@/api/careMember' export interface ContactFormInput { name: string idNumber: string address: string - contacts: EmergencyContact[] + contacts: (CareContact & { tmpId: string })[] } export type ValidateResult = - | { ok: true; contacts: EmergencyContact[] } + | { ok: true; contacts: CareContact[] } | { ok: false; error: string } // 必填项齐全且联系人卡要么空白(忽略)要么填全,才允许提交 @@ -22,5 +22,7 @@ export function validateContactForm(input: ContactFormInput): ValidateResult { return { ok: false, error: '请完整填写联系人' } } - return { ok: true, contacts: filled } + // 提交前剥掉仅供 React key 用的 tmpId + const contacts = filled.map(({ tmpId: _tmpId, ...c }) => c) + return { ok: true, contacts } } diff --git a/src/pages/family/detail.tsx b/src/pages/family/detail.tsx index 1280b44..daf1761 100644 --- a/src/pages/family/detail.tsx +++ b/src/pages/family/detail.tsx @@ -6,11 +6,7 @@ import { Loading } from '@/components/loading' import { StateView } from '@/components/state-view' import { FamilyCard } from '@/components/family-card' import { ListRow, ListRowGroup } from '@/components/list-row' -import { - getFamilyDetail, - canAddCared, - type FamilyDetailData -} from '@/api/family' +import { getHomeDetail, getHomeMemberList, type HomeInfo, type HomeMember } from '@/api/home' import { MAX_CARED_PER_FAMILY } from '@/constants/family' import { ROUTES } from '@/constants/routes' import { navigateBack, navigateTo } from '@/utils/navigation' @@ -22,29 +18,33 @@ export default function FamilyDetail() { const familyId = router.params.id ?? '' const [loading, setLoading] = useState(true) - const [detail, setDetail] = useState(null) + const [family, setFamily] = useState(null) + const [members, setMembers] = useState([]) + const [caredCount, setCaredCount] = useState(0) const load = async () => { setLoading(true) try { - setDetail(await getFamilyDetail(familyId)) + const [home, memberList] = await Promise.all([ + getHomeDetail(familyId), + getHomeMemberList(familyId) + ]) + setFamily(home) + setMembers(memberList) + setCaredCount(home.careCount ?? 0) } catch { - setDetail(null) + setFamily(null) } finally { setLoading(false) } } - // 从添加表单返回时刷新(caredCount 变化) + // 从添加表单返回时刷新 useDidShow(() => { load() }) - const addCared = async () => { - if (!(await canAddCared(familyId))) { - showToast('已达上限') - return - } + const addCared = () => { navigateTo(`${ROUTES.FAMILY_CONTACT_ADD}?familyId=${familyId}`) } @@ -57,7 +57,7 @@ export default function FamilyDetail() { ) } - if (!detail) { + if (!family) { return ( @@ -72,7 +72,7 @@ export default function FamilyDetail() { ) } - const canAdd = detail.family.caredCount < MAX_CARED_PER_FAMILY + const canAdd = caredCount < MAX_CARED_PER_FAMILY const stub = (tip: string) => () => showToast(tip) return ( @@ -80,7 +80,7 @@ export default function FamilyDetail() { - + {/* 添加关爱对象卡:达上限后灰化静态 */} - {detail.members.map((m) => ( - + {members.map((m) => ( + ))} - + diff --git a/src/pages/family/index.tsx b/src/pages/family/index.tsx index 24dc4ac..cca0c46 100644 --- a/src/pages/family/index.tsx +++ b/src/pages/family/index.tsx @@ -4,7 +4,7 @@ import { View, Text } from '@tarojs/components' import { Navbar } from '@/components/navbar' import { Modal } from '@/components/modal' import { FamilyCard } from '@/components/family-card' -import { getFamilies, addFamily, joinFamily, type Family } from '@/api/family' +import { getHomeList, addHome, joinHome, type HomeInfo } from '@/api/home' import { ROUTES } from '@/constants/routes' import { navigateTo } from '@/utils/navigation' import { showToast } from '@/utils/toast' @@ -24,18 +24,18 @@ const showEditableModal = (option: EditableModalOption) => Taro.showModal(option as Taro.showModal.Option) as Promise export default function Family() { - const [list, setList] = useState([]) + const [list, setList] = useState([]) const [showCreateModal, setShowCreateModal] = useState(false) const reload = async () => { try { - setList(await getFamilies()) - } catch { - showToast('加载失败') + setList(await getHomeList()) + } catch (e) { + showToast(e instanceof Error ? e.message : '加载失败') } } - // 从详情页返回时刷新(caredCount 变化) + // 从详情页返回时刷新(关爱对象变化) useDidShow(() => { reload() }) @@ -50,7 +50,7 @@ export default function Family() { }).then(async ({ confirm, content }) => { if (!confirm) return try { - await addFamily(content ?? '') + await addHome(content ?? '') setShowCreateModal(false) await reload() } catch (error) { @@ -67,7 +67,7 @@ export default function Family() { }).then(async ({ confirm, content }) => { if (!confirm) return try { - await joinFamily(content ?? '') + await joinHome(content ?? '') setShowCreateModal(false) await reload() } catch (error) { @@ -82,7 +82,7 @@ export default function Family() { {list.map((family) => ( - + ))} {/* 底部加号卡:弹新建/加入 */} diff --git a/tests/unit/family/contact-validate.spec.ts b/tests/unit/family/contact-validate.spec.ts index 54ae959..901bc1c 100644 --- a/tests/unit/family/contact-validate.spec.ts +++ b/tests/unit/family/contact-validate.spec.ts @@ -1,12 +1,12 @@ import { describe, it, expect } from 'vitest' import { validateContactForm, type ContactFormInput } from '@/pages/family/contact/validate' -import type { EmergencyContact } from '@/api/family' +import type { CareContact } from '@/api/careMember' -const contact = (over: Partial = {}): EmergencyContact => ({ - id: 'c1', +const contact = (over: Partial = {}): CareContact & { tmpId: string } => ({ + tmpId: 'c1', name: '', phone: '', - relation: '', + relationShip: '', ...over }) @@ -47,10 +47,12 @@ describe('添加关爱对象表单校验', () => { expect(!r2.ok && r2.error).toBe('请完整填写联系人') }) - it('姓名电话齐全的联系人保留在结果中', () => { - const c = contact({ id: 'c9', name: '张女士', phone: '13800138000', relation: '配偶' }) + it('姓名电话齐全的联系人保留,且提交前剥掉 tmpId', () => { + const c = contact({ tmpId: 'c9', name: '张女士', phone: '13800138000', relationShip: '配偶' }) const result = validateContactForm(form({ contacts: [c] })) expect(result.ok).toBe(true) - if (result.ok) expect(result.contacts).toEqual([c]) + if (result.ok) { + expect(result.contacts).toEqual([{ name: '张女士', phone: '13800138000', relationShip: '配偶' }]) + } }) }) diff --git a/tests/unit/family/family.spec.ts b/tests/unit/family/family.spec.ts deleted file mode 100644 index b154375..0000000 --- a/tests/unit/family/family.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest' - -// mock storage:内存 Map 语义,与 Taro.setStorageSync/getStorageSync 等价 -const store = new Map() -vi.mock('@/utils/storage', () => ({ - storage: { - get: (key: string): T | null => (store.has(key) ? (store.get(key) as T) : null), - set: (key: string, value: T): void => { store.set(key, value) }, - remove: (key: string): void => { store.delete(key) } - } -})) - -import { - getFamilies, - getFamilyDetail, - addFamily, - joinFamily, - canAddCared, - addCared, - getCaredByFamily, - type CaredPerson -} from '@/api/family' -import { FAMILY_DATA_STORAGE_KEY } from '@/constants/family' - -const caredInput = (over: Partial = {}): Omit => ({ - familyId: '735201884550361', - name: '王奶奶', - idNumber: '440301194001010000', - address: '深圳市南山区', - contacts: [], - ...over -}) - -beforeEach(() => { - store.clear() -}) - -describe('家庭圈 mock 数据层', () => { - it('首次调用 getFamilies 写入样例并返回 4 条', async () => { - const list = await getFamilies() - expect(list).toHaveLength(4) - expect(store.has(FAMILY_DATA_STORAGE_KEY)).toBe(true) - }) - - it('addFamily 追加家庭且持久化,caredCount 从 0 开始', async () => { - await getFamilies() - const f = await addFamily('测试家庭') - expect(f.name).toBe('测试家庭') - expect(f.caredCount).toBe(0) - const again = await getFamilies() - expect(again).toHaveLength(5) - expect(again.some((x) => x.id === f.id)).toBe(true) - }) - - it('addFamily 空名抛错', async () => { - await getFamilies() - await expect(addFamily(' ')).rejects.toThrow('请输入家庭名') - }) - - it('joinFamily 按编号生成新家庭', async () => { - await getFamilies() - const f = await joinFamily('1234567890') - expect(f.name).toBe('加入的 7890') - const again = await getFamilies() - expect(again).toHaveLength(5) - }) - - it('getFamilyDetail 返回家庭 + 成员 + 关爱对象', async () => { - await getFamilies() - const detail = await getFamilyDetail('846443196293189') - expect(detail).not.toBeNull() - expect(detail!.family.name).toBe('JollyPlace') - expect(detail!.members.length).toBe(2) - expect(detail!.cared.length).toBe(1) - expect(detail!.cared[0].name).toBe('Jay') - }) - - it('getFamilyDetail 不存在返回 null', async () => { - await getFamilies() - expect(await getFamilyDetail('nope')).toBeNull() - }) - - it('canAddCared 未达上限 true,加满后 false', async () => { - await getFamilies() - // JollyPlace 已有 Jay(1/2),仍未达上限 - expect(await canAddCared('846443196293189')).toBe(true) - expect(await canAddCared('735201884550361')).toBe(true) - await addCared(caredInput()) - await addCared(caredInput({ name: '李爷爷' })) - expect(await canAddCared('735201884550361')).toBe(false) - }) - - it('addCared 写入并同步家庭 caredCount/caredLabel', async () => { - await getFamilies() - const saved = await addCared(caredInput()) - expect(saved.id).toBeTruthy() - const detail = await getFamilyDetail('735201884550361') - expect(detail).not.toBeNull() - expect(detail!.family.caredCount).toBe(1) - expect(detail!.family.caredLabel).toBe('王奶奶') - expect(detail!.cared[0].contacts).toEqual([]) - }) - - it('addCared 达上限抛错', async () => { - await getFamilies() - await addCared(caredInput()) - await addCared(caredInput({ name: '李爷爷' })) - await expect(addCared(caredInput({ name: '第三人' }))).rejects.toThrow('已达上限') - }) - - it('getCaredByFamily 返回该家庭的关爱对象数组', async () => { - await getFamilies() - const list = await getCaredByFamily('846443196293189') - expect(list.map((c) => c.name)).toEqual(['Jay']) - }) -})