From a1901b56110dbe171ff0c69ce6b0946845513831 Mon Sep 17 00:00:00 2001 From: ozh Date: Thu, 17 Sep 2026 11:56:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(family):=20=E5=85=B3=E7=88=B1=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E8=AF=A6=E6=83=85=E9=A1=B5=EF=BC=88=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?/=E5=88=A0=E9=99=A4/=E7=BC=96=E8=BE=91=EF=BC=89=EF=BC=8C?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E8=A1=8C=E6=8E=A5=E5=85=A5=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 pages/family/contact/detail:getCareMemberDetail 展示姓名/身份证脱敏/地址/紧急联系人,删除走确认弹窗 + deleteCareMember - add 页支持编辑模式:memberId 参数预填原数据,save 带 memberId 走修改 - 家庭详情页关爱对象行加箭头跳转详情 --- src/app.config.ts | 1 + src/constants/routes.ts | 1 + src/pages/family/contact/add.module.scss | 7 + src/pages/family/contact/add.tsx | 44 +++++- src/pages/family/contact/detail.module.scss | 109 +++++++++++++++ src/pages/family/contact/detail.tsx | 144 ++++++++++++++++++++ src/pages/family/detail.tsx | 10 +- 7 files changed, 310 insertions(+), 6 deletions(-) create mode 100644 src/pages/family/contact/detail.module.scss create mode 100644 src/pages/family/contact/detail.tsx diff --git a/src/app.config.ts b/src/app.config.ts index 2e718c3..cc68536 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -12,6 +12,7 @@ export default { 'pages/family/index', 'pages/family/detail', 'pages/family/contact/add', + 'pages/family/contact/detail', 'pages/event/index' ], window: { diff --git a/src/constants/routes.ts b/src/constants/routes.ts index d8e64b7..a31d18e 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -11,5 +11,6 @@ export const ROUTES = Object.freeze({ FAMILY: '/pages/family/index', FAMILY_DETAIL: '/pages/family/detail', FAMILY_CONTACT_ADD: '/pages/family/contact/add', + FAMILY_CONTACT_DETAIL: '/pages/family/contact/detail', SCENE_SELECT: '/pages/scene/select' }) diff --git a/src/pages/family/contact/add.module.scss b/src/pages/family/contact/add.module.scss index 7502696..2616544 100644 --- a/src/pages/family/contact/add.module.scss +++ b/src/pages/family/contact/add.module.scss @@ -5,6 +5,13 @@ padding-bottom: 40px; } +.stateWrap { + margin-top: 80px; + display: flex; + flex-direction: column; + align-items: center; +} + .card, .contactsCard { margin: 24px 24px 0; diff --git a/src/pages/family/contact/add.tsx b/src/pages/family/contact/add.tsx index 268d247..42406fd 100644 --- a/src/pages/family/contact/add.tsx +++ b/src/pages/family/contact/add.tsx @@ -1,8 +1,9 @@ import { useState } from 'react' -import { useRouter } from '@tarojs/taro' +import { useRouter, useDidShow } from '@tarojs/taro' import { View, Text, Input } from '@tarojs/components' import { Navbar } from '@/components/navbar' -import { saveCareMember, type CareContact } from '@/api/careMember' +import { Loading } from '@/components/loading' +import { saveCareMember, getCareMemberDetail, 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' @@ -17,16 +18,39 @@ const emptyContact = (): (CareContact & { tmpId: string }) => ({ relationShip: '' }) +// 编辑模式预填:后端 contacts 可能 null,统一补 tmpId +const toRows = (contacts: CareContact[] | null): (CareContact & { tmpId: string })[] => + (contacts ?? []).map((c) => ({ ...c, tmpId: `tmp-${Date.now()}-${(tmpSeq += 1)}` })) + export default function ContactAdd() { const router = useRouter() const familyId = router.params.familyId ?? '' + const memberId = router.params.memberId ?? '' + const isEdit = memberId !== '' + const [loading, setLoading] = useState(isEdit) const [name, setName] = useState('') const [idNumber, setIdNumber] = useState('') const [address, setAddress] = useState('') const [contacts, setContacts] = useState<(CareContact & { tmpId: string })[]>([]) const [submitting, setSubmitting] = useState(false) + // 编辑模式进入时拉取原数据预填 + useDidShow(() => { + if (!isEdit) return + getCareMemberDetail(memberId) + .then((m) => { + setName(m.name) + setIdNumber(m.idNumber) + setAddress(m.address) + setContacts(toRows(m.contacts)) + }) + .catch((e: unknown) => { + showToast(e instanceof Error ? e.message : '加载失败') + }) + .finally(() => setLoading(false)) + }) + const patchContact = (id: string, key: keyof CareContact, value: string) => { setContacts((prev) => prev.map((c) => (c.tmpId === id ? { ...c, [key]: value } : c))) } @@ -46,13 +70,14 @@ export default function ContactAdd() { setSubmitting(true) try { await saveCareMember({ + memberId: memberId || undefined, homeId: familyId, name: name.trim(), idNumber: idNumber.trim(), address: address.trim(), contacts: result.contacts }) - showToast('添加成功') + showToast(isEdit ? '修改成功' : '添加成功') setTimeout(() => navigateBack(), 500) } catch (error) { showToast(error instanceof Error ? error.message : '添加失败') @@ -61,9 +86,18 @@ export default function ContactAdd() { } } + if (loading) { + return ( + + + + + ) + } + return ( - + @@ -151,7 +185,7 @@ export default function ContactAdd() { hoverClass={submitting ? 'none' : styles.submitPressed} onClick={submit} > - {submitting ? '提交中...' : '确认添加'} + {submitting ? '提交中...' : isEdit ? '保存修改' : '确认添加'} ) diff --git a/src/pages/family/contact/detail.module.scss b/src/pages/family/contact/detail.module.scss new file mode 100644 index 0000000..ffe2de6 --- /dev/null +++ b/src/pages/family/contact/detail.module.scss @@ -0,0 +1,109 @@ +.page { + min-height: 100vh; + background: $color-bg; + padding-bottom: 40px; +} + +.stateWrap { + margin-top: 80px; + display: flex; + flex-direction: column; + align-items: center; +} + +.body { + padding: 24px; +} + +// 顶部概览:圆头像(首字)+ 姓名,对齐 FamilyCard 头部风格 +.profile { + display: flex; + flex-direction: column; + align-items: center; + padding: 24px 0 32px; +} + +.avatar { + width: 120px; + height: 120px; + border-radius: $radius-circle; + background: $color-primary-grad; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; +} + +.avatarText { + font-size: 48px; + font-weight: 600; + color: #ffffff; +} + +.name { + font-size: 34px; + font-weight: 600; + color: $color-text; +} + +.section { + margin-bottom: 24px; +} + +.sectionTitle { + display: block; + font-size: 26px; + color: $color-text-secondary; + margin: 8px 8px 16px; +} + +.sectionEmpty { + display: block; + padding: 32px 8px; + font-size: 26px; + color: $color-text-tertiary; +} + +// 编辑/删除双按钮:编辑主色实底、删除白底红字描边 +.btnRow { + display: flex; + gap: 24px; + margin-top: 32px; +} + +.editBtn { + flex: 1; + height: 96px; + background: $color-primary-grad; + border-radius: $radius-pill; + display: flex; + align-items: center; + justify-content: center; +} + +.editBtnText { + font-size: 32px; + font-weight: 600; + color: #ffffff; +} + +.delBtn { + flex: 1; + height: 96px; + background: $color-bg-white; + border: 2px solid $color-danger; + border-radius: $radius-pill; + display: flex; + align-items: center; + justify-content: center; +} + +.delBtnText { + font-size: 32px; + font-weight: 600; + color: $color-danger; +} + +.btnPressed { + opacity: 0.85; +} diff --git a/src/pages/family/contact/detail.tsx b/src/pages/family/contact/detail.tsx new file mode 100644 index 0000000..d7c653c --- /dev/null +++ b/src/pages/family/contact/detail.tsx @@ -0,0 +1,144 @@ +import { useState } from 'react' +import { useRouter, useDidShow } from '@tarojs/taro' +import { View, Text } from '@tarojs/components' +import Taro from '@tarojs/taro' +import { Navbar } from '@/components/navbar' +import { Loading } from '@/components/loading' +import { StateView } from '@/components/state-view' +import { ListRow, ListRowGroup } from '@/components/list-row' +import { getCareMemberDetail, deleteCareMember, type CareMember } from '@/api/careMember' +import { ROUTES } from '@/constants/routes' +import { navigateBack, navigateTo } from '@/utils/navigation' +import { showToast } from '@/utils/toast' +import styles from './detail.module.scss' + +export default function ContactDetail() { + const router = useRouter() + const memberId = router.params.memberId ?? '' + const familyId = router.params.familyId ?? '' + + const [loading, setLoading] = useState(true) + const [member, setMember] = useState(null) + const [deleting, setDeleting] = useState(false) + + const load = async () => { + setLoading(true) + try { + setMember(await getCareMemberDetail(memberId)) + } catch { + setMember(null) + } finally { + setLoading(false) + } + } + + // 从编辑表单返回时刷新 + useDidShow(() => { + load() + }) + + const goEdit = () => { + navigateTo(`${ROUTES.FAMILY_CONTACT_ADD}?familyId=${familyId}&memberId=${memberId}`) + } + + const confirmDelete = () => { + if (deleting) return + Taro.showModal({ + title: '删除关爱对象', + content: `确定删除「${member?.name ?? ''}」吗?删除后不可恢复。`, + confirmText: '删除', + confirmColor: '#EE4B4B' + }).then(async ({ confirm }) => { + if (!confirm) return + setDeleting(true) + try { + await deleteCareMember(memberId) + showToast('已删除') + setTimeout(() => navigateBack(), 500) + } catch (error) { + showToast(error instanceof Error ? error.message : '删除失败') + } finally { + setDeleting(false) + } + }) + } + + if (loading) { + return ( + + + + + ) + } + + if (!member) { + return ( + + + navigateBack()} + /> + + ) + } + + const contacts = member.contacts ?? [] + // 身份证号脱敏:保留前 4 后 4 + const maskedId = member.idNumber + ? `${member.idNumber.slice(0, 4)}**********${member.idNumber.slice(-4)}` + : '--' + + return ( + + + + + + + {member.name.slice(0, 1)} + + {member.name} + + + + + + + + + + + + 紧急联系人({contacts.length}) + {contacts.length === 0 ? ( + 未填写紧急联系人 + ) : ( + + {contacts.map((c, i) => ( + + ))} + + )} + + + + + 编辑信息 + + + {deleting ? '删除中...' : '删除'} + + + + + ) +} diff --git a/src/pages/family/detail.tsx b/src/pages/family/detail.tsx index cee8e92..3d6d125 100644 --- a/src/pages/family/detail.tsx +++ b/src/pages/family/detail.tsx @@ -123,7 +123,15 @@ export default function FamilyDetail() { ) : ( {caredList.map((c) => ( - + + navigateTo(`${ROUTES.FAMILY_CONTACT_DETAIL}?familyId=${familyId}&memberId=${c.memberId}`) + } + /> ))} )}