rentease-app/pages/billing/order-list.vue

504 lines
12 KiB
Vue
Raw Normal View History

2026-04-20 06:23:11 +00:00
<template>
<view class="order-list-page">
<!-- 自定义导航栏 -->
<view class="custom-nav safe-area-top">
<view class="nav-content">
<view class="nav-btn" @click="goBack">
<uni-icons type="left" size="22" color="#1E293B"></uni-icons>
</view>
<text class="nav-title">我的订单</text>
<view class="nav-btn" @click="refreshData">
<uni-icons type="refresh" size="20" color="#64748B"></uni-icons>
</view>
</view>
</view>
<!-- 筛选标签 -->
<view class="filter-bar">
<view
v-for="tab in tabs"
:key="tab.value"
class="filter-item"
:class="{ active: activeTab === tab.value }"
@click="switchTab(tab.value)"
>
<text>{{tab.label}}</text>
</view>
</view>
<scroll-view
scroll-y
class="page-content"
@scrolltolower="loadMore"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<!-- 订单列表 -->
<view class="order-list" v-if="filteredOrders.length > 0">
<view
v-for="(order, index) in filteredOrders"
:key="order.id"
class="order-card"
@click="viewOrderDetail(order)"
>
<view class="order-header">
<view class="order-info">
<text class="order-no">订单号{{order.orderNo}}</text>
<text class="order-time">{{formatDateTime(order.createTime)}}</text>
</view>
<view class="order-status" :class="order.status">
{{getStatusText(order.status)}}
</view>
</view>
<view class="order-body">
<view class="plan-info">
<text class="plan-name">{{order.planName || order.subscriptionPlan?.name || '-'}}</text>
<text class="plan-duration">{{order.months}} 个月</text>
</view>
<view class="price-info">
<view v-if="order.amount !== order.actualAmount" class="original-price">
¥{{order.amount}}
</view>
<view class="actual-price">¥{{order.actualAmount}}</view>
</view>
</view>
<view class="order-footer" v-if="order.status === 'pending'">
<button class="action-btn cancel" @click.stop="cancelOrder(order)">取消订单</button>
<button class="action-btn pay" @click.stop="payOrder(order)">去支付</button>
</view>
</view>
<!-- 加载更多 -->
<view class="load-more" v-if="hasMore">
<text v-if="loading">加载中...</text>
<text v-else @click="loadMore">加载更多</text>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else>
<uni-icons type="wallet-filled" size="80" color="#E4E7ED"></uni-icons>
<text class="empty-title">暂无订单</text>
<text class="empty-desc">您还没有创建任何订单</text>
<button class="empty-action" @click="goToPlanSelect">去购买套餐</button>
</view>
<view class="safe-area-bottom" style="height: 40rpx;"></view>
</scroll-view>
</view>
</template>
<script>
import { billingApi } from '@/api/index.js'
export default {
data() {
return {
tabs: [
{ label: '全部', value: 'all' },
{ label: '待支付', value: 'pending' },
{ label: '已支付', value: 'paid' },
{ label: '已取消', value: 'cancelled' }
],
activeTab: 'all',
orders: [],
page: 1,
pageSize: 10,
hasMore: true,
loading: false,
refreshing: false
}
},
computed: {
filteredOrders() {
if (this.activeTab === 'all') {
return this.orders
}
return this.orders.filter(order => order.status === this.activeTab)
}
},
onLoad() {
this.loadOrders()
},
onShow() {
this.refreshData()
},
methods: {
async loadOrders() {
if (this.loading || !this.hasMore) return
this.loading = true
try {
const res = await billingApi.getOrders({
page: this.page,
pageSize: this.pageSize
})
if (res.code === 200) {
const newOrders = res.data.list || []
if (this.page === 1) {
this.orders = newOrders
} else {
this.orders = [...this.orders, ...newOrders]
}
this.hasMore = newOrders.length >= this.pageSize
}
} catch (error) {
console.error('加载订单失败:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
this.refreshing = false
}
},
switchTab(value) {
this.activeTab = value
},
onRefresh() {
this.refreshing = true
this.page = 1
this.hasMore = true
this.loadOrders()
},
refreshData() {
this.page = 1
this.hasMore = true
this.loadOrders()
},
loadMore() {
if (this.hasMore && !this.loading) {
this.page++
this.loadOrders()
}
},
getStatusText(status) {
const map = {
pending: '待支付',
paid: '已支付',
cancelled: '已取消'
}
return map[status] || status
},
formatDateTime(dateTime) {
if (!dateTime) return '-'
const date = new Date(dateTime)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
},
viewOrderDetail(order) {
uni.navigateTo({
url: `/pages/billing/order-detail?id=${order.id}`
})
},
async cancelOrder(order) {
uni.showModal({
title: '提示',
content: '确定要取消该订单吗?',
success: async (res) => {
if (res.confirm) {
try {
const result = await billingApi.cancelOrder(order.id)
if (result.code === 200) {
uni.showToast({ title: '已取消', icon: 'success' })
this.refreshData()
} else {
uni.showToast({ title: result.message || '取消失败', icon: 'none' })
}
} catch (error) {
console.error('取消订单失败:', error)
uni.showToast({ title: '取消失败', icon: 'none' })
}
}
}
})
},
async payOrder(order) {
uni.showModal({
title: '支付确认',
content: `确认支付订单 #${order.orderNo},金额 ¥${order.actualAmount}`,
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '支付中...' })
try {
const result = await billingApi.payOrder(order.id)
uni.hideLoading()
if (result.code === 200) {
uni.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
})
this.refreshData()
} else {
uni.showToast({ title: result.message || '支付失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('支付失败:', error)
uni.showToast({ title: '支付失败', icon: 'none' })
}
}
}
})
},
goBack() {
uni.navigateBack()
},
goToPlanSelect() {
uni.navigateTo({
url: '/pages/billing/plan-select'
})
}
}
}
</script>
<style scoped>
.order-list-page {
min-height: 100vh;
background: #F8FAFC;
display: flex;
flex-direction: column;
}
/* 导航栏 */
.custom-nav {
background: #FFFFFF;
border-bottom: 2rpx solid #F1F5F9;
}
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 32rpx;
}
.nav-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #1E293B;
}
/* 筛选栏 */
.filter-bar {
display: flex;
background: #FFFFFF;
padding: 20rpx 32rpx;
border-bottom: 2rpx solid #F1F5F9;
gap: 16rpx;
}
.filter-item {
padding: 16rpx 32rpx;
background: #F8FAFC;
border-radius: 12rpx;
font-size: 28rpx;
color: #64748B;
}
.filter-item.active {
background: #667eea;
color: #FFFFFF;
font-weight: 500;
}
/* 页面内容 */
.page-content {
flex: 1;
padding: 24rpx 32rpx;
}
/* 订单列表 */
.order-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.order-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 28rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.order-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #F1F5F9;
}
.order-info {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.order-no {
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
}
.order-time {
font-size: 24rpx;
color: #94A3B8;
}
.order-status {
padding: 8rpx 20rpx;
border-radius: 8rpx;
font-size: 24rpx;
font-weight: 500;
}
.order-status.pending {
background: #FEF3C7;
color: #D97706;
}
.order-status.paid {
background: #D1FAE5;
color: #059669;
}
.order-status.cancelled {
background: #F3F4F6;
color: #6B7280;
}
.order-body {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.plan-info {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.plan-name {
font-size: 30rpx;
font-weight: 600;
color: #1E293B;
}
.plan-duration {
font-size: 26rpx;
color: #64748B;
}
.price-info {
text-align: right;
}
.original-price {
font-size: 24rpx;
color: #94A3B8;
text-decoration: line-through;
margin-bottom: 4rpx;
}
.actual-price {
font-size: 36rpx;
font-weight: 700;
color: #F56C6C;
}
.order-footer {
display: flex;
justify-content: flex-end;
gap: 16rpx;
padding-top: 20rpx;
border-top: 2rpx solid #F1F5F9;
}
.action-btn {
padding: 16rpx 32rpx;
border-radius: 12rpx;
font-size: 26rpx;
font-weight: 500;
border: none;
}
.action-btn.cancel {
background: #F3F4F6;
color: #64748B;
}
.action-btn.pay {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #FFFFFF;
}
/* 加载更多 */
.load-more {
text-align: center;
padding: 32rpx;
color: #64748B;
font-size: 26rpx;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 32rpx;
}
.empty-title {
font-size: 32rpx;
font-weight: 600;
color: #1E293B;
margin-top: 32rpx;
margin-bottom: 16rpx;
}
.empty-desc {
font-size: 26rpx;
color: #64748B;
margin-bottom: 48rpx;
}
.empty-action {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #FFFFFF;
font-size: 30rpx;
font-weight: 600;
padding: 28rpx 64rpx;
border-radius: 16rpx;
border: none;
}
</style>