家谱现有接口调试50%
This commit is contained in:
@@ -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, '&')
|
||||
.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 '<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
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user