完成50%
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
// generationNo 来自 int64。当前页面必须做加法与排序,因此只接受 JavaScript
|
||||
// 可以精确表达的正安全整数;超出范围时失败关闭,不能把两个世代舍入成同一值。
|
||||
export const MAX_GENERATION_NO = Number.MAX_SAFE_INTEGER
|
||||
|
||||
// 三个限制逐一对应 GenerationPoemBatchBody:输入总长 26000、单代文字
|
||||
// 最多 50 字符、单批最多 500 个世代,不能再合并成一个含义模糊的“长度”。
|
||||
export const MAX_GENERATION_COUNT = 500
|
||||
export const MAX_GENERATION_TEXT_LENGTH = 50
|
||||
export const MAX_GENERATION_POEM_INPUT_LENGTH = 26000
|
||||
export const GENERATION_POEM_STATUS = Object.freeze({
|
||||
ACTIVE: '0',
|
||||
DISABLED: '1',
|
||||
})
|
||||
|
||||
const normalizePositiveInteger = (value) => {
|
||||
if (typeof value === 'number') {
|
||||
return Number.isSafeInteger(value) && value > 0 ? value : null
|
||||
}
|
||||
if (typeof value !== 'string' || !/^[1-9]\d*$/.test(value)) return null
|
||||
const number = Number(value)
|
||||
return Number.isSafeInteger(number) ? number : null
|
||||
}
|
||||
|
||||
// 路由查询始终先经过这里:缺省值可以回落,显式传入的非法值必须失败关闭,
|
||||
// 不能用 Number(...) || fallback 把 0、小数或科学计数法悄悄改成另一代。
|
||||
export const normalizeGenerationNumber = (value, fallback) => {
|
||||
const candidate = value === undefined || value === null || value === ''
|
||||
? fallback
|
||||
: value
|
||||
const number = normalizePositiveInteger(candidate)
|
||||
return number !== null && number <= MAX_GENERATION_NO ? number : null
|
||||
}
|
||||
|
||||
const separatorPattern = /[\s,,;;、/|]/u
|
||||
const separatorRunPattern = /[\s,,;;、/|]+/u
|
||||
const forbiddenControlPattern = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/u
|
||||
const codePointLength = (value) => Array.from(value).length
|
||||
|
||||
const normalizeGenerationText = (value) => {
|
||||
const text = String(value ?? '').trim()
|
||||
if (
|
||||
!text ||
|
||||
codePointLength(text) > MAX_GENERATION_TEXT_LENGTH ||
|
||||
forbiddenControlPattern.test(text)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// 按 OpenAPI 的批量输入规则解析:没有分隔符时每个 Unicode code point 对应
|
||||
// 一代;存在空格、逗号、分号、顿号、斜杠或竖线时,每个分段可包含多字。
|
||||
export const validateGenerationPoemText = (value) => {
|
||||
const input = String(value ?? '')
|
||||
if (!input.trim()) {
|
||||
return Object.freeze({ valid: false, generations: [], message: '请录入字辈内容' })
|
||||
}
|
||||
if (codePointLength(input) > MAX_GENERATION_POEM_INPUT_LENGTH) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations: [],
|
||||
message: `字辈输入最多 ${MAX_GENERATION_POEM_INPUT_LENGTH} 个字符`,
|
||||
})
|
||||
}
|
||||
if (forbiddenControlPattern.test(input)) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations: [],
|
||||
message: '字辈内容包含不支持的控制字符',
|
||||
})
|
||||
}
|
||||
|
||||
const generations = separatorPattern.test(input)
|
||||
? input.split(separatorRunPattern).map((item) => item.trim()).filter(Boolean)
|
||||
: Array.from(input)
|
||||
if (!generations.length) {
|
||||
return Object.freeze({ valid: false, generations, message: '请录入字辈内容' })
|
||||
}
|
||||
if (generations.length > MAX_GENERATION_COUNT) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations,
|
||||
message: `一次最多录入 ${MAX_GENERATION_COUNT} 个世代`,
|
||||
})
|
||||
}
|
||||
if (generations.some((item) => normalizeGenerationText(item) === null)) {
|
||||
return Object.freeze({
|
||||
valid: false,
|
||||
generations,
|
||||
message: `每代字辈文字不能为空且最多 ${MAX_GENERATION_TEXT_LENGTH} 个字符`,
|
||||
})
|
||||
}
|
||||
return Object.freeze({ valid: true, generations, message: '' })
|
||||
}
|
||||
|
||||
const normalizeExistingRow = (row) => {
|
||||
const generationNo = normalizeGenerationNumber(row?.generationNo)
|
||||
const generationText = normalizeGenerationText(row?.generationText)
|
||||
if (
|
||||
generationNo === null ||
|
||||
generationText === null ||
|
||||
!Object.values(GENERATION_POEM_STATUS).includes(row?.status)
|
||||
) {
|
||||
throw new TypeError('已有字辈记录不符合 GenerationPoemView 合同')
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
generationNo,
|
||||
generationText,
|
||||
status: row.status,
|
||||
current: false,
|
||||
}
|
||||
}
|
||||
|
||||
// 只检查已建立字辈的起点到本批起始代之间,避免把家谱尚未录入的更早世代
|
||||
// 当成缺口;算法只遍历已有记录,能够安全处理几十代、几百代甚至稀疏数据。
|
||||
export const findFirstGenerationGap = (
|
||||
rows,
|
||||
startGeneration,
|
||||
sequenceStartGeneration,
|
||||
) => {
|
||||
const start = normalizeGenerationNumber(startGeneration)
|
||||
if (start === null) return null
|
||||
const generations = [...new Set(
|
||||
(Array.isArray(rows) ? rows : [])
|
||||
.filter((row) => row?.status !== GENERATION_POEM_STATUS.DISABLED)
|
||||
.map((row) => normalizeGenerationNumber(row?.generationNo))
|
||||
.filter((generationNo) => generationNo !== null && generationNo < start),
|
||||
)].sort((left, right) => left - right)
|
||||
if (!generations.length) return null
|
||||
const explicitSequenceStart = sequenceStartGeneration === undefined
|
||||
? null
|
||||
: normalizeGenerationNumber(sequenceStartGeneration)
|
||||
if (sequenceStartGeneration !== undefined && explicitSequenceStart === null) {
|
||||
throw new TypeError('字辈序列起点无效')
|
||||
}
|
||||
let expected = explicitSequenceStart ?? generations[0]
|
||||
for (const generationNo of generations) {
|
||||
if (generationNo > expected) return expected
|
||||
expected = generationNo + 1
|
||||
}
|
||||
return expected < start ? expected : null
|
||||
}
|
||||
|
||||
// 批量维护只替换本次覆盖区间。disableMissing=true 时,后续遗漏记录会被
|
||||
// 标成停用但仍保留,确保历史数据不会因一次编辑被物理删除。
|
||||
export const mergeGenerationPoemRows = ({
|
||||
existingRows,
|
||||
generationTexts,
|
||||
startGeneration,
|
||||
currentGeneration,
|
||||
disableMissing,
|
||||
}) => {
|
||||
const start = normalizeGenerationNumber(startGeneration)
|
||||
const current = normalizeGenerationNumber(currentGeneration)
|
||||
if (typeof disableMissing !== 'boolean') {
|
||||
throw new TypeError('disableMissing 必须是布尔值')
|
||||
}
|
||||
if (!Array.isArray(generationTexts) || !generationTexts.length) {
|
||||
throw new TypeError('字辈合并参数无效')
|
||||
}
|
||||
if (generationTexts.length > MAX_GENERATION_COUNT) {
|
||||
throw new RangeError(`一次最多 ${MAX_GENERATION_COUNT} 个世代`)
|
||||
}
|
||||
const nextGenerationTexts = generationTexts.map(normalizeGenerationText)
|
||||
if (nextGenerationTexts.some((item) => item === null)) {
|
||||
throw new TypeError('单代字辈文字无效')
|
||||
}
|
||||
if (start === null || current === null) throw new TypeError('字辈合并参数无效')
|
||||
if (start > MAX_GENERATION_NO - nextGenerationTexts.length + 1) {
|
||||
throw new RangeError('字辈世代超出支持范围')
|
||||
}
|
||||
const end = start + nextGenerationTexts.length - 1
|
||||
|
||||
const rowsByGeneration = new Map()
|
||||
for (const item of Array.isArray(existingRows) ? existingRows : []) {
|
||||
const row = normalizeExistingRow(item)
|
||||
if (rowsByGeneration.has(row.generationNo)) {
|
||||
throw new TypeError('已有字辈包含重复的世代记录')
|
||||
}
|
||||
rowsByGeneration.set(row.generationNo, row)
|
||||
}
|
||||
|
||||
for (let index = 0; index < nextGenerationTexts.length; index += 1) {
|
||||
const generationNo = start + index
|
||||
const existing = rowsByGeneration.get(generationNo)
|
||||
rowsByGeneration.set(generationNo, {
|
||||
...existing,
|
||||
generationNo,
|
||||
generationText: nextGenerationTexts[index],
|
||||
status: GENERATION_POEM_STATUS.ACTIVE,
|
||||
current: false,
|
||||
})
|
||||
}
|
||||
|
||||
if (disableMissing) {
|
||||
for (const [generationNo, row] of rowsByGeneration) {
|
||||
if (generationNo > end) {
|
||||
rowsByGeneration.set(generationNo, {
|
||||
...row,
|
||||
status: GENERATION_POEM_STATUS.DISABLED,
|
||||
current: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...rowsByGeneration.values()]
|
||||
.sort((left, right) => left.generationNo - right.generationNo)
|
||||
.map((row) => ({
|
||||
...row,
|
||||
current:
|
||||
row.status === GENERATION_POEM_STATUS.ACTIVE &&
|
||||
row.generationNo === current,
|
||||
}))
|
||||
}
|
||||
Reference in New Issue
Block a user