feat(family): 关爱对象详情页(查看/删除/编辑),列表行接入跳转
- 新建 pages/family/contact/detail:getCareMemberDetail 展示姓名/身份证脱敏/地址/紧急联系人,删除走确认弹窗 + deleteCareMember - add 页支持编辑模式:memberId 参数预填原数据,save 带 memberId 走修改 - 家庭详情页关爱对象行加箭头跳转详情
This commit is contained in:
parent
2832053569
commit
a1901b5611
|
|
@ -12,6 +12,7 @@ export default {
|
|||
'pages/family/index',
|
||||
'pages/family/detail',
|
||||
'pages/family/contact/add',
|
||||
'pages/family/contact/detail',
|
||||
'pages/event/index'
|
||||
],
|
||||
window: {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View className={styles.page}>
|
||||
<Navbar title={isEdit ? '编辑关爱对象' : '添加关爱对象'} variant="hero" />
|
||||
<View className={styles.stateWrap}><Loading /></View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={styles.page}>
|
||||
<Navbar title="添加关爱对象" variant="hero" />
|
||||
<Navbar title={isEdit ? '编辑关爱对象' : '添加关爱对象'} variant="hero" />
|
||||
|
||||
<View className={styles.card}>
|
||||
<View className={styles.formRow}>
|
||||
|
|
@ -151,7 +185,7 @@ export default function ContactAdd() {
|
|||
hoverClass={submitting ? 'none' : styles.submitPressed}
|
||||
onClick={submit}
|
||||
>
|
||||
<Text className={styles.submitText}>{submitting ? '提交中...' : '确认添加'}</Text>
|
||||
<Text className={styles.submitText}>{submitting ? '提交中...' : isEdit ? '保存修改' : '确认添加'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<CareMember | null>(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 (
|
||||
<View className={styles.page}>
|
||||
<Navbar title="关爱对象详情" variant="hero" />
|
||||
<View className={styles.stateWrap}><Loading /></View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
if (!member) {
|
||||
return (
|
||||
<View className={styles.page}>
|
||||
<Navbar title="关爱对象详情" variant="hero" />
|
||||
<StateView
|
||||
status="error"
|
||||
mode="list"
|
||||
text="该关爱对象不存在"
|
||||
retryText="返回"
|
||||
onRetry={() => navigateBack()}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const contacts = member.contacts ?? []
|
||||
// 身份证号脱敏:保留前 4 后 4
|
||||
const maskedId = member.idNumber
|
||||
? `${member.idNumber.slice(0, 4)}**********${member.idNumber.slice(-4)}`
|
||||
: '--'
|
||||
|
||||
return (
|
||||
<View className={styles.page}>
|
||||
<Navbar title="关爱对象详情" variant="hero" />
|
||||
|
||||
<View className={styles.body}>
|
||||
<View className={styles.profile}>
|
||||
<View className={styles.avatar}>
|
||||
<Text className={styles.avatarText}>{member.name.slice(0, 1)}</Text>
|
||||
</View>
|
||||
<Text className={styles.name}>{member.name}</Text>
|
||||
</View>
|
||||
|
||||
<View className={styles.section}>
|
||||
<ListRowGroup>
|
||||
<ListRow label="姓名" value={member.name} />
|
||||
<ListRow label="身份证号" value={maskedId} />
|
||||
<ListRow label="关爱地址" value={member.address || '--'} />
|
||||
</ListRowGroup>
|
||||
</View>
|
||||
|
||||
<View className={styles.section}>
|
||||
<Text className={styles.sectionTitle}>紧急联系人({contacts.length})</Text>
|
||||
{contacts.length === 0 ? (
|
||||
<Text className={styles.sectionEmpty}>未填写紧急联系人</Text>
|
||||
) : (
|
||||
<ListRowGroup>
|
||||
{contacts.map((c, i) => (
|
||||
<ListRow key={`${c.name}-${i}`} label={`${i + 1}. ${c.name}`} value={c.relationShip ? `${c.relationShip} · ${c.phone}` : c.phone} />
|
||||
))}
|
||||
</ListRowGroup>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View className={styles.btnRow}>
|
||||
<View className={styles.editBtn} hoverClass={styles.btnPressed} onClick={goEdit}>
|
||||
<Text className={styles.editBtnText}>编辑信息</Text>
|
||||
</View>
|
||||
<View
|
||||
className={styles.delBtn}
|
||||
hoverClass={deleting ? 'none' : styles.btnPressed}
|
||||
onClick={deleting ? undefined : confirmDelete}
|
||||
>
|
||||
<Text className={styles.delBtnText}>{deleting ? '删除中...' : '删除'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
@ -123,7 +123,15 @@ export default function FamilyDetail() {
|
|||
) : (
|
||||
<ListRowGroup>
|
||||
{caredList.map((c) => (
|
||||
<ListRow key={c.memberId} label={c.name} value={contactsSummary(c)} />
|
||||
<ListRow
|
||||
key={c.memberId}
|
||||
label={c.name}
|
||||
value={contactsSummary(c)}
|
||||
arrow
|
||||
onClick={() =>
|
||||
navigateTo(`${ROUTES.FAMILY_CONTACT_DETAIL}?familyId=${familyId}&memberId=${c.memberId}`)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ListRowGroup>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue