修复订单列表不显示 source_type=3 订单的问题,支持对对碰等玩法订单 优化订单标题显示逻辑,移除内部标识并添加保底显示 优化订单详情页,当没有实物商品时显示活动信息 重构订单类型判断逻辑,支持更多玩法类型
797 lines
21 KiB
Vue
797 lines
21 KiB
Vue
<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 : (order.activity_name ? 1 : 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 || order.source_type === 3)">
|
||
<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 v-if="(!order.items || order.items.length === 0) && order.activity_name" class="item-card">
|
||
<view class="item-image-wrap">
|
||
<image class="item-image" :src="defaultImage" mode="aspectFill" />
|
||
<view class="winner-tag" v-if="order.is_winner">
|
||
<text class="tag-text">已开启</text>
|
||
</view>
|
||
</view>
|
||
<view class="item-info">
|
||
<text class="item-title">{{ order.activity_name }}</text>
|
||
<view class="item-tags">
|
||
<text class="tag">参与记录</text>
|
||
<text class="tag" v-if="order.issue_number">第{{ order.issue_number }}期</text>
|
||
</view>
|
||
<view class="item-meta">
|
||
<view class="price-wrap">
|
||
<text class="currency">¥</text>
|
||
<text class="price">{{ formatPrice(order.actual_amount) }}</text>
|
||
</view>
|
||
<text class="item-quantity">x1</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 class="section-card proof-section" v-if="order.draw_receipts && order.draw_receipts.length > 0">
|
||
<view class="section-header">
|
||
<text class="section-title">抽奖凭证</text>
|
||
<text class="item-count help-btn" @tap="showProofHelp">?</text>
|
||
</view>
|
||
<view v-for="(receipt, idx) in order.draw_receipts" :key="idx" class="receipt-block">
|
||
<view class="info-row" v-if="receipt.algo_version">
|
||
<text class="label">算法版本</text>
|
||
<text class="value mono">{{ receipt.algo_version }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="receipt.server_seed_hash">
|
||
<text class="label">服务端种子哈希</text>
|
||
<view class="value-wrap">
|
||
<text class="value mono seed-text">{{ receipt.server_seed_hash }}</text>
|
||
<view class="copy-btn" @tap="copyText(receipt.server_seed_hash)">复制</view>
|
||
</view>
|
||
</view>
|
||
<view class="info-row" v-if="receipt.server_sub_seed">
|
||
<text class="label">子种子</text>
|
||
<view class="value-wrap">
|
||
<text class="value mono seed-text">{{ receipt.server_sub_seed }}</text>
|
||
<view class="copy-btn" @tap="copyText(receipt.server_sub_seed)">复制</view>
|
||
</view>
|
||
</view>
|
||
<view class="info-row" v-if="receipt.client_seed">
|
||
<text class="label">客户端种子</text>
|
||
<text class="value mono">{{ receipt.client_seed }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="receipt.draw_id">
|
||
<text class="label">抽奖ID</text>
|
||
<text class="value mono">{{ receipt.draw_id }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="receipt.timestamp">
|
||
<text class="label">时间戳</text>
|
||
<text class="value mono">{{ receipt.timestamp }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="receipt.round_id">
|
||
<text class="label">期次ID</text>
|
||
<text class="value">{{ receipt.round_id }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="proof-notice">
|
||
<text class="notice-icon">🔒</text>
|
||
<text class="notice-text">以上数据可用于验证抽奖结果的公正性</text>
|
||
</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, createWechatOrder } 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() {
|
||
const openid = uni.getStorageSync('openid')
|
||
if (!openid) {
|
||
uni.showToast({ title: '缺少OpenID,请重新登录', icon: 'none' })
|
||
return
|
||
}
|
||
const ord = order.value
|
||
if (!ord || !ord.order_no) return
|
||
uni.showLoading({ title: '拉起支付...' })
|
||
createWechatOrder({ openid, order_no: ord.order_no })
|
||
.then((payRes) => new Promise((resolve, reject) => {
|
||
uni.requestPayment({
|
||
provider: 'wxpay',
|
||
timeStamp: payRes.timeStamp || payRes.timestamp,
|
||
nonceStr: payRes.nonceStr || payRes.noncestr,
|
||
package: payRes.package,
|
||
signType: payRes.signType || 'MD5',
|
||
paySign: payRes.paySign,
|
||
success: resolve,
|
||
fail: reject
|
||
})
|
||
}))
|
||
.then(async () => {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||
await loadOrder()
|
||
})
|
||
.catch((e) => {
|
||
uni.hideLoading()
|
||
if (e?.errMsg && String(e.errMsg).includes('cancel')) {
|
||
uni.showToast({ title: '支付已取消', icon: 'none' })
|
||
return
|
||
}
|
||
uni.showToast({ title: e?.message || '支付失败', 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 || type === 3) {
|
||
// 优先使用分类名称,其次活动名称,最后根据玩法类型显示
|
||
if (order.value && order.value.category_name) return order.value.category_name
|
||
if (order.value && order.value.activity_name) return order.value.activity_name
|
||
const playType = order.value && order.value.play_type
|
||
if (playType === 'match') return '对对碰'
|
||
if (playType === 'ichiban') return '一番赏'
|
||
if (type === 2) return '抽奖订单'
|
||
return '发奖记录'
|
||
}
|
||
return '其他'
|
||
}
|
||
|
||
function showProofHelp() {
|
||
uni.showModal({
|
||
title: '抽奖凭证说明',
|
||
content: '该凭证包含本次抽奖的随机种子和参数,可用于验证抽奖的公平性。您可以复制相关数据,自行进行核验。',
|
||
showCancel: false
|
||
})
|
||
}
|
||
</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: 150rpx;
|
||
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: -60rpx $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;
|
||
}
|
||
|
||
/* 抽奖凭证区 */
|
||
.proof-section {
|
||
.seed-text {
|
||
font-size: 22rpx;
|
||
word-break: break-all;
|
||
max-width: 360rpx;
|
||
@include text-ellipsis(1);
|
||
}
|
||
|
||
.proof-notice {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $spacing-sm;
|
||
margin-top: $spacing-md;
|
||
padding: $spacing-sm $spacing-md;
|
||
background: rgba($brand-primary, 0.06);
|
||
border-radius: $radius-md;
|
||
|
||
.notice-icon {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.notice-text {
|
||
font-size: 22rpx;
|
||
color: $text-sub;
|
||
}
|
||
}
|
||
}
|
||
</style>
|