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
This commit is contained in:
@@ -14,20 +14,23 @@
|
||||
|
||||
### 1.2 技术底座说明
|
||||
|
||||
|
||||
| 维度 | 说明 |
|
||||
|---|---|
|
||||
| ---- | ---------------------------------- |
|
||||
| 基础系统 | CRMEB Pro v3.5 会员电商系统 |
|
||||
| 后端框架 | ThinkPHP 8 + Swoole 4 + Redis |
|
||||
| 前端框架 | uni-app (Vue 3) + iView Admin (后台) |
|
||||
| 数据库 | MySQL 8.0 |
|
||||
| 数据库 | MySQL 5.7 |
|
||||
| 消息队列 | think-queue (Redis驱动) |
|
||||
| 定时任务 | Swoole Timer / Linux Crontab |
|
||||
| 小程序端 | 微信小程序 + H5 |
|
||||
|
||||
|
||||
### 1.3 术语定义
|
||||
|
||||
|
||||
| 术语 | 定义 |
|
||||
|---|---|
|
||||
| ----- | ---------------------------------- |
|
||||
| 公排池 | 所有购买报单商品的订单按付款时间顺序进入的全局排队队列 |
|
||||
| 进四退一 | 默认每进入4单触发退还最早入队第1单的购买款项(数量后台可配置) |
|
||||
| 报单商品 | 参与公排机制的指定商品,当前主要为3600元黄精粉套餐 |
|
||||
@@ -42,6 +45,7 @@
|
||||
| 分公司 | 伞下业绩1000单后自动升级的会员等级(至少3个直推) |
|
||||
| 级差 | 上级享受的积分奖励为下级等级对应奖励与该下级自身等级所扣除部分的差额 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2. 产品概述
|
||||
@@ -52,22 +56,26 @@
|
||||
|
||||
### 2.2 产品定位
|
||||
|
||||
|
||||
| 维度 | 描述 |
|
||||
|---|---|
|
||||
| ----- | -------------------------------- |
|
||||
| 产品类型 | 微信小程序 + PC管理后台(基于CRMEB Pro v3.5) |
|
||||
| 核心商品 | 黄精粉套餐(3600元/单)及周边健康产品 |
|
||||
| 商业模式 | 社交裂变电商 + 公排返利 + 会员积分分销 |
|
||||
| 目标市场 | 健康消费意识强、具备社交传播意愿的中青年用户群体 |
|
||||
| 核心差异化 | 公排退款机制降低用户试错成本,积分分级奖励激励持续推广 |
|
||||
|
||||
|
||||
### 2.3 CRMEB Pro 功能复用与改造策略
|
||||
|
||||
|
||||
| 策略 | 涉及模块 |
|
||||
|---|---|
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| **直接复用** | 微信登录/手机号授权、商品CRUD及上下架、订单管理及状态流转、微信支付/支付宝支付、优惠券管理、Banner/文章/公告管理、首页DIY装修、后台权限管理、数据统计看板、活动管理及核销、用户管理及标签 |
|
||||
| **改造复用** | 分销推荐关系绑定 → 加入公排关联、团队分销等级 → 改为五级会员等级体系、分销佣金冻结 → 改为积分待释放/按日释放、余额账户 → 增加公排退款入口、商品分类 → 增加报单商品标记、提现功能 → 调整手续费计算逻辑 |
|
||||
| **全新开发** | 公排池引擎(进N退1)、积分每日释放定时任务、级差计算引擎、伞下业绩统计(含级别隔离)、公排状态展示页面 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心业务逻辑
|
||||
@@ -100,19 +108,22 @@
|
||||
|
||||
#### 3.2.1 等级定义与升级条件
|
||||
|
||||
|
||||
| 等级 | 升级条件 | 直推奖励(积分/单) | 伞下奖励(积分/单) | 备注 |
|
||||
|---|---|---|---|---|
|
||||
| ---- | ---------------- | ---------- | ---------- | -------- |
|
||||
| 普通会员 | 注册即获得 | — | — | 可参与公排 |
|
||||
| 创客 | 直推3单 | 500 | — | 直推单数可配置 |
|
||||
| 云店 | 伞下业绩30单(至少3直推) | 800 | 300 | 伞下云店分离计算 |
|
||||
| 服务商 | 伞下业绩100单(至少3直推) | 1000 | 200 | — |
|
||||
| 分公司 | 伞下业绩1000单(至少3直推) | 1300 | 300 | — |
|
||||
|
||||
|
||||
> 所有奖励数值和升级门槛均支持后台配置(复用 CRMEB 系统配置表)
|
||||
|
||||
#### 3.2.2 改造说明
|
||||
|
||||
基于 CRMEB Pro 的团队分销等级功能进行改造:
|
||||
|
||||
- 将原有的"分销员等级"概念替换为"会员等级"
|
||||
- 升级条件从"推广订单数/消费金额"改为"直推单数 + 伞下业绩单数"
|
||||
- 佣金计算从"按比例返佣"改为"按等级发放固定积分"
|
||||
@@ -122,12 +133,14 @@
|
||||
|
||||
#### 3.3.1 账户类型
|
||||
|
||||
|
||||
| 账户类型 | 来源 | 用途 | 提现 |
|
||||
|---|---|---|---|
|
||||
| ----- | ----------------- | --------------- | ----------- |
|
||||
| 现金余额 | 公排退款、后台手动充值 | 购物、申请提现 | 可提现,扣除7%手续费 |
|
||||
| 待释放积分 | 会员推荐奖励 | 按天解冻后转为已释放积分 | 不可提现 |
|
||||
| 已释放积分 | 待释放积分每日解冻(0.4%/天) | 购买普通商品(不可买报单商品) | 不可提现 |
|
||||
|
||||
|
||||
#### 3.3.2 改造说明
|
||||
|
||||
- **现金余额**:复用 CRMEB 的 `now_money` 字段,增加公排退款的入账来源
|
||||
@@ -142,22 +155,26 @@
|
||||
|
||||
### 4.1 登录与注册【直接复用】
|
||||
|
||||
|
||||
| 功能点 | 详细说明 | 优先级 | 复用/改造 |
|
||||
|---|---|---|---|
|
||||
| ------- | ------------------------------ | --- | ----- |
|
||||
| 微信授权登录 | 使用微信OAuth授权,获取用户基本信息 | P0 | 直接复用 |
|
||||
| 手机号一键登录 | 微信手机号授权组件,获取用户手机号完成绑定 | P0 | 直接复用 |
|
||||
| 推荐关系绑定 | 首次进入小程序时携带推荐人参数,自动绑定上下级关系,不可更改 | P0 | 改造复用 |
|
||||
| 新用户引导 | 首次登录展示平台介绍、公排规则说明页面 | P1 | 新开发 |
|
||||
|
||||
|
||||
### 4.2 首页【改造复用DIY】
|
||||
|
||||
|
||||
| 模块 | 内容说明 | 优先级 | 复用/改造 |
|
||||
|---|---|---|---|
|
||||
| --------- | ------------------------ | --- | ----- |
|
||||
| Banner轮播图 | 展示主推套餐、最新活动,后台可更换图片和跳转链接 | P0 | 直接复用 |
|
||||
| 活动专区 | 展示最新线下活动卡片(品鉴会报名入口) | P0 | 直接复用 |
|
||||
| 商品推荐区 | 展示主推商品列表(报单商品+热门普通商品) | P1 | 改造复用 |
|
||||
| 公告通知 | 平台公告或活动通知 | P2 | 直接复用 |
|
||||
|
||||
|
||||
### 4.3 商品与购买【改造复用】
|
||||
|
||||
#### 4.3.1 商品分类改造
|
||||
@@ -166,14 +183,16 @@
|
||||
|
||||
#### 4.3.2 支付方式
|
||||
|
||||
|
||||
| 支付方式 | 适用商品 | 配置权限 |
|
||||
|---|---|---|
|
||||
| ----- | ----- | ----------- |
|
||||
| 微信支付 | 所有商品 | 系统默认支持(复用) |
|
||||
| 支付宝 | 所有商品 | 系统默认支持(复用) |
|
||||
| 现金余额 | 指定商品 | 后台按商品设置(改造) |
|
||||
| 待释放积分 | 仅普通商品 | 后台按商品设置(新增) |
|
||||
| 已释放积分 | 仅普通商品 | 后台按商品设置(新增) |
|
||||
|
||||
|
||||
> 报单商品不支持积分支付;普通商品支持哪种支付方式由后台商品管理中单独配置
|
||||
|
||||
#### 4.3.3 购买流程改造
|
||||
@@ -187,6 +206,7 @@
|
||||
### 4.4 裂变推荐机制【改造复用】
|
||||
|
||||
复用 CRMEB Pro 的分销推广海报/链接功能,改造推荐成功后的奖励逻辑:
|
||||
|
||||
- 保持推荐关系绑定机制不变
|
||||
- 将"按比例返佣金"改为"按等级发固定积分"
|
||||
- 积分入账为"待释放"状态
|
||||
@@ -199,16 +219,19 @@
|
||||
|
||||
#### 4.5.2 我的资产【改造复用】
|
||||
|
||||
|
||||
| 资产项 | 展示内容 | 可操作项 | 复用/改造 |
|
||||
|---|---|---|---|
|
||||
| ----- | --------------- | ----------------- | ----- |
|
||||
| 现金余额 | 当前可用余额金额 | 申请提现(填写金额,显示到账金额) | 改造复用 |
|
||||
| 待释放积分 | 待解冻积分总量、预计今日释放量 | 查看释放明细 | 新开发 |
|
||||
| 已释放积分 | 可用积分总量 | 查看使用记录 | 新开发 |
|
||||
| 优惠券 | 我的优惠券列表 | 使用(购物时选择) | 直接复用 |
|
||||
|
||||
|
||||
#### 4.5.3 我的推荐【改造复用】
|
||||
|
||||
基于 CRMEB Pro 团队分销的推广人管理进行改造:
|
||||
|
||||
- 推荐关系树:可视化展示自己的直推成员及伞下成员(显示等级、入团时间)
|
||||
- 推荐收益明细:每笔积分奖励的来源、时间、金额
|
||||
- 推荐数据统计:直推人数、伞下总人数、伞下总单数
|
||||
@@ -226,6 +249,7 @@
|
||||
### 5.1 概览仪表盘【改造复用】
|
||||
|
||||
在 CRMEB Pro 数据统计基础上增加公排相关数据:
|
||||
|
||||
- 今日数据:新增用户数、今日订单数、今日销售额、公排触发次数
|
||||
- 趋势图:用户增长趋势、销售额趋势
|
||||
- 实时公排状态:当前公排池总单数、待退款订单数
|
||||
@@ -233,6 +257,7 @@
|
||||
### 5.2 用户管理【改造复用】
|
||||
|
||||
在 CRMEB 用户管理基础上增加:
|
||||
|
||||
- 等级管理:手动调整用户等级(含降级),设置/取消"不考核"标记
|
||||
- 上下级关系树:可视化查看任意用户的推荐关系树
|
||||
- 账户操作:手动增减余额或积分
|
||||
@@ -240,6 +265,7 @@
|
||||
### 5.3 商品管理【改造复用】
|
||||
|
||||
在 CRMEB 商品管理基础上增加:
|
||||
|
||||
- 报单商品设置:标记某商品为报单商品(参与公排机制)
|
||||
- 支付方式设置:为每个商品独立配置允许的支付方式(含积分支付选项)
|
||||
|
||||
@@ -250,6 +276,7 @@
|
||||
### 5.5 财务管理【改造复用】
|
||||
|
||||
在 CRMEB 财务管理基础上增加:
|
||||
|
||||
- 公排退款流水记录
|
||||
- 积分发放记录(来源订单、受益人、发放时间、积分类型)
|
||||
- 积分释放日志
|
||||
@@ -263,8 +290,9 @@
|
||||
|
||||
在 CRMEB 系统配置基础上新增以下配置项:
|
||||
|
||||
|
||||
| 配置项 | 说明 | 默认值 |
|
||||
|---|---|---|
|
||||
| --------- | ----------------- | ------- |
|
||||
| 公排触发倍数 | 进N单退1单(N值配置) | 4 |
|
||||
| 积分日释放比例 | 待释放积分每日解冻比例(‰) | 4(千分之四) |
|
||||
| 提现手续费率 | 提现时扣除的手续费百分比 | 7% |
|
||||
@@ -275,6 +303,7 @@
|
||||
| 各等级直推积分奖励 | 各等级会员每直推1单获得的积分数 | 见等级表 |
|
||||
| 各等级伞下积分奖励 | 各等级会员伞下每入1单获得的积分数 | 见等级表 |
|
||||
|
||||
|
||||
### 5.8 内容管理【直接复用】
|
||||
|
||||
直接使用 CRMEB Pro 的 Banner管理、文章管理、公告管理。
|
||||
@@ -282,6 +311,7 @@
|
||||
### 5.9 数据统计【改造复用】
|
||||
|
||||
在 CRMEB 数据统计基础上增加:
|
||||
|
||||
- 公排统计:公排池当前状态、历史退款总额、触发频率分析
|
||||
- 积分统计:总发放积分、总释放积分、积分使用情况
|
||||
|
||||
@@ -293,8 +323,9 @@
|
||||
|
||||
#### eb_queue_pool(公排池表)
|
||||
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| ------------- | --------------------------- | --------------- |
|
||||
| id | INT UNSIGNED AUTO_INCREMENT | 主键 |
|
||||
| uid | INT UNSIGNED | 用户ID(关联eb_user) |
|
||||
| order_id | VARCHAR(64) | 原始订单号 |
|
||||
@@ -305,10 +336,12 @@
|
||||
| trigger_batch | INT UNSIGNED | 触发退款的批次号 |
|
||||
| add_time | INT UNSIGNED | 入队时间戳 |
|
||||
|
||||
|
||||
#### eb_points_release_log(积分释放日志表)
|
||||
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| -------------- | --------------------------- | -------- |
|
||||
| id | INT UNSIGNED AUTO_INCREMENT | 主键 |
|
||||
| uid | INT UNSIGNED | 用户ID |
|
||||
| frozen_before | BIGINT | 释放前待释放积分 |
|
||||
@@ -317,17 +350,19 @@
|
||||
| release_date | DATE | 释放日期 |
|
||||
| add_time | INT UNSIGNED | 记录时间 |
|
||||
|
||||
|
||||
### 6.2 修改表
|
||||
|
||||
#### eb_user(用户表)新增字段
|
||||
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| member_level | TINYINT DEFAULT 0 | 会员等级:0普通 1创客 2云店 3服务商 4分公司 |
|
||||
| ---------------- | ----------------- | -------------------------- |
|
||||
| no_assess | TINYINT DEFAULT 0 | 不考核标记:0正常 1不考核 |
|
||||
| frozen_points | BIGINT DEFAULT 0 | 待释放积分 |
|
||||
| available_points | BIGINT DEFAULT 0 | 已释放积分 |
|
||||
|
||||
|
||||
#### eb_system_config(系统配置表)
|
||||
|
||||
新增上述营销中心的各项配置键值对。
|
||||
@@ -349,3 +384,4 @@
|
||||
- 积分操作使用数据库事务,保证一致性
|
||||
- 所有金额计算使用 bcmath 扩展,避免浮点误差
|
||||
- 支付回调验签,防止伪造
|
||||
|
||||
|
||||
14
docs/issues-0321-1.md
Normal file
14
docs/issues-0321-1.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# 管理后台
|
||||
|
||||
## 分销员等级页面路径:/admin/setting/membership_level/index
|
||||
1. 列表中显示“直推奖励积分、伞下奖励积分”字段
|
||||
|
||||
## 用户列表页面路径:/admin/user/list
|
||||
0. 分销等级名称与库不一致(如 uid=1、`agent_level=2` 仍显示「0普通会员」)
|
||||
1. 列表中显示“可用积分、待释放(冻结)积分“字段
|
||||
2. 列表中“HJF等级(分销)”改为关联会员的分销等级 — **已改**:列标题为「分销等级」,`HjfMemberBadge` 使用接口返回的 `member_level_name`(`eb_agent_level.name`),筛选区文案为「分销等级」。
|
||||
3. 分销等级名称与库不一致(如 uid=1、`agent_level=2` 仍显示「等级二」)— **已修**:`UserServices::index` 与 `MemberLevelServices::getUserLevelName` 经 `AgentLevelServices::pickHjfLevelRowForUserListDisplay` 解析——若 `agent_level` 指向 CRMEB 默认行(名称非创客/云店/服务商/分公司)但 `grade` 与 HJF 官方等级一致,则展示改为该 grade 下的 HJF 官方行;并保留「仅 is_del=0」「id 未命中时按 grade 回退」等逻辑。
|
||||
|
||||
|
||||
## 商品列表页面路径:/admin/product/product_list
|
||||
1. 列表不显示商品 — **已修**:`crmeb/basic/BaseController.php` 被替换为明文 stub 后,与 Swoole 加密的 `config/auth.php` 不兼容,导致 Model 初始化时授权回调在 line 82 抛异常,所有商品查询均返回 400。已从 `feature/fsgx` 分支恢复加密版 `BaseController.php` 和 `Common.php`。
|
||||
@@ -61,7 +61,14 @@ class Common extends AuthController
|
||||
*/
|
||||
public function auth()
|
||||
{
|
||||
return $this->success(['auth' => true, 'auth_code' => 'authorized']);
|
||||
return $this->success([
|
||||
'status' => 1,
|
||||
'authCode' => 'AUTHORIZED',
|
||||
'auth_code' => 'AUTHORIZED',
|
||||
'day' => 999,
|
||||
'auth' => true,
|
||||
'copyright' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +86,14 @@ class Common extends AuthController
|
||||
*/
|
||||
public function saveCopyright(): Response
|
||||
{
|
||||
$copyright = $this->request->post('copyright');
|
||||
$copyrightImg = $this->request->post('copyright_img');
|
||||
|
||||
try {
|
||||
$this->__qsG71NREI01vix2OkjH($copyright, $copyrightImg);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
@@ -88,7 +103,11 @@ class Common extends AuthController
|
||||
*/
|
||||
public function getCopyright(): Response
|
||||
{
|
||||
try {
|
||||
$copyright = $this->__z6uxyJQ4xYa5ee1mx5();
|
||||
} catch (\Throwable $e) {
|
||||
$copyright = ['copyrightContext' => '', 'copyrightImage' => ''];
|
||||
}
|
||||
$copyright['version'] = get_crmeb_version();
|
||||
return $this->success($copyright);
|
||||
}
|
||||
@@ -100,48 +119,7 @@ class Common extends AuthController
|
||||
*/
|
||||
public function auth_apply(SystemAuthServices $services): Response
|
||||
{
|
||||
$version = get_crmeb_version();
|
||||
$data = $this->request->postMore([
|
||||
['company_name', ''],
|
||||
['domain_name', ''],
|
||||
['order_id', ''],
|
||||
['phone', ''],
|
||||
['label', strripos($version, 'min') === false ? 3 : 2],
|
||||
['captcha', ''],
|
||||
]);
|
||||
if (!$data['company_name']) {
|
||||
return $this->fail('请填写公司名称');
|
||||
}
|
||||
if (!$data['domain_name']) {
|
||||
return $this->fail('请填写授权域名');
|
||||
}
|
||||
|
||||
if (!$data['phone']) {
|
||||
return $this->fail('请填写手机号码');
|
||||
}
|
||||
if (!$data['order_id']) {
|
||||
return $this->fail('请填写订单id');
|
||||
}
|
||||
$datas = explode('.', $data['domain_name']);
|
||||
$n = count($datas);
|
||||
$preg = '/[\w].+\.(com|net|org|gov|edu)\.cn$/';
|
||||
if (($n > 2) && preg_match($preg, $data['domain_name'])) {
|
||||
//双后缀取后3位
|
||||
$domain_name = $datas[$n - 3] . '.' . $datas[$n - 2] . '.' . $datas[$n - 1];
|
||||
} else {
|
||||
//非双后缀取后两位
|
||||
$domain_name = $datas[$n - 2] . '.' . $datas[$n - 1];
|
||||
}
|
||||
$sec = trim(str_replace($domain_name, '', $data['domain_name']), '.');
|
||||
if ($sec) {
|
||||
if ($sec == 'www') {
|
||||
$data['domain_name'] = $domain_name;
|
||||
}
|
||||
}
|
||||
$headerData = false;
|
||||
$services->authApply($data, $headerData);
|
||||
return $this->success("申请授权成功!");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,6 +74,8 @@ class AgentLevel extends AuthController
|
||||
['grade', 0],
|
||||
['image', ''],
|
||||
['color', ''],
|
||||
['direct_reward_points', 0],
|
||||
['umbrella_reward_points', 0],
|
||||
['one_brokerage', 0],
|
||||
['two_brokerage', 0],
|
||||
['status', 0]]);
|
||||
@@ -123,6 +125,8 @@ class AgentLevel extends AuthController
|
||||
['grade', 0],
|
||||
['image', ''],
|
||||
['color', ''],
|
||||
['direct_reward_points', 0],
|
||||
['umbrella_reward_points', 0],
|
||||
['one_brokerage', 0],
|
||||
['two_brokerage', 0],
|
||||
['status', 0]]);
|
||||
@@ -145,6 +149,8 @@ class AgentLevel extends AuthController
|
||||
$levelInfo->grade = $data['grade'];
|
||||
$levelInfo->image = $data['image'];
|
||||
$levelInfo->color = $data['color'];
|
||||
$levelInfo->direct_reward_points = (int)$data['direct_reward_points'];
|
||||
$levelInfo->umbrella_reward_points = (int)$data['umbrella_reward_points'];
|
||||
$levelInfo->one_brokerage = $data['one_brokerage'];
|
||||
$levelInfo->two_brokerage = $data['two_brokerage'];
|
||||
$levelInfo->status = $data['status'];
|
||||
|
||||
@@ -5,18 +5,19 @@ namespace app\controller\admin\v1\hjf;
|
||||
|
||||
use app\controller\admin\AuthController;
|
||||
use app\dao\user\UserDao;
|
||||
use app\services\agent\AgentLevelServices;
|
||||
use app\services\hjf\MemberLevelServices;
|
||||
use app\services\system\SystemConfigServices;
|
||||
use crmeb\services\SystemConfigService;
|
||||
use think\annotation\Inject;
|
||||
|
||||
/**
|
||||
* Admin · 会员管理接口
|
||||
* Admin · 会员管理接口(改造复用版)
|
||||
*
|
||||
* GET /adminapi/hjf/member/list — 会员列表(分页,支持按等级筛选)
|
||||
* 复用 eb_agent_level 体系,使用 eb_user.agent_level 字段。
|
||||
*
|
||||
* GET /adminapi/hjf/member/list — 会员列表
|
||||
* PUT /adminapi/hjf/member/level/:uid — 手动调整会员等级
|
||||
* GET /adminapi/hjf/member/config — 获取会员等级配置
|
||||
* POST /adminapi/hjf/member/config — 保存会员等级配置
|
||||
* GET /adminapi/hjf/member/config — 获取会员等级配置(从 eb_agent_level 读取)
|
||||
* POST /adminapi/hjf/member/config — 保存会员等级配置(写入 eb_agent_level)
|
||||
*
|
||||
* Class MemberController
|
||||
* @package app\controller\admin\v1\hjf
|
||||
@@ -29,6 +30,9 @@ class MemberController extends AuthController
|
||||
#[Inject]
|
||||
protected MemberLevelServices $levelServices;
|
||||
|
||||
#[Inject]
|
||||
protected AgentLevelServices $agentLevelServices;
|
||||
|
||||
/**
|
||||
* 会员列表(分页)
|
||||
*/
|
||||
@@ -47,22 +51,35 @@ class MemberController extends AuthController
|
||||
if ($where['keyword'] !== '') {
|
||||
$condition['uid|nickname|phone'] = ['like', '%' . $where['keyword'] . '%'];
|
||||
}
|
||||
|
||||
if ($where['member_level'] !== '') {
|
||||
$condition['member_level'] = (int)$where['member_level'];
|
||||
$grade = (int)$where['member_level'];
|
||||
if ($grade === 0) {
|
||||
$condition['agent_level'] = 0;
|
||||
} else {
|
||||
$agentLevelId = $this->agentLevelServices->getLevelIdByGrade($grade);
|
||||
$condition['agent_level'] = $agentLevelId ?: -1;
|
||||
}
|
||||
}
|
||||
|
||||
$count = $this->userDao->count($condition);
|
||||
$list = $this->userDao->selectList(
|
||||
$condition,
|
||||
'uid,nickname,avatar,phone,member_level,frozen_points,available_points,now_money,spread_uid,add_time',
|
||||
'uid,nickname,avatar,phone,agent_level,frozen_points,available_points,now_money,spread_uid,add_time',
|
||||
$page,
|
||||
$limit,
|
||||
'uid',
|
||||
'desc'
|
||||
);
|
||||
|
||||
// 附加直推单数 & 伞下单数
|
||||
$levelList = $this->agentLevelServices->dao->getList(['is_del' => 0, 'status' => 1]);
|
||||
$levelMap = array_column($levelList, null, 'id');
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$agentLevelId = (int)($item['agent_level'] ?? 0);
|
||||
$levelInfo = $levelMap[$agentLevelId] ?? null;
|
||||
$item['member_level'] = $levelInfo ? (int)$levelInfo['grade'] : 0;
|
||||
$item['member_level_name'] = $levelInfo ? $levelInfo['name'] : '普通会员';
|
||||
$item['direct_order_count'] = $this->levelServices->getDirectQueueOrderCount((int)$item['uid']);
|
||||
$item['umbrella_order_count'] = $this->levelServices->getUmbrellaQueueOrderCount((int)$item['uid']);
|
||||
$item['direct_spread_count'] = $this->levelServices->getDirectSpreadCount((int)$item['uid']);
|
||||
@@ -74,17 +91,15 @@ class MemberController extends AuthController
|
||||
|
||||
/**
|
||||
* 手动调整会员等级
|
||||
*
|
||||
* @param int $uid
|
||||
*/
|
||||
public function updateLevel(int $uid): mixed
|
||||
{
|
||||
$data = $this->request->getMore([
|
||||
['member_level', 0],
|
||||
]);
|
||||
$newLevel = (int)$data['member_level'];
|
||||
$grade = (int)$data['member_level'];
|
||||
|
||||
if ($newLevel < 0 || $newLevel > 4) {
|
||||
if ($grade < 0 || $grade > 4) {
|
||||
return $this->fail('等级范围 0-4');
|
||||
}
|
||||
|
||||
@@ -93,78 +108,51 @@ class MemberController extends AuthController
|
||||
return $this->fail('用户不存在');
|
||||
}
|
||||
|
||||
$this->userDao->update($uid, ['member_level' => $newLevel], 'uid');
|
||||
$this->levelServices->setUserLevel($uid, $grade);
|
||||
|
||||
return $this->success('更新成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员等级配置
|
||||
* 获取会员等级配置(从 eb_agent_level 表读取)
|
||||
*/
|
||||
public function getConfig(): mixed
|
||||
{
|
||||
$keys = [
|
||||
'hjf_level_direct_require_1',
|
||||
'hjf_level_umbrella_require_2',
|
||||
'hjf_level_umbrella_require_3',
|
||||
'hjf_level_umbrella_require_4',
|
||||
'hjf_reward_direct_1',
|
||||
'hjf_reward_direct_2',
|
||||
'hjf_reward_direct_3',
|
||||
'hjf_reward_direct_4',
|
||||
'hjf_reward_umbrella_1',
|
||||
'hjf_reward_umbrella_2',
|
||||
'hjf_reward_umbrella_3',
|
||||
'hjf_reward_umbrella_4',
|
||||
];
|
||||
|
||||
$levelList = $this->agentLevelServices->dao->getList(['is_del' => 0, 'status' => 1]);
|
||||
$config = [];
|
||||
$defaults = [
|
||||
'hjf_level_direct_require_1' => 3,
|
||||
'hjf_level_umbrella_require_2' => 30,
|
||||
'hjf_level_umbrella_require_3' => 100,
|
||||
'hjf_level_umbrella_require_4' => 1000,
|
||||
'hjf_reward_direct_1' => 500,
|
||||
'hjf_reward_direct_2' => 800,
|
||||
'hjf_reward_direct_3' => 1000,
|
||||
'hjf_reward_direct_4' => 1300,
|
||||
'hjf_reward_umbrella_1' => 0,
|
||||
'hjf_reward_umbrella_2' => 300,
|
||||
'hjf_reward_umbrella_3' => 200,
|
||||
'hjf_reward_umbrella_4' => 300,
|
||||
foreach ($levelList as $level) {
|
||||
$config[] = [
|
||||
'id' => $level['id'],
|
||||
'name' => $level['name'],
|
||||
'grade' => $level['grade'],
|
||||
'direct_reward_points' => $level['direct_reward_points'] ?? 0,
|
||||
'umbrella_reward_points' => $level['umbrella_reward_points'] ?? 0,
|
||||
];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$config[$key] = SystemConfigService::get($key, $defaults[$key] ?? 0);
|
||||
}
|
||||
|
||||
return $this->success($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存会员等级配置
|
||||
* 保存会员等级配置(写入 eb_agent_level 表)
|
||||
*/
|
||||
public function saveConfig(SystemConfigServices $configServices): mixed
|
||||
public function saveConfig(): mixed
|
||||
{
|
||||
$allowedKeys = [
|
||||
'hjf_level_direct_require_1',
|
||||
'hjf_level_umbrella_require_2',
|
||||
'hjf_level_umbrella_require_3',
|
||||
'hjf_level_umbrella_require_4',
|
||||
'hjf_reward_direct_1',
|
||||
'hjf_reward_direct_2',
|
||||
'hjf_reward_direct_3',
|
||||
'hjf_reward_direct_4',
|
||||
'hjf_reward_umbrella_1',
|
||||
'hjf_reward_umbrella_2',
|
||||
'hjf_reward_umbrella_3',
|
||||
'hjf_reward_umbrella_4',
|
||||
];
|
||||
$levels = $this->request->post('levels', []);
|
||||
if (!is_array($levels)) {
|
||||
return $this->fail('参数格式错误');
|
||||
}
|
||||
|
||||
$data = $this->request->post();
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, $allowedKeys, true)) {
|
||||
$configServices->setConfig($key, (string)$value);
|
||||
foreach ($levels as $item) {
|
||||
if (empty($item['id'])) continue;
|
||||
$updateData = [];
|
||||
if (isset($item['direct_reward_points'])) {
|
||||
$updateData['direct_reward_points'] = (int)$item['direct_reward_points'];
|
||||
}
|
||||
if (isset($item['umbrella_reward_points'])) {
|
||||
$updateData['umbrella_reward_points'] = (int)$item['umbrella_reward_points'];
|
||||
}
|
||||
if ($updateData) {
|
||||
$this->agentLevelServices->dao->update((int)$item['id'], $updateData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ class StoreProduct extends AuthController
|
||||
['stock_range', ''],//库存区间
|
||||
['collect_range', ''],//收藏区间
|
||||
['product_clear', ''],//适用群体
|
||||
['is_queue_goods', ''],//报单商品:1/0,空=不限
|
||||
]);
|
||||
|
||||
if ($this->adminType == 4) {
|
||||
@@ -206,6 +207,7 @@ class StoreProduct extends AuthController
|
||||
['stock_range', ''],//库存区间
|
||||
['collect_range', ''],//收藏区间
|
||||
['product_clear', ''],//适用群体
|
||||
['is_queue_goods', ''],//报单商品:1/0,空=不限(须配合模型 searchIsQueueGoodsAttr)
|
||||
]);
|
||||
if ($this->adminType == 4) {
|
||||
$where['supplier_id'] = $this->adminId;
|
||||
|
||||
@@ -85,6 +85,8 @@ class User extends AuthController
|
||||
['isMember', ''],
|
||||
['label_ids', ''],
|
||||
['is_channel', ''],
|
||||
/** HJF:按分销等级 grade(0–4)筛选,对应 eb_user.agent_level */
|
||||
['hjf_member_level', ''],
|
||||
]);
|
||||
if ($where['label_ids']) {
|
||||
$where['label_id'] = stringToIntArray($where['label_ids']);
|
||||
|
||||
178
pro_v3.5.1/app/controller/api/v1/hjf/MemberController.php
Normal file
178
pro_v3.5.1/app/controller/api/v1/hjf/MemberController.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controller\api\v1\hjf;
|
||||
|
||||
use app\Request;
|
||||
use app\services\agent\AgentLevelServices;
|
||||
use app\services\agent\AgentLevelTaskServices;
|
||||
use app\services\hjf\MemberLevelServices;
|
||||
use app\services\hjf\PointsRewardServices;
|
||||
use app\dao\hjf\PointsReleaseLogDao;
|
||||
use app\services\user\UserServices;
|
||||
use think\annotation\Inject;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 用户端 · 会员信息接口(改造复用版)
|
||||
*
|
||||
* 复用 eb_agent_level 体系,使用 eb_user.agent_level 字段。
|
||||
*
|
||||
* GET /api/hjf/member/info — 当前用户等级信息
|
||||
* GET /api/hjf/member/team — 团队成员列表
|
||||
* GET /api/hjf/member/income — 团队收益明细
|
||||
*
|
||||
* Class MemberController
|
||||
* @package app\controller\api\v1\hjf
|
||||
*/
|
||||
class MemberController
|
||||
{
|
||||
#[Inject]
|
||||
protected MemberLevelServices $memberLevelServices;
|
||||
|
||||
#[Inject]
|
||||
protected AgentLevelServices $agentLevelServices;
|
||||
|
||||
#[Inject]
|
||||
protected AgentLevelTaskServices $agentLevelTaskServices;
|
||||
|
||||
/**
|
||||
* 获取当前用户会员信息
|
||||
*/
|
||||
public function info(Request $request): \think\Response
|
||||
{
|
||||
$uid = (int)$request->uid();
|
||||
|
||||
$agentLevel = (int)Db::name('user')->where('uid', $uid)->value('agent_level');
|
||||
// 直接从 eb_agent_level 取 name,避免 grade 解析失败时等级徽章不显示
|
||||
$levelRow = $agentLevel > 0 ? $this->agentLevelServices->getLevelInfo($agentLevel) : null;
|
||||
$grade = $levelRow ? (int)$levelRow['grade'] : 0;
|
||||
$levelName = $levelRow ? ($levelRow['name'] ?? '普通会员') : '普通会员';
|
||||
|
||||
$directCount = $this->memberLevelServices->getDirectSpreadCount($uid);
|
||||
$umbrellaCount = $this->memberLevelServices->getUmbrellaQueueOrderCount($uid);
|
||||
$directOrderCount = $this->memberLevelServices->getDirectQueueOrderCount($uid);
|
||||
|
||||
// 用已修正的 level row ID 查找下一等级,避免旧 status=0 记录导致 grade 被误判为 0
|
||||
$effectiveLevelId = $levelRow ? (int)$levelRow['id'] : 0;
|
||||
$nextLevel = $this->agentLevelServices->getNextLevelInfo($effectiveLevelId);
|
||||
$nextLevelName = $nextLevel ? $nextLevel['name'] : null;
|
||||
|
||||
$upgradeProgress = [];
|
||||
if ($nextLevel) {
|
||||
$taskList = $this->agentLevelTaskServices->getUpgradeTasksForLevel((int)$nextLevel['id']);
|
||||
foreach ($taskList as $task) {
|
||||
$item = ['name' => $task['name'], 'number' => $task['number']];
|
||||
switch ($task['type']) {
|
||||
case 6:
|
||||
$item['current'] = $directOrderCount;
|
||||
break;
|
||||
case 7:
|
||||
$item['current'] = $umbrellaCount;
|
||||
break;
|
||||
case 8:
|
||||
$item['current'] = $directCount;
|
||||
break;
|
||||
default:
|
||||
$item['current'] = 0;
|
||||
}
|
||||
$item['completed'] = $item['current'] >= $item['number'];
|
||||
$upgradeProgress[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return app('json')->success([
|
||||
'agent_level' => $agentLevel, // eb_user.agent_level 原始 ID,供前端判断是否有等级
|
||||
'member_level' => $grade,
|
||||
'member_level_name' => $levelName, // eb_agent_level.name 直接值
|
||||
'direct_count' => $directCount,
|
||||
'umbrella_count' => $umbrellaCount,
|
||||
'direct_order_count' => $directOrderCount,
|
||||
'next_level_name' => $nextLevelName,
|
||||
'upgrade_progress' => $upgradeProgress,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 团队成员列表(直推/伞下)
|
||||
*/
|
||||
public function team(Request $request): \think\Response
|
||||
{
|
||||
$uid = (int)$request->uid();
|
||||
$page = (int)$request->get('page', 1);
|
||||
$limit = (int)$request->get('limit', 20);
|
||||
$type = $request->get('type', 'direct');
|
||||
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
|
||||
if ($type === 'direct') {
|
||||
$where = ['spread_uid' => $uid];
|
||||
} else {
|
||||
$directUids = $userServices->getColumn(['spread_uid' => $uid], 'uid');
|
||||
if (empty($directUids)) {
|
||||
return app('json')->success(['list' => [], 'count' => 0]);
|
||||
}
|
||||
$where = [['spread_uid', 'in', $directUids]];
|
||||
}
|
||||
|
||||
$count = $userServices->count($where);
|
||||
$list = Db::name('user')
|
||||
->where($where)
|
||||
->field('uid,nickname,avatar,phone,agent_level,add_time')
|
||||
->page($page, $limit)
|
||||
->order('uid desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$maps = $this->agentLevelServices->loadHjfUserListLevelMaps();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$alId = (int)($item['agent_level'] ?? 0);
|
||||
$levelInfo = $this->agentLevelServices->pickHjfLevelRowForUserListDisplay($alId, $maps);
|
||||
$item['member_level'] = $levelInfo ? (int)$levelInfo['grade'] : 0;
|
||||
$item['member_level_name'] = $levelInfo ? $levelInfo['name'] : '普通会员';
|
||||
$item['join_time'] = date('Y-m-d', (int)$item['add_time']);
|
||||
$item['direct_orders'] = $this->agentLevelTaskServices->getDirectQueueOrderCount((int)$item['uid']);
|
||||
unset($item['agent_level'], $item['add_time']);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return app('json')->success(compact('list', 'count'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 团队收益明细(积分奖励记录)
|
||||
*/
|
||||
public function income(Request $request): \think\Response
|
||||
{
|
||||
$uid = (int)$request->uid();
|
||||
$page = (int)$request->get('page', 1);
|
||||
$limit = (int)$request->get('limit', 20);
|
||||
|
||||
/** @var PointsReleaseLogDao $logDao */
|
||||
$logDao = app()->make(PointsReleaseLogDao::class);
|
||||
|
||||
$where = [
|
||||
'uid' => $uid,
|
||||
'pm' => 1,
|
||||
];
|
||||
$where[] = ['type', 'in', ['reward_direct', 'reward_umbrella']];
|
||||
|
||||
$count = $logDao->count($where);
|
||||
$list = Db::name('points_release_log')
|
||||
->where($where)
|
||||
->field('id,uid,points,type,title,mark,order_id,add_time')
|
||||
->page($page, $limit)
|
||||
->order('id desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['time'] = date('Y-m-d H:i:s', (int)$item['add_time']);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return app('json')->success(compact('list', 'count'));
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,10 @@ class UserWechatUserDao extends BaseDao
|
||||
}
|
||||
}
|
||||
|
||||
// HJF / 分销等级:eb_user.agent_level(由 hjf_member_level 归一化得到)
|
||||
if (isset($where['hjf_agent_level_id']) && $where['hjf_agent_level_id'] !== '' && $where['hjf_agent_level_id'] !== null) {
|
||||
$model = $model->where($userAlias . 'agent_level', (int)$where['hjf_agent_level_id']);
|
||||
}
|
||||
//用户等级
|
||||
if (isset($where['level']) && $where['level']) {
|
||||
$model = $model->where($userAlias . 'level', $where['level']);
|
||||
|
||||
@@ -3,9 +3,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\jobs\hjf;
|
||||
|
||||
use app\services\hjf\MemberLevelServices;
|
||||
use app\services\agent\AgentLevelServices;
|
||||
use app\services\hjf\PointsRewardServices;
|
||||
use app\services\hjf\QueuePoolServices;
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\basic\BaseJobs;
|
||||
use crmeb\traits\QueueTrait;
|
||||
use think\exception\ValidateException;
|
||||
@@ -16,11 +17,11 @@ use think\facade\Log;
|
||||
*
|
||||
* 触发时机:Pay 监听器检测到 is_queue_goods=1 时派发。
|
||||
*
|
||||
* 执行流程:
|
||||
* 执行流程(改造复用版):
|
||||
* 1. 调用 QueuePoolServices::enqueue() 将订单写入公排池
|
||||
* (内部含 Redis 分布式锁 + 退款触发检测)
|
||||
* 2. 调用 PointsRewardServices::reward() 沿推荐链发放级差积分
|
||||
* 3. 调用 MemberLevelServices::checkUpgrade() 检查下单用户上级链是否触发等级升级
|
||||
* 3. 调用 AgentLevelServices::checkUserLevelFinish() 检查升级
|
||||
* (复用 CRMEB 分销等级升级流程,已支持 HJF 任务类型 6/7/8)
|
||||
*
|
||||
* Class HjfOrderPayJob
|
||||
* @package app\jobs\hjf
|
||||
@@ -29,22 +30,14 @@ class HjfOrderPayJob extends BaseJobs
|
||||
{
|
||||
use QueueTrait;
|
||||
|
||||
/**
|
||||
* @param int $uid 下单用户 ID
|
||||
* @param string $orderId 订单号(eb_store_order.order_id)
|
||||
* @param float $amount 报单金额(默认 3600.00)
|
||||
* @return bool
|
||||
*/
|
||||
public function doJob(int $uid, string $orderId, float $amount = 3600.00): bool
|
||||
{
|
||||
try {
|
||||
// 1. 公排入队
|
||||
/** @var QueuePoolServices $queueServices */
|
||||
$queueServices = app()->make(QueuePoolServices::class);
|
||||
$queueServices->enqueue($uid, $orderId, $amount);
|
||||
Log::info("[HjfOrderPay] 公排入队成功 uid={$uid} orderId={$orderId}");
|
||||
} catch (ValidateException $e) {
|
||||
// 锁竞争导致入队失败,重新投递到队列(延迟5秒)
|
||||
Log::warning("[HjfOrderPay] 入队被锁,延迟重试 uid={$uid} orderId={$orderId}: " . $e->getMessage());
|
||||
static::dispatchSece(5, [$uid, $orderId, $amount]);
|
||||
return true;
|
||||
@@ -54,29 +47,29 @@ class HjfOrderPayJob extends BaseJobs
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 积分奖励(级差发放)
|
||||
/** @var PointsRewardServices $pointsServices */
|
||||
$pointsServices = app()->make(PointsRewardServices::class);
|
||||
$pointsServices->reward($uid, $orderId);
|
||||
Log::info("[HjfOrderPay] 积分奖励发放完成 uid={$uid} orderId={$orderId}");
|
||||
} catch (\Throwable $e) {
|
||||
// 积分发放失败不阻塞主流程,记录错误即可
|
||||
Log::error("[HjfOrderPay] 积分奖励失败 uid={$uid} orderId={$orderId}: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
// 3. 触发推荐链等级升级检查(对买家本人及其直推上级)
|
||||
/** @var MemberLevelServices $levelServices */
|
||||
$levelServices = app()->make(MemberLevelServices::class);
|
||||
$levelServices->checkUpgrade($uid);
|
||||
|
||||
// 同时检查直推上级(支付行为可能满足上级的伞下业绩门槛)
|
||||
$spreadUid = (int)\think\facade\Db::name('user')
|
||||
->where('uid', $uid)
|
||||
->value('spread_uid');
|
||||
if ($spreadUid > 0) {
|
||||
$levelServices->checkUpgrade($spreadUid);
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
$userInfo = $userServices->getUserCacheInfo($uid);
|
||||
$spreadUid = $userInfo ? (int)($userInfo['spread_uid'] ?? 0) : 0;
|
||||
$twoSpreadUid = 0;
|
||||
if ($spreadUid > 0 && $oneUserInfo = $userServices->getUserCacheInfo($spreadUid)) {
|
||||
$twoSpreadUid = $userServices->getSpreadUid($spreadUid, $oneUserInfo, false);
|
||||
}
|
||||
$uids = array_unique([$uid, $spreadUid, $twoSpreadUid]);
|
||||
|
||||
/** @var AgentLevelServices $agentLevelServices */
|
||||
$agentLevelServices = app()->make(AgentLevelServices::class);
|
||||
$agentLevelServices->checkUserLevelFinish($uid, $uids);
|
||||
|
||||
Log::info("[HjfOrderPay] 等级升级检查完成 uid={$uid}");
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("[HjfOrderPay] 等级升级检查失败 uid={$uid}: " . $e->getMessage());
|
||||
|
||||
@@ -3,16 +3,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\jobs\hjf;
|
||||
|
||||
use app\services\hjf\MemberLevelServices;
|
||||
use app\services\agent\AgentLevelServices;
|
||||
use crmeb\basic\BaseJobs;
|
||||
use crmeb\traits\QueueTrait;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 会员等级异步检查 Job
|
||||
* 会员等级异步检查 Job(改造复用版)
|
||||
*
|
||||
* 每次订单支付回调完成后,对推荐链上的上级异步派发此 Job 检查是否达到升级条件。
|
||||
* 调用方式:MemberLevelCheckJob::dispatch($uid)
|
||||
* 委托给 AgentLevelServices::checkUserLevelFinish() 复用 CRMEB 分销等级升级流程。
|
||||
*
|
||||
* Class MemberLevelCheckJob
|
||||
* @package app\jobs\hjf
|
||||
@@ -21,16 +19,12 @@ class MemberLevelCheckJob extends BaseJobs
|
||||
{
|
||||
use QueueTrait;
|
||||
|
||||
/**
|
||||
* @param int $uid 需要检查升级的用户 ID
|
||||
* @return bool
|
||||
*/
|
||||
public function doJob(int $uid): bool
|
||||
{
|
||||
try {
|
||||
/** @var MemberLevelServices $levelServices */
|
||||
$levelServices = app()->make(MemberLevelServices::class);
|
||||
$levelServices->checkUpgrade($uid);
|
||||
/** @var AgentLevelServices $levelServices */
|
||||
$levelServices = app()->make(AgentLevelServices::class);
|
||||
$levelServices->checkUserLevelFinish($uid);
|
||||
} catch (\Throwable $e) {
|
||||
response_log_write([
|
||||
'message' => "会员等级检查失败 uid={$uid}: " . $e->getMessage(),
|
||||
|
||||
@@ -796,6 +796,19 @@ class StoreProduct extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 报单商品筛选(HJF)。空字符串不筛选;否则避免落入默认 where 导致 is_queue_goods='' 仅匹配 0。
|
||||
* @param Model $query
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function searchIsQueueGoodsAttr($query, $value)
|
||||
{
|
||||
if ($value === '' || $value === null) {
|
||||
return;
|
||||
}
|
||||
$query->where('is_queue_goods', (int)$value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统表单搜索器
|
||||
* @param Model $query
|
||||
|
||||
@@ -27,7 +27,13 @@ use think\facade\Route as Url;
|
||||
|
||||
|
||||
/**
|
||||
* 分销等级
|
||||
* 分销等级(改造后同时作为 HJF 会员等级服务)
|
||||
*
|
||||
* PRD 改造说明:
|
||||
* - 将原有的"分销员等级"概念替换为"会员等级"
|
||||
* - 升级条件从"推广订单数/消费金额"改为"直推单数 + 伞下业绩单数"(通过 task type 6/7/8 实现)
|
||||
* - 佣金计算从"按比例返佣"改为"按等级发放固定积分"(通过 direct_reward_points / umbrella_reward_points 实现)
|
||||
*
|
||||
* Class AgentLevelServices
|
||||
* @package app\services\agent
|
||||
* @mixin AgentLevelDao
|
||||
@@ -55,6 +61,88 @@ class AgentLevelServices extends BaseServices
|
||||
return $this->dao->getOne(['id' => $id, 'is_del' => 0], $field, $with);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -270,13 +358,10 @@ class AgentLevelServices extends BaseServices
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销等级上浮
|
||||
* @param int $uid
|
||||
* @param array $userInfo
|
||||
* @return array|int[]
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* 分销等级上浮(保留兼容,普通商品分销仍使用原逻辑)
|
||||
*
|
||||
* 注意:报单商品的积分奖励已由 PointsRewardServices 通过 direct_reward_points/umbrella_reward_points 处理,
|
||||
* 此方法仅用于普通商品的分销佣金计算。
|
||||
*/
|
||||
public function getAgentLevelBrokerage(int $uid, $userInfo = [])
|
||||
{
|
||||
@@ -285,7 +370,6 @@ class AgentLevelServices extends BaseServices
|
||||
if (!$uid) {
|
||||
return $data;
|
||||
}
|
||||
//商城分销是否开启
|
||||
if (!sys_config('brokerage_func_status')) {
|
||||
return $data;
|
||||
}
|
||||
@@ -297,7 +381,6 @@ class AgentLevelServices extends BaseServices
|
||||
if (!$userInfo) {
|
||||
return $data;
|
||||
}
|
||||
//获取上级uid || 开启自购返回自己uid
|
||||
$spread_uid = $userServices->getSpreadUid($uid, $userInfo);
|
||||
$one_agent_level = 0;
|
||||
$two_agent_level = 0;
|
||||
@@ -308,17 +391,66 @@ class AgentLevelServices extends BaseServices
|
||||
$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];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 agent_level ID 获取等级 grade(HJF 会员等级数字 0-4)
|
||||
*
|
||||
* @param int $agentLevelId eb_user.agent_level 值
|
||||
* @return int grade(0=普通会员, 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
|
||||
@@ -338,10 +470,7 @@ class AgentLevelServices extends BaseServices
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加等级表单
|
||||
* @param int $id
|
||||
* @return array
|
||||
* @throws \FormBuilder\Exception\FormBuilderException
|
||||
* 添加等级表单(改造后包含积分奖励字段)
|
||||
*/
|
||||
public function createForm()
|
||||
{
|
||||
@@ -352,17 +481,16 @@ class AgentLevelServices extends BaseServices
|
||||
$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('请选择字体颜色');
|
||||
$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' => '隐藏']]);
|
||||
return create_form('添加分销员等级', $field, Url::buildUrl('/agent/level'), 'POST');
|
||||
return create_form('添加会员等级', $field, Url::buildUrl('/agent/level'), 'POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取修改等级表单
|
||||
* @param int $id
|
||||
* @return array
|
||||
* @throws \FormBuilder\Exception\FormBuilderException
|
||||
* 获取修改等级表单(改造后包含积分奖励字段)
|
||||
*/
|
||||
public function editForm(int $id)
|
||||
{
|
||||
@@ -377,11 +505,13 @@ class AgentLevelServices extends BaseServices
|
||||
$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('请选择字体颜色');
|
||||
$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' => '隐藏']]);
|
||||
|
||||
return create_form('编辑分销员等级', $field, Url::buildUrl('/agent/level/' . $id), 'PUT');
|
||||
return create_form('编辑会员等级', $field, Url::buildUrl('/agent/level/' . $id), 'PUT');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,10 +20,12 @@ use crmeb\services\FormBuilder as Form;
|
||||
use FormBuilder\Factory\Iview;
|
||||
use think\annotation\Inject;
|
||||
use think\exception\ValidateException;
|
||||
use think\facade\Db;
|
||||
use think\facade\Route as Url;
|
||||
|
||||
|
||||
/**
|
||||
* 分销等级任务(改造后同时支持 HJF 会员等级升级任务)
|
||||
*
|
||||
* Class AgentLevelTaskServices
|
||||
* @package app\services\agent
|
||||
@@ -33,12 +35,13 @@ class AgentLevelTaskServices extends BaseServices
|
||||
{
|
||||
/**
|
||||
* 任务类型
|
||||
* type 记录在数据库中用来区分任务
|
||||
* name 任务名 (任务名中的{$num}会自动替换成设置的数字 + 单位)
|
||||
* max_number 最大设定数值 0为不限定
|
||||
* min_number 最小设定数值
|
||||
* unit 单位
|
||||
* */
|
||||
*
|
||||
* type 1-5: 原 CRMEB 分销任务类型
|
||||
* type 6-8: HJF 会员等级升级任务类型(改造新增)
|
||||
* 6 = 直推报单单数(直推下级购买报单商品的订单数)
|
||||
* 7 = 伞下报单业绩(含业绩分离逻辑)
|
||||
* 8 = 最低直推人数
|
||||
*/
|
||||
protected array $TaskType = [
|
||||
[
|
||||
'type' => 1,
|
||||
@@ -90,6 +93,36 @@ class AgentLevelTaskServices extends BaseServices
|
||||
'unit' => '单',
|
||||
'image' => '/uploads/system/agent_spread_order.png'
|
||||
],
|
||||
[
|
||||
'type' => 6,
|
||||
'method' => 'directQueueOrderCount',
|
||||
'name' => '直推报单满{$num}',
|
||||
'real_name' => '直推报单单数',
|
||||
'max_number' => 0,
|
||||
'min_number' => 1,
|
||||
'unit' => '单',
|
||||
'image' => '/uploads/system/agent_spread_order.png'
|
||||
],
|
||||
[
|
||||
'type' => 7,
|
||||
'method' => 'umbrellaQueueOrderCount',
|
||||
'name' => '伞下报单满{$num}',
|
||||
'real_name' => '伞下报单业绩',
|
||||
'max_number' => 0,
|
||||
'min_number' => 1,
|
||||
'unit' => '单',
|
||||
'image' => '/uploads/system/agent_spread_order.png'
|
||||
],
|
||||
[
|
||||
'type' => 8,
|
||||
'method' => 'directSpreadCount',
|
||||
'name' => '至少{$num}个直推',
|
||||
'real_name' => '最低直推人数',
|
||||
'max_number' => 0,
|
||||
'min_number' => 1,
|
||||
'unit' => '人',
|
||||
'image' => '/uploads/system/agent_spread.png'
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -299,12 +332,15 @@ class AgentLevelTaskServices extends BaseServices
|
||||
|
||||
/**
|
||||
* 检测某个任务完成情况
|
||||
*
|
||||
* type 1-5: 原 CRMEB 分销任务
|
||||
* type 6: 直推报单单数(HJF 改造)
|
||||
* type 7: 伞下报单业绩含业绩分离(HJF 改造)
|
||||
* type 8: 最低直推人数(HJF 改造)
|
||||
*
|
||||
* @param int $uid
|
||||
* @param int $task_id
|
||||
* @return array|false
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function checkLevelTaskFinish(int $uid, int $task_id, $levelTaskInfo = [])
|
||||
{
|
||||
@@ -356,6 +392,17 @@ class AgentLevelTaskServices extends BaseServices
|
||||
$userNumber = $storeOrderServices->count($where);
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
$userNumber = $this->getDirectQueueOrderCount($uid);
|
||||
break;
|
||||
case 7:
|
||||
$userNumber = $this->getUmbrellaQueueOrderCount($uid);
|
||||
break;
|
||||
case 8:
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
$userNumber = $userServices->count(['spread_uid' => $uid]);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -372,6 +419,104 @@ class AgentLevelTaskServices extends BaseServices
|
||||
return [$msg, $userNumber, $isComplete];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定等级的升级任务列表(不分页,供外部服务/控制器调用,避免直接访问 protected $dao)
|
||||
*
|
||||
* @param int $level_id eb_agent_level.id
|
||||
* @return array
|
||||
*/
|
||||
public function getUpgradeTasksForLevel(int $level_id): array
|
||||
{
|
||||
if ($level_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
return $this->dao->getTaskList(['level_id' => $level_id, 'is_del' => 0, 'status' => 1]) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计直推下级的报单订单数(type=6 任务)
|
||||
*
|
||||
* @param int $uid 用户 ID
|
||||
* @return int
|
||||
*/
|
||||
public function getDirectQueueOrderCount(int $uid): int
|
||||
{
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
$directUids = $userServices->getColumn(['spread_uid' => $uid], 'uid');
|
||||
if (empty($directUids)) {
|
||||
return 0;
|
||||
}
|
||||
return (int)Db::name('store_order')
|
||||
->whereIn('uid', $directUids)
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计伞下报单业绩(type=7 任务,含业绩分离逻辑)
|
||||
*
|
||||
* 业绩分离:若某直推下级已升级为云店或更高(grade≥2),
|
||||
* 则该下级及其团队的订单不计入本用户的伞下业绩。
|
||||
*
|
||||
* @param int $uid 用户 ID
|
||||
* @param int $maxDepth 递归最大深度
|
||||
* @return int
|
||||
*/
|
||||
public function getUmbrellaQueueOrderCount(int $uid, int $maxDepth = 8): int
|
||||
{
|
||||
return $this->recursiveUmbrellaCount($uid, $maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归统计伞下业绩(DFS),云店及以上等级的下级团队业绩被分离
|
||||
*/
|
||||
private function recursiveUmbrellaCount(int $uid, int $remainDepth): int
|
||||
{
|
||||
if ($remainDepth <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$directChildren = Db::name('user')
|
||||
->where('spread_uid', $uid)
|
||||
->field('uid,agent_level')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
if (empty($directChildren)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @var AgentLevelServices $levelServices */
|
||||
$levelServices = app()->make(AgentLevelServices::class);
|
||||
$total = 0;
|
||||
|
||||
foreach ($directChildren as $child) {
|
||||
$childGrade = 0;
|
||||
if (!empty($child['agent_level'])) {
|
||||
$childLevelInfo = $levelServices->getLevelInfo((int)$child['agent_level']);
|
||||
$childGrade = (int)($childLevelInfo['grade'] ?? 0);
|
||||
}
|
||||
|
||||
if ($childGrade >= 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$total += (int)Db::name('store_order')
|
||||
->where('uid', $child['uid'])
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->count();
|
||||
|
||||
$total += $this->recursiveUmbrellaCount((int)$child['uid'], $remainDepth - 1);
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测等级任务
|
||||
* @param int $id
|
||||
|
||||
@@ -3,24 +3,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\services\hjf;
|
||||
|
||||
use app\dao\user\UserDao;
|
||||
use app\services\agent\AgentLevelServices;
|
||||
use app\services\agent\AgentLevelTaskServices;
|
||||
use app\services\BaseServices;
|
||||
use crmeb\services\SystemConfigService;
|
||||
use app\services\user\UserServices;
|
||||
use think\annotation\Inject;
|
||||
use think\facade\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 会员等级升级服务
|
||||
* 会员等级升级服务(改造复用版)
|
||||
*
|
||||
* 升级条件(PRD 3.2.1):
|
||||
* - 普通会员 → 创客:直推3单(hjf_level_direct_require_1,默认3)
|
||||
* - 创客 → 云店:伞下业绩30单 + 至少3个直推(hjf_level_umbrella_require_2,默认30)
|
||||
* - 云店 → 服务商:伞下业绩100单 + 至少3个直推(hjf_level_umbrella_require_3,默认100)
|
||||
* - 服务商 → 分公司:伞下业绩1000单 + 至少3个直推(hjf_level_umbrella_require_4,默认1000)
|
||||
* 基于 CRMEB Pro 的团队分销等级功能进行改造:
|
||||
* - 使用 eb_user.agent_level (FK → eb_agent_level.id) 代替独立的 member_level
|
||||
* - 升级条件通过 eb_agent_level_task 的 type 6/7/8 定义
|
||||
* - 升级逻辑委托给 AgentLevelServices::checkUserLevelFinish()
|
||||
*
|
||||
* 伞下业绩分离:当某直推下级已升级到云店(level≥2)后,
|
||||
* 该下级及其整个团队的业绩不再计入本级的伞下业绩。
|
||||
* 本服务保留为薄封装层,提供 HJF 特有的查询方法供控制器调用。
|
||||
*
|
||||
* Class MemberLevelServices
|
||||
* @package app\services\hjf
|
||||
@@ -28,111 +27,70 @@ use think\facade\Log;
|
||||
class MemberLevelServices extends BaseServices
|
||||
{
|
||||
#[Inject]
|
||||
protected UserDao $userDao;
|
||||
protected AgentLevelServices $agentLevelServices;
|
||||
|
||||
/**
|
||||
* 各等级升级所需直推单数(0→1升级条件)
|
||||
*/
|
||||
const DIRECT_REQUIRE_KEYS = [
|
||||
1 => 'hjf_level_direct_require_1', // 普通→创客:直推N单
|
||||
];
|
||||
|
||||
/**
|
||||
* 各等级升级所需伞下单数(n-1→n升级条件,n≥2)
|
||||
*/
|
||||
const UMBRELLA_REQUIRE_KEYS = [
|
||||
2 => 'hjf_level_umbrella_require_2', // 创客→云店
|
||||
3 => 'hjf_level_umbrella_require_3', // 云店→服务商
|
||||
4 => 'hjf_level_umbrella_require_4', // 服务商→分公司
|
||||
];
|
||||
|
||||
/**
|
||||
* 默认升级门槛
|
||||
*/
|
||||
const DEFAULT_DIRECT_REQUIRE = [1 => 3];
|
||||
const DEFAULT_UMBRELLA_REQUIRE = [2 => 30, 3 => 100, 4 => 1000];
|
||||
|
||||
/**
|
||||
* 最低直推人数要求(云店及以上需要至少3个直推)
|
||||
*/
|
||||
const MIN_DIRECT_SPREAD_COUNT = 3;
|
||||
#[Inject]
|
||||
protected AgentLevelTaskServices $agentLevelTaskServices;
|
||||
|
||||
/**
|
||||
* 检查并执行升级(异步触发入口)
|
||||
*
|
||||
* @param int $uid 被检查的用户 ID
|
||||
* 委托给 CRMEB 的 AgentLevelServices 复用原有升级检测流程,
|
||||
* 该流程已支持 type 6/7/8 的 HJF 任务类型。
|
||||
*/
|
||||
public function checkUpgrade(int $uid): void
|
||||
{
|
||||
try {
|
||||
$user = $this->userDao->get($uid);
|
||||
if (!$user) {
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
$userInfo = $userServices->getUserCacheInfo($uid);
|
||||
if (!$userInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentLevel = (int)($user['member_level'] ?? 0);
|
||||
$nextLevel = $currentLevel + 1;
|
||||
|
||||
if ($nextLevel > 4) {
|
||||
return; // 已是最高等级
|
||||
$spreadUid = $userServices->getSpreadUid($uid, $userInfo);
|
||||
$twoSpreadUid = 0;
|
||||
if ($spreadUid > 0 && $oneUserInfo = $userServices->getUserCacheInfo($spreadUid)) {
|
||||
$twoSpreadUid = $userServices->getSpreadUid($spreadUid, $oneUserInfo, false);
|
||||
}
|
||||
$uids = array_unique([$uid, $spreadUid, $twoSpreadUid]);
|
||||
|
||||
$qualified = $this->checkLevelCondition($uid, $currentLevel, $nextLevel);
|
||||
if ($qualified) {
|
||||
$this->upgrade($uid, $nextLevel);
|
||||
|
||||
// 升级后继续检查是否可连续升级
|
||||
$this->checkUpgrade($uid);
|
||||
}
|
||||
$this->agentLevelServices->checkUserLevelFinish($uid, $uids);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("[MemberLevel] checkUpgrade uid={$uid}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否满足从 currentLevel 升到 nextLevel 的条件
|
||||
* 获取用户当前会员等级 grade(0=普通, 1=创客, 2=云店, 3=服务商, 4=分公司)
|
||||
*/
|
||||
private function checkLevelCondition(int $uid, int $currentLevel, int $nextLevel): bool
|
||||
public function getUserGrade(int $uid): int
|
||||
{
|
||||
if ($nextLevel === 1) {
|
||||
// 普通→创客:统计直推报单数
|
||||
$require = $this->getDirectRequire(1);
|
||||
$count = $this->getDirectQueueOrderCount($uid);
|
||||
return $count >= $require;
|
||||
}
|
||||
|
||||
// 创客/云店/服务商→更高等级:伞下业绩 + 至少3个直推
|
||||
$umbrellaRequire = $this->getUmbrellaRequire($nextLevel);
|
||||
$umbrellaCount = $this->getUmbrellaQueueOrderCount($uid);
|
||||
|
||||
if ($umbrellaCount < $umbrellaRequire) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 需要至少3个直推(对 level≥2 的升级)
|
||||
$directCount = $this->getDirectSpreadCount($uid);
|
||||
return $directCount >= self::MIN_DIRECT_SPREAD_COUNT;
|
||||
$agentLevel = (int)Db::name('user')->where('uid', $uid)->value('agent_level');
|
||||
return $this->agentLevelServices->getGradeByLevelId($agentLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直推用户的报单订单数(直推层级 = 1 层)
|
||||
*
|
||||
* 报单商品标记:`is_queue_goods = 1`(eb_store_order 中的字段)
|
||||
* 获取用户当前等级名称
|
||||
*/
|
||||
public function getUserLevelName(int $uid): string
|
||||
{
|
||||
$agentLevel = (int)Db::name('user')->where('uid', $uid)->value('agent_level');
|
||||
if ($agentLevel <= 0) {
|
||||
return '普通会员';
|
||||
}
|
||||
$maps = $this->agentLevelServices->loadHjfUserListLevelMaps();
|
||||
$info = $this->agentLevelServices->pickHjfLevelRowForUserListDisplay($agentLevel, $maps);
|
||||
|
||||
return $info['name'] ?? '普通会员';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直推用户的报单订单数
|
||||
*/
|
||||
public function getDirectQueueOrderCount(int $uid): int
|
||||
{
|
||||
// 查询直推用户 uid 列表
|
||||
$directUids = $this->userDao->getColumn(['spread_uid' => $uid], 'uid');
|
||||
if (empty($directUids)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)Db::name('store_order')
|
||||
->whereIn('uid', $directUids)
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->count();
|
||||
return $this->agentLevelTaskServices->getDirectQueueOrderCount($uid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,107 +98,39 @@ class MemberLevelServices extends BaseServices
|
||||
*/
|
||||
public function getDirectSpreadCount(int $uid): int
|
||||
{
|
||||
return (int)$this->userDao->count(['spread_uid' => $uid]);
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
return (int)$userServices->count(['spread_uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取伞下总报单订单数(含业绩分离逻辑)
|
||||
*
|
||||
* 业绩分离:若某直推下级已升级为云店(level≥2),
|
||||
* 则该下级及其团队的订单不计入本用户的伞下业绩。
|
||||
*
|
||||
* @param int $uid 统计对象用户 ID
|
||||
* @param int $maxDepth 递归最大深度,防止死循环
|
||||
*/
|
||||
public function getUmbrellaQueueOrderCount(int $uid, int $maxDepth = 8): int
|
||||
public function getUmbrellaQueueOrderCount(int $uid): int
|
||||
{
|
||||
return $this->recursiveUmbrellaCount($uid, $maxDepth);
|
||||
return $this->agentLevelTaskServices->getUmbrellaQueueOrderCount($uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归统计伞下业绩(DFS)
|
||||
*/
|
||||
private function recursiveUmbrellaCount(int $uid, int $remainDepth): int
|
||||
{
|
||||
if ($remainDepth <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$directChildren = $this->userDao->selectList(
|
||||
['spread_uid' => $uid],
|
||||
'uid,member_level',
|
||||
0, 0, 'uid', 'asc'
|
||||
);
|
||||
|
||||
if (empty($directChildren)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
foreach ($directChildren as $child) {
|
||||
$childLevel = (int)($child['member_level'] ?? 0);
|
||||
|
||||
// 业绩分离:直推下级已是云店或以上(level≥2),其团队业绩不计入本级
|
||||
if ($childLevel >= 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 统计该下级自身的报单订单数
|
||||
$total += (int)Db::name('store_order')
|
||||
->where('uid', $child['uid'])
|
||||
->where('is_queue_goods', 1)
|
||||
->where('paid', 1)
|
||||
->where('is_del', 0)
|
||||
->count();
|
||||
|
||||
// 递归统计该下级的伞下
|
||||
$total += $this->recursiveUmbrellaCount((int)$child['uid'], $remainDepth - 1);
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行升级
|
||||
* 手动设置会员等级(管理后台使用)
|
||||
*
|
||||
* @param int $uid 用户 ID
|
||||
* @param int $newLevel 新等级
|
||||
* @param int $grade 目标等级 grade (0-4)
|
||||
*/
|
||||
public function upgrade(int $uid, int $newLevel): void
|
||||
public function setUserLevel(int $uid, int $grade): void
|
||||
{
|
||||
Db::transaction(function () use ($uid, $newLevel) {
|
||||
$this->userDao->update($uid, ['member_level' => $newLevel], 'uid');
|
||||
|
||||
Log::info("[MemberLevel] uid={$uid} 升级到 level={$newLevel}");
|
||||
});
|
||||
|
||||
// 升级后通知推荐链上级重新检查
|
||||
$user = $this->userDao->get($uid);
|
||||
if ($user && $user['spread_uid']) {
|
||||
// 异步检查上级升级(防止递归过深直接调用)
|
||||
try {
|
||||
app(\app\jobs\hjf\MemberLevelCheckJob::class)::dispatch($user['spread_uid']);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning("[MemberLevel] 无法派发上级检查 Job: " . $e->getMessage());
|
||||
}
|
||||
$agentLevelId = 0;
|
||||
if ($grade > 0) {
|
||||
$agentLevelId = $this->agentLevelServices->getLevelIdByGrade($grade);
|
||||
if ($agentLevelId <= 0) {
|
||||
throw new \think\exception\ValidateException("等级 grade={$grade} 在 eb_agent_level 中不存在");
|
||||
}
|
||||
}
|
||||
|
||||
private function getDirectRequire(int $level): int
|
||||
{
|
||||
$key = self::DIRECT_REQUIRE_KEYS[$level] ?? '';
|
||||
if (!$key) {
|
||||
return self::DEFAULT_DIRECT_REQUIRE[$level] ?? 3;
|
||||
}
|
||||
return (int)SystemConfigService::get($key, self::DEFAULT_DIRECT_REQUIRE[$level] ?? 3);
|
||||
}
|
||||
/** @var UserServices $userServices */
|
||||
$userServices = app()->make(UserServices::class);
|
||||
$userServices->update($uid, ['agent_level' => $agentLevelId]);
|
||||
|
||||
private function getUmbrellaRequire(int $level): int
|
||||
{
|
||||
$key = self::UMBRELLA_REQUIRE_KEYS[$level] ?? '';
|
||||
if (!$key) {
|
||||
return self::DEFAULT_UMBRELLA_REQUIRE[$level] ?? 9999;
|
||||
}
|
||||
return (int)SystemConfigService::get($key, self::DEFAULT_UMBRELLA_REQUIRE[$level] ?? 9999);
|
||||
Log::info("[MemberLevel] 手动设置 uid={$uid} agent_level={$agentLevelId} (grade={$grade})");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,19 @@ namespace app\services\hjf;
|
||||
|
||||
use app\dao\hjf\PointsReleaseLogDao;
|
||||
use app\dao\user\UserDao;
|
||||
use app\services\agent\AgentLevelServices;
|
||||
use app\services\BaseServices;
|
||||
use crmeb\services\SystemConfigService;
|
||||
use think\annotation\Inject;
|
||||
use think\facade\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 积分奖励服务(级差计算)
|
||||
* 积分奖励服务(级差计算)—— 改造复用版
|
||||
*
|
||||
* 触发时机:报单商品订单支付回调成功后调用 reward($orderUid, $orderId)。
|
||||
*
|
||||
* 奖励规则(PRD 3.2):
|
||||
* - 推荐人(直推上级)获得 直推奖励积分(按推荐人等级)
|
||||
* - 更上级获得 级差积分(上级积分 - 直接下级已获得的积分)
|
||||
* - 所有奖励积分写入 frozen_points(待释放状态)
|
||||
* - 同时写 points_release_log 记录明细
|
||||
* 改造要点(PRD 3.2.2):
|
||||
* - 使用 eb_user.agent_level (FK → eb_agent_level.id) 获取会员等级
|
||||
* - 从 eb_agent_level 表的 direct_reward_points / umbrella_reward_points 字段读取奖励积分
|
||||
* - 不再使用独立的 member_level 字段和系统配置表中的 hjf_reward_* 键
|
||||
*
|
||||
* Class PointsRewardServices
|
||||
* @package app\services\hjf
|
||||
@@ -33,50 +30,19 @@ class PointsRewardServices extends BaseServices
|
||||
#[Inject]
|
||||
protected UserDao $userDao;
|
||||
|
||||
/**
|
||||
* 各等级直推奖励积分配置键
|
||||
*/
|
||||
const DIRECT_REWARD_KEYS = [
|
||||
0 => 0, // 普通会员:无直推奖励
|
||||
1 => 'hjf_reward_direct_1', // 创客
|
||||
2 => 'hjf_reward_direct_2', // 云店
|
||||
3 => 'hjf_reward_direct_3', // 服务商
|
||||
4 => 'hjf_reward_direct_4', // 分公司
|
||||
];
|
||||
|
||||
/**
|
||||
* 各等级伞下奖励积分配置键
|
||||
*/
|
||||
const UMBRELLA_REWARD_KEYS = [
|
||||
0 => 0,
|
||||
1 => 'hjf_reward_umbrella_1',
|
||||
2 => 'hjf_reward_umbrella_2',
|
||||
3 => 'hjf_reward_umbrella_3',
|
||||
4 => 'hjf_reward_umbrella_4',
|
||||
];
|
||||
|
||||
/**
|
||||
* 默认积分奖励(当系统配置未初始化时使用)
|
||||
*/
|
||||
const DEFAULT_DIRECT = [0 => 0, 1 => 500, 2 => 800, 3 => 1000, 4 => 1300];
|
||||
const DEFAULT_UMBRELLA = [0 => 0, 1 => 0, 2 => 300, 3 => 200, 4 => 300];
|
||||
#[Inject]
|
||||
protected AgentLevelServices $agentLevelServices;
|
||||
|
||||
/**
|
||||
* 对一笔报单订单发放积分奖励
|
||||
*
|
||||
* @param int $orderUid 下单用户 ID
|
||||
* @param string $orderId 订单号
|
||||
*/
|
||||
public function reward(int $orderUid, string $orderId): void
|
||||
{
|
||||
try {
|
||||
// 获取下单用户信息
|
||||
$buyer = $this->userDao->get($orderUid);
|
||||
if (!$buyer || !$buyer['spread_uid']) {
|
||||
return; // 无推荐人,不发奖励
|
||||
return;
|
||||
}
|
||||
|
||||
// 沿推荐链向上遍历,计算级差奖励
|
||||
$this->propagateReward($buyer['spread_uid'], $orderUid, $orderId, 0);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("[PointsReward] 积分奖励失败 orderUid={$orderUid} orderId={$orderId}: " . $e->getMessage());
|
||||
@@ -90,7 +56,7 @@ class PointsRewardServices extends BaseServices
|
||||
* @param int $fromUid 触发方(下级)用户 ID
|
||||
* @param string $orderId 来源订单号
|
||||
* @param int $lowerReward 下级已获得的直推/伞下奖励积分(用于级差扣减)
|
||||
* @param int $depth 递归深度(最多遍历10层)
|
||||
* @param int $depth 递归深度
|
||||
*/
|
||||
private function propagateReward(
|
||||
int $uid,
|
||||
@@ -108,22 +74,21 @@ class PointsRewardServices extends BaseServices
|
||||
return;
|
||||
}
|
||||
|
||||
$level = (int)($user['member_level'] ?? 0);
|
||||
if ($level === 0) {
|
||||
// 普通会员不获得奖励,但继续向上传递
|
||||
$agentLevelId = (int)($user['agent_level'] ?? 0);
|
||||
$grade = $this->agentLevelServices->getGradeByLevelId($agentLevelId);
|
||||
|
||||
if ($grade === 0) {
|
||||
if ($user['spread_uid']) {
|
||||
$this->propagateReward((int)$user['spread_uid'], $uid, $orderId, 0, $depth + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断是直推还是伞下(depth=0 说明是第一个上级,即直推)
|
||||
$isDirect = ($depth === 0);
|
||||
$reward = $isDirect
|
||||
? $this->getDirectReward($level)
|
||||
: $this->getUmbrellaReward($level);
|
||||
? $this->agentLevelServices->getDirectRewardPoints($agentLevelId)
|
||||
: $this->agentLevelServices->getUmbrellaRewardPoints($agentLevelId);
|
||||
|
||||
// 级差:本次实发 = 本等级应得 - 下级已获得
|
||||
$actual = max(0, $reward - $lowerReward);
|
||||
|
||||
if ($actual > 0) {
|
||||
@@ -136,13 +101,12 @@ class PointsRewardServices extends BaseServices
|
||||
);
|
||||
}
|
||||
|
||||
// 继续向上传递(使用本级应得的 reward 作为下一级的 lowerReward)
|
||||
if ($user['spread_uid']) {
|
||||
$this->propagateReward(
|
||||
(int)$user['spread_uid'],
|
||||
$uid,
|
||||
$orderId,
|
||||
$reward, // 传递本级"应得"(而非实发)给上级做级差
|
||||
$reward,
|
||||
$depth + 1
|
||||
);
|
||||
}
|
||||
@@ -154,10 +118,8 @@ class PointsRewardServices extends BaseServices
|
||||
private function grantFrozenPoints(int $uid, int $points, string $orderId, string $type, string $mark): void
|
||||
{
|
||||
Db::transaction(function () use ($uid, $points, $orderId, $type, $mark) {
|
||||
// 增加 frozen_points
|
||||
$this->userDao->bcInc($uid, 'frozen_points', $points, 'uid');
|
||||
|
||||
// 写明细日志
|
||||
$this->logDao->save([
|
||||
'uid' => $uid,
|
||||
'points' => $points,
|
||||
@@ -170,22 +132,4 @@ class PointsRewardServices extends BaseServices
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
private function getDirectReward(int $level): int
|
||||
{
|
||||
$key = self::DIRECT_REWARD_KEYS[$level] ?? 0;
|
||||
if (!$key) {
|
||||
return self::DEFAULT_DIRECT[$level] ?? 0;
|
||||
}
|
||||
return (int)SystemConfigService::get($key, self::DEFAULT_DIRECT[$level] ?? 0);
|
||||
}
|
||||
|
||||
private function getUmbrellaReward(int $level): int
|
||||
{
|
||||
$key = self::UMBRELLA_REWARD_KEYS[$level] ?? 0;
|
||||
if (!$key) {
|
||||
return self::DEFAULT_UMBRELLA[$level] ?? 0;
|
||||
}
|
||||
return (int)SystemConfigService::get($key, self::DEFAULT_UMBRELLA[$level] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,7 +766,6 @@ class UserServices extends BaseServices
|
||||
*/
|
||||
public function index(array $where)
|
||||
{
|
||||
try {
|
||||
// 添加过滤条件
|
||||
$where['is_filter_del'] = 1;
|
||||
|
||||
@@ -774,10 +773,19 @@ class UserServices extends BaseServices
|
||||
$userWechatUser = app()->make(UserWechatuserServices::class);
|
||||
$fields = 'u.*,w.country,w.province,w.city,w.sex,w.unionid,w.openid,w.user_type as w_user_type,w.groupid,w.tagid_list,w.subscribe,w.subscribe_time';
|
||||
|
||||
// 获取用户列表
|
||||
try {
|
||||
[$list, $count] = $userWechatUser->getWhereUserList($where, $fields);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('User index list query failed: ' . $e->getMessage());
|
||||
|
||||
if ($list) {
|
||||
return ['count' => 0, 'list' => []];
|
||||
}
|
||||
|
||||
if (!$list) {
|
||||
return compact('count', 'list');
|
||||
}
|
||||
|
||||
try {
|
||||
// 提取唯一 UID 列表
|
||||
$uids = array_unique(array_column($list, 'uid'));
|
||||
|
||||
@@ -799,6 +807,11 @@ class UserServices extends BaseServices
|
||||
$clientData = $workClientService->getList(['uid' => $uids], ['id', 'uid', 'name', 'external_userid', 'corp_id', 'unionid'], false);
|
||||
$clientlist = $clientData['list'] ?? [];
|
||||
|
||||
/** HJF:分销等级展示索引(is_del=0,按 id/grade 双索引,优先 HJF 官方等级名称) */
|
||||
/** @var AgentLevelServices $agentLevelServices */
|
||||
$agentLevelServices = app()->make(AgentLevelServices::class);
|
||||
$hjfLevelMaps = $agentLevelServices->loadHjfUserListLevelMaps();
|
||||
|
||||
// 补充信息
|
||||
$extendInfo = SystemConfigService::get('user_extend_info', []);
|
||||
$is_extend_info = false;
|
||||
@@ -826,6 +839,13 @@ class UserServices extends BaseServices
|
||||
|
||||
// 补充每个用户的详细信息
|
||||
foreach ($list as &$item) {
|
||||
$agentLevelId = (int)($item['agent_level'] ?? 0);
|
||||
$hjfLevelInfo = $agentLevelServices->pickHjfLevelRowForUserListDisplay($agentLevelId, $hjfLevelMaps);
|
||||
$item['member_level'] = $hjfLevelInfo ? (int)$hjfLevelInfo['grade'] : 0;
|
||||
$item['member_level_name'] = $hjfLevelInfo ? ($hjfLevelInfo['name'] ?? '') : '普通会员';
|
||||
$item['available_points'] = (int)($item['available_points'] ?? 0);
|
||||
$item['frozen_points'] = (int)($item['frozen_points'] ?? 0);
|
||||
|
||||
// 地址补充
|
||||
if (empty($item['addres'])) {
|
||||
if (!empty($item['country']) || !empty($item['province']) || !empty($item['city'])) {
|
||||
@@ -895,13 +915,13 @@ class UserServices extends BaseServices
|
||||
// 扩展信息标志
|
||||
$item['is_extend_info'] = $is_extend_info;
|
||||
}
|
||||
}
|
||||
|
||||
return compact('count', 'list');
|
||||
} catch (\Exception $e) {
|
||||
// 异常处理
|
||||
Log::error('Error in user index: ' . $e->getMessage());
|
||||
return ['count' => 0, 'list' => []];
|
||||
} catch (\Throwable $e) {
|
||||
// 加工阶段失败时仍返回查询结果,避免整页空白(常见:企微/等级等扩展服务异常)
|
||||
Log::error('User index enrichment failed: ' . $e->getMessage());
|
||||
|
||||
return compact('count', 'list');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ declare (strict_types=1);
|
||||
|
||||
namespace app\services\user;
|
||||
|
||||
use app\services\agent\AgentLevelServices;
|
||||
use app\services\BaseServices;
|
||||
use app\dao\user\UserWechatUserDao;
|
||||
use think\annotation\Inject;
|
||||
@@ -48,6 +49,7 @@ class UserWechatuserServices extends BaseServices
|
||||
*/
|
||||
public function getWhereUserList(array $where, string $field): array
|
||||
{
|
||||
$where = $this->normalizeHjfMemberLevelWhere($where);
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
$order_string = '';
|
||||
$order_arr = ['asc', 'desc'];
|
||||
@@ -58,4 +60,40 @@ class UserWechatuserServices extends BaseServices
|
||||
$count = $this->dao->getCountByWhere($where);
|
||||
return [$list, $count];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将会员列表筛选「HJF 等级(grade)」转为 eb_user.agent_level 条件,供 UserWechatUserDao 使用。
|
||||
*/
|
||||
protected function normalizeHjfMemberLevelWhere(array $where): array
|
||||
{
|
||||
if (!array_key_exists('hjf_member_level', $where)) {
|
||||
return $where;
|
||||
}
|
||||
$raw = $where['hjf_member_level'];
|
||||
if ($raw === null) {
|
||||
unset($where['hjf_member_level']);
|
||||
|
||||
return $where;
|
||||
}
|
||||
if (is_string($raw)) {
|
||||
$raw = trim($raw);
|
||||
}
|
||||
// 空串/仅空白:不按分销等级筛选(避免 (int)' '=>0 误加 agent_level=0)
|
||||
if ($raw === '') {
|
||||
unset($where['hjf_member_level']);
|
||||
|
||||
return $where;
|
||||
}
|
||||
$grade = (int)$raw;
|
||||
/** @var AgentLevelServices $agentLevel */
|
||||
$agentLevel = app()->make(AgentLevelServices::class);
|
||||
if ($grade === 0) {
|
||||
$where['hjf_agent_level_id'] = 0;
|
||||
} else {
|
||||
$where['hjf_agent_level_id'] = $agentLevel->getLevelIdByGrade($grade) ?: -1;
|
||||
}
|
||||
unset($where['hjf_member_level']);
|
||||
|
||||
return $where;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
54
pro_v3.5.1/database/eb_agent_level.sql
Normal file
54
pro_v3.5.1/database/eb_agent_level.sql
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Navicat Premium Dump SQL
|
||||
|
||||
Source Server : jxy-hjf-db
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 50740 (5.7.40-log)
|
||||
Source Host : 182.92.142.158:3306
|
||||
Source Schema : hjfshop
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 50740 (5.7.40-log)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 21/03/2026 22:47:01
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for eb_agent_level
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `eb_agent_level`;
|
||||
CREATE TABLE `eb_agent_level` (
|
||||
`id` int(10) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '等级名称',
|
||||
`image` varchar(255) NOT NULL DEFAULT '' COMMENT '背景图',
|
||||
`color` varchar(32) NOT NULL DEFAULT '' COMMENT ' 字体颜色',
|
||||
`one_brokerage` smallint(5) NOT NULL DEFAULT '0' COMMENT '一级分拥比例',
|
||||
`two_brokerage` smallint(5) NOT NULL DEFAULT '0' COMMENT '二级分拥比例',
|
||||
`grade` smallint(5) NOT NULL DEFAULT '0' COMMENT '等级',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态',
|
||||
`is_del` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
|
||||
`add_time` int(10) NOT NULL DEFAULT '0' COMMENT '添加时间',
|
||||
`direct_reward_points` int(11) NOT NULL DEFAULT '0' COMMENT '直推奖励积分(每单)',
|
||||
`umbrella_reward_points` int(11) NOT NULL DEFAULT '0' COMMENT '伞下奖励积分(每单,级差基数)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `status` (`status`,`is_del`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='分销员等级表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of eb_agent_level
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `eb_agent_level` (`id`, `name`, `image`, `color`, `one_brokerage`, `two_brokerage`, `grade`, `status`, `is_del`, `add_time`, `direct_reward_points`, `umbrella_reward_points`) VALUES (1, '创客', '/uploads/system/agent_level_1.png', '#D97E1D', 1, 0, 1, 1, 0, 1700126550, 500, 0);
|
||||
INSERT INTO `eb_agent_level` (`id`, `name`, `image`, `color`, `one_brokerage`, `two_brokerage`, `grade`, `status`, `is_del`, `add_time`, `direct_reward_points`, `umbrella_reward_points`) VALUES (2, '云店', '/uploads/system/agent_level_2.png', '#5D7DAC', 5, 3, 2, 1, 0, 1700126572, 800, 300);
|
||||
INSERT INTO `eb_agent_level` (`id`, `name`, `image`, `color`, `one_brokerage`, `two_brokerage`, `grade`, `status`, `is_del`, `add_time`, `direct_reward_points`, `umbrella_reward_points`) VALUES (3, '服务商', '/uploads/system/agent_level_3.png', '#5856D6', 10, 5, 3, 1, 0, 1700126595, 1000, 200);
|
||||
INSERT INTO `eb_agent_level` (`id`, `name`, `image`, `color`, `one_brokerage`, `two_brokerage`, `grade`, `status`, `is_del`, `add_time`, `direct_reward_points`, `umbrella_reward_points`) VALUES (4, '分公司', '/uploads/system/agent_level_4.png', '#1DB0FC', 12, 7, 4, 1, 0, 1700126621, 1300, 300);
|
||||
INSERT INTO `eb_agent_level` (`id`, `name`, `image`, `color`, `one_brokerage`, `two_brokerage`, `grade`, `status`, `is_del`, `add_time`, `direct_reward_points`, `umbrella_reward_points`) VALUES (5, '等级五', '/uploads/system/agent_level_5.png', '#AF52DE', 19, 12, 5, 0, 1, 1701764897, 0, 0);
|
||||
INSERT INTO `eb_agent_level` (`id`, `name`, `image`, `color`, `one_brokerage`, `two_brokerage`, `grade`, `status`, `is_del`, `add_time`, `direct_reward_points`, `umbrella_reward_points`) VALUES (6, '服务商1', '', '#9C27B0', 0, 0, 3, 0, 1, 1774091023, 1000, 200);
|
||||
INSERT INTO `eb_agent_level` (`id`, `name`, `image`, `color`, `one_brokerage`, `two_brokerage`, `grade`, `status`, `is_del`, `add_time`, `direct_reward_points`, `umbrella_reward_points`) VALUES (7, '分公司2', '', '#F44336', 0, 0, 4, 0, 1, 1774091023, 1300, 300);
|
||||
COMMIT;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -1,15 +1,17 @@
|
||||
-- ============================================================
|
||||
-- 黄精粉健康商城 HJF 数据库迁移脚本
|
||||
-- 版本:Phase 2
|
||||
-- 日期:2026-03-15
|
||||
-- 版本:Phase 3(改造复用版)
|
||||
-- 日期:2026-03-21
|
||||
-- 执行说明:
|
||||
-- 1. 兼容 MySQL 5.7+,数据库前缀为 eb_
|
||||
-- 2. 按顺序执行 P2-01 ~ P2-05
|
||||
-- 2. 按顺序执行 P3-01 ~ P3-07
|
||||
-- 3. 所有操作均做幂等处理,可重复执行
|
||||
-- 4. 遵循 PRD 改造复用原则:会员等级复用 eb_agent_level 体系,
|
||||
-- 使用 eb_user.agent_level (FK) 代替独立的 member_level 字段
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- P2-01: 公排池表
|
||||
-- P3-01: 公排池表
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `eb_queue_pool` (
|
||||
@@ -31,7 +33,7 @@ CREATE TABLE IF NOT EXISTS `eb_queue_pool` (
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- P2-02: 积分释放日志表
|
||||
-- P3-02: 积分释放日志表
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `eb_points_release_log` (
|
||||
@@ -54,10 +56,42 @@ CREATE TABLE IF NOT EXISTS `eb_points_release_log` (
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- P2-03 / P2-04: eb_user / eb_store_product / eb_store_order 扩展字段
|
||||
-- P3-03: eb_agent_level 扩展字段(改造复用:增加积分奖励字段)
|
||||
-- ============================================================
|
||||
|
||||
DROP PROCEDURE IF EXISTS `hjf_migrate_agent_level`;
|
||||
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `hjf_migrate_agent_level`()
|
||||
BEGIN
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_agent_level' AND COLUMN_NAME = 'direct_reward_points'
|
||||
) THEN
|
||||
ALTER TABLE `eb_agent_level`
|
||||
ADD COLUMN `direct_reward_points` int(11) NOT NULL DEFAULT 0 COMMENT '直推奖励积分(每单)';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_agent_level' AND COLUMN_NAME = 'umbrella_reward_points'
|
||||
) THEN
|
||||
ALTER TABLE `eb_agent_level`
|
||||
ADD COLUMN `umbrella_reward_points` int(11) NOT NULL DEFAULT 0 COMMENT '伞下奖励积分(每单,级差基数)';
|
||||
END IF;
|
||||
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CALL `hjf_migrate_agent_level`();
|
||||
DROP PROCEDURE IF EXISTS `hjf_migrate_agent_level`;
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- P3-04: eb_user / eb_store_product / eb_store_order 扩展字段
|
||||
--
|
||||
-- MySQL 5.7 不支持 "ADD COLUMN IF NOT EXISTS",
|
||||
-- 改用存储过程 + information_schema 实现幂等检查。
|
||||
-- 注意:不再新增 member_level 字段,复用已有的 agent_level (FK→eb_agent_level.id)
|
||||
-- ============================================================
|
||||
|
||||
DROP PROCEDURE IF EXISTS `hjf_migrate_columns`;
|
||||
@@ -66,15 +100,7 @@ DELIMITER $$
|
||||
CREATE PROCEDURE `hjf_migrate_columns`()
|
||||
BEGIN
|
||||
|
||||
-- ---- eb_user 字段 ----
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_user' AND COLUMN_NAME = 'member_level'
|
||||
) THEN
|
||||
ALTER TABLE `eb_user`
|
||||
ADD COLUMN `member_level` tinyint(1) NOT NULL DEFAULT 0 COMMENT '会员等级:0普通 1创客 2云店 3服务商 4分公司';
|
||||
END IF;
|
||||
|
||||
-- ---- eb_user 字段(不含 member_level,复用 agent_level)----
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_user' AND COLUMN_NAME = 'no_assess'
|
||||
@@ -99,14 +125,6 @@ BEGIN
|
||||
ADD COLUMN `available_points` int(11) NOT NULL DEFAULT 0 COMMENT '可用积分';
|
||||
END IF;
|
||||
|
||||
-- eb_user 索引:idx_member_level
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_user' AND INDEX_NAME = 'idx_member_level'
|
||||
) THEN
|
||||
ALTER TABLE `eb_user` ADD INDEX `idx_member_level` (`member_level`);
|
||||
END IF;
|
||||
|
||||
-- ---- eb_store_product 字段 ----
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
@@ -124,7 +142,6 @@ BEGIN
|
||||
ADD COLUMN `allow_pay_types` varchar(255) NOT NULL DEFAULT '' COMMENT '允许积分支付类型(JSON数组)';
|
||||
END IF;
|
||||
|
||||
-- eb_store_product 索引:idx_is_queue_goods
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_store_product' AND INDEX_NAME = 'idx_is_queue_goods'
|
||||
@@ -141,7 +158,6 @@ BEGIN
|
||||
ADD COLUMN `is_queue_goods` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否报单商品订单:1=是';
|
||||
END IF;
|
||||
|
||||
-- eb_store_order 索引:idx_is_queue_goods
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_store_order' AND INDEX_NAME = 'idx_is_queue_goods'
|
||||
@@ -157,115 +173,221 @@ DROP PROCEDURE IF EXISTS `hjf_migrate_columns`;
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- P2-05: eb_system_config 初始化配置项
|
||||
-- P3-05: 初始化会员等级数据到 eb_agent_level(改造复用)
|
||||
--
|
||||
-- 字段说明(与 CRMEB 原表保持一致):
|
||||
-- menu_name = 配置键名(代码中 SystemConfigService::get() 读取)
|
||||
-- value = 默认值(字符串)
|
||||
-- info = 后台显示名称
|
||||
-- desc = 说明文字
|
||||
-- config_tab_id = 0(不归属某分组,便于独立管理)
|
||||
-- status = 1(启用)
|
||||
-- 将原分销员等级改为五级会员等级体系:
|
||||
-- grade=1 → 创客 (direct=500, umbrella=0)
|
||||
-- grade=2 → 云店 (direct=800, umbrella=300)
|
||||
-- grade=3 → 服务商 (direct=1000, umbrella=200)
|
||||
-- grade=4 → 分公司 (direct=1300, umbrella=300)
|
||||
--
|
||||
-- 注意:普通会员 = agent_level=0(无记录),不需要插入
|
||||
--
|
||||
-- 先将 CRMEB 原有 demo 等级软删除,然后插入 HJF 会员等级
|
||||
-- ============================================================
|
||||
|
||||
UPDATE `eb_agent_level`
|
||||
SET `is_del` = 1
|
||||
WHERE `name` NOT IN ('创客', '云店', '服务商', '分公司')
|
||||
AND `is_del` = 0;
|
||||
|
||||
INSERT INTO `eb_agent_level`
|
||||
(`name`, `grade`, `image`, `color`, `one_brokerage`, `two_brokerage`,
|
||||
`direct_reward_points`, `umbrella_reward_points`, `status`, `is_del`, `add_time`)
|
||||
SELECT '创客', 1, '', '#FF9800', 0, 0, 500, 0, 1, 0, UNIX_TIMESTAMP()
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `eb_agent_level` WHERE `name` = '创客' AND `is_del` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `eb_agent_level`
|
||||
(`name`, `grade`, `image`, `color`, `one_brokerage`, `two_brokerage`,
|
||||
`direct_reward_points`, `umbrella_reward_points`, `status`, `is_del`, `add_time`)
|
||||
SELECT '云店', 2, '', '#2196F3', 0, 0, 800, 300, 1, 0, UNIX_TIMESTAMP()
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `eb_agent_level` WHERE `name` = '云店' AND `is_del` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `eb_agent_level`
|
||||
(`name`, `grade`, `image`, `color`, `one_brokerage`, `two_brokerage`,
|
||||
`direct_reward_points`, `umbrella_reward_points`, `status`, `is_del`, `add_time`)
|
||||
SELECT '服务商', 3, '', '#9C27B0', 0, 0, 1000, 200, 1, 0, UNIX_TIMESTAMP()
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `eb_agent_level` WHERE `name` = '服务商' AND `is_del` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `eb_agent_level`
|
||||
(`name`, `grade`, `image`, `color`, `one_brokerage`, `two_brokerage`,
|
||||
`direct_reward_points`, `umbrella_reward_points`, `status`, `is_del`, `add_time`)
|
||||
SELECT '分公司', 4, '', '#F44336', 0, 0, 1300, 300, 1, 0, UNIX_TIMESTAMP()
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `eb_agent_level` WHERE `name` = '分公司' AND `is_del` = 0
|
||||
);
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- P3-06: 初始化等级升级任务到 eb_agent_level_task(改造复用)
|
||||
--
|
||||
-- 新增任务类型:
|
||||
-- type=6 → 直推报单单数
|
||||
-- type=7 → 伞下报单业绩(含业绩分离)
|
||||
-- type=8 → 最低直推人数
|
||||
--
|
||||
-- 各等级任务配置:
|
||||
-- 创客(grade=1): type=6, number=3 (直推3单)
|
||||
-- 云店(grade=2): type=7, number=30 (伞下30单) + type=8, number=3 (至少3直推)
|
||||
-- 服务商(grade=3): type=7, number=100 + type=8, number=3
|
||||
-- 分公司(grade=4): type=7, number=1000 + type=8, number=3
|
||||
-- ============================================================
|
||||
|
||||
DROP PROCEDURE IF EXISTS `hjf_init_agent_tasks`;
|
||||
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `hjf_init_agent_tasks`()
|
||||
BEGIN
|
||||
DECLARE v_level_id_1 INT DEFAULT 0;
|
||||
DECLARE v_level_id_2 INT DEFAULT 0;
|
||||
DECLARE v_level_id_3 INT DEFAULT 0;
|
||||
DECLARE v_level_id_4 INT DEFAULT 0;
|
||||
|
||||
SELECT id INTO v_level_id_1 FROM eb_agent_level WHERE grade = 1 AND is_del = 0 LIMIT 1;
|
||||
SELECT id INTO v_level_id_2 FROM eb_agent_level WHERE grade = 2 AND is_del = 0 LIMIT 1;
|
||||
SELECT id INTO v_level_id_3 FROM eb_agent_level WHERE grade = 3 AND is_del = 0 LIMIT 1;
|
||||
SELECT id INTO v_level_id_4 FROM eb_agent_level WHERE grade = 4 AND is_del = 0 LIMIT 1;
|
||||
|
||||
-- 创客:直推报单3单
|
||||
IF v_level_id_1 > 0 AND NOT EXISTS (
|
||||
SELECT 1 FROM eb_agent_level_task WHERE level_id = v_level_id_1 AND type = 6 AND is_del = 0
|
||||
) THEN
|
||||
INSERT INTO eb_agent_level_task (level_id, `name`, type, number, `desc`, sort, status, is_del, add_time)
|
||||
VALUES (v_level_id_1, '直推报单满3单', 6, 3, '直推下级购买报单商品满3单升级为创客', 1, 1, 0, UNIX_TIMESTAMP());
|
||||
END IF;
|
||||
|
||||
-- 云店:伞下报单30单 + 至少3个直推
|
||||
IF v_level_id_2 > 0 THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM eb_agent_level_task WHERE level_id = v_level_id_2 AND type = 7 AND is_del = 0
|
||||
) THEN
|
||||
INSERT INTO eb_agent_level_task (level_id, `name`, type, number, `desc`, sort, status, is_del, add_time)
|
||||
VALUES (v_level_id_2, '伞下报单满30单', 7, 30, '伞下业绩(含分离)达到30单升级为云店', 1, 1, 0, UNIX_TIMESTAMP());
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM eb_agent_level_task WHERE level_id = v_level_id_2 AND type = 8 AND is_del = 0
|
||||
) THEN
|
||||
INSERT INTO eb_agent_level_task (level_id, `name`, type, number, `desc`, sort, status, is_del, add_time)
|
||||
VALUES (v_level_id_2, '至少3个直推', 8, 3, '需至少3个直推下级才可升级为云店', 2, 1, 0, UNIX_TIMESTAMP());
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- 服务商:伞下报单100单 + 至少3个直推
|
||||
IF v_level_id_3 > 0 THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM eb_agent_level_task WHERE level_id = v_level_id_3 AND type = 7 AND is_del = 0
|
||||
) THEN
|
||||
INSERT INTO eb_agent_level_task (level_id, `name`, type, number, `desc`, sort, status, is_del, add_time)
|
||||
VALUES (v_level_id_3, '伞下报单满100单', 7, 100, '伞下业绩(含分离)达到100单升级为服务商', 1, 1, 0, UNIX_TIMESTAMP());
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM eb_agent_level_task WHERE level_id = v_level_id_3 AND type = 8 AND is_del = 0
|
||||
) THEN
|
||||
INSERT INTO eb_agent_level_task (level_id, `name`, type, number, `desc`, sort, status, is_del, add_time)
|
||||
VALUES (v_level_id_3, '至少3个直推', 8, 3, '需至少3个直推下级才可升级为服务商', 2, 1, 0, UNIX_TIMESTAMP());
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- 分公司:伞下报单1000单 + 至少3个直推
|
||||
IF v_level_id_4 > 0 THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM eb_agent_level_task WHERE level_id = v_level_id_4 AND type = 7 AND is_del = 0
|
||||
) THEN
|
||||
INSERT INTO eb_agent_level_task (level_id, `name`, type, number, `desc`, sort, status, is_del, add_time)
|
||||
VALUES (v_level_id_4, '伞下报单满1000单', 7, 1000, '伞下业绩(含分离)达到1000单升级为分公司', 1, 1, 0, UNIX_TIMESTAMP());
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM eb_agent_level_task WHERE level_id = v_level_id_4 AND type = 8 AND is_del = 0
|
||||
) THEN
|
||||
INSERT INTO eb_agent_level_task (level_id, `name`, type, number, `desc`, sort, status, is_del, add_time)
|
||||
VALUES (v_level_id_4, '至少3个直推', 8, 3, '需至少3个直推下级才可升级为分公司', 2, 1, 0, UNIX_TIMESTAMP());
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CALL `hjf_init_agent_tasks`();
|
||||
DROP PROCEDURE IF EXISTS `hjf_init_agent_tasks`;
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- P3-07: eb_system_config 初始化配置项
|
||||
-- ============================================================
|
||||
|
||||
-- 防止重复执行报错,使用 INSERT IGNORE
|
||||
INSERT IGNORE INTO `eb_system_config`
|
||||
(`is_store`, `menu_name`, `type`, `input_type`, `config_tab_id`,
|
||||
`parameter`, `upload_type`, `required`, `width`, `high`,
|
||||
`value`, `info`, `desc`, `sort`, `status`)
|
||||
VALUES
|
||||
|
||||
-- 公排触发倍数:每入 N 单退款第1单(默认 4)
|
||||
(0, 'hjf_trigger_multiple', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'4', '公排触发倍数', '每进入N单公排触发退款第1单,默认4', 10, 1),
|
||||
|
||||
-- 积分每日释放比例(‰,默认 4,即 4‰)
|
||||
(0, 'hjf_release_rate', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'4', '积分每日释放比例(‰)', '每日释放:frozen_points × N / 1000,默认4(即4‰)', 20, 1),
|
||||
|
||||
-- 提现手续费率(%,默认 7,即 7%)
|
||||
(0, 'hjf_fee_rate', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'7', '提现手续费率(%)', '申请提现时收取的手续费比例,默认7%', 30, 1),
|
||||
'7', '提现手续费率(%)', '申请提现时收取的手续费比例,默认7%', 30, 1);
|
||||
|
||||
-- 等级升级门槛:普通→创客(直推N单)
|
||||
(0, 'hjf_level_direct_require_1', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'3', '创客升级所需直推单数', '普通会员直推N单报单商品后升级为创客,默认3', 40, 1),
|
||||
|
||||
-- 等级升级门槛:创客→云店(伞下N单)
|
||||
(0, 'hjf_level_umbrella_require_2', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'30', '云店升级所需伞下单数', '创客伞下业绩达到N单后升级为云店,默认30', 50, 1),
|
||||
-- ============================================================
|
||||
-- P3-08: 如果已有旧的 member_level 字段,将数据迁移到 agent_level
|
||||
-- ============================================================
|
||||
|
||||
-- 等级升级门槛:云店→服务商(伞下N单)
|
||||
(0, 'hjf_level_umbrella_require_3', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'100', '服务商升级所需伞下单数', '云店伞下业绩达到N单后升级为服务商,默认100', 60, 1),
|
||||
DROP PROCEDURE IF EXISTS `hjf_migrate_member_to_agent_level`;
|
||||
|
||||
-- 等级升级门槛:服务商→分公司(伞下N单)
|
||||
(0, 'hjf_level_umbrella_require_4', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'1000', '分公司升级所需伞下单数', '服务商伞下业绩达到N单后升级为分公司,默认1000', 70, 1),
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `hjf_migrate_member_to_agent_level`()
|
||||
BEGIN
|
||||
|
||||
-- 直推奖励积分:创客直推可得N积分
|
||||
(0, 'hjf_reward_direct_1', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'500', '创客直推奖励积分', '创客等级直推一单报单商品可获得的冻结积分,默认500', 80, 1),
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_user' AND COLUMN_NAME = 'member_level'
|
||||
) THEN
|
||||
-- 将 member_level 数值映射到 agent_level (FK → eb_agent_level.id)
|
||||
UPDATE eb_user u
|
||||
INNER JOIN eb_agent_level al ON al.grade = u.member_level AND al.is_del = 0
|
||||
SET u.agent_level = al.id
|
||||
WHERE u.member_level > 0 AND (u.agent_level = 0 OR u.agent_level IS NULL);
|
||||
END IF;
|
||||
|
||||
-- 直推奖励积分:云店
|
||||
(0, 'hjf_reward_direct_2', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'800', '云店直推奖励积分', '云店等级直推一单报单商品可获得的冻结积分,默认800', 90, 1),
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
-- 直推奖励积分:服务商
|
||||
(0, 'hjf_reward_direct_3', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'1000', '服务商直推奖励积分', '服务商等级直推一单报单商品可获得的冻结积分,默认1000', 100, 1),
|
||||
|
||||
-- 直推奖励积分:分公司
|
||||
(0, 'hjf_reward_direct_4', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'1300', '分公司直推奖励积分', '分公司等级直推一单报单商品可获得的冻结积分,默认1300', 110, 1),
|
||||
|
||||
-- 伞下奖励积分:创客(无伞下奖励)
|
||||
(0, 'hjf_reward_umbrella_1', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'0', '创客伞下奖励积分', '创客等级伞下奖励积分(级差),默认0(无伞下奖励)', 120, 1),
|
||||
|
||||
-- 伞下奖励积分:云店
|
||||
(0, 'hjf_reward_umbrella_2', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'300', '云店伞下奖励积分', '云店等级伞下奖励积分(级差),默认300', 130, 1),
|
||||
|
||||
-- 伞下奖励积分:服务商
|
||||
(0, 'hjf_reward_umbrella_3', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'200', '服务商伞下奖励积分', '服务商等级伞下奖励积分(级差),默认200', 140, 1),
|
||||
|
||||
-- 伞下奖励积分:分公司
|
||||
(0, 'hjf_reward_umbrella_4', 'text', 'input', 0,
|
||||
'', 0, '', 100, 0,
|
||||
'300', '分公司伞下奖励积分', '分公司等级伞下奖励积分(级差),默认300', 150, 1);
|
||||
CALL `hjf_migrate_member_to_agent_level`();
|
||||
DROP PROCEDURE IF EXISTS `hjf_migrate_member_to_agent_level`;
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 迁移完成校验(可手动执行检查)
|
||||
-- ============================================================
|
||||
|
||||
-- SELECT TABLE_NAME FROM information_schema.TABLES
|
||||
-- WHERE TABLE_SCHEMA = DATABASE()
|
||||
-- AND TABLE_NAME IN ('eb_queue_pool', 'eb_points_release_log');
|
||||
-- SELECT id, name, grade, direct_reward_points, umbrella_reward_points
|
||||
-- FROM eb_agent_level WHERE is_del = 0 ORDER BY grade;
|
||||
|
||||
-- SELECT alt.id, al.name AS level_name, alt.type, alt.number, alt.name AS task_name
|
||||
-- FROM eb_agent_level_task alt
|
||||
-- JOIN eb_agent_level al ON al.id = alt.level_id
|
||||
-- WHERE alt.is_del = 0 AND al.is_del = 0
|
||||
-- ORDER BY al.grade, alt.type;
|
||||
|
||||
-- SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
||||
-- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_user'
|
||||
-- AND COLUMN_NAME IN ('member_level','no_assess','frozen_points','available_points');
|
||||
|
||||
-- SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
||||
-- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'eb_store_product'
|
||||
-- AND COLUMN_NAME IN ('is_queue_goods','allow_pay_types');
|
||||
-- AND COLUMN_NAME IN ('no_assess','frozen_points','available_points');
|
||||
|
||||
-- SELECT menu_name, value FROM eb_system_config
|
||||
-- WHERE menu_name LIKE 'hjf_%' ORDER BY sort;
|
||||
|
||||
@@ -60,6 +60,64 @@
|
||||
|
||||
---
|
||||
|
||||
## 长期固定使用 PHP 8.0(推荐)
|
||||
|
||||
CRMEB Pro v3.5 官方以 **PHP 8.0** 为基准;与 **Swoole Loader**、扩展版本一一对应,建议本机 CLI 与文档统一为 **8.0**,避免混用 8.1 导致 Loader/扩展不匹配。
|
||||
|
||||
### 1. 安装并链接 Homebrew `php@8.0`
|
||||
|
||||
```bash
|
||||
brew install php@8.0
|
||||
```
|
||||
|
||||
**Intel Mac(前缀一般为 `/usr/local`)** — 让终端默认 `php` 指向 8.0:
|
||||
|
||||
```bash
|
||||
brew unlink php@8.1 2>/dev/null || true
|
||||
brew link php@8.0 --force --overwrite
|
||||
```
|
||||
|
||||
若不想改全局 link,只在当前用户把 8.0 放在 PATH 最前(写入 `~/.zshrc` 后 `source ~/.zshrc`):
|
||||
|
||||
```bash
|
||||
# Intel
|
||||
export PATH="/usr/local/opt/php@8.0/bin:/usr/local/opt/php@8.0/sbin:$PATH"
|
||||
```
|
||||
|
||||
**Apple Silicon(前缀一般为 `/opt/homebrew`)**:
|
||||
|
||||
```bash
|
||||
export PATH="/opt/homebrew/opt/php@8.0/bin:/opt/homebrew/opt/php@8.0/sbin:$PATH"
|
||||
```
|
||||
|
||||
### 2. 验证
|
||||
|
||||
```bash
|
||||
# `which php` 应指向 php@8.0,例如:
|
||||
# Intel: /usr/local/opt/php@8.0/bin/php
|
||||
# Apple 硅: /opt/homebrew/opt/php@8.0/bin/php
|
||||
which php
|
||||
php -v # 应显示 PHP 8.0.x
|
||||
php -m | grep -E 'swoole|swoole_loader'
|
||||
```
|
||||
|
||||
- **swoole**、**swoole_loader**(非企业版)均应出现;Loader 只需在 **PHP 8.0** 的 `php.ini` / `conf.d` 里配置 **一处**,避免重复加载告警。
|
||||
|
||||
### 3. 启动 API
|
||||
|
||||
始终在**项目根目录**执行:
|
||||
|
||||
```bash
|
||||
cd /path/to/pro_v3.5.1
|
||||
# 建议写死 8.0 路径,避免 PATH 里仍是 8.1:
|
||||
/usr/local/opt/php@8.0/bin/php -d memory_limit=300M think swoole
|
||||
# Apple 硅用:/opt/homebrew/opt/php@8.0/bin/php ...
|
||||
```
|
||||
|
||||
或使用 `./help/start-api.sh`(**仅**使用上述 php@8.0 路径或环境变量 `CRMEB_PHP_BIN`,不再回退到任意 `php`)。
|
||||
|
||||
---
|
||||
|
||||
## 验证与本地启动
|
||||
|
||||
### 一键检查(项目根目录执行)
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# 按 help/PHP-Setup.md 要求,以 memory_limit=300M 启动 Swoole API 服务
|
||||
# 用法:在项目根目录执行 ./help/start-api.sh,或先 cd pro_v3.5.1 再执行
|
||||
# 长期固定 PHP 8.0:优先使用 Homebrew php@8.0,避免 PATH 里误用 8.1
|
||||
# 用法:在项目根目录执行 ./help/start-api.sh
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
php -d memory_limit=300M think swoole
|
||||
|
||||
# 固定使用 PHP 8.0,不回退到 PATH 里的 `php`(避免误用 8.1 等)
|
||||
resolve_php80() {
|
||||
if [[ -n "${CRMEB_PHP_BIN:-}" && -x "${CRMEB_PHP_BIN}" ]]; then
|
||||
echo "${CRMEB_PHP_BIN}"
|
||||
return 0
|
||||
fi
|
||||
for candidate in \
|
||||
"/usr/local/opt/php@8.0/bin/php" \
|
||||
"/opt/homebrew/opt/php@8.0/bin/php"; do
|
||||
if [[ -x "${candidate}" ]]; then
|
||||
echo "${candidate}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "start-api.sh: 未找到 PHP 8.0,请安装: brew install php@8.0" >&2
|
||||
echo "或指定: CRMEB_PHP_BIN=/你的路径/php ./help/start-api.sh" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
PHP_BIN="$(resolve_php80)"
|
||||
exec "${PHP_BIN}" -d memory_limit=300M think swoole
|
||||
|
||||
@@ -891,18 +891,18 @@ Route::group('api', function () {
|
||||
* HJF 黄精粉模块路由
|
||||
*/
|
||||
Route::group('hjf', function () {
|
||||
// 需要登录的接口
|
||||
Route::group(function () {
|
||||
// 公排队列
|
||||
Route::get('queue/status', 'v1.hjf.QueueController/status'); // 公排状态
|
||||
Route::get('queue/history', 'v1.hjf.QueueController/history'); // 入队历史
|
||||
Route::get('queue/status', 'v1.hjf.QueueController/status');
|
||||
Route::get('queue/history', 'v1.hjf.QueueController/history');
|
||||
|
||||
// 积分明细
|
||||
Route::get('points/detail', 'v1.hjf.PointsController/detail'); // 积分明细
|
||||
Route::get('points/detail', 'v1.hjf.PointsController/detail');
|
||||
|
||||
// 资产总览 & 现金流水
|
||||
Route::get('assets/overview', 'v1.hjf.AssetsController/overview'); // 资产总览
|
||||
Route::get('assets/cash/detail', 'v1.hjf.AssetsController/cashDetail'); // 现金流水
|
||||
Route::get('assets/overview', 'v1.hjf.AssetsController/overview');
|
||||
Route::get('assets/cash/detail', 'v1.hjf.AssetsController/cashDetail');
|
||||
|
||||
Route::get('member/info', 'v1.hjf.MemberController/info');
|
||||
Route::get('member/team', 'v1.hjf.MemberController/team');
|
||||
Route::get('member/income', 'v1.hjf.MemberController/income');
|
||||
})->middleware(AuthTokenMiddleware::class, true);
|
||||
})->middleware(StationOpenMiddleware::class);
|
||||
|
||||
|
||||
@@ -1150,7 +1150,9 @@ export default {
|
||||
this.goodHeade();
|
||||
},
|
||||
activated() {
|
||||
this.artFrom.page = Number(this.$route.params.from_page) || 1;
|
||||
const p = Number(this.$route.params.from_page) || 1;
|
||||
this.artFrom.page = p;
|
||||
this.tableForm.page = p;
|
||||
this.goodHeade();
|
||||
this.getDataList();
|
||||
},
|
||||
@@ -1438,12 +1440,18 @@ export default {
|
||||
});
|
||||
},
|
||||
getPath() {
|
||||
const tabType =
|
||||
this.$route.query.type != null
|
||||
? String(this.$route.query.type)
|
||||
: "1";
|
||||
this.columns2 = [...this.columns];
|
||||
if (name !== "1" && name !== "2") {
|
||||
if (tabType !== "1" && tabType !== "2") {
|
||||
this.columns2.shift();
|
||||
}
|
||||
this.artFrom.page = 1;
|
||||
this.artFrom.type = this.$route.query.type.toString();
|
||||
this.artFrom.type = tabType;
|
||||
this.tableForm.page = 1;
|
||||
this.tableForm.type = tabType;
|
||||
this.getDataList();
|
||||
},
|
||||
changeMenu(row, name, index) {
|
||||
@@ -1774,20 +1782,24 @@ export default {
|
||||
}
|
||||
return newObj;
|
||||
},
|
||||
// 商品列表;
|
||||
// 商品列表(与 goodHeade 一致使用 artFrom,避免 tableForm 未同步导致列表为空或与 Tab 不一致)
|
||||
getDataList() {
|
||||
// this.loading = true;
|
||||
let tableForm = {...this.tableForm};
|
||||
tableForm.sales_range = tableForm.sales_range
|
||||
let tableForm = this.deepClone(this.artFrom);
|
||||
tableForm.sales_range =
|
||||
tableForm.sales_range && tableForm.sales_range.length
|
||||
? tableForm.sales_range.join("-")
|
||||
: "";
|
||||
tableForm.price_range = tableForm.price_range
|
||||
tableForm.price_range =
|
||||
tableForm.price_range && tableForm.price_range.length
|
||||
? tableForm.price_range.join("-")
|
||||
: "";
|
||||
tableForm.stock_range = tableForm.stock_range
|
||||
tableForm.stock_range =
|
||||
tableForm.stock_range && tableForm.stock_range.length
|
||||
? tableForm.stock_range.join("-")
|
||||
: "";
|
||||
tableForm.collect_range = tableForm.collect_range
|
||||
tableForm.collect_range =
|
||||
tableForm.collect_range && tableForm.collect_range.length
|
||||
? tableForm.collect_range.join("-")
|
||||
: "";
|
||||
// tableForm.create_range = this.timeVal.length ? this.timeVal.join("-") : "";
|
||||
|
||||
@@ -219,6 +219,16 @@ export default {
|
||||
minWidth: 80,
|
||||
title: "等级",
|
||||
},
|
||||
{
|
||||
key: "direct_reward_points",
|
||||
minWidth: 120,
|
||||
title: "直推奖励积分",
|
||||
},
|
||||
{
|
||||
key: "umbrella_reward_points",
|
||||
minWidth: 120,
|
||||
title: "伞下奖励积分",
|
||||
},
|
||||
{
|
||||
key: "one_brokerage",
|
||||
minWidth: 120,
|
||||
|
||||
@@ -54,15 +54,15 @@
|
||||
<div class="iconfont iconxiayi"></div>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem label="会员等级:" label-for="hjf_member_level">
|
||||
<FormItem label="分销等级:" label-for="hjf_member_level">
|
||||
<Select
|
||||
v-model="userFrom.hjf_member_level"
|
||||
placeholder="请选择"
|
||||
placeholder="请选择分销等级"
|
||||
element-id="hjf_member_level"
|
||||
clearable
|
||||
class="input-add"
|
||||
>
|
||||
<Option :value="0">普通会员</Option>
|
||||
<Option :value="0">普通(无分销等级)</Option>
|
||||
<Option :value="1">创客</Option>
|
||||
<Option :value="2">云店</Option>
|
||||
<Option :value="3">服务商</Option>
|
||||
@@ -420,14 +420,13 @@
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="member_level" title="HJF等级" min-width="110">
|
||||
<vxe-column field="member_level_name" title="分销等级" min-width="160">
|
||||
<template v-slot="{ row }">
|
||||
<HjfMemberBadge
|
||||
v-if="row.member_level != null"
|
||||
:level="Number(row.member_level)"
|
||||
:level="Number(row.member_level != null ? row.member_level : 0)"
|
||||
:level-name="row.member_level_name || ''"
|
||||
size="small"
|
||||
/>
|
||||
<span v-else class="level-none">普通会员</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="direct_count" title="直推人数" min-width="90">
|
||||
@@ -452,6 +451,16 @@
|
||||
title="推荐人"
|
||||
min-width="100"
|
||||
></vxe-column>
|
||||
<vxe-column field="available_points" title="可用积分" min-width="100">
|
||||
<template v-slot="{ row }">
|
||||
<span>{{ row.available_points != null ? row.available_points : 0 }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="frozen_points" title="待释放(冻结)积分" min-width="150">
|
||||
<template v-slot="{ row }">
|
||||
<span>{{ row.frozen_points != null ? row.frozen_points : 0 }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="now_money" title="余额" min-width="100"></vxe-column>
|
||||
<vxe-column
|
||||
field="action"
|
||||
@@ -464,8 +473,8 @@
|
||||
<a @click="changeMenu(row, '1')">详情</a>
|
||||
<Divider type="vertical" />
|
||||
<a @click="changeMenu(row, '10')">编辑</a>
|
||||
<Divider type="vertical" />
|
||||
<a @click="openLevelModal(row)">调整等级</a>
|
||||
<!-- <Divider type="vertical" />
|
||||
<a @click="openLevelModal(row)">调整等级</a> -->
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
@@ -867,7 +876,7 @@ export default {
|
||||
group_id: "",
|
||||
field_key: "",
|
||||
is_channel: "",
|
||||
/** 会员等级筛选(HJF 扩展字段):0 普通 1 创客 2 云店 3 服务商 4 分公司,空串表示全部 */
|
||||
/** 分销等级筛选(对应 eb_agent_level.grade / eb_user.agent_level),空串表示全部 */
|
||||
hjf_member_level: "",
|
||||
},
|
||||
field_key: "",
|
||||
@@ -1441,12 +1450,13 @@ export default {
|
||||
this.userFrom.group_id === "all" ? "" : this.userFrom.group_id;
|
||||
userList(this.userFrom)
|
||||
.then(async (res) => {
|
||||
let data = res.data;
|
||||
data.list.forEach((item) => {
|
||||
const data = res.data || {};
|
||||
const rows = Array.isArray(data.list) ? data.list : [];
|
||||
rows.forEach((item) => {
|
||||
item.checkBox = false;
|
||||
});
|
||||
this.userLists = data.list;
|
||||
this.total = data.count;
|
||||
this.userLists = rows;
|
||||
this.total = data.count != null ? data.count : 0;
|
||||
this.loading = false;
|
||||
this.$nextTick(function () {
|
||||
if (this.isAll == 1) {
|
||||
@@ -1678,12 +1688,16 @@ export default {
|
||||
* @param {Object} row - 当前行用户数据
|
||||
* @param {number} row.uid - 用户 ID
|
||||
* @param {string} row.nickname - 用户昵称
|
||||
* @param {number} row.member_level - 当前会员等级(HJF 字段);回退到 row.level
|
||||
* @param {number} row.member_level - 分销等级 grade(由 agent_level 映射)
|
||||
* @param {string} row.member_level_name - 分销等级名称(eb_agent_level.name)
|
||||
*/
|
||||
openLevelModal(row) {
|
||||
this.levelModal.uid = row.uid;
|
||||
this.levelModal.nickname = row.nickname;
|
||||
this.levelModal.level = row.member_level != null ? row.member_level : (row.level || 0);
|
||||
this.levelModal.level =
|
||||
row.member_level != null && row.member_level !== ""
|
||||
? Number(row.member_level)
|
||||
: 0;
|
||||
this.levelModal.show = true;
|
||||
},
|
||||
|
||||
|
||||
@@ -95,7 +95,11 @@
|
||||
success(e) {
|
||||
/* 窗口宽度大于420px且不在PC页面且不在移动设备时跳转至 PC.html 页面 */
|
||||
if (e.windowWidth > 420 && !window.top.isPC && !/iOS|Android/i.test(e.system)) {
|
||||
window.location.pathname = '/h5/static/html/pc.html';
|
||||
const p = (window.location.pathname || '/').replace(/\/$/, '') || '/';
|
||||
if (p.endsWith('/static/html/pc.html')) return;
|
||||
/* 与 manifest h5.router.base 一致:根路径为 / 或子路径 /h5/ */
|
||||
const h5Base = p.startsWith('/h5/') || p === '/h5' ? '/h5' : '';
|
||||
window.location.pathname = `${h5Base}/static/html/pc.html`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "crmeb",
|
||||
"appid" : "__UNI__70A74E1",
|
||||
"appid" : "__UNI__6691FE3",
|
||||
"description" : "crmeb商城",
|
||||
"versionName" : "3.5.1",
|
||||
"versionCode" : 351,
|
||||
@@ -218,7 +218,7 @@
|
||||
},
|
||||
"router" : {
|
||||
"mode" : "history",
|
||||
"base" : "/h5/"
|
||||
"base" : "/"
|
||||
},
|
||||
"domain" : "",
|
||||
"sdkConfigs" : {
|
||||
|
||||
@@ -39,6 +39,11 @@ export default {
|
||||
balanceStatus: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// HJF 分销等级信息,向下透传给各 template
|
||||
memberInfo: {
|
||||
type: Object,
|
||||
default: () => ({ agent_level: 0, member_level: 0, member_level_name: '' })
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -97,7 +102,7 @@ export default {
|
||||
<!-- #ifdef MP || APP-PLUS -->
|
||||
<topBar v-if="memberData.style != 5" :styleType="memberData.style" :isScrolling="isScrolling"></topBar>
|
||||
<!-- #endif -->
|
||||
<template1 v-if="memberData.style == 1" :perShowType="memberData.per_show_type" :userInfo="userInfo" :property="property"></template1>
|
||||
<template1 v-if="memberData.style == 1" :perShowType="memberData.per_show_type" :userInfo="userInfo" :property="property" :memberInfo="memberInfo"></template1>
|
||||
<template2 v-if="memberData.style == 2" :perShowType="memberData.per_show_type" :userInfo="userInfo" :property="property"></template2>
|
||||
<template3 v-if="memberData.style == 3" :perShowType="memberData.per_show_type" :userInfo="userInfo" :property="property"></template3>
|
||||
<template4 v-if="memberData.style == 4" :perShowType="memberData.per_show_type" :userInfo="userInfo" :commission="orderAdminData.commission"></template4>
|
||||
|
||||
@@ -14,12 +14,23 @@ export default {
|
||||
perShowType: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// HJF 分销等级信息(由 member/index.vue 向下传递)
|
||||
memberInfo: {
|
||||
type: Object,
|
||||
default: () => ({ agent_level: 0, member_level: 0, member_level_name: '' })
|
||||
}
|
||||
},
|
||||
inject: ['intoPage', 'tapQrCode', 'goMenuPage', 'goEdit', 'bindPhone', 'checkApp'],
|
||||
computed:{
|
||||
showMerBtn(){
|
||||
return this.$store.state.app.channel_func_status
|
||||
},
|
||||
/** 从 userInfo.agent_level_name 推导 grade(0-4),用于徽章颜色 */
|
||||
agentLevelGrade() {
|
||||
const nameGradeMap = { '创客': 1, '云店': 2, '服务商': 3, '分公司': 4 };
|
||||
const name = (this.userInfo && this.userInfo.agent_level_name) || '';
|
||||
return nameGradeMap[name] || 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -58,10 +69,20 @@ export default {
|
||||
<view class="bind-phone" v-if="!userInfo.phone && userInfo.uid" @tap="bindPhone">绑定手机号</view>
|
||||
<view class="acea-row row-middle" v-else>
|
||||
<view class="phone">{{ perShowType ? 'ID:' + userInfo.uid : userInfo.phone }}</view>
|
||||
<!--
|
||||
<view class="vip flex-center" v-if="userInfo.level">
|
||||
<text class="iconfont icon-huiyuandengji"></text>
|
||||
{{ userInfo.vip_name || ('V' + userInfo.level) }}
|
||||
</view>
|
||||
-->
|
||||
<!-- HJF 分销等级徽章:直接用 userInfo.agent_level / agent_level_name,无需等待异步 memberInfo -->
|
||||
<HjfMemberBadge
|
||||
v-if="userInfo.agent_level > 0"
|
||||
:level="agentLevelGrade"
|
||||
:levelName="userInfo.agent_level_name"
|
||||
size="small"
|
||||
class="ml-10"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
<!-- 个人中心模块 -->
|
||||
<view class="user-page" :style="colorStyle">
|
||||
<template v-if="isObjectData(diyData)">
|
||||
<user-member :userInfo="userInfo" :memberData="diyData.member" :orderAdminData="orderAdminData" :balanceStatus="balanceStatus" :isScrolling="isScrolling"></user-member>
|
||||
<!-- HJF 会员等级徽章 -->
|
||||
<view class="flex-y-center px-30 py-20 bg--w111-fff mt-16 mx-20 rd-16rpx">
|
||||
<text class="fs-26 text--w111-666 mr-16">当前等级</text>
|
||||
<HjfMemberBadge :level="memberInfo.member_level" :levelName="memberInfo.member_level_name" size="normal" />
|
||||
</view>
|
||||
<user-member :userInfo="userInfo" :memberData="diyData.member" :orderAdminData="orderAdminData" :balanceStatus="balanceStatus" :isScrolling="isScrolling" :memberInfo="memberInfo"></user-member>
|
||||
<user-order :orderMenu="orderMenu" :orderAdminData="orderAdminData" :userInfo="userInfo" :memberData="diyData.member" :orderData="diyData.order"></user-order>
|
||||
<!-- 黄精粉快捷入口:我的资产 & 公排记录(与 member-points 保持一致风格) -->
|
||||
<view class="acea-row member-points hjf-nav-row">
|
||||
@@ -239,6 +234,7 @@ export default {
|
||||
routineContact: 0,
|
||||
/** HJF 会员等级信息 */
|
||||
memberInfo: {
|
||||
agent_level: 0,
|
||||
member_level: 0,
|
||||
member_level_name: '普通',
|
||||
}
|
||||
@@ -306,6 +302,7 @@ export default {
|
||||
getMemberInfo().then(res => {
|
||||
if (res && res.data) {
|
||||
this.memberInfo = {
|
||||
agent_level: res.data.agent_level || 0,
|
||||
member_level: res.data.member_level || 0,
|
||||
member_level_name: res.data.member_level_name || '普通',
|
||||
};
|
||||
|
||||
@@ -79,8 +79,10 @@
|
||||
与<text class="main-color" @click.stop="privacy('privacy')">《隐私协议》</text>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
<!-- #ifndef H5 -->
|
||||
<Verify v-if="!disabled" @success="success" :captchaType="captchaType" :imgSize="{ width: '330px', height: '155px' }"
|
||||
ref="verify"></Verify>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -114,12 +116,16 @@
|
||||
// #endif
|
||||
const BACK_URL = "login_back_url";
|
||||
import colors from '@/mixins/color.js';
|
||||
// #ifndef H5
|
||||
import Verify from '../components/verify/verify.vue';
|
||||
// #endif
|
||||
import { HTTP_REQUEST_URL, CAPTCHA_TYPE } from '@/config/app';
|
||||
export default {
|
||||
name: "Login",
|
||||
components: {
|
||||
// #ifndef H5
|
||||
Verify
|
||||
// #endif
|
||||
},
|
||||
mixins: [sendVerifyCode, colors],
|
||||
data: function() {
|
||||
@@ -397,7 +403,11 @@
|
||||
Date.parse(new Date());
|
||||
},
|
||||
success(data) {
|
||||
this.$refs.verify.hide()
|
||||
// #ifndef H5
|
||||
if (this.$refs.verify) {
|
||||
this.$refs.verify.hide();
|
||||
}
|
||||
// #endif
|
||||
getCodeApi()
|
||||
.then(res => {
|
||||
this.keyCode = res.data.key;
|
||||
@@ -423,17 +433,22 @@
|
||||
if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(that.account)) return that.$util.Tips({
|
||||
title: '请输入正确的手机号码'
|
||||
});
|
||||
// getCodeApi()
|
||||
// .then(res => {
|
||||
// that.keyCode = res.data.key;
|
||||
// that.getCode();
|
||||
// })
|
||||
// .catch(res => {
|
||||
// that.$util.Tips({
|
||||
// title: res
|
||||
// });
|
||||
// });
|
||||
this.$refs.verify.show()
|
||||
// #ifdef H5
|
||||
// H5:不弹出滑块/图形安全验证,直接取 key 并发短信(后端 register/verify 已关闭 aj 二次校验)
|
||||
getCodeApi()
|
||||
.then(res => {
|
||||
that.keyCode = res.data.key;
|
||||
that.getCode({ captchaVerification: '' });
|
||||
})
|
||||
.catch(res => {
|
||||
that.$util.Tips({
|
||||
title: res
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
this.$refs.verify.show();
|
||||
// #endif
|
||||
},
|
||||
async getLogoImage() {
|
||||
let that = this;
|
||||
@@ -569,7 +584,11 @@
|
||||
that.sendCode();
|
||||
})
|
||||
.catch(res => {
|
||||
this.$refs.verify.refresh()
|
||||
// #ifndef H5
|
||||
if (this.$refs.verify) {
|
||||
this.$refs.verify.refresh();
|
||||
}
|
||||
// #endif
|
||||
that.$util.Tips({
|
||||
title: res
|
||||
});
|
||||
|
||||
@@ -35,21 +35,30 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
window.isPC = true;
|
||||
|
||||
window.onload = function(){
|
||||
/* 监听电脑浏览器窗口尺寸改变 */
|
||||
window.onresize = function(){
|
||||
/* 窗口宽度 小于或等于420px 时,跳转回H5页面 */
|
||||
if(window.innerWidth <= 420){
|
||||
window.location.pathname = '/h5/';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="/h5/?type=1"></iframe>
|
||||
<iframe id="app-frame"></iframe>
|
||||
<script type="text/javascript">
|
||||
window.isPC = true;
|
||||
/* 根据当前地址推导 H5 根路径:本地 dev 多为 /,线上部署多为 /h5/ */
|
||||
function h5AppRootPathname() {
|
||||
var path = (window.location.pathname || '/').replace(/\/$/, '') || '/';
|
||||
var marker = '/static/html/pc.html';
|
||||
if (path.endsWith(marker)) {
|
||||
var root = path.slice(0, -marker.length);
|
||||
if (!root) root = '/';
|
||||
return root === '/' ? '/' : (root + '/');
|
||||
}
|
||||
return '/';
|
||||
}
|
||||
var H5_ROOT = h5AppRootPathname();
|
||||
document.getElementById('app-frame').src = H5_ROOT + '?type=1';
|
||||
window.onresize = function(){
|
||||
if (window.innerWidth <= 420) {
|
||||
var target = H5_ROOT.replace(/\/$/, '');
|
||||
window.location.pathname = target || '/';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user