309598bfa6
- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、 siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法 - 添加帮助文章和站点资讯的参数验证逻辑 - 更新测试文件添加新的API方法测试用例 - 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用 - 更新加入家谱页面为完整的申请流程界面 - 修改资讯详情页面为站点资讯展示页面 - 更新AxiosRequestUtil中认证处理逻辑 - 添加世系树渲染的HTML生成函数用于页面复用 - 更新文档中的API契约说明和页面规划
253 lines
8.8 KiB
JavaScript
253 lines
8.8 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory(root);
|
|
return;
|
|
}
|
|
|
|
root.SiteNewsPages = factory(root);
|
|
if (root.document) {
|
|
root.document.addEventListener('DOMContentLoaded', function () {
|
|
root.SiteNewsPages.init();
|
|
});
|
|
}
|
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
|
'use strict';
|
|
|
|
var ARTICLE_TYPE_LABELS = {
|
|
news: '新闻',
|
|
notice: '公告',
|
|
download: '下载'
|
|
};
|
|
|
|
function escapeHtml(value) {
|
|
return String(value === undefined || value === null ? '' : value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function text(value) {
|
|
return value === undefined || value === null ? '' : String(value).trim();
|
|
}
|
|
|
|
function normalizeId(value) {
|
|
var normalized;
|
|
|
|
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
|
|
if (value === undefined || value === null) return '';
|
|
normalized = String(value).trim();
|
|
return /^[1-9][0-9]*$/.test(normalized) ? normalized : '';
|
|
}
|
|
|
|
function normalizeArticleType(value) {
|
|
var normalized = text(value);
|
|
|
|
return Object.prototype.hasOwnProperty.call(ARTICLE_TYPE_LABELS, normalized)
|
|
? normalized
|
|
: '';
|
|
}
|
|
|
|
function normalizeExternalUrl(value) {
|
|
var normalized = text(value);
|
|
var parsed;
|
|
|
|
if (!normalized) return '';
|
|
try {
|
|
parsed = new URL(normalized);
|
|
} catch (error) {
|
|
return '';
|
|
}
|
|
return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? parsed.href : '';
|
|
}
|
|
|
|
function normalizeArticle(item) {
|
|
var source = item || {};
|
|
var articleId = normalizeId(source.articleId);
|
|
var articleType = normalizeArticleType(source.articleType);
|
|
var articleTitle = text(source.articleTitle);
|
|
|
|
if (!articleId || !articleType || !articleTitle || String(source.status) !== '0') {
|
|
return null;
|
|
}
|
|
return {
|
|
articleId: articleId,
|
|
articleType: articleType,
|
|
articleTitle: articleTitle,
|
|
articleSummary: text(source.articleSummary),
|
|
articleContent: text(source.articleContent),
|
|
externalUrl: normalizeExternalUrl(source.externalUrl),
|
|
publishTime: text(source.publishTime)
|
|
};
|
|
}
|
|
|
|
function normalizeArticleList(data) {
|
|
var items;
|
|
|
|
if (!Array.isArray(data)) return [];
|
|
items = data.map(normalizeArticle);
|
|
return items.some(function (item) { return !item; }) ? [] : items;
|
|
}
|
|
|
|
function renderNormalizedArticleList(items) {
|
|
if (!items.length) return '<div class="api-empty">当前暂无资讯</div>';
|
|
return items.map(function (item) {
|
|
return '<a class="site-info-item" href="article-detail.html?articleId=' +
|
|
encodeURIComponent(item.articleId) + '">' +
|
|
'<small>' + escapeHtml(ARTICLE_TYPE_LABELS[item.articleType]) + '</small>' +
|
|
'<h2>' + escapeHtml(item.articleTitle) + '</h2>' +
|
|
'<p>' + escapeHtml(item.articleSummary) + '</p>' +
|
|
'<span>' + escapeHtml(item.publishTime) + '</span></a>';
|
|
}).join('');
|
|
}
|
|
|
|
function renderArticleList(data) {
|
|
return renderNormalizedArticleList(normalizeArticleList(data));
|
|
}
|
|
|
|
function renderNormalizedArticleDetail(item) {
|
|
var externalLink = item.externalUrl
|
|
? '<a class="btn ghost" data-external-link href="' + escapeHtml(item.externalUrl) +
|
|
'" target="_blank" rel="noopener noreferrer">查看外部内容</a>'
|
|
: '';
|
|
var content = escapeHtml(item.articleContent).replace(/\r?\n/g, '<br />');
|
|
|
|
return '<section class="inner-hero article-hero"><div class="container">' +
|
|
'<div class="hero-copy"><div class="eyebrow">' +
|
|
escapeHtml(ARTICLE_TYPE_LABELS[item.articleType]) + '</div><h1>' +
|
|
escapeHtml(item.articleTitle) + '</h1><p class="lead">' +
|
|
escapeHtml(item.articleSummary) + '</p><div class="article-meta"><span>' +
|
|
escapeHtml(item.publishTime) + '</span></div></div>' +
|
|
'<div class="hero-card article-cover-detail tilt-card"><span>闻</span><p>' +
|
|
escapeHtml(item.articleSummary) + '</p></div></div></section>' +
|
|
'<section class="section alt"><div class="container article-layout">' +
|
|
'<article class="article-content tilt-card"><p>' + content + '</p>' +
|
|
externalLink + '</article><aside class="article-side tilt-card"><h3>相关入口</h3>' +
|
|
'<a href="news.html">返回新闻资讯</a><a href="genealogy.html">了解数字家谱</a>' +
|
|
'<a href="create-genealogy.html">创建一本家谱</a></aside></div></section>';
|
|
}
|
|
|
|
function renderArticleDetail(item) {
|
|
var article = normalizeArticle(item);
|
|
|
|
return article
|
|
? renderNormalizedArticleDetail(article)
|
|
: '<div class="api-empty">资讯不存在或已下线</div>';
|
|
}
|
|
|
|
function readQueryValue(search, key) {
|
|
var params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
|
|
|
|
return text(params.get(key));
|
|
}
|
|
|
|
function readArticleId(search) {
|
|
return normalizeId(readQueryValue(search, 'articleId'));
|
|
}
|
|
|
|
async function loadArticles(api, articleType) {
|
|
var requestedType = text(articleType);
|
|
var normalizedType = normalizeArticleType(requestedType);
|
|
var query = { limit: 100 };
|
|
var data;
|
|
var items;
|
|
|
|
if (!api || typeof api.siteArticles !== 'function') throw new Error('资讯接口不可用');
|
|
if (requestedType && !normalizedType) throw new Error('资讯分类无效');
|
|
if (normalizedType) query.articleType = normalizedType;
|
|
data = await api.siteArticles(query);
|
|
items = normalizeArticleList(data);
|
|
if (!Array.isArray(data) || items.length !== data.length) {
|
|
throw new Error('资讯列表响应无效');
|
|
}
|
|
return items;
|
|
}
|
|
|
|
async function loadArticleDetail(api, targetArticleId) {
|
|
var normalizedId = normalizeId(targetArticleId);
|
|
var items;
|
|
var match;
|
|
|
|
if (!normalizedId) throw new Error('资讯编号无效');
|
|
items = await loadArticles(api, '');
|
|
match = items.find(function (item) {
|
|
return item.articleId === normalizedId;
|
|
});
|
|
if (!match) throw new Error('资讯不存在或已下线');
|
|
return match;
|
|
}
|
|
|
|
async function init() {
|
|
var document = root.document;
|
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
|
var search = root.location && root.location.search || '';
|
|
var listPage = document && document.querySelector('[data-site-news-page]');
|
|
var detailPage = document && document.querySelector('[data-site-article-page]');
|
|
var list;
|
|
var status;
|
|
var requestedType;
|
|
var detail;
|
|
var articleId;
|
|
|
|
if (listPage) {
|
|
list = document.querySelector('[data-site-news-list]');
|
|
status = document.querySelector('[data-site-news-status]');
|
|
requestedType = readQueryValue(search, 'articleType');
|
|
if (!list) return;
|
|
if (requestedType && !normalizeArticleType(requestedType)) {
|
|
list.innerHTML = '<div class="api-empty">资讯分类无效</div>';
|
|
if (status) status.textContent = '资讯分类无效';
|
|
return;
|
|
}
|
|
try {
|
|
list.classList.add('is-loading');
|
|
list.innerHTML = renderNormalizedArticleList(await loadArticles(api, requestedType));
|
|
if (status) status.textContent = '';
|
|
} catch (error) {
|
|
list.innerHTML = '<div class="api-empty">资讯读取失败,请稍后重试</div>';
|
|
if (status) status.textContent = error.message || '资讯读取失败';
|
|
} finally {
|
|
list.classList.remove('is-loading');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (detailPage) {
|
|
detail = document.querySelector('[data-site-article-detail]');
|
|
status = document.querySelector('[data-site-article-status]');
|
|
articleId = readArticleId(search);
|
|
if (!detail) return;
|
|
if (!articleId) {
|
|
detail.innerHTML = '<div class="api-empty">资讯编号无效</div>';
|
|
if (status) status.textContent = '资讯编号无效';
|
|
return;
|
|
}
|
|
try {
|
|
detail.classList.add('is-loading');
|
|
detail.innerHTML = renderNormalizedArticleDetail(await loadArticleDetail(api, articleId));
|
|
if (status) status.textContent = '';
|
|
} catch (error) {
|
|
detail.innerHTML = '<div class="api-empty">' +
|
|
escapeHtml(error.message || '资讯读取失败,请稍后重试') + '</div>';
|
|
if (status) status.textContent = error.message || '资讯读取失败';
|
|
} finally {
|
|
detail.classList.remove('is-loading');
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
normalizeArticle: normalizeArticle,
|
|
normalizeArticleList: normalizeArticleList,
|
|
normalizeArticleType: normalizeArticleType,
|
|
normalizeExternalUrl: normalizeExternalUrl,
|
|
readArticleId: readArticleId,
|
|
renderArticleList: renderArticleList,
|
|
renderArticleDetail: renderArticleDetail,
|
|
loadArticles: loadArticles,
|
|
loadArticleDetail: loadArticleDetail,
|
|
init: init
|
|
};
|
|
});
|