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,250 @@
|
||||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory(root);
|
||||
return;
|
||||
}
|
||||
|
||||
root.JoinReviewPages = factory(root);
|
||||
if (root.document) {
|
||||
root.document.addEventListener('DOMContentLoaded', function () {
|
||||
root.JoinReviewPages.initJoinReviewPage();
|
||||
});
|
||||
}
|
||||
})(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 text;
|
||||
|
||||
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
|
||||
if (value === undefined || value === null) return '';
|
||||
text = String(value).trim();
|
||||
return /^[1-9][0-9]*$/.test(text) ? text : '';
|
||||
}
|
||||
|
||||
function optionalText(value) {
|
||||
if (value === undefined || value === null) return '';
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
function getCurrentGenealogyId(search) {
|
||||
var contextId;
|
||||
var params;
|
||||
|
||||
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
|
||||
contextId = root.ProfileUI.getGenealogyId();
|
||||
return normalizeId(contextId);
|
||||
}
|
||||
params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
|
||||
return normalizeId(params.get('genealogyId'));
|
||||
}
|
||||
|
||||
function buildJoinAuditBody(values) {
|
||||
var source = values || {};
|
||||
var body = { status: String(source.status === undefined || source.status === null ? '' : source.status).trim() };
|
||||
var auditRemark = optionalText(source.auditRemark);
|
||||
|
||||
if (auditRemark) body.auditRemark = auditRemark;
|
||||
return body;
|
||||
}
|
||||
|
||||
function validateJoinAuditBody(body) {
|
||||
if (!body || ['1', '2'].indexOf(body.status) < 0) return '审核状态无效';
|
||||
if (String(body.auditRemark || '').length > 500) return '审核说明不能超过 500 个字符';
|
||||
return '';
|
||||
}
|
||||
|
||||
function normalizePendingApply(item) {
|
||||
var source = item || {};
|
||||
var applyId = normalizeId(source.applyId);
|
||||
var genealogyId = normalizeId(source.genealogyId);
|
||||
|
||||
if (!applyId || !genealogyId || String(source.status) !== '0') return null;
|
||||
return {
|
||||
applyId: applyId,
|
||||
genealogyId: genealogyId,
|
||||
genealogyName: optionalText(source.genealogyName),
|
||||
applicantName: optionalText(source.applicantName) || optionalText(source.appUserNickName) || '未填写姓名',
|
||||
phone: optionalText(source.phone),
|
||||
relationDesc: optionalText(source.relationDesc),
|
||||
applyReason: optionalText(source.applyReason)
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePendingApplies(data) {
|
||||
var items;
|
||||
|
||||
if (!Array.isArray(data)) return [];
|
||||
items = data.map(normalizePendingApply);
|
||||
return items.some(function (item) { return !item; }) ? [] : items;
|
||||
}
|
||||
|
||||
function renderPendingJoinApplies(data, canManage) {
|
||||
var items = normalizePendingApplies(data);
|
||||
|
||||
if (!items.length) return '<div class="api-empty">当前没有待审核申请</div>';
|
||||
return items.map(function (item) {
|
||||
var details = [
|
||||
item.phone ? '申请联系电话:' + item.phone : '',
|
||||
item.relationDesc ? '关系:' + item.relationDesc : '',
|
||||
item.applyReason ? '理由:' + item.applyReason : ''
|
||||
].filter(Boolean).join(' · ');
|
||||
var actions = canManage
|
||||
? '<div class="bottom-actions"><button class="btn primary" type="button" data-join-audit-id="' +
|
||||
escapeHtml(item.applyId) + '" data-join-audit-status="1">通过</button>' +
|
||||
'<button class="btn ghost" type="button" data-join-audit-id="' +
|
||||
escapeHtml(item.applyId) + '" data-join-audit-status="2">拒绝</button></div>'
|
||||
: '';
|
||||
|
||||
return '<article class="module-row"><div><h3>' + escapeHtml(item.applicantName) +
|
||||
'申请加入' + escapeHtml(item.genealogyName || '当前家谱') + '</h3><p>' +
|
||||
escapeHtml(details || '申请人未补充说明') + '</p></div>' + actions + '</article>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function shouldRedirectToLogin(api, error) {
|
||||
var status = error && (error.status || error.code);
|
||||
|
||||
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
|
||||
}
|
||||
|
||||
function redirectToLogin(api) {
|
||||
if (api && api.clearToken) api.clearToken();
|
||||
if (!root.location) return;
|
||||
if (typeof root.location.replace === 'function') root.location.replace('login.html');
|
||||
else root.location.href = 'login.html';
|
||||
}
|
||||
|
||||
function setStatus(message) {
|
||||
var element = root.document && root.document.querySelector('[data-join-review-status]');
|
||||
|
||||
if (element) element.textContent = message || '';
|
||||
}
|
||||
|
||||
async function initJoinReviewPage() {
|
||||
var page = root.document && root.document.querySelector('[data-join-review-page]');
|
||||
var list = root.document && root.document.querySelector('[data-join-apply-list="pending"]');
|
||||
var refresh = root.document && root.document.querySelector('[data-join-review-refresh]');
|
||||
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||
var genealogyId = getCurrentGenealogyId();
|
||||
var canManage = false;
|
||||
var writePending = false;
|
||||
|
||||
if (!page) return;
|
||||
if (!genealogyId) {
|
||||
setStatus('请先从“我的家谱”选择要管理的家谱');
|
||||
list.innerHTML = '<a class="module-row" href="profile-families.html?next=profile-join-review.html">选择家谱后继续</a>';
|
||||
return;
|
||||
}
|
||||
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) {
|
||||
root.ProfileUI.syncGenealogyContextLinks();
|
||||
}
|
||||
if (shouldRedirectToLogin(api)) {
|
||||
redirectToLogin(api);
|
||||
return;
|
||||
}
|
||||
|
||||
async function loadPending() {
|
||||
var data;
|
||||
|
||||
setStatus('正在读取待审核申请…');
|
||||
try {
|
||||
data = await api.pendingGenealogyJoinApplies(genealogyId);
|
||||
list.innerHTML = renderPendingJoinApplies(data, canManage);
|
||||
setStatus(canManage ? '' : '当前账号没有审核该家谱申请的权限');
|
||||
return normalizePendingApplies(data);
|
||||
} catch (error) {
|
||||
if (shouldRedirectToLogin(api, error)) {
|
||||
redirectToLogin(api);
|
||||
return [];
|
||||
}
|
||||
list.innerHTML = '<div class="api-empty">读取待审核申请失败</div>';
|
||||
setStatus(error.message || '读取待审核申请失败,请稍后重试');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var detail = await api.genealogyDetail(genealogyId);
|
||||
|
||||
canManage = detail && detail.canManage === true;
|
||||
if (!canManage) {
|
||||
list.innerHTML = '<div class="api-empty">当前账号没有审核该家谱申请的权限</div>';
|
||||
setStatus('当前账号没有审核该家谱申请的权限');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (shouldRedirectToLogin(api, error)) {
|
||||
redirectToLogin(api);
|
||||
return;
|
||||
}
|
||||
setStatus(error.message || '无法核验当前家谱管理权限');
|
||||
return;
|
||||
}
|
||||
|
||||
refresh.addEventListener('click', function () {
|
||||
loadPending();
|
||||
});
|
||||
list.addEventListener('click', async function (event) {
|
||||
var button = event.target.closest('[data-join-audit-id]');
|
||||
var applyId;
|
||||
var status;
|
||||
var auditRemark = '';
|
||||
var body;
|
||||
var validation;
|
||||
var refreshed;
|
||||
|
||||
if (!button || !list.contains(button) || writePending || !canManage) return;
|
||||
applyId = normalizeId(button.getAttribute('data-join-audit-id'));
|
||||
status = button.getAttribute('data-join-audit-status');
|
||||
if (!applyId || ['1', '2'].indexOf(status) < 0) return;
|
||||
if (status === '1' && root.confirm && !root.confirm('确认通过这条加入申请吗?')) return;
|
||||
if (status === '2' && root.prompt) {
|
||||
auditRemark = root.prompt('可填写拒绝原因(最多 500 个字符)', '') || '';
|
||||
}
|
||||
body = buildJoinAuditBody({ status: status, auditRemark: auditRemark });
|
||||
validation = validateJoinAuditBody(body);
|
||||
if (validation) {
|
||||
setStatus(validation);
|
||||
return;
|
||||
}
|
||||
writePending = true;
|
||||
button.disabled = true;
|
||||
setStatus('正在提交审核结果…');
|
||||
try {
|
||||
await api.auditGenealogyJoinApply(genealogyId, applyId, body);
|
||||
refreshed = await loadPending();
|
||||
if (refreshed.some(function (item) { return item.applyId === applyId; })) {
|
||||
throw new Error('审核后申请仍在待审核列表');
|
||||
}
|
||||
} catch (error) {
|
||||
if (shouldRedirectToLogin(api, error)) {
|
||||
redirectToLogin(api);
|
||||
return;
|
||||
}
|
||||
setStatus(error.message || '提交审核结果失败,请稍后重试');
|
||||
} finally {
|
||||
writePending = false;
|
||||
}
|
||||
});
|
||||
await loadPending();
|
||||
}
|
||||
|
||||
return {
|
||||
getCurrentGenealogyId: getCurrentGenealogyId,
|
||||
buildJoinAuditBody: buildJoinAuditBody,
|
||||
validateJoinAuditBody: validateJoinAuditBody,
|
||||
normalizePendingApply: normalizePendingApply,
|
||||
renderPendingJoinApplies: renderPendingJoinApplies,
|
||||
initJoinReviewPage: initJoinReviewPage
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user