完成70%
This commit is contained in:
+704
-59
@@ -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` })
|
||||
|
||||
Reference in New Issue
Block a user