Files
pingxiangcard/src/pages/order/order-detail.vue
T
jacobxu666andClaude 4b7c540343 fix(order-detail): add top padding to bottom bar buttons
Buttons now have 24rpx gap from the top edge of the bottom bar.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-26 11:52:22 +08:00

592 lines
16 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="page">
<!-- 模块1:订单状态卡片 -->
<view class="status-card" :class="`status-card--${order.status}`">
<view class="status-icon-wrap">
<view class="status-icon" :class="`status-icon--${order.status}`" />
</view>
<text class="status-title">{{ statusLabel }}</text>
<text class="status-desc">{{ statusDesc }}</text>
</view>
<!-- 模块2:商品信息卡片 -->
<view class="info-card">
<text class="product-name">超级玩家卡</text>
<view class="price-row">
<view class="price-current">
<text class="price-symbol">¥</text>
<text class="price-value">198</text>
</view>
</view>
<text class="product-desc">一卡全年畅玩赣湘合作景区商户,实名绑定使用</text>
</view>
<!-- 模块3:订单信息明细 -->
<view class="info-card">
<view class="detail-row">
<text class="detail-label">订单编号</text>
<text class="detail-value">{{ order.id }}</text>
</view>
<view v-if="order.status === 'pending' || order.status === 'activated'" class="detail-row">
<text class="detail-label">卡号</text>
<view class="detail-value-row">
<text class="detail-value detail-value--code">{{ order.redeemCode }}</text>
<view v-if="order.status === 'pending'" class="copy-btn" @tap="copyCode">
<text class="copy-btn__text">复制</text>
</view>
</view>
</view>
<view class="detail-row">
<text class="detail-label">下单时间</text>
<text class="detail-value">{{ order.createTime }}</text>
</view>
<view class="detail-row">
<text class="detail-label">支付时间</text>
<text class="detail-value">{{ order.payTime }}</text>
</view>
<view class="detail-row">
<text class="detail-label">支付渠道</text>
<text class="detail-value">{{ order.payChannel }}</text>
</view>
<view class="detail-row detail-row--last">
<text class="detail-label">支付金额</text>
<text class="detail-value detail-value--price">¥{{ order.payAmount }}.00</text>
</view>
<view v-if="order.status === 'activated' && order.activatedTime" class="detail-row detail-row--last">
<text class="detail-label">绑定时间</text>
<text class="detail-value">{{ order.activatedTime }}</text>
</view>
<view v-if="order.status === 'refunded' && order.refundedTime" class="detail-row detail-row--last">
<text class="detail-label">退款时间</text>
<text class="detail-value">{{ order.refundedTime }}</text>
</view>
</view>
<!-- 模块4:使用提示卡片 -->
<view class="tips-card">
<text class="tips-title">使用须知</text>
<view class="tips-list">
<view class="tips-item">
<view class="tips-dot" />
<text class="tips-text">卡一经绑定或售出满7天不支持退款</text>
</view>
<view class="tips-item">
<view class="tips-dot" />
<text class="tips-text">实名绑定大陆身份证,港澳台/外籍无法绑定</text>
</view>
<view class="tips-item">
<view class="tips-dot" />
<text class="tips-text">预约需提前1天,未提前取消将扣除使用次数</text>
</view>
<view class="tips-item">
<view class="tips-dot" />
<text class="tips-text">一卡一人不可转借</text>
</view>
<view class="tips-item">
<view class="tips-dot" />
<text class="tips-text">第三方平台下单退款需联系对应平台客服</text>
</view>
</view>
<view class="tips-link" @tap="goFaq">
<text class="tips-link__text">查看完整FAQ</text>
</view>
</view>
<!-- 底部安全区占位 -->
<view class="safe-bottom" />
<!-- 模块5:底部固定操作栏 -->
<view class="bottom-bar">
<view v-if="order.status === 'pending'" class="bottom-bar__right">
<view class="btn-action btn-action--outline" @tap="handleRefund">
<text class="btn-action__text--outline">申请退款</text>
</view>
<view class="btn-action btn-action--primary" @tap="goActivate">
<text class="btn-action__text">立即绑定</text>
</view>
</view>
<view v-else class="bottom-bar__right">
<view class="btn-action btn-action--outline" @tap="goHome">
<text class="btn-action__text--outline">返回首页</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
type OrderStatus = 'pending' | 'activated' | 'refunded' | 'expired'
interface OrderDetail {
id: string
status: OrderStatus
createTime: string
payTime: string
payAmount: number
redeemCode: string
payChannel: string
activatedTime?: string
refundedTime?: string
}
const order = ref<OrderDetail>({
id: 'ORD20260625001',
status: 'pending',
createTime: '2026-06-25 10:32',
payTime: '2026-06-25 10:33',
payAmount: 198,
redeemCode: 'CJ0000012398',
payChannel: '微信支付',
activatedTime: undefined,
refundedTime: undefined
})
// Mock 订单数据集
const mockOrders: OrderDetail[] = [
{ id: 'ORD20260625001', status: 'pending', createTime: '2026-06-25 10:32', payTime: '2026-06-25 10:33', payAmount: 198, redeemCode: 'CJ0000012398', payChannel: '微信支付' },
{ id: 'ORD20260624002', status: 'activated', createTime: '2026-06-24 14:18', payTime: '2026-06-24 14:19', payAmount: 198, redeemCode: 'CJ0000012399', payChannel: '微信支付', activatedTime: '2026-06-24 15:00' },
{ id: 'ORD20260623003', status: 'pending', createTime: '2026-06-23 09:45', payTime: '2026-06-23 09:46', payAmount: 198, redeemCode: 'CJ0000012400', payChannel: '微信支付' },
{ id: 'ORD20260620004', status: 'refunded', createTime: '2026-06-20 16:22', payTime: '2026-06-20 16:23', payAmount: 198, redeemCode: 'CJ0000012401', payChannel: '微信支付', refundedTime: '2026-06-21 10:15' },
{ id: 'ORD20260618005', status: 'activated', createTime: '2026-06-18 11:08', payTime: '2026-06-18 11:09', payAmount: 198, redeemCode: 'CJ0000012402', payChannel: '微信支付', activatedTime: '2026-06-18 12:30' },
{ id: 'ORD20260510007', status: 'activated', createTime: '2026-05-10 13:40', payTime: '2026-05-10 13:41', payAmount: 198, redeemCode: 'CJ0000012403', payChannel: '微信支付', activatedTime: '2026-05-10 14:20' },
]
onLoad((query: any) => {
if (query?.orderId) {
const found = mockOrders.find(o => o.id === query.orderId)
if (found) {
order.value = { ...found }
} else {
order.value.id = query.orderId
}
}
})
const statusLabel = computed(() => {
const map: Record<OrderStatus, string> = {
pending: '待绑定',
activated: '已绑定',
refunded: '已退款',
expired: '已过期'
}
return map[order.value.status]
})
const statusDesc = computed(() => {
const map: Record<OrderStatus, string> = {
pending: '已微信支付,未实名绑定,可绑定或申请退款',
activated: '卡已实名绑定,不可退款,仅限本人使用',
refunded: '退款审核通过,金额原路返还微信',
expired: '下单超过绑定有效期,卡号失效无法绑卡'
}
return map[order.value.status]
})
function copyCode() {
uni.setClipboardData({
data: order.value.redeemCode,
success: () => uni.showToast({ title: '已复制', icon: 'success' })
})
}
function goActivate() {
uni.navigateTo({
url: `/pages/card-bind/card-bind?redeemCode=${order.value.redeemCode}`
})
}
function handleRefund() {
uni.showModal({
title: '申请退款',
content: '赣湘全域旅游年卡/超级玩家卡绑定或销售七日以上不可退卡。确认申请退款?',
success: (res) => {
if (res.confirm) {
order.value.status = 'refunded'
order.value.refundedTime = new Date().toISOString().slice(0, 16).replace('T', ' ')
uni.showToast({ title: '退款申请已提交', icon: 'success' })
}
}
})
}
function goFaq() {
uni.navigateTo({ url: '/pages/faq/faq' })
}
function goHome() {
uni.switchTab({ url: '/pages/index/index' })
}
</script>
<style lang="scss" scoped>
$bar-height: 120rpx;
.page {
min-height: 100vh;
background: $color-background;
padding: $spacing-3;
padding-bottom: calc(#{$bar-height} + env(safe-area-inset-bottom) + #{$spacing-3});
font-family: $font-family-body;
}
/* ===== 模块1:状态卡片 ===== */
.status-card {
background: $color-card;
border-radius: $radius-lg;
padding: $spacing-6 $spacing-4;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
&--pending { border-top: 6rpx solid $color-status-pending; }
&--activated { border-top: 6rpx solid $color-status-success; }
&--refunded { border-top: 6rpx solid $color-status-cancel; }
&--expired { border-top: 6rpx solid $uv-error; }
}
.status-icon-wrap {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: $spacing-3;
}
.status-card--pending .status-icon-wrap { background: rgba(255, 149, 0, 0.1); }
.status-card--activated .status-icon-wrap { background: rgba(0, 180, 42, 0.1); }
.status-card--refunded .status-icon-wrap { background: rgba(134, 144, 156, 0.1); }
.status-card--expired .status-icon-wrap { background: rgba(245, 63, 63, 0.1); }
.status-icon {
width: 36rpx;
height: 36rpx;
&--pending {
border: 4rpx solid $color-status-pending;
border-radius: 50%;
position: relative;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 4rpx;
height: 16rpx;
background: $color-status-pending;
transform: translate(-50%, -70%);
}
}
&--activated {
border: 4rpx solid $color-status-success;
border-radius: 50%;
position: relative;
&::after {
content: '';
position: absolute;
top: 40%;
left: 50%;
width: 16rpx;
height: 8rpx;
border-left: 4rpx solid $color-status-success;
border-bottom: 4rpx solid $color-status-success;
transform: translate(-50%, -50%) rotate(-45deg);
}
}
&--refunded {
position: relative;
&::before, &::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 28rpx;
height: 4rpx;
background: $color-status-cancel;
border-radius: 2rpx;
}
&::before { transform: translate(-50%, -50%) rotate(45deg); }
&::after { transform: translate(-50%, -50%) rotate(-45deg); }
}
&--expired {
border: 4rpx solid $uv-error;
border-radius: 50%;
position: relative;
&::before, &::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20rpx;
height: 4rpx;
background: $uv-error;
border-radius: 2rpx;
}
&::before { transform: translate(-50%, -50%) rotate(45deg); }
&::after { transform: translate(-50%, -50%) rotate(-45deg); }
}
}
.status-title {
font-family: $font-family-body;
font-size: $font-size-title;
font-weight: $font-weight-bold;
color: $uv-main-color;
margin-bottom: $spacing-2;
}
.status-desc {
font-family: $font-family-body;
font-size: $font-size-sm;
color: $uv-tips-color;
text-align: center;
}
/* ===== 模块2:商品信息卡片 ===== */
.info-card {
background: $color-card;
border-radius: $radius-lg;
padding: $spacing-4;
margin-top: $spacing-3;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.product-name {
display: block;
font-family: $font-family-body;
font-size: $font-size-base;
font-weight: $font-weight-bold;
color: $uv-main-color;
margin-bottom: $spacing-2;
}
.price-row {
display: flex;
align-items: baseline;
margin-bottom: $spacing-2;
}
.price-current {
display: flex;
align-items: baseline;
margin-right: $spacing-2;
}
.price-symbol {
font-family: $font-family-body;
font-size: $font-size-sm;
font-weight: $font-weight-bold;
color: $color-price;
}
.price-value {
font-family: $font-family-body;
font-size: $font-size-title;
font-weight: $font-weight-bold;
color: $color-price;
line-height: 1;
}
.product-desc {
font-family: $font-family-body;
font-size: $font-size-sm;
color: $uv-tips-color;
line-height: 1.6;
}
/* ===== 模块3:订单明细 ===== */
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: $spacing-3 0;
border-bottom: $border-width solid $uv-border-color;
&--last {
border-bottom: none;
}
}
.detail-label {
font-family: $font-family-body;
font-size: $font-size-sm;
color: $uv-main-color;
flex-shrink: 0;
}
.detail-value {
font-family: $font-family-body;
font-size: $font-size-sm;
color: $uv-content-color;
text-align: right;
&--code {
font-weight: $font-weight-bold;
letter-spacing: 4rpx;
}
&--price {
font-weight: $font-weight-bold;
color: $color-price;
}
}
.detail-value-row {
display: flex;
align-items: center;
gap: $spacing-2;
}
.copy-btn {
padding: 4rpx $spacing-2;
border-radius: $radius-sm;
background: rgba(60, 156, 255, 0.08);
&:active {
transform: scale(0.95);
}
}
.copy-btn__text {
font-family: $font-family-body;
font-size: $font-size-sm;
color: $uv-primary;
font-weight: $font-weight-medium;
}
/* ===== 模块4:使用提示 ===== */
.tips-card {
background: rgba(60, 156, 255, 0.04);
border-radius: $radius-lg;
padding: $spacing-4;
margin-top: $spacing-3;
}
.tips-title {
display: block;
font-family: $font-family-body;
font-size: $font-size-sm;
font-weight: $font-weight-bold;
color: $uv-main-color;
margin-bottom: $spacing-2;
}
.tips-list {
margin-bottom: $spacing-3;
}
.tips-item {
display: flex;
align-items: flex-start;
margin-bottom: $spacing-2;
}
.tips-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $uv-primary;
flex-shrink: 0;
margin-top: 14rpx;
margin-right: $spacing-2;
}
.tips-text {
font-family: $font-family-body;
font-size: $font-size-sm;
color: $uv-tips-color;
line-height: 1.6;
}
.tips-link {
&:active {
opacity: 0.7;
}
}
.tips-link__text {
font-family: $font-family-body;
font-size: $font-size-sm;
color: $uv-primary;
font-weight: $font-weight-medium;
}
/* ===== 底部安全区 ===== */
.safe-bottom {
height: $spacing-3;
}
/* ===== 模块5:底部操作栏 ===== */
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
width: 100%;
box-sizing: border-box;
min-height: $bar-height;
background: $color-card;
display: flex;
align-items: center;
justify-content: flex-end;
padding: $spacing-3 $spacing-6;
padding-bottom: calc(#{$spacing-3} + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
z-index: $z-sticky;
}
.bottom-bar__right {
display: flex;
gap: $spacing-3;
}
.btn-action {
height: 80rpx;
padding: 0 56rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all $transition-fast;
&--primary {
background: $uv-primary;
&:active {
transform: scale(0.97);
}
}
&--outline {
border: 2rpx solid $uv-border-color;
&:active {
transform: scale(0.97);
background: $uv-bg-color;
}
}
}
.btn-action__text {
font-family: $font-family-body;
font-size: $font-size-sm;
font-weight: $font-weight-bold;
color: #fff;
}
.btn-action__text--outline {
font-family: $font-family-body;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $uv-tips-color;
}
</style>