修改完成待测试

This commit is contained in:
2026-09-13 17:45:47 +08:00
parent 9ad572907b
commit bbc024f7f2
80 changed files with 3844 additions and 1672 deletions
+11
View File
@@ -8,6 +8,7 @@ import {
import {
assertPlainPayload,
normalizeOptionalNormalDisableStatus,
normalizeOptionalOssIdList,
normalizeOptionalSafeInteger,
normalizeOptionalText,
normalizeOssIdString,
@@ -15,6 +16,7 @@ import {
} from './request-normalizers.js'
import {
normalizeBusinessFileAccess,
normalizeBusinessFileAccessRows,
normalizeContentProtectionCapabilities,
normalizeResourceCapabilities
} from './business-file-contract.js'
@@ -67,6 +69,7 @@ export const normalizeAppArticle = (value, expectedGenealogyId, expectedArticleI
categoryId: normalizeOptionalNumericId(value.categoryId, '谱文分类标识', 'ARTICLE_RESPONSE_INVALID'),
category: normalizeResponseText(value.categoryName, 'categoryName'),
coverFile: normalizeBusinessFileAccess(value.coverFile, '谱文封面', 'ARTICLE_RESPONSE_INVALID'),
mediaFiles: normalizeBusinessFileAccessRows(value.mediaFiles, '谱文正文图片', 'ARTICLE_RESPONSE_INVALID'),
viewCount: normalizeOptionalNonnegativeInteger(value.viewCount, '谱文阅读数', 'ARTICLE_RESPONSE_INVALID'),
sortOrder: normalizeOptionalNonnegativeInteger(value.sortOrder, '谱文排序值', 'ARTICLE_RESPONSE_INVALID'),
status: normalizeNormalDisableResponseStatus(value.status, '谱文状态', 'ARTICLE_RESPONSE_INVALID'),
@@ -90,6 +93,7 @@ export const normalizeArticleCreatePayload = (payload) => {
'articleTitle',
'articleSummary',
'coverOssId',
'mediaOssIds',
'articleContent',
'authorName',
'sortOrder',
@@ -116,5 +120,12 @@ export const normalizeArticleCreatePayload = (payload) => {
if (payload.coverOssId === null) normalizedPayload.coverOssId = null
else if (payload.coverOssId !== '') normalizedPayload.coverOssId = normalizeOssIdString(payload.coverOssId, 'coverOssId')
}
if (Object.prototype.hasOwnProperty.call(payload, 'mediaOssIds')) {
const mediaOssIds = normalizeOptionalOssIdList(payload.mediaOssIds)
if (mediaOssIds && mediaOssIds.length > 1000) {
throw new TypeError('谱文正文图片标识总长度不能超过1000个字符')
}
normalizedPayload.mediaOssIds = mediaOssIds || ''
}
return normalizedPayload
}
+2 -1
View File
@@ -33,13 +33,14 @@ const requestArticleContentProtection = async ({
export const familyArticleApi = {
async createArticle(genealogyId, payload, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({
const article = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/articles`,
method: 'POST',
data: normalizeArticleCreatePayload(payload)
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeAppArticle(article, normalizedGenealogyId)
},
async getArticles(genealogyId, requestOptions = {}) {
+5
View File
@@ -8,6 +8,7 @@ import { createRequestError } from './request-client.js'
import { assertPlainPayload, normalizeOssIdString } from './request-normalizers.js'
import {
normalizeOptionalNonnegativeInteger,
normalizeOptionalNumericId,
normalizeResponseText
} from './response-normalizers.js'
@@ -227,6 +228,7 @@ export const normalizeAppGenealogy = (item) => {
)
return {
id: normalizeMyGenealogyId(item.genealogyId),
genealogyNo: normalizeGenealogyResponseText(item.genealogyNo, 'genealogyNo'),
name,
firstAncestorName: normalizeGenealogyResponseText(item.firstAncestorName, 'firstAncestorName'),
rootPersonId: normalizeOptionalNumericId(item.rootPersonId, '始迁祖人物标识', 'GENEALOGY_RESPONSE_INVALID'),
@@ -248,6 +250,7 @@ export const normalizeAppGenealogy = (item) => {
),
archivedAt: normalizeGenealogyResponseText(item.archivedAt, 'archivedAt'),
intro: normalizeGenealogyResponseText(item.intro, 'intro'),
createTime: normalizeGenealogyResponseText(item.createTime, 'createTime'),
joinTime: normalizeGenealogyResponseText(item.joinTime, 'joinTime')
}
}
@@ -267,6 +270,8 @@ export const normalizeMyGenealogies = (value) => {
memberCount: genealogy.memberCount,
canManage: genealogy.canManage,
canEditContent: genealogy.canEditContent,
createTime: genealogy.createTime,
joinTime: genealogy.joinTime,
lifecycleStatus: genealogy.lifecycleStatus,
archivedAt: genealogy.archivedAt,
canArchive: genealogy.canArchive,
+2 -1
View File
@@ -89,11 +89,12 @@ export const lifeRecordApi = {
async createGrowthRecord(genealogyId, payload, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
return requestStrict({
const growthRecord = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/growth-records`,
method: 'POST',
data: normalizeGrowthRecordCreatePayload(payload)
}, { requestController: requestOptions.requestController ?? null })
return normalizeAppGrowthRecord(growthRecord, normalizedGenealogyId)
},
async updateGrowthRecord(genealogyId, recordId, payload, requestOptions = {}) {
+53
View File
@@ -1,5 +1,6 @@
import { normalizeOptionalSafeInteger } from './request-normalizers.js'
import { createRequestError } from './request-client.js'
import { normalizeOptionalNumericId } from './response-normalizers.js'
import { LINEAGE_PERSON_OPTIONS } from './lineage-person-options.js'
import { normalizeBusinessOptionProjection } from './business-dictionary-contract.js'
@@ -10,6 +11,16 @@ const optionLabels = (options) => Object.freeze(
const lineagePersonError = (message) =>
createRequestError(message, 'LINEAGE_PERSON_RESPONSE_INVALID')
const lineageRankError = (message) =>
createRequestError(message, 'LINEAGE_RANK_RESPONSE_INVALID')
const normalizeLineageRankText = (value, label) => {
if (typeof value !== 'string' || !value.trim()) {
throw lineageRankError(`世系排行选项缺少${label}`)
}
return value.trim()
}
export const normalizeLineagePersonIdentity = (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)
@@ -146,6 +157,12 @@ export const normalizeLineagePersonDetail = (value, expectedGenealogyId, expecte
throw lineagePersonError('成员详情敏感病史权限无效')
}
const relationName = normalizeLineagePersonText(value.relationName, '关系显示名称')
const rankId = normalizeOptionalNumericId(
value.rankId,
'成员排行标识',
'LINEAGE_PERSON_RESPONSE_INVALID'
)
const rankName = normalizeLineagePersonText(value.rankName, '排行名称')
for (const field of ['canDisable', 'canCreateDocument', 'canManageDocuments']) {
if (value[field] !== undefined && typeof value[field] !== 'boolean') {
throw lineagePersonError(`成员详情权限字段 ${field} 无效`)
@@ -221,6 +238,8 @@ export const normalizeLineagePersonDetail = (value, expectedGenealogyId, expecte
remark,
canManageSensitiveMedicalHistory,
relationName,
rankId,
rankName,
sortOrder,
status: personStatus === '1' ? 'deceased' : 'normal',
canDisable: value.canDisable === true,
@@ -231,6 +250,40 @@ export const normalizeLineagePersonDetail = (value, expectedGenealogyId, expecte
}
}
export const normalizeLineageRankOptions = (value) => {
if (!Array.isArray(value)) throw lineageRankError('世系排行选项响应不是列表')
const rankIds = new Set()
return value.map((option) => {
if (!option || typeof option !== 'object' || Array.isArray(option)) {
throw lineageRankError('世系排行选项包含无效记录')
}
const rankId = normalizeOptionalNumericId(
option.rankId,
'世系排行标识',
'LINEAGE_RANK_RESPONSE_INVALID'
)
if (!rankId || rankIds.has(rankId)) {
throw lineageRankError(rankId ? '世系排行选项包含重复标识' : '世系排行选项缺少标识')
}
rankIds.add(rankId)
const rankCode = normalizeLineageRankText(option.rankCode, '排行编码')
const rankName = normalizeLineageRankText(option.rankName, '排行名称')
const rankType = normalizeLineageRankText(option.rankType, '排行类型')
const genderScope = normalizeLineageRankText(option.genderScope, '排行适用性别')
if (!['ANCESTOR', 'GENERATION'].includes(rankType)) {
throw lineageRankError('世系排行选项类型无效')
}
if (!['0', '1', '2'].includes(genderScope)) {
throw lineageRankError('世系排行选项适用性别无效')
}
const rankOrder = normalizeOptionalSafeInteger(option.rankOrder, '排行顺序')
if (rankOrder !== undefined && rankOrder < 0) {
throw lineageRankError('世系排行选项顺序无效')
}
return { rankId, rankCode, rankName, rankType, genderScope, rankOrder }
})
}
export const normalizeLineageSensitiveProfile = (value, expectedPersonId) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw lineagePersonError('成员敏感健康资料响应不是对象')
+19
View File
@@ -4,6 +4,7 @@ import {
normalizeLineagePersonIdentity,
normalizeLineagePersonOptions,
normalizeLineagePersonPage,
normalizeLineageRankOptions,
normalizeLineageSensitiveProfile
} from './lineage-person-contract.js'
import { normalizeLineageTree } from './lineage-tree-contract.js'
@@ -26,6 +27,24 @@ export const lineageApi = {
return normalizeLineageTree(tree)
},
async getRankOptions(genealogyId, generation, sex, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
if (!Number.isSafeInteger(generation) || generation < 1) {
throw new TypeError('排行选项世代必须是正安全整数')
}
if (!['0', '1', '2'].includes(sex)) {
throw new TypeError('排行选项性别必须是 0、1 或 2')
}
const options = await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/lineage/ranks`,
method: 'GET',
data: { generation, sex }
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeLineageRankOptions(options)
},
async getPerson(genealogyId, personId, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedPersonId = normalizeLineagePersonIdentity(personId, '人物标识')
+10 -1
View File
@@ -1,4 +1,5 @@
import { createRequestError } from './request-client.js'
import { normalizeBusinessFileAccess } from './business-file-contract.js'
const lineageTreeError = (message) =>
createRequestError(message, 'LINEAGE_TREE_RESPONSE_INVALID')
@@ -60,6 +61,7 @@ export const normalizeLineageTree = (value) => {
throw lineageTreeError('世系树人物世代无效')
}
const generationName = normalizeLineageText(node.generationName, '字辈')
const rankName = normalizeLineageText(node.rankName, '排行名称')
const relationName = normalizeLineageText(node.relationName, '人物关系')
const birthDate = lineageDatePart(node.birthDate, '出生日期')
const deathDate = lineageDatePart(node.deathDate, '逝世日期')
@@ -70,15 +72,22 @@ export const normalizeLineageTree = (value) => {
name: normalizeLineageText(node.name, '人物姓名', { required: true }),
relation:
relationOverride ||
rankName ||
(parentId
? (relationName && relationName !== '配偶' ? relationName : '后代')
? (relationName && relationName !== '配偶' ? relationName : '排行待补')
: relationName || '始祖'),
rankName,
generation: node.generation,
branch: generationName
? (generationName.endsWith('字辈') ? generationName : `${generationName}字辈`)
: '字辈待补',
years: birthDate || deathDate ? `${birthDate}${deathDate}` : '生卒待补',
treeYears: compactLineageYears(birthDate, deathDate),
avatarFile: normalizeBusinessFileAccess(
node.avatarFile,
'世系人物头像',
'LINEAGE_TREE_RESPONSE_INVALID'
),
sex: normalizeLineageText(node.sex, '性别'),
personStatus: normalizeLineageText(node.personStatus, '人物状态')
}
+6
View File
@@ -27,6 +27,7 @@ export const normalizeLineageWritePayload = (payload) => {
'aliasName',
'sex',
'generationName',
'rankId',
'fatherId',
'motherId',
'avatarOssId',
@@ -77,6 +78,11 @@ export const normalizeLineageWritePayload = (payload) => {
if (payload[field] === undefined || payload[field] === null || payload[field] === '') continue
normalizedPayload[field] = normalizeLineagePersonIdentity(payload[field], label)
}
if (Object.prototype.hasOwnProperty.call(payload, 'rankId')) {
normalizedPayload.rankId = payload.rankId === null || payload.rankId === ''
? null
: normalizeLineagePersonIdentity(payload.rankId, '排行标识')
}
if (payload.avatarOssId !== undefined && payload.avatarOssId !== null && payload.avatarOssId !== '') {
normalizedPayload.avatarOssId = normalizeOssIdString(payload.avatarOssId, '头像文件标识')
}
+63
View File
@@ -1,11 +1,16 @@
import { createRequestError } from './request-client.js'
import {
normalizeOptionalNonnegativeInteger,
normalizeOptionalNumericId,
normalizeResponseText
} from './response-normalizers.js'
import { normalizeBusinessFileAccess } from './business-file-contract.js'
const promotionPlacements = new Set(['home_banner', 'home_bottom', 'message_bottom', 'profile_bottom'])
const siteArticleTypeLabels = Object.freeze({
news: '资讯',
notice: '公告'
})
export const normalizePromotionPlacement = (value) => {
if (!promotionPlacements.has(value)) throw new TypeError('推广位无效')
@@ -50,6 +55,64 @@ export const normalizeAppPromotions = (value, expectedPlacement) => {
return promotions
}
export const normalizeSiteArticles = (value) => {
if (!Array.isArray(value)) {
throw createRequestError('官网文章响应不是列表', 'SITE_ARTICLE_RESPONSE_INVALID')
}
const articles = value.map((item) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) {
throw createRequestError('官网文章响应包含无效条目', 'SITE_ARTICLE_RESPONSE_INVALID')
}
const id = normalizeOptionalNumericId(item.articleId, '官网文章标识', 'SITE_ARTICLE_RESPONSE_INVALID')
const title = normalizeResponseText(item.articleTitle, 'articleTitle', {
code: 'SITE_ARTICLE_RESPONSE_INVALID',
subject: '官网文章响应'
})
if (!id || !title) {
throw createRequestError('官网文章响应缺少稳定字段', 'SITE_ARTICLE_RESPONSE_INVALID')
}
const externalUrl = normalizeResponseText(item.externalUrl, 'externalUrl', {
code: 'SITE_ARTICLE_RESPONSE_INVALID',
subject: '官网文章响应'
})
if (externalUrl && !/^https:\/\/[^\s/?#]+(?:[/?#][^\s]*)?$/.test(externalUrl)) {
throw createRequestError('官网文章外部链接无效', 'SITE_ARTICLE_RESPONSE_INVALID')
}
const type = normalizeResponseText(item.articleType, 'articleType', {
code: 'SITE_ARTICLE_RESPONSE_INVALID',
subject: '官网文章响应'
})
return {
id,
title,
type,
typeLabel: siteArticleTypeLabels[type] || '资讯',
summary: normalizeResponseText(item.articleSummary, 'articleSummary', {
code: 'SITE_ARTICLE_RESPONSE_INVALID',
subject: '官网文章响应'
}),
content: normalizeResponseText(item.articleContent, 'articleContent', {
code: 'SITE_ARTICLE_RESPONSE_INVALID',
subject: '官网文章响应'
}),
publishTime: normalizeResponseText(item.publishTime, 'publishTime', {
code: 'SITE_ARTICLE_RESPONSE_INVALID',
subject: '官网文章响应'
}),
externalUrl,
sortOrder: normalizeOptionalNonnegativeInteger(
item.sortOrder,
'官网文章排序值',
'SITE_ARTICLE_RESPONSE_INVALID'
)
}
})
if (new Set(articles.map((article) => article.id)).size !== articles.length) {
throw createRequestError('官网文章响应包含重复标识', 'SITE_ARTICLE_RESPONSE_INVALID')
}
return articles
}
const normalizeHelpArticleText = (value, field) => {
if (typeof value !== 'string' || !value.trim()) {
throw createRequestError(`帮助文章缺少 ${field}`, 'HELP_ARTICLE_RESPONSE_INVALID')
+24 -1
View File
@@ -3,11 +3,33 @@ import {
normalizeComplianceDocument,
normalizeComplianceDocumentKey,
normalizeHelpArticles,
normalizePromotionPlacement
normalizePromotionPlacement,
normalizeSiteArticles
} from './site-content-contract.js'
import { requestStrict } from './request-client.js'
export const siteContentApi = {
async getSiteArticles(requestOptions = {}) {
const limit = requestOptions.limit ?? 20
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
throw new TypeError('官网文章数量上限无效')
}
const articleType = typeof requestOptions.articleType === 'string'
? requestOptions.articleType.trim()
: ''
const articles = await requestStrict({
url: '/genealogy/app/site/articles',
method: 'GET',
data: {
limit,
...(articleType ? { articleType } : {})
}
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeSiteArticles(articles)
},
async getHelpArticles(requestOptions = {}) {
const helpArticles = await requestStrict({
url: '/genealogy/app/help-articles',
@@ -25,6 +47,7 @@ export const siteContentApi = {
method: 'GET',
data: { platform: 'app', placement }
}, {
authenticated: false,
requestController: requestOptions.requestController ?? null
})
return normalizeAppPromotions(promotions, placement)