Files
huangjingfen/pro_v3.5.1/view/uniapp/pages/queue/status.vue

481 lines
11 KiB
Vue
Raw Normal View History

<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 {
/**
* 公排状态数据包含 totalOrdersmyOrdersprogress
* @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>