feat(family): 家庭圈列表/详情/关爱对象切换真实后端接口,移除 mock 数据层
This commit is contained in:
parent
b1a9bef903
commit
6889e9b4fa
|
|
@ -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<void> {
|
||||||
|
return request.post('/yanglao/app/ylHomeCareMember/save', params as unknown as Record<string, unknown>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCareMemberList(homeId: string): Promise<CareMember[]> {
|
||||||
|
return request.post('/yanglao/app/ylHomeCareMember/getList', { homeId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCareMemberDetail(memberId: string): Promise<CareMember> {
|
||||||
|
return request.get(`/yanglao/app/ylHomeCareMember/get/${memberId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteCareMember(memberId: string): Promise<void> {
|
||||||
|
return request.post('/yanglao/app/ylHomeCareMember/delete', { memberId })
|
||||||
|
}
|
||||||
|
|
@ -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<FamilyData>(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 = <T>(value: T, ms = 100): Promise<T> =>
|
|
||||||
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<Family[]> {
|
|
||||||
return delay(loadData().families)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFamilyDetail(id: string): Promise<FamilyDetailData | null> {
|
|
||||||
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<Family> {
|
|
||||||
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<Family> {
|
|
||||||
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<boolean> {
|
|
||||||
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<CaredPerson, 'id'>): Promise<CaredPerson> {
|
|
||||||
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<CaredPerson[]> {
|
|
||||||
return delay(loadData().cared.filter((c) => c.familyId === familyId))
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +1,31 @@
|
||||||
import { request } from '@/http'
|
import { request } from '@/http'
|
||||||
|
|
||||||
// 骨架:本期仅锁定接口路径,字段类型按文档目录推断,联调时逐个修正(spec D7)
|
// 家庭圈接口(apifox 2026-09-11 文档版)
|
||||||
|
// 响应 Content 字段文档未给出(ResponseData 泛型占位),类型按命名推断,联调时逐个修正
|
||||||
|
|
||||||
export interface HomeInfo {
|
export interface HomeInfo {
|
||||||
homeId: string
|
homeId: string
|
||||||
homeName: string
|
homeName: string
|
||||||
ownerUserId: string
|
ownerUserId: string
|
||||||
memberCount: number
|
memberCount: number
|
||||||
|
// 以下为列表卡展示推断字段,联调确认
|
||||||
|
careName?: string // 关爱对象名(caredLabel)
|
||||||
|
careCount?: number // 关爱对象数(上限校验用)
|
||||||
|
address?: string
|
||||||
|
deviceCount?: number
|
||||||
|
role?: string // 当前用户角色(群主/成员)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HomeMember {
|
export interface HomeMember {
|
||||||
userId: string
|
userId: string
|
||||||
nickName: string
|
nickName: string
|
||||||
isOwner: boolean
|
isOwner: boolean
|
||||||
|
mobile?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addHome(params: Record<string, unknown>): Promise<void> {
|
export function addHome(name: string): Promise<void> {
|
||||||
return request.post('/yanglao/app/ylHome/add', params)
|
// name 可空,空则后端默认「我的家庭圈」(文档 YlHomeAddReq)
|
||||||
|
return request.post('/yanglao/app/ylHome/add', { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateHome(params: Record<string, unknown>): Promise<void> {
|
export function updateHome(params: Record<string, unknown>): Promise<void> {
|
||||||
|
|
@ -27,11 +37,11 @@ export function dissolveHome(params: Record<string, unknown>): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHomeList(): Promise<HomeInfo[]> {
|
export function getHomeList(): Promise<HomeInfo[]> {
|
||||||
return request.get('/yanglao/app/ylHome/getList')
|
return request.post('/yanglao/app/ylHome/getList')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHomeMemberList(homeId: string): Promise<HomeMember[]> {
|
export function getHomeMemberList(homeId: string): Promise<HomeMember[]> {
|
||||||
return request.get('/yanglao/app/ylHome/getMemberList', { homeId })
|
return request.post('/yanglao/app/ylHome/getMemberList', { homeId })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transferOwner(params: Record<string, unknown>): Promise<void> {
|
export function transferOwner(params: Record<string, unknown>): Promise<void> {
|
||||||
|
|
@ -46,6 +56,7 @@ export function getHomeDetail(homeId: string): Promise<HomeInfo> {
|
||||||
return request.get(`/yanglao/app/ylHome/get/${homeId}`)
|
return request.get(`/yanglao/app/ylHome/get/${homeId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function joinHome(params: Record<string, unknown>): Promise<void> {
|
export function joinHome(code: string): Promise<void> {
|
||||||
return request.post('/yanglao/app/ylHome/join', params)
|
// code 为家庭编号(文档 YlHomeJoinReq)
|
||||||
|
return request.post('/yanglao/app/ylHome/join', { code })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import { View, Text } from '@tarojs/components'
|
import { View, Text } from '@tarojs/components'
|
||||||
import type { Family } from '@/api/family'
|
import type { HomeInfo } from '@/api/home'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
family: Family
|
family: HomeInfo
|
||||||
onClick?: (id: string) => void
|
onClick?: (id: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FamilyCard({ family, onClick }: Props) {
|
export function FamilyCard({ family, onClick }: Props) {
|
||||||
const first = family.name.slice(0, 1)
|
const first = family.homeName.slice(0, 1)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
hoverClass={onClick ? styles.cardPressed : 'none'}
|
hoverClass={onClick ? styles.cardPressed : 'none'}
|
||||||
onClick={() => onClick?.(family.id)}
|
onClick={() => onClick?.(family.homeId)}
|
||||||
>
|
>
|
||||||
{/* 右上淡绿圆装饰 */}
|
{/* 右上淡绿圆装饰 */}
|
||||||
<View className={styles.decor} />
|
<View className={styles.decor} />
|
||||||
|
|
@ -24,21 +24,21 @@ export function FamilyCard({ family, onClick }: Props) {
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.headMain}>
|
<View className={styles.headMain}>
|
||||||
<View className={styles.nameRow}>
|
<View className={styles.nameRow}>
|
||||||
<Text className={styles.name}>{family.name}</Text>
|
<Text className={styles.name}>{family.homeName}</Text>
|
||||||
{family.role && <Text className={styles.roleBadge}>{family.role}</Text>}
|
{family.role && <Text className={styles.roleBadge}>{family.role}</Text>}
|
||||||
</View>
|
</View>
|
||||||
<Text className={styles.subCount}>
|
<Text className={styles.subCount}>
|
||||||
{family.memberCount} 位成员 · {family.deviceCount} 台设备
|
{family.memberCount} 位成员 · {family.deviceCount ?? 0} 台设备
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.rows}>
|
<View className={styles.rows}>
|
||||||
<Text className={styles.rowLabel}>关爱对象</Text>
|
<Text className={styles.rowLabel}>关爱对象</Text>
|
||||||
<Text className={styles.rowValue}>{family.caredLabel}</Text>
|
<Text className={styles.rowValue}>{family.careName || '--'}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.rows}>
|
<View className={styles.rows}>
|
||||||
<Text className={styles.rowLabel}>关爱地址</Text>
|
<Text className={styles.rowLabel}>关爱地址</Text>
|
||||||
<Text className={styles.rowValue}>{family.address}</Text>
|
<Text className={styles.rowValue}>{family.address || '--'}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { getDeviceList } from '@/api/device'
|
||||||
import { DEVICE_ICON_MAP, type DeviceModel } from '@/constants/enums'
|
import { DEVICE_ICON_MAP, type DeviceModel } from '@/constants/enums'
|
||||||
import { ROUTES } from '@/constants/routes'
|
import { ROUTES } from '@/constants/routes'
|
||||||
import { navigateTo } from '@/utils/navigation'
|
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'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
const FALLBACK_ICON = '⌂'
|
const FALLBACK_ICON = '⌂'
|
||||||
|
|
@ -24,10 +24,10 @@ export default function DeviceList() {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [loadError, setLoadError] = useState(false)
|
const [loadError, setLoadError] = useState(false)
|
||||||
|
|
||||||
const [families, setFamilies] = useState<Family[]>([])
|
const [families, setFamilies] = useState<HomeInfo[]>([])
|
||||||
const [currentFamily, setCurrentFamily] = useState<Family | null>(null)
|
const [currentFamily, setCurrentFamily] = useState<HomeInfo | null>(null)
|
||||||
const [familySheetOpen, setFamilySheetOpen] = useState(false)
|
const [familySheetOpen, setFamilySheetOpen] = useState(false)
|
||||||
const [pendingFamily, setPendingFamily] = useState<Family | null>(null)
|
const [pendingFamily, setPendingFamily] = useState<HomeInfo | null>(null)
|
||||||
|
|
||||||
// 在线状态需每次进入页面都新鲜:useDidShow 覆盖
|
// 在线状态需每次进入页面都新鲜:useDidShow 覆盖
|
||||||
// 首次进入、从详情页 navigateBack 返回、tab 切换回三种场景
|
// 首次进入、从详情页 navigateBack 返回、tab 切换回三种场景
|
||||||
|
|
@ -46,7 +46,7 @@ export default function DeviceList() {
|
||||||
|
|
||||||
const loadFamilies = useCallback(async () => {
|
const loadFamilies = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const list = await getFamilies()
|
const list = await getHomeList()
|
||||||
setFamilies(list)
|
setFamilies(list)
|
||||||
// 若当前未选中,默认选第一条
|
// 若当前未选中,默认选第一条
|
||||||
setCurrentFamily((prev) => prev ?? list[0] ?? null)
|
setCurrentFamily((prev) => prev ?? list[0] ?? null)
|
||||||
|
|
@ -93,11 +93,11 @@ export default function DeviceList() {
|
||||||
<View className={styles.familyCard}>
|
<View className={styles.familyCard}>
|
||||||
<View className={styles.familyRow}>
|
<View className={styles.familyRow}>
|
||||||
<Text className={styles.familyLabel}>设备应用家庭</Text>
|
<Text className={styles.familyLabel}>设备应用家庭</Text>
|
||||||
<Text className={styles.familyValue}>{currentFamily?.name ?? scene?.name ?? '未选择'}</Text>
|
<Text className={styles.familyValue}>{currentFamily?.homeName ?? scene?.name ?? '未选择'}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.familyRow}>
|
<View className={styles.familyRow}>
|
||||||
<Text className={styles.familyLabel}>关爱对象</Text>
|
<Text className={styles.familyLabel}>关爱对象</Text>
|
||||||
<Text className={styles.familyValue}>{currentFamily?.caredLabel ?? '--'}</Text>
|
<Text className={styles.familyValue}>{currentFamily?.careName ?? '--'}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.familyRow}>
|
<View className={styles.familyRow}>
|
||||||
<Text className={styles.familyLabel}>关爱地址</Text>
|
<Text className={styles.familyLabel}>关爱地址</Text>
|
||||||
|
|
@ -150,15 +150,15 @@ export default function DeviceList() {
|
||||||
<BottomSheet visible={familySheetOpen} title="切换家庭圈" onClose={closeFamilySheet}>
|
<BottomSheet visible={familySheetOpen} title="切换家庭圈" onClose={closeFamilySheet}>
|
||||||
<View className={styles.sheetList}>
|
<View className={styles.sheetList}>
|
||||||
{families.map((family) => {
|
{families.map((family) => {
|
||||||
const active = pendingFamily?.id === family.id
|
const active = pendingFamily?.homeId === family.homeId
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={family.id}
|
key={family.homeId}
|
||||||
className={`${styles.sheetItem} ${active ? styles.sheetItemActive : ''}`}
|
className={`${styles.sheetItem} ${active ? styles.sheetItemActive : ''}`}
|
||||||
onClick={() => setPendingFamily(family)}
|
onClick={() => setPendingFamily(family)}
|
||||||
>
|
>
|
||||||
<View className={styles.sheetHead}>
|
<View className={styles.sheetHead}>
|
||||||
<Text className={styles.sheetName}>{family.name}</Text>
|
<Text className={styles.sheetName}>{family.homeName}</Text>
|
||||||
{family.role && (
|
{family.role && (
|
||||||
<View className={styles.sheetRole}>
|
<View className={styles.sheetRole}>
|
||||||
<Text className={styles.sheetRoleText}>{family.role}</Text>
|
<Text className={styles.sheetRoleText}>{family.role}</Text>
|
||||||
|
|
@ -166,8 +166,8 @@ export default function DeviceList() {
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.sheetMeta}>
|
<View className={styles.sheetMeta}>
|
||||||
<Text className={styles.sheetMetaItem}>关爱对象 {family.caredLabel}</Text>
|
<Text className={styles.sheetMetaItem}>关爱对象 {family.careName || '--'}</Text>
|
||||||
<Text className={styles.sheetMetaItem}>设备数量 {family.deviceCount}台</Text>
|
<Text className={styles.sheetMetaItem}>设备数量 {family.deviceCount ?? 0}台</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,19 @@ import { useState } from 'react'
|
||||||
import { useRouter } from '@tarojs/taro'
|
import { useRouter } from '@tarojs/taro'
|
||||||
import { View, Text, Input } from '@tarojs/components'
|
import { View, Text, Input } from '@tarojs/components'
|
||||||
import { Navbar } from '@/components/navbar'
|
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 { MAX_EMERGENCY_CONTACTS, MAX_FAMILY_NAME_LEN } from '@/constants/family'
|
||||||
import { navigateBack } from '@/utils/navigation'
|
import { navigateBack } from '@/utils/navigation'
|
||||||
import { showToast } from '@/utils/toast'
|
import { showToast } from '@/utils/toast'
|
||||||
import { validateContactForm } from './validate'
|
import { validateContactForm } from './validate'
|
||||||
import styles from './add.module.scss'
|
import styles from './add.module.scss'
|
||||||
|
|
||||||
const emptyContact = (): EmergencyContact => ({
|
let tmpSeq = 0
|
||||||
id: `tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
const emptyContact = (): (CareContact & { tmpId: string }) => ({
|
||||||
|
tmpId: `tmp-${Date.now()}-${(tmpSeq += 1)}`,
|
||||||
name: '',
|
name: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
relation: ''
|
relationShip: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function ContactAdd() {
|
export default function ContactAdd() {
|
||||||
|
|
@ -23,15 +24,15 @@ export default function ContactAdd() {
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
const [idNumber, setIdNumber] = useState('')
|
const [idNumber, setIdNumber] = useState('')
|
||||||
const [address, setAddress] = useState('')
|
const [address, setAddress] = useState('')
|
||||||
const [contacts, setContacts] = useState<EmergencyContact[]>([])
|
const [contacts, setContacts] = useState<(CareContact & { tmpId: string })[]>([])
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
const patchContact = (id: string, key: keyof EmergencyContact, value: string) => {
|
const patchContact = (id: string, key: keyof CareContact, value: string) => {
|
||||||
setContacts((prev) => prev.map((c) => (c.id === id ? { ...c, [key]: value } : c)))
|
setContacts((prev) => prev.map((c) => (c.tmpId === id ? { ...c, [key]: value } : c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeContact = (id: string) => {
|
const removeContact = (id: string) => {
|
||||||
setContacts((prev) => prev.filter((c) => c.id !== id))
|
setContacts((prev) => prev.filter((c) => c.tmpId !== id))
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
|
@ -44,8 +45,8 @@ export default function ContactAdd() {
|
||||||
|
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
await addCared({
|
await saveCareMember({
|
||||||
familyId,
|
homeId: familyId,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
idNumber: idNumber.trim(),
|
idNumber: idNumber.trim(),
|
||||||
address: address.trim(),
|
address: address.trim(),
|
||||||
|
|
@ -107,8 +108,8 @@ export default function ContactAdd() {
|
||||||
<Text className={styles.contactsTip}>最多可添加 {MAX_EMERGENCY_CONTACTS} 位紧急联系人,可不填</Text>
|
<Text className={styles.contactsTip}>最多可添加 {MAX_EMERGENCY_CONTACTS} 位紧急联系人,可不填</Text>
|
||||||
|
|
||||||
{contacts.map((c) => (
|
{contacts.map((c) => (
|
||||||
<View key={c.id} className={styles.contactItem}>
|
<View key={c.tmpId} className={styles.contactItem}>
|
||||||
<View className={styles.contactDel} hoverClass={styles.contactDelPressed} onClick={() => removeContact(c.id)}>
|
<View className={styles.contactDel} hoverClass={styles.contactDelPressed} onClick={() => removeContact(c.tmpId)}>
|
||||||
<View className={styles.contactDelInner}>
|
<View className={styles.contactDelInner}>
|
||||||
<Text className={styles.contactDelText}>×</Text>
|
<Text className={styles.contactDelText}>×</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -119,7 +120,7 @@ export default function ContactAdd() {
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
placeholder="联系人姓名"
|
placeholder="联系人姓名"
|
||||||
value={c.name}
|
value={c.name}
|
||||||
onInput={(e) => patchContact(c.id, 'name', e.detail.value)}
|
onInput={(e) => patchContact(c.tmpId, 'name', e.detail.value)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.formRow}>
|
<View className={styles.formRow}>
|
||||||
|
|
@ -129,7 +130,7 @@ export default function ContactAdd() {
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="联系人电话"
|
placeholder="联系人电话"
|
||||||
value={c.phone}
|
value={c.phone}
|
||||||
onInput={(e) => patchContact(c.id, 'phone', e.detail.value)}
|
onInput={(e) => patchContact(c.tmpId, 'phone', e.detail.value)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.formRow}>
|
<View className={styles.formRow}>
|
||||||
|
|
@ -137,8 +138,8 @@ export default function ContactAdd() {
|
||||||
<Input
|
<Input
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
placeholder="如:配偶、儿子"
|
placeholder="如:配偶、儿子"
|
||||||
value={c.relation}
|
value={c.relationShip}
|
||||||
onInput={(e) => patchContact(c.id, 'relation', e.detail.value)}
|
onInput={(e) => patchContact(c.tmpId, 'relationShip', e.detail.value)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import type { EmergencyContact } from '@/api/family'
|
import type { CareContact } from '@/api/careMember'
|
||||||
|
|
||||||
export interface ContactFormInput {
|
export interface ContactFormInput {
|
||||||
name: string
|
name: string
|
||||||
idNumber: string
|
idNumber: string
|
||||||
address: string
|
address: string
|
||||||
contacts: EmergencyContact[]
|
contacts: (CareContact & { tmpId: string })[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ValidateResult =
|
export type ValidateResult =
|
||||||
| { ok: true; contacts: EmergencyContact[] }
|
| { ok: true; contacts: CareContact[] }
|
||||||
| { ok: false; error: string }
|
| { ok: false; error: string }
|
||||||
|
|
||||||
// 必填项齐全且联系人卡要么空白(忽略)要么填全,才允许提交
|
// 必填项齐全且联系人卡要么空白(忽略)要么填全,才允许提交
|
||||||
|
|
@ -22,5 +22,7 @@ export function validateContactForm(input: ContactFormInput): ValidateResult {
|
||||||
return { ok: false, error: '请完整填写联系人' }
|
return { ok: false, error: '请完整填写联系人' }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ok: true, contacts: filled }
|
// 提交前剥掉仅供 React key 用的 tmpId
|
||||||
|
const contacts = filled.map(({ tmpId: _tmpId, ...c }) => c)
|
||||||
|
return { ok: true, contacts }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,7 @@ import { Loading } from '@/components/loading'
|
||||||
import { StateView } from '@/components/state-view'
|
import { StateView } from '@/components/state-view'
|
||||||
import { FamilyCard } from '@/components/family-card'
|
import { FamilyCard } from '@/components/family-card'
|
||||||
import { ListRow, ListRowGroup } from '@/components/list-row'
|
import { ListRow, ListRowGroup } from '@/components/list-row'
|
||||||
import {
|
import { getHomeDetail, getHomeMemberList, type HomeInfo, type HomeMember } from '@/api/home'
|
||||||
getFamilyDetail,
|
|
||||||
canAddCared,
|
|
||||||
type FamilyDetailData
|
|
||||||
} from '@/api/family'
|
|
||||||
import { MAX_CARED_PER_FAMILY } from '@/constants/family'
|
import { MAX_CARED_PER_FAMILY } from '@/constants/family'
|
||||||
import { ROUTES } from '@/constants/routes'
|
import { ROUTES } from '@/constants/routes'
|
||||||
import { navigateBack, navigateTo } from '@/utils/navigation'
|
import { navigateBack, navigateTo } from '@/utils/navigation'
|
||||||
|
|
@ -22,29 +18,33 @@ export default function FamilyDetail() {
|
||||||
const familyId = router.params.id ?? ''
|
const familyId = router.params.id ?? ''
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [detail, setDetail] = useState<FamilyDetailData | null>(null)
|
const [family, setFamily] = useState<HomeInfo | null>(null)
|
||||||
|
const [members, setMembers] = useState<HomeMember[]>([])
|
||||||
|
const [caredCount, setCaredCount] = useState(0)
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
setDetail(await getFamilyDetail(familyId))
|
const [home, memberList] = await Promise.all([
|
||||||
|
getHomeDetail(familyId),
|
||||||
|
getHomeMemberList(familyId)
|
||||||
|
])
|
||||||
|
setFamily(home)
|
||||||
|
setMembers(memberList)
|
||||||
|
setCaredCount(home.careCount ?? 0)
|
||||||
} catch {
|
} catch {
|
||||||
setDetail(null)
|
setFamily(null)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从添加表单返回时刷新(caredCount 变化)
|
// 从添加表单返回时刷新
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
load()
|
load()
|
||||||
})
|
})
|
||||||
|
|
||||||
const addCared = async () => {
|
const addCared = () => {
|
||||||
if (!(await canAddCared(familyId))) {
|
|
||||||
showToast('已达上限')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigateTo(`${ROUTES.FAMILY_CONTACT_ADD}?familyId=${familyId}`)
|
navigateTo(`${ROUTES.FAMILY_CONTACT_ADD}?familyId=${familyId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ export default function FamilyDetail() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!detail) {
|
if (!family) {
|
||||||
return (
|
return (
|
||||||
<View className={styles.page}>
|
<View className={styles.page}>
|
||||||
<Navbar title="关爱对象信息" variant="hero" />
|
<Navbar title="关爱对象信息" variant="hero" />
|
||||||
|
|
@ -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)
|
const stub = (tip: string) => () => showToast(tip)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -80,7 +80,7 @@ export default function FamilyDetail() {
|
||||||
<Navbar title="关爱对象信息" variant="hero" />
|
<Navbar title="关爱对象信息" variant="hero" />
|
||||||
|
|
||||||
<View className={styles.body}>
|
<View className={styles.body}>
|
||||||
<FamilyCard family={detail.family} />
|
<FamilyCard family={family} />
|
||||||
|
|
||||||
{/* 添加关爱对象卡:达上限后灰化静态 */}
|
{/* 添加关爱对象卡:达上限后灰化静态 */}
|
||||||
<View
|
<View
|
||||||
|
|
@ -102,15 +102,15 @@ export default function FamilyDetail() {
|
||||||
|
|
||||||
<View className={styles.section}>
|
<View className={styles.section}>
|
||||||
<ListRowGroup>
|
<ListRowGroup>
|
||||||
{detail.members.map((m) => (
|
{members.map((m) => (
|
||||||
<ListRow key={m.id} label={m.name} value={m.role === 'owner' ? '群主' : '成员'} />
|
<ListRow key={m.userId} label={m.nickName} value={m.isOwner ? '群主' : '成员'} />
|
||||||
))}
|
))}
|
||||||
</ListRowGroup>
|
</ListRowGroup>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className={styles.section}>
|
<View className={styles.section}>
|
||||||
<ListRowGroup>
|
<ListRowGroup>
|
||||||
<ListRow label="家庭编号" value={detail.family.id} onClick={stub('复制成功(占位)')} arrow />
|
<ListRow label="家庭编号" value={family.homeId} onClick={stub('复制成功(占位)')} arrow />
|
||||||
<ListRow label="邀请他人" arrow onClick={stub('邀请功能即将上线')} />
|
<ListRow label="邀请他人" arrow onClick={stub('邀请功能即将上线')} />
|
||||||
<ListRow label="转让群主" arrow onClick={stub('转让功能即将上线')} />
|
<ListRow label="转让群主" arrow onClick={stub('转让功能即将上线')} />
|
||||||
<ListRow label="修改群昵称" arrow onClick={stub('修改昵称功能即将上线')} />
|
<ListRow label="修改群昵称" arrow onClick={stub('修改昵称功能即将上线')} />
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { View, Text } from '@tarojs/components'
|
||||||
import { Navbar } from '@/components/navbar'
|
import { Navbar } from '@/components/navbar'
|
||||||
import { Modal } from '@/components/modal'
|
import { Modal } from '@/components/modal'
|
||||||
import { FamilyCard } from '@/components/family-card'
|
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 { ROUTES } from '@/constants/routes'
|
||||||
import { navigateTo } from '@/utils/navigation'
|
import { navigateTo } from '@/utils/navigation'
|
||||||
import { showToast } from '@/utils/toast'
|
import { showToast } from '@/utils/toast'
|
||||||
|
|
@ -24,18 +24,18 @@ const showEditableModal = (option: EditableModalOption) =>
|
||||||
Taro.showModal(option as Taro.showModal.Option) as Promise<EditableModalResult>
|
Taro.showModal(option as Taro.showModal.Option) as Promise<EditableModalResult>
|
||||||
|
|
||||||
export default function Family() {
|
export default function Family() {
|
||||||
const [list, setList] = useState<Family[]>([])
|
const [list, setList] = useState<HomeInfo[]>([])
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
try {
|
try {
|
||||||
setList(await getFamilies())
|
setList(await getHomeList())
|
||||||
} catch {
|
} catch (e) {
|
||||||
showToast('加载失败')
|
showToast(e instanceof Error ? e.message : '加载失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从详情页返回时刷新(caredCount 变化)
|
// 从详情页返回时刷新(关爱对象变化)
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
reload()
|
reload()
|
||||||
})
|
})
|
||||||
|
|
@ -50,7 +50,7 @@ export default function Family() {
|
||||||
}).then(async ({ confirm, content }) => {
|
}).then(async ({ confirm, content }) => {
|
||||||
if (!confirm) return
|
if (!confirm) return
|
||||||
try {
|
try {
|
||||||
await addFamily(content ?? '')
|
await addHome(content ?? '')
|
||||||
setShowCreateModal(false)
|
setShowCreateModal(false)
|
||||||
await reload()
|
await reload()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -67,7 +67,7 @@ export default function Family() {
|
||||||
}).then(async ({ confirm, content }) => {
|
}).then(async ({ confirm, content }) => {
|
||||||
if (!confirm) return
|
if (!confirm) return
|
||||||
try {
|
try {
|
||||||
await joinFamily(content ?? '')
|
await joinHome(content ?? '')
|
||||||
setShowCreateModal(false)
|
setShowCreateModal(false)
|
||||||
await reload()
|
await reload()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -82,7 +82,7 @@ export default function Family() {
|
||||||
|
|
||||||
<View className={styles.list}>
|
<View className={styles.list}>
|
||||||
{list.map((family) => (
|
{list.map((family) => (
|
||||||
<FamilyCard key={family.id} family={family} onClick={goDetail} />
|
<FamilyCard key={family.homeId} family={family} onClick={goDetail} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* 底部加号卡:弹新建/加入 */}
|
{/* 底部加号卡:弹新建/加入 */}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest'
|
||||||
import { validateContactForm, type ContactFormInput } from '@/pages/family/contact/validate'
|
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> = {}): EmergencyContact => ({
|
const contact = (over: Partial<CareContact & { tmpId: string }> = {}): CareContact & { tmpId: string } => ({
|
||||||
id: 'c1',
|
tmpId: 'c1',
|
||||||
name: '',
|
name: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
relation: '',
|
relationShip: '',
|
||||||
...over
|
...over
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -47,10 +47,12 @@ describe('添加关爱对象表单校验', () => {
|
||||||
expect(!r2.ok && r2.error).toBe('请完整填写联系人')
|
expect(!r2.ok && r2.error).toBe('请完整填写联系人')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('姓名电话齐全的联系人保留在结果中', () => {
|
it('姓名电话齐全的联系人保留,且提交前剥掉 tmpId', () => {
|
||||||
const c = contact({ id: 'c9', name: '张女士', phone: '13800138000', relation: '配偶' })
|
const c = contact({ tmpId: 'c9', name: '张女士', phone: '13800138000', relationShip: '配偶' })
|
||||||
const result = validateContactForm(form({ contacts: [c] }))
|
const result = validateContactForm(form({ contacts: [c] }))
|
||||||
expect(result.ok).toBe(true)
|
expect(result.ok).toBe(true)
|
||||||
if (result.ok) expect(result.contacts).toEqual([c])
|
if (result.ok) {
|
||||||
|
expect(result.contacts).toEqual([{ name: '张女士', phone: '13800138000', relationShip: '配偶' }])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
||||||
|
|
||||||
// mock storage:内存 Map 语义,与 Taro.setStorageSync/getStorageSync 等价
|
|
||||||
const store = new Map<string, unknown>()
|
|
||||||
vi.mock('@/utils/storage', () => ({
|
|
||||||
storage: {
|
|
||||||
get: <T>(key: string): T | null => (store.has(key) ? (store.get(key) as T) : null),
|
|
||||||
set: <T>(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<CaredPerson> = {}): Omit<CaredPerson, 'id'> => ({
|
|
||||||
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'])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Loading…
Reference in New Issue