Merge branch 'feature/fsgx' into queue

This commit is contained in:
apple
2026-03-28 12:37:51 +08:00
19 changed files with 839 additions and 140 deletions

View File

@@ -0,0 +1,327 @@
---
name: Fix Issues 0325-1
overview: 修复 UniApp 移动端前端5 个 UI 问题)和 PHP 后端7 个业务逻辑问题)共 12 个问题,涉及会员码图片、海报生成、账单筛选、导航、资产数据展示、佣金周期轮巡、积分奖励、分销等级升级和 e2e 验收测试。
todos:
- id: a1-member-code-image
content: 修复会员码页面:小程序 image 标签加 v-if 防护API 返回值兜底
status: completed
- id: a2-spread-poster
content: 修复分销海报downloadFilestoreImage 失败时 Promise 需 resolve/reject增加错误处理
status: completed
- id: a3-bill-remove-queue-refund
content: 账单明细页移除"公排退款"标签和筛选项
status: completed
- id: a4-queue-status-back-btn
content: 佣金状态页左上角增加返回按钮NavBar
status: completed
- id: a5-assets-data-and-back
content: 后端 HjfAssets 接口补充 total_points_earned 字段 + 资产页增加返回按钮
status: completed
- id: b1-commission-guard
content: 增加校验:推荐人自己必须有报单订单才能获得推荐返现佣金
status: completed
- id: b2-points-pre-upgrade
content: 升级为会员分销等级前,不给直推/伞下奖励积分
status: completed
- id: b3-points-by-qty
content: 积分奖励按订单中报单商品数量(而非订单数)发放
status: completed
- id: b4-umbrella-points
content: 修复伞下积分:创客无伞下积分,云店及以上才有伞下奖励积分,检查配置与级差逻辑
status: completed
- id: b5-upgrade-count
content: 修复创客升级getDirectQueueOrderCount 补充 refund_status 检查,核实任务配置
status: completed
- id: b6-cycle-race
content: 修复推荐返现循环并发竞态:用数据库锁或原子计数器序列化位次计算
status: completed
- id: b7-e2e-verify
content: e2e 测试验收分销员等级配置的奖励积分(直推/伞下)和等级升级任务是否正确
status: completed
isProject: false
---
# 修复 docs/issues-0325-1.md 中的问题
## 第一部分UniApp 移动端前端问题
所有前端文件位于 `pro_v3.5.1/view/uniapp/` 目录下。
---
### A1. 会员码页面图片报错 (pages/users/user_member_code/index.vue)
**问题反馈:** 前端页面报错 `Failed to load local image resource /pages/users/user_member_code/false`
**问题分析:** 小程序端 `<image :src="qrc">` 没有 `v-if` 防护。当 `activityCodeApi` 接口返回 `routineUrl: false`(布尔值)时,`this.qrc` 被赋值为 `false`,运行时将其字符串化为路径 `/pages/users/user_member_code/false`,导致图片加载 500 错误。
**代码定位:**
```17:18:pro_v3.5.1/view/uniapp/pages/users/user_member_code/index.vue
<!-- #ifdef MP -->
<image :src="qrc" class="qrcode"></image>
```
以及 API 回调处(约第 135 行):
```javascript
this.qrc = routineUrl; // routineUrl 可能为 false/null
```
**修复方案:**
- 在小程序的 `<image>` 标签上添加 `v-if="qrc"`(第 18 行),与 H5 分支保持一致
- 在 `activityCodeApi()` 中添加兜底:`this.qrc = routineUrl || ''`
---
### A2. 分销海报加载不出来 (pages/users/user_spread_code/index.vue)
**问题反馈:** 海报加载不出来
**问题分析:** `downloadFilestoreImage()`(约第 354 行)返回的 Promise 在下载失败时**既不 resolve 也不 reject**,只调用了 `Tips()`。这导致 `await` 永久挂起,阻塞了整个海报生成流程。
```363:366:pro_v3.5.1/view/uniapp/pages/users/user_spread_code/index.vue
fail: function() {
return that.$util.Tips({
title: ''
});
```
**修复方案:**
- 在 `downloadFilestoreImage` 的 `fail` 回调中添加 `reject()`(或 `resolve('')`),让 Promise 正常结束
- 在 `spreadMsg` 中,将下载后的图片路径传入 `userPosterCanvas` 前,增加空值/空字符串检查
- 可选:在海报生成循环外包 try/catch确保 `uni.hideLoading()` 始终执行
---
### A3. 账单明细页 — 移除"公排退款" (pages/users/user_bill/index.vue)
**问题反馈:** 去掉公排退款
**问题分析:** 导航栏有"公排退款"筛选标签,列表项中也显示"公排退款"标记。需要全部移除。
**修复方案:** 从模板中删除以下内容:
- 导航栏中 `queue_refund` 类型对应的 `<view>`(第 23-27 行)
- 列表项中的 `<text v-if="vo.type === 'queue_refund'" ...>公排退款</text>` 标记(第 47-50 行)
---
### A4. 佣金状态页 — 增加返回按钮 (pages/queue/status.vue)
**问题反馈:** 左上角增加返回按钮
**问题分析:** 页面顶部没有 NavBar 和返回按钮。
**修复方案:** 参照其他页面的已有模式(如 `user_spread_user/index.vue`
- 从 `@/components/NavBar.vue` 导入 `NavBar`(小程序条件编译)
- 在 `components` 中注册
- 在模板中添加 `<NavBar titleText="佣金状态" :iconColor="iconColor" :textColor="iconColor" showBack :isScrolling="isScrolling" />`,用 `<!-- #ifdef MP -->` 条件编译包裹
- 添加对应的 data 属性(`iconColor`、`isScrolling`)和滚动事件处理
---
### A5. 资产页 — 数据不显示 + 增加返回按钮 (pages/assets/index.vue)
**问题反馈:**
1. "累计获得积分"不显示数据,是否没读取到数据?
2. 左上角增加返回按钮
**问题 1 分析:** "累计获得积分"显示为 `NaN`。前端读取 `assetsInfo.total_points_earned`,但后端 `HjfAssets::overview` 接口未返回该字段。
- 前端计算属性(约第 148 行):`Number(this.assetsInfo.total_points_earned).toLocaleString()` => `NaN`
- 后端响应([HjfAssets.php](pro_v3.5.1/app/controller/api/v1/hjf/HjfAssets.php) 第 42-50 行):仅返回 `frozen_points`、`available_points` 等,缺少 `total_points_earned`
**修复方案(后端):** 在 `HjfAssets::overview()` 的响应中增加 `total_points_earned` 字段。其值应为 `frozen_points + available_points`(累计获得 = 当前待释放 + 已释放):
```php
'total_points_earned' => $frozenPoints + (int)($user['available_points'] ?? 0),
```
**问题 2** 左上角没有返回按钮(同 A4
**修复方案:** 按照 A4 相同的模式添加带 `showBack` 的 NavBar。
---
## 第二部分:后端业务逻辑问题
所有后端文件位于 `pro_v3.5.1/app/` 目录下。
---
### B1. 自己不报单,推荐没有返现佣金(测试问题 #1
**问题反馈:** "自己不报单(没有购买过报单商品的订单),推荐没有返现佣金的"
**问题分析:** [StoreOrderCreateServices.php](pro_v3.5.1/app/services/order/StoreOrderCreateServices.php)(约第 998-1028 行)中的佣金周期逻辑在**被推荐人**下单报单商品时计算佣金,但没有校验**推荐人自己**是否也购买过报单商品。按业务规则,推荐人自己必须先报单(购买过报单商品),推荐下级报单才能获得返现佣金。
**修复方案:** 在 `computeOrderProductTruePrice` 中计算周期佣金之前(约第 1004 行),增加校验:若推荐人(`$spread_uid`)自己没有任何已支付的 `is_queue_goods=1` 订单,则跳过佣金计算(`$oneBrokerage = 0`)。或者在 `Pay.php::compute()` 中写入 `one_brokerage` 前做此校验。
---
### B2. 升级成为会员分销等级前,不给奖励积分(测试问题 #2
**问题反馈:** "判断升级成为**会员分销等级**前,不给奖励积分(直推奖励积分、伞下奖励积分)"
**问题分析:** 在 [Pay.php](pro_v3.5.1/app/listener/order/Pay.php)(第 180-184 行)中,流程是:先检查升级、再发放积分。这意味着如果当前订单使推荐人从 grade 0 升级为创客,`reward()` 调用时已经看到了新等级,会在触发升级的同一笔订单上就发放**直推奖励积分**或**伞下奖励积分**。
根据 PRD直推奖励积分和伞下奖励积分均应从推荐人**已成为会员分销等级之后**的订单才开始发放。触发升级的那笔订单本身不应给新升级的用户发积分。
**修复方案:** 在升级检查执行前,记录各上级用户的"升级前等级"快照,然后在 `reward()` 中使用**升级前的等级**判断是否有资格获得直推/伞下奖励积分。具体方式:
- 在 Pay.php 中 `checkUserLevelFinish` 之前,快照相关用户的 `agent_level`
- 将升级前的等级信息传入 `reward()`,用于资格判断
- 或者在 `reward()` 中检查用户的 `agent_level` 是否在同一事务/时间窗口内被更新,若是则跳过
---
### B3. 积分应按订单中报单商品数量发放,而非按订单数(测试问题 #3
**问题反馈:** "目前积分奖励是按订单数量来给了需要调整为按报单商品数量比如一次性下一个订单含3个报单商品应该给1500积分而不是500"
**问题分析:** `PointsRewardServices::reward()` 每笔订单只调用一次,发放基于等级的固定积分(如创客 500 分)。若**一个订单内**包含 3 个报单商品(商品数量=3应发放 3x500=1500 分,而非 500 分。
**修复方案:**
- 修改 `reward()` 增加 `$qty` 参数(默认为 1表示该订单中报单商品的数量
- 在 `grantFrozenPoints` 中将 `$points * $qty` 计算实际积分
- 在 [Pay.php](pro_v3.5.1/app/listener/order/Pay.php)(第 184 行)中,从 `$orderInfo` 的购物车信息计算订单内报单商品数量(`is_queue_goods=1` 的商品 `cart_num` 之和),传入 `reward()`
- 在 [HjfOrderPayJob.php](pro_v3.5.1/app/jobs/hjf/HjfOrderPayJob.php) 中做同样的修改
---
### B4. 伞下奖励积分规则:创客无伞下积分,云店及以上才有(测试问题 #4
**问题反馈:** "创客级别的分销员,伞下下单没有**伞下奖励积分**,升级为云店级别分销员之后,只要级别低于自己的,伞下下单都应有**伞下奖励积分**"
**问题分析:** 按 PRD 各等级积分配置:
| 等级 | 直推奖励积分 | 伞下奖励积分 |
| --------------- | ------ | ------ |
| 创客 | 500 | 0 |
| 云店 | 800 | 300 |
| 服务中心原PRD名服务商 | 1000 | 200 |
| 合伙人原PRD名分公司 | 1300 | 300 |
> **注意:** 数据库中等级名称已调整,"服务商"改为"服务中心""分公司"改为"合伙人"。后续代码及配置中以数据库实际名称为准。
创客的伞下奖励积分为 **0**,这是**正确的设计**(非 Bug。问题焦点是**云店及以上**级别的会员在伞下下单后应获得伞下奖励积分,但目前可能未正确发放。
在 [PointsRewardServices.php](pro_v3.5.1/app/services/hjf/PointsRewardServices.php) 第 109 行,级差逻辑 `$actual = max(0, $reward - $lowerReward)` 可能导致云店的伞下积分被下级已获得的奖励抵消为 0。
**修复方案:**
- 核查 `eb_agent_level` 表中各等级的 `umbrella_reward_points` 是否与 PRD 一致(创客=0、云店=300、服务中心=200、合伙人=300
- 确认 `AgentLevelServices` 的 `getUmbrellaRewardPoints()` 和 `getDirectRewardPoints()` 正确读取该字段并返回
- 如果数据配置正确但云店仍未拿到伞下积分,检查级差计算中 `$lowerReward` 的传递链是否正确——特别注意当中间层(如创客)的伞下积分为 0 时,传给上级的 `$lowerReward` 应为 0云店的 `$actual` 应为 `max(0, 300 - 0) = 300`
- 若中间层是以**直推奖励积分**500作为 `$lowerReward` 传递的,则需修复 `propagateReward` 使其在递归中区分直推与伞下,确保向上传递的是**伞下**奖励而非直推奖励
---
### B5. 用户 ID=14 只有 2 单就升级为创客(测试问题 #5
**问题反馈:** "用户ID=14直推了2单就升级成为创客的分销等级了但是目前分销等级中创客的升级任务配置的是直推3单"
**问题分析:** [AgentLevelTaskServices.php](pro_v3.5.1/app/services/agent/AgentLevelTaskServices.php)(第 431 行)中 `getDirectQueueOrderCount()` 统计推荐人直推下级的报单订单数。可能原因:
- 统计包含了已退款的订单(查询条件有 `is_del=0`,但缺少 `refund_status` 检查)
- `no_assess=0` 过滤条件未正确生效
- `eb_agent_level_task` 表中创客的任务 `number` 被配置为 2 而非 3
- 竞态条件:并发订单处理可能触发两次升级检查
**修复方案:**
- 检查 `eb_agent_level_task` 表确认创客的任务配置为 `type=6`、`number=3`
- 在 `getDirectQueueOrderCount` 查询中补充 `refund_status` 条件检查(目前缺失,而其他任务类型均有 `refund_status => [0, 3]` 的检查)
- 在升级检查处增加日志,记录升级时刻的精确计数
---
### B6. 推荐返现循环两次都给了 20%(测试问题 #6
**问题反馈:** "**推荐返现循环**目前配置的是'[20,30,50]'。为何用户ID=14直推了2单出现了2次20%的返现佣金?"
**问题分析:** 在 [StoreOrderCreateServices.php](pro_v3.5.1/app/services/order/StoreOrderCreateServices.php) 第 1009-1015 行,位次计算通过统计推荐人下级所有已支付的报单订单数来确定。若两笔订单几乎同时处理,两次 `compute()` 调用可能看到相同的 `completedCount`,导致位次相同(都得到 20%)。正确行为应是:第 1 单 20%、第 2 单 30%、第 3 单 50%,然后循环。
```1015:1015:pro_v3.5.1/app/services/order/StoreOrderCreateServices.php
$position = max(0, $completedCount - 1) % $cycleCount;
```
**修复方案:**
- 添加数据库级别的锁(如对 spread_uid 维度加 `SELECT ... FOR UPDATE`),序列化周期位次计算
- 或者在用户表中增加显式周期计数器字段(如 `eb_user` 表添加 `brokerage_cycle_position` 列),原子递增,而非依赖 count 查询
- 或者使用 `ORDER BY id ASC` 按订单排名确定位次,而非仅统计总数
---
### B7. e2e 测试验收分销员等级配置(测试问题 #7.
**问题反馈:** "e2e测试验收一下分销员等级中配置的奖励积分直推奖励积分、伞下奖励积分和等级任务是否正确"
**问题分析:** 需要端到端验证 `eb_agent_level` 和 `eb_agent_level_task` 表中的数据配置与 PRD 一致。
**验收清单:**
**1) `eb_agent_level` 表 — 各等级奖励积分配置PRD 3.2**
| 等级 | grade | direct_reward_points直推 | umbrella_reward_points伞下 |
| ---- | ----- | ------------------------ | -------------------------- |
| 创客 | 1 | 500 | 0 |
| 云店 | 2 | 800 | 300 |
| 服务中心 | 3 | 1000 | 200 |
| 合伙人 | 4 | 1300 | 300 |
**2) `eb_agent_level_task` 表 — 各等级升级任务配置PRD 3.2**
| 等级 | 任务类型 type | 任务要求 number | 说明 |
| ---- | --------- | ----------- | ---------- |
| 创客 | 6直推报单单数 | 3 | 直推满 3 单 |
| 云店 | 7伞下报单业绩 | 30 | 伞下满 30 单 |
| 云店 | 8最低直推人数 | 3 | 至少 3 个直推 |
| 服务中心 | 7伞下报单业绩 | 100 | 伞下满 100 单 |
| 服务中心 | 8最低直推人数 | 3 | 至少 3 个直推 |
| 合伙人 | 7伞下报单业绩 | 1000 | 伞下满 1000 单 |
| 合伙人 | 8最低直推人数 | 3 | 至少 3 个直推 |
**3) 功能验证场景(手动或脚本):**
- 创客升级:模拟 3 笔直推报单订单,确认准确在第 3 笔时升级
- 积分发放:确认创客直推积分 500/单、伞下积分 0云店直推 800/单、伞下 300/单
- 佣金轮巡:模拟 3 笔直推报单订单,确认佣金比例依次为 20%→30%→50%
- 批量报单:一个订单含多个报单商品时,积分按商品数量乘算
**修复方案:** 编写 SQL 查询脚本或 PHP 命令行工具验证上述配置,不一致则修正数据。
---
## 文件修改汇总
| 问题编号 | 主要需修改的文件 |
| ---- | ---------------------------------------------------------------------------------------------------------- |
| A1 | `view/uniapp/pages/users/user_member_code/index.vue` |
| A2 | `view/uniapp/pages/users/user_spread_code/index.vue` |
| A3 | `view/uniapp/pages/users/user_bill/index.vue` |
| A4 | `view/uniapp/pages/queue/status.vue` |
| A5 | `view/uniapp/pages/assets/index.vue`、`app/controller/api/v1/hjf/HjfAssets.php` |
| B1 | `app/services/order/StoreOrderCreateServices.php` 或 `app/listener/order/Pay.php` |
| B2 | `app/listener/order/Pay.php`、`app/services/hjf/PointsRewardServices.php` |
| B3 | `app/services/hjf/PointsRewardServices.php`、`app/listener/order/Pay.php`、`app/jobs/hjf/HjfOrderPayJob.php` |
| B4 | `app/services/hjf/PointsRewardServices.php`,核查 `eb_agent_level` 数据 |
| B5 | `app/services/agent/AgentLevelTaskServices.php`,核查 `eb_agent_level_task` 数据 |
| B6 | `app/services/order/StoreOrderCreateServices.php` |
| B7 | 验证脚本 / SQL 查询(验收 `eb_agent_level` + `eb_agent_level_task` 数据配置) |

View File

@@ -30,7 +30,7 @@
| 已释放积分 | 已完成释放、可消费的积分,仅可用于普通商品 |
| 直推 | 用户直接邀请并绑定的一级成员 |
| 伞下 | 用户的所有直推及其下级组成的推荐网络 |
| 会员分销等级 | 创客、云店、服务商、分公司 |
| 会员分销等级 | 创客、云店、服务中心、合伙人 |
| 级差 | 上级可获得的奖励与下级当前等级奖励之间的差额机制 |
| 推荐返现循环 | 邀请满 3 人按 20%/30%/50%返现,后续继续按 3 人周期循环 |

View File

@@ -1,21 +1,37 @@
# 管理后台
# uniapp移动端
## 我的页面tabbar页
1. 用户ID右边显示的会员等级改成显示分销等级
## 会员码页面pages/users/user_member_code/index
- 1. 前端页面报错:
` [渲染层网络层错误] Failed to load local image resource /pages/users/user_member_code/false
the server responded with a status of 500 (HTTP/1.1 500 Internal Server Error) `
## 我的资产页面(/pages/assets/index
1. 页面顶部“我的资产”的标题去除
## 分销海报页面pages/users/user_spread_code/index
- 1. 海报加载不出来
## 推荐佣金页面(/pages/queue/status
1. 佣金记录不显示
## 我的资产-》账单明细页面pages/users/user_bill/index
- 1. 去掉公排退款
## 页面: pages/queue/status
- 1. 左上角增加返回按钮
## 页面pages/assets/index
- 1. "累计获得积分"不显示数据,是否没读取到数据?
- 2. 左上角增加返回按钮
---
# 测试
# 其他测试问题
- 1. 自己不报单(没有购买过报单商品的订单),推荐没有返现佣金的,
- 2. 判断升级成为**会员分销等级**前,不给奖励积分(直推奖励积分、伞下奖励积分),
- 3. 目前积分奖励是按订单数量来给了需要调整为按报单商品数量比如一次性下一个订单含3个报单商品应该给1500积分而不是500
- 4. 创客级别的分销员,伞下下单没有**伞下奖励积分**,升级为云店级别分销员之后,只要级别低于自己的,伞下下单都应有**伞下奖励积分**
- 5. 用户ID=14直推了2单就升级成为创客的**分销等级**了但是目前分销等级中创客的升级任务配置的是直推3单
- 6. **推荐返现循环**目前配置的是"[20,30,50]"。为何用户ID=14直推了2单出现了2次20%的返现佣金?
- 7. e2e测试验收一下分销员等级中配置的奖励积分直推奖励积分、伞下奖励积分和等级任务是否正确
# 相关文件
1. **相关文件**`docs/PRD_fsgx_V1.0.md` `docs/page-dev-specs-fsgx.md`
- 1. **相关文件**`docs/PRD_fsgx_V1.0.md` `docs/page-dev-specs-fsgx.md`

37
docs/issues-0327-1.md Normal file
View File

@@ -0,0 +1,37 @@
# 核心功能测试结果
- 分销员直推没有获得返现佣金测试数据uid=20升级为创客分销等级后下级用户下报单商品订单支付后uid=20并没有eb_user_brokerage返现佣金记录。
- 分销海报页面, pages/users/user_spread_code/index 获取不到二维码
fetch("https://www.fsgx.cn/api/user/routine_code", {
"headers": {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9",
"authori-zation": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoIjoiMTRlMWI2MDBiMWZkNTc5ZjQ3NDMzYjg4ZThkODUyOTEiLCJpc3MiOiJ3d3cuZnNneC5jbiIsImF1ZCI6Ind3dy5mc2d4LmNuIiwiaWF0IjoxNzc0NjY2NzA1LCJuYmYiOjE3NzQ2NjY3MDUsImV4cCI6MTc3NTI3MTUwNSwianRpIjp7ImlkIjoyMCwidHlwZSI6InJvdXRpbmUifX0.lbxfmmPVMNFpZmx9EXcb_7viz-YeMC_QymX6tiuCkD4",
"cache-control": "no-cache",
"content-type": "application/json",
"form-type": "routine",
"pragma": "no-cache",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"x-source": "370474988fa2275c"
},
"referrer": "https://servicewechat.com/wx998d9e0a925a1a13/devtools/page-frame.html",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "omit"
});
{"status":200,"msg":"ok","data":{"url":false}}
- `pro_v3.5.1/.cursor/plans/fix_issues_0325-1_f8488785.plan.md`对文档中“B7. e2e 测试验收分销员等级配置(测试问题 #7”执行e2e测试
---
# 测试
# 相关文件
1. **相关文件**`docs/PRD_fsgx_V1.0.md` `docs/page-dev-specs-fsgx.md``pro_v3.5.1/.cursor/plans/fix_issues_0325-1_f8488785.plan.md`

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
/**
* e2e 验收:分销员等级配置检验命令
*
* 用法:
* php think hjf:verify-agent-config
*
* 说明:
* 验证 eb_agent_level 和 eb_agent_level_task 表中的数据配置与 PRD 3.2 一致。
* 若不一致则输出差异明细并自动修正(--fix 参数),修正后输出最终结果。
*
* Class HjfVerifyAgentConfig
* @package app\command
*/
class HjfVerifyAgentConfig extends Command
{
protected function configure(): void
{
$this->setName('hjf:verify-agent-config')
->setDescription('e2e 验收分销员等级奖励积分与升级任务配置是否与 PRD 一致,传入 --fix 自动修正')
->addOption('fix', null, \think\console\input\Option::VALUE_NONE, '自动修正不一致的配置');
}
protected function execute(Input $input, Output $output): int
{
$fix = (bool)$input->getOption('fix');
$hasError = false;
$output->writeln('');
$output->writeln('========================================================');
$output->writeln(' HJF 分销员等级配置 e2e 验收');
$output->writeln('========================================================');
// ----------------------------------------------------------------
// 1) eb_agent_level — 奖励积分配置PRD 3.2
// ----------------------------------------------------------------
$output->writeln('');
$output->writeln('【1】eb_agent_level 奖励积分配置');
$output->writeln('------------------------------------------------------------');
$expectedLevels = [
1 => ['name_hint' => '创客', 'direct_reward_points' => 500, 'umbrella_reward_points' => 0],
2 => ['name_hint' => '云店', 'direct_reward_points' => 800, 'umbrella_reward_points' => 300],
3 => ['name_hint' => '服务中心', 'direct_reward_points' => 1000, 'umbrella_reward_points' => 200],
4 => ['name_hint' => '合伙人', 'direct_reward_points' => 1300, 'umbrella_reward_points' => 300],
];
$levels = Db::name('agent_level')
->whereIn('grade', array_keys($expectedLevels))
->field('id,name,grade,direct_reward_points,umbrella_reward_points')
->select()
->toArray();
$levelsByGrade = [];
foreach ($levels as $level) {
$levelsByGrade[(int)$level['grade']] = $level;
}
foreach ($expectedLevels as $grade => $expected) {
if (!isset($levelsByGrade[$grade])) {
$output->writeln(" [MISS] grade={$grade} ({$expected['name_hint']}) 行不存在!");
$hasError = true;
continue;
}
$row = $levelsByGrade[$grade];
$errors = [];
if ((int)$row['direct_reward_points'] !== $expected['direct_reward_points']) {
$errors[] = "direct_reward_points={$row['direct_reward_points']}(期望 {$expected['direct_reward_points']}";
}
if ((int)$row['umbrella_reward_points'] !== $expected['umbrella_reward_points']) {
$errors[] = "umbrella_reward_points={$row['umbrella_reward_points']}(期望 {$expected['umbrella_reward_points']}";
}
if ($errors) {
$hasError = true;
$output->writeln(" [FAIL] grade={$grade} {$row['name']}" . implode('', $errors));
if ($fix) {
Db::name('agent_level')->where('id', $row['id'])->update([
'direct_reward_points' => $expected['direct_reward_points'],
'umbrella_reward_points' => $expected['umbrella_reward_points'],
]);
$output->writeln(" [FIX] 已修正 grade={$grade} {$row['name']}");
}
} else {
$output->writeln(" [OK] grade={$grade} {$row['name']} direct={$row['direct_reward_points']} umbrella={$row['umbrella_reward_points']}");
}
}
// ----------------------------------------------------------------
// 2) eb_agent_level_task — 升级任务配置PRD 3.2
// ----------------------------------------------------------------
$output->writeln('');
$output->writeln('【2】eb_agent_level_task 升级任务配置');
$output->writeln('------------------------------------------------------------');
// PRD 任务配置:[grade => [[type, number, description], ...]]
$expectedTasks = [
1 => [[6, 3, '直推满 3 单']],
2 => [[7, 30, '伞下满 30 单'], [8, 3, '至少 3 个直推']],
3 => [[7, 100, '伞下满 100 单'], [8, 3, '至少 3 个直推']],
4 => [[7, 1000, '伞下满 1000 单'], [8, 3, '至少 3 个直推']],
];
foreach ($expectedTasks as $grade => $tasks) {
if (!isset($levelsByGrade[$grade])) {
$output->writeln(" [SKIP] grade={$grade} 等级行不存在,跳过任务检查");
continue;
}
$levelId = $levelsByGrade[$grade]['id'];
$levelName = $levelsByGrade[$grade]['name'];
foreach ($tasks as [$type, $number, $desc]) {
$taskRow = Db::name('agent_level_task')
->where('level_id', $levelId)
->where('type', $type)
->field('id,number')
->find();
if (!$taskRow) {
$hasError = true;
$output->writeln(" [MISS] grade={$grade} {$levelName} type={$type}{$desc})行不存在!");
if ($fix) {
Db::name('agent_level_task')->insert([
'level_id' => $levelId,
'type' => $type,
'number' => $number,
]);
$output->writeln(" [FIX] 已插入 grade={$grade} type={$type} number={$number}");
}
} elseif ((int)$taskRow['number'] !== $number) {
$hasError = true;
$output->writeln(" [FAIL] grade={$grade} {$levelName} type={$type}{$desc}number={$taskRow['number']}(期望 {$number}");
if ($fix) {
Db::name('agent_level_task')->where('id', $taskRow['id'])->update(['number' => $number]);
$output->writeln(" [FIX] 已修正 grade={$grade} type={$type} number={$number}");
}
} else {
$output->writeln(" [OK] grade={$grade} {$levelName} type={$type}{$desc}number={$taskRow['number']}");
}
}
}
// ----------------------------------------------------------------
// 输出汇总
// ----------------------------------------------------------------
$output->writeln('');
$output->writeln('========================================================');
if ($hasError) {
if ($fix) {
$output->writeln(' 结果:检测到配置不一致,已自动修正。');
} else {
$output->writeln(' 结果:检测到配置不一致,请使用 --fix 自动修正,或手动更新数据库。');
}
} else {
$output->writeln(' 结果:所有配置与 PRD 一致,验收通过 ✓');
}
$output->writeln('========================================================');
$output->writeln('');
return $hasError ? 1 : 0;
}
}

View File

@@ -39,14 +39,17 @@ class HjfAssets
$frozenPoints = (int)($user['frozen_points'] ?? 0);
$todayRelease = (int)floor($frozenPoints * 0.0004);
$availablePoints = (int)($user['available_points'] ?? 0);
return app('json')->successful([
'brokerage_price' => (string)($user['brokerage_price'] ?? '0.00'),
'now_money' => (string)($user['now_money'] ?? '0.00'),
'frozen_points' => $frozenPoints,
'available_points' => (int)($user['available_points'] ?? 0),
'today_release' => $todayRelease,
'agent_level' => (int)($user['agent_level'] ?? 0),
'agent_level_name' => $agentLevelName,
'brokerage_price' => (string)($user['brokerage_price'] ?? '0.00'),
'now_money' => (string)($user['now_money'] ?? '0.00'),
'frozen_points' => $frozenPoints,
'available_points' => $availablePoints,
'today_release' => $todayRelease,
'total_points_earned' => $frozenPoints + $availablePoints,
'agent_level' => (int)($user['agent_level'] ?? 0),
'agent_level_name' => $agentLevelName,
]);
}
}

View File

@@ -96,6 +96,10 @@ class UserBill
$urlCode = $imageInfo['att_dir'];
if ($imageInfo['image_type'] == 1) $urlCode = sys_config('site_url') . $urlCode;
}
if (!$urlCode) {
$siteUrl = sys_config('site_url', '');
$urlCode = $siteUrl . '?spread=' . $uid;
}
return app('json')->success(['url' => $urlCode]);
}

View File

@@ -48,6 +48,7 @@ class HjfOrderPayJob extends BaseJobs
return false;
}
$preUpgradeLevels = [];
try {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
@@ -59,6 +60,13 @@ class HjfOrderPayJob extends BaseJobs
}
$uids = array_unique([$uid, $spreadUid, $twoSpreadUid]);
// fsgx B2在升级前快照各上级用户的 agent_level避免触发升级的那笔订单发放积分
foreach ($uids as $u) {
if ($u <= 0) continue;
$uInfo = $userServices->get((int)$u, ['uid', 'agent_level']);
$preUpgradeLevels[(int)$u] = $uInfo ? (int)($uInfo['agent_level'] ?? 0) : 0;
}
/** @var AgentLevelServices $agentLevelServices */
$agentLevelServices = app()->make(AgentLevelServices::class);
$agentLevelServices->checkUserLevelFinish($uid, $uids);
@@ -76,10 +84,30 @@ class HjfOrderPayJob extends BaseJobs
->field('id,uid,is_queue_goods')
->find();
if ($orderRow) {
// fsgx B3计算订单中报单商品的总数量积分按数量倍乘
$queueQty = 1;
try {
$cartRows = Db::name('store_order_cart_info')
->where('oid', (int)$orderRow['id'])
->column('cart_info');
$qtySum = 0;
foreach ($cartRows as $row) {
$item = is_string($row) ? json_decode($row, true) : $row;
if (!empty($item['productInfo']['is_queue_goods'])) {
$qtySum += (int)($item['cart_num'] ?? 1);
}
}
if ($qtySum > 0) {
$queueQty = $qtySum;
}
} catch (\Throwable $qe) {
Log::warning("[HjfOrderPay] 计算报单商品数量异常使用默认值1: " . $qe->getMessage());
}
/** @var PointsRewardServices $pointsService */
$pointsService = app()->make(PointsRewardServices::class);
$pointsService->reward($uid, $orderId, (int)$orderRow['id']);
Log::info("[HjfOrderPay] 积分奖励发放完成 uid={$uid} orderId={$orderId}");
$pointsService->reward($uid, $orderId, (int)$orderRow['id'], $preUpgradeLevels, $queueQty);
Log::info("[HjfOrderPay] 积分奖励发放完成 uid={$uid} orderId={$orderId} qty={$queueQty}");
}
} catch (\Throwable $e) {
Log::error("[HjfOrderPay] 积分奖励发放失败 uid={$uid} orderId={$orderId}: " . $e->getMessage());

View File

@@ -175,15 +175,42 @@ class Pay implements ListenerInterface
}
$uids = array_filter(array_unique([$uid, $spreadUid, $twoSpreadUid]));
// fsgx B2在升级前快照各上级用户的 agent_level避免触发升级的那笔订单发放积分
$preUpgradeLevels = [];
foreach ($uids as $u) {
$uInfo = $userServices->get((int)$u, ['uid', 'agent_level']);
$preUpgradeLevels[(int)$u] = $uInfo ? (int)($uInfo['agent_level'] ?? 0) : 0;
}
/** @var AgentLevelServices $agentLevelServices */
$agentLevelServices = app()->make(AgentLevelServices::class);
$agentLevelServices->checkUserLevelFinish($uid, array_values($uids));
// fsgx B3计算订单中报单商品的总数量积分按数量倍乘
$queueQty = 1;
try {
/** @var StoreOrderCartInfoServices $cartSvc */
$cartSvc = app()->make(StoreOrderCartInfoServices::class);
$cartRows = $cartSvc->getColumn(['oid' => (int)$orderInfo['id']], 'cart_info');
$qtySum = 0;
foreach ($cartRows as $row) {
$item = is_string($row) ? json_decode($row, true) : $row;
if (!empty($item['productInfo']['is_queue_goods'])) {
$qtySum += (int)($item['cart_num'] ?? 1);
}
}
if ($qtySum > 0) {
$queueQty = $qtySum;
}
} catch (\Throwable $qe) {
Log::warning('[Pay] 计算报单商品数量异常使用默认值1: ' . $qe->getMessage());
}
/** @var PointsRewardServices $pointsService */
$pointsService = app()->make(PointsRewardServices::class);
$pointsService->reward($uid, (string)$orderInfo['order_id'], (int)$orderInfo['id']);
$pointsService->reward($uid, (string)$orderInfo['order_id'], (int)$orderInfo['id'], $preUpgradeLevels, $queueQty);
Log::info('[Pay] 同步积分奖励发放完成 uid=' . $uid . ' order_id=' . $orderInfo['id']);
Log::info('[Pay] 同步积分奖励发放完成 uid=' . $uid . ' order_id=' . $orderInfo['id'] . ' qty=' . $queueQty);
} catch (\Throwable $e) {
Log::error('[Pay] 同步积分奖励失败 order_id=' . $orderInfo['id'] . ': ' . $e->getMessage());
}

View File

@@ -437,11 +437,13 @@ class AgentLevelTaskServices extends BaseServices
if (empty($directUids)) {
return 0;
}
// fsgx B5补充 refund_status 检查,与其他任务类型保持一致,排除已全额退款订单
return (int)Db::name('store_order')
->whereIn('uid', $directUids)
->where('is_queue_goods', 1)
->where('paid', 1)
->where('is_del', 0)
->whereIn('refund_status', [0, 3])
->count();
}
@@ -490,11 +492,13 @@ class AgentLevelTaskServices extends BaseServices
if ($childGrade >= 2) {
continue;
}
// fsgx B5补充 refund_status 检查,排除已全额退款订单
$total += (int)Db::name('store_order')
->where('uid', $child['uid'])
->where('is_queue_goods', 1)
->where('paid', 1)
->where('is_del', 0)
->whereIn('refund_status', [0, 3])
->count();
$total += $this->recursiveUmbrellaCount((int)$child['uid'], $remainDepth - 1);
}

View File

@@ -40,10 +40,13 @@ class PointsRewardServices extends BaseServices
/**
* 对一笔报单订单发放积分奖励
*
* @param int $orderDbId 订单表主键 id用于 user_bill.link_id 关联后台订单
* @param int $orderDbId 订单表主键 id用于 user_bill.link_id 关联后台订单
* @param array $preUpgradeLevels 升级前各用户的 agent_level 快照 [uid => level_id],用于 B2 校验
* @param int $qty 订单中报单商品数量积分按数量倍乘B3
*/
public function reward(int $orderUid, string $orderId, int $orderDbId = 0): void
public function reward(int $orderUid, string $orderId, int $orderDbId = 0, array $preUpgradeLevels = [], int $qty = 1): void
{
$qty = max(1, $qty);
try {
// 幂等检查:若该订单已有积分奖励记录则跳过,防止重复发放
$exists = Db::name('points_release_log')
@@ -59,7 +62,7 @@ class PointsRewardServices extends BaseServices
if (!$buyer || !$buyer['spread_uid']) {
return;
}
$this->propagateReward($buyer['spread_uid'], $orderUid, $orderId, 0, 0, $orderDbId);
$this->propagateReward($buyer['spread_uid'], $orderUid, $orderId, 0, 0, $orderDbId, $preUpgradeLevels, $qty);
} catch (\Throwable $e) {
Log::error("[PointsReward] 积分奖励失败 orderUid={$orderUid} orderId={$orderId}: " . $e->getMessage());
}
@@ -68,11 +71,13 @@ class PointsRewardServices extends BaseServices
/**
* 向上递归发放级差积分
*
* @param int $uid 当前被奖励用户
* @param int $fromUid 触发方(下级)用户 ID
* @param string $orderId 来源订单号
* @param int $lowerReward 下级已获得的直推/伞下奖励积分(用于级差扣减)
* @param int $depth 递归深度
* @param int $uid 当前被奖励用户
* @param int $fromUid 触发方(下级)用户 ID
* @param string $orderId 来源订单号
* @param int $lowerReward 下级已获得的伞下奖励积分(用于级差扣减)
* @param int $depth 递归深度
* @param array $preUpgradeLevels 升级前各用户的 agent_level 快照 [uid => level_id]
* @param int $qty 报单商品数量积分按数量倍乘B3
*/
private function propagateReward(
int $uid,
@@ -80,7 +85,9 @@ class PointsRewardServices extends BaseServices
string $orderId,
int $lowerReward,
int $depth = 0,
int $orderDbId = 0
int $orderDbId = 0,
array $preUpgradeLevels = [],
int $qty = 1
): void {
if ($depth >= 10 || $uid <= 0) {
return;
@@ -91,22 +98,33 @@ class PointsRewardServices extends BaseServices
return;
}
$agentLevelId = (int)($user['agent_level'] ?? 0);
// fsgx B2使用升级前的 agent_level 判断资格,避免触发升级的那笔订单就给新等级积分
$agentLevelId = array_key_exists($uid, $preUpgradeLevels)
? $preUpgradeLevels[$uid]
: (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, $orderDbId);
$this->propagateReward((int)$user['spread_uid'], $uid, $orderId, 0, $depth + 1, $orderDbId, $preUpgradeLevels, $qty);
}
return;
}
$isDirect = ($depth === 0);
$reward = $isDirect
? $this->agentLevelServices->getDirectRewardPoints($agentLevelId)
: $this->agentLevelServices->getUmbrellaRewardPoints($agentLevelId);
// fsgx B4直推奖励和伞下奖励分别读取级差计算只用伞下奖励做扣减
// 这样当中间层如创客umbrella=0上报时上级云店umbrella=300
// 计算 max(0, 300-0)=300而不会被直推奖励 500 错误抵消
$directReward = $this->agentLevelServices->getDirectRewardPoints($agentLevelId);
$umbrellaReward = $this->agentLevelServices->getUmbrellaRewardPoints($agentLevelId);
$actual = max(0, $reward - $lowerReward);
if ($isDirect) {
// fsgx B3直推奖励按报单商品数量倍乘
$actual = $directReward * $qty;
} else {
// 级差:基于单件奖励做差值,再乘以数量
$actual = max(0, $umbrellaReward - $lowerReward) * $qty;
}
if ($actual > 0) {
$this->grantFrozenPoints(
@@ -114,7 +132,7 @@ class PointsRewardServices extends BaseServices
$actual,
$orderId,
$isDirect ? 'reward_direct' : 'reward_umbrella',
($isDirect ? '直推奖励' : '伞下奖励(级差)') . " - 来源订单 {$orderId}",
($isDirect ? '直推奖励' : '伞下奖励(级差)') . " x{$qty} - 来源订单 {$orderId}",
$orderDbId
);
}
@@ -124,9 +142,11 @@ class PointsRewardServices extends BaseServices
(int)$user['spread_uid'],
$uid,
$orderId,
$reward,
$umbrellaReward, // 向上传单件伞下奖励(级差基数),让上级自行乘以 $qty
$depth + 1,
$orderDbId
$orderDbId,
$preUpgradeLevels,
$qty
);
}
}

View File

@@ -1002,22 +1002,42 @@ class StoreOrderCreateServices extends BaseServices
$useCycleBrokerage = ($cycleCount > 0 && is_array($cycleRates) && count($cycleRates) > 0 && $isQueueGoods === 1);
if ($useCycleBrokerage && $spread_uid > 0) {
// 统计推荐人下级已完成的有效报单商品订单数,取模得到当前位次
// 注意compute() 在 paid=1 之后执行,当前订单已被计入,需 -1 得到"之前完成单数"
/** @var \app\dao\order\StoreOrderDao $orderDao */
$orderDao = app()->make(\app\dao\order\StoreOrderDao::class);
$completedCount = $orderDao->count([
'spread_uid' => $spread_uid,
'is_queue_goods' => 1,
'paid' => 1,
'is_del' => 0,
]);
$position = max(0, $completedCount - 1) % $cycleCount;
$cycleRatePercent = isset($cycleRates[$position]) ? (int)$cycleRates[$position] : (int)($cycleRates[0] ?? 0);
if ($cycleRatePercent > 0) {
$brokerageRatio = bcdiv((string)$cycleRatePercent, 100, 4);
$oneBrokerage = bcmul((string)$price, (string)$brokerageRatio, 2);
}
// fsgx B1 + B6用事务锁序列化位次计算防止并发竞态导致两笔订单拿到相同位次
$brokerageResult = \think\facade\Db::transaction(function () use ($spread_uid, $cycleCount, $cycleRates, $price) {
// 锁定推荐人行,确保同一推荐人同时只有一个事务在计算位次
\think\facade\Db::name('user')
->where('uid', $spread_uid)
->lock(true)
->value('uid');
// fsgx B1推荐人自己必须有报单商品订单才能获得推荐返现佣金
$spreaderOwnCount = (int)\think\facade\Db::name('store_order')
->where('uid', $spread_uid)
->where('is_queue_goods', 1)
->where('paid', 1)
->where('is_del', 0)
->count();
if ($spreaderOwnCount <= 0) {
return '0';
}
// fsgx B6按 id ASC 排序取位次,比 count 更精确且在锁保护下无竞态
// 当前订单在 paid=1 后已写入,这里取所有已完成订单并按序找到本单排名
$completedCount = (int)\think\facade\Db::name('store_order')
->where('spread_uid', $spread_uid)
->where('is_queue_goods', 1)
->where('paid', 1)
->where('is_del', 0)
->count();
$position = max(0, $completedCount - 1) % $cycleCount;
$cycleRatePercent = isset($cycleRates[$position]) ? (int)$cycleRates[$position] : (int)($cycleRates[0] ?? 0);
if ($cycleRatePercent > 0) {
return bcmul((string)$price, bcdiv((string)$cycleRatePercent, '100', 4), 2);
}
return '0';
});
$oneBrokerage = $brokerageResult;
} else {
//一级返佣比例 小于等于零时直接返回 不返佣
if ($storeBrokerageRatio > 0) {

View File

@@ -23,6 +23,7 @@ use crmeb\services\wechat\MiniProgram;
use crmeb\services\wechat\OfficialAccount;
use GuzzleHttp\Psr7\Utils;
use think\annotation\Inject;
use think\facade\Log;
use think\exception\ValidateException;
@@ -282,10 +283,12 @@ class QrcodeServices extends BaseServices
}
}
$siteUrl = sys_config('site_url');
$imageInfo = '';
if (!$imageInfo) {
$res = MiniProgram::appCodeUnlimit($data, $page, 280);
if (!$res) return false;
if (!$res) {
Log::error('[getRoutineQrcodePath] appCodeUnlimit 返回空, scene=' . $data . ', page=' . $page);
return false;
}
$uploadType = (int)sys_config('upload_type', 1);
$upload = UploadService::init($uploadType);
$res = (string)Utils::streamFor($res);
@@ -317,6 +320,7 @@ class QrcodeServices extends BaseServices
if ($imageInfo['image_type'] == 1) $url = $siteUrl . $url;
return $url;
} catch (\Throwable $e) {
Log::error('[getRoutineQrcodePath] 生成小程序码异常: ' . $e->getMessage());
return false;
}
}

View File

@@ -25,5 +25,6 @@ return [
'holiday_gift_push_task' => \app\command\HolidayGiftPushTask::class,
'hjf:release-points' => \app\command\HjfReleasePoints::class,
'hjf:patch-rewards' => \app\command\HjfPatchMissingRewards::class,
'hjf:verify-agent-config' => \app\command\HjfVerifyAgentConfig::class,
],
];

View File

@@ -1,5 +1,8 @@
<template>
<view class="hjf-assets-page" :style="colorStyle">
<!-- #ifdef MP -->
<NavBar titleText="我的资产" :iconColor="iconColor" :textColor="iconColor" showBack :isScrolling="isScrolling"></NavBar>
<!-- #endif -->
<view class="assets-wrapper">
<view class="assets-header">
@@ -116,14 +119,25 @@
<script>
import { getAssetsOverview } from '@/api/hjfAssets.js';
import colors from '@/mixins/color.js';
// #ifdef MP
import NavBar from '@/components/NavBar.vue';
// #endif
export default {
name: 'AssetsIndex',
mixins: [colors],
components: {
// #ifdef MP
NavBar,
// #endif
},
data() {
return {
iconColor: '#FFFFFF',
isScrolling: false,
assetsInfo: null,
loading: false
};
@@ -146,7 +160,8 @@ export default {
},
formattedTotalPoints() {
if (!this.assetsInfo) return '0';
return Number(this.assetsInfo.total_points_earned).toLocaleString();
const val = Number(this.assetsInfo.total_points_earned);
return isNaN(val) ? '0' : val.toLocaleString();
}
},
@@ -160,6 +175,16 @@ export default {
}
},
onPageScroll(e) {
if (e.scrollTop > 50) {
this.isScrolling = true;
this.iconColor = '#333333';
} else {
this.isScrolling = false;
this.iconColor = '#FFFFFF';
}
},
methods: {
fetchAssetsOverview() {
this.loading = true;

View File

@@ -1,5 +1,8 @@
<template>
<view class="brokerage-page" :style="colorStyle">
<!-- #ifdef MP -->
<NavBar titleText="佣金状态" :iconColor="iconColor" :textColor="iconColor" showBack :isScrolling="isScrolling"></NavBar>
<!-- #endif -->
<!-- 顶部渐变区域 -->
<view class="header-gradient">
<view class="header-gradient__circle header-gradient__circle--1"></view>
@@ -88,14 +91,26 @@ import HjfRefundNotice from '@/components/HjfRefundNotice.vue';
import emptyPage from '@/components/emptyPage.vue';
import colors from '@/mixins/color.js';
import { spreadOrder } from '@/api/user.js';
// #ifdef MP
import NavBar from '@/components/NavBar.vue';
// #endif
export default {
name: 'BrokerageStatus',
mixins: [colors],
components: { HjfQueueProgress, HjfRefundNotice, emptyPage },
components: {
HjfQueueProgress,
HjfRefundNotice,
emptyPage,
// #ifdef MP
NavBar,
// #endif
},
data() {
return {
iconColor: '#FFFFFF',
isScrolling: false,
progressData: {},
records: [],
loading: false,
@@ -108,6 +123,16 @@ export default {
};
},
onPageScroll(e) {
if (e.scrollTop > 50) {
this.isScrolling = true;
this.iconColor = '#333333';
} else {
this.isScrolling = false;
this.iconColor = '#FFFFFF';
}
},
onLoad() {
this.loadProgress();
this.loadRecords();

View File

@@ -18,11 +18,6 @@
:class='type == 2 ? "on" : ""'
@click='changeType(2)'
>储值</view>
<view
class='item'
:class='type == "queue_refund" ? "on" : ""'
@click='changeType("queue_refund")'
>公排退款</view>
</view>
<!-- 账单列表 -->
@@ -37,14 +32,9 @@
:key="indexn"
>
<view>
<view class='name line1'>
{{ vo.title }}
<!-- 公排退款标记 -->
<text
v-if="vo.type === 'queue_refund'"
class='queue-refund-tag'
>公排退款</text>
</view>
<view class='name line1'>
{{ vo.title }}
</view>
<view class='time-text'>{{ vo.add_time }}</view>
</view>
<view class='num' :class="vo.pm ? 'num-add' : 'num-sub'">
@@ -86,7 +76,6 @@
* - 0: 全部
* - 1: 消费
* - 2: 储值
* - "queue_refund": 公排退款type=queue_refund 时显示专属标记)
*
* 列表按日期分组,支持上拉分页加载。
*
@@ -109,11 +98,11 @@
page: 1,
/** @type {number} 每页条数 */
limit: 15,
/**
* 当前筛选类型
* 0=全部 1=消费 2=储值 "queue_refund"=公排退款
* @type {number|string}
*/
/**
* 当前筛选类型
* 0=全部 1=消费 2=储值
* @type {number|string}
*/
type: 0,
/** @type {Array<Object>} 按日期分组的账单列表 */
userBillList: [],
@@ -201,12 +190,12 @@
});
},
/**
* 切换账单筛选类型并重置列表
*
* @param {number|string} type - 目标类型0全部 1消费 2储值 "queue_refund"公排退款
* @returns {void}
*/
/**
* 切换账单筛选类型并重置列表
*
* @param {number|string} type - 目标类型0全部 1消费 2储值
* @returns {void}
*/
changeType(type) {
if (this.type === type) return;
this.type = type;
@@ -267,19 +256,6 @@
color: #333333;
}
/* 公排退款标记 */
.queue-refund-tag {
display: inline-block;
margin-left: 10rpx;
padding: 2rpx 12rpx;
border-radius: 20rpx;
font-size: 20rpx;
color: #fff;
background: var(--view-theme);
vertical-align: middle;
line-height: 1.6;
}
/* 顶部类型筛选导航 */
.bill-details .nav {
background-color: #fff;

View File

@@ -13,9 +13,9 @@
<w-barcode :options="config.bar"></w-barcode>
</view> -->
<view class="acea-row row-center-wrapper" style="margin-top: 56rpx;">
<!-- #ifdef MP -->
<image :src="qrc" class="qrcode"></image>
<!-- #endif -->
<!-- #ifdef MP -->
<image v-if="qrc" :src="qrc" class="qrcode"></image>
<!-- #endif -->
<!-- #ifdef H5 -->
<image v-if="$wechat.isWeixin()" :src="qrc" class="qrcode"></image>
<w-qrcode v-else :options="config.qrc"></w-qrcode>
@@ -132,14 +132,14 @@
routineUrl,
wechatUrl
} = res.data;
// #ifdef MP
this.qrc = routineUrl;
// #endif
// #ifdef H5
if (this.$wechat.isWeixin()) {
this.qrc = wechatUrl;
}
// #endif
// #ifdef MP
this.qrc = routineUrl || '';
// #endif
// #ifdef H5
if (this.$wechat.isWeixin()) {
this.qrc = wechatUrl || '';
}
// #endif
});
},
goDetail(val) {

View File

@@ -205,22 +205,23 @@
if (url.indexOf('https://') > -1) return url;
else return url.replace('http://', 'https://');
},
//获取图片
async spreadMsg() {
let res = await spreadMsg()
this.spreadData = res.data.spread
this.nickName = res.data.nickname
this.siteName = res.data.site_name
uni.showLoading({
title: '海报生成中',
mask: true
});
//获取图片
async spreadMsg() {
let res = await spreadMsg()
this.spreadData = res.data.spread
this.nickName = res.data.nickname
this.siteName = res.data.site_name
uni.showLoading({
title: '海报生成中',
mask: true
});
try {
for (let i = 0; i < res.data.spread.length; i++) {
let that = this
let arr2 = [];
let img = await this.downloadFilestoreImage(res.data.spread[i].pic);
let avatar = await this.downloadFilestoreImage(res.data.avatar);
let followCode = res.data.qrcode?await this.downloadFilestoreImage(res.data.qrcode):'';
let followCode = res.data.qrcode ? await this.downloadFilestoreImage(res.data.qrcode) : '';
// #ifdef H5
arr2 = [followCode || this.codeSrc, img, avatar]
// #endif
@@ -231,8 +232,12 @@
// #ifdef APP-PLUS
arr2 = [this.codeSrc, img, avatar]
// #endif
if (!img) {
console.warn('[海报] 背景图下载失败,跳过第', i, '张海报生成');
continue;
}
this.$nextTick(function(){
that.$util.userPosterCanvas(arr2, res.data.nickname, res.data.site_name, i, this.wd, this.hg, (
that.$util.userPosterCanvas(arr2, res.data.nickname, res.data.site_name, i, that.wd, that.hg, (
tempFilePath) => {
that.$set(that.posterImage, i, tempFilePath);
// #ifdef MP
@@ -245,8 +250,12 @@
});
})
}
} catch (e) {
console.error('[海报] 生成异常:', e);
} finally {
uni.hideLoading();
},
}
},
// #ifdef MP
async routineCode() {
let res = await routineCode()
@@ -350,23 +359,26 @@
});
},
// #endif
//图片转符合安全域名路径
downloadFilestoreImage(url) {
return new Promise((resolve, reject) => {
let that = this;
uni.downloadFile({
url: that.setDomain(url),
success: function(res) {
resolve(res.tempFilePath);
},
fail: function() {
return that.$util.Tips({
title: ''
});
}
});
})
},
//图片转符合安全域名路径
downloadFilestoreImage(url) {
return new Promise((resolve, reject) => {
let that = this;
if (!url) {
resolve('');
return;
}
uni.downloadFile({
url: that.setDomain(url),
success: function(res) {
resolve(res.tempFilePath);
},
fail: function(err) {
console.error('[海报] 图片下载失败:', url, err);
resolve('');
}
});
})
},
setShareInfoStatus: function() {
if (this.$wechat.isWeixin()) {
if (this.isLogin) {