peixue-dev/peidu/uniapp/order-package/pages/order/my-orders.vue

664 lines
15 KiB
Vue
Raw Permalink Normal View History

<template>
<view class="order-list-page">
<!-- 顶部标题栏 -->
<view class="header">
<text class="title">我的预约</text>
</view>
<!-- 订单状态筛选 -->
<view class="tabs">
<view
v-for="tab in tabs"
:key="tab.value"
class="tab-item"
:class="{ active: currentTab === tab.value }"
@click="changeTab(tab.value)"
>
<text class="tab-text">{{ tab.label }}</text>
<text v-if="tab.count > 0" class="tab-badge">{{ tab.count }}</text>
</view>
</view>
<!-- 订单列表 -->
<scroll-view class="order-list" scroll-y>
<!-- 订单项 -->
<view
v-for="order in orderList"
:key="order.id"
class="order-item"
@click="goOrderDetail(order.id)"
>
<!-- 订单头部 -->
<view class="order-header">
<text class="order-no">订单号{{ order.orderNo }}</text>
<text class="order-status" :class="'status-' + order.status">
{{ getStatusText(order.status) }}
</text>
</view>
<!-- 服务信息 -->
<view class="order-content">
<view class="service-info">
<text class="service-icon">📚</text>
<view class="service-detail">
<text class="service-name">{{ order.serviceName }}</text>
<text class="service-time">服务时间{{ order.serviceDate }} {{ order.timeSlot }}</text>
<text class="service-duration">服务时长{{ order.duration }}小时</text>
</view>
</view>
<view class="order-price">
<text class="price-label">实付金额</text>
<text class="price-value">¥{{ order.payAmount }}</text>
</view>
</view>
<!-- 订单操作按钮 -->
<view class="order-actions">
<!-- 待支付状态 -->
<template v-if="order.status === 0">
<button class="btn btn-cancel" @click.stop="cancelOrder(order.id)">取消订单</button>
<button class="btn btn-primary" @click.stop="payOrder(order.id)">立即支付</button>
</template>
<!-- 待服务状态 -->
<template v-else-if="order.status === 1">
<button class="btn btn-default" @click.stop="contactTeacher(order)">联系陪伴员</button>
<button class="btn btn-cancel" @click.stop="applyRefund(order.id)">申请退款</button>
</template>
<!-- 服务中状态 -->
<template v-else-if="order.status === 2">
<button class="btn btn-default" @click.stop="viewProgress(order.id)">查看进度</button>
</template>
<!-- 已完成状态 -->
<template v-else-if="order.status === 3">
<button class="btn btn-default" @click.stop="viewFeedback(order.id)">查看反馈</button>
<button class="btn btn-primary" v-if="!order.isEvaluated" @click.stop="evaluate(order.id)">评价</button>
</template>
<!-- 已取消状态 -->
<template v-else-if="order.status === -1">
<button class="btn btn-default" @click.stop="deleteOrder(order.id)">删除订单</button>
<button class="btn btn-primary" @click.stop="rebookService(order)">再次预约</button>
</template>
</view>
</view>
<!-- 空状态 -->
<view v-if="orderList.length === 0" class="empty-state">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无订单</text>
<button class="btn-go-booking" @click="goBooking">立即预约</button>
</view>
<!-- 加载更多 -->
<view v-if="hasMore && orderList.length > 0" class="load-more" @click="loadMore">
<text>{{ loading ? '加载中...' : '加载更多' }}</text>
</view>
<!-- 没有更多 -->
<view v-if="!hasMore && orderList.length > 0" class="no-more">
<text>没有更多了</text>
</view>
</scroll-view>
</view>
</template>
<script>
import api from '@/api/index.js'
export default {
data() {
return {
currentTab: 'all',
tabs: [
{ label: '全部', value: 'all', count: 0 },
{ label: '待支付', value: 0, count: 2 },
{ label: '待服务', value: 1, count: 3 },
{ label: '已完成', value: 3, count: 0 },
{ label: '已取消', value: -1, count: 0 }
],
orderList: [],
page: 1,
pageSize: 10,
hasMore: true,
loading: false
}
},
onLoad(options) {
// 从URL参数获取状态
if (options.status !== undefined) {
this.currentTab = parseInt(options.status)
}
this.loadOrderList()
},
// 注入API
beforeCreate() {
this.$api = api
},
methods: {
// 切换标签
changeTab(value) {
this.currentTab = value
this.page = 1
this.orderList = []
this.hasMore = true
this.loadOrderList()
},
// 加载订单列表
async loadOrderList() {
if (this.loading) return
this.loading = true
uni.showLoading({ title: '加载中...' })
try {
// 调用真实API获取订单列表
const params = {
page: this.page,
pageSize: this.pageSize
}
// 如果不是全部,添加状态筛选
if (this.currentTab !== 'all') {
params.status = this.currentTab
}
const response = await this.$api.orderApi.getOrderList(params)
// 处理返回数据
const orders = response.list || response.records || response || []
this.orderList = this.page === 1 ? orders : [...this.orderList, ...orders]
// 判断是否还有更多数据
if (response.total) {
this.hasMore = this.orderList.length < response.total
} else {
this.hasMore = orders.length >= this.pageSize
}
// 更新标签页的数量统计
if (response.statusCount) {
this.updateTabCounts(response.statusCount)
}
} catch (error) {
console.error('加载订单失败:', error)
uni.showToast({
title: error.message || '加载失败',
icon: 'none'
})
// 如果是第一页加载失败,显示空列表
if (this.page === 1) {
this.orderList = []
}
} finally {
this.loading = false
uni.hideLoading()
}
},
// 更新标签页数量
updateTabCounts(statusCount) {
this.tabs.forEach(tab => {
if (tab.value === 'all') {
tab.count = statusCount.total || 0
} else {
tab.count = statusCount[tab.value] || 0
}
})
},
// 获取模拟数据
getMockOrders() {
return [
{
id: 1,
orderNo: 'PD202512300001',
serviceName: '一对一陪伴服务',
serviceDate: '2025-01-05',
timeSlot: '14:00-16:00',
duration: 2,
payAmount: 200,
status: 0, // 待支付
isEvaluated: false
},
{
id: 2,
orderNo: 'PD202512300002',
serviceName: '数学专项辅导',
serviceDate: '2025-01-06',
timeSlot: '15:00-17:00',
duration: 2,
payAmount: 300,
status: 0, // 待支付
isEvaluated: false
},
{
id: 3,
orderNo: 'PD202512290001',
serviceName: '英语口语陪练',
serviceDate: '2025-01-03',
timeSlot: '10:00-12:00',
duration: 2,
payAmount: 250,
status: 1, // 待服务
isEvaluated: false
},
{
id: 4,
orderNo: 'PD202512290002',
serviceName: '阅读陪伴',
serviceDate: '2025-01-04',
timeSlot: '16:00-18:00',
duration: 2,
payAmount: 180,
status: 1, // 待服务
isEvaluated: false
},
{
id: 5,
orderNo: 'PD202512290003',
serviceName: '作业辅导',
serviceDate: '2025-01-04',
timeSlot: '19:00-21:00',
duration: 2,
payAmount: 200,
status: 1, // 待服务
isEvaluated: false
}
]
},
// 加载更多
loadMore() {
if (!this.hasMore || this.loading) return
this.page++
this.loadOrderList()
},
// 获取状态文本
getStatusText(status) {
const statusMap = {
0: '待支付',
1: '待服务',
2: '服务中',
3: '已完成',
'-1': '已取消'
}
return statusMap[status] || '未知'
},
// 跳转订单详情
goOrderDetail(orderId) {
uni.navigateTo({
url: `/order-package/pages/order/detail?id=${orderId}`
})
},
// 取消订单
async cancelOrder(orderId) {
uni.showModal({
title: '提示',
content: '确定要取消该订单吗?',
success: async (res) => {
if (res.confirm) {
try {
await this.$api.orderApi.cancelOrder(orderId, { reason: '用户主动取消' })
uni.showToast({
title: '订单已取消',
icon: 'success'
})
this.page = 1
this.loadOrderList()
} catch (error) {
uni.showToast({
title: error.message || '取消失败',
icon: 'none'
})
}
}
}
})
},
// 支付订单
payOrder(orderId) {
uni.navigateTo({
url: `/pages/payment/index?orderId=${orderId}`
})
},
// 联系陪伴员
contactTeacher(order) {
uni.showModal({
title: '联系陪伴员',
content: '是否拨打陪伴员电话?',
success: (res) => {
if (res.confirm) {
uni.makePhoneCall({
phoneNumber: '13800138000'
})
}
}
})
},
// 申请退款
applyRefund(orderId) {
uni.navigateTo({
url: `/pages/order/refund?orderId=${orderId}`
})
},
// 查看进度
viewProgress(orderId) {
uni.navigateTo({
url: `/pages/order/progress?orderId=${orderId}`
})
},
// 查看反馈
viewFeedback(orderId) {
uni.navigateTo({
url: `/user-package/pages/review/list`
})
},
// 评价
evaluate(orderId) {
uni.navigateTo({
url: `/user-package/pages/review/submit?orderId=${orderId}`
})
},
// 删除订单
deleteOrder(orderId) {
uni.showModal({
title: '提示',
content: '确定要删除该订单吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '订单已删除',
icon: 'success'
})
// TODO: 调用删除订单API
this.loadOrderList()
}
}
})
},
// 再次预约
rebookService(order) {
uni.navigateTo({
url: `/pages/service/detail?id=${order.serviceId}`
})
},
// 去预约
goBooking() {
uni.switchTab({
url: '/pages/booking/quick-booking'
})
}
}
}
</script>
<style lang="scss" scoped>
@import '@/static/css/common.scss';
.order-list-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.header {
background: $primary-color;
padding: 60rpx 30rpx 30rpx;
.title {
font-size: 36rpx;
font-weight: bold;
color: #fff;
}
}
.tabs {
background: #fff;
display: flex;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
.tab-item {
position: relative;
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
.tab-text {
font-size: 28rpx;
color: #666;
}
.tab-badge {
position: absolute;
top: 16rpx;
right: 20rpx;
min-width: 32rpx;
height: 32rpx;
background: #ff4d4f;
color: #fff;
font-size: 20rpx;
border-radius: 16rpx;
padding: 0 8rpx;
display: flex;
align-items: center;
justify-content: center;
}
&.active {
.tab-text {
color: $primary-color;
font-weight: bold;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: $primary-color;
border-radius: 2rpx;
}
}
}
}
.order-list {
flex: 1;
padding: 20rpx 30rpx;
}
.order-item {
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f5f5f5;
.order-no {
font-size: 24rpx;
color: #999;
}
.order-status {
font-size: 26rpx;
font-weight: bold;
&.status-0 {
color: #ff9800;
}
&.status-1 {
color: $primary-color;
}
&.status-2 {
color: #2196f3;
}
&.status-3 {
color: #999;
}
&.status--1 {
color: #999;
}
}
}
.order-content {
padding: 30rpx;
.service-info {
display: flex;
margin-bottom: 20rpx;
.service-icon {
font-size: 80rpx;
margin-right: 20rpx;
}
.service-detail {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.service-name {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 12rpx;
}
.service-time,
.service-duration {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
}
}
.order-price {
display: flex;
justify-content: flex-end;
align-items: baseline;
.price-label {
font-size: 24rpx;
color: #999;
margin-right: 8rpx;
}
.price-value {
font-size: 32rpx;
font-weight: bold;
color: #ff4d4f;
}
}
}
.order-actions {
display: flex;
justify-content: flex-end;
padding: 20rpx 30rpx;
border-top: 1rpx solid #f5f5f5;
gap: 20rpx;
.btn {
padding: 12rpx 32rpx;
font-size: 26rpx;
border-radius: 40rpx;
border: none;
&.btn-default {
background: #f5f5f5;
color: #666;
}
&.btn-cancel {
background: #fff;
color: #666;
border: 1rpx solid #ddd;
}
&.btn-primary {
background: $primary-color;
color: #fff;
}
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-bottom: 40rpx;
}
.btn-go-booking {
padding: 20rpx 60rpx;
background: $primary-color;
color: #fff;
font-size: 28rpx;
border-radius: 40rpx;
border: none;
}
}
.load-more,
.no-more {
text-align: center;
padding: 30rpx 0;
font-size: 26rpx;
color: #999;
}
</style>