350 lines
10 KiB
JavaScript
350 lines
10 KiB
JavaScript
(function (root, factory) {
|
|
// 字辈谱模块同时支持浏览器页面和 Node 单元测试。
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory(root);
|
|
return;
|
|
}
|
|
|
|
root.GenerationPages = factory(root);
|
|
if (root.document) {
|
|
root.document.addEventListener('DOMContentLoaded', function () {
|
|
root.GenerationPages.init();
|
|
});
|
|
}
|
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
|
'use strict';
|
|
|
|
var documentRef = root.document;
|
|
|
|
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) {
|
|
if (!documentRef) return null;
|
|
return (rootNode || documentRef).querySelector(selector);
|
|
}
|
|
|
|
function queryAll(selector, rootNode) {
|
|
if (!documentRef) return [];
|
|
return 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() {
|
|
var search = root.location && root.location.search;
|
|
var page = query('[data-generation-page]');
|
|
|
|
return getQueryParam(search, 'id') ||
|
|
getQueryParam(search, 'genealogyId') ||
|
|
(page && page.getAttribute('data-genealogy-id')) ||
|
|
'';
|
|
}
|
|
|
|
function toNumber(value, fallback) {
|
|
var number = Number(String(value || '').trim());
|
|
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
}
|
|
|
|
function toBoolean(value) {
|
|
return value === true || value === 'true' || value === '1' || value === 'on';
|
|
}
|
|
|
|
function buildGenerationBody(values) {
|
|
// GenerationPoemBody 必填 generationNo、generationText,其余字段给接口默认值。
|
|
var source = values || {};
|
|
var generationNo = toNumber(source.generationNo, 1);
|
|
|
|
return {
|
|
generationNo: generationNo,
|
|
generationText: String(source.generationText || '').trim(),
|
|
description: String(source.description || '').trim(),
|
|
sortOrder: toNumber(source.sortOrder, generationNo),
|
|
status: String(source.status || '').trim() || '0'
|
|
};
|
|
}
|
|
|
|
function buildGenerationBatchBody(values) {
|
|
// GenerationPoemBatchBody 只包含批量文本和是否遇到断代即停止。
|
|
var source = values || {};
|
|
|
|
return {
|
|
content: String(source.content || '').trim(),
|
|
stopMissingOldGeneration: toBoolean(source.stopMissingOldGeneration)
|
|
};
|
|
}
|
|
|
|
function getFormValues(form) {
|
|
var values = {};
|
|
|
|
queryAll('[name]', form).forEach(function (field) {
|
|
if (field.type === 'checkbox') {
|
|
values[field.name] = field.checked ? 'on' : '';
|
|
return;
|
|
}
|
|
|
|
values[field.name] = field.value;
|
|
});
|
|
|
|
return values;
|
|
}
|
|
|
|
function setBatchStatus(message) {
|
|
var statusNode = query('[data-generation-batch-status]');
|
|
|
|
if (statusNode) {
|
|
statusNode.textContent = message || '';
|
|
}
|
|
}
|
|
|
|
function buildGenerationRow(item, genealogyId) {
|
|
var poemId = pick(item, ['poemId', 'id'], '');
|
|
var generationNo = pick(item, ['generationNo', 'generation', 'sortOrder'], '1');
|
|
var text = pick(item, ['generationText', 'content', 'text'], '未设置');
|
|
var memberCount = pick(item, ['memberCount', 'personCount', 'count'], '0');
|
|
var description = pick(item, ['description', 'remark'], '暂无备注');
|
|
|
|
return '<div class="module-row generation-row" data-generation-id="' + escapeHtml(poemId) + '">' +
|
|
'<div><h3>第 ' + escapeHtml(generationNo) + ' 代:' + escapeHtml(text) + '</h3><p>' + escapeHtml(memberCount) + ' 人 · ' + escapeHtml(description) + '</p></div>' +
|
|
'<button class="pill" type="button" data-generation-edit="' + escapeHtml(poemId) + '" data-generation-no="' + escapeHtml(generationNo) + '" data-generation-text="' + escapeHtml(text) + '" data-genealogy-id="' + escapeHtml(genealogyId) + '">编辑</button>' +
|
|
'</div>';
|
|
}
|
|
|
|
function renderGenerations(items, genealogyId) {
|
|
var container = query('[data-generation-list]');
|
|
|
|
if (!container) return;
|
|
if (!items.length) {
|
|
container.innerHTML = '<div class="api-empty">暂无字辈记录</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = items.map(function (item) {
|
|
return buildGenerationRow(item, genealogyId);
|
|
}).join('');
|
|
}
|
|
|
|
function renderBatchPreview(data) {
|
|
var container = query('[data-generation-batch-preview]');
|
|
var items = normalizeList(data);
|
|
|
|
if (!container) return;
|
|
|
|
if (!items.length) {
|
|
container.innerHTML = '<div class="api-empty">预览完成,接口未返回明细</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = items.map(function (item) {
|
|
return buildGenerationRow(item, getCurrentGenealogyId());
|
|
}).join('');
|
|
}
|
|
|
|
async function loadGenerations() {
|
|
var api = getApi();
|
|
var container = query('[data-generation-list]');
|
|
var genealogyId = getCurrentGenealogyId();
|
|
|
|
if (!api || !container) return;
|
|
if (!genealogyId) {
|
|
container.innerHTML = '<div class="api-empty">请从具体家谱进入字辈谱</div>';
|
|
return;
|
|
}
|
|
|
|
container.classList.add('is-loading');
|
|
try {
|
|
renderGenerations(normalizeList(await api.generationPoems(genealogyId)), genealogyId);
|
|
} catch (error) {
|
|
showMessage(error.message || '字辈谱加载失败');
|
|
} finally {
|
|
container.classList.remove('is-loading');
|
|
}
|
|
}
|
|
|
|
async function createGeneration() {
|
|
var api = getApi();
|
|
var genealogyId = getCurrentGenealogyId();
|
|
var generationNo;
|
|
var generationText;
|
|
var body;
|
|
|
|
if (!api || !genealogyId) {
|
|
showMessage('请从具体家谱进入字辈谱');
|
|
return;
|
|
}
|
|
|
|
generationNo = root.prompt('请输入世代序号', '');
|
|
if (!generationNo) return;
|
|
generationText = root.prompt('请输入字辈内容', '');
|
|
if (!generationText) return;
|
|
|
|
body = buildGenerationBody({
|
|
generationNo: generationNo,
|
|
generationText: generationText
|
|
});
|
|
|
|
try {
|
|
await api.createGenerationPoem(genealogyId, body);
|
|
showMessage('字辈已新增');
|
|
loadGenerations();
|
|
} catch (error) {
|
|
showMessage(error.message || '新增字辈失败');
|
|
}
|
|
}
|
|
|
|
async function editGeneration(button) {
|
|
var api = getApi();
|
|
var genealogyId = button.getAttribute('data-genealogy-id') || getCurrentGenealogyId();
|
|
var poemId = button.getAttribute('data-generation-edit');
|
|
var currentText = button.getAttribute('data-generation-text') || '';
|
|
var generationNo = button.getAttribute('data-generation-no') || '1';
|
|
var nextText;
|
|
|
|
if (!api || !genealogyId || !poemId) return;
|
|
|
|
nextText = root.prompt('请输入新的字辈', currentText);
|
|
if (!nextText) return;
|
|
|
|
button.classList.add('is-loading');
|
|
try {
|
|
await api.updateGenerationPoem(genealogyId, poemId, buildGenerationBody({
|
|
generationNo: generationNo,
|
|
generationText: nextText
|
|
}));
|
|
showMessage('字辈已更新');
|
|
loadGenerations();
|
|
} catch (error) {
|
|
showMessage(error.message || '更新字辈失败');
|
|
} finally {
|
|
button.classList.remove('is-loading');
|
|
}
|
|
}
|
|
|
|
async function submitGenerationBatch(action) {
|
|
var api = getApi();
|
|
var genealogyId = getCurrentGenealogyId();
|
|
var form = query('[data-generation-batch-form]');
|
|
var body;
|
|
var result;
|
|
|
|
if (!api || !genealogyId || !form) {
|
|
showMessage('请从具体家谱进入字辈谱');
|
|
return;
|
|
}
|
|
|
|
body = buildGenerationBatchBody(getFormValues(form));
|
|
if (!body.content) {
|
|
showMessage('请填写批量字辈内容');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setBatchStatus(action === 'save' ? '批量保存中...' : '批量预览中...');
|
|
|
|
if (action === 'save') {
|
|
await api.saveGenerationPoemsBatch(genealogyId, body);
|
|
setBatchStatus('批量保存完成');
|
|
showMessage('批量字辈已保存');
|
|
loadGenerations();
|
|
return;
|
|
}
|
|
|
|
result = await api.previewGenerationPoems(genealogyId, body);
|
|
renderBatchPreview(result);
|
|
setBatchStatus('批量预览完成');
|
|
} catch (error) {
|
|
setBatchStatus('批量处理失败');
|
|
showMessage(error.message || '批量字辈处理失败');
|
|
}
|
|
}
|
|
|
|
function bindActions() {
|
|
if (!documentRef) return;
|
|
|
|
documentRef.addEventListener('click', function (event) {
|
|
var addButton = event.target.closest('[data-generation-add]');
|
|
var editButton = event.target.closest('[data-generation-edit]');
|
|
var batchButton = event.target.closest('[data-generation-batch-action]');
|
|
|
|
if (addButton) {
|
|
event.preventDefault();
|
|
createGeneration();
|
|
}
|
|
|
|
if (editButton) {
|
|
event.preventDefault();
|
|
editGeneration(editButton);
|
|
}
|
|
|
|
if (batchButton) {
|
|
event.preventDefault();
|
|
submitGenerationBatch(batchButton.getAttribute('data-generation-batch-action'));
|
|
}
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
if (!documentRef) return;
|
|
|
|
bindActions();
|
|
loadGenerations();
|
|
}
|
|
|
|
return {
|
|
normalizeList: normalizeList,
|
|
buildGenerationBody: buildGenerationBody,
|
|
buildGenerationBatchBody: buildGenerationBatchBody,
|
|
buildGenerationRow: buildGenerationRow,
|
|
init: init
|
|
};
|
|
});
|