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

504 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>