完成70%

This commit is contained in:
2026-07-24 07:56:22 +08:00
parent bb6431b319
commit c7f278fe79
92 changed files with 4741 additions and 14440 deletions
+704 -59
View File
@@ -2,7 +2,6 @@ import {
currentUser,
genealogies,
publicGenealogies,
treeMembers,
notifications,
joinApplications,
listFamilyFeedFixtures,
@@ -352,24 +351,6 @@ const saveLogin = (loginResult) => {
return loginResult
}
const toTreeNode = (person, index = 0) => {
const generation = Number(person.generationNo || person.generation || 1)
return {
...person,
relatives: Array.isArray(person.relatives)
? person.relatives.map((relative) => ({ ...relative }))
: [],
id: person.id || person.personId,
name: person.personName || person.name || '未命名族人',
relation: person.relation || (generation === 1 ? '始祖' : '族人'),
generation,
years: person.years || [person.birthDate, person.deathDate].filter(Boolean).join('—') || '生卒待补',
branch: person.branch || '主支',
x: person.x ?? (20 + (index % 4) * 20),
y: person.y ?? (generation * 31 - 24)
}
}
const lineageTreeError = (message) =>
createRequestError(message, 'LINEAGE_TREE_RESPONSE_INVALID')
@@ -506,9 +487,18 @@ const normalizeLineagePersonDetail = (
}
const name = normalizeLineagePersonText(value.name, '姓名', { required: true })
const generationName = normalizeLineagePersonText(value.generationName, '字辈')
const aliasName = normalizeLineagePersonText(value.aliasName, '别名或曾用名')
const birthDate = normalizeLineagePersonDate(value.birthDate, '出生日期')
const deathDate = normalizeLineagePersonDate(value.deathDate, '逝世日期')
const personStatus = normalizeLineagePersonText(value.personStatus, '人物状态').toUpperCase()
const birthLunar = normalizeLineagePersonText(value.birthLunar, '出生农历')
const deathLunar = normalizeLineagePersonText(value.deathLunar, '逝世农历')
const birthPlace = normalizeLineagePersonText(value.birthPlace, '出生地')
const deathPlace = normalizeLineagePersonText(value.deathPlace, '逝世地')
const burialPlace = normalizeLineagePersonText(value.burialPlace, '安葬地')
const spouseNames = normalizeLineagePersonText(value.spouseNames, '配偶姓名')
const personStatus = normalizeLineagePersonText(value.personStatus, '人物状态')
const biography = normalizeLineagePersonText(value.biography, '生平')
const remark = normalizeLineagePersonText(value.remark, '备注')
const relatives = []
for (const relation of [
{ id: value.fatherId, name: value.fatherName, label: '父亲' },
@@ -530,6 +520,7 @@ const normalizeLineagePersonDetail = (
genealogyId,
genealogyName: normalizeLineagePersonText(value.genealogyName, '家谱名称') || '当前家谱',
name,
aliasName,
generation: value.generation,
generationName,
relation: value.generation === 1 ? '始祖' : '家谱成员',
@@ -538,15 +529,409 @@ const normalizeLineagePersonDetail = (
: '字辈待补',
sex: normalizeLineagePersonText(value.sex, '性别'),
birthDate,
birthLunar,
deathDate,
deathLunar,
years: birthDate || deathDate ? `${birthDate}${deathDate}` : '生卒待补',
birthplace: normalizeLineagePersonText(value.birthPlace, '出生地'),
biography: normalizeLineagePersonText(value.biography, '生平'),
status: ['DECEASED', 'DEAD'].includes(personStatus) ? 'deceased' : 'normal',
birthplace: birthPlace,
deathPlace,
burialPlace,
spouseNames,
personStatus,
biography,
remark,
status: ['DECEASED', 'DEAD'].includes(personStatus.toUpperCase()) ? 'deceased' : 'normal',
relatives
}
}
const normalizeLineagePersonPage = (value, expectedGenealogyId) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw lineagePersonError('成员分页响应不是对象')
}
if (!Array.isArray(value.rows)) {
throw lineagePersonError('成员分页缺少 rows')
}
if (!Number.isSafeInteger(value.total) || value.total < 0) {
throw lineagePersonError('成员分页 total 无效')
}
const rows = value.rows.map((item) =>
normalizeLineagePersonDetail(
item,
expectedGenealogyId,
normalizeLineagePersonIdentity(item?.personId, '人物标识'),
),
)
if (new Set(rows.map((item) => item.id)).size !== rows.length) {
throw lineagePersonError('成员分页包含重复人物标识')
}
return { rows, total: value.total }
}
const normalizeFeedCommentId = (value, label) => {
if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) return value
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return String(value)
throw createRequestError(`家族动态评论${label}无效`, 'FEED_COMMENT_RESPONSE_INVALID')
}
const normalizeFeedCommentText = (value, label, { required = false } = {}) => {
if (value === undefined || value === null) {
if (required) throw createRequestError(`家族动态评论缺少${label}`, 'FEED_COMMENT_RESPONSE_INVALID')
return ''
}
if (typeof value !== 'string') {
throw createRequestError(`家族动态评论${label}无效`, 'FEED_COMMENT_RESPONSE_INVALID')
}
const normalized = value.trim()
if (required && !normalized) {
throw createRequestError(`家族动态评论缺少${label}`, 'FEED_COMMENT_RESPONSE_INVALID')
}
return normalized
}
const normalizeFeedComments = (value, expectedGenealogyId, expectedFeedId) => {
if (!Array.isArray(value)) {
throw createRequestError('家族动态评论响应不是列表', 'FEED_COMMENT_RESPONSE_INVALID')
}
const comments = value.map((item) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) {
throw createRequestError('家族动态评论包含无效条目', 'FEED_COMMENT_RESPONSE_INVALID')
}
const genealogyId = normalizeFeedCommentId(item.genealogyId, '家谱标识')
const feedId = normalizeFeedCommentId(item.feedId, '动态标识')
if (genealogyId !== expectedGenealogyId || feedId !== expectedFeedId) {
throw createRequestError('家族动态评论归属与请求不匹配', 'FEED_COMMENT_RESPONSE_INVALID')
}
return {
id: normalizeFeedCommentId(item.commentId, '标识'),
author: normalizeFeedCommentText(item.appUserNickName, '用户昵称') || '未署名成员',
content: normalizeFeedCommentText(item.commentContent, '评论内容', { required: true }),
time: normalizeFeedCommentText(item.createTime, '创建时间'),
parentCommentId: item.parentCommentId === undefined || item.parentCommentId === null
? null
: normalizeFeedCommentId(item.parentCommentId, '父评论标识'),
replyCount: Number.isSafeInteger(item.replyCount) && item.replyCount >= 0 ? item.replyCount : 0,
}
})
if (new Set(comments.map((item) => item.id)).size !== comments.length) {
throw createRequestError('家族动态评论包含重复标识', 'FEED_COMMENT_RESPONSE_INVALID')
}
return comments
}
const normalizeFeedCommentPayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) {
throw new TypeError('家族动态评论请求必须是普通对象')
}
const fields = Object.keys(payload)
if (fields.some((field) => !['parentCommentId', 'commentContent'].includes(field))) {
throw new TypeError('家族动态评论请求包含未声明字段')
}
const commentContent = normalizeFeedCommentText(payload.commentContent, '评论内容', { required: true })
if (Array.from(commentContent).length > 1000) throw new TypeError('家族动态评论不能超过 1000 个字符')
const data = { commentContent }
if (payload.parentCommentId !== undefined && payload.parentCommentId !== null) {
data.parentCommentId = normalizeFeedCommentId(payload.parentCommentId, '父评论标识')
}
return data
}
const normalizeFeedCreatePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) {
throw new TypeError('家族动态请求必须是普通对象')
}
if (Object.keys(payload).some((field) => field !== 'feedContent')) {
throw new TypeError('家族动态请求包含未声明字段')
}
if (typeof payload.feedContent !== 'string' || !payload.feedContent.trim()) {
throw new TypeError('家族动态内容不能为空')
}
return { feedContent: payload.feedContent.trim() }
}
const normalizeArticleCreatePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) {
throw new TypeError('谱文请求必须是普通对象')
}
if (Object.keys(payload).some((field) => !['articleTitle', 'articleContent'].includes(field))) {
throw new TypeError('谱文请求包含未声明字段')
}
const articleTitle = typeof payload.articleTitle === 'string' ? payload.articleTitle.trim() : ''
const articleContent = typeof payload.articleContent === 'string' ? payload.articleContent.trim() : ''
if (!articleTitle) throw new TypeError('谱文标题不能为空')
if (!articleContent) throw new TypeError('谱文正文不能为空')
return { articleTitle, articleContent }
}
const normalizeAlbumCreatePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) {
throw new TypeError('相册请求必须是普通对象')
}
if (Object.keys(payload).some((field) => field !== 'albumName')) {
throw new TypeError('相册请求包含未声明字段')
}
if (typeof payload.albumName !== 'string' || !payload.albumName.trim()) {
throw new TypeError('相册名称不能为空')
}
return { albumName: payload.albumName.trim() }
}
const normalizeRelativeRecordCreatePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) {
throw new TypeError('亲友往来请求必须是普通对象')
}
const allowedFields = ['relativeName', 'relationName', 'eventName', 'eventTime', 'giftAmount', 'recordContent']
if (Object.keys(payload).some((field) => !allowedFields.includes(field))) {
throw new TypeError('亲友往来请求包含未声明字段')
}
const relativeName = typeof payload.relativeName === 'string' ? payload.relativeName.trim() : ''
if (!relativeName) throw new TypeError('亲友姓名不能为空')
const data = { relativeName }
for (const field of ['relationName', 'eventName', 'eventTime', 'recordContent']) {
if (payload[field] === undefined) continue
if (typeof payload[field] !== 'string') throw new TypeError(`亲友往来${field}必须是字符串`)
const value = payload[field].trim()
if (value) data[field] = value
}
if (payload.giftAmount !== undefined && payload.giftAmount !== '' && payload.giftAmount !== null) {
if (typeof payload.giftAmount !== 'number' || !Number.isFinite(payload.giftAmount)) {
throw new TypeError('亲友往来礼金金额必须是有限数字')
}
data.giftAmount = payload.giftAmount
}
return data
}
const normalizeGrowthRecordCreatePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) throw new TypeError('成长记录请求必须是普通对象')
if (Object.keys(payload).some((field) => !['recordTitle', 'recordContent', 'recordDate'].includes(field))) throw new TypeError('成长记录请求包含未声明字段')
const recordTitle = typeof payload.recordTitle === 'string' ? payload.recordTitle.trim() : ''
if (!recordTitle) throw new TypeError('成长记录标题不能为空')
const data = { recordTitle }
for (const field of ['recordContent', 'recordDate']) {
if (payload[field] === undefined) continue
if (typeof payload[field] !== 'string') throw new TypeError(`成长记录${field}必须是字符串`)
const value = payload[field].trim()
if (value) data[field] = value
}
return data
}
const normalizeMemoCreatePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) throw new TypeError('备忘请求必须是普通对象')
if (Object.keys(payload).some((field) => !['memoTitle', 'memoContent', 'remindTime'].includes(field))) throw new TypeError('备忘请求包含未声明字段')
const memoTitle = typeof payload.memoTitle === 'string' ? payload.memoTitle.trim() : ''
if (!memoTitle) throw new TypeError('备忘标题不能为空')
const data = { memoTitle }
for (const field of ['memoContent', 'remindTime']) {
if (payload[field] === undefined) continue
if (typeof payload[field] !== 'string') throw new TypeError(`备忘${field}必须是字符串`)
const value = payload[field].trim()
if (value) data[field] = value
}
return data
}
const normalizeMeritRecordCreatePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) throw new TypeError('功德记录请求必须是普通对象')
const allowedFields = ['donorName', 'meritTitle', 'meritType', 'meritContent', 'amount', 'meritTime']
if (Object.keys(payload).some((field) => !allowedFields.includes(field))) throw new TypeError('功德记录请求包含未声明字段')
const donorName = typeof payload.donorName === 'string' ? payload.donorName.trim() : ''
const meritTitle = typeof payload.meritTitle === 'string' ? payload.meritTitle.trim() : ''
if (!donorName || !meritTitle) throw new TypeError('功德记录捐赠人和标题不能为空')
const data = { donorName, meritTitle }
for (const field of ['meritType', 'meritContent', 'meritTime']) {
if (payload[field] === undefined) continue
if (typeof payload[field] !== 'string') throw new TypeError(`功德记录${field}必须是字符串`)
const value = payload[field].trim()
if (value) data[field] = value
}
if (payload.amount !== undefined && payload.amount !== '' && payload.amount !== null) {
if (typeof payload.amount !== 'number' || !Number.isFinite(payload.amount)) throw new TypeError('功德金额必须是有限数字')
data.amount = payload.amount
}
return data
}
const normalizeGenerationPoemId = (value, label) => {
if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) return value
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return String(value)
throw createRequestError(`字辈${label}无效`, 'GENERATION_POEM_RESPONSE_INVALID')
}
const normalizeGenerationPoemNumber = (value, label) => {
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return value
if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) {
const normalized = Number(value)
if (Number.isSafeInteger(normalized)) return normalized
}
throw createRequestError(`字辈${label}无效`, 'GENERATION_POEM_RESPONSE_INVALID')
}
const normalizeGenerationPoemText = (value, label, { required = false, maxLength = null } = {}) => {
if (value === undefined || value === null) {
if (required) throw createRequestError(`字辈缺少${label}`, 'GENERATION_POEM_RESPONSE_INVALID')
return ''
}
if (typeof value !== 'string') throw createRequestError(`字辈${label}无效`, 'GENERATION_POEM_RESPONSE_INVALID')
const normalized = value.trim()
if (required && !normalized) throw createRequestError(`字辈缺少${label}`, 'GENERATION_POEM_RESPONSE_INVALID')
if (maxLength !== null && Array.from(normalized).length > maxLength) {
throw createRequestError(`字辈${label}超出合同长度`, 'GENERATION_POEM_RESPONSE_INVALID')
}
return normalized
}
const normalizeGenerationPoemStatus = (value) => {
if (value === '0' || value === '1') return value
throw createRequestError('字辈状态无效', 'GENERATION_POEM_RESPONSE_INVALID')
}
const normalizeGenerationPoemRows = (value, expectedGenealogyId) => {
if (!Array.isArray(value)) throw createRequestError('字辈响应不是列表', 'GENERATION_POEM_RESPONSE_INVALID')
const rows = value.map((item) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) {
throw createRequestError('字辈响应包含无效条目', 'GENERATION_POEM_RESPONSE_INVALID')
}
const genealogyId = normalizeGenerationPoemId(item.genealogyId, '家谱标识')
if (genealogyId !== expectedGenealogyId) {
throw createRequestError('字辈响应家谱标识与请求不匹配', 'GENERATION_POEM_RESPONSE_INVALID')
}
return {
poemId: normalizeGenerationPoemId(item.poemId, '标识'),
genealogyId,
genealogyNo: normalizeGenerationPoemText(item.genealogyNo, '家谱编号'),
genealogyName: normalizeGenerationPoemText(item.genealogyName, '家谱名称'),
generationNo: normalizeGenerationPoemNumber(item.generationNo, '世代'),
generationText: normalizeGenerationPoemText(item.generationText, '文字', { required: true, maxLength: 50 }),
description: normalizeGenerationPoemText(item.description, '说明', { maxLength: 500 }),
sortOrder: item.sortOrder,
status: normalizeGenerationPoemStatus(item.status),
}
})
if (new Set(rows.map((item) => item.poemId)).size !== rows.length) {
throw createRequestError('字辈响应包含重复标识', 'GENERATION_POEM_RESPONSE_INVALID')
}
if (new Set(rows.map((item) => item.generationNo)).size !== rows.length) {
throw createRequestError('字辈响应包含重复世代', 'GENERATION_POEM_RESPONSE_INVALID')
}
return rows.sort((left, right) => left.generationNo - right.generationNo)
}
const normalizeGenerationPoemBatchPayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) {
throw new TypeError('字辈批量请求必须是普通对象')
}
if (Object.keys(payload).some((field) => !['poemText', 'disableMissing'].includes(field))) {
throw new TypeError('字辈批量请求包含未声明字段')
}
const poemText = normalizeGenerationPoemText(payload.poemText, '内容', { required: true, maxLength: 26000 })
const data = { poemText }
if (payload.disableMissing !== undefined) {
if (typeof payload.disableMissing !== 'boolean') throw new TypeError('字辈停用策略必须是布尔值')
data.disableMissing = payload.disableMissing
}
return data
}
const normalizeGenerationPoemPreview = (value, expectedGenealogyId) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw createRequestError('字辈批量预览响应不是对象', 'GENERATION_POEM_RESPONSE_INVALID')
}
const genealogyId = normalizeGenerationPoemId(value.genealogyId, '预览家谱标识')
if (genealogyId !== expectedGenealogyId) {
throw createRequestError('字辈预览家谱标识与请求不匹配', 'GENERATION_POEM_RESPONSE_INVALID')
}
const counts = ['createCount', 'updateCount', 'keepCount', 'disableCount']
const preview = { genealogyId, items: Array.isArray(value.items) ? value.items : null }
for (const field of counts) {
if (!Number.isSafeInteger(value[field]) || value[field] < 0) {
throw createRequestError(`字辈预览${field}无效`, 'GENERATION_POEM_RESPONSE_INVALID')
}
preview[field] = value[field]
}
if (preview.items === null) {
throw createRequestError('字辈预览缺少明细列表', 'GENERATION_POEM_RESPONSE_INVALID')
}
return preview
}
const normalizeLineageWritePayload = (payload) => {
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.getPrototypeOf(payload) !== Object.prototype) {
throw new TypeError('人物写入请求必须是普通对象')
}
const allowedFields = new Set([
'name',
'aliasName',
'generationName',
'birthDate',
'birthLunar',
'birthPlace',
'deathDate',
'deathLunar',
'deathPlace',
'burialPlace',
'biography',
'remark',
'relationName'
])
for (const field of Object.keys(payload)) {
if (!allowedFields.has(field)) throw new TypeError(`人物写入包含未声明字段:${field}`)
}
const name = normalizeLineagePersonText(payload.name, '姓名', { required: true })
if (name.length > 20) throw new TypeError('人物姓名长度超出当前页面合同')
const data = { name }
for (const [field, label, maxLength] of [
['generationName', '字辈', 12],
['aliasName', '别名或曾用名', null],
['birthLunar', '出生农历', null],
['birthPlace', '出生地', null],
['deathLunar', '逝世农历', null],
['deathPlace', '逝世地', null],
['burialPlace', '安葬地', null],
['biography', '人物简介', 500],
['remark', '备注', null],
['relationName', '关系显示名称', null]
]) {
if (payload[field] === undefined) continue
const value = normalizeLineagePersonText(payload[field], label)
if (maxLength && value.length > maxLength) throw new TypeError(`人物${label}长度超出当前页面合同`)
if (value) data[field] = value
}
for (const [field, label] of [
['birthDate', '出生日期'],
['deathDate', '离世日期']
]) {
if (payload[field] === undefined) continue
const value = normalizeLineagePersonDate(payload[field], label)
if (value) data[field] = value
}
if (data.birthDate && data.deathDate && data.deathDate < data.birthDate) {
throw new TypeError('人物离世日期不能早于出生日期')
}
return data
}
const lineageRelationPath = Object.freeze({
FATHER: 'parents',
MOTHER: 'parents',
SPOUSE: 'spouses',
SIBLING: 'siblings',
SON: 'children',
DAUGHTER: 'children'
})
const requireLineageWriteRequestController = (requestOptions) => {
if (!requestOptions || typeof requestOptions !== 'object' || Array.isArray(requestOptions) || Object.getPrototypeOf(requestOptions) !== Object.prototype) {
throw new TypeError('人物写入请求选项必须是普通对象')
}
const fields = Object.keys(requestOptions)
if (fields.some((field) => field !== 'requestController')) {
throw new TypeError('人物写入请求选项包含未声明字段')
}
return requestOptions.requestController ?? null
}
export const appApi = {
async getCaptchaRequirement({ sceneCode, subject }, requestOptions = {}) {
requireRemoteAuth()
@@ -614,6 +999,32 @@ export const appApi = {
})
}, requestOptions)
},
async changePassword({ oldPasswordHash, newPasswordHash }, requestOptions = {}) {
requireRemoteAuth()
await requestStrict({
url: '/genealogy/app/auth/password',
method: 'PUT',
data: {
oldPassword: assertPasswordHash(oldPasswordHash),
newPassword: assertPasswordHash(newPasswordHash)
}
}, {
requireData: false,
requestController: requestOptions.requestController ?? null
})
return null
},
async logout(requestOptions = {}) {
if (resolveRuntimeMode() !== 'remote') return null
await requestStrict({
url: '/genealogy/app/auth/logout',
method: 'DELETE'
}, {
requireData: false,
requestController: requestOptions.requestController ?? null
})
return null
},
async submitFeedback(payload, requestOptions = {}) {
const data = normalizeFeedbackPayload(payload)
if (!requestOptions || typeof requestOptions !== 'object' || Array.isArray(requestOptions) || Object.getPrototypeOf(requestOptions) !== Object.prototype) {
@@ -646,10 +1057,9 @@ export const appApi = {
},
async getMyGenealogies(requestOptions = {}) {
if (!hasRemoteConfig()) {
return genealogies.map((item) => ({
...item,
accessRole: item.membership === 'created' ? 'owner' : 'member'
}))
const error = new Error('我的家谱需要真实读取服务,当前本地预览不会伪造家谱列表')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
const result = await requestStrict({
url: '/genealogy/app/genealogies/mine',
@@ -673,7 +1083,11 @@ export const appApi = {
},
async getOverview(genealogyId, requestOptions = {}) {
const normalizedId = normalizeGenealogyPathId(genealogyId)
if (!hasRemoteConfig()) return this.getGenealogy(normalizedId)
if (!hasRemoteConfig()) {
const error = new Error('家谱概览需要真实读取服务,当前本地预览不会伪造概览数据')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedId}/overview`,
method: 'GET'
@@ -687,58 +1101,289 @@ export const appApi = {
return overview
},
async getTree(genealogyId, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedId = normalizeGenealogyPathId(genealogyId)
const tree = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedId}/lineage/tree`,
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeLineageTree(tree)
if (!hasRemoteConfig()) {
const error = new Error('世系树需要真实读取服务,当前本地预览不会伪造人物节点')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
return treeMembers
.filter((item) => String(item.genealogyId) === String(genealogyId))
.map(toTreeNode)
const normalizedId = normalizeGenealogyPathId(genealogyId)
const tree = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedId}/lineage/tree`,
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeLineageTree(tree)
},
async getPerson(genealogyId, personId, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('人物详情需要真实读取服务,当前本地预览不会伪造成员资料')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedPersonId = normalizeLineagePersonIdentity(personId, '人物标识')
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/lineage/persons/${normalizedPersonId}`,
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeLineagePersonDetail(result, normalizedGenealogyId, normalizedPersonId)
},
async getPersonPage(genealogyId, { pageNum = 1, pageSize = 10, keyword = '' } = {}, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('成员目录需要真实读取服务,当前本地预览不会伪造目录数据')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
if (!Number.isSafeInteger(pageNum) || pageNum < 1) throw new TypeError('成员目录页码无效')
if (!Number.isSafeInteger(pageSize) || pageSize < 1) throw new TypeError('成员目录页大小无效')
if (typeof keyword !== 'string') throw new TypeError('成员目录关键词无效')
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/lineage/persons/page`,
method: 'GET',
data: { pageNum, pageSize, ...(keyword.trim() ? { keyword: keyword.trim() } : {}) }
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeLineagePersonPage(result, normalizedGenealogyId)
},
async getFeedComments(genealogyId, feedId, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('动态评论需要真实读取服务,当前本地预览不会伪造评论数据')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedFeedId = normalizeFeedCommentId(feedId, '动态标识')
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/feeds/${normalizedFeedId}/comments`,
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeFeedComments(result, normalizedGenealogyId, normalizedFeedId)
},
async createFeedComment(genealogyId, feedId, payload, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('动态评论需要真实服务,当前本地预览不会伪造提交成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedFeedId = normalizeFeedCommentId(feedId, '动态标识')
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/feeds/${normalizedFeedId}/comments`,
method: 'POST',
data: normalizeFeedCommentPayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
},
async getGenerationPoems(genealogyId, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('字辈列表需要真实读取服务,当前本地预览不会伪造字辈数据')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/generation-poems`,
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeGenerationPoemRows(result, normalizedGenealogyId)
},
async getGenerationPoemManagement(genealogyId, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('字辈维护列表需要真实读取服务,当前本地预览不会伪造管理权限')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/generation-poems/management`,
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeGenerationPoemRows(result, normalizedGenealogyId)
},
async previewGenerationPoemBatch(genealogyId, payload, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('字辈批量预览需要真实服务,当前本地预览不会伪造差异结果')
error.code = 'REMOTE_READ_REQUIRED'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/generation-poems/batch/preview`,
method: 'POST',
data: normalizeGenerationPoemBatchPayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeGenerationPoemPreview(result, normalizedGenealogyId)
},
async saveGenerationPoemBatch(genealogyId, payload, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('字辈批量保存需要真实服务,当前本地预览不会伪造保存成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/generation-poems/batch/save`,
method: 'POST',
data: normalizeGenerationPoemBatchPayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
},
async createPerson(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedPersonId = normalizeLineagePersonIdentity(personId, '人物标识')
const result = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/lineage/persons/${normalizedPersonId}`,
method: 'GET'
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/lineage/persons`,
method: 'POST',
data: normalizeLineageWritePayload(payload)
}, {
requestController: requestOptions.requestController ?? null
requestController: requireLineageWriteRequestController(requestOptions)
})
return normalizeLineagePersonDetail(result, normalizedGenealogyId, normalizedPersonId)
}
const person = treeMembers.find(
(item) => String(item.id) === String(personId) &&
String(item.genealogyId) === String(genealogyId)
)
return person ? toTreeNode(person) : null
},
async createPerson(genealogyId, payload) {
if (hasRemoteConfig()) {
const person = await request({ url: `/genealogy/app/genealogies/${genealogyId}/lineage/persons`, method: 'POST', data: payload })
return toTreeNode(person)
}
const error = new Error('人物创建接口在本地预览模式不可用,当前内容不会保存')
error.code = 'WRITE_UNAVAILABLE'
throw error
},
async createRelatedPerson(genealogyId, personId, relationType, payload, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('人物关系写入接口在本地预览模式不可用,当前内容不会保存')
error.code = 'WRITE_UNAVAILABLE'
throw error
}
const relationPath = lineageRelationPath[relationType]
if (!relationPath) throw new TypeError('人物关系类型不属于当前合同')
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedPersonId = normalizeLineagePersonIdentity(personId, '人物标识')
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/lineage/persons/${normalizedPersonId}/${relationPath}`,
method: 'POST',
data: normalizeLineageWritePayload(payload)
}, {
requestController: requireLineageWriteRequestController(requestOptions)
})
},
async updatePerson(genealogyId, personId, payload, requestOptions = {}) {
if (!hasRemoteConfig()) {
const error = new Error('人物编辑接口在本地预览模式不可用,当前内容不会保存')
error.code = 'WRITE_UNAVAILABLE'
throw error
}
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedPersonId = normalizeLineagePersonIdentity(personId, '人物标识')
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/lineage/persons/${normalizedPersonId}`,
method: 'PUT',
data: normalizeLineageWritePayload(payload)
}, {
requestController: requireLineageWriteRequestController(requestOptions)
})
},
async getFeeds(genealogyId) {
return hasRemoteConfig()
? request({ url: `/genealogy/app/genealogies/${genealogyId}/feeds` })
: listFamilyFeedFixtures(genealogyId)
},
async createFeed(genealogyId, payload) {
if (hasRemoteConfig()) return request({ url: `/genealogy/app/genealogies/${genealogyId}/feeds`, method: 'POST', data: payload })
async createFeed(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/feeds`,
method: 'POST',
data: normalizeFeedCreatePayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
}
const error = new Error('动态发布接口尚未接入,当前内容不会保存')
error.code = 'WRITE_UNAVAILABLE'
throw error
},
async createArticle(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/articles`,
method: 'POST',
data: normalizeArticleCreatePayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
}
const error = new Error('谱文创建需要真实服务,当前本地预览不会伪造创建成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
},
async createAlbum(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/albums`,
method: 'POST',
data: normalizeAlbumCreatePayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
}
const error = new Error('相册创建需要真实服务,当前本地预览不会伪造创建成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
},
async createRelativeRecord(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/relative-records`,
method: 'POST',
data: normalizeRelativeRecordCreatePayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
}
const error = new Error('亲友往来创建需要真实服务,当前本地预览不会伪造创建成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
},
async createGrowthRecord(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({ url: `/genealogy/app/genealogies/${normalizedGenealogyId}/growth-records`, method: 'POST', data: normalizeGrowthRecordCreatePayload(payload) }, { requestController: requestOptions.requestController ?? null })
}
const error = new Error('成长记录创建需要真实服务,当前本地预览不会伪造创建成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
},
async createMemo(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({ url: `/genealogy/app/genealogies/${normalizedGenealogyId}/memos`, method: 'POST', data: normalizeMemoCreatePayload(payload) }, { requestController: requestOptions.requestController ?? null })
}
const error = new Error('备忘创建需要真实服务,当前本地预览不会伪造创建成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
},
async createMeritRecord(genealogyId, payload, requestOptions = {}) {
if (hasRemoteConfig()) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({ url: `/genealogy/app/genealogies/${normalizedGenealogyId}/merit-records`, method: 'POST', data: normalizeMeritRecordCreatePayload(payload) }, { requestController: requestOptions.requestController ?? null })
}
const error = new Error('功德记录创建需要真实服务,当前本地预览不会伪造创建成功')
error.code = 'REMOTE_WRITE_REQUIRED'
throw error
},
async getArticles(genealogyId) {
return hasRemoteConfig()
? request({ url: `/genealogy/app/genealogies/${genealogyId}/articles` })