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/index',
|
||||||
'pages/family/detail',
|
'pages/family/detail',
|
||||||
'pages/family/contact/add',
|
'pages/family/contact/add',
|
||||||
|
'pages/family/contact/detail',
|
||||||
'pages/event/index'
|
'pages/event/index'
|
||||||
],
|
],
|
||||||
window: {
|
window: {
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ export const ROUTES = Object.freeze({
|
||||||
FAMILY: '/pages/family/index',
|
FAMILY: '/pages/family/index',
|
||||||
FAMILY_DETAIL: '/pages/family/detail',
|
FAMILY_DETAIL: '/pages/family/detail',
|
||||||
FAMILY_CONTACT_ADD: '/pages/family/contact/add',
|
FAMILY_CONTACT_ADD: '/pages/family/contact/add',
|
||||||
|
FAMILY_CONTACT_DETAIL: '/pages/family/contact/detail',
|
||||||
SCENE_SELECT: '/pages/scene/select'
|
SCENE_SELECT: '/pages/scene/select'
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,13 @@
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stateWrap {
|
||||||
|
margin-top: 80px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.card,
|
.card,
|
||||||
.contactsCard {
|
.contactsCard {
|
||||||
margin: 24px 24px 0;
|
margin: 24px 24px 0;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter } from '@tarojs/taro'
|
import { useRouter, useDidShow } 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 { 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 { 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'
|
||||||
|
|
@ -17,16 +18,39 @@ const emptyContact = (): (CareContact & { tmpId: string }) => ({
|
||||||
relationShip: ''
|
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() {
|
export default function ContactAdd() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const familyId = router.params.familyId ?? ''
|
const familyId = router.params.familyId ?? ''
|
||||||
|
const memberId = router.params.memberId ?? ''
|
||||||
|
const isEdit = memberId !== ''
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(isEdit)
|
||||||
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<(CareContact & { tmpId: string })[]>([])
|
const [contacts, setContacts] = useState<(CareContact & { tmpId: string })[]>([])
|
||||||
const [submitting, setSubmitting] = useState(false)
|
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) => {
|
const patchContact = (id: string, key: keyof CareContact, value: string) => {
|
||||||
setContacts((prev) => prev.map((c) => (c.tmpId === id ? { ...c, [key]: value } : c)))
|
setContacts((prev) => prev.map((c) => (c.tmpId === id ? { ...c, [key]: value } : c)))
|
||||||
}
|
}
|
||||||
|
|
@ -46,13 +70,14 @@ export default function ContactAdd() {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
await saveCareMember({
|
await saveCareMember({
|
||||||
|
memberId: memberId || undefined,
|
||||||
homeId: familyId,
|
homeId: familyId,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
idNumber: idNumber.trim(),
|
idNumber: idNumber.trim(),
|
||||||
address: address.trim(),
|
address: address.trim(),
|
||||||
contacts: result.contacts
|
contacts: result.contacts
|
||||||
})
|
})
|
||||||
showToast('添加成功')
|
showToast(isEdit ? '修改成功' : '添加成功')
|
||||||
setTimeout(() => navigateBack(), 500)
|
setTimeout(() => navigateBack(), 500)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(error instanceof Error ? error.message : '添加失败')
|
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 (
|
return (
|
||||||
<View className={styles.page}>
|
<View className={styles.page}>
|
||||||
<Navbar title="添加关爱对象" variant="hero" />
|
<Navbar title={isEdit ? '编辑关爱对象' : '添加关爱对象'} variant="hero" />
|
||||||
|
|
||||||
<View className={styles.card}>
|
<View className={styles.card}>
|
||||||
<View className={styles.formRow}>
|
<View className={styles.formRow}>
|
||||||
|
|
@ -151,7 +185,7 @@ export default function ContactAdd() {
|
||||||
hoverClass={submitting ? 'none' : styles.submitPressed}
|
hoverClass={submitting ? 'none' : styles.submitPressed}
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
>
|
>
|
||||||
<Text className={styles.submitText}>{submitting ? '提交中...' : '确认添加'}</Text>
|
<Text className={styles.submitText}>{submitting ? '提交中...' : isEdit ? '保存修改' : '确认添加'}</Text>
|
||||||
</View>
|
</View>
|
||||||
</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>
|
<ListRowGroup>
|
||||||
{caredList.map((c) => (
|
{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>
|
</ListRowGroup>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue