feat: 支持角色身份自定义名称全链路配置与动态展示
这个提交包含在:
@@ -20,7 +20,7 @@ class PartnerAccept extends Command
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('shopro:partner:accept')
|
||||
->setDescription('跑通合伙股东验收剧本:V5极差/平级、馆县拆分、30日暂停、退款、日结');
|
||||
->setDescription('跑通收益中心全角色验收剧本:V5极差/平级、馆县拆分、30日暂停、退款、日结');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
|
||||
@@ -13,7 +13,7 @@ class PartnerInstall extends Command
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('shopro:partner:install')
|
||||
->setDescription('导入合伙股东表结构/配置,并升级后台菜单');
|
||||
->setDescription('导入收益中心表结构/配置,并升级后台菜单');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
|
||||
@@ -15,7 +15,7 @@ class PartnerSettle extends Command
|
||||
$this->setName('shopro:partner:settle')
|
||||
->addOption('date', 'd', Option::VALUE_OPTIONAL, '结算日期 Y-m-d,默认昨天')
|
||||
->addOption('rerun', 'r', Option::VALUE_NONE, '删除当天日结后重算')
|
||||
->setDescription('合伙股东日结(马豆/馆县均分/兑换券释放)');
|
||||
->setDescription('收益中心日结(马豆/馆县均分/兑换券释放)');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
|
||||
@@ -72,7 +72,7 @@ class Partner
|
||||
$service->refundPartial($order, $item);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
format_log_error($e, 'Partner.orderItemRefundAfter', '合伙股东退款处理异常: ' . $e->getMessage());
|
||||
format_log_error($e, 'Partner.orderItemRefundAfter', '收益中心退款处理异常: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ class IdentityService
|
||||
'identities' => [],
|
||||
'identity_text' => '',
|
||||
'is_partner' => false,
|
||||
'is_advanced' => false,
|
||||
'maker_level_name' => '',
|
||||
'role_names' => PartnerConfig::roleNames(),
|
||||
'custom_role_names' => PartnerConfig::roleNames(),
|
||||
];
|
||||
if (!$agent) {
|
||||
return $empty;
|
||||
@@ -25,6 +28,13 @@ class IdentityService
|
||||
$isCounty = intval(is_array($agent) ? ($agent['is_county'] ?? 0) : ($agent->is_county ?? 0));
|
||||
$isPartner = intval(is_array($agent) ? ($agent['is_partner'] ?? 0) : ($agent->is_partner ?? 0));
|
||||
|
||||
$roleNames = PartnerConfig::roleNames();
|
||||
$tasterName = $roleNames['taster'];
|
||||
$makerBaseName = $roleNames['maker'];
|
||||
$venueName = $roleNames['venue'];
|
||||
$countyName = $roleNames['county'];
|
||||
$partnerName = $roleNames['partner'];
|
||||
|
||||
$identities = [];
|
||||
$parts = [];
|
||||
$makerLevelName = '';
|
||||
@@ -33,25 +43,25 @@ class IdentityService
|
||||
$identities[] = 'taster';
|
||||
} elseif ($level >= 2) {
|
||||
$identities[] = 'maker';
|
||||
$makerLevelName = '创客V' . ($level - 1);
|
||||
$makerLevelName = $makerBaseName . 'V' . ($level - 1);
|
||||
$parts[] = $makerLevelName;
|
||||
}
|
||||
if ($isVenue) {
|
||||
$identities[] = 'venue';
|
||||
$parts[] = '酒道馆';
|
||||
$parts[] = $venueName;
|
||||
}
|
||||
if ($isCounty) {
|
||||
$identities[] = 'county';
|
||||
$parts[] = '区县代理';
|
||||
$parts[] = $countyName;
|
||||
}
|
||||
if ($isPartner) {
|
||||
$identities[] = 'partner';
|
||||
$parts[] = '合伙股东';
|
||||
$parts[] = $partnerName;
|
||||
}
|
||||
|
||||
// 若无进阶角色身份且等级为1,则身份为基础品酒师
|
||||
// 若无进阶角色身份且等级为1,则身份为基础品酒师/自定义基础名称
|
||||
if (empty($parts) && $level == 1) {
|
||||
$parts[] = '品酒师';
|
||||
$parts[] = $tasterName;
|
||||
}
|
||||
|
||||
$isAdvanced = ($level >= 2) || ($isVenue == 1) || ($isCounty == 1) || ($isPartner == 1);
|
||||
@@ -62,6 +72,8 @@ class IdentityService
|
||||
'is_partner' => (bool) $isPartner,
|
||||
'is_advanced' => $isAdvanced,
|
||||
'maker_level_name' => $makerLevelName,
|
||||
'role_names' => $roleNames,
|
||||
'custom_role_names' => $roleNames,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -121,11 +133,15 @@ class IdentityService
|
||||
{
|
||||
if (!$agent) {
|
||||
$baseRate = PartnerConfig::get('partner_madow_base_rate', '0.00000075');
|
||||
$roleNames = PartnerConfig::roleNames();
|
||||
return [
|
||||
'identity_text' => '',
|
||||
'identities' => [],
|
||||
'is_partner' => false,
|
||||
'is_advanced' => false,
|
||||
'maker_level_name' => '',
|
||||
'role_names' => $roleNames,
|
||||
'custom_role_names' => $roleNames,
|
||||
'madow' => '0',
|
||||
'madow_rate' => self::formatRate($baseRate),
|
||||
'madow_value' => '0.00',
|
||||
@@ -136,12 +152,15 @@ class IdentityService
|
||||
];
|
||||
}
|
||||
$full = self::attachAgent($agent, $rate);
|
||||
$roleNames = $full['role_names'] ?? PartnerConfig::roleNames();
|
||||
return [
|
||||
'identity_text' => $full['identity_text'],
|
||||
'identities' => $full['identities'],
|
||||
'is_partner' => $full['is_partner'],
|
||||
'is_advanced' => $full['is_advanced'] ?? false,
|
||||
'maker_level_name' => $full['maker_level_name'],
|
||||
'role_names' => $roleNames,
|
||||
'custom_role_names' => $roleNames,
|
||||
'madow' => $full['madow'],
|
||||
'madow_rate' => $full['madow_rate'],
|
||||
'madow_value' => $full['madow_value'],
|
||||
|
||||
@@ -68,6 +68,17 @@ class PartnerConfig
|
||||
return $map;
|
||||
}
|
||||
|
||||
public static function roleNames()
|
||||
{
|
||||
return [
|
||||
'taster' => trim((string) self::get('partner_role_name_taster', '品酒师')) ?: '品酒师',
|
||||
'maker' => trim((string) self::get('partner_role_name_maker', '创客')) ?: '创客',
|
||||
'venue' => trim((string) self::get('partner_role_name_venue', '酒道馆')) ?: '酒道馆',
|
||||
'county' => trim((string) self::get('partner_role_name_county', '区县代理')) ?: '区县代理',
|
||||
'partner' => trim((string) self::get('partner_role_name_partner', '合伙股东')) ?: '合伙股东',
|
||||
];
|
||||
}
|
||||
|
||||
public static function rewardTypeText($type)
|
||||
{
|
||||
$map = self::rewardTypeMap();
|
||||
@@ -76,17 +87,22 @@ class PartnerConfig
|
||||
|
||||
public static function rewardTypeMap()
|
||||
{
|
||||
$roles = self::roleNames();
|
||||
$maker = $roles['maker'];
|
||||
$venue = $roles['venue'];
|
||||
$county = $roles['county'];
|
||||
|
||||
return [
|
||||
'push3' => '推荐开拓奖',
|
||||
'maker_diff' => '创客进阶奖',
|
||||
'maker_peer' => '创客协同奖',
|
||||
'maker_overtaken' => '创客卓越奖',
|
||||
'team_venue' => '酒道馆运营奖',
|
||||
'team_county' => '区县统筹奖',
|
||||
'venue_peer' => '酒道馆协同奖',
|
||||
'county_peer' => '区县协同奖',
|
||||
'venue_share' => '酒道馆加权奖励',
|
||||
'county_share' => '区县加权奖励',
|
||||
'maker_diff' => $maker . '进阶奖',
|
||||
'maker_peer' => $maker . '协同奖',
|
||||
'maker_overtaken' => $maker . '卓越奖',
|
||||
'team_venue' => $venue . '运营奖',
|
||||
'team_county' => $county . '统筹奖',
|
||||
'venue_peer' => $venue . '协同奖',
|
||||
'county_peer' => $county . '协同奖',
|
||||
'venue_share' => $venue . '加权奖励',
|
||||
'county_share' => $county . '加权奖励',
|
||||
'madow_share' => '每日贡献马豆',
|
||||
'voucher_grant' => '消费补贴发放',
|
||||
'voucher_release' => '消费补贴到账',
|
||||
@@ -97,13 +113,18 @@ class PartnerConfig
|
||||
|
||||
public static function skipReasonText($code, $forUser = false)
|
||||
{
|
||||
$roles = self::roleNames();
|
||||
$maker = $roles['maker'];
|
||||
$venue = $roles['venue'];
|
||||
$county = $roles['county'];
|
||||
|
||||
$inner = [
|
||||
'no_parent' => '无推荐上级',
|
||||
'self_buy' => '本人自购',
|
||||
'inactive_30d' => '受益人 30 日无自购',
|
||||
'not_maker' => '不是创客身份',
|
||||
'not_venue' => '不是酒道馆身份',
|
||||
'not_county' => '不是区县代理身份',
|
||||
'not_maker' => '不是' . $maker . '身份',
|
||||
'not_venue' => '不是' . $venue . '身份',
|
||||
'not_county' => '不是' . $county . '身份',
|
||||
'cap_not_higher' => '等级未超过基数水位',
|
||||
'already_settled' => '幂等跳过',
|
||||
'partner_goods_off' => '商品未参与',
|
||||
|
||||
@@ -199,6 +199,8 @@ class Config extends Common
|
||||
'partner_maker_overtaken_rate', 'partner_team_total', 'partner_venue_split',
|
||||
'partner_county_full', 'partner_venue_share_per_order', 'partner_county_share_per_order',
|
||||
'partner_venue_peer_rate', 'partner_county_peer_rate',
|
||||
'partner_role_name_taster', 'partner_role_name_maker', 'partner_role_name_venue',
|
||||
'partner_role_name_county', 'partner_role_name_partner',
|
||||
];
|
||||
if ('GET' === $this->request->method()) {
|
||||
$all = ShoproConfig::getConfigs('shop.commission', false) ?: [];
|
||||
@@ -228,6 +230,11 @@ class Config extends Common
|
||||
'partner_county_share_per_order' => 50,
|
||||
'partner_venue_peer_rate' => 0.10,
|
||||
'partner_county_peer_rate' => 0.10,
|
||||
'partner_role_name_taster' => '品酒师',
|
||||
'partner_role_name_maker' => '创客',
|
||||
'partner_role_name_venue' => '酒道馆',
|
||||
'partner_role_name_county' => '区县代理',
|
||||
'partner_role_name_partner' => '合伙股东',
|
||||
];
|
||||
foreach ($defaults as $key => $value) {
|
||||
if ($configs[$key] === null || $configs[$key] === '') {
|
||||
@@ -243,6 +250,11 @@ class Config extends Common
|
||||
$save[$key] = $params[$key];
|
||||
}
|
||||
}
|
||||
foreach (['partner_role_name_taster', 'partner_role_name_maker', 'partner_role_name_venue', 'partner_role_name_county', 'partner_role_name_partner'] as $roleKey) {
|
||||
if (isset($save[$roleKey])) {
|
||||
$save[$roleKey] = trim((string) $save[$roleKey]);
|
||||
}
|
||||
}
|
||||
if (isset($save['partner_active_days']) && intval($save['partner_active_days']) < 1) {
|
||||
$this->error('领取窗口天数须 ≥ 1');
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ class Dashboard extends Common
|
||||
'available_voucher' => number_format($availableVoucher, 2, '.', ''),
|
||||
];
|
||||
|
||||
// 7. 合伙股东网络与全角色身份矩阵 (收益按角色专属类型归集,杜绝重复统计)
|
||||
// 7. 收益中心网络与全角色身份矩阵 (收益按角色专属类型归集,杜绝重复统计)
|
||||
$totalRegisteredUsers = (int) User::count();
|
||||
$totalAgents = (int) Agent::count();
|
||||
$normalUserCount = max(0, $totalRegisteredUsers - $totalAgents);
|
||||
@@ -194,7 +194,7 @@ class Dashboard extends Common
|
||||
$activeUserIds = $activeService->activeUserIds($activeWindowStart, $now);
|
||||
$activeUserMap = array_flip($activeUserIds);
|
||||
|
||||
// 全量合伙股东基础信息
|
||||
// 全量成员档案基础信息
|
||||
$allAgents = Agent::field('user_id, level, is_venue, is_county, madow, last_self_order_time')->select();
|
||||
|
||||
// 收益明细按 (user_id + reward_type) 聚合,精确归集各角色产出收益
|
||||
@@ -284,18 +284,25 @@ class Dashboard extends Common
|
||||
];
|
||||
};
|
||||
|
||||
$roleNames = PartnerConfig::roleNames();
|
||||
$tasterTitle = $roleNames['taster'];
|
||||
$makerTitle = $roleNames['maker'];
|
||||
$venueTitle = $roleNames['venue'];
|
||||
$countyTitle = $roleNames['county'];
|
||||
$partnerTitle = $roleNames['partner'];
|
||||
|
||||
// 统计各角色身份详细指标
|
||||
$roleList = [
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 1; }, 'taster', '品酒师', '基础身份', '人', '品', '#806af6', 1, '', $roleRewardTypeFilter['taster']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 2; }, 'maker_v1', '创客V1', '创客梯队', '人', 'V1', '#13c2c2', 2, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 3; }, 'maker_v2', '创客V2', '创客梯队', '人', 'V2', '#13c2c2', 3, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 4; }, 'maker_v3', '创客V3', '创客梯队', '人', 'V3', '#13c2c2', 4, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 5; }, 'maker_v4', '创客V4', '创客梯队', '人', 'V4', '#13c2c2', 5, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 6; }, 'maker_v5', '创客V5', '创客梯队', '人', 'V5', '#13c2c2', 6, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 7; }, 'maker_v6', '创客V6', '创客梯队', '人', 'V6', '#13c2c2', 7, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level >= 2; }, 'maker_total', '创客队伍小计', '创客梯队', '人', '创', '#13c2c2', 'maker', '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->is_venue == 1; }, 'venue', '实体酒道馆', '渠道授权', '家', '馆', '#fa8c16', '', 'venue', $roleRewardTypeFilter['venue']),
|
||||
$calcRoleStats(function($a) { return (int)$a->is_county == 1; }, 'county', '区县代理', '渠道授权', '个', '县', '#722ed1', '', 'county', $roleRewardTypeFilter['county']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 1; }, 'taster', $tasterTitle, '基础身份', '人', mb_substr($tasterTitle, 0, 1), '#806af6', 1, '', $roleRewardTypeFilter['taster']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 2; }, 'maker_v1', $makerTitle . 'V1', $makerTitle . '梯队', '人', 'V1', '#13c2c2', 2, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 3; }, 'maker_v2', $makerTitle . 'V2', $makerTitle . '梯队', '人', 'V2', '#13c2c2', 3, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 4; }, 'maker_v3', $makerTitle . 'V3', $makerTitle . '梯队', '人', 'V3', '#13c2c2', 4, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 5; }, 'maker_v4', $makerTitle . 'V4', $makerTitle . '梯队', '人', 'V4', '#13c2c2', 5, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 6; }, 'maker_v5', $makerTitle . 'V5', $makerTitle . '梯队', '人', 'V5', '#13c2c2', 6, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level == 7; }, 'maker_v6', $makerTitle . 'V6', $makerTitle . '梯队', '人', 'V6', '#13c2c2', 7, '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->level >= 2; }, 'maker_total', $makerTitle . '队伍小计', $makerTitle . '梯队', '人', mb_substr($makerTitle, 0, 1), '#13c2c2', 'maker', '', $roleRewardTypeFilter['maker']),
|
||||
$calcRoleStats(function($a) { return (int)$a->is_venue == 1; }, 'venue', $venueTitle, '渠道授权', '家', mb_substr($venueTitle, 0, 1), '#fa8c16', '', 'venue', $roleRewardTypeFilter['venue']),
|
||||
$calcRoleStats(function($a) { return (int)$a->is_county == 1; }, 'county', $countyTitle, '渠道授权', '个', mb_substr($countyTitle, 0, 1), '#722ed1', '', 'county', $roleRewardTypeFilter['county']),
|
||||
];
|
||||
|
||||
// 增加普通注册会员行 (明确标明无需自购考核)
|
||||
@@ -365,6 +372,8 @@ class Dashboard extends Common
|
||||
'active_rate' => $activeRate,
|
||||
'role_list' => $roleList,
|
||||
'role_stats' => $roleStats,
|
||||
'role_names' => $roleNames,
|
||||
'custom_role_names' => $roleNames,
|
||||
];
|
||||
|
||||
// 8. CFO 视角:平台资金池结余、刚性法币流动负债与提现风控
|
||||
@@ -408,7 +417,7 @@ class Dashboard extends Common
|
||||
'pending_voucher_quota' => number_format($pendingVoucher, 2, '.', ''),
|
||||
];
|
||||
|
||||
// 9. COO 视角:转化复购漏斗、合伙股东自购续期雷达、供应链库存与实时待办看板
|
||||
// 9. COO 视角:转化复购漏斗、进阶成员自购续期雷达、供应链库存与实时待办看板
|
||||
$paidOrderUsers = Order::whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
|
||||
->group('user_id')
|
||||
->field('user_id, count(*) as buy_count')
|
||||
@@ -428,7 +437,7 @@ class Dashboard extends Common
|
||||
$repeatPurchaseRate = $paidUsersCount > 0 ? round(($repeatBuyCount / $paidUsersCount) * 100, 1) : 0.0;
|
||||
$arpu = $paidUsersCount > 0 ? round($netGmv / $paidUsersCount, 2) : 0.0;
|
||||
|
||||
// 9.2 合伙股东 30 天自购续期雷达 (仅统计具备合伙股东身份的会员,避免恐慌误判)
|
||||
// 9.2 进阶成员 30 天自购续期雷达 (仅统计具备进阶角色的会员,避免恐慌误判)
|
||||
$activePartnersCount = 0;
|
||||
$warningPartnersCount = 0;
|
||||
$pendingRenewCount = 0;
|
||||
@@ -506,7 +515,7 @@ class Dashboard extends Common
|
||||
}
|
||||
}
|
||||
|
||||
// 股东专属自购达标率(分子为在保合格股东,分母为合伙股东总人数)
|
||||
// 进阶成员自购达标率(分子为在保合格进阶成员,分母为进阶角色总人数)
|
||||
$partnerActiveRate = $partnerShareholderCount > 0
|
||||
? round((($activePartnersCount + $warningPartnersCount) / $partnerShareholderCount) * 100, 1)
|
||||
: 0.0;
|
||||
@@ -634,6 +643,8 @@ class Dashboard extends Common
|
||||
'partner_network' => $partnerNetwork,
|
||||
'finance_overview' => $financeOverview,
|
||||
'operation_overview' => $operationOverview,
|
||||
'custom_role_names' => $roleNames,
|
||||
'role_names' => $roleNames,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -887,7 +898,7 @@ class Dashboard extends Common
|
||||
$searchHistory = new SearchHistory();
|
||||
$hotSearch = $searchHistory->hotSearch();
|
||||
|
||||
// 3. 合伙股东收益排行 TOP 5
|
||||
// 3. 收益中心收益排行 TOP 5
|
||||
$partnerRanking = [];
|
||||
$rankingRows = Reward::field('user_id, sum(amount) as total_income, count(*) as reward_count')
|
||||
->where('status', 1)
|
||||
|
||||
@@ -61,14 +61,16 @@ class Agent extends Common
|
||||
|
||||
$identityInfo = \addons\shopro\service\partner\IdentityService::build($detail);
|
||||
$isPartner = !empty($identityInfo['is_partner']);
|
||||
$isAdvanced = !empty($identityInfo['is_advanced']);
|
||||
$detail->is_partner = $isPartner ? 1 : 0;
|
||||
$detail->is_advanced = $isAdvanced ? 1 : 0;
|
||||
|
||||
$detail->next_push3_amount = number_format((float) $next, 2, '.', '');
|
||||
$detail->next_push3_text = '第 ' . ($seq + 1) . ' 单将返 ' . $detail->next_push3_amount;
|
||||
$detail->last_self_order_text = $last ? date('Y-m-d H:i:s', $last) : '从未下单';
|
||||
|
||||
// 业务核心规范:合伙股东仅是会员专属进阶身份,普通会员与品酒师免自购考核
|
||||
if (!$isPartner) {
|
||||
// 业务核心规范:进阶角色(创客V1~V6、酒道馆、区县代理、合伙股东)需自购考核;基础会员及品酒师免自购考核
|
||||
if (!$isAdvanced) {
|
||||
$detail->active_status_text = '免自购考核 (基础会员/品酒师)';
|
||||
} else {
|
||||
$detail->active_status_text = !empty($detail->active_30d)
|
||||
@@ -98,6 +100,8 @@ class Agent extends Common
|
||||
$detail->partner_mode = \addons\shopro\service\partner\PartnerConfig::isPartnerMode() ? 1 : 0;
|
||||
$rate = (new \addons\shopro\service\partner\RateService())->currentRate();
|
||||
$detail->madow_value = \addons\shopro\service\partner\IdentityService::formatMoney((float) ($detail->madow ?? 0) * (float) $rate);
|
||||
$detail->custom_role_names = \addons\shopro\service\partner\PartnerConfig::roleNames();
|
||||
$detail->role_names = $detail->custom_role_names;
|
||||
|
||||
$this->success('分销商详情', null, $detail);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class Reward extends Common
|
||||
'buyer_nickname' => '买家',
|
||||
'order_id' => '订单',
|
||||
'reward_type_text' => '奖励类型',
|
||||
'role_text' => '合伙身份',
|
||||
'role_text' => '受益身份',
|
||||
'amount' => '结算金额',
|
||||
'madow' => '马豆',
|
||||
'base_amount' => '基数',
|
||||
@@ -61,7 +61,7 @@ class Reward extends Common
|
||||
}
|
||||
$export = new \addons\shopro\library\Export();
|
||||
$params = [
|
||||
'file_name' => '合伙人服务收益结算台账',
|
||||
'file_name' => '收益中心结算台账',
|
||||
'cell_titles' => $cellTitles,
|
||||
'total' => $total,
|
||||
'is_sub_cell' => false,
|
||||
|
||||
@@ -23,7 +23,10 @@ class Agent extends Common
|
||||
'identity_text',
|
||||
'identities',
|
||||
'is_partner',
|
||||
'is_advanced',
|
||||
'maker_level_name',
|
||||
'role_names',
|
||||
'custom_role_names',
|
||||
'voucher_available',
|
||||
'active_30d',
|
||||
'madow_value',
|
||||
@@ -93,11 +96,26 @@ class Agent extends Common
|
||||
return \addons\shopro\service\partner\IdentityService::build($data)['is_partner'];
|
||||
}
|
||||
|
||||
public function getIsAdvancedAttr($value, $data)
|
||||
{
|
||||
return \addons\shopro\service\partner\IdentityService::build($data)['is_advanced'] ?? false;
|
||||
}
|
||||
|
||||
public function getMakerLevelNameAttr($value, $data)
|
||||
{
|
||||
return \addons\shopro\service\partner\IdentityService::build($data)['maker_level_name'];
|
||||
}
|
||||
|
||||
public function getRoleNamesAttr($value, $data)
|
||||
{
|
||||
return \addons\shopro\service\partner\IdentityService::build($data)['role_names'] ?? \addons\shopro\service\partner\PartnerConfig::roleNames();
|
||||
}
|
||||
|
||||
public function getCustomRoleNamesAttr($value, $data)
|
||||
{
|
||||
return $this->getRoleNamesAttr($value, $data);
|
||||
}
|
||||
|
||||
public function getVoucherAvailableAttr($value, $data)
|
||||
{
|
||||
$released = (float) ($data['voucher_released'] ?? 0);
|
||||
|
||||
@@ -314,26 +314,36 @@
|
||||
</el-form-item>
|
||||
|
||||
<div style="font-weight: 600; font-size: 13px; color: var(--sa-title); margin: 16px 0 10px; padding-bottom: 4px; border-bottom: 1px solid var(--sa-border);" class="sa-flex sa-row-between">
|
||||
<span>合伙股东身份(会员进阶身份)</span>
|
||||
<span>角色身份与进阶权益</span>
|
||||
{if $auth->check('shopro/commission/agent/edit')}
|
||||
<el-button type="primary" size="small" link @click="onOpenIdentity">开通 / 调整身份</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<template v-if="state.data.is_partner">
|
||||
<el-form-item label="合伙股东身份:">
|
||||
<template v-if="state.data.is_advanced">
|
||||
<el-form-item label="身份标签:">
|
||||
<el-tag type="warning" effect="dark" size="small">{{ state.data.identity_text }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="酒道馆:">
|
||||
<el-form-item :label="(state.data.custom_role_names?.maker || '创客') + '等级:'">
|
||||
<el-tag size="small" :type="Number(state.data.level) >= 2 ? 'primary' : 'info'">
|
||||
{{ Number(state.data.level) >= 2 ? (state.data.maker_level_name || (state.data.custom_role_names?.maker || '创客') + 'V' + (Number(state.data.level) - 1)) : '未升级' + (state.data.custom_role_names?.maker || '创客') }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="(state.data.custom_role_names?.venue || '酒道馆') + ':'">
|
||||
<el-tag size="small" :type="state.data.is_venue ? 'success' : 'info'">
|
||||
{{ state.data.is_venue ? '已开通' : '未开通' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="区县代理:">
|
||||
<el-form-item :label="(state.data.custom_role_names?.county || '区县代理') + ':'">
|
||||
<el-tag size="small" :type="state.data.is_county ? 'warning' : 'info'">
|
||||
{{ state.data.is_county ? '已开通' + (state.data.county_code ? ' (' + state.data.county_code + ')' : '') : '未开通' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="(state.data.custom_role_names?.partner || '合伙股东') + ':'">
|
||||
<el-tag size="small" :type="state.data.is_partner ? 'danger' : 'info'">
|
||||
{{ state.data.is_partner ? '已授予' + (state.data.custom_role_names?.partner || '合伙股东') : '未开通' }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="30天自购考核:">
|
||||
<span v-if="state.data.active_30d" style="color: #52c41a; font-weight: 500;">
|
||||
🟢 正常领取
|
||||
@@ -358,17 +368,17 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-form-item label="进阶身份:">
|
||||
<el-tag type="info" effect="plain" size="small">基础会员 · 品酒师(暂未开通合伙股东)</el-tag>
|
||||
<el-form-item label="当前身份:">
|
||||
<el-tag type="info" effect="plain" size="small">{{ state.data.identity_text || ('基础会员 · ' + (state.data.custom_role_names?.taster || '品酒师')) }}</el-tag>
|
||||
</el-form-item>
|
||||
<div style="font-size: 12px; color: var(--sa-subfont); line-height: 1.6; background: #f8fafc; border: 1px dashed var(--sa-border); border-radius: 4px; padding: 8px 12px; margin: 4px 0 10px;">
|
||||
💡 <strong>「合伙股东」是会员专属进阶身份</strong>:当会员升级创客(V1~V6)或开通酒道馆、区县代理时自动附加合伙股东身份标签,并享有团队分润与平台日结分红。当前会员为基础会员,享有推三返一,<strong>免自购考核</strong>。
|
||||
💡 <strong>进阶角色体系说明</strong>:系统拥有{{ state.data.custom_role_names?.maker || '创客' }}(V1~V6)、{{ state.data.custom_role_names?.venue || '酒道馆' }}、{{ state.data.custom_role_names?.county || '区县代理' }}、{{ state.data.custom_role_names?.partner || '合伙股东' }}等独立进阶角色,享有团队分润与平台日结分红。当前会员为基础会员,享有推三返一,<strong>免自购考核</strong>。
|
||||
</div>
|
||||
<el-form-item label="自购考核要求:">
|
||||
<el-tag size="small" type="info">免自购考核 (基础会员/品酒师)</el-tag>
|
||||
<el-tag size="small" type="info">免自购考核 (基础会员/{{ state.data.custom_role_names?.taster || '品酒师' }})</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="合伙股东权益:">
|
||||
<span style="color: var(--sa-subfont);">晋升创客或开通馆/县后即可享有团队奖、极差奖与马豆分红</span>
|
||||
<el-form-item label="进阶权益:">
|
||||
<span style="color: var(--sa-subfont);">升级创客或开通馆/县/股东后即可享有团队奖、极差奖与马豆分红</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -61,14 +61,15 @@
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.identity_text || '-' }}</div>
|
||||
<div>
|
||||
<el-tag v-if="scope.row.is_venue" size="small" type="success">酒道馆</el-tag>
|
||||
<el-tag v-if="scope.row.is_county" size="small" type="warning">区县</el-tag>
|
||||
<el-tag v-if="scope.row.is_venue" size="small" type="success">{{ scope.row.role_names?.venue || '酒道馆' }}</el-tag>
|
||||
<el-tag v-if="scope.row.is_county" size="small" type="warning">{{ scope.row.role_names?.county || '区县代理' }}</el-tag>
|
||||
<el-tag v-if="scope.row.is_partner" size="small" type="danger">{{ scope.row.role_names?.partner || '合伙股东' }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="30日活跃" min-width="100">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.is_partner">
|
||||
<template v-if="scope.row.is_advanced">
|
||||
<span class="sa-flex sa-col-center">
|
||||
<i class="active-dot" :class="scope.row.active_30d ? 'is-on' : 'is-off'"></i>
|
||||
{{ scope.row.active_30d ? '有自购' : '已暂停' }}
|
||||
|
||||
@@ -885,6 +885,23 @@
|
||||
<div class="tip">partner_mode=1 时新单不再写百分比佣金</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="sa-title is-line">角色身份自定义名称</div>
|
||||
<el-form-item label="品酒师自定义名称">
|
||||
<el-input class="sa-w-360" v-model="form.model.partner_role_name_taster" placeholder="默认:品酒师"></el-input>
|
||||
<div class="tip">基础会员身份名称,如自定义为「品鉴官」,为空时恢复默认「品酒师」</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="创客自定义基础名称">
|
||||
<el-input class="sa-w-360" v-model="form.model.partner_role_name_maker" placeholder="默认:创客"></el-input>
|
||||
<div class="tip">创客基础称谓,系统自动衍生各等级(如输入「服务商」,将展示为 服务商V1 ~ 服务商V6)</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="酒道馆自定义名称">
|
||||
<el-input class="sa-w-360" v-model="form.model.partner_role_name_venue" placeholder="默认:酒道馆"></el-input>
|
||||
<div class="tip">实体枢纽身份名称,如自定义为「星级体验馆」,为空时恢复默认「酒道馆」</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="区县代理自定义名称">
|
||||
<el-input class="sa-w-360" v-model="form.model.partner_role_name_county" placeholder="默认:区县代理"></el-input>
|
||||
<div class="tip">区域渠道领头人身份名称,如自定义为「城市合伙人」,为空时恢复默认「区县代理」</div>
|
||||
</el-form-item>
|
||||
<div class="sa-title is-line">计单 / 活跃</div>
|
||||
<el-form-item label="合格订单口径">
|
||||
<el-radio-group v-model="form.model.partner_count_type">
|
||||
|
||||
@@ -528,8 +528,8 @@
|
||||
<div class="network-card card">
|
||||
<div class="sa-flex sa-row-between mb-2">
|
||||
<div class="sa-flex">
|
||||
<span class="network-badge bg-primary mr-2">品</span>
|
||||
<span class="font-bold text-dark" style="font-size: 14px;">品酒师会员</span>
|
||||
<span class="network-badge bg-primary mr-2">{{ (state.partner.role_names?.taster || '品').substr(0, 1) }}</span>
|
||||
<span class="font-bold text-dark" style="font-size: 14px;">{{ state.partner.role_names?.taster || '品酒师' }}会员</span>
|
||||
</div>
|
||||
<span class="text-primary font-bold cursor-pointer" style="font-size: 18px;" @click="onOpenAgent(1)">
|
||||
{{ state.partner.taster_count }} <span style="font-size: 12px; color: var(--sa-subfont);">人</span>
|
||||
@@ -552,8 +552,8 @@
|
||||
<div class="network-card card">
|
||||
<div class="sa-flex sa-row-between mb-2">
|
||||
<div class="sa-flex">
|
||||
<span class="network-badge bg-cyan mr-2">创</span>
|
||||
<span class="font-bold text-dark" style="font-size: 14px;">创客骨干梯队 (V1~V6)</span>
|
||||
<span class="network-badge bg-cyan mr-2">{{ (state.partner.role_names?.maker || '创').substr(0, 1) }}</span>
|
||||
<span class="font-bold text-dark" style="font-size: 14px;">{{ state.partner.role_names?.maker || '创客' }}骨干梯队 (V1~V6)</span>
|
||||
</div>
|
||||
<span class="text-cyan font-bold cursor-pointer" style="font-size: 18px;" @click="onOpenAgent('')">
|
||||
{{ state.partner.maker_count }} <span style="font-size: 12px; color: var(--sa-subfont);">人</span>
|
||||
@@ -584,15 +584,15 @@
|
||||
<span class="font-bold text-dark" style="font-size: 14px;">实体与区域渠道</span>
|
||||
</div>
|
||||
<div class="sa-flex">
|
||||
<span class="text-orange font-bold mr-2" style="font-size: 14px;">馆 {{ state.partner.venue_count }} 家</span>
|
||||
<span class="text-purple font-bold" style="font-size: 14px;">县 {{ state.partner.county_count }} 个</span>
|
||||
<span class="text-orange font-bold mr-2" style="font-size: 14px;">{{ (state.partner.role_names?.venue || '馆').substr(0, 1) }} {{ state.partner.venue_count }} 家</span>
|
||||
<span class="text-purple font-bold" style="font-size: 14px;">{{ (state.partner.role_names?.county || '县').substr(0, 1) }} {{ state.partner.county_count }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sa-flex sa-row-between text-muted" style="font-size: 12px;">
|
||||
<span>酒道馆:持豆 <strong class="text-purple">{{ state.partner.role_stats?.venue?.madow_total || '0.00' }}</strong> / 收益 <strong class="text-warning">¥{{ state.partner.role_stats?.venue?.reward_amount || '0.00' }}</strong></span>
|
||||
<span>{{ state.partner.role_names?.venue || '酒道馆' }}:持豆 <strong class="text-purple">{{ state.partner.role_stats?.venue?.madow_total || '0.00' }}</strong> / 收益 <strong class="text-warning">¥{{ state.partner.role_stats?.venue?.reward_amount || '0.00' }}</strong></span>
|
||||
</div>
|
||||
<div class="sa-flex sa-row-between text-muted" style="font-size: 12px; margin-top: 6px;">
|
||||
<span>区县代理:持豆 <strong class="text-purple">{{ state.partner.role_stats?.county?.madow_total || '0.00' }}</strong> / 收益 <strong class="text-warning">¥{{ state.partner.role_stats?.county?.reward_amount || '0.00' }}</strong></span>
|
||||
<span>{{ state.partner.role_names?.county || '区县代理' }}:持豆 <strong class="text-purple">{{ state.partner.role_stats?.county?.madow_total || '0.00' }}</strong> / 收益 <strong class="text-warning">¥{{ state.partner.role_stats?.county?.reward_amount || '0.00' }}</strong></span>
|
||||
</div>
|
||||
<div class="sa-flex sa-row-between text-muted" style="font-size: 12px; margin-top: 6px;">
|
||||
<span>享团队级差分润 + 平台日结均分池</span>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reward_type_text" label="奖励类型" min-width="120"></el-table-column>
|
||||
<el-table-column prop="role_text" label="合伙身份" min-width="80"></el-table-column>
|
||||
<el-table-column prop="role_text" label="受益身份" min-width="80"></el-table-column>
|
||||
<el-table-column label="结算金额" min-width="90">
|
||||
<template #default="scope"> {{ scope.row.amount }} </template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -198,6 +198,25 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
}, function (ret, res) {
|
||||
state.data = res.data.data
|
||||
pagination.total = res.data.total
|
||||
if (res.data.data && res.data.data.length > 0) {
|
||||
const roleNames = res.data.data[0].custom_role_names || res.data.data[0].role_names;
|
||||
if (roleNames) {
|
||||
if (state.filter.tools.identity && state.filter.tools.identity.options) {
|
||||
state.filter.tools.identity.options.data = [
|
||||
{ value: 'taster', label: roleNames.taster || '品酒师' },
|
||||
{ value: 'maker', label: roleNames.maker || '创客' },
|
||||
{ value: 'venue', label: roleNames.venue || '酒道馆' },
|
||||
{ value: 'county', label: roleNames.county || '区县' },
|
||||
];
|
||||
}
|
||||
if (state.filter.tools.is_venue) {
|
||||
state.filter.tools.is_venue.label = roleNames.venue || '酒道馆';
|
||||
}
|
||||
if (state.filter.tools.is_county) {
|
||||
state.filter.tools.is_county.label = roleNames.county || '区县代理';
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, function (ret, res) { })
|
||||
}
|
||||
@@ -313,6 +332,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
county_code: row.county_code || '',
|
||||
area_arr: row.county_code ? findAreaPath(identity.areaSelect, row.county_code) || [] : [],
|
||||
upgrade_lock: Number(row.upgrade_lock || 0),
|
||||
role_names: row.role_names || row.custom_role_names || {},
|
||||
remark: '',
|
||||
}
|
||||
identity.visible = true
|
||||
@@ -332,13 +352,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
}
|
||||
}
|
||||
|
||||
function identityLabel(level, isVenue, isCounty, isPartner) {
|
||||
function identityLabel(level, isVenue, isCounty, isPartner, roleNames) {
|
||||
const rn = roleNames || identity.form?.role_names || {}
|
||||
const tasterTitle = rn.taster || '品酒师'
|
||||
const makerTitle = rn.maker || '创客'
|
||||
const venueTitle = rn.venue || '酒道馆'
|
||||
const countyTitle = rn.county || '区县代理'
|
||||
const partnerTitle = rn.partner || '合伙股东'
|
||||
|
||||
const lv = identity.levels.find(i => Number(i.level) === Number(level))
|
||||
let name = lv?.name || (Number(level) === 1 ? '品酒师' : `创客V${Number(level) - 1}`)
|
||||
let name = lv?.name || (Number(level) === 1 ? tasterTitle : `${makerTitle}V${Number(level) - 1}`)
|
||||
const tags = [name]
|
||||
if (Number(isVenue)) tags.push('酒道馆')
|
||||
if (Number(isCounty)) tags.push('区县代理')
|
||||
if (Number(isPartner)) tags.push('合伙股东')
|
||||
if (Number(isVenue)) tags.push(venueTitle)
|
||||
if (Number(isCounty)) tags.push(countyTitle)
|
||||
if (Number(isPartner)) tags.push(partnerTitle)
|
||||
return tags.filter((v, i, a) => a.indexOf(v) === i).join(' · ')
|
||||
}
|
||||
|
||||
@@ -560,13 +587,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
return
|
||||
}
|
||||
const { ElMessageBox } = ElementPlus
|
||||
const rn = state.data.custom_role_names || state.data.role_names || {}
|
||||
const tasterTitle = rn.taster || '品酒师'
|
||||
const makerTitle = rn.maker || '创客'
|
||||
const venueTitle = rn.venue || '酒道馆'
|
||||
const countyTitle = rn.county || '区县代理'
|
||||
const partnerTitle = rn.partner || '合伙股东'
|
||||
|
||||
const lv = identity.levels.find(i => Number(i.level) === Number(identity.form.level))
|
||||
const tags = []
|
||||
if (Number(identity.form.level) >= 2) tags.push(lv?.name || `创客V${identity.form.level - 1}`)
|
||||
else tags.push(lv?.name || '品酒师')
|
||||
if (Number(identity.form.is_venue)) tags.push('酒道馆')
|
||||
if (Number(identity.form.is_county)) tags.push('区县代理')
|
||||
if (Number(identity.form.is_partner)) tags.push('合伙股东')
|
||||
if (Number(identity.form.level) >= 2) tags.push(lv?.name || `${makerTitle}V${identity.form.level - 1}`)
|
||||
else tags.push(lv?.name || tasterTitle)
|
||||
if (Number(identity.form.is_venue)) tags.push(venueTitle)
|
||||
if (Number(identity.form.is_county)) tags.push(countyTitle)
|
||||
if (Number(identity.form.is_partner)) tags.push(partnerTitle)
|
||||
const after = tags.filter((v, i, a) => a.indexOf(v) === i).join(' · ')
|
||||
ElMessageBox.confirm(
|
||||
`将「${identity.form.nickname}」从 ${identity.form.before_text || '当前身份'} 调整为 ${after}。已入账收益不追溯。确定?`,
|
||||
|
||||
@@ -315,7 +315,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
|
||||
num: '0.00',
|
||||
text: '分成奖励支出',
|
||||
path: '',
|
||||
tip: '选定时间区间内已真实入账的合伙收益与分成现金总额',
|
||||
tip: '选定时间区间内已真实入账的收益中心奖励与分成现金总额',
|
||||
status: 'payout',
|
||||
},
|
||||
retention: {
|
||||
|
||||
@@ -268,10 +268,10 @@ class DashboardWealthTest
|
||||
$this->assert(isset($operation['pay_conversion_rate']), "运营包含 pay_conversion_rate 付费转化率");
|
||||
$this->assert(isset($operation['repeat_purchase_rate']), "运营包含 repeat_purchase_rate 复购率");
|
||||
$this->assert(isset($operation['arpu']), "运营包含 arpu 付费买家客单贡献");
|
||||
$this->assert(isset($operation['active_partners']), "运营包含 active_partners 活跃达标合伙人");
|
||||
$this->assert(isset($operation['warning_partners']), "运营包含 warning_partners 7天内即将到期预警合伙人");
|
||||
$this->assert(isset($operation['pending_renew_partners']), "运营包含 pending_renew_partners 待续期激活合伙人");
|
||||
$this->assert(isset($operation['new_no_order_partners']), "运营包含 new_no_order_partners 新成员待首购合伙人");
|
||||
$this->assert(isset($operation['active_partners']), "运营包含 active_partners 活跃达标进阶成员");
|
||||
$this->assert(isset($operation['warning_partners']), "运营包含 warning_partners 7天内即将到期预警进阶成员");
|
||||
$this->assert(isset($operation['pending_renew_partners']), "运营包含 pending_renew_partners 待续期激活进阶成员");
|
||||
$this->assert(isset($operation['new_no_order_partners']), "运营包含 new_no_order_partners 新成员待首购进阶成员");
|
||||
$this->assert(is_array($operation['warning_list']), "运营包含 warning_list 临期预警人员名单");
|
||||
$this->assert(is_array($operation['pending_renew_list']), "运营包含 pending_renew_list 待续期人员名单");
|
||||
$this->assert(isset($operation['low_stock_count']), "运营包含 low_stock_count 低库存告急SKU数量");
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 角色身份自定义名称全链路规范与对齐测试
|
||||
* 覆盖范围:配置层、服务层、模型层、后台管理、数据大屏、UniApp 移动端
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function assert(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. PartnerConfig.php 验证
|
||||
const partnerConfigPath = path.resolve(__dirname, '../addons/shopro/service/partner/PartnerConfig.php');
|
||||
assert(fs.existsSync(partnerConfigPath), 'PartnerConfig.php 文件存在');
|
||||
if (fs.existsSync(partnerConfigPath)) {
|
||||
const content = fs.readFileSync(partnerConfigPath, 'utf-8');
|
||||
assert(content.includes('public static function roleNames'), 'PartnerConfig 包含 roleNames() 方法');
|
||||
assert(content.includes('partner_role_name_taster'), 'roleNames() 包含品酒师别名读取');
|
||||
assert(content.includes('partner_role_name_maker'), 'roleNames() 包含创客别名读取');
|
||||
assert(content.includes('partner_role_name_venue'), 'roleNames() 包含酒道馆别名读取');
|
||||
assert(content.includes('partner_role_name_county'), 'roleNames() 包含区县别名读取');
|
||||
assert(content.includes('partner_role_name_partner'), 'roleNames() 包含合伙股东别名读取');
|
||||
assert(content.includes("$maker . '进阶奖'"), 'rewardTypeMap() 动态拼接创客进阶奖');
|
||||
assert(content.includes("$venue . '运营奖'"), 'rewardTypeMap() 动态拼接酒道馆运营奖');
|
||||
assert(content.includes("$county . '统筹奖'"), 'rewardTypeMap() 动态拼接区县统筹奖');
|
||||
}
|
||||
|
||||
// 2. IdentityService.php 验证
|
||||
const identityServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/IdentityService.php');
|
||||
assert(fs.existsSync(identityServicePath), 'IdentityService.php 文件存在');
|
||||
if (fs.existsSync(identityServicePath)) {
|
||||
const content = fs.readFileSync(identityServicePath, 'utf-8');
|
||||
assert(content.includes('PartnerConfig::roleNames()'), 'IdentityService 调用 PartnerConfig::roleNames()');
|
||||
assert(content.includes("'custom_role_names' => $roleNames"), 'IdentityService::build 返回 custom_role_names');
|
||||
assert(content.includes("'maker_level_name' =>"), 'IdentityService::build 返回动态 maker_level_name');
|
||||
assert(content.includes("'role_names' => $roleNames"), 'IdentityService::attachProfile 附加 role_names');
|
||||
}
|
||||
|
||||
// 3. Agent.php 模型验证
|
||||
const agentModelPath = path.resolve(__dirname, '../application/admin/model/shopro/commission/Agent.php');
|
||||
assert(fs.existsSync(agentModelPath), 'Agent.php 模型文件存在');
|
||||
if (fs.existsSync(agentModelPath)) {
|
||||
const content = fs.readFileSync(agentModelPath, 'utf-8');
|
||||
assert(content.includes("'role_names'"), 'Agent 模型 $append 包含 role_names');
|
||||
assert(content.includes("'custom_role_names'"), 'Agent 模型 $append 包含 custom_role_names');
|
||||
assert(content.includes('getRoleNamesAttr'), 'Agent 模型包含 getRoleNamesAttr 获取器');
|
||||
}
|
||||
|
||||
// 4. Config.php 控制器与 config/index.html 验证
|
||||
const configCtrlPath = path.resolve(__dirname, '../application/admin/controller/shopro/Config.php');
|
||||
const configViewPath = path.resolve(__dirname, '../application/admin/view/shopro/config/index.html');
|
||||
assert(fs.existsSync(configCtrlPath), 'Config.php 控制器存在');
|
||||
assert(fs.existsSync(configViewPath), 'config/index.html 视图存在');
|
||||
if (fs.existsSync(configCtrlPath)) {
|
||||
const content = fs.readFileSync(configCtrlPath, 'utf-8');
|
||||
assert(content.includes("'partner_role_name_taster'"), 'Config.php 包含品酒师配置字段保存');
|
||||
assert(content.includes("'partner_role_name_maker'"), 'Config.php 包含创客配置字段保存');
|
||||
assert(content.includes("'partner_role_name_venue'"), 'Config.php 包含酒道馆配置字段保存');
|
||||
assert(content.includes("'partner_role_name_county'"), 'Config.php 包含区县配置字段保存');
|
||||
}
|
||||
if (fs.existsSync(configViewPath)) {
|
||||
const content = fs.readFileSync(configViewPath, 'utf-8');
|
||||
assert(content.includes('角色身份自定义名称'), 'config/index.html 包含角色身份自定义名称配置模块');
|
||||
assert(content.includes('form.model.partner_role_name_taster'), '包含品酒师输入框绑定');
|
||||
assert(content.includes('form.model.partner_role_name_maker'), '包含创客输入框绑定');
|
||||
assert(content.includes('form.model.partner_role_name_venue'), '包含酒道馆输入框绑定');
|
||||
assert(content.includes('form.model.partner_role_name_county'), '包含区县代理输入框绑定');
|
||||
}
|
||||
|
||||
// 5. 后台分销商管理与详情页验证
|
||||
const agentCtrlPath = path.resolve(__dirname, '../application/admin/controller/shopro/commission/Agent.php');
|
||||
const agentDetailViewPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/agent/detail.html');
|
||||
const agentIndexViewPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/agent/index.html');
|
||||
const agentJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/commission/agent.js');
|
||||
assert(fs.existsSync(agentCtrlPath), 'commission/Agent.php 控制器存在');
|
||||
assert(fs.existsSync(agentDetailViewPath), 'agent/detail.html 存在');
|
||||
assert(fs.existsSync(agentIndexViewPath), 'agent/index.html 存在');
|
||||
assert(fs.existsSync(agentJsPath), 'agent.js 存在');
|
||||
|
||||
if (fs.existsSync(agentCtrlPath)) {
|
||||
const content = fs.readFileSync(agentCtrlPath, 'utf-8');
|
||||
assert(content.includes('custom_role_names = \\addons\\shopro\\service\\partner\\PartnerConfig::roleNames()'), 'Agent.php detail() 接口返回 custom_role_names');
|
||||
}
|
||||
if (fs.existsSync(agentDetailViewPath)) {
|
||||
const content = fs.readFileSync(agentDetailViewPath, 'utf-8');
|
||||
assert(content.includes('state.data.custom_role_names?.venue'), 'detail.html 动态绑定酒道馆 label');
|
||||
assert(content.includes('state.data.custom_role_names?.county'), 'detail.html 动态绑定区县代理 label');
|
||||
}
|
||||
if (fs.existsSync(agentIndexViewPath)) {
|
||||
const content = fs.readFileSync(agentIndexViewPath, 'utf-8');
|
||||
assert(content.includes('scope.row.role_names?.venue'), 'index.html 动态显示酒道馆 tag');
|
||||
assert(content.includes('scope.row.role_names?.county'), 'index.html 动态显示区县 tag');
|
||||
}
|
||||
if (fs.existsSync(agentJsPath)) {
|
||||
const content = fs.readFileSync(agentJsPath, 'utf-8');
|
||||
assert(content.includes('function identityLabel('), 'agent.js 弹窗使用 dynamic identityLabel');
|
||||
assert(content.includes('state.filter.tools.identity.options.data = ['), 'agent.js 列表搜索工具栏同步自定义名称');
|
||||
}
|
||||
|
||||
// 6. 数据大屏验证
|
||||
const dashboardCtrlPath = path.resolve(__dirname, '../application/admin/controller/shopro/Dashboard.php');
|
||||
const dashboardViewPath = path.resolve(__dirname, '../application/admin/view/shopro/dashboard/index.html');
|
||||
assert(fs.existsSync(dashboardCtrlPath), 'Dashboard.php 存在');
|
||||
assert(fs.existsSync(dashboardViewPath), 'dashboard/index.html 存在');
|
||||
if (fs.existsSync(dashboardCtrlPath)) {
|
||||
const content = fs.readFileSync(dashboardCtrlPath, 'utf-8');
|
||||
assert(content.includes('$roleNames = PartnerConfig::roleNames()'), 'Dashboard.php 读取角色别名配置');
|
||||
assert(content.includes("'custom_role_names' => $roleNames"), 'Dashboard.php 返回 custom_role_names');
|
||||
}
|
||||
if (fs.existsSync(dashboardViewPath)) {
|
||||
const content = fs.readFileSync(dashboardViewPath, 'utf-8');
|
||||
assert(content.includes('state.partner.role_names?.taster'), '大屏品酒师卡片使用动态名称');
|
||||
assert(content.includes('state.partner.role_names?.maker'), '大屏创客卡片使用动态名称');
|
||||
assert(content.includes('state.partner.role_names?.venue'), '大屏酒道馆卡片使用动态名称');
|
||||
assert(content.includes('state.partner.role_names?.county'), '大屏区县卡片使用动态名称');
|
||||
}
|
||||
|
||||
// 7. UniApp 移动端验证
|
||||
const userCardVuePath = path.resolve(__dirname, '../uniapp/sheep/components/s-user-card/s-user-card.vue');
|
||||
const commInfoVuePath = path.resolve(__dirname, '../uniapp/pages/commission/components/commission-info.vue');
|
||||
const teamVuePath = path.resolve(__dirname, '../uniapp/pages/commission/team.vue');
|
||||
const rewardsVuePath = path.resolve(__dirname, '../uniapp/pages/commission/rewards.vue');
|
||||
|
||||
assert(fs.existsSync(userCardVuePath), 's-user-card.vue 存在');
|
||||
assert(fs.existsSync(commInfoVuePath), 'commission-info.vue 存在');
|
||||
assert(fs.existsSync(teamVuePath), 'team.vue 存在');
|
||||
assert(fs.existsSync(rewardsVuePath), 'rewards.vue 存在');
|
||||
|
||||
if (fs.existsSync(userCardVuePath)) {
|
||||
const content = fs.readFileSync(userCardVuePath, 'utf-8');
|
||||
assert(content.includes("roleNames.taster || '品酒师'"), 's-user-card.vue 动态显示品酒师徽章');
|
||||
assert(content.includes("roleNames.venue || '酒道馆'"), 's-user-card.vue 动态显示酒道馆徽章');
|
||||
assert(content.includes("roleNames.county || '区县'"), 's-user-card.vue 动态显示区县徽章');
|
||||
}
|
||||
if (fs.existsSync(commInfoVuePath)) {
|
||||
const content = fs.readFileSync(commInfoVuePath, 'utf-8');
|
||||
assert(content.includes("roleNames.venue || '酒道馆'"), 'commission-info.vue 动态显示酒道馆 mini-tag');
|
||||
assert(content.includes("roleNames.county || '区县'"), 'commission-info.vue 动态显示区县 mini-tag');
|
||||
assert(content.includes('levelTitle'), 'commission-info.vue 等级标题动态映射');
|
||||
}
|
||||
if (fs.existsSync(teamVuePath)) {
|
||||
const content = fs.readFileSync(teamVuePath, 'utf-8');
|
||||
assert(content.includes("roleNames.venue || '酒道馆'"), 'team.vue 列表项动态显示酒道馆');
|
||||
assert(content.includes("roleNames.county || '区县代理'"), 'team.vue 列表项动态显示区县代理');
|
||||
assert(content.includes('filterPills = computed('), 'team.vue 筛选胶囊采用计算属性');
|
||||
}
|
||||
if (fs.existsSync(rewardsVuePath)) {
|
||||
const content = fs.readFileSync(rewardsVuePath, 'utf-8');
|
||||
assert(content.includes('tabMaps = computed('), 'rewards.vue Tab列表采用计算属性');
|
||||
assert(content.includes("roleNames.value.maker || '创客'"), 'rewards.vue 收益Tab动态绑定创客');
|
||||
assert(content.includes("roleNames.value.venue || '酒道馆'"), 'rewards.vue 收益Tab动态绑定酒道馆');
|
||||
assert(content.includes("roleNames.value.county || '区县'"), 'rewards.vue 收益Tab动态绑定区县');
|
||||
}
|
||||
|
||||
console.log('\n--------------------------------------------------------');
|
||||
console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`);
|
||||
console.log('========================================================\n');
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -18,10 +18,10 @@
|
||||
mode="aspectFill"
|
||||
>
|
||||
</image>
|
||||
<text class="tag-title">{{ agentInfo.level_info?.name }}</text>
|
||||
<text class="tag-title">{{ levelTitle }}</text>
|
||||
</view>
|
||||
<view v-if="agentInfo.is_venue || agentInfo.identities?.includes('venue')" class="mini-tag">酒道馆</view>
|
||||
<view v-if="agentInfo.is_county || agentInfo.identities?.includes('county')" class="mini-tag">区县</view>
|
||||
<view v-if="agentInfo.is_venue || agentInfo.identities?.includes('venue')" class="mini-tag">{{ roleNames.venue || '酒道馆' }}</view>
|
||||
<view v-if="agentInfo.is_county || agentInfo.identities?.includes('county')" class="mini-tag">{{ roleNames.county || '区县' }}</view>
|
||||
<view class="ss-iconfont uicon-arrow-right" style="color: #fff; font-size: 28rpx">
|
||||
</view>
|
||||
</view>
|
||||
@@ -39,6 +39,29 @@
|
||||
const agentInfo = computed(() => sheep.$store('user').agentInfo);
|
||||
const headerBg = sheep.$url.css('/assets/addons/shopro/uniapp/commission/background.png');
|
||||
|
||||
const roleNames = computed(() => {
|
||||
return (
|
||||
userInfo.value?.custom_role_names ||
|
||||
userInfo.value?.role_names ||
|
||||
agentInfo.value?.custom_role_names ||
|
||||
agentInfo.value?.role_names ||
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
const levelTitle = computed(() => {
|
||||
if (userInfo.value?.maker_level_name) return userInfo.value.maker_level_name;
|
||||
if (agentInfo.value?.maker_level_name) return agentInfo.value.maker_level_name;
|
||||
const rawName = agentInfo.value?.level_info?.name;
|
||||
if (rawName && roleNames.value.maker && rawName.includes('创客')) {
|
||||
return rawName.replace('创客', roleNames.value.maker);
|
||||
}
|
||||
if (rawName && roleNames.value.taster && rawName.includes('品酒师')) {
|
||||
return rawName.replace('品酒师', roleNames.value.taster);
|
||||
}
|
||||
return rawName || '';
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
showMoney: false,
|
||||
});
|
||||
|
||||
@@ -96,21 +96,34 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import _ from 'lodash';
|
||||
|
||||
const headerBg = sheep.$url.css('/assets/addons/shopro/uniapp/user/wallet_card_bg.png');
|
||||
|
||||
const tabMaps = [
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
const agentInfo = computed(() => sheep.$store('user').agentInfo);
|
||||
|
||||
const roleNames = computed(() => {
|
||||
return (
|
||||
userInfo.value?.custom_role_names ||
|
||||
userInfo.value?.role_names ||
|
||||
agentInfo.value?.custom_role_names ||
|
||||
agentInfo.value?.role_names ||
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
const tabMaps = computed(() => [
|
||||
{ name: '全部', value: 'all' },
|
||||
{ name: '开拓奖', value: 'push3' },
|
||||
{ name: '创客', value: 'maker' },
|
||||
{ name: '酒道馆', value: 'venue' },
|
||||
{ name: '区县', value: 'county' },
|
||||
{ name: roleNames.value.maker || '创客', value: 'maker' },
|
||||
{ name: roleNames.value.venue || '酒道馆', value: 'venue' },
|
||||
{ name: roleNames.value.county || '区县', value: 'county' },
|
||||
{ name: '加权奖励', value: 'share' },
|
||||
];
|
||||
]);
|
||||
|
||||
const emptyPagination = {
|
||||
data: [],
|
||||
|
||||
@@ -221,13 +221,13 @@
|
||||
v-if="item.agent?.is_venue || item.agent?.identities?.includes('venue')"
|
||||
class="mini-tag tag-venue"
|
||||
>
|
||||
酒道馆
|
||||
{{ roleNames.venue || '酒道馆' }}
|
||||
</view>
|
||||
<view
|
||||
v-if="item.agent?.is_county || item.agent?.identities?.includes('county')"
|
||||
class="mini-tag tag-county"
|
||||
>
|
||||
区县代理
|
||||
{{ roleNames.county || '区县代理' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -403,13 +403,13 @@
|
||||
v-if="state.currentMember.agent?.is_venue || state.currentMember.agent?.identities?.includes('venue')"
|
||||
class="id-pill mini-tag tag-venue ss-m-r-12 ss-m-b-10"
|
||||
>
|
||||
酒道馆
|
||||
{{ roleNames.venue || '酒道馆' }}
|
||||
</view>
|
||||
<view
|
||||
v-if="state.currentMember.agent?.is_county || state.currentMember.agent?.identities?.includes('county')"
|
||||
class="id-pill mini-tag tag-county ss-m-r-12 ss-m-b-10"
|
||||
>
|
||||
区县代理
|
||||
{{ roleNames.county || '区县代理' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -516,6 +516,16 @@
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
const headerBg = sheep.$url.css('/assets/addons/shopro/uniapp/user/withdraw_bg.png');
|
||||
|
||||
const roleNames = computed(() => {
|
||||
return (
|
||||
userInfo.value?.custom_role_names ||
|
||||
userInfo.value?.role_names ||
|
||||
agentInfo.value?.custom_role_names ||
|
||||
agentInfo.value?.role_names ||
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
// 控制吸顶及页面滚动
|
||||
onPageScroll((e) => {
|
||||
state.scrollTop = e.scrollTop > 100;
|
||||
@@ -527,13 +537,13 @@
|
||||
{ name: '二级间推', value: '2' },
|
||||
];
|
||||
|
||||
const filterPills = [
|
||||
const filterPills = computed(() => [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '合伙股东', value: 'partner' },
|
||||
{ label: roleNames.value.partner || '合伙股东', value: 'partner' },
|
||||
{ label: '正常活跃', value: 'active' },
|
||||
{ label: '临期预警', value: 'expiring' },
|
||||
{ label: '已暂停', value: 'inactive' },
|
||||
];
|
||||
]);
|
||||
|
||||
const sortOptions = [
|
||||
{ label: '最新加入', value: 'createtime' },
|
||||
@@ -581,12 +591,25 @@
|
||||
}
|
||||
|
||||
function memberLevelName(item) {
|
||||
return (
|
||||
item?.agent?.level_info?.name ||
|
||||
item?.agent?.maker_level_name ||
|
||||
item?.agent?.identity_text ||
|
||||
''
|
||||
);
|
||||
const rNames =
|
||||
item?.agent?.custom_role_names ||
|
||||
item?.agent?.role_names ||
|
||||
roleNames.value ||
|
||||
{};
|
||||
if (item?.agent?.maker_level_name) {
|
||||
return item.agent.maker_level_name;
|
||||
}
|
||||
const rawName = item?.agent?.level_info?.name;
|
||||
if (rawName) {
|
||||
if (rNames.maker && rawName.includes('创客')) {
|
||||
return rawName.replace('创客', rNames.maker);
|
||||
}
|
||||
if (rNames.taster && rawName.includes('品酒师')) {
|
||||
return rawName.replace('品酒师', rNames.taster);
|
||||
}
|
||||
return rawName;
|
||||
}
|
||||
return item?.agent?.identity_text || '';
|
||||
}
|
||||
|
||||
function formatTime(timestamp) {
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
class="identity-row ss-flex ss-col-center ss-flex-wrap"
|
||||
@tap="onBadgeTap"
|
||||
>
|
||||
<view v-if="hasTaster" class="identity-tag taster-tag">品酒师</view>
|
||||
<view v-if="hasTaster" class="identity-tag taster-tag">{{ roleNames.taster || '品酒师' }}</view>
|
||||
<view v-if="makerLevelName" class="identity-tag maker-tag">{{ makerLevelName }}</view>
|
||||
<view v-if="hasVenue" class="identity-tag venue-tag">酒道馆</view>
|
||||
<view v-if="hasCounty" class="identity-tag county-tag">区县</view>
|
||||
<view v-if="hasPartner" class="identity-tag partner-tag">合伙股东</view>
|
||||
<view v-if="hasVenue" class="identity-tag venue-tag">{{ roleNames.venue || '酒道馆' }}</view>
|
||||
<view v-if="hasCounty" class="identity-tag county-tag">{{ roleNames.county || '区县' }}</view>
|
||||
<view v-if="hasPartner" class="identity-tag partner-tag">{{ roleNames.partner || '合伙股东' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -111,27 +111,42 @@
|
||||
|
||||
const identityText = computed(() => userInfo.value?.identity_text || '');
|
||||
|
||||
const roleNames = computed(() => {
|
||||
return userInfo.value?.custom_role_names || userInfo.value?.role_names || {};
|
||||
});
|
||||
|
||||
const makerLevelName = computed(() => userInfo.value?.maker_level_name || '');
|
||||
|
||||
const hasVenue = computed(
|
||||
() => identities.value.includes('venue') || identityText.value.includes('酒道馆'),
|
||||
() =>
|
||||
identities.value.includes('venue') ||
|
||||
identityText.value.includes('酒道馆') ||
|
||||
(roleNames.value.venue && identityText.value.includes(roleNames.value.venue)),
|
||||
);
|
||||
|
||||
const hasCounty = computed(
|
||||
() => identities.value.includes('county') || identityText.value.includes('区县'),
|
||||
() =>
|
||||
identities.value.includes('county') ||
|
||||
identityText.value.includes('区县') ||
|
||||
(roleNames.value.county && identityText.value.includes(roleNames.value.county)),
|
||||
);
|
||||
|
||||
const hasPartner = computed(() => {
|
||||
return (
|
||||
Boolean(userInfo.value?.is_partner) ||
|
||||
identities.value.includes('partner') ||
|
||||
identityText.value.includes('合伙股东')
|
||||
identityText.value.includes('合伙股东') ||
|
||||
(roleNames.value.partner && identityText.value.includes(roleNames.value.partner))
|
||||
);
|
||||
});
|
||||
|
||||
const hasTaster = computed(() => {
|
||||
if (makerLevelName.value) return false;
|
||||
return identities.value.includes('taster') || identityText.value.includes('品酒师');
|
||||
return (
|
||||
identities.value.includes('taster') ||
|
||||
identityText.value.includes('品酒师') ||
|
||||
(roleNames.value.taster && identityText.value.includes(roleNames.value.taster))
|
||||
);
|
||||
});
|
||||
|
||||
const showIdentityBadges = computed(
|
||||
|
||||
在新工单中引用
屏蔽一个用户