561 lines
13 KiB
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> |