Files
huangjingfen/pro_v3.5.1/view/uniapp/pages/queue/status.vue
apple f6227c0253 feat: 黄精粉前端功能集成 + 个人中心/资产/公排页面优化 + 去除admin copyright
主要改动:
- 个人中心: 去除HjfMemberBadge徽章, 会员等级改显示vip_name,
  "我的资产"/"公排查询"导航项改为与member-points一致风格
- 我的资产页面: 去除HjfMemberBadge, 美化卡片圆角和阴影
- 公排查询页面: 美化顶部渐变和订单卡片样式
- Admin登录页和后台布局: 彻底删除footer copyright信息
- 新增黄精粉业务页面/组件/API/Mock数据(Phase 1)
- 新增PHP环境配置文档和启动脚本

Made-with: Cursor
2026-03-13 00:49:22 +08:00

481 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="queue-status-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-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>
</view>
</view>
<view class="order-list-section">
<view class="section-header">
<view class="section-header__dot"></view>
<view class="section-header__title">我的排队订单</view>
</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>
<view v-else-if="!queueStatus.myOrders || queueStatus.myOrders.length === 0" class="empty-wrap">
<emptyPage title="暂无排队记录~" src="/statics/images/noOrder.gif" />
</view>
<view v-else class="order-list">
<view
v-for="(order, index) in queueStatus.myOrders"
:key="order.id || index"
class="order-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>
<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>
</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>
</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>
<HjfRefundNotice
:visible="showRefund"
:amount="refundData.amount"
:order-id="refundData.orderId"
@close="handleRefundClose"
/>
</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 HjfQueueProgress from '@/components/HjfQueueProgress.vue';
import HjfRefundNotice from '@/components/HjfRefundNotice.vue';
import emptyPage from '@/components/emptyPage.vue';
import colors from '@/mixins/color.js';
export default {
name: 'QueueStatus',
mixins: [colors],
components: {
HjfQueueProgress,
HjfRefundNotice,
emptyPage
},
data() {
return {
/**
* 公排状态数据,包含 totalOrders、myOrders、progress
* @type {{ totalOrders: number, myOrders: Array, progress: Object }}
*/
queueStatus: {},
/**
* 是否正在加载数据
* @type {boolean}
*/
loading: false,
/**
* 是否显示退款通知弹窗
* @type {boolean}
*/
showRefund: false,
/**
* 退款弹窗所需数据
* @type {{ amount: number, orderId: string }}
*/
refundData: {
amount: 0,
orderId: ''
},
/** @type {boolean} 是否正在上拉加载更多 */
loadingMore: false,
/** @type {boolean} 是否已加载完全部数据 */
finished: false
};
},
onLoad() {
this.loadQueueStatus();
},
onShow() {
this.checkPendingRefundNotice();
},
onReachBottom() {
this.loadMoreOrders();
},
methods: {
/**
* 加载公排状态数据
* 调用 getQueueStatus(),将返回值赋给 queueStatus
* 并在检测到已退款订单时触发退款弹窗。
* @returns {Promise<void>}
*/
loadQueueStatus() {
this.loading = true;
getQueueStatus()
.then(res => {
if (res && res.data) {
this.$set(this, 'queueStatus', res.data);
this.detectNewRefund(res.data.myOrders || []);
}
})
.catch(err => {
console.error('[QueueStatus] loadQueueStatus error:', err);
uni.showToast({ title: '加载失败,请稍后重试', icon: 'none' });
})
.finally(() => {
this.loading = 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;
}
}
};
</script>
<style scoped lang="scss">
.queue-status-page {
min-height: 100vh;
background: #f4f5f7;
padding-bottom: 40rpx;
}
.header-gradient {
background: linear-gradient(135deg, var(--view-theme, #e93323) 0%, var(--view-gradient, #f76b1c) 100%);
padding: 36rpx 30rpx 48rpx;
position: relative;
overflow: hidden;
}
.header-gradient__bg-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;
}
.header-card {
position: relative;
z-index: 1;
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: 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 {
margin: -16rpx 20rpx 0;
position: relative;
z-index: 2;
}
.section-header {
display: flex;
align-items: center;
padding: 24rpx 4rpx 20rpx;
}
.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;
}
.loading-wrap {
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%;
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;
text-align: center;
}
.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;
}
</style>