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,169 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const FeedbackPages = require('../public/js/feedback-pages.js');
|
||||
|
||||
const feedbackId = '2062179707935264769';
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.resolve(__dirname, '..', relativePath), 'utf8');
|
||||
}
|
||||
|
||||
function feedbackFixture(overrides) {
|
||||
return Object.assign({
|
||||
feedbackId,
|
||||
appUserId: '2062179707935264701',
|
||||
appUserNickName: '叶子',
|
||||
appUserPhone: '19100000000',
|
||||
feedbackType: 'bug',
|
||||
feedbackContent: '页面按钮无响应',
|
||||
contactInfo: '19181970173',
|
||||
handleStatus: '2',
|
||||
handleResult: '已经修复',
|
||||
handlerId: '2062179707935264702',
|
||||
handleTime: '2026-07-29 13:00:00',
|
||||
status: '0',
|
||||
remark: '感谢反馈'
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
test('反馈请求只构造 AppFeedbackBody 并裁剪文本', () => {
|
||||
assert.deepEqual(FeedbackPages.buildFeedbackBody({
|
||||
feedbackType: ' bug ',
|
||||
feedbackContent: ' 页面按钮无响应 ',
|
||||
contactInfo: ' 19181970173 ',
|
||||
feedbackTitle: 'must-drop',
|
||||
appUserId: 'must-drop',
|
||||
handlerId: 'must-drop'
|
||||
}), {
|
||||
feedbackType: 'bug',
|
||||
feedbackContent: '页面按钮无响应',
|
||||
contactInfo: '19181970173'
|
||||
});
|
||||
assert.deepEqual(FeedbackPages.buildFeedbackBody({
|
||||
feedbackType: '',
|
||||
feedbackContent: ' 建议增加导出 ',
|
||||
contactInfo: ' '
|
||||
}), {
|
||||
feedbackContent: '建议增加导出'
|
||||
});
|
||||
});
|
||||
|
||||
test('反馈内容必填且类型只允许后端确认的四种值', () => {
|
||||
assert.equal(FeedbackPages.validateFeedbackBody({ feedbackContent: '建议增加导出' }), '');
|
||||
assert.equal(FeedbackPages.validateFeedbackBody({
|
||||
feedbackType: 'advice',
|
||||
feedbackContent: '建议增加导出'
|
||||
}), '');
|
||||
assert.match(FeedbackPages.validateFeedbackBody({ feedbackContent: '' }), /反馈内容/);
|
||||
assert.match(FeedbackPages.validateFeedbackBody({
|
||||
feedbackType: 'suggestion',
|
||||
feedbackContent: '建议增加导出'
|
||||
}), /反馈类型/);
|
||||
});
|
||||
|
||||
test('FeedbackVo 只保留用户可见字段并保持长 ID 字符串', () => {
|
||||
assert.deepEqual(FeedbackPages.normalizeFeedback(feedbackFixture()), {
|
||||
feedbackId,
|
||||
feedbackType: 'bug',
|
||||
feedbackContent: '页面按钮无响应',
|
||||
contactInfo: '19181970173',
|
||||
handleStatus: '2',
|
||||
handleResult: '已经修复',
|
||||
handleTime: '2026-07-29 13:00:00',
|
||||
status: '0',
|
||||
remark: '感谢反馈'
|
||||
});
|
||||
assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({
|
||||
feedbackId: Number.MAX_SAFE_INTEGER + 1
|
||||
})), null);
|
||||
assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({
|
||||
feedbackContent: ''
|
||||
})), null);
|
||||
assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({
|
||||
handleStatus: '9'
|
||||
})), null);
|
||||
assert.equal(FeedbackPages.normalizeFeedback(feedbackFixture({
|
||||
status: '9'
|
||||
})), null);
|
||||
});
|
||||
|
||||
test('反馈数组包含非法记录时整批拒绝', () => {
|
||||
assert.equal(FeedbackPages.normalizeFeedbackList([feedbackFixture()]).length, 1);
|
||||
assert.deepEqual(FeedbackPages.normalizeFeedbackList([
|
||||
feedbackFixture(),
|
||||
feedbackFixture({ feedbackId: '' })
|
||||
]), []);
|
||||
assert.deepEqual(FeedbackPages.normalizeFeedbackList({ rows: [feedbackFixture()] }), []);
|
||||
});
|
||||
|
||||
test('详情只精确匹配 URL 中的反馈 ID 且不存在时不回退', () => {
|
||||
const secondId = '2062179707935264770';
|
||||
const list = [feedbackFixture(), feedbackFixture({ feedbackId: secondId, feedbackContent: '第二条' })];
|
||||
|
||||
assert.equal(FeedbackPages.getFeedbackId('?feedbackId=' + secondId), secondId);
|
||||
assert.equal(FeedbackPages.getFeedbackId('?feedbackId=unsafe'), '');
|
||||
assert.equal(FeedbackPages.findFeedbackById(list, secondId).feedbackContent, '第二条');
|
||||
assert.equal(FeedbackPages.findFeedbackById(list, '2062179707935264999'), null);
|
||||
assert.equal(
|
||||
FeedbackPages.buildFeedbackDetailUrl(secondId),
|
||||
'ticket-detail.html?feedbackId=2062179707935264770'
|
||||
);
|
||||
});
|
||||
|
||||
test('反馈列表和详情转义文本且不暴露内部账号信息', () => {
|
||||
const item = feedbackFixture({
|
||||
feedbackContent: '<script>alert(1)</script>',
|
||||
handleResult: '<img src=x>',
|
||||
appUserId: 'secret-user',
|
||||
handlerId: 'secret-handler',
|
||||
appUserPhone: 'secret-account-phone'
|
||||
});
|
||||
const listHtml = FeedbackPages.renderFeedbackList([item], {
|
||||
detailPage: 'ticket-detail.html'
|
||||
});
|
||||
const detailHtml = FeedbackPages.renderFeedbackDetail(item);
|
||||
|
||||
assert.match(listHtml, /<script>/);
|
||||
assert.match(detailHtml, /<img src=x>/);
|
||||
assert.doesNotMatch(listHtml + detailHtml, /<script|<img|secret-user|secret-handler|secret-account-phone|appUserId|handlerId/);
|
||||
});
|
||||
|
||||
test('两个提交页只提供 AppFeedbackBody 字段并加载真实脚本', () => {
|
||||
['profile-feedback.html', 'submit-ticket.html'].forEach((page) => {
|
||||
const source = read(page);
|
||||
|
||||
assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/);
|
||||
assert.match(source, /data-feedback-form/);
|
||||
assert.match(source, /name="feedbackType"/);
|
||||
assert.match(source, /name="feedbackContent"/);
|
||||
assert.match(source, /name="contactInfo"/);
|
||||
assert.match(source, /public\/js\/feedback-pages\.js/);
|
||||
assert.doesNotMatch(source, /name="(?:feedbackTitle|feedbackId|appUserId|handlerId)"/);
|
||||
});
|
||||
});
|
||||
|
||||
test('我的工单和详情页读取同一反馈集合且不制造处理动作', () => {
|
||||
const listPage = read('my-tickets.html');
|
||||
const detailPage = read('ticket-detail.html');
|
||||
|
||||
[listPage, detailPage].forEach((source) => {
|
||||
assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/);
|
||||
assert.match(source, /public\/js\/feedback-pages\.js/);
|
||||
});
|
||||
assert.match(listPage, /data-feedback-list/);
|
||||
assert.match(listPage, /data-feedback-refresh/);
|
||||
assert.match(detailPage, /data-feedback-detail/);
|
||||
assert.doesNotMatch(detailPage, /回复|追问|删除|关闭工单|name="feedbackId"/);
|
||||
});
|
||||
|
||||
test('反馈运行时提交后重读同一 ID,详情不存在时不回退', () => {
|
||||
const source = read('public/js/feedback-pages.js');
|
||||
|
||||
assert.match(source, /await api\.submitFeedback\(/);
|
||||
assert.match(source, /await api\.myFeedback\(\)/);
|
||||
assert.match(source, /提交后无法读取同一反馈记录/);
|
||||
assert.match(source, /反馈记录不存在或无权查看/);
|
||||
});
|
||||
Reference in New Issue
Block a user