Agent.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. <?php
  2. namespace app\api\controller\commission;
  3. use think\Db;
  4. use app\common\model\User as UserModel;
  5. use app\common\model\commission\Agent as AgentModel;
  6. use app\common\model\commission\Reward as RewardModel;
  7. use app\common\model\commission\Apply as ApplyModel;
  8. use app\common\model\Goods as GoodsModel;
  9. use app\common\Service\Wallet;
  10. use app\common\Enum\AgentType;
  11. use app\common\library\BcMath;
  12. use app\common\Enum\PageTypeEnum;
  13. use app\common\Service\Third\Wechat\Wechat as WechatService;
  14. use app\common\Enum\ChannelEnum;
  15. use app\common\Enum\ShareEnum;
  16. use app\common\Service\ShopConfigService;
  17. class Agent extends Commission
  18. {
  19. protected $noNeedLogin = [];
  20. protected $noNeedRight = ['*'];
  21. // 分销商详情
  22. public function index()
  23. {
  24. $status = $this->service->getAgentStatus(true);
  25. $condition = [
  26. 'type' => '',
  27. 'value' => ''
  28. ];
  29. switch ($status) {
  30. case AgentModel::AGENT_STATUS_NULL:
  31. $condition = $this->service->config->getBecomeAgentEvent();
  32. if ($condition['type'] === 'goods') {
  33. $condition['value'] = GoodsModel::show()->whereIn('id', $condition['value'])->select();
  34. }
  35. $this->error('', $condition, 100);
  36. break;
  37. case AgentModel::AGENT_STATUS_NEEDINFO:
  38. $this->error('待完善信息,请补充您的资料后提交审核', $condition, 103);
  39. break;
  40. case AgentModel::AGENT_STATUS_PENDING:
  41. $this->error('正在审核中,请耐心等候结果', $condition, 104);
  42. break;
  43. case AgentModel::AGENT_STATUS_REJECT:
  44. $agentFormStatus = $this->service->config->isAgentApplyForm();
  45. if ($agentFormStatus) {
  46. $this->error('抱歉!您的申请信息未通过,请尝试修改后重新提交', $condition, 105);
  47. } else {
  48. $this->error('抱歉!您的申请未通过,请尝试重新申请', $condition, 106);
  49. }
  50. break;
  51. case AgentModel::AGENT_STATUS_FREEZE:
  52. $this->error('抱歉!您的账户已被冻结,如有疑问请联系客服', $condition, 107);
  53. break;
  54. }
  55. $data = $this->service->agent;
  56. // 增强代理商数据信息
  57. if ($data) {
  58. $data = $data->toArray();
  59. // 添加代理商类型描述(使用枚举类)
  60. $data['agent_type_text'] = AgentType::getTypeText($data['agent_type']);
  61. // 构造代理商身份描述和区域信息
  62. $identityData = $this->buildAgentIdentityData($data);
  63. $data['agent_identity_title'] = $identityData['title'];
  64. $data['manage_area'] = $identityData['area_info'];
  65. $data['manage_area_text'] = $identityData['area_text'];
  66. // 获取用户基本信息和佣金数据
  67. $user = $this->service->user;
  68. if ($user) {
  69. $data['user_info'] = [
  70. 'nickname' => $user->nickname,
  71. 'avatar' => cdnurl($user->avatar), // 转换为完整CDN URL
  72. 'username' => $user->username,
  73. ];
  74. }
  75. $data['commission'] = $this->service->user->commission ?? 0;
  76. // 获取代理商申请信息
  77. $applyInfo = ApplyModel::where('user_id', $this->service->user->id)
  78. ->order('id desc')
  79. ->find();
  80. $data['apply_info'] = $applyInfo;
  81. }
  82. $this->success('分销商信息', $data);
  83. }
  84. /**
  85. * 构造代理商身份数据(包括标题和区域信息)
  86. * 优化版本:直接使用数据库中存储的名称字段,无需关联查询区域表
  87. * @param array $agentData 代理商数据
  88. * @return array
  89. */
  90. private function buildAgentIdentityData($agentData)
  91. {
  92. $result = [
  93. 'title' => '普通代理商',
  94. 'area_info' => null,
  95. 'area_text' => '-'
  96. ];
  97. switch ($agentData['agent_type']) {
  98. case AgentType::NORMAL:
  99. $result['title'] = '普通代理商';
  100. break;
  101. case AgentType::PROVINCE:
  102. // 省级代理商:山东省代理商
  103. $provinceName = $agentData['manage_province_name'] ?? '';
  104. if (!empty($provinceName)) {
  105. $result['title'] = $provinceName . '代理商';
  106. $result['area_info'] = [
  107. 'province_name' => $provinceName,
  108. 'province_id' => $agentData['manage_province_id'] ?? null
  109. ];
  110. $result['area_text'] = $provinceName;
  111. } else {
  112. $result['title'] = '省级代理商';
  113. }
  114. break;
  115. case AgentType::CITY:
  116. // 市级代理商:临沂市代理商
  117. $areaInfo = [];
  118. $areaText = [];
  119. // 获取省份信息
  120. $provinceName = $agentData['manage_province_name'] ?? '';
  121. if (!empty($provinceName)) {
  122. $areaInfo['province_name'] = $provinceName;
  123. $areaInfo['province_id'] = $agentData['manage_province_id'] ?? null;
  124. $areaText[] = $provinceName;
  125. }
  126. // 获取城市信息
  127. $cityName = $agentData['manage_city_name'] ?? '';
  128. if (!empty($cityName)) {
  129. $result['title'] = $cityName . '代理商';
  130. $areaInfo['city_name'] = $cityName;
  131. $areaInfo['city_id'] = $agentData['manage_city_id'] ?? null;
  132. $areaText[] = $cityName;
  133. } else {
  134. $result['title'] = '市级代理商';
  135. }
  136. if (!empty($areaInfo)) {
  137. $result['area_info'] = $areaInfo;
  138. $result['area_text'] = implode('-', $areaText);
  139. }
  140. break;
  141. case AgentType::DISTRICT:
  142. // 区级代理商:兰山区代理商
  143. $areaInfo = [];
  144. $areaText = [];
  145. // 获取省份信息
  146. $provinceName = $agentData['manage_province_name'] ?? '';
  147. if (!empty($provinceName)) {
  148. $areaInfo['province_name'] = $provinceName;
  149. $areaInfo['province_id'] = $agentData['manage_province_id'] ?? null;
  150. $areaText[] = $provinceName;
  151. }
  152. // 获取城市信息
  153. $cityName = $agentData['manage_city_name'] ?? '';
  154. if (!empty($cityName)) {
  155. $areaInfo['city_name'] = $cityName;
  156. $areaInfo['city_id'] = $agentData['manage_city_id'] ?? null;
  157. $areaText[] = $cityName;
  158. }
  159. // 获取区域信息
  160. $districtName = $agentData['manage_district_name'] ?? '';
  161. if (!empty($districtName)) {
  162. $result['title'] = $districtName . '代理商';
  163. $areaInfo['district_name'] = $districtName;
  164. $areaInfo['district_id'] = $agentData['manage_district_id'] ?? null;
  165. $areaText[] = $districtName;
  166. } else {
  167. $result['title'] = '区域代理商';
  168. }
  169. if (!empty($areaInfo)) {
  170. $result['area_info'] = $areaInfo;
  171. $result['area_text'] = implode('-', $areaText);
  172. }
  173. break;
  174. }
  175. return $result;
  176. }
  177. // 我的团队
  178. public function team()
  179. {
  180. $params = $this->request->param();
  181. // 使用验证器验证分页参数:page, page_size, time_filter, start_date, end_date
  182. $validate = new \app\api\validate\Agent();
  183. if (!$validate->scene('team')->check($params)) {
  184. $this->error($validate->getError());
  185. }
  186. $agentId = $this->service->user->id;
  187. // 获取验证后的参数
  188. $pageSize = isset($params['page_size']) ? (int)$params['page_size'] : 8;
  189. $timeFilter = isset($params['time_filter']) ? $params['time_filter'] : 'all';
  190. $startDate = isset($params['start_date']) ? $params['start_date'] : '';
  191. $endDate = isset($params['end_date']) ? $params['end_date'] : '';
  192. // 构建查询条件数组
  193. $whereConditions = [
  194. 'parent_user_id' => $agentId,
  195. 'status' => 1
  196. ];
  197. // 根据时间筛选参数添加时间条件
  198. if ($timeFilter !== 'all') {
  199. $timeConditions = $this->getTimeFilterConditions($timeFilter, $startDate, $endDate);
  200. if ($timeConditions) {
  201. $whereConditions['bind_time'] = ['>=', $timeConditions['start_time']];
  202. if (isset($timeConditions['end_time'])) {
  203. // 使用between条件更简洁
  204. $whereConditions['bind_time'] = ['between', [$timeConditions['start_time'], $timeConditions['end_time']]];
  205. }
  206. }
  207. }
  208. $data = UserModel::where($whereConditions)->field('id,username,nickname,avatar,mobile,bind_time,commission,order_count,total_consume,status,parent_user_id')
  209. ->with(['agent' => function ($query) {
  210. return $query->with('level_info')->field('user_id,agent_type,child_user_count_all,child_order_money_all');
  211. }])
  212. ->paginate($pageSize);
  213. // 将分页对象转换为数组进行处理
  214. $paginateData = $data->toArray();
  215. // 为代理商成员添加身份标题和统计数据
  216. $this->addAgentIdentityTitle($paginateData['data']);
  217. // 获取当前代理商信息
  218. $currentAgent = $this->service->agent;
  219. // 重新构造返回数据
  220. $result = [
  221. 'total' => $paginateData['total'],
  222. 'per_page' => $paginateData['per_page'],
  223. 'current_page' => $paginateData['current_page'],
  224. 'last_page' => $paginateData['last_page'],
  225. 'list' => $paginateData['data'],
  226. 'agent_info' => [
  227. 'child_user_count_all' => intval($currentAgent['child_user_count_all'] ?? 0),
  228. 'child_order_money_all' => BcMath::format($currentAgent['child_order_money_all'] ?? '0.00')
  229. ]
  230. ];
  231. $this->success("", $result);
  232. }
  233. /**
  234. * 为团队成员添加身份标题和统计数据(统一处理所有用户)
  235. * @param array $teamMembers 团队成员数据数组的引用
  236. * @return void
  237. */
  238. private function addAgentIdentityTitle(&$teamMembers)
  239. {
  240. if (empty($teamMembers)) {
  241. return;
  242. }
  243. foreach ($teamMembers as &$member) {
  244. //处理头像
  245. $member['avatar'] = !empty($member['avatar']) ? cdnurl($member['avatar']) : '';
  246. //格式化绑定时间
  247. $member['bind_time_text'] = !empty($member['bind_time']) ? date('Y-m-d', $member['bind_time']) : '';
  248. if (!empty($member['agent'])) {
  249. // 代理商用户:构造具体的代理商身份标题
  250. $identityData = $this->buildAgentIdentityData($member['agent']);
  251. $member['agent_identity_title'] = $identityData['title'];
  252. // 添加代理商统计数据
  253. $member['order_count'] = intval($member['agent']['child_user_count_all'] ?? 0);
  254. $member['total_consume'] = BcMath::format($member['agent']['child_order_money_all'] ?? '0.00');
  255. } else {
  256. // 普通用户:统一设置为"普通用户"
  257. $member['agent_identity_title'] = '普通用户';
  258. // 添加普通用户统计数据
  259. $member['order_count'] = intval($member['order_count'] ?? 0);
  260. $member['total_consume'] = BcMath::format($member['total_consume'] ?? '0.00');
  261. }
  262. }
  263. }
  264. /**
  265. * 获取时间筛选条件
  266. * @param string $timeFilter 时间筛选类型
  267. * @param string $startDate 开始日期 (Y-m-d格式,用于自定义时间段)
  268. * @param string $endDate 结束日期 (Y-m-d格式,用于自定义时间段)
  269. * @return array|null 返回包含开始时间和结束时间的数组
  270. */
  271. private function getTimeFilterConditions($timeFilter, $startDate = '', $endDate = '')
  272. {
  273. $currentTime = time();
  274. switch ($timeFilter) {
  275. case 'recent_7_days':
  276. return [
  277. 'start_time' => $currentTime - 7 * 24 * 3600,
  278. ];
  279. case 'recent_30_days':
  280. return [
  281. 'start_time' => $currentTime - 30 * 24 * 3600,
  282. ];
  283. case 'recent_90_days':
  284. return [
  285. 'start_time' => $currentTime - 90 * 24 * 3600,
  286. ];
  287. case 'current_year':
  288. // 当前年份的开始时间
  289. $yearStart = strtotime(date('Y-01-01 00:00:00'));
  290. // 当前年份的结束时间
  291. $yearEnd = strtotime(date('Y-12-31 23:59:59'));
  292. return [
  293. 'start_time' => $yearStart,
  294. 'end_time' => $yearEnd,
  295. ];
  296. case 'custom':
  297. // 自定义时间段
  298. if (empty($startDate) || empty($endDate)) {
  299. return null;
  300. }
  301. $startTime = strtotime($startDate . ' 00:00:00');
  302. $endTime = strtotime($endDate . ' 23:59:59');
  303. if ($startTime === false || $endTime === false || $startTime > $endTime) {
  304. return null;
  305. }
  306. return [
  307. 'start_time' => $startTime,
  308. 'end_time' => $endTime,
  309. ];
  310. default:
  311. return null;
  312. }
  313. }
  314. // 佣金转余额/提现
  315. public function transfer()
  316. {
  317. $amount = $this->request->param('amount');
  318. if ($amount <= 0) {
  319. $this->error('请输入正确的金额');
  320. }
  321. $agent = $this->service->agent;
  322. if (!$agent) {
  323. $this->error('您还不是分销商');
  324. }
  325. // 使用BcMath工具类检查可提现余额(累计收益 - 已提现金额)
  326. $totalIncome = $agent->total_income ?? '0.00';
  327. $withdrawnAmount = $agent->withdrawn_amount ?? '0.00';
  328. $requestAmount = BcMath::format($amount);
  329. $availableBalance = BcMath::sub($totalIncome, $withdrawnAmount);
  330. // 使用BcMath比较函数检查余额是否足够
  331. if (BcMath::comp($requestAmount, $availableBalance) > 0) {
  332. $this->error('提现金额超过可用余额,可用余额:' . $availableBalance . '元');
  333. }
  334. Db::transaction(function () use ($amount, $agent) {
  335. $user = auth_user();
  336. // 转账到用户余额
  337. Wallet::change($user, 'money', $amount, 'commission_withdraw');
  338. // 更新代理商的已提现金额
  339. AgentModel::where('user_id', $user->id)->setInc('withdrawn_amount', $amount);
  340. });
  341. $this->success('提现成功');
  342. }
  343. /**
  344. * 生成分销海报微信小程序码
  345. */
  346. public function posterQrcode()
  347. {
  348. // 验证当前用户是否为分销商
  349. $agent = $this->service->agent;
  350. if (!$agent) {
  351. $this->error('您还不是分销商');
  352. }
  353. // 获取当前用户ID作为分享者
  354. $shareUserId = $this->service->user->id;
  355. // 从请求头获取platform参数
  356. $requestPlatform = $this->request->header('platform', '');
  357. if (!$requestPlatform) {
  358. $this->error('缺少platform参数');
  359. }
  360. // 将ChannelEnum映射到ShareEnum平台常量
  361. $shareEnumPlatform = $this->mapChannelToSharePlatform($requestPlatform);
  362. // 使用ShareEnum获取平台ID (1=H5,2=微信公众号网页,3=微信小程序,4=App)
  363. $platformId = ShareEnum::getPlatformId($shareEnumPlatform);
  364. $fromPlatformId = $platformId; // 来源平台同当前平台
  365. $commissionConfig = ShopConfigService::getConfigs('shop.commission');
  366. // try {
  367. // 初始化微信服务 - 固定使用微信小程序平台生成小程序码
  368. $wechatPlatform = ChannelEnum::CHANNEL_WECHAT_MINI_PROGRAM;
  369. $payload = [];
  370. $wechat = new WechatService($wechatPlatform, $payload);
  371. $mp = $wechat->getApp();
  372. // 构造spm参数: shareId.page.query.platform.from
  373. // shareId: 分享者用户ID
  374. // page: 页面类型 (分销海报页面 = 6)
  375. // query: 代理商ID (使用分享者ID)
  376. // platform: 从请求头获取的平台ID
  377. // from: 来源平台ID
  378. $spmParams = sprintf('%s.%s.%s.%s.%s',
  379. $shareUserId,
  380. PageTypeEnum::AGENT_POSTER,
  381. $shareUserId,
  382. $platformId, // 使用映射后的平台ID
  383. $fromPlatformId // 来源平台ID
  384. );
  385. // 固定跳转到分销海报页面,携带smp参数和邀请码
  386. $page = 'pages/index/index'; // 分销海报页面路径
  387. //$scene = 'spm=' . $spmParams . '&invite_code=' . $agent->invite_code;
  388. $scene = 'invite_code=' . $agent->invite_code;
  389. // 生成小程序码
  390. $content = $mp->app_code->getUnlimit($scene, [
  391. 'page' => $page,
  392. 'is_hyaline' => false,
  393. 'env_version' => 'trial',
  394. 'check_path' => false
  395. ]);
  396. if ($content instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
  397. // 将小程序码转换为base64
  398. $qrcodeBase64 = base64_encode($content->getBody());
  399. // 构建返回数据
  400. $result = [
  401. 'qrcode_base64' => 'data:image/png;base64,' . $qrcodeBase64,
  402. 'invite_code' =>$agent->invite_code? $agent->invite_code : '',
  403. 'poster_background' => !empty($commissionConfig['poster_background_image']) ? cdnurl($commissionConfig['poster_background_image'], true) : '',
  404. ];
  405. $this->success('生成成功', $result);
  406. } else {
  407. // 小程序码获取失败
  408. $msg = $content['errcode'] ?? '-';
  409. $msg .= $content['errmsg'] ?? '';
  410. $this->error('小程序码生成失败:' . $msg);
  411. }
  412. // } catch (\Exception $e) {
  413. // $this->error('小程序码生成失败:' . $e->getMessage());
  414. // }
  415. }
  416. /**
  417. * 将ChannelEnum的平台常量映射到ShareEnum的平台常量
  418. * @param string $channelPlatform ChannelEnum平台常量
  419. * @return string ShareEnum平台常量
  420. */
  421. private function mapChannelToSharePlatform($channelPlatform)
  422. {
  423. $channelToShareMap = [
  424. ChannelEnum::CHANNEL_H5 => ShareEnum::PLATFORM_H5,
  425. ChannelEnum::CHANNEL_WECHAT_OFFICIAL_ACCOUNT => ShareEnum::PLATFORM_WECHAT_OFFICIAL_ACCOUNT,
  426. ChannelEnum::CHANNEL_WECHAT_MINI_PROGRAM => ShareEnum::PLATFORM_WECHAT_MINI_PROGRAM,
  427. ChannelEnum::CHANNEL_IOS_APP => ShareEnum::PLATFORM_APP,
  428. ChannelEnum::CHANNEL_ANDROID_APP => ShareEnum::PLATFORM_APP,
  429. ];
  430. return $channelToShareMap[$channelPlatform] ?? ShareEnum::PLATFORM_WECHAT_MINI_PROGRAM; // 默认微信小程序
  431. }
  432. }