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,189 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const JoinPages = require('../public/js/join-pages.js');
|
||||
|
||||
const genealogyId = '2062179707935264769';
|
||||
const applyId = '2062179707935264770';
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.resolve(__dirname, '..', relativePath), 'utf8');
|
||||
}
|
||||
|
||||
test('加入申请只构造 AppGenealogyJoinApplyBody 并裁剪可选文本', () => {
|
||||
assert.deepEqual(JoinPages.buildJoinApplyBody({
|
||||
applicantName: ' 叶子 ',
|
||||
phone: ' 19181970173 ',
|
||||
relationDesc: ' 族亲 ',
|
||||
applyReason: ' 申请加入 ',
|
||||
inviterUserId: 'must-drop',
|
||||
genealogyId: 'must-drop'
|
||||
}), {
|
||||
applicantName: '叶子',
|
||||
phone: '19181970173',
|
||||
relationDesc: '族亲',
|
||||
applyReason: '申请加入'
|
||||
});
|
||||
assert.deepEqual(JoinPages.buildJoinApplyBody({
|
||||
applicantName: ' ',
|
||||
phone: '',
|
||||
relationDesc: null
|
||||
}), {});
|
||||
});
|
||||
|
||||
test('加入申请按后端长度约束校验四个可选字段', () => {
|
||||
assert.equal(JoinPages.validateJoinApplyBody({ applicantName: '叶'.repeat(50) }), '');
|
||||
assert.match(JoinPages.validateJoinApplyBody({ applicantName: '叶'.repeat(51) }), /50/);
|
||||
assert.equal(JoinPages.validateJoinApplyBody({ phone: '1'.repeat(30) }), '');
|
||||
assert.match(JoinPages.validateJoinApplyBody({ phone: '1'.repeat(31) }), /30/);
|
||||
assert.equal(JoinPages.validateJoinApplyBody({ relationDesc: '亲'.repeat(100) }), '');
|
||||
assert.match(JoinPages.validateJoinApplyBody({ relationDesc: '亲'.repeat(101) }), /100/);
|
||||
assert.equal(JoinPages.validateJoinApplyBody({ applyReason: '因'.repeat(500) }), '');
|
||||
assert.match(JoinPages.validateJoinApplyBody({ applyReason: '因'.repeat(501) }), /500/);
|
||||
});
|
||||
|
||||
test('可申请家谱必须提供稳定字符串 ID、谱名和姓氏,并拒绝停用家谱', () => {
|
||||
assert.deepEqual(JoinPages.normalizeJoinGenealogy({
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
regionFullName: '四川省',
|
||||
memberCount: 12,
|
||||
joinMode: '0',
|
||||
status: '0',
|
||||
ownerUserId: 'must-drop'
|
||||
}), {
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
regionFullName: '四川省',
|
||||
memberCount: 12,
|
||||
joinMode: '0'
|
||||
});
|
||||
assert.equal(JoinPages.normalizeJoinGenealogy({
|
||||
genealogyId: Number.MAX_SAFE_INTEGER + 1,
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
status: '0'
|
||||
}), null);
|
||||
assert.equal(JoinPages.normalizeJoinGenealogy({
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
status: '1'
|
||||
}), null);
|
||||
});
|
||||
|
||||
test('我的申请只接收稳定 ID 和 0/1/2/3 状态', () => {
|
||||
assert.deepEqual(JoinPages.normalizeJoinApply({
|
||||
applyId,
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
relationDesc: '族亲',
|
||||
applyReason: '申请加入',
|
||||
auditRemark: '已核实',
|
||||
auditTime: '2026-07-29 12:00:00',
|
||||
status: '1',
|
||||
appUserId: 'must-drop',
|
||||
inviterUserId: 'must-drop',
|
||||
auditUserId: 'must-drop',
|
||||
appUserPhone: 'must-drop'
|
||||
}), {
|
||||
applyId,
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
relationDesc: '族亲',
|
||||
applyReason: '申请加入',
|
||||
auditRemark: '已核实',
|
||||
auditTime: '2026-07-29 12:00:00',
|
||||
status: '1'
|
||||
});
|
||||
assert.equal(JoinPages.normalizeJoinApply({
|
||||
applyId: Number.MAX_SAFE_INTEGER + 1,
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
status: '0'
|
||||
}), null);
|
||||
assert.equal(JoinPages.normalizeJoinApply({
|
||||
applyId,
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
status: '9'
|
||||
}), null);
|
||||
});
|
||||
|
||||
test('申请数组只要包含非法元素就整批拒绝', () => {
|
||||
const valid = { applyId, genealogyId, genealogyName: '叶氏家谱', status: '0' };
|
||||
|
||||
assert.equal(JoinPages.normalizeJoinApplies([valid]).length, 1);
|
||||
assert.deepEqual(JoinPages.normalizeJoinApplies([valid, { status: '0' }]), []);
|
||||
assert.deepEqual(JoinPages.normalizeJoinApplies({ rows: [valid] }), []);
|
||||
});
|
||||
|
||||
test('普通用户列表隐藏内部字段且只有待审核申请可以撤销', () => {
|
||||
const html = JoinPages.renderMyJoinApplies([{
|
||||
applyId,
|
||||
genealogyId,
|
||||
genealogyName: '<叶氏家谱>',
|
||||
surname: '叶',
|
||||
relationDesc: '族亲',
|
||||
applyReason: '申请加入',
|
||||
status: '0',
|
||||
appUserId: 'secret-user',
|
||||
inviterUserId: 'secret-inviter',
|
||||
auditUserId: 'secret-auditor',
|
||||
appUserPhone: 'secret-phone'
|
||||
}]);
|
||||
const approvedHtml = JoinPages.renderMyJoinApplies([{
|
||||
applyId,
|
||||
genealogyId,
|
||||
genealogyName: '叶氏家谱',
|
||||
status: '1'
|
||||
}]);
|
||||
|
||||
assert.match(html, /<叶氏家谱>/);
|
||||
assert.match(html, new RegExp('data-join-cancel-id="' + applyId + '"'));
|
||||
assert.doesNotMatch(approvedHtml, /data-join-cancel-id/);
|
||||
assert.doesNotMatch(html, /secret-user|secret-inviter|secret-auditor|secret-phone|must-drop/);
|
||||
});
|
||||
|
||||
test('申请加入页由后端家谱选项驱动且不允许手填业务 ID 或邀请码', () => {
|
||||
const source = read('join-genealogy.html');
|
||||
|
||||
assert.match(source, /data-join-apply-page/);
|
||||
assert.match(source, /data-join-genealogy-search/);
|
||||
assert.match(source, /data-join-genealogy-options/);
|
||||
assert.match(source, /data-join-apply-form/);
|
||||
assert.match(source, /name="applicantName"/);
|
||||
assert.match(source, /name="phone"/);
|
||||
assert.match(source, /name="relationDesc"/);
|
||||
assert.match(source, /name="applyReason"/);
|
||||
assert.match(source, /public\/js\/join-pages\.js/);
|
||||
assert.doesNotMatch(source, /name="(?:genealogyId|applyId|inviterUserId|inviteCode)"/);
|
||||
assert.doesNotMatch(source, /邀请码|分享码|手工.*ID|输入.*ID/);
|
||||
});
|
||||
|
||||
test('我的加入申请页加载真实列表和刷新入口', () => {
|
||||
const source = read('profile-join-family.html');
|
||||
|
||||
assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/);
|
||||
assert.match(source, /data-my-join-applies-page/);
|
||||
assert.match(source, /data-join-apply-list="mine"/);
|
||||
assert.match(source, /data-join-apply-refresh/);
|
||||
assert.match(source, /public\/js\/join-pages\.js/);
|
||||
assert.doesNotMatch(source, /邀请码|分享码|输入.*ID/);
|
||||
});
|
||||
|
||||
test('加入申请运行时提交与撤销后都重新读取我的申请', () => {
|
||||
const source = read('public/js/join-pages.js');
|
||||
|
||||
assert.match(source, /await api\.applyToGenealogy\(/);
|
||||
assert.match(source, /await api\.myGenealogyJoinApplies\(\)/);
|
||||
assert.match(source, /await api\.cancelGenealogyJoinApply\(/);
|
||||
assert.match(source, /提交后无法读取同一申请/);
|
||||
assert.match(source, /撤销后申请仍处于待审核状态/);
|
||||
});
|
||||
Reference in New Issue
Block a user