(function (root, factory) {
// 家谱模块脚本同时支持浏览器运行和 Node 单元测试。
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.GenealogyPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.GenealogyPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
function normalizeList(data) {
// 列表接口可能返回数组、rows、records 或 list,这里统一成数组供页面渲染。
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 buildGenealogyBody(values) {
// 创建家谱接口必填 genealogyName、surname、regionCode,地区编码由地区选择器回填。
var source = values || {};
return {
genealogyName: source.genealogyName || '',
surname: source.surname || '',
regionCode: source.regionCode || '',
addressDetail: source.addressDetail || '',
intro: source.intro || '',
visibility: source.visibility || '1',
joinMode: source.joinMode || '1'
};
}
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 readForm(form) {
// 页面表单 name 与接口字段对齐,后续加字段只需要补 HTML。
var values = {};
queryAll('input[name], textarea[name], select[name]', form).forEach(function (field) {
values[field.name] = String(field.value || '').trim();
});
return values;
}
function getQueryParam(search, name) {
// 详情页和加入页用 query 参数传递 genealogyId,测试环境也可直接传 search 字符串。
var params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
return params.get(name) || '';
}
function getCurrentGenealogyId() {
var search = root.location && root.location.search;
return getQueryParam(search, 'id') || getQueryParam(search, 'genealogyId');
}
function escapeHtml(value) {
// 接口内容进入 innerHTML 前统一转义,避免公开页面渲染异常内容。
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function setBusy(button, busy) {
// 加载状态只加 class,样式统一写在 CSS 中。
if (!button) return;
button.classList.toggle('is-loading', busy);
if ('disabled' in button) button.disabled = busy;
button.setAttribute('aria-busy', busy ? 'true' : 'false');
}
function showMessage(message) {
if (root.layui && root.layui.layer) {
root.layui.layer.msg(message);
return;
}
root.alert(message);
}
function requireValue(value, message) {
if (value) return true;
showMessage(message);
return false;
}
function buildFamilyHeroTitle(detail) {
return pick(detail, ['genealogyName', 'name', 'title'], '未命名家谱');
}
function formatJoinMode(value) {
var map = {
0: '关闭加入',
1: '审核加入',
2: '邀请码加入'
};
return map[value] || value || '多人共修';
}
function buildFamilySummary(detail) {
// 后端详情字段可能来自统计字段或普通展示字段,这里集中做字段映射。
var memberCount = pick(detail, ['memberCount', 'membersCount'], '0');
var generationCount = pick(detail, ['generationCount', 'generationTotal'], '0');
var articleCount = pick(detail, ['articleCount', 'articlesCount'], '0');
var albumCount = pick(detail, ['albumCount', 'albumsCount'], '0');
return [
{ label: '成员', value: memberCount },
{ label: '世代', value: generationCount },
{ label: '谱文', value: articleCount },
{ label: '相册', value: albumCount }
];
}
function buildJoinApplyBody(values) {
// 接口文档要求 applicantName、phone、relationDesc、applyReason,可选 inviterUserId。
var source = values || {};
var inviterUserId = source.inviterUserId ? Number(source.inviterUserId) : undefined;
return {
applicantName: String(source.applicantName || '').trim(),
phone: String(source.phone || '').trim(),
relationDesc: String(source.relationDesc || '').trim(),
applyReason: String(source.applyReason || '').trim() || '申请加入家谱',
inviterUserId: inviterUserId
};
}
function renderPublicGenealogies(items) {
// 广场页复用现有卡片结构,只替换接口返回的数据。
var container = query('[data-genealogy-list="public"]');
var html;
if (!container) return;
if (!items.length) {
container.innerHTML = '
暂无公开家谱
';
return;
}
html = items.slice(0, 6).map(function (item, index) {
// 第一条沿用推荐卡片样式,其余走普通家谱卡片样式。
var id = pick(item, ['genealogyId', 'id'], '');
var name = pick(item, ['genealogyName', 'name', 'title'], '未命名家谱');
var surname = pick(item, ['surname'], '家谱');
var region = pick(item, ['addressDetail', 'regionName', 'originPlace'], '地区待完善');
var intro = pick(item, ['intro', 'description', 'summary'], '家谱资料正在完善中。');
var className = index === 0 ? 'family-card featured tilt-card' : 'card family-lite tilt-card';
return '' +
'' + name + '
' +
'' + intro + '
' +
'' + region + '' + surname + '
' +
'';
}).join('');
container.innerHTML = html;
}
function buildMyGenealogyRow(item) {
// 我的家谱列表保持 profile-module.css 的 module-row 结构。
var id = pick(item, ['genealogyId', 'id'], '');
var name = pick(item, ['genealogyName', 'name'], '未命名家谱');
var role = pick(item, ['roleName', 'memberRole'], '成员');
var count = pick(item, ['memberCount', 'membersCount'], '0');
return '' +
'' + name + '
' + role + ' · 共修入 ' + count + ' 人
' +
'进入主页';
}
function renderMyGenealogies(items) {
// 没有数据时显示公共空状态,避免保留旧静态示例误导用户。
var container = query('[data-genealogy-list="mine"]');
if (!container) return;
if (!items.length) {
container.innerHTML = '你还没有创建或加入家谱
';
return;
}
container.innerHTML = items.map(buildMyGenealogyRow).join('');
}
function renderFamilyDetail(detail) {
var page = query('[data-family-detail]');
var summaryGrid = query('[data-family-summary]');
var joinLink = query('[data-family-join-link]');
var id;
if (!page) return;
id = pick(detail, ['genealogyId', 'id'], getCurrentGenealogyId());
queryAll('[data-family-text]').forEach(function (node) {
var field = node.getAttribute('data-family-text');
var fallback = node.getAttribute('data-fallback') || '';
var value = field === 'title'
? buildFamilyHeroTitle(detail)
: pick(detail, field.split('|'), fallback);
node.textContent = field.indexOf('joinMode') !== -1 ? formatJoinMode(value) : value;
});
if (summaryGrid) {
summaryGrid.innerHTML = buildFamilySummary(detail).map(function (item) {
return '' + escapeHtml(item.value) + '' + escapeHtml(item.label) + '';
}).join('');
}
if (joinLink && id) {
joinLink.href = 'join-genealogy.html?id=' + encodeURIComponent(id);
}
}
async function loadFamilyDetail() {
// 只有详情页存在 data-family-detail,且 URL 带 id/genealogyId 时才请求接口。
var api = getApi();
var page = query('[data-family-detail]');
var genealogyId = getCurrentGenealogyId();
if (!api || !page) return;
if (!genealogyId) {
showMessage('缺少家谱ID');
return;
}
page.classList.add('is-loading');
try {
renderFamilyDetail(await api.genealogyDetail(genealogyId));
} catch (error) {
showMessage(error.message || '家谱详情加载失败');
} finally {
page.classList.remove('is-loading');
}
}
async function submitJoinApply(form, button) {
var api = getApi();
var values = readForm(form);
var genealogyId = values.genealogyId || getCurrentGenealogyId();
var body = buildJoinApplyBody(values);
if (!api) return;
if (!requireValue(genealogyId, '请填写家谱ID或邀请码')) return;
if (!requireValue(body.applicantName, '请填写你的姓名')) return;
if (!requireValue(body.phone, '请填写手机号')) return;
if (!requireValue(body.relationDesc, '请填写关系说明')) return;
setBusy(button, true);
try {
await api.applyJoinGenealogy(genealogyId, body);
showMessage('申请已提交,请等待管理员审核');
root.location.href = form.getAttribute('data-success-url') || 'profile-join-family.html';
} catch (error) {
showMessage(error.message || '申请提交失败');
} finally {
setBusy(button, false);
}
}
async function loadPublicGenealogies() {
// 只有带 data-genealogy-list="public" 的页面才会真正发起请求。
var api = getApi();
var container = query('[data-genealogy-list="public"]');
if (!api || !container) return;
container.classList.add('is-loading');
try {
renderPublicGenealogies(normalizeList(await api.publicGenealogies()));
} catch (error) {
showMessage(error.message || '公开家谱加载失败');
} finally {
container.classList.remove('is-loading');
}
}
async function loadMyGenealogies() {
// 只有个人中心“我的家谱”页面会命中这个容器。
var api = getApi();
var container = query('[data-genealogy-list="mine"]');
if (!api || !container) return;
container.classList.add('is-loading');
try {
renderMyGenealogies(normalizeList(await api.myGenealogies()));
} catch (error) {
showMessage(error.message || '我的家谱加载失败');
} finally {
container.classList.remove('is-loading');
}
}
async function submitGenealogy(form, button) {
// 创建成功后跳到我的家谱列表,后续继续维护成员和内容。
var api = getApi();
var body = buildGenealogyBody(readForm(form));
if (!api) return;
if (!requireValue(body.genealogyName, '请填写家谱名称')) return;
if (!requireValue(body.surname, '请填写主要姓氏')) return;
if (!requireValue(body.regionCode, '请选择家族所在地')) return;
setBusy(button, true);
try {
await api.createGenealogy(body);
showMessage('家谱创建成功');
root.location.href = form.getAttribute('data-success-url') || 'profile-families.html';
} catch (error) {
showMessage(error.message || '家谱创建失败');
} finally {
setBusy(button, false);
}
}
function bindCreateForm() {
// 创建表单用 submit 事件,兼容按钮点击和键盘回车提交。
var form = query('[data-genealogy-form="create"]');
if (!form) return;
form.addEventListener('submit', function (event) {
event.preventDefault();
submitGenealogy(form, query('[type="submit"]', form));
});
}
function bindJoinForm() {
// 加入页用同一个 submit 入口,兼容详情页带 id 跳转和用户手动输入家谱ID。
var form = query('[data-genealogy-form="join"]');
var idField;
var currentId;
if (!form) return;
idField = query('[name="genealogyId"]', form);
currentId = getCurrentGenealogyId();
if (idField && currentId && !idField.value) idField.value = currentId;
form.addEventListener('submit', function (event) {
event.preventDefault();
submitJoinApply(form, query('[type="submit"]', form));
});
}
function init() {
// 一个模块脚本服务多个家谱页面,通过 data 属性决定当前页面要做什么。
if (!documentRef) return;
bindCreateForm();
bindJoinForm();
loadPublicGenealogies();
loadMyGenealogies();
loadFamilyDetail();
}
return {
normalizeList: normalizeList,
pick: pick,
buildGenealogyBody: buildGenealogyBody,
getQueryParam: getQueryParam,
buildFamilyHeroTitle: buildFamilyHeroTitle,
buildFamilySummary: buildFamilySummary,
formatJoinMode: formatJoinMode,
buildJoinApplyBody: buildJoinApplyBody,
buildMyGenealogyRow: buildMyGenealogyRow,
init: init
};
});