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

569 lines
15 KiB
Vue
Raw Permalink Normal View History

2026-04-20 06:23:11 +00:00
<template>
<view class="order-detail-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"></view>
</view>
</view>
<scroll-view scroll-y class="page-content" v-if="order">
<!-- 订单状态 -->
<view class="status-section" :class="order.status">
<view class="status-icon">
<uni-icons :type="statusIcon" size="48" color="#FFFFFF"></uni-icons>
</view>
<text class="status-text">{{statusText}}</text>
<text class="status-desc">{{statusDesc}}</text>
</view>
<!-- 订单信息 -->
<view class="section-card">
<view class="section-title">订单信息</view>
<view class="info-list">
<view class="info-item">
<text class="info-label">订单编号</text>
<text class="info-value">{{order.orderNo}}</text>
</view>
<view class="info-item">
<text class="info-label">创建时间</text>
<text class="info-value">{{formatDateTime(order.createTime)}}</text>
</view>
<view class="info-item" v-if="order.payTime">
<text class="info-label">支付时间</text>
<text class="info-value">{{formatDateTime(order.payTime)}}</text>
</view>
<view class="info-item" v-if="order.cancelTime">
<text class="info-label">取消时间</text>
<text class="info-value">{{formatDateTime(order.cancelTime)}}</text>
</view>
</view>
</view>
<!-- 套餐信息 -->
<view class="section-card" v-if="order.subscriptionPlan">
<view class="section-title">套餐信息</view>
<view class="plan-card">
<text class="plan-name">{{order.subscriptionPlan.name}}</text>
<text class="plan-desc">{{order.subscriptionPlan.description}}</text>
<view class="plan-resources">
<view class="resource-item">
<uni-icons type="home-filled" size="16" color="#667eea"></uni-icons>
<text>{{order.subscriptionPlan.maxApartments}} 栋公寓</text>
</view>
<view class="resource-item">
<uni-icons type="shop-filled" size="16" color="#667eea"></uni-icons>
<text>{{order.subscriptionPlan.maxRooms}} 个房间</text>
</view>
<view class="resource-item">
<uni-icons type="person-filled" size="16" color="#667eea"></uni-icons>
<text>{{order.subscriptionPlan.maxUsers}} 个用户</text>
</view>
</view>
</view>
</view>
<!-- 费用明细 -->
<view class="section-card">
<view class="section-title">费用明细</view>
<view class="price-list">
<view class="price-row">
<text class="row-label">套餐单价</text>
<text class="row-value">¥{{order.unitPrice || order.subscriptionPlan?.monthlyPrice || 0}}/</text>
</view>
<view class="price-row">
<text class="row-label">购买时长</text>
<text class="row-value">{{order.months}} 个月</text>
</view>
<view class="price-row">
<text class="row-label">基础费用</text>
<text class="row-value">¥{{order.amount}}</text>
</view>
<view class="price-row" v-if="order.discountAmount > 0">
<text class="row-label">优惠金额</text>
<text class="row-value discount">-¥{{order.discountAmount}}</text>
</view>
<view class="price-row total">
<text class="row-label">实付金额</text>
<text class="row-value total-price">¥{{order.actualAmount}}</text>
</view>
</view>
</view>
<!-- 支付信息 -->
<view class="section-card" v-if="order.status === 'paid'">
<view class="section-title">支付信息</view>
<view class="info-list">
<view class="info-item">
<text class="info-label">支付方式</text>
<text class="info-value">{{getPaymentMethodText(order.paymentMethod)}}</text>
</view>
<view class="info-item" v-if="order.transactionId">
<text class="info-label">交易号</text>
<text class="info-value">{{order.transactionId}}</text>
</view>
</view>
</view>
<!-- 有效期 -->
<view class="section-card" v-if="order.status === 'paid' && order.startDate">
<view class="section-title">有效期</view>
<view class="validity-info">
<view class="validity-item">
<text class="validity-label">开始时间</text>
<text class="validity-value">{{formatDate(order.startDate)}}</text>
</view>
<view class="validity-arrow">
<uni-icons type="right" size="20" color="#94A3B8"></uni-icons>
</view>
<view class="validity-item">
<text class="validity-label">结束时间</text>
<text class="validity-value">{{formatDate(order.endDate)}}</text>
</view>
</view>
</view>
<view class="safe-area-bottom" style="height: 140rpx;"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar safe-area-bottom" v-if="order && order.status === 'pending'">
<button class="action-btn cancel" @click="cancelOrder">取消订单</button>
<button class="action-btn pay" @click="payOrder">立即支付</button>
</view>
</view>
</template>
<script>
import { billingApi } from '@/api/index.js'
export default {
data() {
return {
orderId: null,
order: null
}
},
computed: {
statusIcon() {
const map = {
pending: 'info-filled',
paid: 'checkmarkempty',
cancelled: 'closeempty'
}
return map[this.order?.status] || 'info-filled'
},
statusText() {
const map = {
pending: '待支付',
paid: '已支付',
cancelled: '已取消'
}
return map[this.order?.status] || '未知状态'
},
statusDesc() {
const map = {
pending: '请在24小时内完成支付否则订单将自动取消',
paid: '订单已支付成功,套餐已生效',
cancelled: '订单已取消,如有疑问请联系客服'
}
return map[this.order?.status] || ''
}
},
onLoad(options) {
this.orderId = options.id
if (this.orderId) {
this.loadOrderDetail()
}
},
methods: {
async loadOrderDetail() {
try {
uni.showLoading({ title: '加载中...' })
const res = await billingApi.getOrderDetail(this.orderId)
uni.hideLoading()
if (res.code === 200) {
this.order = res.data
} else {
uni.showToast({ title: res.message || '加载失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('加载订单详情失败:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
}
},
getPaymentMethodText(method) {
const map = {
alipay: '支付宝',
wechat: '微信支付',
bank: '银行转账'
}
return map[method] || method || '-'
},
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')}`
},
formatDate(date) {
if (!date) return '-'
return new Date(date).toLocaleDateString('zh-CN')
},
cancelOrder() {
uni.showModal({
title: '提示',
content: '确定要取消该订单吗?',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({ title: '取消中...' })
const result = await billingApi.cancelOrder(this.orderId)
uni.hideLoading()
if (result.code === 200) {
uni.showToast({ title: '已取消', icon: 'success' })
this.loadOrderDetail()
} else {
uni.showToast({ title: result.message || '取消失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('取消订单失败:', error)
uni.showToast({ title: '取消失败', icon: 'none' })
}
}
}
})
},
payOrder() {
uni.showModal({
title: '支付确认',
content: `确认支付订单 #${this.order.orderNo},金额 ¥${this.order.actualAmount}`,
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({ title: '支付中...' })
const result = await billingApi.payOrder(this.orderId)
uni.hideLoading()
if (result.code === 200) {
uni.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
})
this.loadOrderDetail()
} else {
uni.showToast({ title: result.message || '支付失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('支付失败:', error)
uni.showToast({ title: '支付失败', icon: 'none' })
}
}
}
})
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style scoped>
.order-detail-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;
}
/* 页面内容 */
.page-content {
flex: 1;
padding: 24rpx 32rpx;
}
/* 状态区域 */
.status-section {
background: #FFFFFF;
border-radius: 24rpx;
padding: 48rpx;
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.status-section.pending {
background: linear-gradient(135deg, #FEF3C7 0%, #FDE68A 100%);
}
.status-section.paid {
background: linear-gradient(135deg, #D1FAE5 0%, #A7F3D0 100%);
}
.status-section.cancelled {
background: linear-gradient(135deg, #F3F4F6 0%, #E5E7EB 100%);
}
.status-icon {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24rpx;
}
.status-section.pending .status-icon {
background: #F59E0B;
}
.status-section.paid .status-icon {
background: #10B981;
}
.status-section.cancelled .status-icon {
background: #9CA3AF;
}
.status-text {
font-size: 36rpx;
font-weight: 700;
color: #1E293B;
margin-bottom: 12rpx;
}
.status-desc {
font-size: 26rpx;
color: #64748B;
}
/* 区块卡片 */
.section-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 28rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #1E293B;
margin-bottom: 24rpx;
}
/* 信息列表 */
.info-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.info-label {
font-size: 28rpx;
color: #64748B;
}
.info-value {
font-size: 28rpx;
color: #1E293B;
font-weight: 500;
}
/* 套餐卡片 */
.plan-card {
background: #F8FAFC;
border-radius: 16rpx;
padding: 28rpx;
}
.plan-name {
display: block;
font-size: 32rpx;
font-weight: 700;
color: #1E293B;
margin-bottom: 12rpx;
}
.plan-desc {
display: block;
font-size: 26rpx;
color: #64748B;
margin-bottom: 20rpx;
}
.plan-resources {
display: flex;
gap: 24rpx;
flex-wrap: wrap;
}
.resource-item {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 26rpx;
color: #64748B;
}
/* 费用明细 */
.price-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.price-row.total {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 2rpx solid #F1F5F9;
}
.row-label {
font-size: 28rpx;
color: #64748B;
}
.row-value {
font-size: 28rpx;
color: #1E293B;
font-weight: 500;
}
.row-value.discount {
color: #67C23A;
}
.row-value.total-price {
font-size: 40rpx;
color: #F56C6C;
font-weight: 700;
}
/* 有效期 */
.validity-info {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.validity-item {
flex: 1;
background: #F8FAFC;
border-radius: 16rpx;
padding: 24rpx;
text-align: center;
}
.validity-label {
display: block;
font-size: 24rpx;
color: #64748B;
margin-bottom: 8rpx;
}
.validity-value {
display: block;
font-size: 28rpx;
color: #1E293B;
font-weight: 600;
}
.validity-arrow {
flex-shrink: 0;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #FFFFFF;
border-top: 2rpx solid #F1F5F9;
padding: 24rpx 32rpx;
display: flex;
gap: 24rpx;
}
.action-btn {
flex: 1;
padding: 28rpx;
border-radius: 16rpx;
font-size: 30rpx;
font-weight: 600;
border: none;
}
.action-btn.cancel {
background: #F3F4F6;
color: #64748B;
}
.action-btn.pay {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #FFFFFF;
}
</style>