feat(fsgx): 完成全部24项开发任务 Phase1-7

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
This commit is contained in:
apple
2026-03-23 22:32:19 +08:00
parent 788ee0c0c0
commit 434aa8c69d
13098 changed files with 2008990 additions and 961 deletions

View File

@@ -0,0 +1,58 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
class Article extends Message
{
/**
* @var string
*/
protected string $type = 'mpnews';
/**
* Properties.
*
* @var array
*/
protected array $properties = [
'thumb_media_id',
'author',
'title',
'content',
'digest',
'source_url',
'show_cover',
];
/**
* Aliases of attribute.
*
* @var array
*/
protected array $jsonAliases = [
'content_source_url' => 'source_url',
'show_cover_pic' => 'show_cover',
];
/**
* @var array
*/
protected array $required = [
'thumb_media_id',
'title',
'content',
'show_cover',
];
}

View File

@@ -0,0 +1,249 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
use crmeb\services\wechat\WechatException;
use EasyWeChat\Kernel\Support\Arr;
use think\helper\Str;
trait HasAttributes
{
/**
* @var array
*/
protected array $attributes = [];
/**
* @var bool
*/
protected bool $snakeable = true;
/**
* Set Attributes.
*
* @param array $attributes
*
* @return HasAttributes
*/
public function setAttributes(array $attributes = []): self
{
$this->attributes = $attributes;
return $this;
}
/**
* Set attribute.
*
* @param string $attribute
* @param string $value
*
* @return $this
*/
public function setAttribute(string $attribute, string $value)
{
Arr::set($this->attributes, $attribute, $value);
return $this;
}
/**
* Get attribute.
*
* @param string $attribute
* @param mixed|null $default
*
* @return mixed
*/
public function getAttribute(string $attribute, mixed $default = null)
{
return Arr::get($this->attributes, $attribute, $default);
}
/**
* @param string $attribute
*
* @return bool
*/
public function isRequired(string $attribute)
{
return in_array($attribute, $this->getRequired(), true);
}
/**
* @return array|mixed
*/
public function getRequired(): mixed
{
return property_exists($this, 'required') ? $this->required : [];
}
/**
* Set attribute.
*
* @param string $attribute
* @param mixed $value
*
* @return $this
*/
public function with(string $attribute, mixed $value)
{
$this->snakeable && $attribute = Str::snake($attribute);
$this->setAttribute($attribute, $value);
return $this;
}
/**
* Override parent set() method.
*
* @param string $attribute
* @param mixed $value
*
* @return $this
*/
public function set(string $attribute, mixed $value)
{
$this->setAttribute($attribute, $value);
return $this;
}
/**
* Override parent get() method.
*
* @param string $attribute
* @param mixed|null $default
*
* @return mixed
*/
public function get(string $attribute, mixed $default = null)
{
return $this->getAttribute($attribute, $default);
}
/**
* @param string $key
*
* @return bool
*/
public function has(string $key)
{
return Arr::has($this->attributes, $key);
}
/**
* @param array $attributes
*
* @return $this
*/
public function merge(array $attributes)
{
$this->attributes = array_merge($this->attributes, $attributes);
return $this;
}
/**
* @param array|string $keys
*
* @return array
*/
public function only(array|string $keys)
{
return array_intersect_key($this->attributes, array_flip((array)$keys));
}
/**
* Return all items.
*
* @return array
*
*/
public function all()
{
$this->checkRequiredAttributes();
return $this->attributes;
}
/**
* Magic call.
*
* @param string $method
* @param array $args
*
* @return $this
*/
public function __call($method, $args)
{
if (0 === stripos($method, 'with')) {
return $this->with(substr($method, 4), array_shift($args));
}
throw new \BadMethodCallException(sprintf('Method "%s" does not exists.', $method));
}
/**
* Magic get.
*
* @param string $property
*
* @return mixed
*/
public function __get($property)
{
return $this->get($property);
}
/**
* Magic set.
*
* @param string $property
* @param mixed $value
*
* @return $this
*/
public function __set($property, $value)
{
return $this->with($property, $value);
}
/**
* Whether or not an data exists by key.
*
* @param string $key
*
* @return bool
*/
public function __isset($key)
{
return isset($this->attributes[$key]);
}
/**
* Check required attributes.
*
*/
protected function checkRequiredAttributes()
{
foreach ($this->getRequired() as $attribute) {
if (is_null($this->get($attribute))) {
throw new WechatException(sprintf('"%s" cannot be empty.', $attribute));
}
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
/**
* 图片
* Class Image
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/14
* @package crmeb\services\wechat\message
*/
class Image extends Media
{
/**
* 消息类型
* @var string
*/
protected string $type = 'image';
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
use think\helper\Str;
class Media extends Message
{
/**
* Properties.
*
* @var array
*/
protected array $properties = ['media_id'];
/**
* @var array
*/
protected array $required = [
'media_id',
];
/**
* MaterialClient constructor.
*
* @param string $mediaId
* @param string|null $type
* @param array $attributes
*/
public function __construct(string $mediaId, string $type = null, array $attributes = [])
{
parent::__construct(array_merge(['media_id' => $mediaId], $attributes));
!empty($type) && $this->setType($type);
}
/**
* @return string
*
*/
public function getMediaId(): string
{
$this->checkRequiredAttributes();
return $this->get('media_id');
}
public function toXmlArray()
{
return [
Str::studly($this->getType()) => [
'MediaId' => $this->get('media_id'),
],
];
}
}

View File

@@ -0,0 +1,217 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Support\XML;
/**
* 消息
* Class Message
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/14
* @package crmeb\services\wechat\message
*/
class Message implements MessageInterface
{
use HasAttributes;
public const TEXT = 2;
public const IMAGE = 4;
public const VOICE = 8;
public const VIDEO = 16;
public const SHORT_VIDEO = 32;
public const LOCATION = 64;
public const LINK = 128;
public const DEVICE_EVENT = 256;
public const DEVICE_TEXT = 512;
public const FILE = 1024;
public const TEXT_CARD = 2048;
public const TRANSFER = 4096;
public const EVENT = 1048576;
public const MINIPROGRAM_PAGE = 2097152;
public const MINIPROGRAM_NOTICE = 4194304;
public const ALL = self::TEXT | self::IMAGE | self::VOICE | self::VIDEO | self::SHORT_VIDEO | self::LOCATION | self::LINK
| self::DEVICE_EVENT | self::DEVICE_TEXT | self::FILE | self::TEXT_CARD | self::TRANSFER | self::EVENT
| self::MINIPROGRAM_PAGE | self::MINIPROGRAM_NOTICE;
/**
* @var string
*/
protected string $type;
/**
* @var int
*/
protected int $id;
/**
* @var string
*/
protected string $to;
/**
* @var string
*/
protected string $from;
/**
* @var array
*/
protected array $properties = [];
/**
* @var array
*/
protected array $jsonAliases = [];
/**
* Message constructor.
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
$this->setAttributes($attributes);
}
/**
* Return type name message.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @param string $type
*/
public function setType(string $type)
{
$this->type = $type;
}
/**
* Magic getter.
*
* @param string $property
*
* @return mixed
*/
public function __get($property)
{
if (property_exists($this, $property)) {
return $this->$property;
}
return $this->getAttribute($property);
}
/**
* Magic setter.
*
* @param string $property
* @param mixed $value
*
* @return Message
*/
public function __set($property, $value)
{
if (property_exists($this, $property)) {
$this->$property = $value;
} else {
$this->setAttribute($property, $value);
}
return $this;
}
/**
* @param array $appends
*
* @return array
*/
public function transformForJsonRequestWithoutType(array $appends = [])
{
return $this->transformForJsonRequest($appends, false);
}
/**
* @param array $appends
* @param bool $withType
*
* @return array
*
* @throws InvalidArgumentException
*/
public function transformForJsonRequest(array $appends = [], bool $withType = true): array
{
if (!$withType) {
return $this->propertiesToArray([], $this->jsonAliases);
}
$messageType = $this->getType();
$data = array_merge(['msgtype' => $messageType], $appends);
$data[$messageType] = array_merge($data[$messageType] ?? [], $this->propertiesToArray([], $this->jsonAliases));
return $data;
}
/**
* @param array $appends
* @param bool $returnAsArray
*
* @return string
*/
public function transformToXml(array $appends = [], bool $returnAsArray = false)
{
$data = array_merge(['MsgType' => $this->getType()], $this->toXmlArray(), $appends);
return $returnAsArray ? $data : XML::build($data);
}
/**
* @param array $data
* @param array $aliases
*
* @return array
*
* @throws InvalidArgumentException
*/
protected function propertiesToArray(array $data, array $aliases = []): array
{
$this->checkRequiredAttributes();
foreach ($this->attributes as $property => $value) {
if (is_null($value) && !$this->isRequired($property)) {
continue;
}
$alias = array_search($property, $aliases, true);
$data[$alias ?: $property] = $this->get($property);
}
return $data;
}
public function toXmlArray()
{
throw new \RuntimeException(sprintf('Class "%s" cannot support transform to XML message.', __CLASS__));
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
interface MessageInterface
{
/**
* @return string
*/
public function getType(): string;
/**
* @param array $appends
* @param bool $withType
* @return array
*/
public function transformForJsonRequest(array $appends = [], bool $withType = true): array;
/**
* @return string|array
*/
public function transformToXml();
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
class News extends Message
{
/**
* @var string
*/
protected string $type = 'news';
/**
* @var array
*/
protected array $properties = [
'items',
];
/**
* News constructor.
*
* @param array $items
*/
public function __construct(array $items = [])
{
parent::__construct(compact('items'));
}
/**
* @param array $data
* @param array $aliases
*
* @return array
*/
public function propertiesToArray(array $data, array $aliases = []): array
{
return ['articles' => array_map(function ($item) {
if ($item instanceof NewsItem) {
return $item->toJsonArray();
}
}, $this->get('items'))];
}
/**
* @return array
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/26
*/
public function toXmlArray(): array
{
$items = [];
foreach ($this->get('items') as $item) {
if ($item instanceof NewsItem) {
$items[] = $item->toXmlArray();
}
}
return [
'ArticleCount' => count($items),
'Articles' => $items,
];
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
/**
* Class NewsItem
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/26
* @package crmeb\services\wechat\message
*/
class NewsItem extends Message
{
/**
* Messages type.
*
* @var string
*/
protected string $type = 'news';
/**
* Properties.
*
* @var array
*/
protected array $properties = [
'title',
'description',
'url',
'image',
];
/**
* @return array
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/26
*/
public function toJsonArray(): array
{
return [
'title' => $this->get('title'),
'description' => $this->get('description'),
'url' => $this->get('url'),
'picurl' => $this->get('image'),
];
}
/**
* @return array
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/26
*/
public function toXmlArray(): array
{
return [
'Title' => $this->get('title'),
'Description' => $this->get('description'),
'Url' => $this->get('url'),
'PicUrl' => $this->get('image'),
];
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
/**
* Class Raw
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/26
* @package crmeb\services\wechat\message
*/
class Raw extends Message
{
/**
* @var string
*/
protected string $type = 'raw';
/**
* Properties.
*
* @var array
*/
protected array $properties = ['content'];
/**
* Constructor.
*
* @param string $content
*/
public function __construct(string $content)
{
parent::__construct(['content' => strval($content)]);
}
/**
* @param array $appends
* @param bool $withType
*
* @return array
*/
public function transformForJsonRequest(array $appends = [], bool $withType = true): array
{
return json_decode($this->content, true) ?? [];
}
public function __toString()
{
return $this->get('content') ?? '';
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
/**
* text消息
* Class Text
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/14
* @package crmeb\services\wechat\message
*/
class Text extends Message
{
/**
* Message type.
*
* @var string
*/
protected string $type = 'text';
/**
* Properties.
*
* @var array
*/
protected array $properties = ['content'];
/**
* Text constructor.
*
* @param string $content
*/
public function __construct(string $content = '')
{
parent::__construct(compact('content'));
}
/**
* @return array
*/
public function toXmlArray(): array
{
return [
'Content' => $this->get('content'),
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
/**
* 卡卷
* Class TextCard
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/14
* @package crmeb\services\wechat\message
*/
class TextCard extends Message
{
/**
* Messages type.
*
* @var string
*/
protected string $type = 'textcard';
/**
* Properties.
*
* @var array
*/
protected array $properties = [
'title',
'description',
'url',
];
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
class Transfer extends Message
{
/**
* Messages type.
*
* @var string
*/
protected string $type = 'transfer_customer_service';
/**
* Properties.
*
* @var array
*/
protected array $properties = [
'account',
];
/**
* Transfer constructor.
*
* @param string|null $account
*/
public function __construct(string $account = null)
{
parent::__construct(compact('account'));
}
/**
* @return array
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/26
*/
public function toXmlArray(): array
{
return empty($this->get('account')) ? [] : [
'TransInfo' => [
'KfAccount' => $this->get('account'),
],
];
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
class Video extends Media
{
/**
**
* Messages type.
*
* @var string
*/
protected string $type = 'video';
/**
* Properties.
*
* @var array
*/
protected array $properties = [
'title',
'description',
'media_id',
'thumb_media_id',
];
/**
* Video constructor.
*
* @param string $mediaId
* @param array $attributes
*/
public function __construct(string $mediaId, array $attributes = [])
{
parent::__construct($mediaId, 'video', $attributes);
}
/**
* @return array[]
* @author 等风来
* @email 136327134@qq.com
* @date 2023/9/26
*/
public function toXmlArray(): array
{
return [
'Video' => [
'MediaId' => $this->get('media_id'),
'Title' => $this->get('title'),
'Description' => $this->get('description'),
],
];
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\message;
class Voice extends Media
{
/**
* Messages type.
*
* @var string
*/
protected string $type = 'voice';
/**
* Properties.
*
* @var array
*/
protected array $properties = [
'media_id',
'recognition',
];
}