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,186 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const FamilyHomePages = require('../public/js/family-home-pages.js');
|
||||
|
||||
const genealogyId = '2062179707935264769';
|
||||
const root = path.resolve(__dirname, '..');
|
||||
|
||||
function overviewFixture(overrides) {
|
||||
return Object.assign({
|
||||
genealogyId,
|
||||
genealogyNo: 'G20260729001',
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
ancestralHall: '南阳堂',
|
||||
originPlace: '四川成都',
|
||||
regionCode: '510100',
|
||||
regionName: '成都市',
|
||||
regionFullName: '四川省 成都市',
|
||||
addressDetail: '高新区',
|
||||
coverOssId: '2062179707935264700',
|
||||
intro: '叶氏源流',
|
||||
ownerUserId: '2062179707935264701',
|
||||
visibility: '1',
|
||||
joinMode: '1',
|
||||
memberCount: 12,
|
||||
personCount: 36,
|
||||
status: '0',
|
||||
roleType: 'owner',
|
||||
canManage: true,
|
||||
canEditContent: true,
|
||||
memberStatus: '0',
|
||||
joinTime: '2026-07-29 12:00:00'
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
test('家谱主页只保留 AppGenealogyVo 的安全展示字段并匹配当前上下文', () => {
|
||||
assert.deepEqual(FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture(),
|
||||
genealogyId
|
||||
), {
|
||||
genealogyId,
|
||||
genealogyNo: 'G20260729001',
|
||||
genealogyName: '叶氏家谱',
|
||||
surname: '叶',
|
||||
ancestralHall: '南阳堂',
|
||||
originPlace: '四川成都',
|
||||
regionFullName: '四川省 成都市',
|
||||
memberCount: 12,
|
||||
personCount: 36,
|
||||
canManage: true,
|
||||
canEditContent: true
|
||||
});
|
||||
|
||||
assert.equal(FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture({ genealogyId: '2062179707935264770' }),
|
||||
genealogyId
|
||||
), null);
|
||||
assert.equal(FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture({ genealogyId: Number.MAX_SAFE_INTEGER + 1 }),
|
||||
genealogyId
|
||||
), null);
|
||||
});
|
||||
|
||||
test('家谱主页拒绝停用、空名称和非法计数响应', () => {
|
||||
assert.equal(FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture({ status: '1' }),
|
||||
genealogyId
|
||||
), null);
|
||||
assert.equal(FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture({ genealogyName: '' }),
|
||||
genealogyId
|
||||
), null);
|
||||
assert.equal(FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture({ memberCount: -1 }),
|
||||
genealogyId
|
||||
), null);
|
||||
assert.equal(FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture({ personCount: Number.MAX_SAFE_INTEGER + 1 }),
|
||||
genealogyId
|
||||
), null);
|
||||
});
|
||||
|
||||
test('家谱概览渲染转义文本并隐藏内部用户与文件编号', () => {
|
||||
const overview = FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture({
|
||||
genealogyName: '<叶氏家谱>',
|
||||
ancestralHall: '<南阳堂>'
|
||||
}),
|
||||
genealogyId
|
||||
);
|
||||
const html = FamilyHomePages.renderFamilyOverview(overview);
|
||||
|
||||
assert.match(html, /<叶氏家谱>/);
|
||||
assert.match(html, /<南阳堂>/);
|
||||
assert.match(html, /成员 12 人/);
|
||||
assert.match(html, /世系人物 36 人/);
|
||||
assert.doesNotMatch(html, /ownerUserId|coverOssId|206217970793526470[01]/);
|
||||
});
|
||||
|
||||
test('家谱管理入口只接受后端布尔能力字段', () => {
|
||||
assert.equal(FamilyHomePages.shouldShowFamilyManagement(
|
||||
FamilyHomePages.normalizeFamilyOverview(overviewFixture({ canManage: true }), genealogyId)
|
||||
), true);
|
||||
assert.equal(FamilyHomePages.shouldShowFamilyManagement(
|
||||
FamilyHomePages.normalizeFamilyOverview(overviewFixture({ canManage: 'true' }), genealogyId)
|
||||
), false);
|
||||
});
|
||||
|
||||
test('家谱主页只把缺失登录态和 401 视为登录失效', () => {
|
||||
assert.equal(FamilyHomePages.shouldRedirectToLogin({
|
||||
getToken() { return 'token'; }
|
||||
}, { status: 403 }), false);
|
||||
assert.equal(FamilyHomePages.shouldRedirectToLogin({
|
||||
getToken() { return 'token'; }
|
||||
}, { status: 401 }), true);
|
||||
assert.equal(FamilyHomePages.shouldRedirectToLogin({
|
||||
getToken() { return ''; }
|
||||
}), true);
|
||||
});
|
||||
|
||||
test('家谱主页只读取当前家谱概览和世系树', async () => {
|
||||
const calls = [];
|
||||
const tree = [{
|
||||
personId: '2062179707935264770',
|
||||
genealogyId,
|
||||
name: '始祖',
|
||||
spouses: [],
|
||||
children: []
|
||||
}];
|
||||
const result = await FamilyHomePages.loadFamilyHome({
|
||||
genealogyOverview(receivedId) {
|
||||
calls.push(['overview', receivedId]);
|
||||
return Promise.resolve(overviewFixture());
|
||||
},
|
||||
lineageTree(receivedId) {
|
||||
calls.push(['tree', receivedId]);
|
||||
return Promise.resolve(tree);
|
||||
}
|
||||
}, genealogyId);
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
['overview', genealogyId],
|
||||
['tree', genealogyId]
|
||||
]);
|
||||
assert.deepEqual(result.overview, FamilyHomePages.normalizeFamilyOverview(
|
||||
overviewFixture(),
|
||||
genealogyId
|
||||
));
|
||||
assert.deepEqual(result.tree, tree);
|
||||
});
|
||||
|
||||
test('家谱主页开放真实只读概览且不制造邀请或聚合统计入口', () => {
|
||||
const source = fs.readFileSync(path.join(root, 'profile-family-home.html'), 'utf8');
|
||||
|
||||
assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/);
|
||||
assert.doesNotMatch(source, /四川武胜汤氏族/);
|
||||
assert.doesNotMatch(source, /href="profile-invite\.html"/);
|
||||
assert.match(source, /PC 暂未开放邀请/);
|
||||
assert.match(source, /data-family-home-title/);
|
||||
assert.match(source, /data-family-home-overview/);
|
||||
assert.match(source, /data-family-home-member-count/);
|
||||
assert.match(source, /data-family-home-person-count/);
|
||||
assert.match(source, /data-family-home-management/);
|
||||
assert.match(source, /data-lineage-home-tree/);
|
||||
assert.match(source, /src="public\/js\/lineage-pages\.js"/);
|
||||
assert.match(source, /src="public\/js\/family-home-pages\.js"/);
|
||||
|
||||
[
|
||||
'profile-feed.html',
|
||||
'profile-tree.html',
|
||||
'profile-article.html',
|
||||
'profile-album.html',
|
||||
'profile-video.html',
|
||||
'profile-merit.html',
|
||||
'profile-ceremony.html'
|
||||
].forEach((href) => {
|
||||
assert.match(
|
||||
source,
|
||||
new RegExp('href="' + href.replace('.', '\\.') + '"[^>]*data-genealogy-context-link'),
|
||||
`${href} 缺少家谱上下文`
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user