(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 = ''; function normalizeList(data) { // 通用列表响应可能是数组,也可能包在 rows、records、list、data 中。 if (Array.isArray(data)) return data; if (!data || typeof data !== 'object') return []; if (Array.isArray(data.rows)) return data.rows; if (Array.isArray(data.records)) return data.records; if (Array.isArray(data.list)) return data.list; if (Array.isArray(data.data)) return data.data; return []; } function pick(item, keys, fallback) { var source = item || {}; var index; var value; for (index = 0; index < keys.length; index += 1) { value = source[keys[index]]; if (value !== undefined && value !== null && value !== '') return value; } return fallback || ''; } function escapeHtml(value) { // 世系人物信息进入 innerHTML 前统一转义,避免姓名和简介污染页面结构。 return String(value === undefined || value === null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function query(selector, rootNode) { return (rootNode || documentRef).querySelector(selector); } function queryAll(selector, rootNode) { return Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)); } function showMessage(message) { if (root.layui && root.layui.layer) { root.layui.layer.msg(message); return; } root.alert(message); } function getQueryParam(search, name) { var params = new URLSearchParams(String(search || '').replace(/^\?/, '')); return params.get(name) || ''; } function getCurrentGenealogyId() { var search = root.location && root.location.search; var page = query('[data-lineage-page], [data-family-home-page]'); return getQueryParam(search, 'id') || getQueryParam(search, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || ''; } function toNumber(value) { var text = String(value === undefined || value === null ? '' : value).trim(); var number = Number(text); return text && Number.isFinite(number) ? number : undefined; } function trimOrUndefined(value) { var text = String(value === undefined || value === null ? '' : value).trim(); return text || undefined; } function formatSex(value) { var text = String(value === undefined || value === null ? '' : value); if (text === '0' || text === '男') return '男'; if (text === '1' || text === '女') return '女'; return '未知'; } function getPersonId(item) { return pick(item, ['personId', 'lineagePersonId', 'id'], ''); } function buildLineagePersonBody(values) { // LineagePersonBody 来自 APP.openapi.json,空值统一转为 undefined,避免提交无意义空字符串。 var source = values || {}; return { appUserId: toNumber(source.appUserId), personNo: trimOrUndefined(source.personNo), personName: String(source.personName || '').trim(), aliasName: trimOrUndefined(source.aliasName), sex: String(source.sex || '').trim() || '0', generationNo: toNumber(source.generationNo), generationName: trimOrUndefined(source.generationName), avatarOssId: toNumber(source.avatarOssId), birthDate: trimOrUndefined(source.birthDate), deathDate: trimOrUndefined(source.deathDate), introduction: trimOrUndefined(source.introduction) }; } function buildPersonRow(item) { var personId = getPersonId(item); var name = pick(item, ['personName', 'name', 'realName'], '未命名成员'); var generationNo = pick(item, ['generationNo', 'generation', 'generationIndex'], '-'); var generationName = pick(item, ['generationName', 'generationText'], '未设置'); var sex = formatSex(pick(item, ['sex', 'gender'], '')); var bound = pick(item, ['boundAccount', 'bindAccount', 'hasAccount', 'appUserId'], '') ? '已绑定账号' : '未绑定账号'; return ''; } function getChildren(item) { var source = item || {}; return normalizeList(source.children || source.childList || source.descendants); } function buildTreeNode(item) { var personId = getPersonId(item); var name = pick(item, ['personName', 'name', 'realName'], '未命名成员'); var generationNo = pick(item, ['generationNo', 'generation', 'generationIndex'], '-'); var children = getChildren(item); var html = '
  • '; if (children.length) { html += ''; } return html + '
  • '; } function buildHomeSummary(detail, rootCount) { // 家谱主页摘要优先展示后端家谱信息,世系根节点数量来自树接口。 var source = detail || {}; var genealogyNo = pick(source, ['genealogyNo', 'genealogyCode', 'code'], '未设置编号'); var hallName = pick(source, ['hallName', 'ancestralHall', 'tanghao'], '未设置'); var memberCount = pick(source, ['memberCount', 'personCount', 'members'], '0'); return genealogyNo + ' · 堂号 ' + hallName + ' · 共 ' + memberCount + ' 人 · ' + rootCount + ' 个世系根节点'; } function renderTree(items) { var container = query('[data-lineage-tree], [data-lineage-home-tree]'); var list = normalizeList(items); if (!container) return; if (!list.length) { container.innerHTML = '
    暂无世系树数据
    '; return; } container.innerHTML = ''; } function renderPersons(items) { var container = query('[data-lineage-list]'); var list = normalizeList(items); if (!container) return; if (!list.length) { container.innerHTML = '
    暂无世系人物
    '; return; } container.innerHTML = list.map(buildPersonRow).join(''); } function renderHomeDetail(detail, rootCount) { var title = query('[data-family-home-title]'); var summary = query('[data-family-home-summary]'); var source = detail || {}; if (title) title.textContent = pick(source, ['genealogyName', 'name'], title.textContent || '家谱主页'); if (summary) summary.textContent = buildHomeSummary(source, rootCount); } function renderDetail(item) { var container = query('[data-lineage-detail]'); var source = item || {}; var name = pick(source, ['personName', 'name', 'realName'], '未命名成员'); var intro = pick(source, ['introduction', 'intro', 'remark'], '暂无简介'); if (!container) return; container.innerHTML = '
    ' + '

    姓名' + escapeHtml(name) + '' + escapeHtml(formatSex(pick(source, ['sex', 'gender'], ''))) + '

    ' + '

    字辈' + escapeHtml(pick(source, ['generationName', 'generationText'], '未设置')) + '第 ' + escapeHtml(pick(source, ['generationNo', 'generation'], '-')) + ' 世

    ' + '

    生卒' + escapeHtml(pick(source, ['birthDate'], '未填写')) + '' + escapeHtml(pick(source, ['deathDate'], '健在或未填')) + '

    ' + '

    简介' + escapeHtml(intro) + '世系资料

    ' + '
    '; } function getFormValues(form) { var values = {}; queryAll('[name]', form).forEach(function (field) { values[field.name] = field.value; }); return values; } async function loadLineage() { var api = getApi(); var genealogyId = getCurrentGenealogyId(); var keyword = query('[data-lineage-keyword]'); var homeSummary = query('[data-family-home-summary]'); var treeData; var treeList; var queryData = { pageNum: 1, pageSize: 50 }; if (!api) return; if (!genealogyId) { renderTree([]); renderPersons([]); showMessage('请从具体家谱进入世系图'); return; } if (keyword && keyword.value.trim()) queryData.keyword = keyword.value.trim(); try { treeData = await api.lineageTree(genealogyId); treeList = normalizeList(treeData); renderTree(treeData); if (homeSummary && api.genealogyDetail) { renderHomeDetail(await api.genealogyDetail(genealogyId), treeList.length); } if (query('[data-lineage-list]')) { renderPersons(await api.lineagePersonsPage(genealogyId, queryData)); } } catch (error) { showMessage(error.message || '世系数据加载失败'); } } async function loadPersonDetail(personId) { var api = getApi(); var genealogyId = getCurrentGenealogyId(); if (!api || !genealogyId || !personId) return; selectedPersonId = personId; queryAll('[data-lineage-person]').forEach(function (item) { item.classList.toggle('is-selected', item.getAttribute('data-lineage-person') === String(personId)); }); try { renderDetail(await api.lineagePersonDetail(genealogyId, personId)); } catch (error) { showMessage(error.message || '人物详情加载失败'); } } async function submitPerson(form) { var api = getApi(); var genealogyId = getCurrentGenealogyId(); var body = buildLineagePersonBody(getFormValues(form)); if (!api || !genealogyId) { showMessage('请从具体家谱进入世系图'); return; } if (!body.personName) { showMessage('请填写成员姓名'); return; } try { await api.createLineagePerson(genealogyId, body); form.reset(); showMessage('世系人物已新增'); loadLineage(); } catch (error) { showMessage(error.message || '新增世系人物失败'); } } async function createRelation(relationType) { var api = getApi(); var genealogyId = getCurrentGenealogyId(); var relationNames = { parents: '父母', spouses: '配偶', children: '子女', siblings: '兄弟姐妹' }; var personName; if (!api || !genealogyId || !selectedPersonId) { showMessage('请先在成员列表中选择一个成员'); return; } personName = root.prompt('请输入要添加的' + (relationNames[relationType] || '亲属') + '姓名', ''); if (!personName) return; try { await api.addLineageRelation(genealogyId, selectedPersonId, relationType, buildLineagePersonBody({ personName: personName })); showMessage('亲属关系已新增'); loadLineage(); } catch (error) { showMessage(error.message || '新增亲属关系失败'); } } function bindActions() { if (!documentRef) return; documentRef.addEventListener('submit', function (event) { var form = event.target.closest('[data-lineage-form]'); if (!form) return; event.preventDefault(); submitPerson(form); }); documentRef.addEventListener('click', function (event) { var personButton = event.target.closest('[data-lineage-person]'); var searchButton = event.target.closest('[data-lineage-search]'); var relationButton = event.target.closest('[data-lineage-relation]'); if (personButton) { event.preventDefault(); loadPersonDetail(personButton.getAttribute('data-lineage-person')); } if (searchButton) { event.preventDefault(); loadLineage(); } if (relationButton) { event.preventDefault(); createRelation(relationButton.getAttribute('data-lineage-relation')); } }); } function init() { if (!documentRef) return; bindActions(); loadLineage(); } return { normalizeList: normalizeList, buildLineagePersonBody: buildLineagePersonBody, formatSex: formatSex, buildPersonRow: buildPersonRow, buildTreeNode: buildTreeNode, buildHomeSummary: buildHomeSummary, init: init }; });