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

569 lines
15 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-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>