feat(dmepa): 全局统一规范DMEPA命名并优化游客与品酒师身份显示

这个提交包含在:
chen
2026-09-19 15:41:39 +08:00
父节点 3868becc63
当前提交 fd8c1d09cc
共有 65 个文件被更改,包括 1068 次插入 和 390 次删除

查看文件

@@ -104,7 +104,7 @@ class DashboardWealthTest
'total() 返回完整的 wealth_data 资金盘宏观指标');
$this->assert(!empty($madow) && isset($madow['total_madow'], $madow['current_rate'], $madow['rate_per_wan'], $madow['total_madow_cny']),
'total() 返回完整的 madow_data 马豆资产与折算人民币指标');
'total() 返回完整的 madow_data dmepa资产与折算人民币指标');
$this->assert(!empty($voucher) && isset($voucher['total_voucher'], $voucher['released_voucher'], $voucher['pending_voucher']),
'total() 返回完整的 voucher_data 消费兑换券杠杆资金池指标');
@@ -120,7 +120,7 @@ class DashboardWealthTest
}
/**
* 测试 2:每一个角色身份的会员数、马豆数量、分佣数量与马豆价值精算校验
* 测试 2:每一个角色身份的会员数、dmepa数量、分佣数量与dmepa价值精算校验
*/
public function testAllRolesStatisticsIntegrity()
{
@@ -134,7 +134,7 @@ class DashboardWealthTest
$this->assert(isset($roleStats[$roleKey]), "矩阵包含角色身份: {$roleKey}");
$item = $roleStats[$roleKey] ?? [];
$this->assert(isset($item['user_count'], $item['madow_total'], $item['madow_cny'], $item['reward_amount'], $item['reward_count'], $item['active_rate']),
"角色 {$roleKey} 完整包含会员数、持豆量、马豆市值、分佣总额、分佣笔数与活跃率");
"角色 {$roleKey} 完整包含会员数、持豆量、dmepa市值、分佣总额、分佣笔数与活跃率");
}
// 普通会员无需自购考核标识

查看文件

@@ -67,7 +67,7 @@ class MultiAssetRechargeTest
public function run()
{
echo "========================================================\n";
echo " 会员多资产(余额/积分/马豆/兑换券)充值业务测试 \n";
echo " 会员多资产(余额/积分/dmepa/兑换券)充值业务测试 \n";
echo "========================================================\n\n";
$this->setUp();
@@ -291,41 +291,41 @@ class MultiAssetRechargeTest
public function testMadowRechargeAndDeduct()
{
// 充值 5.12345678 颗马豆
// 充值 5.12345678 颗dmepa
$res1 = $this->invokeRecharge([
'id' => $this->testUserId,
'type' => 'madow',
'amount' => '5.12345678',
'memo' => '充值马豆',
'memo' => '充值dmepa',
]);
$agent1 = AgentModel::where('user_id', $this->testUserId)->find();
$this->assert($res1['code'] === 1 && $agent1->madow == '15.12345678', '马豆8位高精度充值到账', "当前马豆: {$agent1->madow}");
$this->assert($res1['code'] === 1 && $agent1->madow == '15.12345678', 'dmepa8位高精度充值到账', "当前dmepa: {$agent1->madow}");
// 扣除 3.00000000 颗马豆
// 扣除 3.00000000 颗dmepa
$res2 = $this->invokeRecharge([
'id' => $this->testUserId,
'type' => 'madow',
'amount' => '-3.00000000',
'memo' => '扣除马豆',
'memo' => '扣除dmepa',
]);
$agent2 = AgentModel::where('user_id', $this->testUserId)->find();
$this->assert($res2['code'] === 1 && $agent2->madow == '12.12345678', '马豆高精度扣除成功', "当前马豆: {$agent2->madow}");
$this->assert($res2['code'] === 1 && $agent2->madow == '12.12345678', 'dmepa高精度扣除成功', "当前dmepa: {$agent2->madow}");
// 验证流水表记录
$log = RewardModel::where('user_id', $this->testUserId)->where('reward_type', 'admin_madow')->order('id', 'desc')->find();
$this->assert($log && $log->status == 1, '马豆充值写入收益流水明细');
$this->assert($log && $log->status == 1, 'dmepa充值写入收益流水明细');
}
public function testMadowOverdrawn()
{
// 尝试超额扣除马豆
// 尝试超额扣除dmepa
$res = $this->invokeRecharge([
'id' => $this->testUserId,
'type' => 'madow',
'amount' => '-99.00000000',
'memo' => '超额扣除马豆',
'memo' => '超额扣除dmepa',
]);
$this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, '马豆持仓不足拦截超扣', $res['msg'] ?? '');
$this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, 'dmepa持仓不足拦截超扣', $res['msg'] ?? '');
}
public function testVoucherRechargeAndDeduct()

查看文件

@@ -71,15 +71,15 @@ function doRecharge($type, $amount, $memo)
echo "=== 开始对会员 10 (尚善) 真实链路充值验证 ===\n";
// 1. 马豆充值 +8 颗
$r1 = doRecharge('madow', '8.00000000', '真实环境测试充值马豆');
// 1. dmepa充值 +8 颗
$r1 = doRecharge('madow', '8.00000000', '真实环境测试充值dmepa');
$agent1 = Db::name('shopro_commission_agent')->where('user_id', 10)->find();
echo "1. 充值马豆+8结果: " . json_encode($r1, JSON_UNESCAPED_UNICODE) . " 当前持仓: " . ($agent1['madow'] ?? '0') . "\n";
echo "1. 充值dmepa+8结果: " . json_encode($r1, JSON_UNESCAPED_UNICODE) . " 当前持仓: " . ($agent1['madow'] ?? '0') . "\n";
// 2. 马豆扣除 -8 颗
$r2 = doRecharge('madow', '-8.00000000', '真实环境测试扣除马豆');
// 2. dmepa扣除 -8 颗
$r2 = doRecharge('madow', '-8.00000000', '真实环境测试扣除dmepa');
$agent2 = Db::name('shopro_commission_agent')->where('user_id', 10)->find();
echo "2. 扣除马豆-8结果: " . json_encode($r2, JSON_UNESCAPED_UNICODE) . " 恢复持仓: " . ($agent2['madow'] ?? '0') . "\n";
echo "2. 扣除dmepa-8结果: " . json_encode($r2, JSON_UNESCAPED_UNICODE) . " 恢复持仓: " . ($agent2['madow'] ?? '0') . "\n";
// 3. 兑换券充值 +20 元
$r3 = doRecharge('voucher', '20.00', '真实环境测试充值兑换券');
@@ -105,7 +105,7 @@ echo "6. 扣除积分-10结果: " . json_encode($r6, JSON_UNESCAPED_UNICODE) . "
$rewards = Db::name('shopro_partner_reward')->where('user_id', 10)->whereIn('reward_type', ['admin_madow', 'admin_voucher'])->order('id', 'desc')->limit(4)->select();
echo "\n=== 会员 10 收益流水落库检查 (共 " . count($rewards) . " 笔充值/扣除流水) ===\n";
foreach ($rewards as $idx => $item) {
echo " [" . ($idx + 1) . "] 类型: {$item['reward_type']} | 金额: {$item['amount']} | 马豆: {$item['madow']} | 备注: " . $item['ext'] . "\n";
echo " [" . ($idx + 1) . "] 类型: {$item['reward_type']} | 金额: {$item['amount']} | dmepa: {$item['madow']} | 备注: " . $item['ext'] . "\n";
}
echo "\n=== 会员 10 真实测试全流程 100% 成功完毕 ===\n";

查看文件

@@ -32,7 +32,7 @@ assert(rewardModelContent.includes('function getWalletTypeTextAttr'), '包含 ge
assert(rewardModelContent.includes("PartnerConfig::rewardWalletType()"), '现金分润缺省联动 PartnerConfig::rewardWalletType()');
assert(rewardModelContent.includes("'商城余额'"), 'wallet_type_text 映射包含 商城余额');
assert(rewardModelContent.includes("'佣金钱包'"), 'wallet_type_text 映射包含 佣金钱包');
assert(rewardModelContent.includes("'分红马豆'"), 'wallet_type_text 映射包含 分红马豆');
assert(rewardModelContent.includes("'分红dmepa'"), 'wallet_type_text 映射包含 分红dmepa');
// --- 2. Order.php 订单分成构建与入账账户对齐验证 ---
console.log('\n--- 2. Order.php 订单分成构建与入账账户对齐验证 ---');

查看文件

@@ -0,0 +1,94 @@
const fs = require('fs');
const path = require('path');
const assert = require('assert');
console.log('========================================================');
console.log(' 现金收益明细隔离虚拟资产与清除0元记录 专项测试');
console.log('========================================================\n');
const rootDir = path.resolve(__dirname, '../../');
// 1. 验证 partner/Index.php
const indexCtrlPath = path.join(rootDir, 'addons/shopro/controller/partner/Index.php');
assert(fs.existsSync(indexCtrlPath), 'addons/shopro/controller/partner/Index.php 文件存在');
const indexContent = fs.readFileSync(indexCtrlPath, 'utf8');
// 验证现金白名单与排他性
assert(indexContent.includes('$cashTypes = array_merge(PartnerConfig::instantTypes(), [\'venue_share\', \'county_share\'])'), 'rewards 接口定义了专属现金类型白名单');
assert(!indexContent.includes("'share' => ['venue_share', 'county_share', 'madow_share']"), '加权奖 Tab 中已彻底移除 madow_share (马豆分红)');
assert(indexContent.includes("'share' => ['venue_share', 'county_share']"), '加权奖 Tab 仅保留纯现金均分奖励');
assert(indexContent.includes("$query->whereIn('reward_type', $cashTypes)"), '全部 Tab 默认仅查询纯现金奖励白名单');
// 验证 0 元记录过滤
assert(indexContent.includes("$q->where('amount', '>', 0)"), 'rewards 强制要求已入账记录金额大于0');
assert(indexContent.includes("->whereOr('status', -1)"), '保留未达成的拦截记录');
// 验证来源文本规范化
assert(indexContent.includes("'source_text' => $sourceText"), 'rewards 接口返回结构化 source_text');
assert(indexContent.includes('全网加权日结分红'), '日结均分奖励明确来源文本为全网加权日结分红');
console.log(' [✓ PASS] 服务端 partner/Index.php 现金类型白名单与0元过滤已就绪');
// 2. 验证前端 rewards.vue
const rewardsVuePath = path.join(rootDir, 'uniapp/pages/commission/rewards.vue');
assert(fs.existsSync(rewardsVuePath), 'rewards.vue 页面文件存在');
const rewardsContent = fs.readFileSync(rewardsVuePath, 'utf8');
assert(rewardsContent.includes('item.source_text'), 'rewards.vue 优先展示 source_text');
assert(!rewardsContent.includes("来自 {{ item.buyer_nickname || '好友' }}"), 'rewards.vue 彻底移除生硬的“来自 好友”兜底');
assert(rewardsContent.includes('state.current.source_text'), 'rewards.vue 详情弹窗优先展示 source_text');
console.log(' [✓ PASS] 前端 rewards.vue 来源展示优化与兜底修复已就绪');
// 3. 模拟逻辑单元测试
const instantTypes = ['push3', 'maker_diff', 'maker_peer', 'maker_overtaken', 'team_venue', 'team_county', 'venue_peer', 'county_peer'];
const cashTypes = [...instantTypes, 'venue_share', 'county_share'];
const mockRows = [
{ id: 1, reward_type: 'push3', amount: '398.00', madow: '0', status: 1, buyer: { nickname: '张三' } },
{ id: 2, reward_type: 'madow_share', amount: '0.00', madow: '120.00', status: 1, buyer: null }, // 每日贡献马豆
{ id: 3, reward_type: 'venue_share', amount: '50.00', madow: '0', status: 1, buyer: null }, // 酒道馆均分现金
{ id: 4, reward_type: 'voucher_release', amount: '0.00', madow: '0', status: 1, buyer: null }, // 券释放
{ id: 5, reward_type: 'push3', amount: '0.00', madow: '0', status: -1, skip_reason: 'inactive_30d', buyer: { nickname: '李四' } }, // 未自购拦截
{ id: 6, reward_type: 'push3', amount: '0.00', madow: '0', status: 1, buyer: null }, // 异常 0 元已入账记录
];
// 模拟查询逻辑
function filterRewards(tab) {
return mockRows.filter(row => {
// 1. 类型过滤
if (tab === 'all') {
if (!cashTypes.includes(row.reward_type)) return false;
} else if (tab === 'share') {
if (!['venue_share', 'county_share'].includes(row.reward_type)) return false;
} else if (tab === 'push3') {
if (row.reward_type !== 'push3') return false;
}
// 2. 金额过滤: amount > 0 或者 status === -1
const amountNum = parseFloat(row.amount);
if (!(amountNum > 0 || row.status === -1)) {
return false;
}
return true;
});
}
// 验证 全部 Tab
const allList = filterRewards('all');
assert(allList.some(r => r.id === 1), '全部 Tab 应包含正常推三返一');
assert(allList.some(r => r.id === 3), '全部 Tab 应包含酒道馆日结现金均分');
assert(allList.some(r => r.id === 5), '全部 Tab 应包含被拦截的跳过记录');
assert(!allList.some(r => r.id === 2), '全部 Tab 绝不能包含每日贡献马豆 (madow_share)');
assert(!allList.some(r => r.id === 4), '全部 Tab 绝不能包含券释放 (voucher_release)');
assert(!allList.some(r => r.id === 6), '全部 Tab 绝不能包含已入账但金额为0的废弃记录');
console.log(' [✓ PASS] 全部 Tab 纯现金过滤验证成功 (过滤掉所有 madow_share 与 0 元记录)');
// 验证 加权奖 Tab
const shareList = filterRewards('share');
assert(shareList.some(r => r.id === 3), '加权奖 Tab 包含酒道馆日结现金均分');
assert(!shareList.some(r => r.id === 2), '加权奖 Tab 彻底排除马豆分红');
console.log(' [✓ PASS] 加权奖 Tab 纯现金过滤验证成功');
console.log('\n========================================================');
console.log(' 全部修复项与业务隔离测试 100% 通过!');
console.log('========================================================');

查看文件

@@ -0,0 +1,156 @@
const fs = require('fs');
const path = require('path');
const assert = require('assert');
console.log('========================================================');
console.log(' 未买单用户显示“游客”、购买1单后显示“品酒师”专项测试');
console.log('========================================================\n');
const rootDir = path.resolve(__dirname, '../../');
// 1. 验证 IdentityService.php
const identityServicePath = path.join(rootDir, 'addons/shopro/service/partner/IdentityService.php');
assert(fs.existsSync(identityServicePath), 'IdentityService.php 文件存在');
const idServiceContent = fs.readFileSync(identityServicePath, 'utf8');
assert(idServiceContent.includes('function hasPurchased('), 'IdentityService 包含 hasPurchased 方法');
assert(idServiceContent.includes("'total_consume'"), 'hasPurchased 优先使用 total_consume 快速命中');
assert(idServiceContent.includes("'shopro_order'"), 'hasPurchased 校验有效订单');
assert(idServiceContent.includes("'apply_refund_status', '<>', 2"), 'hasPurchased 排除全退订单');
assert(idServiceContent.includes("identities[] = 'guest'"), '未买单时 identities 包含 guest');
assert(idServiceContent.includes("identities[] = 'taster'"), '已买单时 identities 包含 taster');
assert(idServiceContent.includes("$parts[] = $hasPurchased ? $tasterName : '游客'"), '无进阶身份时按买单状态显示品酒师或游客');
assert(idServiceContent.includes("'is_guest' => $isGuest"), 'IdentityService::build 返回 is_guest');
assert(idServiceContent.includes("'has_purchased' => $hasPurchased"), 'IdentityService::build 返回 has_purchased');
assert(idServiceContent.includes("'is_guest' => $full['is_guest']"), 'IdentityService::attachProfile 返回 is_guest');
console.log(' [✓ PASS] IdentityService.php 服务层买单检测与身份映射逻辑已就绪');
// 2. 验证 Agent.php 模型
const agentModelPath = path.join(rootDir, 'application/admin/model/shopro/commission/Agent.php');
assert(fs.existsSync(agentModelPath), 'Agent.php 模型文件存在');
const agentModelContent = fs.readFileSync(agentModelPath, 'utf8');
assert(agentModelContent.includes("'is_guest'"), 'Agent 模型 $append 数组包含 is_guest');
assert(agentModelContent.includes("'has_purchased'"), 'Agent 模型 $append 数组包含 has_purchased');
assert(agentModelContent.includes('function getIsGuestAttr'), 'Agent 模型包含 getIsGuestAttr 获取器');
assert(agentModelContent.includes('function getHasPurchasedAttr'), 'Agent 模型包含 getHasPurchasedAttr 获取器');
console.log(' [✓ PASS] Agent.php 模型 append 属性与获取器已就绪');
// 3. 验证 User.php 控制器 (API 与 Admin)
const apiUserCtrlPath = path.join(rootDir, 'addons/shopro/controller/user/User.php');
const apiUserContent = fs.readFileSync(apiUserCtrlPath, 'utf8');
assert(apiUserContent.includes('attachProfile($agent, null, $user)'), 'api user/profile 传递 user 对象至 attachProfile');
const adminUserCtrlPath = path.join(rootDir, 'application/admin/controller/shopro/user/User.php');
const adminUserContent = fs.readFileSync(adminUserCtrlPath, 'utf8');
assert(adminUserContent.includes('attachProfile($agent, $rate, $user)'), '后台 User::index 传递 user 对象至 attachProfile');
assert(adminUserContent.includes('attachProfile($agent, null, $user)'), '后台 User::detail 传递 user 对象至 attachProfile');
console.log(' [✓ PASS] 后端控制器已打通 user 对象与 profile 联动');
// 4. 验证 Uniapp 前端组件
const userCardPath = path.join(rootDir, 'uniapp/sheep/components/s-user-card/s-user-card.vue');
assert(fs.existsSync(userCardPath), 's-user-card.vue 组件存在');
const userCardContent = fs.readFileSync(userCardPath, 'utf8');
assert(userCardContent.includes('v-if="isGuest" class="identity-tag guest-tag">游客</view>'), 's-user-card 包含游客标签模板渲染');
assert(userCardContent.includes('const isGuest = computed('), 's-user-card 包含 isGuest 计算属性');
assert(userCardContent.includes('.guest-tag {'), 's-user-card 包含 guest-tag 视觉样式');
console.log(' [✓ PASS] s-user-card.vue 游客标签与品酒师徽章动态切换已就绪');
const commInfoPath = path.join(rootDir, 'uniapp/pages/commission/components/commission-info.vue');
const commInfoContent = fs.readFileSync(commInfoPath, 'utf8');
assert(commInfoContent.includes('userInfo.value?.identity_text'), 'commission-info.vue 优先使用 identity_text 展示游客/品酒师');
console.log(' [✓ PASS] commission-info.vue 收益中心顶部身份文案已就绪');
const teamPath = path.join(rootDir, 'uniapp/pages/commission/team.vue');
const teamContent = fs.readFileSync(teamPath, 'utf8');
assert(teamContent.includes('item?.agent?.identity_text'), 'team.vue 格式化下级身份优先使用 identity_text');
console.log(' [✓ PASS] team.vue 团队作战中心下级身份显示已就绪');
// 5. 模拟逻辑单元测试
function simulateBuild(agent, user, orders) {
const level = agent ? (agent.level || 0) : 0;
const isVenue = agent ? (agent.is_venue || 0) : 0;
const isCounty = agent ? (agent.is_county || 0) : 0;
const isPartner = agent ? (agent.is_partner || 0) : 0;
let hasPurchased = false;
if (user && parseFloat(user.total_consume || 0) > 0) {
hasPurchased = true;
} else if (orders && orders.length > 0) {
hasPurchased = orders.some(o => ['paid', 'completed'].includes(o.status) && o.apply_refund_status !== 2);
}
const isAdvanced = (level >= 2) || (isVenue === 1) || (isCounty === 1) || (isPartner === 1);
const isGuest = (level === 1 && !hasPurchased && !isAdvanced);
const identities = [];
const parts = [];
if (level === 1) {
if (hasPurchased) {
identities.push('taster');
} else if (!isAdvanced) {
identities.push('guest');
}
} else if (level >= 2) {
identities.push('maker');
parts.push('创客V' + (level - 1));
}
if (isVenue) {
identities.push('venue');
parts.push('酒道馆');
}
if (isCounty) {
identities.push('county');
parts.push('区县');
}
if (isPartner) {
identities.push('partner');
parts.push('合伙股东');
}
if (parts.length === 0 && level === 1) {
parts.push(hasPurchased ? '品酒师' : '游客');
}
return {
identities,
identity_text: parts.join(' · ') || (isGuest ? '游客' : ''),
is_guest: isGuest,
has_purchased: hasPurchased,
};
}
// 模拟场景测试
// 场景 1: 新用户注册,未买单 (0单,0消费)
const res1 = simulateBuild({ level: 1, user_id: 101 }, { id: 101, total_consume: 0 }, []);
assert.strictEqual(res1.identity_text, '游客', '场景1:新用户未买单 identity_text 应为游客');
assert.strictEqual(res1.is_guest, true, '场景1:新用户未买单 is_guest 应为 true');
assert(res1.identities.includes('guest'), '场景1:identities 包含 guest');
assert(!res1.identities.includes('taster'), '场景1:identities 不含 taster');
// 场景 2: 购买 1 单已支付 (消费 1398)
const res2 = simulateBuild({ level: 1, user_id: 102 }, { id: 102, total_consume: 1398 }, [{ status: 'paid', apply_refund_status: 0 }]);
assert.strictEqual(res2.identity_text, '品酒师', '场景2:购买1单后 identity_text 应升级为品酒师');
assert.strictEqual(res2.is_guest, false, '场景2:购买1单后 is_guest 应为 false');
assert(res2.identities.includes('taster'), '场景2:identities 包含 taster');
assert(!res2.identities.includes('guest'), '场景2:identities 不含 guest');
// 场景 3: 购买后发生全单退款完成 (apply_refund_status = 2)
const res3 = simulateBuild({ level: 1, user_id: 103 }, { id: 103, total_consume: 0 }, [{ status: 'paid', apply_refund_status: 2 }]);
assert.strictEqual(res3.identity_text, '游客', '场景3:全额退款后身份应回退为游客');
assert.strictEqual(res3.is_guest, true, '场景3:全额退款后 is_guest 应为 true');
// 场景 4: 进阶创客角色不受影响 (level = 3 创客V2,未下单)
const res4 = simulateBuild({ level: 3, user_id: 104 }, { id: 104, total_consume: 0 }, []);
assert.strictEqual(res4.identity_text, '创客V2', '场景4:进阶创客未买单仍正常展示创客V2');
assert.strictEqual(res4.is_guest, false, '场景4:进阶创客 is_guest 应为 false');
// 场景 5: 进阶酒道馆+区县角色不受影响
const res5 = simulateBuild({ level: 1, is_venue: 1, is_county: 1, user_id: 105 }, { id: 105, total_consume: 0 }, []);
assert.strictEqual(res5.identity_text, '酒道馆 · 区县', '场景5:酒道馆与区县正常展示');
assert.strictEqual(res5.is_guest, false, '场景5:复合身份 is_guest 应为 false');
console.log(' [✓ PASS] 所有业务场景逻辑模拟测试 100% 通过!\n');
console.log('========================================================');
console.log(' 所有专项检查与验证均已完美通过!');
console.log('========================================================');

查看文件

@@ -1,7 +1,7 @@
/**
* tests/partner/verify_madow_switch_spec.js
*
* 验证“我的马豆”页面马币市值指示胶囊后台开关:
* 验证“我的dmepa”页面马币市值指示胶囊后台开关:
* 1. 后端配置读取与默认值向后兼容逻辑验证
* 2. 移动端前台显隐条件判定逻辑(开启、关闭、未配置)验证
* 3. 接口数据契约与统一返回结构校验
@@ -13,7 +13,7 @@ const path = require('path');
const assert = require('assert');
console.log('====================================================');
console.log(' 开始验证:“我的马豆”马币市值指示胶囊后台显隐开关');
console.log(' 开始验证:“我的dmepa”马币市值指示胶囊后台显隐开关');
console.log('====================================================\n');
let passCount = 0;

查看文件

@@ -0,0 +1,174 @@
/**
* tests/partner/verify_remove_mabi_wording_spec.js
*
* 验证“我的dmepa”全链路移除“马币”二字:
* 1. 移动端 madow.vue 指示胶囊文案对齐为「市值 1 dmepa ≈ xx 元」
* 2. 移动端 madow.vue 抽屉弹窗默认文案与数据净化逻辑(不含“马币”)
* 3. 服务端 PartnerConfig::madowRuleTitle 与 madowRules 彻底去除“马币”并具备脱敏自愈能力
* 4. 后台 Config 控制器、后台视图 index.html、config.js 默认值与提示语彻底移除“马币”
*/
const fs = require('fs');
const path = require('path');
const assert = require('assert');
console.log('====================================================');
console.log(' 验证:“我的dmepa”全链路彻底移除「马币」二字');
console.log('====================================================\n');
let passCount = 0;
let failCount = 0;
function runTest(title, fn) {
try {
fn();
console.log(`[PASS] ${title}`);
passCount++;
} catch (err) {
console.error(`[FAIL] ${title}`);
console.error(` 错误原因: ${err.message}`);
failCount++;
}
}
const rootDir = path.resolve(__dirname, '../../');
// 1. 验证 uniapp/pages/commission/madow.vue
runTest('uniapp/pages/commission/madow.vue 胶囊文案由「马币市值」调整为「市值」', () => {
const file = path.join(rootDir, 'uniapp/pages/commission/madow.vue');
assert.ok(fs.existsSync(file), 'madow.vue 存在');
const content = fs.readFileSync(file, 'utf-8');
// 核心指示胶囊
assert.ok(/市值\s*1\s*DMEPA\s*≈/i.test(content), '胶囊必须使用「市值 1 DMEPA ≈」');
assert.ok(!content.includes('马币市值'), '胶囊中严禁出现「马币市值」');
// 顶栏红框文字去除验证
assert.ok(content.includes('生态资产'), '必须保留「生态资产」标签');
assert.ok(!content.includes('持有dmepa总量') && !content.includes('持有DMEPA总量'), '顶栏「持有DMEPA总量」文字已按需求彻底去掉');
// 默认规则弹窗标题
assert.ok(content.includes("ruleTitle: '市值与增值规则'"), '默认弹窗标题必须为「市值与增值规则」');
// 默认规则内容
assert.ok(content.includes("title: '市值动态增值'"), '默认规则条目必须为「市值动态增值」');
// 数据接收净化
assert.ok(content.includes('.replace(/马币/g, \'\')'), 'getMadow 必须对动态规则标题和条目做去除「马币」净化');
});
// 2. 验证 addons/shopro/service/partner/PartnerConfig.php
runTest('addons/shopro/service/partner/PartnerConfig.php 规则方法彻底移除「马币」并脱敏净化', () => {
const file = path.join(rootDir, 'addons/shopro/service/partner/PartnerConfig.php');
assert.ok(fs.existsSync(file), 'PartnerConfig.php 存在');
const content = fs.readFileSync(file, 'utf-8');
assert.ok(content.includes("self::get('partner_madow_rule_title', '市值与增值规则')"), '默认标题为「市值与增值规则」');
assert.ok(content.includes("return str_replace('马币', '', $title);"), '标题输出时执行 str_replace 清除「马币」');
assert.ok(content.includes("'title' => '市值动态增值'"), '默认规则条目包含「市值动态增值」');
assert.ok(content.includes("str_replace('马币', '', $rule['title'])"), '条目输出时执行 str_replace 清除「马币」');
});
// 3. 验证 application/admin/controller/shopro/Config.php
runTest('application/admin/controller/shopro/Config.php 后台自愈默认值去除「马币」', () => {
const file = path.join(rootDir, 'application/admin/controller/shopro/Config.php');
assert.ok(fs.existsSync(file), 'Config.php 存在');
const content = fs.readFileSync(file, 'utf-8');
assert.ok(content.includes("'partner_madow_rule_title' => '市值与增值规则'"), 'ruleDefaults 默认标题去除「马币」');
assert.ok(content.includes("['title' => '市值动态增值'"), 'ruleDefaults 默认条目去除「马币」');
});
// 4. 验证 application/admin/view/shopro/config/index.html
runTest('application/admin/view/shopro/config/index.html 视图提示与占位符去除「马币」', () => {
const file = path.join(rootDir, 'application/admin/view/shopro/config/index.html');
assert.ok(fs.existsSync(file), 'index.html 存在');
const content = fs.readFileSync(file, 'utf-8');
assert.ok(content.includes('市值 1 dmepa ≈ xxxx 元'), 'tip 提示更新为「市值 1 dmepa ≈ xxxx 元」');
assert.ok(content.includes('placeholder="市值与增值规则"'), '输入框占位符更新为「市值与增值规则」');
});
// 5. 验证 public/assets/js/backend/shopro/config.js
runTest('public/assets/js/backend/shopro/config.js 默认值去除「马币」', () => {
const file = path.join(rootDir, 'public/assets/js/backend/shopro/config.js');
assert.ok(fs.existsSync(file), 'config.js 存在');
const content = fs.readFileSync(file, 'utf-8');
assert.ok(content.includes("form.model.partner_madow_rule_title = '市值与增值规则'"), 'config.js 默认标题更新为「市值与增值规则」');
assert.ok(content.includes("{ title: '市值动态增值'"), 'config.js 默认规则条目更新为「市值动态增值」');
});
// 6. API 模拟测试:涵盖正常场景、业务失败场景、参数错误场景
runTest('API 模拟测试:正常请求 partner/madow 接口数据结构与文案校验', () => {
const mockSuccessResponse = {
code: 1,
msg: 'ok',
data: {
madow: '3993.0000',
madow_rate: '0.00000858',
madow_value: '0.03',
show_madow_rate: 1,
rule_title: '市值与增值规则',
rules: [
{ title: 'DMEPA获取与核算', desc: '平台合伙人与创客达成考核或完成订单后,系统将于次日 00:30 起统一参与每日DMEPA加权核算并自动分发。' },
{ title: '市值动态增值', desc: '市值随全平台订单增加而上浮增值。全网完成订单总量越多,DMEPA单价所对应的市值越高,共享生态红利。' },
{ title: '价值说明与权益', desc: '市值仅作为平台数字生态资产价值参考展示,不可直接兑换现金提现。具体权益按平台股东与合伙人章程执行。' }
],
list: []
}
};
assert.strictEqual(mockSuccessResponse.code, 1);
assert.strictEqual(mockSuccessResponse.data.rule_title.includes('马币'), false, '返回的 rule_title 严禁包含马币');
assert.strictEqual(mockSuccessResponse.data.rules[1].title.includes('马币'), false, '返回的 rules[1].title 严禁包含马币');
assert.strictEqual(mockSuccessResponse.data.rules[1].desc.includes('马币'), false, '返回的 rules[1].desc 严禁包含马币');
});
runTest('API 模拟测试:当服务端数据包含历史「马币」文案时,前端净化脱敏逻辑生效', () => {
const dirtyData = {
rule_title: '马币市值与增值规则',
rules: [
{ title: '马币市值动态增值', desc: '马币市值随全平台订单增加而上浮增值。' }
]
};
const sanitizedTitle = String(dirtyData.rule_title).replace(/马币/g, '');
const sanitizedRules = dirtyData.rules.map(item => ({
...item,
title: String(item.title || '').replace(/马币/g, ''),
desc: String(item.desc || '').replace(/马币/g, '')
}));
assert.strictEqual(sanitizedTitle, '市值与增值规则');
assert.strictEqual(sanitizedRules[0].title, '市值动态增值');
assert.strictEqual(sanitizedRules[0].desc, '市值随全平台订单增加而上浮增值。');
});
runTest('API 模拟测试:业务拒绝场景(未登录/无权限)统一响应封装', () => {
const mockAuthFailResponse = {
code: 0,
msg: '请先登录后查看DMEPA资产',
data: null
};
assert.strictEqual(mockAuthFailResponse.code, 0);
assert.strictEqual(typeof mockAuthFailResponse.msg, 'string');
});
runTest('API 模拟测试:参数错误场景(page为非法字符串)边界处理', () => {
const sanitizePage = (inputPage) => {
const p = parseInt(inputPage, 10);
return isNaN(p) || p < 1 ? 1 : p;
};
assert.strictEqual(sanitizePage('invalid'), 1);
assert.strictEqual(sanitizePage('-5'), 1);
assert.strictEqual(sanitizePage('3'), 3);
});
console.log('\n====================================================');
console.log(`测试完成: 成功 ${passCount} 项, 失败 ${failCount} 项`);
console.log('====================================================');
if (failCount > 0) {
process.exit(1);
}

查看文件

@@ -105,7 +105,9 @@ assert(fs.existsSync(moneyVuePath), 'money.vue 文件存在');
if (fs.existsSync(moneyVuePath)) {
const content = fs.readFileSync(moneyVuePath, 'utf-8');
assert(content.includes("name: '收益分成'") && content.includes("value: 'reward'"), 'money.vue 的 tabMaps 增加「收益分成」筛选');
assert(content.includes('收益分成全额入账'), '动态指示胶囊说明收益分成全额入账可直接抵扣');
assert(content.includes('tag-badge') && content.includes('现金资产'), '保留现金资产专属标签');
assert(!content.includes('账户钱包余额'), '已成功移除「账户钱包余额」文案');
assert(!content.includes('线上充值与收益分成全额入账'), '已成功移除「线上充值与收益分成全额入账」胶囊文案');
}
// ----------------------------------------------------