439 行
11 KiB
Vue
439 行
11 KiB
Vue
<template>
|
||
<view class="order-list-container">
|
||
<!-- 顶部状态分类 Tab -->
|
||
<view class="tab-header">
|
||
<view
|
||
v-for="tab in tabs"
|
||
:key="tab.key"
|
||
class="tab-item btn-active"
|
||
:class="{ active: currentTab === tab.key }"
|
||
@click="switchTab(tab.key)"
|
||
>
|
||
<text class="tab-text">{{ tab.name }}</text>
|
||
<view class="tab-indicator" v-if="currentTab === tab.key"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单列表 -->
|
||
<view class="orders-wrap">
|
||
<view
|
||
v-for="order in filteredOrders"
|
||
:key="order.id"
|
||
class="order-card"
|
||
>
|
||
<!-- 订单卡片顶部 -->
|
||
<view class="order-head">
|
||
<text class="order-sn">单号:{{ order.orderSn }}</text>
|
||
<view class="status-pill" :class="order.status">
|
||
{{ getStatusText(order.status) }}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 陪玩与服务信息 -->
|
||
<view class="expert-block">
|
||
<image class="expert-avatar" :src="order.expert.avatar" mode="aspectFill" />
|
||
<view class="expert-meta">
|
||
<view class="name-row">
|
||
<text class="expert-name">{{ order.expert.nickname }}</text>
|
||
<text class="game-tag">{{ order.expert.game }}</text>
|
||
</view>
|
||
<text class="service-name">{{ order.serviceTitle }}</text>
|
||
<text class="order-time">下单时间:{{ order.createdAt }}</text>
|
||
</view>
|
||
<view class="amount-box">
|
||
<text class="amount-val">¥{{ order.totalAmount }}</text>
|
||
<text class="quantity-text">共 {{ order.quantity }} 小时</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部业务操作按钮组 -->
|
||
<view class="order-actions">
|
||
<template v-if="order.status === 'PENDING_PAY'">
|
||
<button class="action-btn cancel-btn btn-active" @click="cancelOrder(order.id)">取消</button>
|
||
<button class="action-btn primary-btn btn-active" @click="payOrder(order.id)">立即付款</button>
|
||
</template>
|
||
|
||
<template v-else-if="order.status === 'IN_SERVICE'">
|
||
<button class="action-btn chat-btn btn-active" @click="contactExpert(order.expert.nickname)">联系大神</button>
|
||
<button class="action-btn refund-btn btn-active" @click="applyRefund(order.id)">申请退款</button>
|
||
<button class="action-btn success-btn btn-active" @click="confirmComplete(order.id)">确认验收</button>
|
||
</template>
|
||
|
||
<template v-else-if="order.status === 'COMPLETED'">
|
||
<button class="action-btn chat-btn btn-active" @click="reBook(order.expert.id)">再次预约</button>
|
||
<button class="action-btn rate-btn btn-active" @click="rateExpert(order.id)">评价大神</button>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="filteredOrders.length === 0" class="empty-state">
|
||
<image class="empty-svg" :src="ICONS.orderList" mode="aspectFit" />
|
||
<text class="empty-text">暂无此类订单</text>
|
||
<button class="empty-btn btn-active" @click="goToExplore">前往发现大厅寻找大神</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue';
|
||
import { ORDERS_LIST } from '@/common/mock.js';
|
||
import { ICONS } from '@/common/icons.js';
|
||
|
||
const tabs = [
|
||
{ key: 'ALL', name: '全部' },
|
||
{ key: 'IN_SERVICE', name: '进行中' },
|
||
{ key: 'PENDING_PAY', name: '待付款' },
|
||
{ key: 'COMPLETED', name: '已完成' },
|
||
{ key: 'REFUNDING', name: '退款/售后' },
|
||
];
|
||
|
||
const currentTab = ref('ALL');
|
||
const orders = ref(ORDERS_LIST);
|
||
|
||
const filteredOrders = computed(() => {
|
||
if (currentTab.value === 'ALL') return orders.value;
|
||
return orders.value.filter(o => o.status === currentTab.value);
|
||
});
|
||
|
||
const switchTab = (tabKey) => {
|
||
currentTab.value = tabKey;
|
||
};
|
||
|
||
const goToExplore = () => {
|
||
uni.switchTab({
|
||
url: '/pages/index/index'
|
||
});
|
||
};
|
||
|
||
const getStatusText = (status) => {
|
||
const map = {
|
||
PENDING_PAY: '待付款',
|
||
IN_SERVICE: '服务中',
|
||
COMPLETED: '已完成',
|
||
REFUNDING: '退款中',
|
||
};
|
||
return map[status] || status;
|
||
};
|
||
|
||
const cancelOrder = (id) => {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要取消此订单吗?',
|
||
confirmColor: '#EF4444',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
orders.value = orders.value.filter(o => o.id !== id);
|
||
uni.showToast({ title: '订单已取消', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const payOrder = (id) => {
|
||
uni.showToast({ title: '拉起微信支付成功', icon: 'success' });
|
||
const o = orders.value.find(item => item.id === id);
|
||
if (o) o.status = 'IN_SERVICE';
|
||
};
|
||
|
||
const contactExpert = (name) => {
|
||
uni.showToast({ title: `已呼叫大神【${name}】进入开黑语音`, icon: 'none' });
|
||
};
|
||
|
||
const applyRefund = (id) => {
|
||
uni.showModal({
|
||
title: '申请退款/售后仲裁',
|
||
content: '申请后客服及平台专家将审核对局录像与连麦记录,款项将原路退回。',
|
||
confirmText: '确认申请',
|
||
confirmColor: '#EF4444',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
const o = orders.value.find(item => item.id === id);
|
||
if (o) o.status = 'REFUNDING';
|
||
uni.showToast({ title: '退款申请已提交,等待审核', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const confirmComplete = (id) => {
|
||
uni.showModal({
|
||
title: '确认验收',
|
||
content: '确认大神已圆满完成陪玩时长?确认后款项将结算入大神钱包。',
|
||
confirmText: '确认验收',
|
||
confirmColor: '#10B981',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
const o = orders.value.find(item => item.id === id);
|
||
if (o) o.status = 'COMPLETED';
|
||
uni.showToast({ title: '验收完成,去给大神好评吧!', icon: 'success' });
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const reBook = (expertId) => {
|
||
uni.navigateTo({
|
||
url: `/pages/expert/detail?id=${expertId}`
|
||
});
|
||
};
|
||
|
||
const rateExpert = (id) => {
|
||
uni.showToast({ title: '评价功能已就绪,感谢支持!', icon: 'success' });
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.order-list-container {
|
||
min-height: 100vh;
|
||
background-color: #0A0F1D;
|
||
padding: 20rpx 32rpx calc(160rpx + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.tab-header {
|
||
display: flex;
|
||
background-color: #151D30;
|
||
border-radius: 20rpx;
|
||
padding: 8rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 18rpx 0;
|
||
position: relative;
|
||
border-radius: 14rpx;
|
||
|
||
.tab-text {
|
||
font-size: 26rpx;
|
||
color: #94A3B8;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&.active {
|
||
background: rgba(99, 102, 241, 0.18);
|
||
|
||
.tab-text {
|
||
color: #FFFFFF;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.tab-indicator {
|
||
position: absolute;
|
||
bottom: 4rpx;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 32rpx;
|
||
height: 6rpx;
|
||
background: #6366F1;
|
||
border-radius: 3rpx;
|
||
box-shadow: 0 0 10rpx #6366F1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.orders-wrap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
|
||
.order-card {
|
||
background-color: #151D30;
|
||
border-radius: 24rpx;
|
||
padding: 28rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.3);
|
||
|
||
.order-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
padding-bottom: 16rpx;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||
|
||
.order-sn {
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.status-pill {
|
||
font-size: 20rpx;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 12rpx;
|
||
font-weight: 600;
|
||
|
||
&.PENDING_PAY {
|
||
background: rgba(245, 158, 11, 0.15);
|
||
color: #F59E0B;
|
||
}
|
||
&.IN_SERVICE {
|
||
background: rgba(16, 185, 129, 0.15);
|
||
color: #10B981;
|
||
}
|
||
&.COMPLETED {
|
||
background: rgba(148, 163, 184, 0.15);
|
||
color: #94A3B8;
|
||
}
|
||
&.REFUNDING {
|
||
background: rgba(239, 68, 68, 0.15);
|
||
color: #EF4444;
|
||
}
|
||
}
|
||
}
|
||
|
||
.expert-block {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
|
||
.expert-avatar {
|
||
width: 88rpx;
|
||
height: 88rpx;
|
||
border-radius: 44rpx;
|
||
margin-right: 18rpx;
|
||
}
|
||
|
||
.expert-meta {
|
||
flex: 1;
|
||
|
||
.name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
margin-bottom: 6rpx;
|
||
|
||
.expert-name {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
.game-tag {
|
||
font-size: 20rpx;
|
||
color: #818CF8;
|
||
background: rgba(99, 102, 241, 0.15);
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 6rpx;
|
||
}
|
||
}
|
||
|
||
.service-name {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
display: block;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.order-time {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
|
||
.amount-box {
|
||
text-align: right;
|
||
|
||
.amount-val {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
display: block;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.quantity-text {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
}
|
||
|
||
.order-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 16rpx;
|
||
padding-top: 16rpx;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||
|
||
.action-btn {
|
||
height: 60rpx;
|
||
line-height: 58rpx;
|
||
padding: 0 26rpx;
|
||
font-size: 22rpx;
|
||
border-radius: 30rpx;
|
||
margin: 0;
|
||
|
||
&.cancel-btn {
|
||
background: transparent;
|
||
color: #94A3B8;
|
||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||
}
|
||
|
||
&.primary-btn {
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
color: #FFF;
|
||
border: none;
|
||
}
|
||
|
||
&.chat-btn {
|
||
background: rgba(99, 102, 241, 0.12);
|
||
color: #818CF8;
|
||
border: 1px solid rgba(99, 102, 241, 0.25);
|
||
}
|
||
|
||
&.refund-btn {
|
||
background: rgba(239, 68, 68, 0.1);
|
||
color: #EF4444;
|
||
border: 1px solid rgba(239, 68, 68, 0.25);
|
||
}
|
||
|
||
&.success-btn {
|
||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||
color: #FFF;
|
||
border: none;
|
||
}
|
||
|
||
&.rate-btn {
|
||
background: rgba(245, 158, 11, 0.15);
|
||
color: #F59E0B;
|
||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 100rpx 0;
|
||
|
||
.empty-svg {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
opacity: 0.5;
|
||
margin: 0 auto 16rpx;
|
||
display: block;
|
||
}
|
||
.empty-text {
|
||
color: #64748B;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.empty-btn {
|
||
margin: 32rpx auto 0;
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
color: #FFF;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
padding: 0 40rpx;
|
||
border-radius: 36rpx;
|
||
width: fit-content;
|
||
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.35);
|
||
}
|
||
}
|
||
}
|
||
</style>
|