主要改动: - 个人中心: 去除HjfMemberBadge徽章, 会员等级改显示vip_name, "我的资产"/"公排查询"导航项改为与member-points一致风格 - 我的资产页面: 去除HjfMemberBadge, 美化卡片圆角和阴影 - 公排查询页面: 美化顶部渐变和订单卡片样式 - Admin登录页和后台布局: 彻底删除footer copyright信息 - 新增黄精粉业务页面/组件/API/Mock数据(Phase 1) - 新增PHP环境配置文档和启动脚本 Made-with: Cursor
481 lines
11 KiB
Vue
481 lines
11 KiB
Vue
<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>
|