完成50%

This commit is contained in:
2026-07-23 08:23:59 +08:00
parent 9b0ad62df4
commit f1edc6b533
218 changed files with 24318 additions and 5514 deletions
+658 -33
View File
@@ -1,3 +1,8 @@
import {
GENEALOGY_ACCESS_PRESET,
getGenealogyAccessPresetLabel,
} from '../utils/genealogy-contracts.js'
export const currentUser = {
id: 1,
name: '汤文远',
@@ -7,7 +12,7 @@ export const currentUser = {
export const genealogies = [
{
id: 1001,
id: '1001',
surname: '汤',
name: '汤氏家谱',
hall: '敦睦堂',
@@ -15,12 +20,17 @@ export const genealogies = [
memberCount: 158,
updatedAt: '2024-05-12',
activeCount: 8,
visibility: '仅成员可见',
accessPreset: GENEALOGY_ACCESS_PRESET.MEMBER_ONLY,
membership: 'created',
motto: '敦亲睦族,敬祖传家。'
motto: '敦亲睦族,敬祖传家。',
ancestorName: '汤文远',
parentName: '汤氏中华总谱',
branchName: '洛阳主支',
source: '由洛阳汤氏族人整理并维护',
publicDescription: '公开展示家谱身份、地区、堂号与支系信息。'
},
{
id: 1002,
id: '1002',
surname: '汤',
name: '汤氏宗谱',
hall: '承志堂',
@@ -28,43 +38,658 @@ export const genealogies = [
memberCount: 286,
updatedAt: '2024-04-28',
activeCount: 15,
visibility: '公开可申请',
accessPreset: GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY,
membership: 'joined',
motto: '继往开来,世守家风。'
motto: '继往开来,世守家风。',
ancestorName: '汤正明',
parentName: '汤氏鲁西总谱',
branchName: '济宁主支',
source: '由济宁汤氏族人整理并维护',
publicDescription: '公开展示家谱身份、地区、堂号与支系信息。'
}
]
export const treeMembers = [
{ id: 1, name: '汤文远', relation: '始祖', generation: 1, years: '1940—2012', branch: '主支', x: 50, y: 7 },
{ id: 2, name: '汤正国', relation: '长子', generation: 2, years: '1965—', branch: '主支', x: 20, y: 38 },
{ id: 3, name: '汤正华', relation: '次子', generation: 2, years: '1968—', branch: '主支', x: 50, y: 38 },
{ id: 4, name: '汤正强', relation: '三子', generation: 2, years: '1972—', branch: '支系', x: 80, y: 38 },
{ id: 5, name: '汤凯', relation: '长孙', generation: 3, years: '1992—', branch: '主支', x: 16, y: 70 },
{ id: 6, name: '汤', relation: '长女', generation: 3, years: '1995—', branch: '主支', x: 35, y: 70 },
{ id: 7, name: '汤晨', relation: '次子', generation: 3, years: '1998—', branch: '主支', x: 54, y: 70 },
{ id: 8, name: '汤昊', relation: '三子', generation: 3, years: '2001—', branch: '支系', x: 73, y: 70 },
{ id: 9, name: '汤宁', relation: '四女', generation: 3, years: '2005—', branch: '支系', x: 90, y: 70 }
// G01、G05、G06 与 G08 共用这一份家谱展示夹具。成员关系只在
// genealogies 和本地创建预览中出现;公开搜索结果绝不能据此获得管理权限。
const toPublicProjection = ({ membership: _membership, ...genealogy }) => genealogy
export const publicGenealogies = [
{
id: '2001',
surname: '汤',
name: '汤氏南阳宗谱',
hall: '敦睦堂',
location: '河南·南阳',
parentName: '汤氏中华总谱',
branchName: '南阳主支',
manager: '管理员 汤文礼',
certification: '资料已认证',
memberCount: 428,
activeCount: 316,
updatedAt: '2026-07-12',
relation: 'available',
source: '由南阳汤氏族人整理并维护',
publicDescription: '公开展示家谱身份、地区、堂号与支系信息;成员资料和世系详情仅向已加入成员开放。',
motto: '敦亲睦族,敬祖传家。',
accessPreset: GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY,
ancestorName: '汤文远'
},
{
...toPublicProjection(genealogies[1]),
manager: '管理员 汤文远',
certification: '资料已认证',
relation: 'joined'
},
{
id: '2003',
surname: '汤',
name: '汤氏济宁宗谱',
hall: '敬宗堂',
location: '山东·济宁',
parentName: '汤氏鲁西总谱',
branchName: '济宁主支',
manager: '管理员 汤正明',
certification: '管理员已实名',
memberCount: 286,
updatedAt: '2026-07-08',
relation: 'pending',
accessPreset: GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY,
},
{
id: '2004',
surname: '汤',
name: '汤氏清河家谱',
hall: '思源堂',
location: '山东·临清',
parentName: '汤氏鲁西总谱',
branchName: '清河支系',
manager: '管理员 汤志成',
certification: '资料已认证',
memberCount: 96,
updatedAt: '2026-07-05',
relation: 'rejected',
accessPreset: GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY,
},
{
id: '2005',
surname: '汤',
name: '汤氏汝南支谱',
hall: '崇本堂',
location: '河南·驻马店',
parentName: '汤氏中原总谱',
branchName: '汝南三支',
manager: '管理员 汤国安',
certification: '管理员已实名',
memberCount: 72,
updatedAt: '2026-07-02',
relation: 'removed',
accessPreset: GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY,
},
{
...toPublicProjection(genealogies[0]),
manager: '创建者 当前用户',
certification: '资料待完善',
relation: 'owned'
}
]
export const familyFeeds = [
{ id: 1, author: '汤正华', title: '清明祭祖通知', content: '本周六上午举行清明祭祖,敬请各支系族亲相互转告。', date: '今天 09:30', type: '族务' },
{ id: 2, author: '汤', title: '老宅修缮旧影', content: '整理出一组敦睦堂老宅照片,已收入家族相册。', date: '昨天 20:18', type: '相册' }
]
export const familyContent = {
article: [{ id: 1, title: '敦睦堂家训摘录', summary: '孝友传家,勤俭立业,敬祖睦族。', date: '2025年4月' }],
album: [{ id: 1, title: '敦睦堂老宅旧影', summary: '收录修缮前后的珍贵照片,共 6 张。', date: '2025年3月' }],
ceremony: [{ id: 1, title: '清明祭祖', summary: '本周六上午举行祭祖仪式,请族人相互转告。', date: '3 天后' }],
record: [{ id: 1, title: '老宅修缮备忘', summary: '屋脊木构加固方案已确认,等待施工。', date: '今天' }]
const localCreatedGenealogy = {
id: 'local-created-genealogy',
surname: '汤',
name: '本地创建预览',
hall: '堂号待补',
location: '所在地待补',
memberCount: 1,
activeCount: 1,
updatedAt: '尚未同步',
accessPreset: GENEALOGY_ACCESS_PRESET.MEMBER_ONLY,
localPreview: true,
motto: '当前仅为本地流程预览,尚未提交服务器。',
ancestorName: '待补',
parentName: '无上级谱',
branchName: '主支',
source: '本地创建流程预览',
publicDescription: '尚未同步到服务器。'
}
export const notifications = [
{ id: 1, title: '有新的入谱申请', content: '陈先生申请加入四川武胜汤氏族谱,等待你的审核。', time: '10:24', unread: true },
{ id: 2, title: '祭祀活动提醒', content: '清明祭祖将在 3 天后开始。', time: '昨天', unread: true },
{ id: 3, title: '相册有新照片', content: '汤悦上传了 6 张老宅修缮照片。', time: '4月9日', unread: false }
const localGenealogyPreviews = new Map([
[localCreatedGenealogy.id, localCreatedGenealogy]
])
let localPreviewSequence = 0
const buildLocalGenealogyPreview = (id, draft, previous = {}) => {
const accessPreset = Object.values(GENEALOGY_ACCESS_PRESET).includes(draft.accessPreset)
? draft.accessPreset
: GENEALOGY_ACCESS_PRESET.MEMBER_ONLY
return {
...previous,
id,
surname: String(draft.surname || '').trim(),
name: String(draft.name || '').trim(),
hall: String(draft.hall || '').trim() || '堂号待补',
location: String(draft.location || '').trim(),
memberCount: 1,
activeCount: 1,
updatedAt: '尚未同步',
accessPreset,
localPreview: true,
motto: '当前仅为本地流程预览,尚未提交服务器。',
ancestorName: previous.ancestorName || '待补',
parentName: '无上级谱',
branchName: '主支',
source: '本地创建流程预览',
publicDescription: `尚未同步到服务器;访问规则为“${getGenealogyAccessPresetLabel(accessPreset)}”。`
}
}
// G03 与 G05 共用这一份临时预览 owner。它只在当前运行实例中保存用户刚提交的
// 快照,不建立成员关系、不写持久存储,也不冒充后端创建结果。
export const createLocalGenealogyPreview = (draft) => {
localPreviewSequence += 1
const id = `local-created-${Date.now().toString(36)}-${localPreviewSequence}`
localGenealogyPreviews.set(id, buildLocalGenealogyPreview(id, draft))
return id
}
export const updateLocalGenealogyPreview = (genealogyId, draft) => {
const normalizedId = String(genealogyId || '')
const preview = localGenealogyPreviews.get(normalizedId)
if (!preview) return null
localGenealogyPreviews.set(
normalizedId,
buildLocalGenealogyPreview(normalizedId, draft, preview)
)
return normalizedId
}
export const updateLocalGenealogyPreviewAncestor = (genealogyId, draft) => {
const normalizedId = String(genealogyId || '')
const preview = localGenealogyPreviews.get(normalizedId)
if (!preview) return null
const updated = {
...preview,
ancestorName: String(draft.personName || '').trim() || '待补',
ancestor: Object.freeze({ ...draft })
}
localGenealogyPreviews.set(normalizedId, updated)
return updated
}
export const removeLocalGenealogyPreview = (genealogyId) => {
const normalizedId = String(genealogyId || '')
if (!normalizedId || normalizedId === localCreatedGenealogy.id) return false
return localGenealogyPreviews.delete(normalizedId)
}
export const findGenealogyFixture = (genealogyId) => {
const normalizedId = String(genealogyId || '')
return [...genealogies, ...publicGenealogies]
.find((item) => String(item.id) === normalizedId) ||
localGenealogyPreviews.get(normalizedId) || null
}
// 这里只解析本地视觉夹具的显示角色,不代表后端授权。成员关系仍由
// genealogies[*].membership 唯一持有;localPreview 只能进入无业务入口的预览态。
export const getGenealogyFixtureAccess = (genealogyId) => {
const normalizedId = String(genealogyId || '')
const fixture = findGenealogyFixture(normalizedId)
const memberFixture = genealogies.find((item) => item.id === normalizedId)
const publicFixture = publicGenealogies.find((item) => item.id === normalizedId)
const access = fixture?.localPreview
? 'preview'
: memberFixture?.membership === 'created'
? 'owner'
: memberFixture?.membership === 'joined'
? 'member'
: 'public'
const relation = fixture?.localPreview
? 'preview'
: memberFixture?.membership === 'created'
? 'owned'
: memberFixture?.membership === 'joined'
? 'joined'
: publicFixture?.relation || 'unknown'
return Object.freeze({
viewMode: access === 'public' || access === 'preview' ? access : 'member',
accessRole: access === 'owner' || access === 'member' ? access : 'guest',
relation,
canView:
Boolean(fixture?.localPreview) ||
access === 'owner' ||
access === 'member' ||
fixture?.accessPreset === GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY,
canApply:
fixture?.accessPreset === GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY &&
['available', 'rejected', 'removed'].includes(relation)
})
}
// 搜索结果的公开性与操作资格消费同一访问预设。成员可看到自己已经加入或
// 创建的私密家谱;陌生账号只能看到明确为 PUBLIC_APPLY 的投影。
export const isGenealogySearchVisible = (genealogyId) => {
const fixture = findGenealogyFixture(genealogyId)
if (!fixture) return false
const access = getGenealogyFixtureAccess(genealogyId)
return (
access.accessRole !== 'guest' ||
fixture.accessPreset === GENEALOGY_ACCESS_PRESET.PUBLIC_APPLY
)
}
// T01、T03—T08 与旧 mock API 共用这一份成员夹具。utils/api.js 是当前阶段
// 唯一允许写入裸数组的模块;页面必须通过下方查询函数取得深拷贝,避免页面
// 表单或关系投影反向污染世系树和其他页面。
export const treeMembers = [
{
id: '101', appUserId: '2001', genealogyId: '1001', parentId: '', name: '汤文远', relation: '始祖',
generation: 12, generationName: '文字辈', branch: '主支', years: '1940—2012',
birthDate: '1940-03-01', deathDate: '2012-08-16', birthplace: '河南南阳',
status: 'deceased', summary: '一生敦亲睦族,参与整理家族旧谱。',
note: '始祖 · 档案完整',
relatives: [
{ personId: '102', relation: '长子' },
{ personId: '103', relation: '次子' }
]
},
{
id: '102', appUserId: '2002', genealogyId: '1001', parentId: '101', name: '汤正国', relation: '长子',
generation: 13, generationName: '正字辈', branch: '长房', years: '1965—',
birthDate: '1965-05-12', deathDate: '', birthplace: '河南洛阳',
status: 'privacy', summary: '负责长房资料核对。',
note: '家谱管理员',
relatives: [
{ personId: '101', relation: '父亲' },
{ personId: '104', relation: '长子' },
{ personId: '105', relation: '女儿' }
]
},
{
id: '103', appUserId: '2003', genealogyId: '1001', parentId: '101', name: '汤正华', relation: '次子',
generation: 13, generationName: '正字辈', branch: '二房', years: '资料受限',
birthDate: '1968-09-03', deathDate: '', birthplace: '河南洛阳',
status: 'forbidden', summary: '资料仍在补充。',
note: '资料待补充',
relatives: [
{ personId: '101', relation: '父亲' },
{ personId: '106', relation: '长子' }
]
},
{
id: '104', appUserId: '2004', genealogyId: '1001', parentId: '102', name: '汤凯', relation: '长孙',
generation: 14, generationName: '凯字辈', branch: '长房', years: '1992—',
birthDate: '1992-06-01', deathDate: '', birthplace: '河南洛阳',
status: 'privacy', summary: '协助整理年轻一代成员资料。',
note: '档案已核对',
relatives: [{ personId: '102', relation: '父亲' }]
},
{
id: '105', appUserId: '2005', genealogyId: '1001', parentId: '102', name: '汤悦', relation: '长孙女',
generation: 14, generationName: '凯字辈', branch: '长房', years: '1995—',
birthDate: '1995-04-18', deathDate: '', birthplace: '河南洛阳',
status: 'privacy', summary: '参与家族影像与口述资料整理。',
note: '档案已核对',
relatives: [{ personId: '102', relation: '父亲' }]
},
{
id: '106', appUserId: '2006', genealogyId: '1001', parentId: '103', name: '汤晨', relation: '次孙',
generation: 14, generationName: '凯字辈', branch: '二房', years: '1998—',
birthDate: '1998-11-09', deathDate: '', birthplace: '河南洛阳',
status: 'privacy', summary: '二房成员资料已完成初步核对。',
note: '档案已核对',
relatives: [{ personId: '103', relation: '父亲' }]
}
]
export const joinApplications = [
{ id: 1, name: '汤志成', phone: '139****6421', relation: '自述为汤正华堂侄', appliedAt: '今天 10:24', status: 'PENDING' },
{ id: 2, name: '汤雨薇', phone: '136****2798', relation: '自述为汤正国之女', appliedAt: '昨天 18:02', status: 'PENDING' }
const cloneTreeMemberFixture = (member) => ({
...member,
relatives: member.relatives.map((relative) => ({ ...relative }))
})
export const listTreeMemberFixtures = (genealogyId) => {
const normalizedGenealogyId = String(genealogyId || '')
if (!normalizedGenealogyId) return []
return treeMembers
.filter((member) => member.genealogyId === normalizedGenealogyId)
.map(cloneTreeMemberFixture)
}
export const findTreeMemberFixture = (genealogyId, personId) => {
const normalizedGenealogyId = String(genealogyId || '')
const normalizedPersonId = String(personId || '')
if (!normalizedGenealogyId || !normalizedPersonId) return null
const member = treeMembers.find(
(item) => item.genealogyId === normalizedGenealogyId && item.id === normalizedPersonId
)
return member ? cloneTreeMemberFixture(member) : null
}
// 页面展示只能消费这个受控投影。privacy/forbidden 成员在 owner 边界即删除
// 生平、居住地、支系、亲属等字段,避免组件先取得完整对象再依赖模板隐藏。
const projectTreeMemberPresentation = (member) => {
if (!['privacy', 'forbidden'].includes(member.status)) {
return cloneTreeMemberFixture(member)
}
return {
id: member.id,
appUserId: member.appUserId,
genealogyId: member.genealogyId,
name: member.name,
relation: member.relation,
generation: member.generation,
status: member.status
}
}
export const listTreeMemberPresentationFixtures = (genealogyId) => {
const normalizedGenealogyId = String(genealogyId || '')
if (!normalizedGenealogyId) return []
return treeMembers
.filter((member) => member.genealogyId === normalizedGenealogyId)
.map(projectTreeMemberPresentation)
}
export const findTreeMemberPresentationFixture = (genealogyId, personId) => {
const normalizedGenealogyId = String(genealogyId || '')
const normalizedPersonId = String(personId || '')
if (!normalizedGenealogyId || !normalizedPersonId) return null
const member = treeMembers.find(
(item) => item.genealogyId === normalizedGenealogyId && item.id === normalizedPersonId
)
return member ? projectTreeMemberPresentation(member) : null
}
// F01—F09 共用这一组只读内容夹具。家谱 ID 与实体 ID 共同构成身份;页面和
// mock API 只能通过下方 list/find 查询取得深拷贝,不能把页面草稿写回正式列表。
const familyFeeds = [
{
id: '1', genealogyId: '1001', tag: '团圆记忆', time: '今天 10:24',
title: '端午家宴', content: '今年端午全家相聚,长辈讲起祖居旧事,孩子们也为大家拍下了新的全家福。饭后我们把照片和口述片段整理进家族档案,让这份热闹成为往后仍能翻看的共同记忆。',
author: '汤正国',
comments: [
{ id: '1', author: '汤淑华', time: '今天 10:42', content: '一家人能常常相聚,就是最珍贵的福气。' },
{ id: '2', author: '汤文清', time: '今天 11:08', content: '照片已经整理好了,晚些时候放进春节团圆相册。' }
]
},
{
id: '2', genealogyId: '1001', tag: '家族通知', time: '昨天 18:02',
title: '修谱资料征集', content: '请家人补充老照片中的人物姓名、拍摄时间和地点。无法确认的信息也可以先写下线索,由熟悉往事的长辈共同核对。',
author: '谱主', comments: []
}
]
const familyArticles = [
{
id: '101', genealogyId: '1001', category: '家风家训', title: '孝友传家的日常',
summary: '从敬老、睦亲与守信的小事里,看见家风如何代代相传。', author: '汤文正',
updatedAt: '2024 年 5 月 12 日',
paragraphs: [
'孝友传家,不只在族谱序言里,也在一家人每日的言行中。长辈以宽厚待晚辈,晚辈以耐心照料长辈,亲友之间守信互助,便是最朴素也最长久的家风。',
'勤俭并非一味节省,而是珍惜所得、量入为出,也愿意在家人需要时伸出援手。家中每一代人都可以用自己的方式,把这份分寸与担当继续传下去。',
'敬祖睦宗,最终是为了让今天的家人彼此认识、彼此关心。记录姓名与世代之外,也应留下真实的生活、共同经历和温暖记忆。'
]
},
{
id: '102', genealogyId: '1001', category: '家族往事', title: '祖居门前的那棵桂花树',
summary: '长辈口述的旧居记忆,以及每年中秋一家人相聚的故事。', author: '汤淑华',
updatedAt: '2024 年 5 月 10 日',
paragraphs: [
'祖居门前曾有一棵桂花树。每到中秋,院里都是清甜的香气,远道回来的家人也总能循着那股味道找到家门。',
'后来房屋几经修缮,桂花树仍被大家小心保留下来。它见过孩子长大,也见过长辈把往事一遍遍讲给后来人。'
]
},
{
id: '103', genealogyId: '1001', category: '族谱序言', title: '续修族谱序',
summary: '说明本次续修的缘起、资料来源与共同参与的家人。', author: '谱主',
updatedAt: '2024 年 5 月 8 日',
paragraphs: [
'本次续修以旧谱、碑记、户籍资料和长辈口述为基础,由家人共同核对补充。凡暂不能确认之处,均保留来源和疑问,留待后续查证。',
'愿这份记录不仅理清世系,也能保存家风、人物与共同记忆。'
]
}
]
const familyAlbums = [
{
id: '201', genealogyId: '1001', name: '2024 春节团圆', updatedAt: '今天更新',
description: '三代家人的团圆饭与院前合影', cover: '/static/assets/modules/family/f08/f08-reunion-hero.png',
photos: [
{ id: '20101', src: '/static/assets/modules/family/f08/f08-reunion-hero.png', alt: '春节团圆时三代家人的合影', caption: '除夕团圆 · 2024' },
{ id: '20102', src: '/static/assets/modules/family/f08/f08-family-portrait.png', alt: '家人在院落前的春节合影', caption: '院前合影 · 2024' },
{ id: '20103', src: '/static/assets/modules/family/f08/f08-reunion-table.png', alt: '家人围坐吃年夜饭', caption: '围桌守岁 · 2024' },
{ id: '20104', src: '/static/assets/modules/family/f08/f08-ancestral-home.png', alt: '祖居院落的复古旧照', caption: '祖居旧影 · 1968' },
{ id: '20105', src: '/static/assets/modules/family/f08/f08-ancestral-portrait.png', alt: '老一辈家人在祖居门前的合影', caption: '门前合影 · 1972' }
]
},
{
id: '202', genealogyId: '1001', name: '祖居旧影', updatedAt: '5 月 10 日更新',
description: '祖居、旧物与长辈珍藏的老照片', cover: '/static/assets/modules/family/f08/f08-ancestral-home.png',
photos: [
{ id: '20201', src: '/static/assets/modules/family/f08/f08-ancestral-home.png', alt: '祖居院落的复古旧照', caption: '祖居旧影 · 1968' },
{ id: '20202', src: '/static/assets/modules/family/f08/f08-ancestral-portrait.png', alt: '老一辈家人在祖居门前的合影', caption: '门前合影 · 1972' }
]
},
{
id: '203', genealogyId: '1001', name: '儿童成长', updatedAt: '持续更新',
description: '记录孩子们每一个值得珍藏的瞬间', cover: '/static/assets/modules/family/f08/f08-family-portrait.png',
photos: [
{ id: '20301', src: '/static/assets/modules/family/f08/f08-family-portrait.png', alt: '家人在院落前的春节合影', caption: '院前合影 · 2024' }
]
}
]
const cloneFamilyFeedFixture = (feed) => ({
...feed,
comments: feed.comments.map((comment) => ({ ...comment }))
})
const cloneFamilyArticleFixture = (article) => ({
...article,
paragraphs: [...article.paragraphs]
})
const cloneFamilyAlbumFixture = (album) => ({
...album,
photoCount: album.photos.length,
photos: album.photos.map((photo) => ({ ...photo }))
})
const listScopedFamilyFixtures = (items, clone, genealogyId) => {
const normalizedGenealogyId = String(genealogyId || '')
if (!normalizedGenealogyId) return []
return items
.filter((item) => item.genealogyId === normalizedGenealogyId)
.map(clone)
}
const findScopedFamilyFixture = (items, clone, genealogyId, entityId) => {
const normalizedGenealogyId = String(genealogyId || '')
const normalizedEntityId = String(entityId || '')
if (!normalizedGenealogyId || !normalizedEntityId) return null
const item = items.find(
(candidate) => candidate.genealogyId === normalizedGenealogyId && candidate.id === normalizedEntityId
)
return item ? clone(item) : null
}
export const listFamilyFeedFixtures = (genealogyId) =>
listScopedFamilyFixtures(familyFeeds, cloneFamilyFeedFixture, genealogyId)
export const findFamilyFeedFixture = (genealogyId, feedId) =>
findScopedFamilyFixture(familyFeeds, cloneFamilyFeedFixture, genealogyId, feedId)
export const listFamilyArticleFixtures = (genealogyId) =>
listScopedFamilyFixtures(familyArticles, cloneFamilyArticleFixture, genealogyId)
export const findFamilyArticleFixture = (genealogyId, articleId) =>
findScopedFamilyFixture(familyArticles, cloneFamilyArticleFixture, genealogyId, articleId)
export const listFamilyAlbumFixtures = (genealogyId) =>
listScopedFamilyFixtures(familyAlbums, cloneFamilyAlbumFixture, genealogyId)
export const findFamilyAlbumFixture = (genealogyId, albumId) =>
findScopedFamilyFixture(familyAlbums, cloneFamilyAlbumFixture, genealogyId, albumId)
// R03—R11 共用这一组只读记录夹具。它们只描述本地视觉预览,不冒充
// OpenAPI 响应;每个实体都显式携带 genealogyId,详情必须由家谱与实体 ID
// 共同定位。页面只能通过下方 list/find 选择器取得深拷贝,不能把草稿、完成
// 状态或新增记录写回这里。
const relativeRecordFixtures = [
{
relativeId: '301', genealogyId: '1001', relativeName: '汤文正一家', relationName: '族亲',
eventName: '新春贺礼', eventTime: '2024-02-10', giftAmount: 600,
recordContent: '新春团拜时赠予长辈的心意'
},
{
relativeId: '302', genealogyId: '1001', relativeName: '汤淑华', relationName: '家族长辈',
eventName: '寿宴礼单', eventTime: '2024-04-18', giftAmount: 1000,
recordContent: '汤老先生八十寿辰'
}
]
const ceremonyFixtures = [
{
ceremonyId: '501', genealogyId: '1001', ceremonyType: '祭祖', ceremonyTitle: '清明祭祖',
ceremonyTime: '2025-04-04', location: '汤氏宗祠',
ceremonyDesc: '缅怀先祖,整理祭扫礼序,并由长辈讲述家族往事。',
invitees: [
{ inviteeUserId: '2001', inviteStatus: '1' },
{ inviteeUserId: '2002', inviteStatus: '0' }
]
},
{
ceremonyId: '502', genealogyId: '1001', ceremonyType: '家宴', ceremonyTitle: '中秋家宴',
ceremonyTime: '2025-09-17', location: '祖居院落',
ceremonyDesc: '家人团聚,共叙近况并整理年度家族影像。',
invitees: [{ inviteeUserId: '2003', inviteStatus: '1' }]
},
{
ceremonyId: '503', genealogyId: '1001', ceremonyType: '团拜', ceremonyTitle: '新春团拜',
ceremonyTime: '2025-01-29', location: '家族礼堂',
ceremonyDesc: '新春相聚,向长辈问安并记录家族近况。', invitees: []
}
]
const growthRecordFixtures = [
{
recordId: '701', genealogyId: '1001', lineagePersonId: '101', recordTitle: '整理第一册旧谱',
recordDate: '1988-03', recordContent: '第一次独立整理家中保存的旧谱与口述线索。'
},
{
recordId: '702', genealogyId: '1001', lineagePersonId: '104', recordTitle: '第一次参与修谱',
recordDate: '2024-06', recordContent: '协助长辈核对照片人物与出生年份。'
}
]
const memoFixtures = [
{
memoId: '801', genealogyId: '1001', memoTitle: '修谱资料整理', remindTime: '本月底前',
memoContent: '补充老照片中的人物姓名和拍摄时间。', completedLabel: '待办理'
},
{
memoId: '802', genealogyId: '1001', memoTitle: '重阳敬老活动', remindTime: '10 月 11 日上午',
memoContent: '在祠堂集合,并确认接送长辈的车辆。', completedLabel: '已完成'
}
]
const meritRecordFixtures = [
{
meritId: '901', genealogyId: '1001', meritTypeLabel: '共同修缮', meritTitle: '修缮祠堂',
donorName: '汤氏家人共同参与', meritTime: '2024 年春', amount: null,
meritContent: '协助整理院落、修补门窗并登记旧物。'
},
{
meritId: '902', genealogyId: '1001', meritTypeLabel: '奖学助学', meritTitle: '支持后辈勤学',
donorName: '家族教育小组', meritTime: '2024 年夏', amount: null,
meritContent: '为家族中努力求学的孩子提供书籍与经验分享。'
}
]
const cloneRecordFixture = (record) => ({
...record,
...(Array.isArray(record.invitees)
? { invitees: record.invitees.map((invitee) => ({ ...invitee })) }
: {})
})
const listScopedRecordFixtures = (records, genealogyId) => {
const normalizedGenealogyId = String(genealogyId || '')
if (!normalizedGenealogyId) return []
return records
.filter((record) => record.genealogyId === normalizedGenealogyId)
.map(cloneRecordFixture)
}
const findScopedRecordFixture = (records, idField, genealogyId, entityId) => {
const normalizedGenealogyId = String(genealogyId || '')
const normalizedEntityId = String(entityId || '')
if (!normalizedGenealogyId || !normalizedEntityId) return null
const record = records.find(
(item) => item.genealogyId === normalizedGenealogyId && item[idField] === normalizedEntityId
)
return record ? cloneRecordFixture(record) : null
}
export const listRelativeRecordFixtures = (genealogyId) =>
listScopedRecordFixtures(relativeRecordFixtures, genealogyId)
export const findRelativeRecordFixture = (genealogyId, relativeId) =>
findScopedRecordFixture(relativeRecordFixtures, 'relativeId', genealogyId, relativeId)
export const listCeremonyFixtures = (genealogyId) =>
listScopedRecordFixtures(ceremonyFixtures, genealogyId)
export const findCeremonyFixture = (genealogyId, ceremonyId) =>
findScopedRecordFixture(ceremonyFixtures, 'ceremonyId', genealogyId, ceremonyId)
export const listGrowthRecordFixtures = (genealogyId, lineagePersonId = '') => {
const records = listScopedRecordFixtures(growthRecordFixtures, genealogyId)
const normalizedPersonId = String(lineagePersonId || '')
return normalizedPersonId
? records.filter((record) => record.lineagePersonId === normalizedPersonId)
: records
}
export const findGrowthRecordFixture = (genealogyId, recordId) =>
findScopedRecordFixture(growthRecordFixtures, 'recordId', genealogyId, recordId)
export const listMemoFixtures = (genealogyId) =>
listScopedRecordFixtures(memoFixtures, genealogyId)
export const findMemoFixture = (genealogyId, memoId) =>
findScopedRecordFixture(memoFixtures, 'memoId', genealogyId, memoId)
export const listMeritRecordFixtures = (genealogyId) =>
listScopedRecordFixtures(meritRecordFixtures, genealogyId)
export const findMeritRecordFixture = (genealogyId, meritId) =>
findScopedRecordFixture(meritRecordFixtures, 'meritId', genealogyId, meritId)
export const notifications = [
{
id: 'review-1',
title: '申请待审核',
content: '汤志成申请加入汤氏家谱,请核实亲属关系。',
body: '汤志成申请加入汤氏家谱,请核实申请人的亲属关系与世代信息后完成审核。',
time: '今天 10:28',
source: '汝南汤氏家谱',
unread: true,
targetType: 'GENEALOGY_REVIEW',
targetParams: { genealogyId: '1001' },
targetLabel: '前往入谱审核'
},
{
id: 'approved',
title: '入谱申请已通过',
content: '你申请加入汝南汤氏家谱的请求已通过。',
body: '你申请加入汝南汤氏家谱的请求已通过,现在可以查看家谱与家族动态。',
time: '昨天 18:10',
source: '汝南汤氏家谱',
unread: false,
targetType: 'GENEALOGY_HOME',
targetParams: { genealogyId: '1001' },
targetLabel: '查看我的家谱'
}
]
const cloneNotificationFixture = (notification) => ({
...notification,
targetParams: { ...notification.targetParams }
})
export const listNotificationFixtures = () =>
notifications.map(cloneNotificationFixture)
export const findNotificationFixture = (notificationId) => {
const normalizedId = String(notificationId || '')
if (!normalizedId) return null
const notification = notifications.find((item) => item.id === normalizedId)
return notification ? cloneNotificationFixture(notification) : null
}
export const joinApplications = [
{ id: '1', name: '汤志成', phone: '139****6421', relation: '自述为汤正华堂侄', appliedAt: '今天 10:24', status: 'PENDING' },
{ id: '2', name: '汤雨薇', phone: '136****2798', relation: '自述为汤正国之女', appliedAt: '昨天 18:02', status: 'PENDING' }
]
-46
View File
@@ -1,46 +0,0 @@
// 全量视觉交付的本地页面目录:只保存页面内容与状态,样式由 ModulePage 的六类母版统一维护。
export const pageCatalog = {
f03: { number: 'F-03', title: '动态详情', subtitle: '一段属于家人的共同记忆', template: 'detail', sections: [['端午家宴', '今年端午全家相聚,留下了许多温暖照片。'], ['家人评论', '愿家族和睦兴旺,岁岁平安。']], action: '写下评论' },
f04: { number: 'F-04', title: '谱文', subtitle: '收录家族文章、家训与往事', template: 'list', sections: [['家风家训', '代代相传的处世之道'], ['家族往事', '珍贵的口述与文字记录'], ['族谱序言', '一部家谱的起源']], action: '新建谱文' },
f05: {
number: 'F-05',
title: '谱文详情',
subtitle: '家族文字档案',
template: 'detail',
sections: [['家风家训', '孝友传家,勤俭立业;敬祖睦宗,诚实待人。'], ['收录时间', '2024 年 5 月 12 日']],
action: '收藏谱文',
states: {
empty: {
eyebrow: '谱文已失效',
title: '这篇谱文已无法查看',
copy: '内容可能已被作者删除或取消公开,请返回谱文列表查看其他内容。',
action: '返回谱文列表',
actionMode: 'back',
},
},
},
f07: { number: 'F-07', title: '家族相册', subtitle: '让每一张照片都回到家人身边', template: 'list', sections: [['2024 春节团圆', '18 张照片 · 更新于今天'], ['祖居旧影', '32 张照片 · 家族档案'], ['儿童成长', '46 张照片 · 持续更新']], action: '新建相册' },
r01: { number: 'R-01', title: '人物录', subtitle: '记录家族中值得铭记的人', template: 'list', sections: [['汤文正', '家谱管理员 · 第 18 世'], ['汤淑华', '家族长辈 · 第 17 世'], ['汤文清', '青年代表 · 第 19 世']], action: '新建人物' },
r03: { number: 'R-03', title: '贺礼簿', subtitle: '记录每一份家人之间的心意', template: 'list', sections: [['新春贺礼', '2024 年春节 · 12 条记录'], ['寿宴礼单', '汤老先生八十寿辰'], ['添丁祝福', '家族新成员的祝愿']], action: '新增贺礼' },
r04: { number: 'R-04', title: '编辑贺礼', subtitle: '如实留下礼仪往来记录', template: 'form', fields: ['贺礼名称', '赠送人', '日期', '备注'], action: '保存贺礼', note: '删除确认功能待接入' },
r05: { number: 'R-05', title: '礼仪活动', subtitle: '家族的重要时刻与礼序', template: 'list', sections: [['清明祭祖', '2024 年 4 月 4 日'], ['中秋家宴', '2024 年 9 月 17 日'], ['新春团拜', '2025 年 1 月 29 日']], action: '新建礼仪' },
r06: { number: 'R-06', title: '礼仪详情', subtitle: '清明祭祖', template: 'detail', sections: [['活动说明', '缅怀先祖,凝聚家人,共叙家风传承。'], ['参与人员', '已登记 26 位家人']], action: '编辑活动' },
r07: { number: 'R-07', title: '编辑礼仪', subtitle: '补全时间、地点与活动说明', template: 'form', fields: ['活动名称', '活动日期', '举办地点', '活动说明'], action: '保存活动', note: '发布提醒功能待接入' },
r08: { number: 'R-08', title: '成长日志', subtitle: '珍藏生命成长中的每一个瞬间', template: 'timeline', sections: [['第一次叫爸爸', '2024 年 3 月 · 家人共同记录'], ['入园第一天', '2024 年 9 月 · 留下勇敢的笑脸']], action: '记录成长' },
r09: { number: 'R-09', title: '人生事', subtitle: '记录值得回望的人生节点', template: 'timeline', sections: [['大学毕业', '2018 年 6 月 · 新的起点'], ['结为连理', '2022 年 10 月 · 家人见证']], action: '新增人生事' },
r10: { number: 'R-10', title: '家族备忘', subtitle: '把重要的事留给未来的自己', template: 'list', sections: [['修谱资料整理', '请在本月底前补充老照片信息'], ['重阳敬老活动', '10 月 11 日上午在祠堂集合']], action: '新增备忘' },
r11: { number: 'R-11', title: '功德记录', subtitle: '致谢每一份对家族的守护', template: 'list', sections: [['修缮祠堂', '2024 年春 · 家族共同支持'], ['奖学助学', '2024 年夏 · 鼓励后辈勤学']], action: '新增记录' },
n02: { number: 'N-02', title: '消息详情', subtitle: '家谱提醒', template: 'detail', sections: [['申请待审核', '汤文清申请加入家谱,请在审核页面查看。'], ['收到时间', '今天 10:28']], action: '标记已读' },
m02: { number: 'M-02', title: '个人资料', subtitle: '完善你的家谱身份信息', template: 'form', fields: ['头像昵称', '真实姓名', '常住地区', '个人简介'], action: '保存资料', note: '头像上传服务待接入' },
m03: { number: 'M-03', title: '账号与安全', subtitle: '保护你的家谱账号', template: 'settings', sections: [['登录密码', '建议定期更新'], ['绑定手机号', '用于登录和安全验证'], ['登录设备', '暂无异常设备']], action: '检查账号安全' },
m04: { number: 'M-04', title: '修改密码', subtitle: '设置新的安全密码', template: 'form', fields: ['当前密码', '新密码', '确认新密码'], action: '确认修改', note: '密码服务待接入' },
m05: { number: 'M-05', title: '修改手机号', subtitle: '更换用于登录的手机号', template: 'form', fields: ['当前手机号', '新手机号', '验证码'], action: '确认更换', note: '短信验证服务待接入' },
m06: { number: 'M-06', title: '帮助中心', subtitle: '常见问题与使用说明', template: 'settings', sections: [['如何创建家谱', '从家谱首页点击新建家谱'], ['如何邀请家人', '通过申请审核或邀请码加入'], ['资料隐私说明', '个人信息按权限展示']], action: '联系家谱助手' },
m07: { number: 'M-07', title: '意见反馈', subtitle: '你的建议会让家谱更好', template: 'form', fields: ['反馈类型', '问题描述', '联系方式'], action: '提交反馈', note: '反馈服务待接入' },
m08: { number: 'M-08', title: '应用推广', subtitle: '邀请亲友共建家族记忆', template: 'detail', sections: [['邀请家人', '将家谱分享给家人,共同补全家族故事。'], ['推广说明', '邀请功能与奖励规则待接入。']], action: '生成邀请海报' },
m09: { number: 'M-09', title: 'VIP 与订单', subtitle: '家谱服务与订阅记录', template: 'status', badge: '礼', lead: '当前没有订阅订单', note: 'VIP 服务开放后会在这里展示权益与订单', action: '查看服务说明' },
m10: { number: 'M-10', title: '关于家谱', subtitle: '传承每一段值得珍藏的家族记忆', template: 'settings', sections: [['用户协议', '功能待接入'], ['隐私政策', '功能待接入'], ['当前版本', '1.0.0'], ['退出登录', '仅本地演示']], action: '查看版本说明' }
}