feat(订单): 实现订单详情页功能
添加订单详情页路由配置 开发订单详情页UI及交互逻辑 对接订单详情和取消订单API 更新文档记录开发进度 优化订单状态显示逻辑
This commit is contained in:
parent
09ca0c252d
commit
d930756130
@ -21,6 +21,7 @@
|
|||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">优惠券</text>
|
<text class="label">优惠券</text>
|
||||||
<picker
|
<picker
|
||||||
|
class="picker-full"
|
||||||
mode="selector"
|
mode="selector"
|
||||||
:range="coupons"
|
:range="coupons"
|
||||||
range-key="name"
|
range-key="name"
|
||||||
@ -40,6 +41,7 @@
|
|||||||
<view class="form-item" v-if="showCards">
|
<view class="form-item" v-if="showCards">
|
||||||
<text class="label">道具卡</text>
|
<text class="label">道具卡</text>
|
||||||
<picker
|
<picker
|
||||||
|
class="picker-full"
|
||||||
mode="selector"
|
mode="selector"
|
||||||
:range="propCards"
|
:range="propCards"
|
||||||
range-key="name"
|
range-key="name"
|
||||||
@ -259,6 +261,11 @@ function handleConfirm() {
|
|||||||
margin-bottom: $spacing-xs;
|
margin-bottom: $spacing-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.picker-full {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.picker-display {
|
.picker-display {
|
||||||
border: 2rpx solid $border-color-light;
|
border: 2rpx solid $border-color-light;
|
||||||
border-radius: $radius-md;
|
border-radius: $radius-md;
|
||||||
|
|||||||
@ -48,6 +48,12 @@
|
|||||||
"navigationBarTitleText": "我的订单"
|
"navigationBarTitleText": "我的订单"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/orders/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订单详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/address/index",
|
"path": "pages/address/index",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
645
pages/orders/detail.vue
Normal file
645
pages/orders/detail.vue
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<view v-if="loading" class="loading-state">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 错误状态 -->
|
||||||
|
<view v-else-if="error" class="error-state">
|
||||||
|
<view class="error-icon">⚠️</view>
|
||||||
|
<text class="error-text">{{ error }}</text>
|
||||||
|
<button class="retry-btn" @tap="loadOrder">重试</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单内容 -->
|
||||||
|
<view v-else-if="order" class="content">
|
||||||
|
<!-- 状态头部背景 -->
|
||||||
|
<view class="status-header-bg" :class="getStatusClass(order)">
|
||||||
|
<view class="bg-circle c1"></view>
|
||||||
|
<view class="bg-circle c2"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 状态卡片 -->
|
||||||
|
<view class="status-card">
|
||||||
|
<view class="status-content">
|
||||||
|
<view class="status-icon-wrap">
|
||||||
|
<text class="status-icon">{{ getStatusIcon(order) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-info">
|
||||||
|
<text class="status-title">{{ statusText(order) }}</text>
|
||||||
|
<text class="status-desc" v-if="order.status === 1">请在 15 分钟内完成支付</text>
|
||||||
|
<text class="status-desc" v-else-if="order.status === 3">订单已取消</text>
|
||||||
|
<text class="status-desc" v-else>感谢您的购买,期待再次光临</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 奖品/商品列表 -->
|
||||||
|
<view class="section-card product-section">
|
||||||
|
<view class="section-header">
|
||||||
|
<text class="section-title">商品清单</text>
|
||||||
|
<text class="item-count">共 {{ order.items ? order.items.length : 0 }} 件</text>
|
||||||
|
</view>
|
||||||
|
<view class="order-items">
|
||||||
|
<view v-for="(item, index) in order.items" :key="index" class="item-card">
|
||||||
|
<view class="item-image-wrap">
|
||||||
|
<image class="item-image" :src="getProductImage(item)" mode="aspectFill" />
|
||||||
|
<!-- 中奖标识 -->
|
||||||
|
<view class="winner-tag" v-if="order.is_winner && order.source_type === 2">
|
||||||
|
<text class="tag-text">中奖</text>
|
||||||
|
</view>
|
||||||
|
<view class="level-tag" v-if="order.reward_level">
|
||||||
|
<text class="tag-text">{{ order.reward_level }}赏</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-title">{{ item.title || '商品' }}</text>
|
||||||
|
<view class="item-tags" v-if="order.activity_name">
|
||||||
|
<text class="tag">{{ order.activity_name }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-meta">
|
||||||
|
<view class="price-wrap">
|
||||||
|
<text class="currency">¥</text>
|
||||||
|
<text class="price">{{ formatPrice(item.price) }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="item-quantity">x{{ item.quantity }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单信息 -->
|
||||||
|
<view class="section-card info-section">
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="label">订单编号</text>
|
||||||
|
<view class="value-wrap">
|
||||||
|
<text class="value mono">{{ order.order_no }}</text>
|
||||||
|
<view class="copy-btn" @tap="copyText(order.order_no)">复制</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="label">下单时间</text>
|
||||||
|
<text class="value">{{ formatTime(order.created_at) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row" v-if="order.paid_at">
|
||||||
|
<text class="label">支付时间</text>
|
||||||
|
<text class="value">{{ formatTime(order.paid_at) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row" v-if="order.cancelled_at">
|
||||||
|
<text class="label">取消时间</text>
|
||||||
|
<text class="value">{{ formatTime(order.cancelled_at) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="label">订单来源</text>
|
||||||
|
<text class="value">{{ getSourceTypeText(order.source_type) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 金额明细 -->
|
||||||
|
<view class="section-card amount-section">
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="label">商品总额</text>
|
||||||
|
<text class="value">¥{{ formatPrice(order.total_amount) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row" v-if="order.discount_amount">
|
||||||
|
<text class="label">优惠金额</text>
|
||||||
|
<text class="value discount">-¥{{ formatPrice(order.discount_amount) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="divider"></view>
|
||||||
|
<view class="total-row">
|
||||||
|
<text class="total-label">实付款</text>
|
||||||
|
<view class="total-price-wrap">
|
||||||
|
<text class="currency">¥</text>
|
||||||
|
<text class="total-price">{{ formatPrice(order.actual_amount) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<view class="footer-actions safe-area-bottom" v-if="order && order.status === 1">
|
||||||
|
<view class="action-btn secondary" @tap="handleCancel">取消订单</view>
|
||||||
|
<view class="action-btn primary" @tap="handlePay">立即支付</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { getOrderDetail, cancelOrder } from '../../api/appUser'
|
||||||
|
|
||||||
|
const orderId = ref('')
|
||||||
|
const order = ref(null)
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref('')
|
||||||
|
|
||||||
|
const defaultImage = 'https://keaiya-1259195914.cos.ap-shanghai.myqcloud.com/images/default-product.png'
|
||||||
|
|
||||||
|
onLoad((options) => {
|
||||||
|
if (options.id) {
|
||||||
|
orderId.value = options.id
|
||||||
|
loadOrder()
|
||||||
|
} else {
|
||||||
|
error.value = '参数错误'
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadOrder() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
try {
|
||||||
|
const res = await getOrderDetail(orderId.value)
|
||||||
|
order.value = res
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e.message || '获取订单详情失败'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认取消',
|
||||||
|
content: '确定要取消这个订单吗?',
|
||||||
|
confirmColor: '#FF6B00',
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.showLoading({ title: '取消中...' })
|
||||||
|
try {
|
||||||
|
await cancelOrder(orderId.value, '用户主动取消')
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '订单已取消', icon: 'success' })
|
||||||
|
loadOrder()
|
||||||
|
} catch (e) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: e.message || '取消失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePay() {
|
||||||
|
uni.showToast({ title: '支付功能开发中', icon: 'none' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyText(text) {
|
||||||
|
if (!text) return
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: String(text),
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({ title: '已复制', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPrice(price) {
|
||||||
|
if (price === undefined || price === null) return '0.00'
|
||||||
|
return (Number(price) / 100).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(t) {
|
||||||
|
if (!t) return ''
|
||||||
|
const date = new Date(t)
|
||||||
|
const y = date.getFullYear()
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const d = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hh = String(date.getHours()).padStart(2, '0')
|
||||||
|
const mm = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
const ss = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProductImage(item) {
|
||||||
|
if (item.product_images) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(item.product_images)
|
||||||
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
||||||
|
return parsed[0]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (typeof item.product_images === 'string' && item.product_images.startsWith('http')) {
|
||||||
|
return item.product_images
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultImage
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusText(item) {
|
||||||
|
const status = item.status
|
||||||
|
if (status === 1) return '待付款'
|
||||||
|
if (status === 2) return '已完成'
|
||||||
|
if (status === 3) return '已取消'
|
||||||
|
return '进行中'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusClass(item) {
|
||||||
|
const status = item.status
|
||||||
|
if (status === 1) return 'status-pending'
|
||||||
|
if (status === 2) return 'status-completed'
|
||||||
|
if (status === 3) return 'status-cancelled'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusIcon(item) {
|
||||||
|
const status = item.status
|
||||||
|
if (status === 1) return '🕒'
|
||||||
|
if (status === 2) return '🎉'
|
||||||
|
if (status === 3) return '🚫'
|
||||||
|
return '📦'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceTypeText(type) {
|
||||||
|
if (type === 1) return '商城订单'
|
||||||
|
if (type === 2) return '一番赏'
|
||||||
|
if (type === 3) return '发奖记录'
|
||||||
|
return '其他'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: $bg-page;
|
||||||
|
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态头部背景 */
|
||||||
|
.status-header-bg {
|
||||||
|
height: 360rpx;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0 0 40rpx 40rpx;
|
||||||
|
|
||||||
|
&.status-pending { background: linear-gradient(135deg, #FF9F43 0%, #FF6B6B 100%); }
|
||||||
|
&.status-completed { background: linear-gradient(135deg, #2ECC71 0%, #27AE60 100%); }
|
||||||
|
&.status-cancelled { background: linear-gradient(135deg, #95A5A6 0%, #7F8C8D 100%); }
|
||||||
|
|
||||||
|
.bg-circle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
&.c1 { width: 300rpx; height: 300rpx; top: -100rpx; right: -50rpx; }
|
||||||
|
&.c2 { width: 200rpx; height: 200rpx; bottom: 50rpx; left: -50rpx; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态卡片 */
|
||||||
|
.status-card {
|
||||||
|
margin: -160rpx $spacing-lg 0;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: $radius-xl;
|
||||||
|
padding: $spacing-xl;
|
||||||
|
box-shadow: $shadow-card;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.status-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacing-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon-wrap {
|
||||||
|
width: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
background: $bg-secondary;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
font-size: 44rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $text-main;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-desc {
|
||||||
|
font-size: $font-sm;
|
||||||
|
color: $text-sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用卡片样式 */
|
||||||
|
.section-card {
|
||||||
|
margin: $spacing-lg;
|
||||||
|
background: $bg-card;
|
||||||
|
border-radius: $radius-lg;
|
||||||
|
padding: $spacing-lg;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
animation: slideUp 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from { opacity: 0; transform: translateY(20rpx); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: $spacing-lg;
|
||||||
|
padding-bottom: $spacing-sm;
|
||||||
|
border-bottom: 2rpx dashed $border-color-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $text-main;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 6rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
background: $brand-primary;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-count {
|
||||||
|
font-size: $font-sm;
|
||||||
|
color: $text-sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品列表 */
|
||||||
|
.item-card {
|
||||||
|
display: flex;
|
||||||
|
gap: $spacing-md;
|
||||||
|
margin-bottom: $spacing-lg;
|
||||||
|
|
||||||
|
&:last-child { margin-bottom: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-image-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: $radius-md;
|
||||||
|
overflow: hidden;
|
||||||
|
background: $bg-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.winner-tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: $gradient-gold;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
border-radius: 0 0 $radius-md 0;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.tag-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-tag {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 2rpx 10rpx;
|
||||||
|
border-radius: $radius-sm 0 0 0;
|
||||||
|
|
||||||
|
.tag-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $text-main;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
@include text-ellipsis(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-tags {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: $brand-primary;
|
||||||
|
background: rgba($brand-primary, 0.08);
|
||||||
|
padding: 2rpx 10rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
color: $text-main;
|
||||||
|
|
||||||
|
.currency { font-size: 24rpx; font-weight: 600; }
|
||||||
|
.price { font-size: 32rpx; font-weight: 700; font-family: 'DIN Alternate', sans-serif; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-quantity {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 信息行 */
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: $text-main;
|
||||||
|
|
||||||
|
&.mono { font-family: monospace; }
|
||||||
|
&.discount { color: $uni-color-error; font-weight: 600; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
border: 1rpx solid $border-color;
|
||||||
|
padding: 2rpx 12rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
background: $bg-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1rpx;
|
||||||
|
background: $border-color-light;
|
||||||
|
margin: 20rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding-top: 10rpx;
|
||||||
|
|
||||||
|
.total-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-main;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-price-wrap {
|
||||||
|
color: $brand-primary;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
.currency { font-size: 28rpx; font-weight: 600; }
|
||||||
|
.total-price { font-size: 40rpx; font-weight: 800; font-family: 'DIN Alternate', sans-serif; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部操作栏 */
|
||||||
|
.footer-actions {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20rpx);
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 24rpx;
|
||||||
|
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 48rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:active { transform: scale(0.96); }
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
background: #fff;
|
||||||
|
color: $text-main;
|
||||||
|
border: 2rpx solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
background: $gradient-brand;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: $shadow-warm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading & Error */
|
||||||
|
.loading-state, .error-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 80vh;
|
||||||
|
|
||||||
|
.loading-text, .error-text {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
border: 6rpx solid rgba($brand-primary, 0.2);
|
||||||
|
border-top-color: $brand-primary;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.retry-btn {
|
||||||
|
margin-top: 32rpx;
|
||||||
|
background: $brand-primary;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
padding: 12rpx 48rpx;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -247,7 +247,8 @@ function switchTab(tab) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function apiStatus() {
|
function apiStatus() {
|
||||||
return currentTab.value === 'pending' ? 'pending' : 'completed'
|
// 1: 待付款, 2: 已完成
|
||||||
|
return currentTab.value === 'pending' ? 1 : 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤掉 source_type=3 的发奖订单
|
// 过滤掉 source_type=3 的发奖订单
|
||||||
|
|||||||
2
说明文档.md
2
说明文档.md
@ -31,5 +31,7 @@
|
|||||||
* [x] 2025-12-17: 修复 `pages/login/index.vue` 等多处 `$border-color` 未定义错误,在 `uni.scss` 中增加变量别名。
|
* [x] 2025-12-17: 修复 `pages/login/index.vue` 等多处 `$border-color` 未定义错误,在 `uni.scss` 中增加变量别名。
|
||||||
* [x] 2025-12-17: 修复 `pages/mine/index.vue` 编译错误,在 `api/appUser.js` 中补充 `getUserInfo`, `getUserTasks`, `getInviteRecords` 导出。
|
* [x] 2025-12-17: 修复 `pages/mine/index.vue` 编译错误,在 `api/appUser.js` 中补充 `getUserInfo`, `getUserTasks`, `getInviteRecords` 导出。
|
||||||
* [x] 2025-12-17: 将 dev 分支代码强制推送至 main 分支 (Deployment/Sync)。
|
* [x] 2025-12-17: 将 dev 分支代码强制推送至 main 分支 (Deployment/Sync)。
|
||||||
|
* [x] 2025-12-18: 实现订单详情 API 与取消订单 API (后端接口对接)。
|
||||||
|
* [x] 2025-12-18: 开发订单详情页 UI 及交互逻辑。
|
||||||
* [ ] 2025-12-17: 进行中 - 优化 `pages/activity/yifanshang/index.vue` 及相关组件。
|
* [ ] 2025-12-17: 进行中 - 优化 `pages/activity/yifanshang/index.vue` 及相关组件。
|
||||||
* [ ] 2025-12-17: 待开始 - 优化 `pages/login/index.vue` 视觉细节。
|
* [ ] 2025-12-17: 待开始 - 优化 `pages/login/index.vue` 视觉细节。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user