文件
shanghaipeiwan/pwuniapp/pages/order/list.vue

439 行
11 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<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>