(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.ArticlePages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.ArticlePages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; var articlesById = Object.create(null); var writePending = false; var canEditContent = false; function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function query(selector, node) { return documentRef ? (node || documentRef).querySelector(selector) : null; } function queryAll(selector, node) { return documentRef ? Array.prototype.slice.call((node || documentRef).querySelectorAll(selector)) : []; } function stringValue(value) { return value === undefined || value === null ? '' : String(value); } function trimOrUndefined(value) { var text = stringValue(value).trim(); return text || undefined; } function queryParam(search, name) { return new URLSearchParams(stringValue(search).replace(/^\?/, '')).get(name) || ''; } function normalizeId(value) { if (value === undefined || value === null || value === '') return ''; if (typeof value === 'number' && !Number.isSafeInteger(value)) return ''; return /^[1-9][0-9]*$/.test(String(value)) ? String(value) : ''; } function normalizeNullableId(value) { if (value === undefined || value === null || value === '') return ''; return normalizeId(value); } function getCurrentGenealogyId(search) { var source = search === undefined && root.location ? root.location.search : search; var contextId; if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) { contextId = root.ProfileUI.getGenealogyId(); if (contextId) return normalizeId(contextId); } return normalizeId(queryParam(source, 'genealogyId')); } function getCurrentArticleId(search) { var source = search === undefined && root.location ? root.location.search : search; return normalizeId(queryParam(source, 'articleId')); } function optionalSafeInteger(value) { var number; if (value === undefined || value === null || value === '') return undefined; number = Number(value); return Number.isSafeInteger(number) ? number : undefined; } function buildArticleBody(values) { var source = values || {}; var body = { articleTitle: stringValue(source.articleTitle).trim(), articleContent: stringValue(source.articleContent).trim(), status: '0' }; var articleSummary = trimOrUndefined(source.articleSummary); var coverOssId = normalizeNullableId(source.coverOssId); var authorName = trimOrUndefined(source.authorName); var sortOrder = optionalSafeInteger(source.sortOrder); if (articleSummary !== undefined) body.articleSummary = articleSummary; if (source.coverOssId !== undefined && source.coverOssId !== null && source.coverOssId !== '') { if (coverOssId) body.coverOssId = coverOssId; else body.invalidCoverOssId = true; } if (authorName !== undefined) body.authorName = authorName; if (source.sortOrder !== undefined && source.sortOrder !== null && source.sortOrder !== '') { if (sortOrder !== undefined) body.sortOrder = sortOrder; else body.invalidSortOrder = true; } return body; } function validateArticleBody(body) { if (!body || !body.articleTitle) return '请填写谱文标题'; if (!body.articleContent) return '请填写谱文正文'; if (body.invalidCoverOssId) return '封面文件编号无效,请重新选择文件'; if (body.invalidSortOrder) return '排序值必须是安全整数'; if (body.status === '1') return '当前 PC 无法重新读取停用谱文,暂不开放停用'; if (body.status !== '0') return '谱文状态只能是 0'; return ''; } function normalizeArticle(item) { var articleId = normalizeId(item && item.articleId); var genealogyId = normalizeId(item && item.genealogyId); var categoryId = normalizeNullableId(item && item.categoryId); var coverOssId = normalizeNullableId(item && item.coverOssId); var articleTitle = stringValue(item && item.articleTitle).trim(); var articleContent = stringValue(item && item.articleContent).trim(); var viewCount = optionalSafeInteger(item && item.viewCount); var sortOrder = optionalSafeInteger(item && item.sortOrder); var status = stringValue(item && item.status); var article; if (!articleId || !genealogyId || !articleTitle || !articleContent) return null; if (item.categoryId !== undefined && item.categoryId !== null && item.categoryId !== '' && !categoryId) return null; if (item.coverOssId !== undefined && item.coverOssId !== null && item.coverOssId !== '' && !coverOssId) return null; if (item.viewCount !== undefined && item.viewCount !== null && item.viewCount !== '' && viewCount === undefined) return null; if (item.sortOrder !== undefined && item.sortOrder !== null && item.sortOrder !== '' && sortOrder === undefined) return null; if (status !== '0' && status !== '1') return null; article = { articleId: articleId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), categoryName: stringValue(item.categoryName), categoryCode: stringValue(item.categoryCode), articleTitle: articleTitle, articleSummary: stringValue(item.articleSummary), articleContent: articleContent, authorName: stringValue(item.authorName), publishTime: stringValue(item.publishTime), status: status, remark: stringValue(item.remark) }; if (categoryId) article.categoryId = categoryId; if (coverOssId) article.coverOssId = coverOssId; if (viewCount !== undefined) article.viewCount = viewCount; if (sortOrder !== undefined) article.sortOrder = sortOrder; return article; } function normalizeArticles(data) { var normalized; if (!Array.isArray(data)) return []; normalized = data.map(normalizeArticle); return normalized.some(function (article) { return !article; }) ? [] : normalized; } function matchesSavedArticle(item, expectedArticleId) { var article = normalizeArticle(item); return Boolean(article && article.articleId === normalizeId(expectedArticleId)); } function escapeHtml(value) { return stringValue(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function buildListUrl(genealogyId, articleId) { var url = 'profile-article.html?genealogyId=' + encodeURIComponent(genealogyId); return articleId ? url + '&articleId=' + encodeURIComponent(articleId) : url; } function buildEditUrl(genealogyId, articleId) { var url = 'profile-article-edit.html?genealogyId=' + encodeURIComponent(genealogyId); return articleId ? url + '&articleId=' + encodeURIComponent(articleId) : url; } function detailValue(label, value) { return '

' + escapeHtml(label) + '' + escapeHtml(value || '未提供') + '

'; } function renderArticleDetail(article) { var actions = ''; if (!article) return '
请选择一篇谱文查看详情
'; if (canEditContent) { actions = '
编辑谱文' + '
'; } return '
' + detailValue('标题', article.articleTitle) + detailValue('摘要', article.articleSummary) + detailValue('正文', article.articleContent) + detailValue('作者', article.authorName) + detailValue('分类', article.categoryName) + detailValue('发布时间', article.publishTime) + detailValue('浏览量', article.viewCount === undefined ? '' : article.viewCount) + detailValue('备注', article.remark) + '
' + actions; } function renderArticleRow(article) { var actions = ''; if (canEditContent) { actions += '编辑'; } return '

' + escapeHtml(article.articleTitle) + '

' + escapeHtml(article.articleSummary || article.authorName || article.publishTime || '暂无摘要') + '

' + actions + '
'; } 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'); else root.location.href = 'login.html'; return true; } function showMessage(message) { if (root.layui && root.layui.layer) root.layui.layer.msg(message); else if (root.alert) root.alert(message); } function syncLinks() { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks(); } function setFormStatus(message) { var target = query('[data-article-form-status]'); if (target) target.textContent = message || ''; } function setEditorEnabled(enabled, message) { var placeholder = query('[data-article-editor-placeholder]'); queryAll('[data-article-editor], [data-article-editor-action]').forEach(function (element) { element.hidden = !enabled; }); if (placeholder) { placeholder.hidden = Boolean(enabled); if (message) placeholder.textContent = message; } } function setWritePending(value) { writePending = Boolean(value); queryAll('[data-article-form] button, [data-article-form] input, [data-article-form] textarea, [data-article-delete-id]').forEach(function (control) { control.disabled = writePending; }); } function renderNoContext() { var message = '请先选择家谱,再查看或维护谱文。'; var list = query('[data-article-list]'); var detail = query('[data-article-detail]'); if (list) list.innerHTML = '
' + message + '
'; if (detail) detail.innerHTML = '
当前没有家谱上下文。
'; setEditorEnabled(false, message); setFormStatus(message); } function requireGenealogyId() { var genealogyId = getCurrentGenealogyId(); if (!genealogyId) renderNoContext(); return genealogyId; } async function loadCapability(api, genealogyId) { var detail = await api.genealogyDetail(genealogyId); canEditContent = Boolean(detail && (detail.canEditContent || detail.canManage)); queryAll('[data-article-create-link], [data-article-editor-action]').forEach(function (element) { element.hidden = !canEditContent; }); return detail; } function renderArticles(data) { var container = query('[data-article-list]'); var articles = normalizeArticles(data); articlesById = Object.create(null); articles.forEach(function (article) { articlesById[article.articleId] = article; }); if (!container) return articles; if (!Array.isArray(data) || (data.length && !articles.length)) { container.innerHTML = '
谱文响应缺少稳定 ArticleVo 字段,请联系后端核对。
'; return []; } if (!articles.length) { container.innerHTML = '
暂无谱文
'; return []; } container.innerHTML = articles.map(renderArticleRow).join(''); return articles; } function renderDetail(article) { var container = query('[data-article-detail]'); if (container) container.innerHTML = renderArticleDetail(article); } async function loadArticleDetail(articleId) { var api = getApi(); var genealogyId = requireGenealogyId(); var article; if (!genealogyId || !normalizeId(articleId) || redirectUnauthorized(api)) return null; try { article = normalizeArticle(await api.articleDetail(genealogyId, articleId)); if (!article || article.articleId !== normalizeId(articleId)) { throw new Error('谱文详情响应缺少稳定 ArticleVo 字段'); } articlesById[article.articleId] = article; renderDetail(article); return article; } catch (error) { if (redirectUnauthorized(api, error)) return null; renderDetail(null); showMessage(isForbidden(error) ? '当前账号无权查看该谱文。' : (error.message || '谱文详情加载失败')); return null; } } async function loadArticles() { var api = getApi(); var genealogyId; var articles; var requestedId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; syncLinks(); try { await loadCapability(api, genealogyId); articles = renderArticles(await api.articles(genealogyId)); requestedId = getCurrentArticleId(); if (requestedId && articlesById[requestedId]) await loadArticleDetail(requestedId); else if (articles.length) await loadArticleDetail(articles[0].articleId); else renderDetail(null); } catch (error) { if (redirectUnauthorized(api, error)) return; if (query('[data-article-list]')) { query('[data-article-list]').innerHTML = '
' + (isForbidden(error) ? '当前账号无权查看该家谱的谱文。' : '谱文加载失败,请稍后重试。') + '
'; } showMessage(isForbidden(error) ? '当前账号无权查看该家谱的谱文。' : (error.message || '谱文加载失败')); } } function getFormValues(form) { var values = {}; queryAll('[name]', form).forEach(function (field) { values[field.name] = field.value; }); return values; } function setFieldValue(name, value) { var field = query('[data-article-form] [name="' + name + '"]'); if (field) field.value = value === undefined || value === null ? '' : String(value); } function fillArticleForm(article) { var contentField; var contentEditor; if (!article || article.status !== '0') throw new Error('停用谱文无法通过当前 PC 接口重新读取或编辑'); setFieldValue('articleTitle', article.articleTitle); setFieldValue('articleSummary', article.articleSummary); setFieldValue('coverOssId', article.coverOssId); setFieldValue('articleContent', article.articleContent); setFieldValue('authorName', article.authorName); setFieldValue('sortOrder', article.sortOrder); setFieldValue('status', '0'); contentField = query('[name="articleContent"]'); if (root.AppRichEditor && root.AppRichEditor.init && contentField) { contentEditor = root.AppRichEditor.init(contentField); if (contentEditor && contentEditor.editor && contentEditor.editor.setHtml) { contentEditor.editor.setHtml(article.articleContent); contentEditor.sync(); } } } async function loadArticleEditor() { var api = getApi(); var genealogyId; var articleId; var article; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; articleId = getCurrentArticleId(); syncLinks(); setEditorEnabled(false, '正在加载谱文编辑信息…'); try { await loadCapability(api, genealogyId); if (!canEditContent) throw { status: 403, message: '当前账号无权维护该家谱的谱文。' }; if (articleId) { article = normalizeArticle(await api.articleDetail(genealogyId, articleId)); if (!article || article.articleId !== articleId) throw new Error('谱文详情响应缺少稳定 ArticleVo 字段'); fillArticleForm(article); if (query('[data-article-editor-title]')) query('[data-article-editor-title]').textContent = '编辑谱文'; } setEditorEnabled(true); } catch (error) { if (redirectUnauthorized(api, error)) return; setEditorEnabled(false, isForbidden(error) ? '当前账号无权维护该家谱的谱文。' : (error.message || '谱文编辑信息加载失败')); setFormStatus(isForbidden(error) ? '当前账号无权维护该家谱的谱文。' : (error.message || '谱文编辑信息加载失败')); } } async function submitArticle(form) { var api = getApi(); var genealogyId = requireGenealogyId(); var articleId = getCurrentArticleId(); var body; var validation; var result; var saved; var savedId; if (writePending || !genealogyId || redirectUnauthorized(api)) return; if (root.AppRichEditor && root.AppRichEditor.syncAll) root.AppRichEditor.syncAll(); body = buildArticleBody(getFormValues(form)); validation = validateArticleBody(body); if (validation) { setFormStatus(validation); return; } delete body.invalidCoverOssId; delete body.invalidSortOrder; setWritePending(true); setFormStatus('正在保存谱文…'); try { result = articleId ? await api.updateArticle(genealogyId, articleId, body) : await api.createArticle(genealogyId, body); saved = normalizeArticle(result); savedId = articleId || (saved && saved.articleId); if (!savedId) throw new Error('保存响应缺少 articleId'); if (!matchesSavedArticle(await api.articleDetail(genealogyId, savedId), savedId)) { throw new Error('保存后重读未返回同一篇谱文'); } if (root.location) root.location.href = buildListUrl(genealogyId, savedId); } catch (error) { if (redirectUnauthorized(api, error)) return; setFormStatus(isForbidden(error) ? '当前账号无权保存该谱文。' : (error.message || '谱文保存失败')); } finally { setWritePending(false); } } async function deleteArticle(articleId) { var api = getApi(); var genealogyId = requireGenealogyId(); var article = articlesById[articleId]; if (writePending || !genealogyId || !normalizeId(articleId) || redirectUnauthorized(api)) return; if (root.confirm && !root.confirm('确认删除“' + (article ? article.articleTitle : '该谱文') + '”吗?删除后不可恢复。')) return; setWritePending(true); try { await api.deleteArticle(genealogyId, articleId); await loadArticles(); showMessage('谱文已删除'); } catch (error) { if (redirectUnauthorized(api, error)) return; showMessage(isForbidden(error) ? '当前账号无权删除该谱文。' : (error.message || '谱文删除失败')); } finally { setWritePending(false); } } function bindActions() { if (!documentRef) return; documentRef.addEventListener('click', function (event) { var detailButton = event.target.closest('[data-article-detail-id]'); var deleteButton = event.target.closest('[data-article-delete-id]'); if (detailButton) { event.preventDefault(); loadArticleDetail(detailButton.getAttribute('data-article-detail-id')); return; } if (deleteButton) { event.preventDefault(); deleteArticle(deleteButton.getAttribute('data-article-delete-id')); } }); documentRef.addEventListener('submit', function (event) { var form = event.target.closest('[data-article-form]'); if (!form) return; event.preventDefault(); submitArticle(form); }); } function init() { if (!documentRef) return; bindActions(); if (query('[data-article-page]')) loadArticles(); if (query('[data-article-edit-page]')) loadArticleEditor(); } return { getCurrentGenealogyId: getCurrentGenealogyId, getCurrentArticleId: getCurrentArticleId, buildArticleBody: buildArticleBody, validateArticleBody: validateArticleBody, normalizeArticle: normalizeArticle, normalizeArticles: normalizeArticles, matchesSavedArticle: matchesSavedArticle, renderArticleDetail: renderArticleDetail, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });