(function (root, factory) {
// 世系人物模块同时支持浏览器页面和 Node 单元测试。
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.LineagePages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.LineagePages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var selectedPersonId = '';
var editingPersonId = '';
var relationMode = '';
var peopleById = Object.create(null);
var currentPageNum = 1;
var currentPageSize = 20;
var currentPageTotal = null;
var managementAccess = false;
var writePending = false;
var currentUserId = '';
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function isForbidden(error) {
return Number(error && (error.status || error.code)) === 403;
}
function redirectUnauthorized(api, error) {
if (!shouldRedirectToLogin(api, error)) return false;
if (api && api.clearToken) api.clearToken();
if (!root.location) return true;
if (typeof root.location.replace === 'function') {
root.location.replace('login.html');
return true;
}
root.location.href = 'login.html';
return true;
}
function query(selector, rootNode) {
return documentRef ? (rootNode || documentRef).querySelector(selector) : null;
}
function queryAll(selector, rootNode) {
return documentRef ? Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)) : [];
}
function showMessage(message) {
if (root.layui && root.layui.layer) {
root.layui.layer.msg(message);
return;
}
if (root.alert) root.alert(message);
}
function getQueryParam(search, name) {
var params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
return params.get(name) || '';
}
function getCurrentGenealogyId(search) {
var page = query('[data-lineage-page]');
var source = search === undefined && root.location ? root.location.search : search;
var profileGenealogyId;
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
profileGenealogyId = root.ProfileUI.getGenealogyId();
if (profileGenealogyId) return profileGenealogyId;
}
return getQueryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || '';
}
function trimOrUndefined(value) {
var text = String(value === undefined || value === null ? '' : value).trim();
return text || undefined;
}
function toIntegerOrUndefined(value) {
var text = trimOrUndefined(value);
var number;
if (!text) return undefined;
number = Number(text);
return Number.isInteger(number) && Number.isSafeInteger(number) ? number : undefined;
}
function normalizeId(value) {
var text;
if (value === undefined || value === null || value === '') return '';
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
text = String(value);
return /^\d+$/.test(text) ? text : '';
}
function getPersonId(item) {
return normalizeId(item && item.personId);
}
function formatLineageDate(value) {
var text = trimOrUndefined(value);
if (!text) return undefined;
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(text)) return text.replace('T', ' ') + ':00';
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(text)) return text.replace('T', ' ');
return text;
}
function buildLineagePersonBody(values) {
var source = values || {};
var bindingMode = trimOrUndefined(source.bindingMode);
var body = {
bindingMode: bindingMode,
name: trimOrUndefined(source.name)
};
var optionalFields = [
'personNo', 'aliasName', 'sex', 'generationName', 'birthLunar', 'birthPlace',
'deathLunar', 'deathPlace', 'burialPlace', 'personStatus', 'biography',
'remark', 'relationName'
];
var idFields = ['avatarOssId', 'fatherId', 'motherId'];
var generation = trimOrUndefined(source.generation);
var sortOrder = trimOrUndefined(source.sortOrder);
var birthDate = formatLineageDate(source.birthDate);
var deathDate = formatLineageDate(source.deathDate);
optionalFields.forEach(function (field) {
var value = trimOrUndefined(source[field]);
if (value !== undefined) body[field] = value;
});
idFields.forEach(function (field) {
var value = normalizeId(source[field]);
if (value) body[field] = value;
});
if (bindingMode === 'SPECIFIED') {
body.appUserId = normalizeId(source.appUserId) || trimOrUndefined(source.appUserId);
}
if (birthDate !== undefined) body.birthDate = birthDate;
if (deathDate !== undefined) body.deathDate = deathDate;
if (generation !== undefined) body.generation = Number(generation);
if (sortOrder !== undefined) body.sortOrder = Number(sortOrder);
return body;
}
function validateLineagePersonBody(body) {
if (!body || !body.name) return '请填写成员姓名';
if (!body.bindingMode) return '请选择账号绑定方式';
if (['NONE', 'SELF', 'SPECIFIED'].indexOf(body.bindingMode) === -1) return '账号绑定方式无效';
if (body.bindingMode === 'SPECIFIED' && !normalizeId(body.appUserId)) return '指定账号绑定缺少用户选项';
if (body.bindingMode !== 'SPECIFIED' && body.appUserId !== undefined) return '当前绑定方式不能提交指定用户';
if (body.sex !== undefined && ['0', '1', '2'].indexOf(body.sex) === -1) return '性别选项无效';
if (body.personStatus !== undefined && ['0', '1', '2'].indexOf(body.personStatus) === -1) return '人物状态选项无效';
if (body.birthLunar !== undefined && ['0', '1'].indexOf(body.birthLunar) === -1) return '出生历法选项无效';
if (body.deathLunar !== undefined && ['0', '1'].indexOf(body.deathLunar) === -1) return '逝世历法选项无效';
if (['avatarOssId', 'fatherId', 'motherId'].some(function (field) {
return body[field] !== undefined && !normalizeId(body[field]);
})) return '人物关联字段无效';
if (body.generation !== undefined && (!Number.isInteger(body.generation) || !Number.isSafeInteger(body.generation))) {
return '世代序号必须是整数';
}
if (body.sortOrder !== undefined && (!Number.isInteger(body.sortOrder) || !Number.isSafeInteger(body.sortOrder))) {
return '排序值必须是整数';
}
return '';
}
function normalizeLineagePerson(item) {
var personId = getPersonId(item);
var name = trimOrUndefined(item && item.name);
var person;
var invalidId = false;
if (!personId || !name) return null;
person = { personId: personId, name: name };
[
'genealogyName', 'genealogyNo', 'appUserNickName', 'personNo', 'aliasName', 'sex', 'generationName',
'fatherName', 'motherName', 'spouseNames', 'birthDate', 'birthLunar', 'birthPlace', 'deathDate',
'deathLunar', 'deathPlace', 'burialPlace', 'personStatus', 'biography', 'status', 'remark',
'relationType', 'relationName'
].forEach(function (field) {
if (item && item[field] !== undefined && item[field] !== null) person[field] = String(item[field]);
});
['appUserId', 'genealogyId', 'fatherId', 'motherId', 'avatarOssId'].forEach(function (field) {
if (item && item[field] !== undefined && item[field] !== null) {
var value = normalizeId(item[field]);
if (!value) {
invalidId = true;
return;
}
person[field] = value;
}
});
if (invalidId) return null;
if (toIntegerOrUndefined(item && item.generation) !== undefined) person.generation = toIntegerOrUndefined(item.generation);
if (toIntegerOrUndefined(item && item.sortOrder) !== undefined) person.sortOrder = toIntegerOrUndefined(item.sortOrder);
return person;
}
function normalizeLineageAccess(genealogy) {
return {
canEditContent: Boolean(genealogy && genealogy.canEditContent === true)
};
}
function normalizeGenerationOption(item) {
var generation = toIntegerOrUndefined(item && item.generationNo);
var generationName = trimOrUndefined(item && item.generationText);
if (!generation || !generationName) return null;
return {
generation: generation,
generationName: generationName
};
}
function normalizeGenealogyMemberOption(item) {
var memberId = normalizeId(item && item.memberId);
var genealogyId = normalizeId(item && item.genealogyId);
var appUserId = normalizeId(item && item.appUserId);
var status = trimOrUndefined(item && item.status);
if (!memberId || !genealogyId || !appUserId || status !== '0') return null;
return {
memberId: memberId,
genealogyId: genealogyId,
appUserId: appUserId,
appUserNickName: String(item.appUserNickName === undefined || item.appUserNickName === null ? '' : item.appUserNickName),
memberName: String(item.memberName === undefined || item.memberName === null ? '' : item.memberName),
roleType: String(item.roleType === undefined || item.roleType === null ? '' : item.roleType),
status: status
};
}
function normalizeGenealogyMemberOptions(data) {
if (!Array.isArray(data)) return [];
return data.map(normalizeGenealogyMemberOption).filter(Boolean);
}
function normalizeLineageBinding(person, userId) {
var appUserId = normalizeId(person && person.appUserId);
var normalizedUserId = normalizeId(userId);
if (!appUserId) return { bindingMode: 'NONE', appUserId: '' };
if (normalizedUserId && appUserId === normalizedUserId) return { bindingMode: 'SELF', appUserId: '' };
return { bindingMode: 'SPECIFIED', appUserId: appUserId };
}
function normalizeList(data) {
if (Array.isArray(data)) return data;
if (data && Array.isArray(data.rows)) return data.rows;
return [];
}
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function getPersonSummary(person) {
var values = [];
if (person.generation !== undefined) values.push('第 ' + person.generation + ' 世');
if (person.generationName) values.push('字辈:' + person.generationName);
if (person.aliasName) values.push('别名:' + person.aliasName);
if (person.relationName) values.push('关系称谓:' + person.relationName);
if (person.personStatus) values.push('人物状态:' + person.personStatus);
return values.join(' · ') || '未填写世代信息';
}
function renderLineageList(data) {
var container = query('[data-lineage-list]');
var normalized = normalizeList(data).map(normalizeLineagePerson);
var invalidCount = normalized.filter(function (item) { return !item; }).length;
var people = normalized.filter(Boolean);
peopleById = Object.create(null);
people.forEach(function (person) {
peopleById[person.personId] = person;
});
if (!container) return;
if (invalidCount) {
container.innerHTML = '
人物响应缺少可安全使用的 personId 或 name,请联系后端补充 DTO。
';
return;
}
if (!people.length) {
container.innerHTML = '
暂无世系人物
';
return;
}
container.innerHTML = people.map(function (person) {
var selected = person.personId === selectedPersonId ? ' is-selected' : '';
return '';
}).join('');
}
function renderTreeNode(item, ancestry) {
var person = normalizeLineagePerson(item);
var nextAncestry;
var spouses;
var children;
var branches = '';
if (!person) return '';
if (ancestry[person.personId]) {
return '