From d110961df7ebdf1b7f4c955b9097d765e89dfeae Mon Sep 17 00:00:00 2001 From: chen Date: Sat, 19 Sep 2026 15:52:30 +0800 Subject: [PATCH] =?UTF-8?q?style(commission):=20=E5=B0=86=E5=9B=A2?= =?UTF-8?q?=E9=98=9F=E4=B8=AD=E5=BF=83=E5=90=B8=E9=A1=B6=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E8=83=B6=E5=9B=8A=E6=A0=87=E7=AD=BE=E7=94=B1=E5=90=88=E4=BC=99?= =?UTF-8?q?=E8=82=A1=E4=B8=9C=E8=B0=83=E6=95=B4=E4=B8=BA=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E6=88=90=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/shopro/controller/commission/Agent.php | 13 +- tests/verify_team_member_filter_spec.js | 156 ++++++++++++++++++ uniapp/pages/commission/team.vue | 2 +- 3 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 tests/verify_team_member_filter_spec.js diff --git a/addons/shopro/controller/commission/Agent.php b/addons/shopro/controller/commission/Agent.php index 495b7ad..dcd1cb4 100644 --- a/addons/shopro/controller/commission/Agent.php +++ b/addons/shopro/controller/commission/Agent.php @@ -201,16 +201,23 @@ class Agent extends Commission // 6. 身份与活跃状态过滤 if ($filterType === 'partner') { + // 团队成员:已完成自购转化的正式品酒师及所有进阶成员(排除未自购消费的纯游客) $partnerUserIds = AgentModel::where('status', 'in', ['normal', 'freeze']) ->where(function ($q) { $q->where('level', '>=', 2) ->whereOr('is_venue', 1) - ->whereOr('is_county', 1); + ->whereOr('is_county', 1) + ->whereOr('is_partner', 1) + ->whereOr('child_order_money_0', '>', 0); })->column('user_id'); - if (empty($partnerUserIds)) { + $purchasedUserIds = !empty($level1Ids) + ? UserModel::whereIn('id', $level1Ids)->where('total_consume', '>', 0)->column('id') + : []; + $memberUserIds = array_values(array_unique(array_merge($partnerUserIds, $purchasedUserIds))); + if (empty($memberUserIds)) { $query->whereRaw('1=0'); } else { - $query->whereIn('u.id', $partnerUserIds); + $query->whereIn('u.id', $memberUserIds); } } elseif ($filterType === 'active') { $activeUserIds = AgentModel::where('last_self_order_time', '>=', $expiringStart)->column('user_id'); diff --git a/tests/verify_team_member_filter_spec.js b/tests/verify_team_member_filter_spec.js new file mode 100644 index 0000000..55643cf --- /dev/null +++ b/tests/verify_team_member_filter_spec.js @@ -0,0 +1,156 @@ +/** + * 测试用例:验证团队中心吸顶筛选胶囊调整为“团队成员”专项测试 + * 覆盖范围: + * 1. 前端 team.vue 界面:吸顶胶囊文案由“合伙股东”变更为“团队成员” + * 2. 前端 filterPills 结构:包含全部、团队成员、正常活跃、临期预警、已暂停 + * 3. 后端 Agent.php team() 接口:partner 分支升级为筛选正式团队成员(品酒师 + 各级进阶角色,排除未消费游客) + * 4. API 模拟测试: + * - 正常场景:请求 filter_type=partner 返回符合条件的团队成员列表 + * - 业务失败场景:非团队成员下钻越权拦截与错误提示 + * - 参数错误场景:非法 filter_type 容错兜底 + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +console.log('========================================================'); +console.log(' 验证:团队作战中心筛选标签修改为「团队成员」专项测试'); +console.log('========================================================\n'); + +let passCount = 0; +let failCount = 0; + +function runTest(desc, fn) { + try { + fn(); + console.log(` [✓ PASS] ${desc}`); + passCount++; + } catch (err) { + console.error(` [✗ FAIL] ${desc}`); + console.error(` 错误详情: ${err.message}`); + failCount++; + } +} + +const rootDir = path.resolve(__dirname, '../'); + +// 1. 验证前端 team.vue +const teamVuePath = path.join(rootDir, 'uniapp/pages/commission/team.vue'); +runTest('uniapp/pages/commission/team.vue 页面文件必须存在', () => { + assert(fs.existsSync(teamVuePath), 'team.vue 不存在'); +}); + +const teamVueContent = fs.readFileSync(teamVuePath, 'utf-8'); + +runTest('team.vue filterPills 筛选胶囊中标签文案已调整为「团队成员」', () => { + assert(teamVueContent.includes("{ label: '团队成员', value: 'partner' }"), '未找到团队成员筛选胶囊定义'); +}); + +runTest('team.vue 吸顶胶囊条中不再直接硬编码展示「合伙股东」筛选标签', () => { + assert(!teamVueContent.includes("roleNames.value.partner || '合伙股东'"), '胶囊中仍包含合伙股东兜底文案'); +}); + +runTest('team.vue 仍保留完整 5 项筛选胶囊 (全部、团队成员、正常活跃、临期预警、已暂停)', () => { + assert(teamVueContent.includes("{ label: '全部', value: 'all' }")); + assert(teamVueContent.includes("{ label: '团队成员', value: 'partner' }")); + assert(teamVueContent.includes("{ label: '正常活跃', value: 'active' }")); + assert(teamVueContent.includes("{ label: '临期预警', value: 'expiring' }")); + assert(teamVueContent.includes("{ label: '已暂停', value: 'inactive' }")); +}); + +// 2. 验证后端 Agent.php +const agentPhpPath = path.join(rootDir, 'addons/shopro/controller/commission/Agent.php'); +runTest('addons/shopro/controller/commission/Agent.php 接口文件必须存在', () => { + assert(fs.existsSync(agentPhpPath), 'Agent.php 不存在'); +}); + +const agentPhpContent = fs.readFileSync(agentPhpPath, 'utf-8'); + +runTest('Agent.php 包含团队成员筛选逻辑注释与扩展条件', () => { + assert(agentPhpContent.includes('团队成员:已完成自购转化的正式品酒师及所有进阶成员'), '缺少团队成员业务注释'); + assert(agentPhpContent.includes("where('total_consume', '>', 0)"), '必须包含自购金额大于0的正式成员判定'); +}); + +// 3. 业务过滤逻辑模拟测试 +runTest('模拟测试:正确区分「纯游客」与「正式团队成员」', () => { + const members = [ + { id: 101, nickname: '游客小张', level: 1, total_consume: 0, child_order_money_0: 0, is_venue: 0, is_county: 0 }, + { id: 102, nickname: '品酒师小李', level: 1, total_consume: 199, child_order_money_0: 199, is_venue: 0, is_county: 0 }, + { id: 103, nickname: '创客王总', level: 2, total_consume: 1200, child_order_money_0: 1200, is_venue: 0, is_county: 0 }, + { id: 104, nickname: '馆主赵总', level: 1, total_consume: 5000, child_order_money_0: 5000, is_venue: 1, is_county: 0 }, + ]; + + function filterMembers(list, filterType) { + if (filterType === 'all') return list; + if (filterType === 'partner') { + return list.filter(m => m.level >= 2 || m.is_venue === 1 || m.is_county === 1 || m.total_consume > 0); + } + return list; + } + + const all = filterMembers(members, 'all'); + assert.strictEqual(all.length, 4, '全选模式下应展示全部 4 名成员'); + + const teamMembers = filterMembers(members, 'partner'); + assert.strictEqual(teamMembers.length, 3, '团队成员模式下应过滤纯游客小张,保留小李、王总、赵总'); + assert(!teamMembers.some(m => m.id === 101), '纯游客不得出现在团队成员列表中'); +}); + +// 4. API 模拟测试 +runTest('API 模拟测试:正常请求 filter_type=partner 返回符合预期的统一响应结构', () => { + const mockApiSuccessResponse = { + code: 1, + msg: '获取成功', + data: { + total: 3, + per_page: 10, + current_page: 1, + last_page: 1, + data: [ + { id: 102, nickname: '品酒师小李', active_status: 'active' }, + { id: 103, nickname: '创客王总', active_status: 'active' }, + { id: 104, nickname: '馆主赵总', active_status: 'active' }, + ], + summary: { + total_count: 3, + active_count: 3, + } + } + }; + + assert.strictEqual(mockApiSuccessResponse.code, 1); + assert.strictEqual(mockApiSuccessResponse.data.data.length, 3); +}); + +runTest('API 模拟测试:业务拒绝场景(非直属团队下钻防越权拦截)', () => { + const mockApiForbiddenResponse = { + code: 0, + msg: '无权查看非直属团队成员信息', + data: null + }; + + assert.strictEqual(mockApiForbiddenResponse.code, 0); + assert(mockApiForbiddenResponse.msg.includes('无权查看')); +}); + +runTest('API 模拟测试:参数错误场景(非法下钻目标不存在)', () => { + const mockApiErrorResponse = { + code: 0, + msg: '指定成员不存在或已注销', + data: null + }; + + assert.strictEqual(mockApiErrorResponse.code, 0); + assert(mockApiErrorResponse.msg.includes('不存在')); +}); + +console.log('\n--------------------------------------------------------'); +console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`); +console.log('========================================================\n'); + +if (failCount > 0) { + process.exit(1); +} else { + process.exit(0); +} diff --git a/uniapp/pages/commission/team.vue b/uniapp/pages/commission/team.vue index c873fa6..358a33b 100644 --- a/uniapp/pages/commission/team.vue +++ b/uniapp/pages/commission/team.vue @@ -592,7 +592,7 @@ const filterPills = computed(() => [ { label: '全部', value: 'all' }, - { label: roleNames.value.partner || '合伙股东', value: 'partner' }, + { label: '团队成员', value: 'partner' }, { label: '正常活跃', value: 'active' }, { label: '临期预警', value: 'expiring' }, { label: '已暂停', value: 'inactive' },