786 lines
28 KiB
JavaScript
786 lines
28 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';
|
||
|
||
function confirmAction(message) {
|
||
if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') {
|
||
return root.ProfileUI.confirmAction(message);
|
||
}
|
||
return Promise.resolve(!root['confirm'] || root['confirm'](message));
|
||
}
|
||
|
||
var documentRef = root.document;
|
||
var poemsById = Object.create(null);
|
||
var lastPreviewSignature = '';
|
||
var managementAccess = false;
|
||
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 setGenerationState(selector, type, message) {
|
||
var target = query(selector);
|
||
|
||
if (!target) return;
|
||
if (root.ProfileUI && root.ProfileUI.setApiState) {
|
||
root.ProfileUI.setApiState(target, type, message);
|
||
return;
|
||
}
|
||
target.innerHTML = '<div class="api-state api-state--' + escapeHtml(type) + '" data-api-state="' + escapeHtml(type) + '">' +
|
||
escapeHtml(message) + '</div>';
|
||
}
|
||
|
||
function setGenerationFormStatus(message, type) {
|
||
var target = query('[data-generation-form-status]');
|
||
|
||
if (!target) return;
|
||
if (!message) {
|
||
target.innerHTML = '';
|
||
return;
|
||
}
|
||
if (root.ProfileUI && root.ProfileUI.setApiState) {
|
||
root.ProfileUI.setApiState(target, type || 'error', message);
|
||
return;
|
||
}
|
||
target.textContent = 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) {
|
||
var text = String(value || '').trim();
|
||
|
||
if (!text) return [];
|
||
if (/[\s,,;;、//||]/.test(text)) {
|
||
return text.split(/[\s,,;;、//||]+/).filter(Boolean);
|
||
}
|
||
return Array.from(text);
|
||
}
|
||
|
||
function normalizeId(value) {
|
||
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 getPoemId(item) {
|
||
return normalizeId(item && item.poemId);
|
||
}
|
||
|
||
function normalizeGenerationPoem(item) {
|
||
var poemId = getPoemId(item);
|
||
var genealogyId = normalizeId(item && item.genealogyId);
|
||
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 (genealogyId) poem.genealogyId = genealogyId;
|
||
if (item.genealogyNo !== undefined && item.genealogyNo !== null) poem.genealogyNo = String(item.genealogyNo);
|
||
if (item.genealogyName !== undefined && item.genealogyName !== null) poem.genealogyName = String(item.genealogyName);
|
||
if (item.description !== undefined && item.description !== null) poem.description = String(item.description);
|
||
if (toIntegerOrUndefined(item.sortOrder) !== undefined) poem.sortOrder = toIntegerOrUndefined(item.sortOrder);
|
||
if (item.remark !== undefined && item.remark !== null) poem.remark = String(item.remark);
|
||
return poem;
|
||
}
|
||
|
||
function canEditGenerations(genealogy) {
|
||
return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true));
|
||
}
|
||
|
||
function normalizeGenerationAccess(genealogy) {
|
||
var canEditContent = canEditGenerations(genealogy);
|
||
|
||
return {
|
||
canEditContent: canEditContent,
|
||
listMethod: canEditContent ? 'generationPoemsManagement' : 'generationPoems'
|
||
};
|
||
}
|
||
|
||
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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
function renderGenerationPoem(poem) {
|
||
var details = [];
|
||
var stateText = poem.status === '1' ? '已停用' : '正常';
|
||
var actions = '';
|
||
|
||
if (poem.description) details.push('说明:' + poem.description);
|
||
if (poem.sortOrder !== undefined) details.push('排序:' + poem.sortOrder);
|
||
details.push('状态:' + stateText);
|
||
if (managementAccess) {
|
||
actions = '<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>';
|
||
}
|
||
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>' +
|
||
actions + '</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) {
|
||
setGenerationState('[data-generation-list]', 'error', '字辈响应缺少 poemId、generationNo、generationText 或 status,请联系后端补充 DTO。');
|
||
return;
|
||
}
|
||
if (!poems.length) {
|
||
setGenerationState('[data-generation-list]', 'empty', '暂无字辈记录');
|
||
return;
|
||
}
|
||
container.innerHTML = poems.map(renderGenerationPoem).join('');
|
||
}
|
||
|
||
function normalizeCount(value) {
|
||
var count = toIntegerOrUndefined(value);
|
||
|
||
return count !== undefined && count >= 0 ? count : undefined;
|
||
}
|
||
|
||
function normalizePreviewItem(item) {
|
||
var action = item && item.action;
|
||
var generationNo = toIntegerOrUndefined(item && item.generationNo);
|
||
var poemId = getPoemId(item);
|
||
var oldText = trimOrUndefined(item && item.oldGenerationText);
|
||
var newText = trimOrUndefined(item && item.newGenerationText);
|
||
var oldStatus = trimOrUndefined(item && item.oldStatus);
|
||
var newStatus = trimOrUndefined(item && item.newStatus);
|
||
var normalized;
|
||
|
||
if (!generationNo || ['create', 'update', 'keep', 'disable'].indexOf(action) === -1) return null;
|
||
if (newStatus !== '0' && newStatus !== '1') return null;
|
||
if (action === 'create' && !newText) return null;
|
||
if (action !== 'create' && (!poemId || !oldText || (oldStatus !== '0' && oldStatus !== '1'))) return null;
|
||
if ((action === 'update' || action === 'keep') && !newText) return null;
|
||
|
||
normalized = {
|
||
generationNo: generationNo
|
||
};
|
||
if (poemId) normalized.poemId = poemId;
|
||
if (oldText) normalized.oldGenerationText = oldText;
|
||
if (newText) normalized.newGenerationText = newText;
|
||
if (oldStatus !== undefined) normalized.oldStatus = oldStatus;
|
||
normalized.newStatus = newStatus;
|
||
normalized.action = action;
|
||
if (item.warning !== undefined && item.warning !== null) normalized.warning = String(item.warning);
|
||
return normalized;
|
||
}
|
||
|
||
function normalizePreview(data) {
|
||
var counts;
|
||
var items;
|
||
var actualCounts = { create: 0, update: 0, keep: 0, disable: 0 };
|
||
|
||
if (!data || !Array.isArray(data.items) || !data.items.length) return null;
|
||
counts = {
|
||
createCount: normalizeCount(data.createCount),
|
||
updateCount: normalizeCount(data.updateCount),
|
||
keepCount: normalizeCount(data.keepCount),
|
||
disableCount: normalizeCount(data.disableCount)
|
||
};
|
||
if (Object.keys(counts).some(function (key) { return counts[key] === undefined; })) return null;
|
||
items = data.items.map(normalizePreviewItem);
|
||
if (items.some(function (item) { return !item; })) return null;
|
||
items.forEach(function (item) { actualCounts[item.action] += 1; });
|
||
if (counts.createCount !== actualCounts.create ||
|
||
counts.updateCount !== actualCounts.update ||
|
||
counts.keepCount !== actualCounts.keep ||
|
||
counts.disableCount !== actualCounts.disable) return null;
|
||
|
||
counts.items = items;
|
||
return counts;
|
||
}
|
||
|
||
function buildBatchConfirmMessage(disableMissing) {
|
||
if (disableMissing) {
|
||
return '确认按当前预览保存字辈,并停用未出现在文本中的后续世代吗?历史记录不会删除。';
|
||
}
|
||
return '确认按当前预览保存字辈吗?';
|
||
}
|
||
|
||
function renderBatchPreview(data) {
|
||
var container = query('[data-generation-batch-preview]');
|
||
var preview = normalizePreview(data);
|
||
var summary;
|
||
|
||
if (!container) return false;
|
||
if (!preview) {
|
||
setGenerationState('[data-generation-batch-preview]', 'error', '批量预览响应缺少 items,请联系后端补充 DTO。');
|
||
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 setGenerationEditorVisible(visible) {
|
||
var editor = query('[data-generation-editor]');
|
||
|
||
if (editor) editor.hidden = !visible;
|
||
}
|
||
|
||
function setGenerationSubmitPending(pending) {
|
||
queryAll('[data-generation-editor-submit]').forEach(function (button) {
|
||
if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent;
|
||
button.disabled = Boolean(pending) || !managementAccess;
|
||
button.textContent = pending ? '正在保存…' : button.dataset.defaultLabel;
|
||
button.setAttribute('aria-busy', pending ? 'true' : 'false');
|
||
});
|
||
}
|
||
|
||
function refreshManagementControls() {
|
||
var disabled = !managementAccess || writePending;
|
||
|
||
queryAll('[data-generation-management]').forEach(function (element) {
|
||
element.hidden = !managementAccess;
|
||
});
|
||
if (!managementAccess) setGenerationEditorVisible(false);
|
||
queryAll('[data-generation-add], [data-generation-batch-action], [data-generation-edit], [data-generation-status]').forEach(function (control) {
|
||
control.disabled = disabled;
|
||
});
|
||
queryAll('[data-generation-management] textarea, [data-generation-management] input, [data-generation-editor] textarea, [data-generation-editor] input, [data-generation-editor] select, [data-generation-editor] button').forEach(function (control) {
|
||
control.disabled = disabled;
|
||
});
|
||
setGenerationSubmitPending(writePending);
|
||
}
|
||
|
||
function setManagementEnabled(enabled) {
|
||
managementAccess = Boolean(enabled);
|
||
refreshManagementControls();
|
||
}
|
||
|
||
function setWritePending(pending) {
|
||
writePending = Boolean(pending);
|
||
refreshManagementControls();
|
||
}
|
||
|
||
function renderForbiddenState() {
|
||
setGenerationState('[data-generation-list]', 'forbidden', '当前账号没有字辈维护权限');
|
||
lastPreviewSignature = '';
|
||
setManagementEnabled(false);
|
||
setBatchStatus('当前账号没有字辈维护权限');
|
||
setGenerationFormStatus('当前账号没有字辈维护权限', 'forbidden');
|
||
}
|
||
|
||
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) setGenerationState('[data-generation-list]', 'error', '请从具体家谱进入字辈管理');
|
||
setGenerationEditorVisible(false);
|
||
setGenerationFormStatus('请从具体家谱进入字辈管理', 'error');
|
||
showMessage('请从具体家谱进入字辈管理');
|
||
}
|
||
return genealogyId;
|
||
}
|
||
|
||
function openPoemEditor(poemId) {
|
||
var form = query('[data-generation-form]');
|
||
var existing = poemId ? poemsById[poemId] : null;
|
||
var title = query('[data-generation-editor-title]');
|
||
|
||
if (!managementAccess || writePending || !form || (poemId && !existing)) return;
|
||
form.reset();
|
||
queryAll('[name]', form).forEach(function (field) {
|
||
var value;
|
||
|
||
if (field.name === 'poemId') value = poemId || '';
|
||
if (field.name === 'generationNo') value = existing ? existing.generationNo : '';
|
||
if (field.name === 'generationText') value = existing ? existing.generationText : '';
|
||
if (field.name === 'description') value = existing && existing.description ? existing.description : '';
|
||
if (field.name === 'sortOrder') value = existing && existing.sortOrder !== undefined ? existing.sortOrder : '';
|
||
if (field.name === 'status') value = existing ? existing.status : '';
|
||
if (value !== undefined) field.value = value;
|
||
});
|
||
if (title) title.textContent = existing ? '编辑字辈' : '新增字辈';
|
||
setGenerationFormStatus('');
|
||
setGenerationEditorVisible(true);
|
||
if (root.location && root.location.hash !== '#generation-editor') root.location.hash = 'generation-editor';
|
||
if (query('[name="generationNo"]', form) && query('[name="generationNo"]', form).focus) {
|
||
query('[name="generationNo"]', form).focus();
|
||
}
|
||
}
|
||
|
||
function closePoemEditor() {
|
||
var form = query('[data-generation-form]');
|
||
|
||
if (form) form.reset();
|
||
setGenerationFormStatus('');
|
||
setGenerationEditorVisible(false);
|
||
}
|
||
|
||
async function loadGenerationPoems() {
|
||
var api = getApi();
|
||
var genealogyId;
|
||
var access;
|
||
|
||
if (redirectUnauthorized(api)) return;
|
||
genealogyId = requireGenealogyId();
|
||
if (!genealogyId) return;
|
||
syncGenealogyLinks(genealogyId);
|
||
setGenerationState('[data-generation-list]', 'loading', '正在加载字辈记录…');
|
||
try {
|
||
access = normalizeGenerationAccess(await api.genealogyDetail(genealogyId));
|
||
setManagementEnabled(access.canEditContent);
|
||
renderGenerationPoems(await api[access.listMethod](genealogyId));
|
||
} catch (error) {
|
||
if (redirectUnauthorized(api, error)) return;
|
||
if (isForbidden(error)) {
|
||
renderForbiddenState();
|
||
return;
|
||
}
|
||
setGenerationState('[data-generation-list]', 'error', '字辈列表加载失败,请稍后重试。');
|
||
showMessage(error.message || '字辈列表加载失败');
|
||
}
|
||
}
|
||
|
||
async function submitPoem(form) {
|
||
var api = getApi();
|
||
var genealogyId;
|
||
var poemId;
|
||
var body;
|
||
var validation;
|
||
|
||
if (writePending || !managementAccess || redirectUnauthorized(api) || !form) return;
|
||
genealogyId = requireGenealogyId();
|
||
if (!genealogyId) return;
|
||
poemId = normalizeId(getFormValues(form).poemId);
|
||
if (getFormValues(form).poemId && !poemId) {
|
||
setGenerationFormStatus('字辈编号无效,请返回列表重新编辑。', 'error');
|
||
return;
|
||
}
|
||
if (poemId && !poemsById[poemId]) {
|
||
setGenerationFormStatus('当前字辈不存在或已更新,请返回列表刷新后重试。', 'error');
|
||
return;
|
||
}
|
||
body = buildGenerationPoemBody(getFormValues(form));
|
||
validation = validateGenerationPoemBody(body);
|
||
if (validation) {
|
||
setGenerationFormStatus(validation, 'error');
|
||
return;
|
||
}
|
||
setGenerationFormStatus('正在保存字辈…', 'loading');
|
||
setWritePending(true);
|
||
try {
|
||
if (poemId) {
|
||
await api.updateGenerationPoem(genealogyId, poemId, body);
|
||
} else {
|
||
await api.createGenerationPoem(genealogyId, body);
|
||
}
|
||
showMessage('字辈已保存');
|
||
closePoemEditor();
|
||
await loadGenerationPoems();
|
||
} catch (error) {
|
||
if (redirectUnauthorized(api, error)) return;
|
||
if (isForbidden(error)) {
|
||
renderForbiddenState();
|
||
return;
|
||
}
|
||
setGenerationFormStatus(error.message || '字辈保存失败', 'error');
|
||
} 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 (!await confirmAction(buildBatchConfirmMessage(body.disableMissing))) 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('submit', function (event) {
|
||
var form = event.target.closest('[data-generation-form]');
|
||
|
||
if (!form) return;
|
||
event.preventDefault();
|
||
submitPoem(form);
|
||
});
|
||
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();
|
||
openPoemEditor('');
|
||
return;
|
||
}
|
||
if (target.closest('[data-generation-editor-cancel]')) {
|
||
event.preventDefault();
|
||
closePoemEditor();
|
||
return;
|
||
}
|
||
if (!poemButton) return;
|
||
event.preventDefault();
|
||
if (poemButton.hasAttribute('data-generation-edit')) openPoemEditor(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,
|
||
canEditGenerations: canEditGenerations,
|
||
splitPoemText: splitPoemText,
|
||
normalizeGenerationPoem: normalizeGenerationPoem,
|
||
normalizeGenerationAccess: normalizeGenerationAccess,
|
||
normalizePreview: normalizePreview,
|
||
buildBatchConfirmMessage: buildBatchConfirmMessage,
|
||
normalizeList: normalizeList,
|
||
shouldRedirectToLogin: shouldRedirectToLogin,
|
||
isForbidden: isForbidden,
|
||
init: init
|
||
};
|
||
});
|