feat(api): 添加帮助中心反馈系统和VIP服务功能
- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、 siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法 - 添加帮助文章和站点资讯的参数验证逻辑 - 更新测试文件添加新的API方法测试用例 - 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用 - 更新加入家谱页面为完整的申请流程界面 - 修改资讯详情页面为站点资讯展示页面 - 更新AxiosRequestUtil中认证处理逻辑 - 添加世系树渲染的HTML生成函数用于页面复用 - 更新文档中的API契约说明和页面规划
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory(root);
|
||||
return;
|
||||
}
|
||||
|
||||
root.AppPromotionPages = factory(root);
|
||||
if (root.document) {
|
||||
root.document.addEventListener('DOMContentLoaded', function () {
|
||||
root.AppPromotionPages.init();
|
||||
});
|
||||
}
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||
'use strict';
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value === undefined || value === null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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 text(value) {
|
||||
return value === undefined || value === null ? '' : String(value).trim();
|
||||
}
|
||||
|
||||
function normalizeTargetUrl(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 normalizePromotion(item) {
|
||||
var source = item || {};
|
||||
var promotionId = normalizeId(source.promotionId);
|
||||
var promotionTitle = text(source.promotionTitle);
|
||||
|
||||
if (!promotionId || !promotionTitle || String(source.status) !== '0') return null;
|
||||
return {
|
||||
promotionId: promotionId,
|
||||
promotionTitle: promotionTitle,
|
||||
promotionDesc: text(source.promotionDesc),
|
||||
targetUrl: normalizeTargetUrl(source.targetUrl)
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePromotionList(data) {
|
||||
var items;
|
||||
|
||||
if (!Array.isArray(data)) return [];
|
||||
items = data.map(normalizePromotion);
|
||||
return items.some(function (item) { return !item; }) ? [] : items;
|
||||
}
|
||||
|
||||
function renderPromotionCard(item) {
|
||||
var content = '<div><h3>' + escapeHtml(item.promotionTitle) + '</h3>' +
|
||||
'<p>' + escapeHtml(item.promotionDesc) + '</p></div>';
|
||||
|
||||
if (!item.targetUrl) {
|
||||
return '<article class="promotion-card" data-promotion-id="' +
|
||||
escapeHtml(item.promotionId) + '">' + content + '</article>';
|
||||
}
|
||||
return '<a class="promotion-card" data-promotion-id="' + escapeHtml(item.promotionId) +
|
||||
'" href="' + escapeHtml(item.targetUrl) +
|
||||
'" target="_blank" rel="noopener noreferrer">' + content + '</a>';
|
||||
}
|
||||
|
||||
function renderPromotionList(data) {
|
||||
var items = normalizePromotionList(data);
|
||||
|
||||
if (!items.length) return '<div class="api-empty">当前暂无应用推广</div>';
|
||||
return items.map(renderPromotionCard).join('');
|
||||
}
|
||||
|
||||
function renderPromotionLoginRequired() {
|
||||
return '<div class="api-empty">登录后可查看应用推广,<a href="login.html">立即登录</a></div>';
|
||||
}
|
||||
|
||||
async function loadPromotions(api) {
|
||||
var data;
|
||||
var items;
|
||||
|
||||
if (!api || typeof api.promotions !== 'function') throw new Error('应用推广接口不可用');
|
||||
data = await api.promotions({ platform: 'pc' });
|
||||
items = normalizePromotionList(data);
|
||||
if (!Array.isArray(data) || items.length !== data.length) {
|
||||
throw new Error('推广列表响应无效');
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function isUnauthorized(error) {
|
||||
return Number(error && (error.status || error.code)) === 401;
|
||||
}
|
||||
|
||||
async function init() {
|
||||
var page = root.document && root.document.querySelector('[data-promotion-page]');
|
||||
var list = root.document && root.document.querySelector('[data-promotion-list]');
|
||||
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||
|
||||
if (!page || !list) return;
|
||||
if (!api || !api.getToken || !api.getToken()) {
|
||||
list.innerHTML = renderPromotionLoginRequired();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
list.classList.add('is-loading');
|
||||
list.innerHTML = renderPromotionList(await loadPromotions(api));
|
||||
} catch (error) {
|
||||
if (isUnauthorized(error)) {
|
||||
if (api.clearToken) api.clearToken();
|
||||
list.innerHTML = renderPromotionLoginRequired();
|
||||
return;
|
||||
}
|
||||
list.innerHTML = '<div class="api-empty">应用推广读取失败,请稍后重试</div>';
|
||||
} finally {
|
||||
list.classList.remove('is-loading');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
normalizePromotion: normalizePromotion,
|
||||
normalizePromotionList: normalizePromotionList,
|
||||
normalizeTargetUrl: normalizeTargetUrl,
|
||||
renderPromotionList: renderPromotionList,
|
||||
renderPromotionLoginRequired: renderPromotionLoginRequired,
|
||||
loadPromotions: loadPromotions,
|
||||
isUnauthorized: isUnauthorized,
|
||||
init: init
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user