/** * 后台提现管理(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); }