import { useState } from 'react' import { useRouter, useDidShow, useShareAppMessage } from '@tarojs/taro' import Taro from '@tarojs/taro' import { View, Text, Button } from '@tarojs/components' import { useAppSelector } from '@/store/hooks' import { Navbar } from '@/components/navbar' 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 { Modal } from '@/components/modal' import { getHomeDetail, getHomeMemberList, joinHome, transferOwner, updateMemberNickName, type HomeInfo, type HomeMember } from '@/api/home' import { getCareMemberList, type CareMember } from '@/api/careMember' import { MAX_CARED_PER_FAMILY } from '@/constants/family' import { ROUTES } from '@/constants/routes' import { navigateTo, reLaunch } from '@/utils/navigation' import { showToast } from '@/utils/toast' import styles from './detail.module.scss' // Taro 类型未跟进 wx.showModal 的 editable 扩展(微信 2.17.1+),运行时真实存在 interface EditableModalOption extends Taro.showModal.Option { editable?: boolean placeholderText?: string } interface EditableModalResult extends Taro.showModal.SuccessCallbackResult { content?: string } const showEditableModal = (option: EditableModalOption) => Taro.showModal(option as Taro.showModal.Option) as Promise // 成员显示名:家庭内昵称优先,未设置回落微信昵称 // 注意 2026-09-23 实测:清空昵称后端返回空串 '' 而非 null,需按空白处理 const memberName = (m: HomeMember): string => m.nickName?.trim() || m.userNickName?.trim() || '成员' // 关爱对象联系人摘要:最多显示前 2 位姓名,其余以 +N 概括;未填则不显示 const contactsSummary = (c: CareMember): string => { const list = c.contacts ?? [] if (list.length === 0) return '' const head = list.slice(0, 2).map((ct) => ct.name).join('、') const rest = list.length > 2 ? ` 等 ${list.length} 人` : '' return `${head}${rest}` } export default function FamilyDetail() { const router = useRouter() const familyId = router.params.id ?? '' const isInviteEnter = router.params.invite === '1' const token = useAppSelector((s) => s.auth.token) const myUserId = useAppSelector((s) => s.auth.userId) const [loading, setLoading] = useState(true) const [joining, setJoining] = useState(false) const [family, setFamily] = useState(null) const [members, setMembers] = useState([]) const [caredList, setCaredList] = useState([]) // 转让群主:底部入口仅群主可见,点击后弹选人 Modal const [transferOpen, setTransferOpen] = useState(false) const [transferring, setTransferring] = useState(false) // 改昵称:点击成员行触发 const [renaming, setRenaming] = useState(false) // 拉取家庭数据:详情必须成功;成员/关爱对象列表失败降级为空(受邀者尚未入圈, // 这两个接口会报权限错,不能因此把整个页面判成「家庭不存在」) // 返回本次拉到的成员列表(供 autoJoin 用最新数据判断,避免 stale state) const load = async (): Promise => { // 分享卡直达本页且未登录:先去登录,登录后回跳本页 if (!token) { const target = encodeURIComponent(`${ROUTES.FAMILY_DETAIL}?id=${familyId}&invite=1`) reLaunch(`${ROUTES.LOGIN}?redirect=${target}`) return null } setLoading(true) try { const home = await getHomeDetail(familyId) const [memberList, careList] = await Promise.all([ getHomeMemberList(familyId).catch(() => [] as HomeMember[]), getCareMemberList(familyId).catch(() => [] as CareMember[]) ]) setFamily(home) setMembers(memberList) // 上限判定以关爱对象实际列表为准,家庭卡计数仅展示 setCaredList(careList) return memberList } catch { setFamily(null) return null } finally { setLoading(false) } } // 从添加表单返回时刷新;受邀进入且尚未是成员时自动加入(跳过手动点击步骤) useDidShow(() => { load().then((memberList) => { if ( !isInviteEnter || joining || myUserId == null || !memberList || memberList.length === 0 || memberList.some((m) => m.memberId === myUserId) ) return autoJoin(familyId) }) }) // 受邀直达:已登录但非成员时自动加入,无需手点 const autoJoin = async (homeId: string) => { if (joining) return setJoining(true) try { await joinHome(homeId) showToast('已加入家庭') await load() } catch (e) { // 加入失败(如接口异常)也要让页面可退出:详情能拉到就展示,拉不到走「家庭不存在」分支(带返回按钮) showToast(e instanceof Error ? e.message : '加入失败') if (!family) await load() } finally { setJoining(false) } } // 邀请分享:接收方点分享卡直达本页,登录后自动加入 const inviterName = () => { const me = members.find((m) => m.memberId === myUserId) return memberName(me ?? members[0]) } useShareAppMessage(() => ({ title: `${inviterName()} 邀请你加入家庭圈「${family?.name ?? ''}」`, path: `${ROUTES.FAMILY_DETAIL}?id=${familyId}&invite=1` })) const addCared = () => { navigateTo(`${ROUTES.FAMILY_CONTACT_ADD}?familyId=${familyId}`) } // 我在成员列表里的角色:转让入口只对群主开放 const myMember = members.find((m) => m.memberId === myUserId) ?? null const isOwner = myMember?.role === 'OWNER' // 注意:成员响应无 userId 字段(2026-09-23 实测),memberId 是家庭圈成员记录 ID, // 与登录 userId 不同源,isOwner 恒为 false——仅作未来后端补字段后的预留,勿用于门禁 // 转让/改昵称入口全开:前端无法可靠识别自己,由后端权威校验 // (transferOwner 实测已有 400「不能转让给自己」「成员不存在」等完整校验) const openTransfer = () => { if (transferring) return setTransferOpen(true) } const closeTransfer = () => { if (transferring) return setTransferOpen(false) } const doTransfer = async (target: HomeMember) => { if (transferring) return setTransferring(true) try { await transferOwner({ memberId: target.memberId }) showToast('已转让群主') setTransferOpen(false) await load() } catch (e) { showToast(e instanceof Error ? e.message : '转让失败') } finally { setTransferring(false) } } // 点击成员行改昵称:wx.showModal editable 输入框,预填当前昵称 const renameMember = async (target: HomeMember) => { if (renaming) return setRenaming(true) try { const { confirm, content } = await showEditableModal({ title: '修改群昵称', editable: true, placeholderText: '请输入新的昵称', content: target.nickName ?? '' }) if (!confirm) return const next = (content ?? '').trim() if (!next) { showToast('昵称不能为空') return } if (next === (target.nickName ?? '').trim()) { showToast('昵称未变化') return } await updateMemberNickName({ memberId: target.memberId, nickName: next }) showToast('已修改') await load() } catch (e) { showToast(e instanceof Error ? e.message : '修改失败') } finally { setRenaming(false) } } if (loading) { return ( ) } if (!family) { return ( load()} /> {/* 分享卡直达时页面栈只有本页,navigateBack 无处可退会卡死——退回首页 */} reLaunch(ROUTES.HOME)}> 回首页 ) } const caredCount = caredList.length const canAdd = caredCount < MAX_CARED_PER_FAMILY const stub = (tip: string) => () => showToast(tip) return ( {/* 添加关爱对象卡:达上限后整卡隐藏(占位无意义),上限信息改由标题计数表达 */} {canAdd && ( + + 最多可添加 {MAX_CARED_PER_FAMILY} 人 )} 关爱对象 {caredCount}/{MAX_CARED_PER_FAMILY} {caredCount === 0 ? ( 暂无关爱对象,点击上方卡片添加 ) : ( {caredList.map((c) => ( navigateTo(`${ROUTES.FAMILY_CONTACT_DETAIL}?familyId=${familyId}&memberId=${c.memberId}`) } /> ))} )} 家庭成员 {members.length === 0 ? ( 暂无其他成员 ) : ( {members.map((m) => ( renameMember(m)} /> ))} )} {/* open-type=share 的 Button 铺满整行做命中区,样式与普通 ListRow 一致 */} {/* 转让群主选人弹层:全部成员可选(含自己),后端对「转让给自己」返回 400 由 toast 透出 */} 转让群主给谁? {members.length === 0 ? ( 暂无可转让的成员 ) : ( members.map((m) => ( doTransfer(m)} > {memberName(m)} {m.role === 'OWNER' ? '群主' : '成员'} · 群昵称:{m.nickName || '未设置'} )) )} 取消 ) }