feat(withdraw): build dedicated withdrawal page with optimized layout and multichannel support

这个提交包含在:
xcbyy
2026-10-13 09:19:34 +08:00
父节点 e867488d0d
当前提交 1fb0cc4273
共有 9 个文件被更改,包括 1962 次插入 和 16 次删除

查看文件

@@ -0,0 +1,127 @@
import * as fs from 'fs';
import * as path from 'path';
import { ContentSecurityService } from '../src/core/security/content-security.service';
describe('ClientFeatures (TDD 测试套件: 用户端功能闭环)', () => {
const rootDir = path.resolve(__dirname, '../../');
const uniPagesDir = path.join(rootDir, 'pwuniapp', 'pages');
const serverSrcDir = path.join(rootDir, 'server', 'src');
const contentSecurityService = new ContentSecurityService();
describe('1. 举报违规行为通道闭环验证', () => {
it('后端 repository 必须实现 createReport 与 getReports 并支持存证入库', () => {
const repoPath = path.join(serverSrcDir, 'db', 'repository.ts');
const content = fs.readFileSync(repoPath, 'utf-8');
expect(content).toContain('createReport');
expect(content).toContain('getReports');
expect(content).toContain('reports');
expect(content).toContain('reporter_id');
expect(content).toContain('target_type');
});
it('后端 index.ts 必须暴露 POST /api/reports 路由并集成文本安全检测', () => {
const indexPath = path.join(serverSrcDir, 'index.ts');
const content = fs.readFileSync(indexPath, 'utf-8');
expect(content).toContain("app.post('/api/reports'");
expect(content).toContain('contentSecurityService.checkText');
expect(content).toContain('repo.createReport');
});
it('违规线索说明命中涉黄/欺诈敏感词时,内容安全风控应拦截', () => {
const checkResult = contentSecurityService.checkText('加微信私下转账免手续费直接打款');
expect(checkResult.pass).toBe(false);
expect(checkResult.code).toBe(87014);
expect(checkResult.suggest).toBe('block');
});
});
describe('2. 订单待支付倒计时与退款仲裁工单闭环验证', () => {
it('15分钟待支付倒计时计算逻辑应准确', () => {
const createdAt = new Date().toISOString();
const createdTime = new Date(createdAt).getTime();
const expireTime = createdTime + 15 * 60 * 1000;
const now = createdTime + 5 * 60 * 1000; // 过去了5分钟
const remainingSeconds = Math.max(0, Math.floor((expireTime - now) / 1000));
expect(remainingSeconds).toBe(10 * 60); // 剩余 600 秒
const minutes = Math.floor(remainingSeconds / 60);
const seconds = remainingSeconds % 60;
expect(`${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`).toBe('10:00');
});
it('后端 repository 必须实现 applyOrderRefund 并原子写入 arbitrations 仲裁表', () => {
const repoPath = path.join(serverSrcDir, 'db', 'repository.ts');
const content = fs.readFileSync(repoPath, 'utf-8');
expect(content).toContain('applyOrderRefund');
expect(content).toContain('争议仲裁中');
expect(content).toContain('arbitrations');
});
it('后端 index.ts POST /api/orders/:id/refund 必须接收理由与说明并执行风控拦截', () => {
const indexPath = path.join(serverSrcDir, 'index.ts');
const content = fs.readFileSync(indexPath, 'utf-8');
expect(content).toContain("app.post('/api/orders/:id/refund'");
expect(content).toContain('reason');
expect(content).toContain('contentSecurityService.checkText');
expect(content).toContain('repo.applyOrderRefund');
});
});
describe('3. 个人资料性别与签名更新闭环验证', () => {
it('后端 repository 必须在 updateUserProfile 中支持 gender 与 signature 字段处理', () => {
const repoPath = path.join(serverSrcDir, 'db', 'repository.ts');
const content = fs.readFileSync(repoPath, 'utf-8');
expect(content).toContain('gender');
expect(content).toContain('signature');
expect(content).toContain('slice(0, 64)');
});
it('后端 index.ts POST /api/users/:id/profile 必须支持性别白名单与个性签名风控', () => {
const indexPath = path.join(serverSrcDir, 'index.ts');
const content = fs.readFileSync(indexPath, 'utf-8');
expect(content).toContain("app.post('/api/users/:id/profile'");
expect(content).toContain('signature');
expect(content).toContain('contentSecurityService.checkText');
});
});
describe('4. 前端核心页面规范与结构完整性校验', () => {
it('个人中心页面 (user/index.vue) 必须包含个性签名展示与性别修改入口', () => {
const userVuePath = path.join(uniPagesDir, 'user', 'index.vue');
const content = fs.readFileSync(userVuePath, 'utf-8');
expect(content).toContain('user.signature');
expect(content).toContain('editForm.gender');
expect(content).toContain('editForm.signature');
expect(content).toContain('个性签名');
});
it('订单中心页面 (order/list.vue) 必须包含倒计时徽章、退款仲裁工单与举报入口', () => {
const orderVuePath = path.join(uniPagesDir, 'order', 'list.vue');
const content = fs.readFileSync(orderVuePath, 'utf-8');
expect(content).toContain('countdown-badge');
expect(content).toContain('getOrderCountdown');
expect(content).toContain('openRefundModal');
expect(content).toContain('openReportModal');
expect(content).toContain('refundForm');
expect(content).toContain('reportForm');
});
it('陪玩详情页面 (expert/detail.vue) 必须包含违规举报操作入口与弹窗', () => {
const detailVuePath = path.join(uniPagesDir, 'expert', 'detail.vue');
const content = fs.readFileSync(detailVuePath, 'utf-8');
expect(content).toContain('openReportModal');
expect(content).toContain('submitReportTicket');
expect(content).toContain('presetReportReasons');
expect(content).toContain('review-modal-mask');
});
});
});

查看文件

@@ -130,7 +130,7 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
const raw = fs.readFileSync(pagesJsonPath, 'utf-8');
const config = JSON.parse(raw);
expect(Array.isArray(config.pages)).toBe(true);
expect(config.pages.length).toBe(16);
expect(config.pages.length).toBe(17);
for (const pageItem of config.pages) {
const vueFilePath = path.join(rootDir, 'pwuniapp', `${pageItem.path}.vue`);
@@ -674,6 +674,53 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
});
});
});
describe('15. 独立专属收益提现页面验证 (pages/user/withdraw.vue)', () => {
it('pages.json 必须成功注册 pages/user/withdraw 且物理文件必须存在', () => {
const wdPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'withdraw.vue');
expect(fs.existsSync(wdPath)).toBe(true);
const pagesJsonPath = path.join(rootDir, 'pwuniapp', 'pages.json');
const pagesJson = fs.readFileSync(pagesJsonPath, 'utf-8');
expect(pagesJson).toContain('pages/user/withdraw');
expect(pagesJson).toContain('收益提现');
});
it('独立提现页面必须支持微信零钱、支付宝、银行卡三渠道与金额输入校验', () => {
const wdPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'withdraw.vue');
const content = fs.readFileSync(wdPath, 'utf-8');
expect(content).toContain('currentChannel');
expect(content).toContain('WECHAT');
expect(content).toContain('ALIPAY');
expect(content).toContain('BANK');
expect(content).toContain('withdrawAmount');
expect(content).toContain('handleFillAllCommission');
expect(content).toContain('withdrawCommission');
expect(content).toContain('showRecordsDrawer');
});
it('个人中心与工作台必须实现对专属提现页面的原生导航跳转', () => {
const userPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
const userContent = fs.readFileSync(userPath, 'utf-8');
expect(userContent).toContain('/pages/user/withdraw');
const wsPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'workspace.vue');
const wsContent = fs.readFileSync(wsPath, 'utf-8');
expect(wsContent).toContain('/pages/user/withdraw');
});
it('提现页面代码严禁包含任何系统 Emoji 与 showModal 4 字门禁违规', () => {
const wdPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'withdraw.vue');
const content = fs.readFileSync(wdPath, 'utf-8');
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
expect(emojiRegex.test(content)).toBe(false);
const modalMatches = content.matchAll(/(?:confirmText|cancelText):\s*['"]([^'"]+)['"]/g);
for (const match of modalMatches) {
const btnText = match[1];
expect(btnText.length).toBeLessThanOrEqual(4);
}
});
});
});