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

Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods

Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口

Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则

Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)

Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口

Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单

Made-with: Cursor
This commit is contained in:
apple
2026-03-23 22:32:19 +08:00
parent 788ee0c0c0
commit 434aa8c69d
13098 changed files with 2008990 additions and 961 deletions

View File

@@ -13,9 +13,9 @@
<view class="hero-card__bg-circle hero-card__bg-circle--1"></view>
<view class="hero-card__bg-circle hero-card__bg-circle--2"></view>
<view class="hero-card__main">
<view class="hero-card__label">金余额()</view>
<view class="hero-card__label">推荐佣金余额()</view>
<view class="hero-card__money">
<text class="hero-card__yen">¥</text>{{ assetsInfo ? Number(assetsInfo.now_money).toFixed(2) : '0.00' }}
<text class="hero-card__yen">¥</text>{{ assetsInfo ? Number(assetsInfo.brokerage_price || 0).toFixed(2) : '0.00' }}
</view>
</view>
<view class="hero-card__row">
@@ -98,8 +98,8 @@
<text class="iconfont icon-qianbao"></text>
</view>
<view class="stats-info">
<view class="stats-value">¥{{ assetsInfo.total_queue_refund }}</view>
<view class="stats-label">公排累计退款</view>
<view class="stats-value">¥{{ Number(assetsInfo.brokerage_price || 0).toFixed(2) }}</view>
<view class="stats-label">推荐累计佣金</view>
</view>
</view>
<view class="stats-divider"></view>
@@ -169,8 +169,8 @@ export default {
this.loading = true;
getAssetsOverview()
.then(res => {
if (res && res.status === 200) {
this.assetsInfo = res.data;
if (res && (res.status === 200 || res.data)) {
this.assetsInfo = res.data || res;
}
})
.catch(() => {

View File

@@ -207,36 +207,23 @@
</view>
</view>
</view>
<!-- 报单商品公排提示is_queue_goods=1 时显示 -->
<view class="hjf-queue-notice mt-24" v-if="isQueueOrder">
<view class="hjf-queue-notice__header flex-y-center">
<text class="hjf-queue-notice__icon iconfont icon-tishi"></text>
<text class="hjf-queue-notice__title fs-26 fw-500">公排商品提示</text>
<!-- 报单商品佣金提示is_queue_goods=1 时显示 -->
<view class="hjf-queue-notice mt-24" v-if="isQueueOrder">
<view class="hjf-queue-notice__header flex-y-center">
<text class="hjf-queue-notice__icon iconfont icon-tishi"></text>
<text class="hjf-queue-notice__title fs-26 fw-500">报单商品提示</text>
</view>
<view class="hjf-queue-notice__body mt-16">
<!-- 佣金说明 -->
<view class="hjf-queue-notice__item flex-y-center mt-12">
<text class="hjf-queue-notice__dot"></text>
<text class="fs-24 text--w111-666 lh-34rpx">购买后您的推荐人将根据推荐周期获得佣金返现</text>
</view>
<!-- 积分说明 -->
<view class="hjf-queue-notice__item flex-y-center mt-12">
<text class="hjf-queue-notice__dot"></text>
<text class="fs-24 text--w111-666 lh-34rpx">报单商品不支持积分支付抵扣</text>
</view>
<view class="hjf-queue-notice__body mt-16">
<!-- 拆单提示购买数量 > 1 时显示 -->
<view class="hjf-queue-notice__item flex-y-center" v-if="queueSplitCount > 1">
<text class="hjf-queue-notice__dot"></text>
<text class="fs-24 text--w111-666 lh-34rpx">
本次将拆分为
<text class="hjf-queue-notice__num">{{ queueSplitCount }}</text>
个独立公排订单每单独立排队
</text>
</view>
<!-- 公排规则说明 -->
<view class="hjf-queue-notice__item flex-y-center mt-12">
<text class="hjf-queue-notice__dot"></text>
<text class="fs-24 text--w111-666 lh-34rpx">购买后自动加入公排池进四退一全额返还</text>
</view>
<!-- 预计退款批次 -->
<view class="hjf-queue-notice__item flex-y-center mt-12">
<text class="hjf-queue-notice__dot"></text>
<text class="fs-24 text--w111-666 lh-34rpx">
预计退款时间购买后第
<text class="hjf-queue-notice__num">{{ estimatedRefundBatch }}</text>
个批次仅供参考以实际排队进度为准
</text>
</view>
<!-- 跳转公排状态页 -->
<view class="hjf-queue-notice__link flex-y-center justify-end mt-16" @tap="goPage(1, '/pages/queue/status')">
<text class="fs-24 hjf-queue-notice__link-text">查看公排进度</text>
@@ -292,8 +279,9 @@
<text class="iconfont icon-ic_rightarrow fs-24 text--w111-999 pl-8"></text>
</view>
</view>
<view class="cell flex-between-center" v-if="[0, 6].includes(type) && integral_ratio_status == 1">
<text class="text--w111-333 fs-28">积分抵扣</text>
<!-- 报单商品禁用积分抵扣 -->
<view class="cell flex-between-center" v-if="[0, 6].includes(type) && integral_ratio_status == 1 && !isQueueOrder">
<text class="text--w111-333 fs-28">积分抵扣</text>
<view class="flex-y-center">
<view>
{{ useIntegral ? '剩余积分' : '当前积分' }}

View File

@@ -14,24 +14,21 @@
<text class="fs-40 fw-500 text--w111-fff pl-16" v-else>{{ order_pay_info.paid ? '订单支付成功' : '等待支付...' }}</text>
</view>
<!-- 公排入队成功提示 -->
<view v-if="isQueueOrder && order_pay_info.paid && queueEntries.length > 0" class="queue-success-notice mt-20">
<!-- 报单商品佣金提示 -->
<view v-if="isQueueOrder && order_pay_info.paid" class="queue-success-notice mt-20">
<view class="flex-y-center">
<text class="iconfont icon-chenggong fs-28 queue-icon"></text>
<text class="fs-26 text--w111-fff">已加入公排队列</text>
<text class="fs-26 text--w111-fff">报单商品支付成功</text>
</view>
<view class="fs-22 text--w111-fff mt-8 opacity-80" v-if="queueEntries.length === 1">
排队序号 #{{ queueEntries[0].queue_no }}{{ queueEntries[0].estimated_wait }}
</view>
<view class="fs-22 text--w111-fff mt-8 opacity-80" v-else>
{{ queueEntries.length }} 个订单已入队
<view class="fs-22 text--w111-fff mt-8 opacity-80">
推荐好友购买可获得佣金返现
</view>
</view>
<view class="flex-center mt-30">
<view class="w-192 h-64 rd-40rpx flex-center fs-24 text--w111-fff white-border" @tap="goIndex">返回首页</view>
<view v-if="isQueueOrder && order_pay_info.paid" class="w-192 h-64 rd-40rpx flex-center fs-24 text--w111-fff white-border ml-48" @tap="goQueueStatus">
查看公排
查看佣金
</view>
<view v-else-if="!order_pay_info.is_send_gift || !order_pay_info.paid" class="w-192 h-64 rd-40rpx flex-center fs-24 text--w111-fff white-border ml-48" @tap="goOrderDetails">
查看订单

View File

@@ -420,10 +420,10 @@
</view>
<!-- 底部操作按钮 -->
<view class="page_footer bg--w111-fff-s111-80 w-full z-99 fixed-lb pb-safe">
<!-- 报单商品公排提示is_queue_goods=1 时在购买按钮上方显示 -->
<!-- 报单商品佣金提示is_queue_goods=1 时在购买按钮上方显示 -->
<view v-if="isQueueGoods" class="queue-goods-notice flex-y-center px-32 py-16">
<text class="iconfont icon-ic_user fs-28 queue-goods-notice__icon"></text>
<text class="fs-24 queue-goods-notice__text pl-8">报单商品 · 购买后自动参与公排进四退一全额返还</text>
<text class="fs-24 queue-goods-notice__text pl-8">报单商品 · 推荐好友购买可获得佣金返现</text>
</view>
<view class="w-full h-104 pl-32 pr-20 flex">
<view class="flex">

View File

@@ -1,75 +1,72 @@
<template>
<view :style="colorStyle" class="queue-rules">
<!-- 公排机制图示 -->
<!-- 推荐佣金流程图示 -->
<view class="section">
<view class="section-title">公排机制图示</view>
<view class="section-title">推荐佣金流程</view>
<view class="mechanism-card">
<view class="mechanism-title">进四退一流程</view>
<view class="mechanism-title">推荐3人周期佣金流程</view>
<view class="flow-diagram">
<!-- 第一步 -->
<view class="flow-step">
<view class="step-circle step-in"></view>
<view class="step-desc">用户A 购买报单商品</view>
<view class="step-sub">¥3,600 自动入队#1</view>
<view class="step-circle step-in"></view>
<view class="step-desc">您邀请用户A 购买报单商品</view>
<view class="step-sub">您获得 20% 佣金第1位比例</view>
</view>
<view class="flow-arrow"></view>
<!-- 第二步 -->
<view class="flow-step">
<view class="step-circle step-in"></view>
<view class="step-desc">用户B 购买报单商品</view>
<view class="step-sub">¥3,600 自动入队#2</view>
<view class="step-circle step-in"></view>
<view class="step-desc">您邀请用户B 购买报单商品</view>
<view class="step-sub">您获得 30% 佣金第2位比例</view>
</view>
<view class="flow-arrow"></view>
<!-- 第三步 -->
<view class="flow-step">
<view class="step-circle step-in"></view>
<view class="step-desc">用户C 购买报单商品</view>
<view class="step-sub">¥3,600 自动入队#3</view>
</view>
<view class="flow-arrow"></view>
<!-- 第四步触发退款 -->
<!-- 第三步触发完整周期 -->
<view class="flow-step trigger">
<view class="step-circle step-in"></view>
<view class="step-desc">用户D 购买报单商品</view>
<view class="step-sub">¥3,600 自动入队#4</view>
<view class="trigger-badge">触发退款</view>
<view class="step-circle step-in"></view>
<view class="step-desc">您邀请用户C 购买报单商品</view>
<view class="step-sub">您获得 50% 佣金第3位比例</view>
<view class="trigger-badge">完成一个周期</view>
</view>
<view class="flow-arrow refund-arrow"></view>
<!-- 退款 -->
<!-- 新周期 -->
<view class="flow-step refund-step">
<view class="step-circle step-out">退</view>
<view class="step-desc">用户A 全额退款</view>
<view class="step-sub">¥3,600 返还至现金余额</view>
<view class="step-circle step-out"></view>
<view class="step-desc">进入下一推荐周期</view>
<view class="step-sub">重新从 20% 开始计算</view>
</view>
</view>
<!-- 循环说明 -->
<view class="cycle-note">
<text class="cycle-icon">🔄</text>
<text class="cycle-text">如此循环进入4单最早的1单全额退款</text>
<text class="cycle-text">如此循环推荐3人为一个周期按比例获得佣金</text>
</view>
</view>
<!-- 示例计算卡片 -->
<view class="example-card">
<view class="example-title">示例计算</view>
<view class="example-title">示例计算报单商品 ¥3,600</view>
<view class="example-row">
<text class="example-label">报单金额</text>
<text class="example-value">¥3,600.00</text>
<text class="example-label">推荐第1人佣金20%</text>
<text class="example-value">¥720.00</text>
</view>
<view class="example-row">
<text class="example-label">退款金额</text>
<text class="example-value highlight">¥3,600.00全额</text>
<text class="example-label">推荐第2人佣金30%</text>
<text class="example-value">¥1,080.00</text>
</view>
<view class="example-row">
<text class="example-label">平均等待单数</text>
<text class="example-value"> 3 即约3倍报单量</text>
<text class="example-label">推荐第3人佣金50%</text>
<text class="example-value highlight">¥1,800.00</text>
</view>
<view class="example-divider"></view>
<view class="example-row">
<text class="example-label">一个周期累计佣金</text>
<text class="example-value highlight">¥3,600.00100%</text>
</view>
<view class="example-divider"></view>
<view class="example-desc">
假设公排池每天新增20单您的排队序号为第14位预计约需等待 3 天可触发退款
实际等待时间取决于整体报单速度报单越活跃退款越快
佣金将在被推荐人支付订单后直接发放至您的佣金余额可随时申请提现手续费7%
</view>
</view>
</view>
@@ -113,7 +110,7 @@
<!-- 底部声明 -->
<view class="footer-note">
<text>本平台公排规则最终解释权归黄精粉健康商城所有</text>
<text>本平台推荐佣金规则最终解释权归范氏国香商城所有</text>
</view>
</view>
@@ -145,32 +142,32 @@ export default {
*/
ruleItems: [
{
title: '全额退款保障',
content: '参与公排的报单金额为 ¥3,600退款时按原金额全额返还至您的现金余额不收取任何手续费。'
title: '推荐佣金机制',
content: '购买报单商品¥3,600当您推荐好友购买时您可获得对应周期位次的佣金比例20%/30%/50%。'
},
{
title: '进四退一机制',
content: '公排池按照入队时间先后排队。每当新入队单数达到当前最早排队单数的4倍时最早的1单自动触发退款。'
title: '3人周期循环',
content: '每推荐3人为一个完整周期依次按第1人20%、第2人30%、第3人50%发放佣金。周期结束后自动进入下一轮。'
},
{
title: '退款自动到账',
content: '退款金额将在触发退款后立即到账至您的金余额,无需手动申请,可在"我的资产"中查看。'
title: '佣金自动到账',
content: '佣金将在被推荐人支付订单后直接发放至您的金余额,无需手动申请,可在"推荐佣金"页查看。'
},
{
title: '入队资格',
content: '购买报单商品(黄精粉套餐 ¥3,600系统自动为该订单分配全局唯一的排队序号按购买时间先后排序。'
title: '报单商品范围',
content: '购买标注为"报单商品"的商品才参与佣金计算,普通商品按常规佣金比例计算。'
},
{
title: '多单独立排队',
content: '同一用户购买多单报单商品,每单独立分配排队序号,各自独立参与公排循环,互不影响。'
title: '推荐关系绑定',
content: '被推荐人须通过您的专属邀请链接/二维码注册并购买,方可绑定推荐关系并产生佣金。'
},
{
title: '积分奖励叠加',
content: '公排退款与积分奖励体系相互独立,参与公排的同时可正常获得推荐积分奖励,两者并行不悖。'
content: '推荐佣金与积分奖励体系相互独立,获得佣金的同时可正常获得待释放积分奖励,每日自动释放0.4‰到可用积分。'
},
{
title: '规则变更通知',
content: '若平台对公排规则进行调整,将提前通过公告及消息通知用户,变更后的规则不溯及既往订单。'
content: '若平台对佣金规则进行调整,将提前通过公告及消息通知用户,变更后的规则不溯及既往订单。'
}
],
@@ -180,38 +177,38 @@ export default {
*/
faqItems: [
{
question: '参与公排后多久能收到退款',
answer: '等待时间取决于公排池的整体报单速度。每进入4单触发最早1单退款。若每天新增约20单一般约3-5天可收到退款。您可在"公排状态"页查看实时进度和预估等待时间。',
question: '推荐一个人能得多少佣金',
answer: '取决于您在当前周期的位次。第1人20%第2人30%第3人50%。以报单商品¥3,600为例分别是¥720、¥1,080、¥1,800。',
open: false
},
{
question: '退款会到哪里?',
answer: '退款金额将全额返还至您在平台的现金余额,可在"我的资产"中查看并可随时申请提现提现手续费7%)。',
question: '佣金会到哪里?',
answer: '佣金将发放至您的佣金余额,可在"推荐佣金"页查看并可随时申请提现提现手续费7%)。',
open: false
},
{
question: '一个人可以参与多次公排吗',
answer: '可以。每次购买报单商品均会独立进入公排队列,获得新的排队序号。多单独立排队,各自触发退款,相互不影响。',
question: '推荐超过3人怎么计算',
answer: '每满3人为一个周期自动进入下一周期重新从第1位20%开始计算。如推荐第4人其佣金比例与第1人相同20%。',
open: false
},
{
question: '公排退款后还能继续参与吗?',
answer: '可以。退款到账后您可以再次购买报单商品重新入队,循环享受公排返利。',
question: '我没有购买报单商品可以推荐别人吗?',
answer: '可以推荐,但获得佣金需要您有对应的分销等级。建议先购买报单商品成为会员,再邀请好友。',
open: false
},
{
question: '为什么我的排队序号不是第1位',
answer: '公排池是全平台共享队列,您的排队序号代表全局位置。序号前面的用户将优先触发退款,请耐心等待。',
question: '待释放积分是什么',
answer: '每次获得佣金时同步获得等额待释放积分1元佣金=100积分。积分每天自动释放0.4‰到可用积分,可用于平台消费抵扣。',
open: false
},
{
question: '公排和积分奖励可以同时获得吗?',
answer: '可以。购买报单商品后,您的直接推荐人可获得积分奖励,同时该订单进入公排队列。两套机制并行运作,互不影响。',
question: '佣金和积分可以同时获得吗?',
answer: '可以。推荐好友购买报单商品后,您同时获得佣金和待释放积分,两套机制并行运作,互不影响。',
open: false
},
{
question: '如何查看我的排队进度?',
answer: '进入"公排状态"页可查看:您的排队序号、当前批次进度X/4)、预计等待时间。页面实时展示全局公排进度。',
question: '如何查看我的佣金进度?',
answer: '进入"推荐佣金"页可查看:当前周期推荐进度X/3)、各档比例、累计佣金、佣金记录明细。',
open: false
}
]

View File

@@ -1,480 +1,250 @@
<template>
<view class="queue-status-page" :style="colorStyle">
<view class="brokerage-page" :style="colorStyle">
<!-- 顶部渐变区域 -->
<view class="header-gradient">
<view class="header-gradient__bg-circle header-gradient__bg-circle--1"></view>
<view class="header-gradient__bg-circle header-gradient__bg-circle--2"></view>
<view class="header-gradient__circle header-gradient__circle--1"></view>
<view class="header-gradient__circle header-gradient__circle--2"></view>
<view class="header-card">
<view class="header-card__label">公排池总单数</view>
<view class="header-card__total">{{ queueStatus.totalOrders || 0 }}</view>
<view class="header-card__progress" v-if="queueStatus.progress">
<HjfQueueProgress
:current-count="queueStatus.progress.current_batch_count"
:trigger-multiple="queueStatus.progress.trigger_multiple"
:next-refund-no="queueStatus.progress.next_refund_queue_no"
/>
<view class="header-card__label">累计佣金收入</view>
<view class="header-card__amount">
<text class="header-card__currency">¥</text>
<text class="header-card__value">{{ progressData.total_brokerage || '0.00' }}</text>
</view>
<!-- 佣金周期进度环 -->
<HjfQueueProgress
:cycle-current="progressData.cycle_current || 0"
:cycle-total="progressData.cycle_total || 3"
:cycle-rates="progressData.cycle_rates || [20, 30, 50]"
/>
</view>
</view>
<view class="order-list-section">
<!-- 佣金记录列表 -->
<view class="records-section">
<view class="section-header">
<view class="section-header__dot"></view>
<view class="section-header__title">我的排队订单</view>
<view class="section-header__bar"></view>
<text class="section-header__title">佣金记录</text>
<text class="section-header__more" @tap="goToCommissionDetail">查看全部</text>
</view>
<view v-if="loading" class="loading-wrap">
<view class="loading-dots">
<view class="loading-dot loading-dot--1"></view>
<view class="loading-dot loading-dot--2"></view>
<view class="loading-dot loading-dot--3"></view>
</view>
<text class="loading-text">加载中...</text>
<view class="loading-dot loading-dot--1"></view>
<view class="loading-dot loading-dot--2"></view>
<view class="loading-dot loading-dot--3"></view>
</view>
<view v-else-if="!queueStatus.myOrders || queueStatus.myOrders.length === 0" class="empty-wrap">
<emptyPage title="暂无排队记录" src="/statics/images/noOrder.gif" />
<view v-else-if="records.length === 0" class="empty-wrap">
<emptyPage title="暂无佣金记录" src="/statics/images/noOrder.gif" />
</view>
<view v-else class="order-list">
<view v-else class="record-list">
<view
v-for="(order, index) in queueStatus.myOrders"
:key="order.id || index"
class="order-item"
v-for="(item, index) in records"
:key="item.id || index"
class="record-item"
>
<view class="order-item__top">
<view class="order-item__no">
<text class="order-item__no-hash">#</text>
<text class="order-item__no-value">{{ order.queue_no }}</text>
<view class="record-item__left">
<view class="record-item__avatar-wrap">
<image
class="record-item__avatar"
:src="item.avatar || '/statics/images/avatar.png'"
mode="aspectFill"
/>
</view>
<view
class="order-item__tag"
:class="order.status === 0 ? 'order-item__tag--active' : 'order-item__tag--refunded'"
>
<view class="order-item__tag-dot" :class="order.status === 0 ? 'order-item__tag-dot--active' : 'order-item__tag-dot--refunded'"></view>
{{ order.status === 0 ? '排队中' : '已退款' }}
<view class="record-item__info">
<text class="record-item__name">{{ item.nickname || '用户' }}</text>
<text class="record-item__time">{{ item.time }}</text>
</view>
</view>
<view class="order-item__id">{{ order.order_id }}</view>
<view class="order-item__bottom">
<text class="order-item__amount">¥{{ Number(order.amount).toFixed(2) }}</text>
<text class="order-item__wait">{{ order.estimated_wait }}</text>
<view class="record-item__right">
<text class="record-item__amount">+¥{{ item.number }}</text>
<text class="record-item__type">{{ item.title || '推荐佣金' }}</text>
</view>
</view>
</view>
</view>
<view v-if="queueStatus.myOrders && queueStatus.myOrders.length > 0" class="load-more-bar">
<text v-if="loadingMore" class="load-more-text">加载中...</text>
<text v-else-if="finished" class="load-more-text">没有更多内容啦~</text>
<text v-else class="load-more-text">上拉加载更多</text>
<!-- 上拉加载 -->
<view v-if="records.length > 0" class="load-more">
<text v-if="loadingMore" class="load-more__text">加载中...</text>
<text v-else-if="finished" class="load-more__text"> 没有更多了 </text>
</view>
<!-- 佣金到账通知 -->
<HjfRefundNotice
:visible="showRefund"
:amount="refundData.amount"
:order-id="refundData.orderId"
@close="handleRefundClose"
:visible="showNotice"
:amount="noticeData.amount"
:order-id="noticeData.orderId"
@close="showNotice = false"
/>
</view>
</template>
<script>
/**
* @file pages/queue/status.vue
* @description P12 公排状态页 — 展示公排池总单数、当前批次进度及我的排队订单列表,
* 并在退款到账时弹出 HjfRefundNotice 通知。
* @see docs/frontend-new-pages-spec.md 第 2.2.4 节
*/
import { getQueueStatus } from '@/api/hjfQueue.js';
import { getBrokerageProgress } from '@/api/hjfQueue.js';
import HjfQueueProgress from '@/components/HjfQueueProgress.vue';
import HjfRefundNotice from '@/components/HjfRefundNotice.vue';
import emptyPage from '@/components/emptyPage.vue';
import colors from '@/mixins/color.js';
import { getCommissionInfo } from '@/api/user.js';
export default {
name: 'QueueStatus',
name: 'BrokerageStatus',
mixins: [colors],
components: {
HjfQueueProgress,
HjfRefundNotice,
emptyPage
},
components: { HjfQueueProgress, HjfRefundNotice, emptyPage },
data() {
return {
/**
* 公排状态数据,包含 totalOrders、myOrders、progress
* @type {{ totalOrders: number, myOrders: Array, progress: Object }}
*/
queueStatus: {},
/**
* 是否正在加载数据
* @type {boolean}
*/
progressData: {},
records: [],
loading: false,
/**
* 是否显示退款通知弹窗
* @type {boolean}
*/
showRefund: false,
/**
* 退款弹窗所需数据
* @type {{ amount: number, orderId: string }}
*/
refundData: {
amount: 0,
orderId: ''
},
/** @type {boolean} 是否正在上拉加载更多 */
loadingMore: false,
/** @type {boolean} 是否已加载完全部数据 */
finished: false
finished: false,
page: 1,
limit: 15,
showNotice: false,
noticeData: { amount: 0, orderId: '' }
};
},
onLoad() {
this.loadQueueStatus();
},
onShow() {
this.checkPendingRefundNotice();
this.loadProgress();
this.loadRecords();
},
onReachBottom() {
this.loadMoreOrders();
if (!this.loadingMore && !this.finished) this.loadRecords();
},
methods: {
/**
* 加载公排状态数据
* 调用 getQueueStatus(),将返回值赋给 queueStatus
* 并在检测到已退款订单时触发退款弹窗。
* @returns {Promise<void>}
*/
loadQueueStatus() {
this.loading = true;
getQueueStatus()
loadProgress() {
getBrokerageProgress()
.then(res => {
if (res && res.data) {
this.$set(this, 'queueStatus', res.data);
this.detectNewRefund(res.data.myOrders || []);
}
if (res && res.data) this.progressData = res.data;
})
.catch(err => {
console.error('[QueueStatus] loadQueueStatus error:', err);
uni.showToast({ title: '加载失败,请稍后重试', icon: 'none' });
.catch(() => {});
},
loadRecords() {
if (this.loading || this.loadingMore || this.finished) return;
const isFirst = this.page === 1;
if (isFirst) this.loading = true; else this.loadingMore = true;
// type=3 获取返佣记录
getCommissionInfo({ page: this.page, limit: this.limit }, 3)
.then(res => {
const list = (res && res.data && res.data.list) ? res.data.list : [];
if (list.length < this.limit) this.finished = true;
if (isFirst) this.records = list;
else this.records = [...this.records, ...list];
this.page++;
})
.catch(() => {
if (isFirst) this.records = [];
})
.finally(() => {
this.loading = false;
this.loadingMore = false;
});
},
/**
* 检测是否存在刚完成退款的订单,有则弹出退款通知。
* 策略:取 status=1 且 refund_time 最大的一条(最近退款),
* 结合页面跳转参数 show_refund=1 触发弹窗。
* @param {Array} orders - 我的排队订单列表
*/
detectNewRefund(orders) {
const refunded = orders.filter(o => o.status === 1 && o.refund_time > 0);
if (!refunded.length) return;
refunded.sort((a, b) => b.refund_time - a.refund_time);
const latest = refunded[0];
const showParam = this._pageParams && this._pageParams.show_refund;
if (showParam === '1') {
this.refundData = {
amount: latest.amount,
orderId: latest.order_id
};
this.showRefund = true;
}
},
/**
* 页面显示时检查是否需要弹出退款通知(从外部跳转携带参数时使用)
*/
checkPendingRefundNotice() {
const pages = getCurrentPages();
const current = pages[pages.length - 1];
const options = (current && current.options) || {};
if (options.show_refund === '1' && this.queueStatus.myOrders) {
this.detectNewRefund(this.queueStatus.myOrders);
}
},
/**
* 上拉加载更多(当前数据由单次接口全量返回,模拟已到底状态)
* Phase 4 接入分页接口后替换为真实分页逻辑。
*/
loadMoreOrders() {
if (this.loadingMore || this.finished) return;
this.loadingMore = true;
setTimeout(() => {
this.finished = true;
this.loadingMore = false;
}, 500);
},
/**
* 关闭退款通知弹窗
*/
handleRefundClose() {
this.showRefund = false;
goToCommissionDetail() {
uni.navigateTo({ url: '/pages/users/user_spread_money/index' });
}
}
};
</script>
<style scoped lang="scss">
.queue-status-page {
.brokerage-page {
min-height: 100vh;
background: #f4f5f7;
padding-bottom: 40rpx;
padding-bottom: 60rpx;
}
.header-gradient {
background: linear-gradient(135deg, var(--view-theme, #e93323) 0%, var(--view-gradient, #f76b1c) 100%);
padding: 36rpx 30rpx 48rpx;
padding: 40rpx 30rpx 56rpx;
position: relative;
overflow: hidden;
}
.header-gradient__bg-circle {
.header-gradient__circle {
position: absolute;
border-radius: 50%;
background: #fff;
opacity: 0.06;
}
.header-gradient__bg-circle--1 {
width: 400rpx;
height: 400rpx;
top: -160rpx;
right: -100rpx;
}
.header-gradient__bg-circle--2 {
width: 240rpx;
height: 240rpx;
bottom: -60rpx;
left: -50rpx;
opacity: 0.07;
}
.header-gradient__circle--1 { width: 400rpx; height: 400rpx; top: -150rpx; right: -80rpx; }
.header-gradient__circle--2 { width: 240rpx; height: 240rpx; bottom: -60rpx; left: -50rpx; }
.header-card {
position: relative;
z-index: 1;
background: rgba(255, 255, 255, 0.14);
background: rgba(255,255,255,0.14);
border-radius: 28rpx;
padding: 36rpx 32rpx;
backdrop-filter: blur(10px);
}
.header-card__label { font-size: 26rpx; color: rgba(255,255,255,0.8); margin-bottom: 8rpx; }
.header-card__amount { display: flex; align-items: baseline; margin-bottom: 28rpx; }
.header-card__currency { font-size: 36rpx; color: #fff; font-weight: 600; margin-right: 4rpx; }
.header-card__value { font-size: 72rpx; font-weight: 700; color: #fff; line-height: 1; }
.header-card__label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 10rpx;
}
.header-card__total {
font-size: 64rpx;
font-weight: 700;
color: #fff;
font-family: 'SemiBold', sans-serif;
line-height: 1.1;
margin-bottom: 28rpx;
}
.header-card__progress {
background: rgba(255, 255, 255, 0.95);
border-radius: 18rpx;
overflow: hidden;
}
.order-list-section {
.records-section {
margin: -16rpx 20rpx 0;
position: relative;
z-index: 2;
background: #fff;
border-radius: 24rpx;
padding: 0 0 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
}
.section-header {
display: flex;
align-items: center;
padding: 24rpx 4rpx 20rpx;
padding: 28rpx 30rpx 20rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.section-header__dot {
width: 8rpx;
height: 30rpx;
border-radius: 4rpx;
background: var(--view-theme, #e93323);
margin-right: 14rpx;
flex-shrink: 0;
}
.section-header__title {
font-size: 30rpx;
font-weight: 600;
color: #333;
.section-header__bar {
width: 6rpx; height: 28rpx; border-radius: 3rpx;
background: var(--view-theme, #e93323); margin-right: 14rpx;
}
.section-header__title { font-size: 30rpx; font-weight: 600; color: #333; flex: 1; }
.section-header__more { font-size: 24rpx; color: var(--view-theme, #e93323); }
.loading-wrap {
display: flex; justify-content: center; align-items: center; gap: 12rpx;
padding: 60rpx 0;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.loading-dots {
display: flex;
gap: 12rpx;
}
.loading-dot {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
width: 14rpx; height: 14rpx; border-radius: 50%;
background: var(--view-theme, #e93323);
animation: dot-bounce 1.2s infinite ease-in-out;
}
.loading-dot--2 { animation-delay: 0.2s; }
.loading-dot--3 { animation-delay: 0.4s; }
@keyframes dot-bounce {
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
40% { opacity: 1; transform: scale(1.2); }
}
.loading-text {
font-size: 26rpx;
color: #999;
}
.empty-wrap { padding: 40rpx 0; }
.empty-wrap {
padding: 40rpx 0;
text-align: center;
.record-item {
display: flex; align-items: center; justify-content: space-between;
padding: 28rpx 30rpx;
border-bottom: 1rpx solid #f8f8f8;
}
.record-item__left { display: flex; align-items: center; gap: 20rpx; }
.record-item__avatar-wrap { width: 76rpx; height: 76rpx; border-radius: 50%; overflow: hidden; flex-shrink: 0; }
.record-item__avatar { width: 100%; height: 100%; }
.record-item__name { font-size: 28rpx; color: #333; font-weight: 500; display: block; }
.record-item__time { font-size: 22rpx; color: #999; display: block; margin-top: 6rpx; }
.record-item__right { text-align: right; }
.record-item__amount { font-size: 34rpx; font-weight: 700; color: #e93323; display: block; }
.record-item__type { font-size: 22rpx; color: #aaa; display: block; margin-top: 4rpx; }
.order-item {
background: #fff;
border-radius: 24rpx;
padding: 30rpx 32rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
position: relative;
overflow: hidden;
}
.order-item__top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 18rpx;
}
.order-item__no {
display: flex;
align-items: baseline;
}
.order-item__no-hash {
font-size: 26rpx;
font-weight: 500;
color: var(--view-theme, #e93323);
margin-right: 4rpx;
}
.order-item__no-value {
font-size: 36rpx;
font-weight: 700;
color: #333;
font-family: 'SemiBold', sans-serif;
}
.order-item__tag {
font-size: 22rpx;
font-weight: 500;
padding: 8rpx 20rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
gap: 8rpx;
}
.order-item__tag-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
}
.order-item__tag--active {
background: #e6f7ee;
color: #389e0d;
}
.order-item__tag-dot--active {
background: #52c41a;
}
.order-item__tag--refunded {
background: #f5f5f5;
color: #999;
}
.order-item__tag-dot--refunded {
background: #bbb;
}
.order-item__id {
font-size: 24rpx;
color: #999;
padding-bottom: 18rpx;
word-break: break-all;
}
.order-item__bottom {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 18rpx;
border-top: 1rpx solid #f0f0f0;
}
.order-item__amount {
font-size: 34rpx;
font-weight: 600;
color: #333;
font-family: 'SemiBold', sans-serif;
}
.order-item__wait {
font-size: 24rpx;
color: #999;
background: #fafafa;
padding: 6rpx 16rpx;
border-radius: 8rpx;
}
.load-more-bar {
padding: 32rpx 0 48rpx;
text-align: center;
}
.load-more-text {
font-size: 26rpx;
color: #aaa;
}
.load-more { padding: 32rpx 0 20rpx; text-align: center; }
.load-more__text { font-size: 24rpx; color: #bbb; }
</style>

View File

@@ -4,10 +4,14 @@
<template v-if="isObjectData(diyData)">
<user-member :userInfo="userInfo" :memberData="diyData.member" :orderAdminData="orderAdminData" :balanceStatus="balanceStatus" :isScrolling="isScrolling"></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">
<view class="acea-row row-middle row-center item" @tap="intoPage('/pages/assets/index')">
<view>
<!-- fsgx 快捷入口我的资产 & 推荐佣金 member-points 保持一致风格 -->
<!-- 分销等级徽标 -->
<view v-if="userInfo && userInfo.agent_level_name" class="hjf-level-badge acea-row row-center row-middle">
<text class="hjf-level-badge__tag">{{ userInfo.agent_level_name }}</text>
</view>
<view class="acea-row member-points hjf-nav-row">
<view class="acea-row row-middle row-center item" @tap="intoPage('/pages/assets/index')">
<view>
<view>我的资产</view>
<view class="arrow">
查看余额积分
@@ -16,12 +20,12 @@
</view>
<image src="@/static/images/user-member.png" class="image"></image>
</view>
<view class="acea-row row-middle row-center item" @tap="intoPage('/pages/queue/status')">
<view>
<view>公排查询</view>
<view class="arrow">
查看排队进度
<text class="iconfont icon-ic_rightarrow"></text>
<view class="acea-row row-middle row-center item" @tap="intoPage('/pages/queue/status')">
<view>
<view>推荐佣金</view>
<view class="arrow">
查看佣金进度
<text class="iconfont icon-ic_rightarrow"></text>
</view>
</view>
<image src="@/static/images/user-points.png" class="image"></image>
@@ -695,7 +699,20 @@ export default {
}
}
// 黄精粉导航行:复用 member-points 样式(与会员中心/积分商城完全一致)
// 分销等级徽标
.hjf-level-badge {
padding: 8rpx 30rpx;
&__tag {
background: linear-gradient(90deg, var(--view-theme, #e93323), var(--view-gradient, #f76b1c));
color: #fff;
font-size: 24rpx;
padding: 4rpx 20rpx;
border-radius: 20rpx;
font-weight: 500;
}
}
// fsgx 导航行:复用 member-points 样式(与会员中心/积分商城完全一致)
.hjf-nav-row {
margin-top: 0 !important;
}

View File

@@ -59,6 +59,27 @@
</view>
</view>
</view>
<!-- fsgx: 佣金周期进度摘要 -->
<view v-if="brokerageProgress" class="fsgx-brokerage-summary" @tap="goToBrokeragePage">
<view class="fsgx-brokerage-summary__title">推荐佣金进度</view>
<view class="fsgx-brokerage-summary__body">
<view class="fsgx-brokerage-summary__progress">
<view
v-for="(rate, idx) in brokerageProgress.cycle_rates"
:key="idx"
class="fsgx-brokerage-summary__dot"
:class="{ 'fsgx-brokerage-summary__dot--done': idx < brokerageProgress.cycle_current }"
>
<text>{{ rate }}%</text>
</view>
</view>
<view class="fsgx-brokerage-summary__count">
{{ brokerageProgress.cycle_current }}/{{ brokerageProgress.cycle_total }} 已推荐
</view>
</view>
<view class="fsgx-brokerage-summary__arrow"></view>
</view>
<home></home>
<BaseCode v-if="isCode" :code="code" :codeImg="codeImg" :isShowCode.sync="isCode"></BaseCode>
<view class="img-modal" v-if="agentTgcg" :class="[agentTgcg ? 'tui-modal-show' : '']">
@@ -86,6 +107,7 @@
getAgentCode,
agentSpread
} from '@/api/user.js';
import { getBrokerageProgress } from '@/api/hjfQueue.js';
import {
openExtrctSubscribe
} from '@/utils/SubscribeMessage.js';
@@ -118,9 +140,10 @@
iconColor: '#000000',
isScrolling: false,
// #endif
imgHost: HTTP_REQUEST_URL,
userInfo: {},
taskShow: false,
imgHost: HTTP_REQUEST_URL,
userInfo: {},
brokerageProgress: null,
taskShow: false,
yesterdayPrice: 0.0,
isAuto: false, //没有授权的不会自动授权
isShowAuth: false, //是否隐藏授权
@@ -320,6 +343,7 @@
this.agentLevelList();
// this.getUserInfo()
this.clickTab(0);
this.loadBrokerageProgress();
// #ifdef MP
const queryData = uni.getEnterOptionsSync(); // uni-app版本 3.5.1+ 支持
if (queryData.query.scene) {
@@ -340,6 +364,16 @@
if (this.$Cache.get('agent_id')) this.bindAgent();
},
methods: {
loadBrokerageProgress() {
getBrokerageProgress()
.then(res => {
if (res && res.data) this.brokerageProgress = res.data;
})
.catch(() => {});
},
goToBrokeragePage() {
uni.navigateTo({ url: '/pages/queue/status' });
},
// 绑定员工关系
bindAgent(agent_id) {
agentSpread({
@@ -534,6 +568,69 @@
</script>
<style scoped lang="scss">
/* fsgx: 佣金周期进度摘要卡片 */
.fsgx-brokerage-summary {
margin: 20rpx 24rpx;
background: #fff;
border-radius: 16rpx;
padding: 24rpx 28rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
&__title {
font-size: 26rpx;
font-weight: 600;
color: #333;
margin-right: 20rpx;
white-space: nowrap;
}
&__body {
flex: 1;
display: flex;
align-items: center;
gap: 12rpx;
}
&__progress {
display: flex;
align-items: center;
gap: 10rpx;
}
&__dot {
width: 64rpx;
height: 40rpx;
border-radius: 8rpx;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20rpx;
color: #999;
&--done {
background: var(--view-theme, #e93323);
color: #fff;
}
}
&__count {
font-size: 22rpx;
color: #999;
margin-left: 12rpx;
white-space: nowrap;
}
&__arrow {
font-size: 36rpx;
color: #ccc;
margin-left: 12rpx;
line-height: 1;
}
}
.headerBg {
position: absolute;
top: 0;