家谱现有接口调试50%

This commit is contained in:
rain
2026-07-09 17:29:25 +08:00
commit 6050508144
262 changed files with 63354 additions and 0 deletions
+400
View File
@@ -0,0 +1,400 @@
(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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
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 '<button class="module-row lineage-row" type="button" data-lineage-person="' + escapeHtml(personId) + '">' +
'<div><h3>' + escapeHtml(name) + '</h3><p>第 ' + escapeHtml(generationNo) + ' 世 · ' + escapeHtml(generationName) + '字辈 · ' + escapeHtml(sex) + ' · ' + escapeHtml(bound) + '</p></div>' +
'<span class="pill">查看资料</span>' +
'</button>';
}
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 = '<li><button type="button" class="lineage-node" data-lineage-person="' + escapeHtml(personId) + '">' +
'<strong>' + escapeHtml(name) + '</strong><span>第 ' + escapeHtml(generationNo) + ' 世</span></button>';
if (children.length) {
html += '<ul>' + children.map(buildTreeNode).join('') + '</ul>';
}
return html + '</li>';
}
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 = '<div class="api-empty">暂无世系树数据</div>';
return;
}
container.innerHTML = '<ul class="lineage-tree">' + list.map(buildTreeNode).join('') + '</ul>';
}
function renderPersons(items) {
var container = query('[data-lineage-list]');
var list = normalizeList(items);
if (!container) return;
if (!list.length) {
container.innerHTML = '<div class="api-empty">暂无世系人物</div>';
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 = '<div class="form-like">' +
'<p><span>姓名</span><b>' + escapeHtml(name) + '</b><em>' + escapeHtml(formatSex(pick(source, ['sex', 'gender'], ''))) + '</em></p>' +
'<p><span>字辈</span><b>' + escapeHtml(pick(source, ['generationName', 'generationText'], '未设置')) + '</b><em>第 ' + escapeHtml(pick(source, ['generationNo', 'generation'], '-')) + ' 世</em></p>' +
'<p><span>生卒</span><b>' + escapeHtml(pick(source, ['birthDate'], '未填写')) + '</b><em>' + escapeHtml(pick(source, ['deathDate'], '健在或未填')) + '</em></p>' +
'<p><span>简介</span><b>' + escapeHtml(intro) + '</b><em>世系资料</em></p>' +
'</div>';
}
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
};
});