129 lines
5.5 KiB
JavaScript
129 lines
5.5 KiB
JavaScript
import { createRequestError } from './request-client.js'
|
|
import { normalizeBusinessFileAccess } from './business-file-contract.js'
|
|
|
|
const lineageTreeError = (message) =>
|
|
createRequestError(message, 'LINEAGE_TREE_RESPONSE_INVALID')
|
|
|
|
const normalizeLineagePersonId = (value) => {
|
|
if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) return value
|
|
if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return String(value)
|
|
throw lineageTreeError('世系树包含无效人物标识')
|
|
}
|
|
|
|
const normalizeLineageText = (value, label, { required = false } = {}) => {
|
|
if (value === undefined || value === null) {
|
|
if (required) throw lineageTreeError(`世系树缺少${label}`)
|
|
return ''
|
|
}
|
|
if (typeof value !== 'string') throw lineageTreeError(`世系树${label}无效`)
|
|
const normalized = value.trim()
|
|
if (required && !normalized) throw lineageTreeError(`世系树缺少${label}`)
|
|
return normalized
|
|
}
|
|
|
|
const lineageDatePart = (value, label) => {
|
|
const normalized = normalizeLineageText(value, label)
|
|
if (!normalized) return ''
|
|
if (!/^\d{4}-\d{2}-\d{2}/.test(normalized)) {
|
|
throw lineageTreeError(`世系树${label}无效`)
|
|
}
|
|
if (normalized.length === 10) return normalized
|
|
const instant = new Date(normalized)
|
|
if (Number.isNaN(instant.getTime())) throw lineageTreeError(`世系树${label}无效`)
|
|
|
|
const chinaTime = new Date(instant.getTime() + 8 * 60 * 60 * 1000)
|
|
const year = chinaTime.getUTCFullYear()
|
|
const month = String(chinaTime.getUTCMonth() + 1).padStart(2, '0')
|
|
const day = String(chinaTime.getUTCDate()).padStart(2, '0')
|
|
return `${year}-${month}-${day}`
|
|
}
|
|
|
|
const compactLineageYears = (birthDate, deathDate) => {
|
|
const birthYear = birthDate ? birthDate.slice(0, 4) : ''
|
|
const deathYear = deathDate ? deathDate.slice(0, 4) : ''
|
|
return birthYear || deathYear ? `${birthYear}—${deathYear}` : '生卒待补'
|
|
}
|
|
|
|
export const normalizeLineageTree = (value) => {
|
|
if (!Array.isArray(value)) throw lineageTreeError('世系树响应不是列表')
|
|
const seen = new Set()
|
|
const normalized = []
|
|
const normalizedById = new Map()
|
|
const appendNode = (node, parentId, relationOverride = '', spouseOf = '') => {
|
|
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
throw lineageTreeError('世系树包含无效节点')
|
|
}
|
|
const id = normalizeLineagePersonId(node.personId)
|
|
if (seen.has(id)) throw lineageTreeError('世系树包含重复人物标识')
|
|
seen.add(id)
|
|
if (seen.size > 5000) throw lineageTreeError('世系树节点数量超出客户端上限')
|
|
if (!Number.isSafeInteger(node.generation) || node.generation < 1) {
|
|
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, '逝世日期')
|
|
const normalizedNode = {
|
|
id,
|
|
parentId,
|
|
...(spouseOf ? { spouseOf } : {}),
|
|
name: normalizeLineageText(node.name, '人物姓名', { required: true }),
|
|
relation:
|
|
relationOverride ||
|
|
rankName ||
|
|
(parentId
|
|
? (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, '人物状态')
|
|
}
|
|
normalized.push(normalizedNode)
|
|
normalizedById.set(id, normalizedNode)
|
|
return id
|
|
}
|
|
const walk = (node, parentId = null, depth = 0) => {
|
|
if (depth > 64) throw lineageTreeError('世系树深度超出客户端上限')
|
|
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
throw lineageTreeError('世系树包含无效节点')
|
|
}
|
|
const spouses = node.spouses ?? []
|
|
const children = node.children ?? []
|
|
if (!Array.isArray(spouses) || !Array.isArray(children)) {
|
|
throw lineageTreeError('世系树亲属集合无效')
|
|
}
|
|
const id = normalizeLineagePersonId(node.personId)
|
|
const knownSpouseId = spouses
|
|
.map((spouse) => normalizeLineagePersonId(spouse?.personId))
|
|
.find((spouseId) => normalizedById.has(spouseId)) || ''
|
|
if (seen.has(id)) {
|
|
if (knownSpouseId) return id
|
|
throw lineageTreeError('世系树包含重复人物标识')
|
|
}
|
|
const knownSpouse = knownSpouseId ? normalizedById.get(knownSpouseId) : null
|
|
const nodeParentId = knownSpouse ? knownSpouse.parentId : parentId
|
|
const appendedId = appendNode(node, nodeParentId, knownSpouse ? '配偶' : '', knownSpouseId)
|
|
spouses.forEach((spouse) => {
|
|
const spouseId = normalizeLineagePersonId(spouse?.personId)
|
|
if (seen.has(spouseId)) return
|
|
appendNode(spouse, nodeParentId, '配偶', appendedId)
|
|
})
|
|
children.forEach((child) => walk(child, knownSpouseId || appendedId, depth + 1))
|
|
}
|
|
value.forEach((root) => walk(root))
|
|
return normalized
|
|
}
|