peixue-dev/peidu/uniapp/order-package/pages/order/detail.vue

561 lines
13 KiB
Vue

<template>
<view class="order-detail-page">
<scroll-view scroll-y class="content-scroll">
<!-- 订单状态 -->
<view class="status-section">
<view class="status-icon">
<text v-if="order.status === 0">💰</text>
<text v-else-if="order.status === 1">⏰</text>
<text v-else-if="order.status === 2">📚</text>
<text v-else-if="order.status === 3">✅</text>
<text v-else>❌</text>
</view>
<view class="status-text">{{ getStatusText(order.status) }}</view>
<view class="status-desc" v-if="order.status === 0">
请在{{ order.expireTime }}前完成支付
</view>
</view>
<!-- 服务信息 -->
<view class="service-section">
<view class="section-title">服务信息</view>
<view class="service-card">
<view class="service-icon">
<text>{{ order.serviceIcon }}</text>
</view>
<view class="service-info">
<view class="service-name">{{ order.serviceName }}</view>
<view class="service-time">{{ order.serviceTime }}</view>
<view class="service-duration">时长:{{ order.duration }}分钟</view>
</view>
</view>
</view>
<!-- 陪伴员信息 -->
<view class="teacher-section" v-if="order.teacherName">
<view class="section-title">陪伴员信息</view>
<view class="teacher-card" @click="goTeacherDetail">
<view class="teacher-avatar">
<text>👨‍🏫</text>
</view>
<view class="teacher-info">
<view class="teacher-name">{{ order.teacherName }}</view>
<view class="teacher-phone">{{ order.teacherPhone }}</view>
</view>
<view class="contact-btn" @click.stop="callTeacher">
<text>📞</text>
</view>
</view>
</view>
<!-- 学生信息 -->
<view class="student-section">
<view class="section-title">学生信息</view>
<view class="info-row">
<text class="label">学生姓名</text>
<text class="value">{{ order.studentName }}</text>
</view>
<view class="info-row">
<text class="label">年级</text>
<text class="value">{{ order.grade }}</text>
</view>
<view class="info-row">
<text class="label">联系电话</text>
<text class="value">{{ order.contactPhone }}</text>
</view>
</view>
<!-- 订单信息 -->
<view class="order-section">
<view class="section-title">订单信息</view>
<view class="info-row">
<text class="label">订单编号</text>
<text class="value">{{ order.orderNo }}</text>
</view>
<view class="info-row">
<text class="label">下单时间</text>
<text class="value">{{ order.createTime }}</text>
</view>
<view class="info-row" v-if="order.payTime">
<text class="label">支付时间</text>
<text class="value">{{ order.payTime }}</text>
</view>
<view class="info-row">
<text class="label">服务地址</text>
<text class="value">{{ order.address }}</text>
</view>
</view>
<!-- 费用明细 -->
<view class="price-section">
<view class="section-title">费用明细</view>
<view class="price-row">
<text class="label">服务费用</text>
<text class="value">¥{{ order.servicePrice }}</text>
</view>
<view class="price-row" v-if="order.discount">
<text class="label">优惠金额</text>
<text class="value discount">-¥{{ order.discount }}</text>
</view>
<view class="price-row total">
<text class="label">实付金额</text>
<text class="value">¥{{ order.totalPrice }}</text>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<!-- 待支付 -->
<template v-if="order.status === 0">
<button class="btn-cancel" @click="cancelOrder">取消订单</button>
<button class="btn-pay" @click="payOrder">立即支付</button>
</template>
<!-- 待服务 -->
<template v-else-if="order.status === 1">
<button class="btn-cancel" @click="cancelOrder">取消订单</button>
<button class="btn-contact" @click="contactTeacher">联系陪伴员</button>
</template>
<!-- 服务中 -->
<template v-else-if="order.status === 2">
<button class="btn-contact" @click="contactTeacher">联系陪伴员</button>
</template>
<!-- 已完成 -->
<template v-else-if="order.status === 3">
<button class="btn-review" @click="goReview" v-if="!order.reviewed">评价</button>
<button class="btn-rebuy" @click="rebuy">再次预约</button>
</template>
</view>
</view>
</template>
<script>
import api from '@/api/index.js'
export default {
data() {
return {
orderId: null,
order: {
id: null,
orderNo: '',
status: 0,
serviceIcon: '🎓',
serviceName: '',
serviceTime: '',
duration: 0,
teacherName: '',
teacherPhone: '',
teacherId: null,
studentName: '',
grade: '',
contactPhone: '',
createTime: '',
payTime: '',
address: '',
servicePrice: '0',
discount: '0',
totalPrice: '0',
expireTime: '',
reviewed: false,
serviceId: null
},
loading: false
}
},
onLoad(options) {
this.orderId = options.id
this.loadOrderDetail()
},
methods: {
async loadOrderDetail() {
if (!this.orderId) {
uni.showToast({ title: '订单ID不能为空', icon: 'none' })
return
}
this.loading = true
uni.showLoading({ title: '加载中...' })
try {
const res = await api.orderApi.getOrderDetail(this.orderId)
// 数据映射
this.order = {
id: res.id,
orderNo: res.orderNo || '',
status: res.status !== undefined ? res.status : 0,
serviceIcon: '🎓',
serviceName: res.serviceName || '',
serviceTime: res.serviceTime || res.appointmentTime || '',
duration: res.duration || 0,
teacherName: res.teacherName || '',
teacherPhone: res.teacherPhone || '',
teacherId: res.teacherId,
studentName: res.studentName || '',
grade: res.grade || '',
contactPhone: res.contactPhone || res.phone || '',
createTime: res.createTime || '',
payTime: res.payTime || '',
address: res.address || res.serviceAddress || '',
servicePrice: res.servicePrice || res.originalPrice || '0',
discount: res.discount || res.discountAmount || '0',
totalPrice: res.totalPrice || res.actualPrice || '0',
expireTime: res.expireTime || '',
reviewed: res.reviewed || false,
serviceId: res.serviceId
}
} catch (error) {
console.error('加载订单详情失败:', error)
uni.showToast({
title: error.message || '加载失败',
icon: 'none'
})
// 加载失败,延迟返回
setTimeout(() => {
uni.navigateBack()
}, 1500)
} finally {
this.loading = false
uni.hideLoading()
}
},
getStatusText(status) {
const statusMap = {
0: '待支付',
1: '待服务',
2: '服务中',
3: '已完成',
'-1': '已取消'
}
return statusMap[status] || '未知'
},
goTeacherDetail() {
uni.navigateTo({
url: `/pages/teacher/detail?id=${this.order.teacherId}`
})
},
callTeacher() {
uni.makePhoneCall({
phoneNumber: this.order.teacherPhone.replace(/\*/g, '')
})
},
contactTeacher() {
uni.showToast({
title: '联系陪伴员功能开发中',
icon: 'none'
})
},
async cancelOrder() {
const [err, res] = await uni.showModal({
title: '取消订单',
content: '确定要取消这个订单吗?'
})
if (!res.confirm) return
uni.showLoading({ title: '取消中...' })
try {
await api.orderApi.cancelOrder(this.orderId, {
reason: '用户主动取消'
})
uni.hideLoading()
uni.showToast({
title: '订单已取消',
icon: 'success'
})
// 刷新订单详情
setTimeout(() => {
this.loadOrderDetail()
}, 1500)
} catch (error) {
console.error('取消订单失败:', error)
uni.hideLoading()
uni.showToast({
title: error.message || '取消失败',
icon: 'none'
})
}
},
payOrder() {
uni.navigateTo({
url: `/pages/payment/index?orderId=${this.orderId}`
})
},
goReview() {
uni.navigateTo({
url: `/pages/review/create?orderId=${this.orderId}`
})
},
rebuy() {
// 快速预约是 tabBar 页面,不支持传参
// 先保存参数到本地存储
uni.setStorageSync('bookingParams', { serviceId: this.order.serviceId })
uni.switchTab({
url: `/pages/booking/quick-booking`
})
}
}
}
</script>
<style lang="scss" scoped>
$primary-green: #2d9687;
$white: #fff;
$black: #333;
$gray: #999;
$light-gray: #f5f5f5;
.order-detail-page {
min-height: 100vh;
background: $light-gray;
display: flex;
flex-direction: column;
}
.content-scroll {
flex: 1;
padding-bottom: 120rpx;
}
.status-section {
background: linear-gradient(135deg, $primary-green 0%, #3da896 100%);
padding: 60rpx 30rpx;
display: flex;
flex-direction: column;
align-items: center;
.status-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.status-text {
font-size: 36rpx;
font-weight: bold;
color: $white;
margin-bottom: 12rpx;
}
.status-desc {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.9);
}
}
.service-section,
.teacher-section,
.student-section,
.order-section,
.price-section {
background: $white;
margin: 20rpx 30rpx;
padding: 30rpx;
border-radius: 16rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: $black;
margin-bottom: 24rpx;
}
}
.service-card {
display: flex;
gap: 20rpx;
.service-icon {
width: 80rpx;
height: 80rpx;
background: rgba(45, 150, 135, 0.1);
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
flex-shrink: 0;
}
.service-info {
flex: 1;
.service-name {
font-size: 30rpx;
font-weight: bold;
color: $black;
margin-bottom: 8rpx;
}
.service-time,
.service-duration {
font-size: 26rpx;
color: $gray;
margin-bottom: 4rpx;
}
}
}
.teacher-card {
display: flex;
align-items: center;
gap: 20rpx;
.teacher-avatar {
width: 80rpx;
height: 80rpx;
background: rgba(45, 150, 135, 0.1);
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
flex-shrink: 0;
}
.teacher-info {
flex: 1;
.teacher-name {
font-size: 30rpx;
font-weight: bold;
color: $black;
margin-bottom: 8rpx;
}
.teacher-phone {
font-size: 26rpx;
color: $gray;
}
}
.contact-btn {
width: 60rpx;
height: 60rpx;
background: $primary-green;
border-radius: 30rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
}
.info-row {
display: flex;
justify-content: space-between;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 28rpx;
color: $gray;
}
.value {
font-size: 28rpx;
color: $black;
text-align: right;
flex: 1;
margin-left: 20rpx;
}
}
.price-row {
display: flex;
justify-content: space-between;
padding: 16rpx 0;
.label {
font-size: 28rpx;
color: $gray;
}
.value {
font-size: 28rpx;
color: $black;
&.discount {
color: #ff4d4f;
}
}
&.total {
border-top: 1rpx solid #f0f0f0;
padding-top: 20rpx;
margin-top: 12rpx;
.label {
font-size: 30rpx;
font-weight: bold;
color: $black;
}
.value {
font-size: 36rpx;
font-weight: bold;
color: $primary-green;
}
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: $white;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.08);
display: flex;
gap: 20rpx;
button {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
font-size: 32rpx;
font-weight: bold;
}
.btn-cancel {
background: $white;
color: $gray;
border: 1rpx solid #ddd;
}
.btn-pay,
.btn-contact,
.btn-rebuy {
background: $primary-green;
color: $white;
}
.btn-review {
background: #ffc107;
color: $white;
}
}
</style>