Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods
Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口
Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则
Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)
Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口
Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单
Made-with: Cursor
279 lines
12 KiB
PHP
279 lines
12 KiB
PHP
<?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\wechat;
|
||
|
||
|
||
use app\dao\wechat\WechatCardDao;
|
||
use app\services\BaseServices;
|
||
use crmeb\exceptions\AdminException;
|
||
use crmeb\services\DownloadImageService;
|
||
use crmeb\services\FormBuilder;
|
||
use crmeb\services\SystemConfigService;
|
||
use crmeb\services\wechat\OfficialAccount;
|
||
use think\annotation\Inject;
|
||
use think\facade\Log;
|
||
|
||
/**
|
||
* 微信卡券
|
||
* Class WechatCardServices
|
||
* @package app\services\wechat
|
||
* @mixin WechatCardDao
|
||
*/
|
||
class WechatCardServices extends BaseServices
|
||
{
|
||
protected array $code_type = [
|
||
'CODE_TYPE_NONE' => '文本',
|
||
'CODE_TYPE_BARCODE' => '一维码',
|
||
'CODE_TYPE_QRCODE' => '二维码',
|
||
'CODE_TYPE_ONLY_QRCODE' => '仅显示二维码',
|
||
'CODE_TYPE_ONLY_BARCODE' => '仅显示一维码',
|
||
'CODE_TYPE_NONE' => '不显示任何码型'
|
||
];
|
||
|
||
/**
|
||
* @var FormBuilder
|
||
*/
|
||
#[Inject]
|
||
protected FormBuilder $builder;
|
||
|
||
/**
|
||
* @var WechatCardDao
|
||
*/
|
||
#[Inject]
|
||
protected WechatCardDao $dao;
|
||
|
||
|
||
/**
|
||
* 获取微信卡券列表
|
||
* @param array $where 查询条件
|
||
* @return array
|
||
*/
|
||
public function getList(array $where)
|
||
{
|
||
[$page, $limit] = $this->getPageValue();
|
||
$count = $this->dao->count($where);
|
||
$list = $this->dao->getList($where, $page, $limit);
|
||
return compact('count', $list);
|
||
}
|
||
|
||
/**
|
||
* 获取微信会员卡信息
|
||
* @return array
|
||
*/
|
||
public function getInfo()
|
||
{
|
||
if (!sys_config('wechat_appid') || !sys_config('wechat_appsecret')) {
|
||
throw new AdminException('请先配置公众号的appid、appSecret等参数,且需要开通微信卡券功能');
|
||
}
|
||
$data = [];
|
||
try {
|
||
$data['color'] = OfficialAccount::getCardColors()['colors'] ?? [];
|
||
} catch (\Throwable $e) {
|
||
$data['color'] = [];
|
||
}
|
||
if (!$data['color']) {
|
||
throw new AdminException('请先在公众号后台开通微信卡券功能');
|
||
}
|
||
$info = $this->dao->getOne(['card_type' => 'member_card', 'status' => 1, 'is_del' => 0]);
|
||
$data['info'] = ['custom_cell' => []];
|
||
$data['selet'] = 0;
|
||
if ($info) {
|
||
$info = $info->toArray();
|
||
$info['custom_cell'] = $info['especial']['custom_cell'] ?? [];
|
||
$data['info'] = $info;
|
||
$data['selet'] = $info['background_pic_url'] ? 1 : 0;
|
||
|
||
}
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 创建微信卡券表单
|
||
* @param array $formData 表单数据
|
||
* @return array
|
||
*/
|
||
public function createForm($formData)
|
||
{
|
||
$f[] = $this->builder->input('brand_name', '商户名称', $formData['brand_name'] ?? '')->required('请填写名称');
|
||
$f[] = $this->builder->input('title', '卡券名称', $formData['title'] ?? '')->required('请填写卡券名称');
|
||
$f[] = $this->builder->input('service_phone', '电话', $formData['service_phone'] ?? '')->required('请填写电话');
|
||
$f[] = $this->builder->select('code_type', 'CODE展示类型', $formData['code_type'])->setOptions(FormBuilder::setOptions($this->code_type))->multiple(true)->required('请选择CODE展示类型');
|
||
$f[] = $this->builder->input('color', '卡券颜色', $formData['color'] ?? '')->required('请填写卡券颜色');
|
||
$f[] = $this->builder->frameImage('logo_url', 'LOGO', $this->url('admin/widget.images/index', ['fodder' => 'logo_url'], true), $formData['logo_url'] ?? '')->info('建议300*300')->icon('ios-add')->width('960px')->height('550px')->modal(['footer-hide' => true]);
|
||
$f[] = $this->builder->frameImage('background_pic_url', '背景图', $this->url('admin/widget.images/index', ['fodder' => 'background_pic_url'], true), $formData['background_pic_url'] ?? '')->info('建议1000*600')->icon('ios-add')->width('960px')->height('550px')->modal(['footer-hide' => true]);
|
||
$f[] = $this->builder->input('notice', '提示', $formData['notice'] ?? '')->required('请填写提示信息');
|
||
$f[] = $this->builder->input('description', '描述', $formData['description'] ?? '')->required('请填写描述');
|
||
$f[] = $this->builder->textarea('prerogative', '卡券特权说明', $formData['notice'] ?? '')->info('会员卡特权说明,限制1024汉字')->required('请填写卡券特权说明');
|
||
return create_form('微信卡券添加', $f, $this->url('/wechat/card/0'), 'post');
|
||
}
|
||
|
||
/**
|
||
* 添加|编辑微信卡券
|
||
* @param int $id
|
||
* @param array $data
|
||
* @return bool
|
||
* @throws \think\db\exception\DataNotFoundException
|
||
* @throws \think\db\exception\DbException
|
||
* @throws \think\db\exception\ModelNotFoundException
|
||
*/
|
||
public function save(array $data)
|
||
{
|
||
$card = $this->dao->getOne(['card_type' => 'member_card', 'status' => 1, 'is_del' => 0]);
|
||
if ($card) {
|
||
[$cardInfo, $especial] = $this->wxCreate($data, $card['card_id']);
|
||
unset($data['custom_cell']);
|
||
$data['especial'] = $especial;
|
||
if (!$this->dao->update($card['id'], $data)) {
|
||
throw new AdminException('修改失败');
|
||
}
|
||
} else {
|
||
[$cardInfo, $especial] = $this->wxCreate($data);
|
||
$this->activateUserForm($cardInfo['card_id']);
|
||
unset($data['custom_cell']);
|
||
$data['card_id'] = $cardInfo['card_id'];
|
||
$data['especial'] = $especial;
|
||
if (!$this->dao->save($data)) {
|
||
throw new AdminException('添加失败');
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 微信卡券添加|编辑
|
||
* @param array $card
|
||
* @param string $card_id
|
||
* @param string $card_type
|
||
* @return mixed
|
||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
|
||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
|
||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||
*/
|
||
public function wxCreate(array $card, string $card_id = '', string $card_type = 'member_card')
|
||
{
|
||
if (!sys_config('wechat_appid') || !sys_config('wechat_appsecret')) {
|
||
throw new AdminException('请先配置公众号的appid、appSecret等参数,且需要开通微信卡券功能');
|
||
}
|
||
$baseUrl = sys_config('site_url');
|
||
try {
|
||
$logo = root_path() . 'public' . app()->make(DownloadImageService::class)->downloadImage($card['logo_url'])['path'];
|
||
$background = $card['background_pic_url'] ? (root_path() . 'public' . app()->make(DownloadImageService::class)->downloadImage($card['background_pic_url'])['path']) : '';
|
||
} catch (\Throwable $e) {
|
||
Log::error('添加会员卡券封面图出错误,原因:' . $e->getMessage());
|
||
$logo = root_path() . 'public' . $card['logo_url'];
|
||
$background = root_path() . 'public' . $card['background_pic_url'];
|
||
}
|
||
$base_info = [
|
||
'logo_url' => OfficialAccount::temporaryUpload($logo)['media_id'],
|
||
'brand_name' => $card['brand_name'],
|
||
'code_type' => 'CODE_TYPE_NONE',
|
||
'title' => $card['title'],
|
||
'color' => $card['color'] ?: 'Color010',
|
||
'notice' => $card['notice'],
|
||
'service_phone' => $card['service_phone'],
|
||
'description' => $card['description'],
|
||
'use_custom_code' => false,
|
||
'can_share' => true,
|
||
'can_give_friend' => false,
|
||
'get_limit' => 1,
|
||
'date_info' => ['type' => 'DATE_TYPE_PERMANENT'],
|
||
'sku' => ['quantity' => 50000000],
|
||
'promotion_url' => $baseUrl . '/pages/users/user_spread_user/index',
|
||
'promotion_url_name' => '推荐给朋友',
|
||
'promotion_url_sub_title' => ''
|
||
];
|
||
if ($card['center_title']) {
|
||
$base_info = array_merge($base_info, [
|
||
'center_title' => $card['center_title'],
|
||
'center_sub_title ' => $card['center_sub_title'],
|
||
'center_url' => $card['center_url'],
|
||
]);
|
||
}
|
||
$integral = SystemConfigService::more(['integral_max_num', 'integral_ratio', 'order_give_integral']);
|
||
$especial = [
|
||
// 'name' => $card['title'],//入口名称
|
||
// 'tips' => $card['description'],//入口提示语
|
||
// 'url' => $baseUrl,//入口跳转地址
|
||
'supply_bonus' => true,//显示积分
|
||
'bonus_rule' => [
|
||
'cost_money_unit' => 100,//消费金额。以分为单位
|
||
'increase_bonus' => $integral['order_give_integral'],//对应增加的积分
|
||
'max_increase_bonus' => $integral['integral_max_num'],//用户单次可获取的积分上限
|
||
'cost_bonus_unit' => 1,//每使用X积分
|
||
'reduce_money' => bcmul($integral['integral_ratio'], '100', 0),//抵扣xx元,(这里以分为单位)
|
||
],
|
||
'supply_balance' => false,//是否支持充值
|
||
// 'activate_url' => $baseUrl,
|
||
'bonus_url' => $baseUrl . '/pages/users/user_integral/index',//积分详情跳转链接
|
||
'prerogative' => $card['prerogative'],
|
||
// 'auto_activate' => false,
|
||
'wx_activate' => true,
|
||
'background_pic_url' => $background ? OfficialAccount::temporaryUpload($background)['media_id'] : '',
|
||
'custom_field2' => [
|
||
'name_type' => 'FIELD_NAME_TYPE_TIMS',//消费次数
|
||
'url' => $baseUrl . '/pages/users/order_list/index'
|
||
],
|
||
'custom_field3' => [
|
||
'name_type' => 'FIELD_NAME_TYPE_COUPON',//优惠券
|
||
'url' => $baseUrl . '/pages/users/user_coupon/index'
|
||
],
|
||
];
|
||
if ($card['custom_cell']) {
|
||
$cell_data = [];
|
||
$i = 1;
|
||
foreach ($card['custom_cell'] as $item) {
|
||
$cell_data['custom_cell' . $i] = $item;
|
||
$i++;
|
||
}
|
||
$especial = array_merge($especial, $cell_data);
|
||
}
|
||
@unlink($logo);
|
||
@unlink($background);
|
||
if ($card_id) {
|
||
unset($especial['bonus_rule']);
|
||
unset($base_info['brand_name'], $base_info['sku'], $base_info['use_custom_code']);
|
||
return [OfficialAccount::updateCard($card_id, $card_type, $base_info, $especial), array_merge($especial, ['custom_cell' => $card['custom_cell']])];
|
||
} else {
|
||
return [OfficialAccount::createCard($card_type, $base_info, $especial), array_merge($especial, ['custom_cell' => $card['custom_cell']])];
|
||
}
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* 创建会员卡激活表单
|
||
* @param string $cardId
|
||
* @return \EasyWeChat\Support\Collection
|
||
*/
|
||
public function activateUserForm(string $cardId)
|
||
{
|
||
$requireForm = [
|
||
'required_form' => [
|
||
'can_modify' => false,
|
||
'common_field_id_list' => [
|
||
'USER_FORM_INFO_FLAG_MOBILE'
|
||
]
|
||
]
|
||
];
|
||
$optionFrom = [
|
||
'optional_form' => [
|
||
'can_modify' => false,
|
||
'common_field_id_list' => [
|
||
'USER_FORM_INFO_FLAG_BIRTHDAY'
|
||
]
|
||
]
|
||
];
|
||
return OfficialAccount::cardActivateUserForm($cardId, $requireForm, $optionFrom);
|
||
}
|
||
|
||
}
|