完成10%

This commit is contained in:
rain
2026-07-24 18:11:16 +08:00
parent 8716a68fdb
commit 8870136d1b
33 changed files with 3853 additions and 336 deletions
+601
View File
@@ -0,0 +1,601 @@
(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;
var poemsById = Object.create(null);
var lastPreviewSignature = '';
var managementAccess = true;
var writePending = false;
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function isForbidden(error) {
return Number(error && (error.status || error.code)) === 403;
}
function redirectUnauthorized(api, error) {
if (!shouldRedirectToLogin(api, error)) return false;
if (api && api.clearToken) api.clearToken();
if (!root.location) return true;
if (typeof root.location.replace === 'function') {
root.location.replace('login.html');
return true;
}
root.location.href = 'login.html';
return true;
}
function query(selector, rootNode) {
return documentRef ? (rootNode || documentRef).querySelector(selector) : null;
}
function queryAll(selector, rootNode) {
return documentRef ? 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(search) {
var page = query('[data-generation-page]');
var source = search === undefined && root.location ? root.location.search : search;
var profileGenealogyId;
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
profileGenealogyId = root.ProfileUI.getGenealogyId();
if (profileGenealogyId) return profileGenealogyId;
}
return getQueryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || '';
}
function trimOrUndefined(value) {
var text = String(value === undefined || value === null ? '' : value).trim();
return text || undefined;
}
function toIntegerOrUndefined(value) {
var text = trimOrUndefined(value);
var number;
if (!text) return undefined;
number = Number(text);
return Number.isInteger(number) && Number.isSafeInteger(number) ? number : undefined;
}
function buildGenerationPoemBody(values) {
var source = values || {};
var body = {
generationNo: toIntegerOrUndefined(source.generationNo),
generationText: trimOrUndefined(source.generationText)
};
var description = trimOrUndefined(source.description);
var sortOrder = trimOrUndefined(source.sortOrder);
var status = trimOrUndefined(source.status);
if (description !== undefined) body.description = description;
if (sortOrder !== undefined) body.sortOrder = Number(sortOrder);
if (status !== undefined) body.status = status;
return body;
}
function buildBatchBody(values) {
var source = values || {};
return {
poemText: String(source.poemText || '').trim(),
disableMissing: Boolean(source.disableMissing)
};
}
function validateGenerationPoemBody(body) {
if (!body || !Number.isInteger(body.generationNo) || body.generationNo < 1 || body.generationNo > 2147483647) {
return '世代序号必须是 1 到 2147483647 之间的整数';
}
if (!body.generationText) return '请填写字辈文字';
if (body.generationText.length > 50) return '字辈文字不能超过 50 个字符';
if (body.description && body.description.length > 500) return '说明不能超过 500 个字符';
if (body.sortOrder !== undefined && (!Number.isInteger(body.sortOrder) || !Number.isSafeInteger(body.sortOrder))) {
return '排序值必须是整数';
}
if (body.sortOrder !== undefined && (body.sortOrder < -2147483648 || body.sortOrder > 2147483647)) {
return '排序值必须在 -2147483648 到 2147483647 之间';
}
if (body.status !== undefined && body.status !== '0' && body.status !== '1') return '字辈状态只能是 0 或 1';
return '';
}
function validateBatchBody(body) {
var words;
if (!body || !body.poemText) return '请填写批量字辈内容';
if (body.poemText.length > 26000) return '批量字辈内容不能超过 26000 个字符';
words = splitPoemText(body.poemText);
if (words.length > 500) return '一次最多导入 500 个世代';
if (words.some(function (word) { return word.length > 50; })) return '单个字辈不能超过 50 个字符';
return '';
}
function splitPoemText(value) {
return String(value || '').trim().split(/[\s,;;、/|]+/).filter(Boolean);
}
function getPoemId(item) {
var value = item && item.poemId;
var text;
if (value === undefined || value === null || value === '') return '';
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
text = String(value);
return /^\d+$/.test(text) ? text : '';
}
function normalizeGenerationPoem(item) {
var poemId = getPoemId(item);
var generationNo = toIntegerOrUndefined(item && item.generationNo);
var generationText = trimOrUndefined(item && item.generationText);
var status = trimOrUndefined(item && item.status);
var poem;
if (!poemId || generationNo === undefined || !generationText || (status !== '0' && status !== '1')) return null;
poem = {
poemId: poemId,
generationNo: generationNo,
generationText: generationText,
status: status
};
if (item.description !== undefined && item.description !== null) poem.description = String(item.description);
if (toIntegerOrUndefined(item.sortOrder) !== undefined) poem.sortOrder = toIntegerOrUndefined(item.sortOrder);
return poem;
}
function normalizeList(data) {
if (Array.isArray(data)) return data;
if (data && Array.isArray(data.rows)) return data.rows;
return [];
}
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderGenerationPoem(poem) {
var details = [];
var stateText = poem.status === '1' ? '已停用' : '正常';
if (poem.description) details.push('说明:' + poem.description);
if (poem.sortOrder !== undefined) details.push('排序:' + poem.sortOrder);
details.push('状态:' + stateText);
return '<div class="module-row" data-generation-poem-id="' + escapeHtml(poem.poemId) + '">' +
'<div><h3>第 ' + escapeHtml(poem.generationNo) + ' 代:' + escapeHtml(poem.generationText) + '</h3><p>' + escapeHtml(details.join(' · ')) + '</p></div>' +
'<div class="row-actions">' +
'<button class="pill" type="button" data-generation-edit="' + escapeHtml(poem.poemId) + '">编辑</button>' +
'<button class="pill" type="button" data-generation-status="' + escapeHtml(poem.poemId) + '">' + (poem.status === '1' ? '恢复' : '停用') + '</button>' +
'</div></div>';
}
function renderGenerationPoems(data) {
var container = query('[data-generation-list]');
var normalized = normalizeList(data).map(normalizeGenerationPoem);
var invalidCount = normalized.filter(function (item) { return !item; }).length;
var poems = normalized.filter(Boolean);
poemsById = Object.create(null);
poems.forEach(function (poem) {
poemsById[poem.poemId] = poem;
});
if (!container) return;
if (invalidCount) {
container.innerHTML = '<div class="api-empty">字辈响应缺少 poemId、generationNo、generationText 或 status,请联系后端补充 DTO。</div>';
return;
}
if (!poems.length) {
container.innerHTML = '<div class="api-empty">暂无字辈记录</div>';
return;
}
container.innerHTML = poems.map(renderGenerationPoem).join('');
}
function normalizePreview(data) {
if (!data || !Array.isArray(data.items)) return null;
return {
createCount: toIntegerOrUndefined(data.createCount),
updateCount: toIntegerOrUndefined(data.updateCount),
keepCount: toIntegerOrUndefined(data.keepCount),
disableCount: toIntegerOrUndefined(data.disableCount),
items: data.items
};
}
function renderBatchPreview(data) {
var container = query('[data-generation-batch-preview]');
var preview = normalizePreview(data);
var summary;
if (!container) return false;
if (!preview) {
container.innerHTML = '<div class="api-empty">批量预览响应缺少 items,请联系后端补充 DTO。</div>';
return false;
}
summary = '新增 ' + (preview.createCount === undefined ? '-' : preview.createCount) +
' 条 · 更新 ' + (preview.updateCount === undefined ? '-' : preview.updateCount) +
' 条 · 保留 ' + (preview.keepCount === undefined ? '-' : preview.keepCount) +
' 条 · 停用 ' + (preview.disableCount === undefined ? '-' : preview.disableCount) + ' 条';
container.innerHTML = '<div class="module-row"><div><h3>变更预览</h3><p>' + escapeHtml(summary) + '</p></div></div>' +
preview.items.map(function (item) {
var generationNo = item && item.generationNo;
var action = item && item.action;
var oldText = item && item.oldGenerationText;
var newText = item && item.newGenerationText;
var warning = item && item.warning;
var oldStatus = item && item.oldStatus;
var newStatus = item && item.newStatus;
var text = '第 ' + (generationNo === undefined || generationNo === null ? '-' : generationNo) + ' 代:' +
(oldText === undefined || oldText === null ? '(新增)' : oldText) + ' → ' +
(newText === undefined || newText === null ? '(无)' : newText);
if (action) text += ' · ' + action;
if (oldStatus !== undefined || newStatus !== undefined) {
text += ' · 状态:' + formatStatus(oldStatus) + ' → ' + formatStatus(newStatus);
}
if (warning) text += ' · 警告:' + warning;
return '<div class="module-row"><p>' + escapeHtml(text) + '</p></div>';
}).join('');
return true;
}
function formatStatus(value) {
if (String(value) === '0') return '正常';
if (String(value) === '1') return '停用';
return value === undefined || value === null ? '未提供' : String(value);
}
function getFormValues(form) {
var values = {};
queryAll('[name]', form).forEach(function (field) {
values[field.name] = field.type === 'checkbox' ? field.checked : field.value;
});
return values;
}
function setBatchStatus(message) {
var target = query('[data-generation-batch-status]');
if (target) target.textContent = message || '';
}
function refreshManagementControls() {
var disabled = !managementAccess || writePending;
queryAll('[data-generation-add], [data-generation-batch-action], [data-generation-edit], [data-generation-status]').forEach(function (control) {
control.disabled = disabled;
});
}
function setManagementEnabled(enabled) {
managementAccess = Boolean(enabled);
refreshManagementControls();
}
function setWritePending(pending) {
writePending = Boolean(pending);
refreshManagementControls();
}
function renderForbiddenState() {
var container = query('[data-generation-list]');
if (container) container.innerHTML = '<div class="api-empty">当前账号没有字辈维护权限</div>';
lastPreviewSignature = '';
setManagementEnabled(false);
setBatchStatus('当前账号没有字辈维护权限');
}
function syncGenealogyLinks(genealogyId) {
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) {
root.ProfileUI.syncGenealogyContextLinks();
return;
}
queryAll('[data-genealogy-context-link]').forEach(function (link) {
var href = link.getAttribute('href');
var base;
if (!href || href === '#') return;
base = href.split('?')[0];
link.href = base + '?genealogyId=' + encodeURIComponent(genealogyId);
});
}
function requireGenealogyId() {
var genealogyId = getCurrentGenealogyId();
var container;
if (!genealogyId) {
container = query('[data-generation-list]');
if (container) container.innerHTML = '<div class="api-empty">请从具体家谱进入字辈管理</div>';
showMessage('请从具体家谱进入字辈管理');
}
return genealogyId;
}
function promptForPoem(existing) {
var generationNo;
var generationText;
var description;
var sortOrder;
if (!root.prompt) return null;
generationNo = root.prompt('世代序号(从 1 开始)', existing ? String(existing.generationNo) : '');
if (generationNo === null) return null;
generationText = root.prompt('字辈文字(最多 50 个字符)', existing ? existing.generationText : '');
if (generationText === null) return null;
description = root.prompt('说明(最多 500 个字符,可留空)', existing && existing.description ? existing.description : '');
if (description === null) return null;
sortOrder = root.prompt('排序值(可留空,越小越靠前)', existing && existing.sortOrder !== undefined ? String(existing.sortOrder) : '');
if (sortOrder === null) return null;
return buildGenerationPoemBody({
generationNo: generationNo,
generationText: generationText,
description: description,
sortOrder: sortOrder,
status: existing && existing.status
});
}
async function loadGenerationPoems() {
var api = getApi();
var genealogyId;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncGenealogyLinks(genealogyId);
try {
renderGenerationPoems(await api.generationPoemsManagement(genealogyId));
setManagementEnabled(true);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (isForbidden(error)) {
renderForbiddenState();
return;
}
showMessage(error.message || '字辈列表加载失败');
}
}
async function savePoem(poemId) {
var api = getApi();
var genealogyId;
var existing = poemId ? poemsById[poemId] : null;
var body;
var validation;
if (writePending || !managementAccess || redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || (poemId && !existing)) return;
body = promptForPoem(existing);
if (!body) return;
validation = validateGenerationPoemBody(body);
if (validation) {
showMessage(validation);
return;
}
setWritePending(true);
try {
if (poemId) {
await api.updateGenerationPoem(genealogyId, poemId, body);
} else {
await api.createGenerationPoem(genealogyId, body);
}
showMessage('字辈已保存');
await loadGenerationPoems();
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (isForbidden(error)) {
renderForbiddenState();
return;
}
showMessage(error.message || '字辈保存失败');
} finally {
setWritePending(false);
}
}
async function togglePoemStatus(poemId) {
var api = getApi();
var genealogyId;
var existing = poemsById[poemId];
var body;
if (writePending || !managementAccess || redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !existing) return;
body = buildGenerationPoemBody({
generationNo: existing.generationNo,
generationText: existing.generationText,
description: existing.description,
sortOrder: existing.sortOrder,
status: existing.status === '1' ? '0' : '1'
});
setWritePending(true);
try {
await api.updateGenerationPoem(genealogyId, poemId, body);
showMessage(existing.status === '1' ? '字辈已恢复' : '字辈已停用');
await loadGenerationPoems();
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (isForbidden(error)) {
renderForbiddenState();
return;
}
showMessage(error.message || '字辈状态更新失败');
} finally {
setWritePending(false);
}
}
async function previewBatch() {
var api = getApi();
var genealogyId;
var form = query('[data-generation-batch-form]');
var body;
var validation;
if (writePending || !managementAccess || redirectUnauthorized(api) || !form) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
body = buildBatchBody(getFormValues(form));
validation = validateBatchBody(body);
if (validation) {
setBatchStatus(validation);
return;
}
setBatchStatus('正在预览…');
setWritePending(true);
try {
if (renderBatchPreview(await api.previewGenerationPoems(genealogyId, body))) {
lastPreviewSignature = JSON.stringify(body);
setBatchStatus('预览完成,请确认后保存');
} else {
lastPreviewSignature = '';
setBatchStatus('预览响应不完整');
}
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (isForbidden(error)) {
renderForbiddenState();
return;
}
lastPreviewSignature = '';
setBatchStatus(error.message || '批量预览失败');
} finally {
setWritePending(false);
}
}
async function saveBatch() {
var api = getApi();
var genealogyId;
var form = query('[data-generation-batch-form]');
var body;
var validation;
if (writePending || !managementAccess || redirectUnauthorized(api) || !form) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
body = buildBatchBody(getFormValues(form));
validation = validateBatchBody(body);
if (validation) {
setBatchStatus(validation);
return;
}
if (lastPreviewSignature !== JSON.stringify(body)) {
setBatchStatus('请先预览当前内容,再保存');
return;
}
if (root.confirm && !root.confirm('确认按当前预览保存字辈吗?')) return;
setBatchStatus('正在保存…');
setWritePending(true);
try {
await api.saveGenerationPoems(genealogyId, body);
lastPreviewSignature = '';
setBatchStatus('批量字辈已保存');
await loadGenerationPoems();
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (isForbidden(error)) {
renderForbiddenState();
return;
}
setBatchStatus(error.message || '批量保存失败');
} finally {
setWritePending(false);
}
}
function bindActions() {
if (!documentRef) return;
documentRef.addEventListener('click', function (event) {
var target = event.target;
var action = target.closest('[data-generation-batch-action]');
var poemButton = target.closest('[data-generation-edit], [data-generation-status]');
if (action) {
event.preventDefault();
if (action.getAttribute('data-generation-batch-action') === 'preview') previewBatch();
if (action.getAttribute('data-generation-batch-action') === 'save') saveBatch();
return;
}
if (target.closest('[data-generation-add]')) {
event.preventDefault();
savePoem('');
return;
}
if (!poemButton) return;
event.preventDefault();
if (poemButton.hasAttribute('data-generation-edit')) savePoem(poemButton.getAttribute('data-generation-edit'));
if (poemButton.hasAttribute('data-generation-status')) togglePoemStatus(poemButton.getAttribute('data-generation-status'));
});
}
function init() {
if (!documentRef) return;
bindActions();
if (query('[data-generation-page]')) loadGenerationPoems();
}
return {
getCurrentGenealogyId: getCurrentGenealogyId,
buildGenerationPoemBody: buildGenerationPoemBody,
buildBatchBody: buildBatchBody,
validateGenerationPoemBody: validateGenerationPoemBody,
validateBatchBody: validateBatchBody,
splitPoemText: splitPoemText,
normalizeGenerationPoem: normalizeGenerationPoem,
normalizePreview: normalizePreview,
normalizeList: normalizeList,
shouldRedirectToLogin: shouldRedirectToLogin,
isForbidden: isForbidden,
init: init
};
});