Files
huangjingfen/pro_v3.5.1/app/services/agent/AgentLevelServices.php

574 lines
25 KiB
PHP
Raw Normal View History

<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\agent;
use app\dao\agent\AgentLevelDao;
use app\services\BaseServices;
use app\services\order\StoreOrderServices;
use app\services\user\UserBrokerageServices;
use app\services\user\UserExtractServices;
use app\services\user\UserServices;
use app\services\user\UserSpreadServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use FormBuilder\Factory\Iview;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Route as Url;
/**
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
* 分销等级(改造后同时作为 HJF 会员等级服务)
*
* PRD 改造说明:
* - 将原有的"分销员等级"概念替换为"会员等级"
* - 升级条件从"推广订单数/消费金额"改为"直推单数 + 伞下业绩单数"(通过 task type 6/7/8 实现)
* - 佣金计算从"按比例返佣"改为"按等级发放固定积分"(通过 direct_reward_points / umbrella_reward_points 实现)
*
* Class AgentLevelServices
* @package app\services\agent
* @mixin AgentLevelDao
*/
class AgentLevelServices extends BaseServices
{
/**
* @var AgentLevelDao
*/
#[Inject]
protected AgentLevelDao $dao;
/**
* 获取某一个等级信息
* @param int $id
* @param string $field
* @param array $with
* @return array|\think\Model|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getLevelInfo(int $id, string $field = '*', array $with = [])
{
return $this->dao->getOne(['id' => $id, 'is_del' => 0], $field, $with);
}
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
/**
* HJF 官方会员等级名称(与 database/hjf_migration.sql 插入数据一致)
* 用于区分 CRMEB 默认「等级一/等级二…」与 HJF 创客/云店…
*/
public const HJF_OFFICIAL_LEVEL_NAMES = ['创客', '云店', '服务商', '分公司'];
/**
* 一次查询并返回用户列表展示所需等级索引(供外部服务调用)
* 不暴露 dao 属性,避免外部直接访问 protected $dao
*
* @return array{byId: array<int,array>, byGradeAny: array<int,array>, byGradeOfficial: array<int,array>}
*/
public function loadHjfUserListLevelMaps(): array
{
$rows = $this->dao->getList(['is_del' => 0]);
return $this->buildHjfUserListLevelMaps($rows);
}
/**
* is_del=0 的等级行列表构建用户列表展示用索引(一次查询后复用)
*
* @param array<int,array<string,mixed>> $hjfLevelRows
* @return array{byId: array<int,array>, byGradeAny: array<int,array>, byGradeOfficial: array<int,array>}
*/
public function buildHjfUserListLevelMaps(array $hjfLevelRows): array
{
$byId = [];
$byGradeAny = [];
$byGradeOfficial = [];
$official = self::HJF_OFFICIAL_LEVEL_NAMES;
foreach ($hjfLevelRows as $hjfRow) {
$lid = (int)($hjfRow['id'] ?? 0);
if ($lid > 0) {
$byId[$lid] = $hjfRow;
}
$g = (int)($hjfRow['grade'] ?? 0);
if ($g > 0 && !isset($byGradeAny[$g])) {
// dao 已 order grade asc,id desc同 grade 先出现者为较大 id
$byGradeAny[$g] = $hjfRow;
}
$nm = (string)($hjfRow['name'] ?? '');
if ($g > 0 && in_array($nm, $official, true) && !isset($byGradeOfficial[$g])) {
$byGradeOfficial[$g] = $hjfRow;
}
}
return [
'byId' => $byId,
'byGradeAny' => $byGradeAny,
'byGradeOfficial' => $byGradeOfficial,
];
}
/**
* 用户列表等场景:解析应展示的等级行
*
* - agent_level 指向 CRMEB 默认行(如 id=2「等级二」)时,按 grade 改用 HJF 官方行(如「云店」)
* - id 已软删或误把 grade 写入 agent_level 时,按 byGradeAny 回退
*/
public function pickHjfLevelRowForUserListDisplay(int $agentLevelId, array $maps): ?array
{
if ($agentLevelId <= 0) {
return null;
}
$byId = $maps['byId'] ?? [];
$byGradeAny = $maps['byGradeAny'] ?? [];
$byGradeOfficial = $maps['byGradeOfficial'] ?? [];
$official = self::HJF_OFFICIAL_LEVEL_NAMES;
$row = $byId[$agentLevelId] ?? null;
if ($row === null) {
return $byGradeAny[$agentLevelId] ?? null;
}
$nm = (string)($row['name'] ?? '');
$g = (int)($row['grade'] ?? 0);
if ($g > 0 && !in_array($nm, $official, true) && isset($byGradeOfficial[$g])) {
return $byGradeOfficial[$g];
}
return $row;
}
/**
* 获取等级列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getLevelList(array $where)
{
$where['is_del'] = 0;
[$page, $limit] = $this->getPageValue();
$count = $this->dao->count($where);
$list = [];
if ($count) {
$list = $this->dao->getList($where, '*', ['task' => function ($query) {
$query->field('count(*) as sum');
}], $page, $limit);
$store_brokerage_ratio = sys_config('store_brokerage_ratio');
$store_brokerage_two = sys_config('store_brokerage_two');
foreach ($list as &$item) {
$item['one_brokerage_ratio'] = $this->compoteBrokerage($store_brokerage_ratio, $item['one_brokerage']);
$item['two_brokerage_ratio'] = $this->compoteBrokerage($store_brokerage_two, $item['two_brokerage']);
}
}
return compact('count', 'list');
}
/**
* 商城获取分销员等级列表
* @param int $uid
* @return array
*/
public function getUserlevelList(int $uid)
{
//商城分销是否开启
if (!sys_config('brokerage_func_status')) {
return [];
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$user = $userServices->getUserCacheInfo($uid);
if (!$user) {
throw new ValidateException('没有此用户');
}
try {
//检测升级
$this->checkUserLevelFinish($uid);
} catch (\Throwable $e) {
}
$list = $this->dao->getList(['is_del' => 0, 'status' => 1]);
$agent_level = $user['agent_level'] ?? 0;
//没等级默认最低等级
if (!$agent_level) {
$levelInfo = [];
} else {
$levelInfo = $this->getLevelInfo($agent_level) ?: [];
}
$sum_task = $finish_task = 0;
if ($levelInfo) {
/** @var AgentLevelTaskServices $levelTaskServices */
$levelTaskServices = app()->make(AgentLevelTaskServices::class);
$sum_task = $levelTaskServices->count(['level_id' => $levelInfo['id'], 'is_del' => 0, 'status' => 1]);
/** @var AgentLevelTaskRecordServices $levelTaskRecordServices */
$levelTaskRecordServices = app()->make(AgentLevelTaskRecordServices::class);
$finish_task = $levelTaskRecordServices->count(['level_id' => $levelInfo['id'], 'uid' => $uid]);
}
$levelInfo['sum_task'] = $sum_task;
$levelInfo['finish_task'] = $finish_task;
//推广订单总数
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$user['spread_order_count'] = $orderServices->count(['type' => 0, 'paid' => 1, 'refund_status' => [0, 3], 'is_del' => 0, 'is_system_del' => 0, 'spread_or_uid' => $uid]);
//冻结佣金和可提现金额
/** @var UserBrokerageServices $userBrokerageServices */
$userBrokerageServices = app()->make(UserBrokerageServices::class);
$user['broken_commission'] = $userBrokerageServices->getUserFrozenPrice($uid);
$user['commissionCount'] = bcsub($user['brokerage_price'], $user['broken_commission'], 2);
$user['commissionCount'] = max($user['commissionCount'], 0);
$whereType = [['type', 'in', ['self_brokerage', 'one_brokerage', 'two_brokerage', 'brokerage_user', 'division_brokerage', 'agent_brokerage', 'staff_brokerage']]];
$brokerage = $userBrokerageServices->sum($whereType + ['uid' => $uid, 'pm' => 1], 'number', true);
$refund = $userBrokerageServices->sum(['uid' => $uid, 'pm' => 0, 'type' => 'refund'], 'number', true);
$user['accumulate'] = bcsub((string)$brokerage, (string)$refund, 2);
//佣金排行
$user['position_count'] = $userBrokerageServices->getUserBrokerageRank($uid, 'week')['position'];
//推广人排行
$startTime = strtotime('this week Monday');
$endTime = time();
$field = 'spread_uid,count(uid) AS count,spread_time';
/** @var UserSpreadServices $userSpreadServices */
$userSpreadServices = app()->make(UserSpreadServices::class);
$rankList = $userSpreadServices->getAgentRankList([$startTime, $endTime], $field);
$rank = 0;
if ($rankList) {
$key = array_search($uid, array_column($rankList, 'spread_uid'));
if ($key !== false) {
$rank = (int)$key + 1;
}
}
foreach ($rankList as $key => $item) {
if ($item['spread_uid'] == $uid) $rank = $key + 1;
}
$user['rank_count'] = $rank;
$user['division_type'] = (int)sys_config('division_open') ? $user['division_type'] : 0;
$user['spread_count'] = $userServices->count(['spread_uid' => $uid]);
/** @var UserExtractServices $extractService */
$extractService = app()->make(UserExtractServices::class);
$user['extract_price'] = $extractService->sum(['uid' => $uid, 'status' => 1], 'extract_price');
$user['division_apply_open'] = (int)sys_config('division_apply_open');
if($user['agent_id'] == 0 && $user['division_type'] == 2){
$user['agent_id'] = $user['uid'];
}
return ['user' => $user, 'level_list' => $list, 'level_info' => $levelInfo];
}
/**
* 获取下一等级
* @param int $level_id
* @return array|\think\Model|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getNextLevelInfo(int $level_id = 0)
{
$grade = 0;
if ($level_id) {
$grade = $this->dao->value(['id' => $level_id, 'is_del' => 0, 'status' => 1], 'grade') ?: 0;
}
return $this->dao->getOne([['grade', '>', $grade], ['is_del', '=', 0], ['status', '=', 1]]);
}
/**
* 检测用户是否能升级
* @param int $uid
* @param array $uids
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkUserLevelFinish(int $uid, array $uids = [])
{
//商城分销是否开启
if (!sys_config('brokerage_func_status')) {
return false;
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserCacheInfo($uid);
if (!$userInfo) {
return false;
}
$list = $this->dao->getList(['is_del' => 0, 'status' => 1]);
if (!$list) {
return false;
}
if (!$uids) {
//获取上级uid 开启自购返回自己uid
$spread_uid = $userServices->getSpreadUid($uid, $userInfo);
$two_spread_uid = 0;
if ($spread_uid > 0 && $one_user_info = $userServices->getUserCacheInfo($spread_uid)) {
$two_spread_uid = $userServices->getSpreadUid($spread_uid, $one_user_info, false);
}
$uids = array_unique([$uid, $spread_uid, $two_spread_uid]);
}
foreach ($uids as $uid) {
if ($uid <= 0) continue;
if ($uid != $userInfo['uid']) {
$userInfo = $userServices->getUserCacheInfo($uid);
if (!$userInfo)
continue;
}
$now_grade = 0;
if ($userInfo['agent_level']) {
$now_grade = $this->dao->value(['id' => $userInfo['agent_level']], 'grade') ?: 0;
}
foreach ($list as $levelInfo) {
if (!$levelInfo || $levelInfo['grade'] <= $now_grade) {
continue;
}
/** @var AgentLevelTaskServices $levelTaskServices */
$levelTaskServices = app()->make(AgentLevelTaskServices::class);
$task_list = $levelTaskServices->getTaskList(['level_id' => $levelInfo['id'], 'is_del' => 0, 'status' => 1]);
if (!$task_list) {
continue;
}
foreach ($task_list as $task) {
$levelTaskServices->checkLevelTaskFinish($uid, (int)$task['id'], $task);
}
/** @var AgentLevelTaskRecordServices $levelTaskRecordServices */
$levelTaskRecordServices = app()->make(AgentLevelTaskRecordServices::class);
$ids = array_column($task_list, 'id');
$finish_task = $levelTaskRecordServices->count(['level_id' => $levelInfo['id'], 'uid' => $uid, 'task_id' => $ids]);
//任务完成升这一等级
if ($finish_task >= count($task_list)) {
$userServices->update($uid, ['agent_level' => $levelInfo['id']]);
} else {
break;
}
}
}
return true;
}
/**
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
* 分销等级上浮(保留兼容,普通商品分销仍使用原逻辑)
*
* 注意:报单商品的积分奖励已由 PointsRewardServices 通过 direct_reward_points/umbrella_reward_points 处理,
* 此方法仅用于普通商品的分销佣金计算。
*/
public function getAgentLevelBrokerage(int $uid, $userInfo = [])
{
$one_brokerage_up = $two_brokerage_up = $spread_uid = $spread_two_uid = 0;
$data = [$one_brokerage_up, $two_brokerage_up, $spread_uid, $spread_two_uid];
if (!$uid) {
return $data;
}
if (!sys_config('brokerage_func_status')) {
return $data;
}
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
if (!$userInfo) {
$userInfo = $userServices->getUserCacheInfo($uid);
}
if (!$userInfo) {
return $data;
}
$spread_uid = $userServices->getSpreadUid($uid, $userInfo);
$one_agent_level = 0;
$two_agent_level = 0;
if ($spread_uid > 0 && $one_user_info = $userServices->getUserInfo($spread_uid)) {
$one_agent_level = $one_user_info['agent_level'] ?? 0;
$spread_two_uid = $userServices->getSpreadUid($spread_uid, $one_user_info, false);
if ($spread_two_uid > 0 && $two_user_info = $userServices->getUserInfo($spread_two_uid)) {
$two_agent_level = $two_user_info['agent_level'] ?? 0;
}
}
$storeBrokerageRatio = sys_config('store_brokerage_ratio');
$storeBrokerageRatio = $one_agent_level ? bcadd($storeBrokerageRatio, bcmul($storeBrokerageRatio, bcdiv(($this->getLevelInfo($one_agent_level)['one_brokerage'] ?? 0), 100, 2), 2), 2) : $storeBrokerageRatio;
$storeBrokerageTwo = sys_config('store_brokerage_two');
$storeBrokerageTwo = $two_agent_level ? bcadd($storeBrokerageTwo, bcmul($storeBrokerageTwo, bcdiv(($this->getLevelInfo($two_agent_level)['two_brokerage'] ?? 0), 100, 2), 2), 2) : $storeBrokerageTwo;
return [$storeBrokerageRatio, $storeBrokerageTwo, $spread_uid, $spread_two_uid];
}
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
/**
* 根据 agent_level ID 获取等级 gradeHJF 会员等级数字 0-4
*
* @param int $agentLevelId eb_user.agent_level
* @return int grade0=普通会员, 1=创客, 2=云店, 3=服务商, 4=分公司)
*/
public function getGradeByLevelId(int $agentLevelId): int
{
if ($agentLevelId <= 0) {
return 0;
}
$levelInfo = $this->getLevelInfo($agentLevelId);
return (int)($levelInfo['grade'] ?? 0);
}
/**
* 根据 agent_level ID 获取直推奖励积分
*/
public function getDirectRewardPoints(int $agentLevelId): int
{
if ($agentLevelId <= 0) {
return 0;
}
$levelInfo = $this->getLevelInfo($agentLevelId);
return (int)($levelInfo['direct_reward_points'] ?? 0);
}
/**
* 根据 agent_level ID 获取伞下奖励积分
*/
public function getUmbrellaRewardPoints(int $agentLevelId): int
{
if ($agentLevelId <= 0) {
return 0;
}
$levelInfo = $this->getLevelInfo($agentLevelId);
return (int)($levelInfo['umbrella_reward_points'] ?? 0);
}
/**
* 根据 grade 获取 agent_level ID
*
* @param int $grade 等级数字 (1=创客, 2=云店, 3=服务商, 4=分公司)
* @return int agent_level ID找不到返回 0
*/
public function getLevelIdByGrade(int $grade): int
{
if ($grade <= 0) {
return 0;
}
return (int)$this->dao->value(['grade' => $grade, 'is_del' => 0, 'status' => 1], 'id') ?: 0;
}
/**
* 计算一二级返佣比率上浮
* @param $ratio
* @param $brokerage
* @return int|string
*/
public function compoteBrokerage($ratio, $brokerage)
{
if (!$ratio) {
return 0;
}
$brokerage = bcdiv((string)$brokerage, '100', 4);
if ($brokerage) {
return bcmul((string)$ratio, bcadd('1', (string)$brokerage, 4), 2);
}
return $brokerage;
}
/**
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
* 添加等级表单(改造后包含积分奖励字段)
*/
public function createForm()
{
$store_brokerage_ratio = sys_config('store_brokerage_ratio');
$store_brokerage_two = sys_config('store_brokerage_two');
$field[] = Form::input('name', '等级名称:')->col(24);
$field[] = Form::number('grade', '等级:', 0)->min(0)->precision(0);
$field[] = Form::frameImage('image', '背景图:', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')))->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true])->appendValidate(Iview::validateStr()->required()->message('请选择背景图'));
$field[] = Form::color('color', '字体颜色:')->required('请选择字体颜色');
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
$field[] = Form::number('direct_reward_points', '直推奖励积分:', 0)->info('该等级会员每直推1单报单商品获得的冻结积分数')->min(0);
$field[] = Form::number('umbrella_reward_points', '伞下奖励积分:', 0)->info('该等级会员伞下每入1单报单商品获得的冻结积分数级差基数')->min(0);
$field[] = Form::number('one_brokerage', '一级上浮:', 0)->info('在分销一级佣金基础上浮0-1000之间整数百分比目前一级返佣比率' . $store_brokerage_ratio . '%例如上浮10%,则返佣比率:一级返佣比率 * (1 + 一级上浮比率) = ' . $this->compoteBrokerage($store_brokerage_ratio, 10) . '%')->min(0)->max(1000);
$field[] = Form::number('two_brokerage', '二级上浮:', 0)->info('在分销二级佣金基础上浮0-1000之间整数百分比目前二级返佣比率' . $store_brokerage_two . '%例如上浮10%,则返佣比率:二级返佣比率 * (1 + 二级上浮比率) = ' . $this->compoteBrokerage($store_brokerage_two, 10) . '%')->min(0)->max(1000);
$field[] = Form::radio('status', '是否显示:', 1)->options([['value' => 1, 'label' => '显示'], ['value' => 0, 'label' => '隐藏']]);
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
return create_form('添加会员等级', $field, Url::buildUrl('/agent/level'), 'POST');
}
/**
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
* 获取修改等级表单(改造后包含积分奖励字段)
*/
public function editForm(int $id)
{
$store_brokerage_ratio = sys_config('store_brokerage_ratio');
$store_brokerage_two = sys_config('store_brokerage_two');
$levelInfo = $this->getLevelInfo($id);
if (!$levelInfo)
throw new AdminException('数据不存在');
$field = [];
$field[] = Form::hidden('id', $id);
$field[] = Form::input('name', '等级名称', $levelInfo['name'])->col(24);
$field[] = Form::number('grade', '等级', $levelInfo['grade'])->min(0)->precision(0);
$field[] = Form::frameImage('image', '背景图', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')), $levelInfo['image'])->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true])->appendValidate(Iview::validateStr()->required()->message('请选择背景图'));
$field[] = Form::color('color', '字体颜色', $levelInfo['color'] ?? '')->required('请选择字体颜色');
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
$field[] = Form::number('direct_reward_points', '直推奖励积分', $levelInfo['direct_reward_points'] ?? 0)->info('该等级会员每直推1单报单商品获得的冻结积分数')->min(0);
$field[] = Form::number('umbrella_reward_points', '伞下奖励积分', $levelInfo['umbrella_reward_points'] ?? 0)->info('该等级会员伞下每入1单报单商品获得的冻结积分数级差基数')->min(0);
$field[] = Form::number('one_brokerage', '一级上浮', $levelInfo['one_brokerage'])->info('在分销一级佣金基础上浮0-1000之间整数百分比目前一级返佣比率' . $store_brokerage_ratio . '%,上浮' . $levelInfo['one_brokerage'] . '%,则返佣比率:一级返佣比率 * (1 + 一级上浮比率) = ' . $this->compoteBrokerage($store_brokerage_ratio, $levelInfo['one_brokerage']) . '%')->min(0)->max(1000);
$field[] = Form::number('two_brokerage', '二级上浮', $levelInfo['two_brokerage'])->info('在分销二级佣金基础上浮0-1000之间整数百分比目前二级返佣比率' . $store_brokerage_two . '%,上浮' . $levelInfo['two_brokerage'] . '%,则返佣比率:二级返佣比率 * (1 + 二级上浮比率) = ' . $this->compoteBrokerage($store_brokerage_two, $levelInfo['two_brokerage']) . '%')->min(0)->max(1000);
$field[] = Form::radio('status', '是否显示', $levelInfo['status'])->options([['value' => 1, 'label' => '显示'], ['value' => 0, 'label' => '隐藏']]);
feat(hjf): H5路由修复、分销等级显示优化、个人中心等级徽章 H5 部署与路由: - manifest.json: router.base 改为 "/" 适配 public/ 根目录部署 - nginx-crmeb.conf: 恢复与 feature/fsgx 一致的原始配置 - App.vue: PC端重定向路径改为动态推导,修复死循环加载问题 - static/html/pc.html: 动态推导 H5 根路径,适配本地/云端两种部署 H5登录: - pages/users/login/index.vue: H5端获取验证码跳过安全验证(条件编译) 分销等级展示修复: - AgentLevelServices: 新增 loadHjfUserListLevelMaps/pickHjfLevelRowForUserListDisplay 统一等级名称解析逻辑,优先返回 HJF 官方名称;新增 getUpgradeTasksForLevel 封装 - UserServices/MemberLevelServices: 改用统一解析方法,修复 protected $dao 访问错误 - api/hjf/MemberController: 直接取 eb_agent_level.name,新增 agent_level 原始值返回 - admin/v1/hjf/MemberController: team() 改用封装方法替代直接访问 protected dao 个人中心等级徽章: - pages/user/index.vue + member/index.vue: memberInfo 沿链路透传 - member/template1.vue: UID右侧显示HjfMemberBadge,直接读 userInfo.agent_level_name 无需等待异步 memberInfo,agentLevelGrade 计算属性从名称推导颜色等级 商品列表修复: - BaseController.php/Common.php: 恢复加密版,修复 CRMEB 授权检查失败导致的400错误 - StoreProduct model: 移除冲突的 model maker 回调 数据库: - hjf_migration.sql: 完善会员等级体系迁移脚本 - eb_agent_level.sql: 新增等级初始数据脚本 Made-with: Cursor
2026-03-22 01:43:36 +08:00
return create_form('编辑会员等级', $field, Url::buildUrl('/agent/level/' . $id), 'PUT');
}
/**
* 赠送分销等级表单
* @param int $uid
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function levelForm(int $uid)
{
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
throw new AdminException('分销员不存在');
}
$levelList = $this->dao->getList(['is_del' => 0, 'status' => 1]);
$setOptionLabel = function () use ($levelList) {
$menus = [];
foreach ($levelList as $level) {
$menus[] = ['value' => $level['id'], 'label' => $level['name']];
}
return $menus;
};
$field[] = Form::hidden('uid', $uid);
$field[] = Form::select('id', '分销等级', $userInfo['agent_level'] ?? 0)->setOptions(Form::setOptions($setOptionLabel))->filterable(true);
return create_form('赠送分销等级', $field, Url::buildUrl('/agent/give_level'), 'post');
}
/**
* 赠送分销等级
* @param int $uid
* @param int $id
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function givelevel(int $uid, int $id)
{
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
throw new AdminException('分销员不存在');
}
$levelInfo = $this->getLevelInfo($id);
if (!$levelInfo) {
throw new AdminException('分销等级不存在');
}
if ($userServices->update($uid, ['agent_level' => $id]) === false) {
throw new AdminException('赠送失败');
}
return true;
}
}