(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 = '
' + escapeHtml(message) + '
'; } 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, '''); } 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 = '
' + '' + '' + '
'; } return '
' + '

第 ' + escapeHtml(poem.generationNo) + ' 代:' + escapeHtml(poem.generationText) + '

' + escapeHtml(details.join(' · ')) + '

' + actions + '
'; } 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 = '

变更预览

' + escapeHtml(summary) + '

' + 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 '

' + escapeHtml(text) + '

'; }).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 }; });