249 行
12 KiB
JavaScript
249 行
12 KiB
JavaScript
/**
|
||
* 后台提现管理(shopro/withdraw)导出功能全链路规格与算法模拟测试
|
||
* 覆盖范围:
|
||
* 1. application/admin/controller/shopro/Withdraw.php 控制器 export 方法、权限配置、save_type 配置注入
|
||
* 2. application/admin/model/shopro/Withdraw.php 模型 user 关联包含 mobile 字段
|
||
* 3. application/admin/view/shopro/withdraw/index.html 视图导出按钮与权限指令
|
||
* 4. public/assets/js/backend/shopro/withdraw.js 前端脚本 onExport 方法与 Vue ref/exportLoading 响应式变量
|
||
* 5. 提现数据导出映射逻辑(普通余额提现 vs DMEPA数字资产提现)单元与格式算法模拟
|
||
* 6. 大数防科学计数法(\t 前缀)、汇总行金额精确累加逻辑模拟断言
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const assert = require('assert');
|
||
|
||
let passCount = 0;
|
||
let failCount = 0;
|
||
|
||
function testAssert(condition, message) {
|
||
if (condition) {
|
||
console.log(` \x1b[32m[PASS]\x1b[0m ${message}`);
|
||
passCount++;
|
||
} else {
|
||
console.error(` \x1b[31m[FAIL]\x1b[0m ${message}`);
|
||
failCount++;
|
||
}
|
||
}
|
||
|
||
console.log('========================================================');
|
||
console.log(' 后台提现管理订单导出功能全链路专项测试');
|
||
console.log('========================================================\n');
|
||
|
||
// ----------------------------------------------------
|
||
// 1. Withdraw.php 控制器层导出功能验证
|
||
// ----------------------------------------------------
|
||
console.log('--- 1. application/admin/controller/shopro/Withdraw.php 控制器导出逻辑 ---');
|
||
const controllerPath = path.resolve(__dirname, '../application/admin/controller/shopro/Withdraw.php');
|
||
testAssert(fs.existsSync(controllerPath), 'Withdraw.php 控制器文件存在');
|
||
if (fs.existsSync(controllerPath)) {
|
||
const content = fs.readFileSync(controllerPath, 'utf-8');
|
||
testAssert(content.includes("'export'"), '$noNeedRight 包含 export 白名单');
|
||
testAssert(content.includes('$this->assignconfig("save_type"'), 'index() 注入 save_type 配置');
|
||
testAssert(content.includes('public function export()'), '控制器包含 public function export() 方法');
|
||
testAssert(content.includes("$this->model->sheepFilter()"), 'export 使用 $this->model->sheepFilter() 进行数据过滤');
|
||
testAssert(content.includes("addons\\shopro\\library\\Export"), '引入 Shopro 统一导出库 Export');
|
||
testAssert(content.includes("'商户单号'"), '导出表头包含 商户单号');
|
||
testAssert(content.includes("'申请用户'"), '导出表头包含 申请用户');
|
||
testAssert(content.includes("'用户手机号'"), '导出表头包含 用户手机号');
|
||
testAssert(content.includes("'提现资产'"), '导出表头包含 提现资产');
|
||
testAssert(content.includes("提现金额/换算市值"), '导出表头包含 提现金额/换算市值');
|
||
testAssert(content.includes("实际到账"), '导出表头包含 实际到账');
|
||
testAssert(content.includes("'提现方式'"), '导出表头包含 提现方式');
|
||
testAssert(content.includes("银行卡号"), '导出表头包含 银行卡号');
|
||
testAssert(content.includes("'资产折算明细'"), '导出表头包含 资产折算明细');
|
||
testAssert(content.includes('"\\t"'), '导出对长订单号、卡号等使用制表符防止科学计数法');
|
||
testAssert(content.includes("is_madow_withdraw"), '正确区分 DMEPA 资产提现与普通余额提现');
|
||
testAssert(content.includes("'id' => '合计: 共 ' . $total"), '生成底部汇总统计行');
|
||
}
|
||
|
||
// ----------------------------------------------------
|
||
// 2. Withdraw.php 模型层用户信息关联验证
|
||
// ----------------------------------------------------
|
||
console.log('\n--- 2. application/admin/model/shopro/Withdraw.php 模型层验证 ---');
|
||
const modelPath = path.resolve(__dirname, '../application/admin/model/shopro/Withdraw.php');
|
||
testAssert(fs.existsSync(modelPath), 'Withdraw.php 模型文件存在');
|
||
if (fs.existsSync(modelPath)) {
|
||
const content = fs.readFileSync(modelPath, 'utf-8');
|
||
testAssert(content.includes("field('id, nickname, mobile, avatar, total_consume')"), 'user 关联字段包含 mobile');
|
||
}
|
||
|
||
// ----------------------------------------------------
|
||
// 3. index.html 视图层导出按钮与权限验证
|
||
// ----------------------------------------------------
|
||
console.log('\n--- 3. application/admin/view/shopro/withdraw/index.html 视图层验证 ---');
|
||
const viewPath = path.resolve(__dirname, '../application/admin/view/shopro/withdraw/index.html');
|
||
testAssert(fs.existsSync(viewPath), 'index.html 视图文件存在');
|
||
if (fs.existsSync(viewPath)) {
|
||
const content = fs.readFileSync(viewPath, 'utf-8');
|
||
testAssert(content.includes("onExport('export')"), '页面包含 onExport 导出点击事件');
|
||
testAssert(content.includes(":loading=\"exportLoading\""), '按钮绑定 exportLoading 加载状态');
|
||
testAssert(content.includes("$auth->check('shopro/withdraw/export')"), '导出按钮包裹权限校验');
|
||
}
|
||
|
||
// ----------------------------------------------------
|
||
// 4. withdraw.js 前端脚本层验证
|
||
// ----------------------------------------------------
|
||
console.log('\n--- 4. public/assets/js/backend/shopro/withdraw.js 前端脚本验证 ---');
|
||
const jsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/withdraw.js');
|
||
testAssert(fs.existsSync(jsPath), 'withdraw.js 前端脚本文件存在');
|
||
if (fs.existsSync(jsPath)) {
|
||
const content = fs.readFileSync(jsPath, 'utf-8');
|
||
testAssert(content.includes("const { reactive, onMounted, ref } = Vue"), '从 Vue 中解构引入 ref');
|
||
testAssert(content.includes("const exportLoading = ref(false)"), '定义 exportLoading 响应式变量');
|
||
testAssert(content.includes("function onExport(type = 'export')"), '实现 onExport 导出处理方法');
|
||
testAssert(content.includes("Config.save_type == 'download'"), '支持直接浏览器下载模式 (Config.save_type == download)');
|
||
testAssert(content.includes("Fast.api.ajax"), '支持异步导出模式 (Fast.api.ajax)');
|
||
testAssert(content.includes("exportLoading,") && content.includes("onExport,"), 'setup() return 暴露 exportLoading 与 onExport');
|
||
}
|
||
|
||
// ----------------------------------------------------
|
||
// 5. 提现数据导出格式化算法与业务模拟
|
||
// ----------------------------------------------------
|
||
console.log('\n--- 5. 提现数据导出格式化算法与业务模拟 ---');
|
||
|
||
// 模拟导出映射函数(与控制器逻辑对齐)
|
||
function formatExportRow(row) {
|
||
const isMadow = Boolean(row.is_madow_withdraw);
|
||
const withdrawInfo = row.withdraw_info || {};
|
||
const withdrawType = row.withdraw_type || '';
|
||
const withdrawTypeMap = {
|
||
'wechat': '微信零钱',
|
||
'alipay': '支付宝',
|
||
'bank': '银行卡',
|
||
};
|
||
const statusMap = {
|
||
'0': '待审核',
|
||
'1': '处理中',
|
||
'2': '已处理',
|
||
'-1': '已拒绝',
|
||
};
|
||
|
||
let realName = '';
|
||
let bankName = '';
|
||
let accountNo = '';
|
||
if (withdrawType === 'bank') {
|
||
realName = withdrawInfo.real_name || '';
|
||
bankName = withdrawInfo.bank_name || '';
|
||
accountNo = withdrawInfo.card_no ? `\t${withdrawInfo.card_no}` : '';
|
||
} else if (withdrawType === 'alipay') {
|
||
realName = withdrawInfo.real_name || '';
|
||
accountNo = withdrawInfo.account ? `\t${withdrawInfo.account}` : '';
|
||
} else if (withdrawType === 'wechat') {
|
||
realName = withdrawInfo.real_name || '';
|
||
accountNo = withdrawInfo.openid ? `\t${withdrawInfo.openid}` : '';
|
||
}
|
||
|
||
let assetName = '钱包余额';
|
||
let assetAmountDetail = '-';
|
||
let amountDisplay = Number(row.money || 0).toFixed(2);
|
||
let actualAmountDisplay = Number(row.actual_money || 0).toFixed(2);
|
||
let chargeMoneyDisplay = Number(row.charge_money || 0).toFixed(2);
|
||
let serviceFeeRateDisplay = `${row.service_fee || 0}%`;
|
||
|
||
if (isMadow) {
|
||
assetName = 'DMEPA数字资产';
|
||
const madowAmount = Number(withdrawInfo.madow_amount || 0);
|
||
const madowRate = Number(withdrawInfo.madow_rate || 0);
|
||
assetAmountDetail = `DMEPA: ${madowAmount.toFixed(4)}, 汇率: ¥${madowRate.toFixed(4)}`;
|
||
amountDisplay = `¥${Number(row.money || 0).toFixed(2)}`;
|
||
actualAmountDisplay = `¥${Number(row.actual_money || 0).toFixed(2)}`;
|
||
}
|
||
|
||
return {
|
||
id: row.id,
|
||
sn: `\t${row.sn}`,
|
||
user: `[${row.user_id}] ${row.user ? row.user.nickname : ''}`,
|
||
mobile: row.user && row.user.mobile ? `\t${row.user.mobile}` : '-',
|
||
asset_name: assetName,
|
||
money: amountDisplay,
|
||
actual_money: actualAmountDisplay,
|
||
service_fee: serviceFeeRateDisplay,
|
||
charge_money: chargeMoneyDisplay,
|
||
withdraw_type_text: withdrawTypeMap[withdrawType] || withdrawType,
|
||
real_name: realName,
|
||
bank_name: bankName,
|
||
card_no: accountNo,
|
||
asset_detail: assetAmountDetail,
|
||
status_text: statusMap[String(row.status)] || String(row.status),
|
||
status_msg: row.status_msg || '-',
|
||
createtime: '2026-09-21 14:00:00',
|
||
updatetime: '2026-09-21 14:10:00',
|
||
};
|
||
}
|
||
|
||
// 模拟场景 A:普通钱包余额提现至银行卡
|
||
const mockRowA = {
|
||
id: 101,
|
||
sn: 'TX202609210001',
|
||
user_id: 8888,
|
||
user: { id: 8888, nickname: '创客小张', mobile: '13800138000' },
|
||
is_madow_withdraw: false,
|
||
money: 1000.00,
|
||
actual_money: 990.00,
|
||
charge_money: 10.00,
|
||
service_fee: 1.0,
|
||
withdraw_type: 'bank',
|
||
withdraw_info: {
|
||
real_name: '张三',
|
||
bank_name: '中国工商银行',
|
||
card_no: '6222021000112233445'
|
||
},
|
||
status: 2,
|
||
status_msg: '打款成功',
|
||
};
|
||
|
||
const formattedA = formatExportRow(mockRowA);
|
||
testAssert(formattedA.asset_name === '钱包余额', '普通余额提现资产显示为 钱包余额');
|
||
testAssert(formattedA.mobile === '\t13800138000', '手机号添加制表符防失真');
|
||
testAssert(formattedA.card_no === '\t6222021000112233445', '银行卡号添加制表符防科学计数法');
|
||
testAssert(formattedA.money === '1000.00', '提现金额精准显示 1000.00');
|
||
testAssert(formattedA.actual_money === '990.00', '实际到账金额精准显示 990.00');
|
||
testAssert(formattedA.charge_money === '10.00', '手续费精准显示 10.00');
|
||
testAssert(formattedA.status_text === '已处理', '状态正确转为 已处理');
|
||
|
||
// 模拟场景 B:DMEPA 数字资产提现
|
||
const mockRowB = {
|
||
id: 102,
|
||
sn: 'TX202609210002',
|
||
user_id: 9999,
|
||
user: { id: 9999, nickname: '合伙人李四', mobile: '13900139000' },
|
||
is_madow_withdraw: true,
|
||
money: 5000.00,
|
||
actual_money: 4950.00,
|
||
charge_money: 50.00,
|
||
service_fee: 1.0,
|
||
withdraw_type: 'bank',
|
||
withdraw_info: {
|
||
real_name: '李四',
|
||
bank_name: '招商银行',
|
||
card_no: '6214830123456789',
|
||
madow_amount: 1000.0000,
|
||
madow_rate: 5.0000,
|
||
},
|
||
status: 0,
|
||
status_msg: '',
|
||
};
|
||
|
||
const formattedB = formatExportRow(mockRowB);
|
||
testAssert(formattedB.asset_name === 'DMEPA数字资产', 'DMEPA提现资产显示为 DMEPA数字资产');
|
||
testAssert(formattedB.asset_detail.includes('DMEPA: 1000.0000'), '资产折算明细包含 DMEPA 数量');
|
||
testAssert(formattedB.asset_detail.includes('汇率: ¥5.0000'), '资产折算明细包含汇率');
|
||
testAssert(formattedB.status_text === '待审核', '状态正确转为 待审核');
|
||
|
||
// 模拟场景 C:合计行计算验证
|
||
const totalMoney = mockRowA.money + mockRowB.money;
|
||
const totalActualMoney = mockRowA.actual_money + mockRowB.actual_money;
|
||
const totalChargeMoney = mockRowA.charge_money + mockRowB.charge_money;
|
||
testAssert(totalMoney === 6000.00, '提现总额合计为 6000.00 元');
|
||
testAssert(totalActualMoney === 5940.00, '实际到账总额合计为 5940.00 元');
|
||
testAssert(totalChargeMoney === 60.00, '手续费总额合计为 60.00 元');
|
||
|
||
console.log('\n========================================================');
|
||
console.log(`测试完成!通过: ${passCount}, 失败: ${failCount}`);
|
||
console.log('========================================================\n');
|
||
|
||
if (failCount > 0) {
|
||
process.exit(1);
|
||
}
|