consumer-app/pages/profile/serviceRecords.vue

685 lines
17 KiB
Vue

<template>
<view class="service-records-page">
<!-- 头部区域 -->
<NavHeader title="服务记录" />
<!-- Tab 切换 -->
<view class="tab-bar">
<view
class="tab-item"
:class="{ active: currentTab === 'pending_payment' }"
@click="switchTab('pending_payment')"
>
<text class="tab-text">待支付</text>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 'pending_verification' }"
@click="switchTab('pending_verification')"
>
<text class="tab-text">待核销</text>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 'completed' }"
@click="switchTab('completed')"
>
<text class="tab-text">已完成</text>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 'cancelled' }"
@click="switchTab('cancelled')"
>
<text class="tab-text">已取消</text>
</view>
</view>
<!-- 列表内容 -->
<scroll-view
class="record-list"
scroll-y="true"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="handleRefresh"
@scrolltolower="handleLoadMore"
:lower-threshold="100"
>
<!-- 空数据提示 -->
<view class="empty-state" v-if="!loading && currentList.length === 0">
<image
class="empty-icon"
src="/static/home/entry_icon.png"
mode="aspectFit"
></image>
<text class="empty-text">暂无{{ getTabLabel() }}记录</text>
</view>
<!-- 记录列表项 -->
<view
class="record-item"
v-for="(item, index) in currentList"
:key="index"
@click="handleRecordClick(item)"
>
<view class="record-header">
<view class="record-title-row">
<text class="record-title">{{ item.serviceName }}</text>
<view class="status-badge" :class="getStatusClass(item.status)">
<text class="status-text">{{ getStatusText(item.status) }}</text>
</view>
</view>
<text class="record-time">{{ item.createTime }}</text>
</view>
<view class="record-content">
<view class="record-info-row">
<text class="info-label">服务类型:</text>
<text class="info-value">{{ item.serviceType }}</text>
</view>
<view class="record-info-row">
<text class="info-label">服务门店:</text>
<text class="info-value">{{ item.storeName }}</text>
</view>
<view class="record-info-row">
<text class="info-label">订单号:</text>
<text class="info-value">{{ item.orderNo }}</text>
</view>
<view class="record-info-row">
<text class="info-label">订单金额:</text>
<text class="info-value price">¥{{ item.amount }}</text>
</view>
</view>
<!-- 操作按钮区域 -->
<view class="record-actions" v-if="item.status === 'pending_payment'">
<button
class="action-btn cancel-btn"
@click.stop="handleCancel(item)"
>
取消订单
</button>
<button class="action-btn pay-btn" @click.stop="handlePay(item)">
立即支付
</button>
</view>
<view
class="record-actions"
v-else-if="item.status === 'pending_verification'"
>
<button
class="action-btn detail-btn"
@click.stop="handleViewDetail(item)"
>
查看详情
</button>
</view>
<view class="record-actions" v-else-if="item.status === 'completed'">
<button
class="action-btn detail-btn"
@click.stop="handleViewDetail(item)"
>
查看详情
</button>
<button
class="action-btn review-btn"
@click.stop="handleReview(item)"
>
评价
</button>
</view>
<view class="record-actions" v-else-if="item.status === 'cancelled'">
<button
class="action-btn detail-btn"
@click.stop="handleViewDetail(item)"
>
查看详情
</button>
</view>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="currentList.length > 0">
<text v-if="loadingMore" class="load-more-text">加载中...</text>
<text v-else-if="!hasMore" class="load-more-text">没有更多数据了</text>
<text v-else class="load-more-text"></text>
</view>
</scroll-view>
</view>
</template>
<script>
import NavHeader from "@/components/NavHeader/NavHeader.vue";
export default {
components: {
NavHeader
},
data() {
return {
currentTab: "pending_payment", // 当前选中的 tab
refreshing: false,
loading: false,
loadingMore: false,
hasMore: true,
pageNo: 1,
pageSize: 10,
// 假数据
mockData: {
pending_payment: [
{
id: 1,
orderNo: "ORD20250101001",
serviceName: "汽车维修保养",
serviceType: "维修服务",
storeName: "XX汽车维修中心",
amount: "299.00",
createTime: "2025-01-15 10:30:00",
status: "pending_payment",
},
{
id: 2,
orderNo: "ORD20250101002",
serviceName: "家电清洗服务",
serviceType: "清洗服务",
storeName: "XX家电清洗店",
amount: "158.00",
createTime: "2025-01-14 15:20:00",
status: "pending_payment",
},
{
id: 3,
orderNo: "ORD20250101003",
serviceName: "手机维修",
serviceType: "维修服务",
storeName: "XX手机维修店",
amount: "199.00",
createTime: "2025-01-13 09:15:00",
status: "pending_payment",
},
],
pending_verification: [
{
id: 4,
orderNo: "ORD20250101004",
serviceName: "汽车维修保养",
serviceType: "维修服务",
storeName: "XX汽车维修中心",
amount: "299.00",
createTime: "2025-01-10 14:30:00",
status: "pending_verification",
},
{
id: 5,
orderNo: "ORD20250101005",
serviceName: "家电清洗服务",
serviceType: "清洗服务",
storeName: "XX家电清洗店",
amount: "158.00",
createTime: "2025-01-09 11:20:00",
status: "pending_verification",
},
],
completed: [
{
id: 6,
orderNo: "ORD20250101006",
serviceName: "汽车维修保养",
serviceType: "维修服务",
storeName: "XX汽车维修中心",
amount: "299.00",
createTime: "2025-01-05 16:30:00",
status: "completed",
},
{
id: 7,
orderNo: "ORD20250101007",
serviceName: "家电清洗服务",
serviceType: "清洗服务",
storeName: "XX家电清洗店",
amount: "158.00",
createTime: "2025-01-04 10:20:00",
status: "completed",
},
{
id: 8,
orderNo: "ORD20250101008",
serviceName: "手机维修",
serviceType: "维修服务",
storeName: "XX手机维修店",
amount: "199.00",
createTime: "2025-01-03 08:15:00",
status: "completed",
},
],
cancelled: [
{
id: 9,
orderNo: "ORD20250101009",
serviceName: "汽车维修保养",
serviceType: "维修服务",
storeName: "XX汽车维修中心",
amount: "299.00",
createTime: "2025-01-02 14:30:00",
status: "cancelled",
},
{
id: 10,
orderNo: "ORD20250101010",
serviceName: "家电清洗服务",
serviceType: "清洗服务",
storeName: "XX家电清洗店",
amount: "158.00",
createTime: "2025-01-01 11:20:00",
status: "cancelled",
},
],
},
};
},
computed: {
currentList() {
return this.mockData[this.currentTab] || [];
},
},
onLoad() {
this.loadData();
},
methods: {
// 切换 Tab
switchTab(tab) {
if (this.currentTab === tab) return;
this.currentTab = tab;
this.pageNo = 1;
this.hasMore = true;
this.loadData();
},
// 获取 Tab 标签文本
getTabLabel() {
const labels = {
pending_payment: "待支付",
pending_verification: "待核销",
completed: "已完成",
cancelled: "已取消",
};
return labels[this.currentTab] || "";
},
// 获取状态文本
getStatusText(status) {
const statusMap = {
pending_payment: "待支付",
pending_verification: "待核销",
completed: "已完成",
cancelled: "已取消",
};
return statusMap[status] || "";
},
// 获取状态样式类
getStatusClass(status) {
const classMap = {
pending_payment: "status-pending",
pending_verification: "status-verification",
completed: "status-completed",
cancelled: "status-cancelled",
};
return classMap[status] || "";
},
// 加载数据
loadData() {
this.loading = true;
// 模拟数据加载
setTimeout(() => {
this.loading = false;
this.refreshing = false;
this.loadingMore = false;
// 这里使用假数据,实际应该调用接口
}, 500);
},
// 下拉刷新
handleRefresh() {
this.refreshing = true;
this.pageNo = 1;
this.hasMore = true;
this.loadData();
},
// 上拉加载更多
handleLoadMore() {
if (this.hasMore && !this.loadingMore && !this.loading) {
this.loadingMore = true;
this.pageNo += 1;
// 模拟加载更多
setTimeout(() => {
this.loadingMore = false;
// 假数据没有更多了
this.hasMore = false;
}, 500);
}
},
// 点击记录项
handleRecordClick(item) {
// 可以跳转到详情页
console.log("点击记录:", item);
},
// 取消订单
handleCancel(item) {
uni.showModal({
title: "提示",
content: "确定要取消该订单吗?",
success: (res) => {
if (res.confirm) {
uni.showToast({
title: "订单已取消",
icon: "success",
});
// 这里应该调用接口取消订单,然后刷新列表
// 暂时从待支付列表中移除
const index = this.mockData.pending_payment.findIndex(
(i) => i.id === item.id
);
if (index > -1) {
this.mockData.pending_payment.splice(index, 1);
// 添加到已取消列表
this.mockData.cancelled.unshift({
...item,
status: "cancelled",
});
}
}
},
});
},
// 立即支付
handlePay(item) {
uni.showToast({
title: "跳转支付页面",
icon: "none",
});
// 这里应该跳转到支付页面
// uni.navigateTo({
// url: `/pages/payment/payment?orderNo=${item.orderNo}&amount=${item.amount}`
// });
},
// 查看详情
handleViewDetail(item) {
uni.showToast({
title: "查看详情",
icon: "none",
});
// 这里应该跳转到详情页面
// uni.navigateTo({
// url: `/pages/order/detail?orderNo=${item.orderNo}`
// });
},
// 评价
handleReview(item) {
uni.showToast({
title: "跳转评价页面",
icon: "none",
});
// 这里应该跳转到评价页面
// uni.navigateTo({
// url: `/pages/order/review?orderNo=${item.orderNo}`
// });
},
},
};
</script>
<style lang="scss" scoped>
.service-records-page {
min-height: 100vh;
background: #e2e8f1;
display: flex;
flex-direction: column;
}
/* Tab 切换栏 */
.tab-bar {
display: flex;
padding: 0 20rpx;
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
position: relative;
.tab-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 28rpx;
color: #999999;
}
&.active {
.tab-text {
color: #004294;
font-weight: 600;
}
&::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: #004294;
border-radius: 2rpx;
}
}
}
}
/* 列表区域 */
.record-list {
flex: 1;
padding: 20rpx;
height: 0; // 配合 flex: 1 使用
box-sizing: border-box;
/* 空数据提示 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
min-height: 500rpx;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.5;
}
.empty-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 28rpx;
color: #999999;
}
}
/* 加载更多提示 */
.load-more {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0;
min-height: 80rpx;
.load-more-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 400;
font-size: 24rpx;
color: #999999;
}
}
/* 记录项 */
.record-item {
background-color: #ffffff;
border-radius: 20rpx;
margin-bottom: 20rpx;
padding: 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.record-header {
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.record-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
.record-title {
flex: 1;
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 30rpx;
color: #1a1819;
}
.status-badge {
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
.status-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
}
&.status-pending {
background-color: rgba(255, 107, 0, 0.1);
.status-text {
color: #ff6b00;
}
}
&.status-verification {
background-color: rgba(0, 66, 148, 0.1);
.status-text {
color: #004294;
}
}
&.status-completed {
background-color: rgba(76, 175, 80, 0.1);
.status-text {
color: #4caf50;
}
}
&.status-cancelled {
background-color: rgba(158, 158, 158, 0.1);
.status-text {
color: #9e9e9e;
}
}
}
}
.record-time {
font-family: PingFang-SC, PingFang-SC;
font-weight: 400;
font-size: 22rpx;
color: #999999;
}
}
.record-content {
margin-bottom: 24rpx;
.record-info-row {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
line-height: 1.5;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 24rpx;
color: #888888;
margin-right: 8rpx;
flex-shrink: 0;
}
.info-value {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 24rpx;
color: #333333;
flex: 1;
&.price {
color: #d51c3c;
font-weight: 600;
font-size: 28rpx;
}
}
}
}
.record-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
button{
margin: 0;
padding: 0;
}
.action-btn {
width: 131rpx;
height: 50rpx;
border-radius: 10rpx;
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 26rpx;
display: flex;
align-items: center;
justify-content: center;
&.cancel-btn {
background-color: #f5f5f5;
color: #666666;
}
&.pay-btn {
background-color: #004294;
color: #ffffff;
}
&.detail-btn {
background-color: #f5f5f5;
color: #666666;
}
&.review-btn {
background-color: #004294;
color: #ffffff;
}
}
}
}
}
</style>