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,88 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\pc;
use app\services\BaseServices;
use app\services\order\StoreCartServices;
use app\services\product\product\StoreProductServices;
use app\services\user\level\SystemUserLevelServices;
use app\services\user\member\MemberCardServices;
use app\services\user\UserServices;
class CartServices extends BaseServices
{
/**
* PC端购物车列表
* @param int $uid
* @return array[]
*/
public function getCartList(int $uid)
{
/** @var StoreCartServices $storeCartServices */
$storeCartServices = app()->make(StoreCartServices::class);
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$list = $storeCartServices->getCartList(['uid' => $uid, 'is_pay' => 0, 'is_new' => 0, 'is_del' => 0, 'type' => 0], 0, 0, ['productInfo', 'attrInfo']);
/** @var MemberCardServices $memberCardService */
$memberCardService = app()->make(MemberCardServices::class);
$vipStatus = $memberCardService->isOpenMemberCardCache('vip_price', false);
/** @var UserServices $user */
$user = app()->make(UserServices::class);
$userInfo = $user->getUserInfo($uid);
//用户等级是否开启
$discount = 100;
if (sys_config('member_func_status', 1)) {
/** @var SystemUserLevelServices $systemLevel */
$systemLevel = app()->make(SystemUserLevelServices::class);
$discount = $systemLevel->getDiscount($uid, (int)$userInfo['level']);
}
$valid = $invalid = [];
foreach ($list as &$item) {
$is_valid = $item['attrInfo']['suk'] ?? 0;
$item['productInfo']['attrInfo'] = $item['attrInfo'] ?? [];
$item['productInfo']['attrInfo']['image'] = $item['attrInfo']['image'] ?? $item['productInfo']['image'];
if (isset($item['productInfo']['attrInfo'])) {
$item['productInfo']['attrInfo'] = get_thumb_water($item['productInfo']['attrInfo']);
}
$item['productInfo'] = get_thumb_water($item['productInfo']);
$productInfo = $item['productInfo'];
$item['vip_truePrice'] = 0;
if (isset($productInfo['attrInfo']['product_id']) && $item['product_attr_unique']) {
$item['costPrice'] = $productInfo['attrInfo']['cost'] ?? 0;
$item['trueStock'] = $productInfo['attrInfo']['stock'] ?? 0;
$item['truePrice'] = $productInfo['attrInfo']['price'] ?? 0;
$item['sum_price'] = $productInfo['attrInfo']['price'] ?? 0;
[$truePrice, $vip_truePrice, $type] = $productServices->setLevelPrice($productInfo['attrInfo']['price'] ?? 0, $uid, $userInfo, $vipStatus, $discount, $productInfo['attrInfo']['vip_price'] ?? 0, $productInfo['is_vip'] ?? 0, true,['level_type'=>$productInfo['level_type'] ?? 1,'level_price'=>$productInfo['attrInfo']['level_price'] ?? '']);
} else {
$item['costPrice'] = $item['productInfo']['cost'] ?? 0;
$item['trueStock'] = $item['productInfo']['stock'] ?? 0;
$item['truePrice'] = $item['productInfo']['price'] ?? 0;
$item['sum_price'] = $item['productInfo']['price'] ?? 0;
[$truePrice, $vip_truePrice, $type] = $productServices->setLevelPrice($item['productInfo']['price'] ?? 0, $uid, $userInfo, $vipStatus, $discount, $item['productInfo']['vip_price'] ?? 0, $item['productInfo']['is_vip'] ?? 0);
}
$item['truePrice'] = $truePrice;
$item['vip_truePrice'] = $vip_truePrice;
$item['price_type'] = $type;
unset($item['attrInfo']);
if ($item['status'] == 1 && $is_valid && $item['trueStock'] > 0) {
$valid[] = $item;
} else {
$invalid[] = $item;
}
}
return ['valid' => $valid, 'invalid' => $invalid];
}
}

View File

@@ -0,0 +1,145 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\pc;
use app\services\BaseServices;
use app\services\user\UserServices;
use app\services\wechat\WechatUserServices;
use crmeb\services\CacheService;
use crmeb\services\wechat\OfficialAccount;
use think\exception\ValidateException;
class LoginServices extends BaseServices
{
/**
* 扫码登陆
* @param string $key
* @return array|int[]
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function scanLogin(string $key)
{
$hasKey = CacheService::has($key);
if ($hasKey === false) {
$status = 0;//不存在需要刷新二维码
} else {
$keyValue = CacheService::get($key);
if ($keyValue === 0) {
$status = 1;//正在扫描中
/** @var UserServices $user */
$user = app()->make(UserServices::class);
$userInfo = $user->get(['uniqid' => $key], ['account', 'uniqid']);
if ($userInfo) {
$tokenInfo = $this->authLogin($userInfo->account);
$tokenInfo['status'] = 3;
$userInfo->uniqid = '';
$userInfo->save();
CacheService::delete($key);
return $tokenInfo;
}
} else {
$status = 2;//没有扫描
}
}
return ['status' => $status];
}
/**
* 扫码登陆
* @param string $account
* @param string|null $password
* @return array
*/
public function authLogin(string $account, string $password = null)
{
/** @var UserServices $user */
$user = app()->make(UserServices::class);
$userInfo = $user->get(['account' => $account]);
if (!$userInfo) {
throw new ValidateException('没有此用户');
}
if ($password && !password_verify($password, $userInfo->pwd)) {
throw new ValidateException('账号或密码错误');
}
if (!$userInfo->status) {
throw new ValidateException('您已被禁止登录');
}
$uid = (int)$userInfo->uid;
$token = $this->createToken($uid, 'api', $userInfo->pwd ?? '');
$userInfo->update_time = time();
$userInfo->ip = request()->ip();
$userInfo->save();
// 用户登录成功事件
event('user.login', [$uid, app('request')->ip()]);
return [
'token' => $token['token'],
'exp_time' => $token['params']['exp'],
'userInfo' => $userInfo->toArray()
];
}
/**
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function wechatAuth()
{
/** @var OfficialAccount $service */
$service = app()->make(OfficialAccount::class);
$info = $service->setAccessEnd(OfficialAccount::PC)->userFromCode();
if (!$info) {
throw new ValidateException('授权失败');
}
$wechatInfo = $info;
if (!isset($wechatInfo['unionid'])) {
throw new ValidateException('unionid不存在');
}
if (!isset($wechatInfo['nickname'])) {
$wechatInfo = OfficialAccount::instance()->user()->get($wechatInfo['openid'])->toArray();
if (!isset($wechatInfo['nickname']))
throw new ValidateException('授权失败');
if (isset($wechatInfo['tagid_list']))
$wechatInfo['tagid_list'] = implode(',', $wechatInfo['tagid_list']);
} else {
if (isset($wechatInfo['privilege'])) unset($wechatInfo['privilege']);
/** @var WechatUserServices $wechatUser */
$wechatUser = app()->make(WechatUserServices::class);
if (!$wechatUser->getOne(['openid' => $wechatInfo['openid']])) {
$wechatInfo['subscribe'] = 0;
}
}
$wechatInfo['user_type'] = 'pc';
$openid = $wechatInfo['openid'];
/** @var WechatUserServices $wechatUserServices */
$wechatUserServices = app()->make(WechatUserServices::class);
$user = $wechatUserServices->getAuthUserInfo($openid, 'pc');
$createData = [$openid, $wechatInfo, 0, 'pc', 'pc'];
if (!$user) {
$user = $wechatUserServices->wechatOauthAfter($createData);
} else {
//更新用户信息
$wechatUserServices->wechatUpdata([$user['uid'], $wechatInfo]);
}
$token = $this->createToken((int)$user->uid, 'api', $user->pwd ?? '');
return [
'token' => $token['token'],
'exp_time' => $token['params']['exp']
];
}
}

View File

@@ -0,0 +1,53 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\pc;
use app\services\BaseServices;
use app\services\order\StoreOrderServices;
class OrderServices extends BaseServices
{
/**
* 轮询订单状态
* @param string $order_id
* @return bool
*/
public function checkOrderStatus(string $order_id)
{
/** @var StoreOrderServices $order */
$order = app()->make(StoreOrderServices::class);
$res = $order->count(['order_id' => $order_id, 'paid' => 1]);
if ($res) return true;
return false;
}
/**
* 获取订单列表
* @param array $where
* @param array|string[] $field
* @param array $with
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getOrderList(array $where, array $field = ['*'], array $with = [])
{
/** @var StoreOrderServices $order */
$order = app()->make(StoreOrderServices::class);
$data['list'] = $order->getOrderApiList($where, $field, $with);
$data['count'] = $order->count($where);
return $data;
}
}

View File

@@ -0,0 +1,106 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\pc;
use app\services\BaseServices;
use app\services\other\QrcodeServices;
use app\services\product\category\StoreProductCategoryServices;
use app\services\product\product\StoreProductServices;
use app\services\user\UserServices;
/**
* Class ProductServices
* @package app\services\pc
*/
class ProductServices extends BaseServices
{
/**
* PC端获取商品列表
* @param array $where
* @param int $uid
* @return mixed
*/
public function getProductList(array $where, int $uid)
{
/** @var StoreProductServices $product */
$product = app()->make(StoreProductServices::class);
$where['is_show'] = 1;
$where['is_del'] = 0;
$where['is_general_product'] = 1;
$where['is_vip_product'] = 0;
$where['product_type'] = [0, 1, 2, 3];
if ($uid) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$where['is_vip_product'] = $userServices->checkUserIsSvip($uid) ? -1 : 0;
}
$data['count'] = $product->getCount($where);
[$page, $limit] = $this->getPageValue();
$list = $product->getSearchList($where, $page, $limit, ['id,store_name,cate_id,image,IFNULL(sales, 0) + IFNULL(ficti, 0) as sales,price,stock,activity,ot_price,spec_type,recommend_image,unit_name,star'], '', ['couponId']);
foreach ($list as &$item) {
$item['presale_pay_status'] = $product->checkPresaleProductPay((int)$item['id'], $item);
}
$list = $product->getActivityList($list);
$data['list'] = get_thumb_water($product->getProduceOtherList($list, $uid, !!$where['status']), 'mid');
return $data;
}
/**
* PC端商品详情小程序码
* @param int $product_id
* @return bool|int|mixed|string
*/
public function getProductRoutineCode(int $product_id)
{
try {
$namePath = 'routine_product_' . $product_id . '.jpg';
/** @var QrcodeServices $QrcodeService */
$QrcodeService = app()->make(QrcodeServices::class);
//生成小程序地址
return $QrcodeService->getRoutineQrcodePath($product_id, 0, 0, $namePath);
} catch (\Exception $e) {
return '';
}
}
/**
* pc首页分类商品
* @param int $uid
* @return array
*/
public function getCategoryProduct(int $uid = 0)
{
/** @var StoreProductCategoryServices $category */
$category = app()->make(StoreProductCategoryServices::class);
/** @var StoreProductServices $product */
$product = app()->make(StoreProductServices::class);
[$page, $limit] = $this->getPageValue();
$list = $category->getCid($page, $limit);
$where = ['is_vip_product' => 0, 'is_general_product' => 1,'is_show' => 1, 'is_del' => 0, 'is_verify' => 1, 'product_type' => [0, 1, 2, 3]];
if ($uid) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$where['is_vip_product'] = $userServices->checkUserIsSvip($uid) ? -1 : 0;
}
$where['pid'] = 0;
foreach ($list as &$info) {
$productList = $product->getSearchList($where + ['cid' => $info['id']], 1, 8, ['id,store_name,image,IFNULL(sales, 0) + IFNULL(ficti, 0) as sales,price,ot_price,star']);
$info['productList'] = get_thumb_water($productList, 'mid');
}
$data['list'] = $list;
$data['count'] = $category->getCidCount();
return $data;
}
}