1044 lines
26 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-container">
<!-- 顶部装饰背景 - 漂浮光球 -->
<view class="bg-decoration"></view>
<view class="header-area">
<view class="page-title">任务中心</view>
<view class="page-subtitle">Task Center</view>
</view>
<!-- 进度统计卡片 - 毛玻璃风格 -->
<view class="progress-card glass-card">
<view class="progress-header">
<text class="progress-title">📊 我的任务进度</text>
</view>
<view class="progress-stats">
<view class="stat-item">
<text class="stat-value highlight">{{ userProgress.orderCount || 0 }}</text>
<text class="stat-label">累计订单</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value highlight">{{ userProgress.inviteCount || 0 }}</text>
<text class="stat-label">邀请人数</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value first-order-check" :class="{ done: userProgress.firstOrder }">
{{ userProgress.firstOrder ? '✓' : '—' }}
</view>
<text class="stat-label">首单完成</text>
</view>
</view>
</view>
<!-- 任务列表 -->
<scroll-view
scroll-y
class="content-scroll"
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh"
>
<!-- 加载状态 -->
<view v-if="loading && tasks.length === 0" class="loading-state">
<view class="spinner"></view>
<text>加载中...</text>
</view>
<!-- 空状态 -->
<view v-else-if="tasks.length === 0" class="empty-state">
<text class="empty-icon">📝</text>
<text class="empty-text">暂无可用任务</text>
<text class="empty-hint">敬请期待更多精彩活动</text>
</view>
<!-- 任务卡片列表 -->
<view v-else class="task-list">
<view
v-for="(task, index) in tasks"
:key="task.id"
class="task-card"
:style="{ animationDelay: `${index * 0.1}s` }"
>
<!-- 任务头部 -->
<view class="task-header" @click="toggleTask(task.id)">
<view class="task-info">
<text class="task-icon">{{ getTaskIcon(task) }}</text>
<view class="task-meta">
<text class="task-name">{{ task.name }}</text>
<text class="task-desc">{{ task.description }}</text>
<!-- 新增独立进度展示 (当存在 sub_progress 时显示) -->
<view class="sub-progress-list" v-if="taskProgress[task.id]?.subProgress?.length > 0">
<view
v-for="sub in taskProgress[task.id].subProgress"
:key="sub.activity_id"
class="sub-progress-item"
>
<text class="sub-label">活动 {{ sub.activity_id }}</text>
<view class="sub-bar-bg">
<view class="sub-bar-fill" :style="{ width: getSubProgressWidth(sub, task) }"></view>
</view>
<text class="sub-value">¥{{ sub.order_amount / 100 }}</text>
</view>
</view>
</view>
</view>
<view class="task-status-wrap">
<view v-if="task.quota > 0 && task.quota <= task.claimed_count" class="task-quota-tag exhausted">
已领完
</view>
<view v-else-if="task.quota > 0" class="task-quota-tag">
剩余 {{ task.quota - task.claimed_count }} 份
</view>
<view class="task-status" :class="getTaskStatusClass(task)">
{{ getTaskStatusText(task) }}
</view>
<text class="expand-arrow" :class="{ expanded: expandedTasks[task.id] }"></text>
</view>
</view>
<!-- 档位列表 (可展开) -->
<view class="tier-list" v-if="expandedTasks[task.id] && task.tiers && task.tiers.length > 0">
<view
v-for="tier in task.tiers"
:key="tier.id"
class="tier-item"
:class="{ 'tier-claimed': isTierClaimed(task.id, tier.id), 'tier-claimable': isTierClaimable(task, tier) }"
>
<view class="tier-left">
<view class="tier-condition">
<text class="tier-badge">{{ getTierBadge(tier) }}</text>
<text class="tier-text">{{ getTierConditionText(tier) }}</text>
</view>
<view class="tier-reward">
<text class="reward-icon">🎁</text>
<text class="reward-text">{{ getTierRewardText(task, tier) }}</text>
</view>
</view>
<view class="tier-right">
<!-- 已领取 -->
<view v-if="isTierClaimed(task.id, tier.id)" class="tier-btn claimed">
<text>已领取</text>
</view>
<!-- 已领完 (任务级限量逻辑) -->
<view v-else-if="task.quota > 0 && task.quota <= task.claimed_count" class="tier-btn disabled">
<text>已领完</text>
</view>
<!-- 已领完 (档位级限量逻辑) -->
<view v-else-if="tier.quota > 0 && tier.remaining === 0" class="tier-btn disabled">
<text>已领完</text>
</view>
<!-- 可领取 -->
<view v-else-if="isTierClaimable(task, tier)" class="tier-btn claimable" @click="claimReward(task, tier)">
<text>{{ claiming[`${task.id}_${tier.id}`] ? '领取中...' : '领取' }}</text>
</view>
<!-- 进度中 -->
<view v-else class="tier-progress">
<text class="progress-text">{{ getTierProgressText(task, tier) }}</text>
<!-- 限量进度提示 -->
<text class="quota-text" v-if="tier.quota > 0">剩余 {{ tier.remaining }} 份</text>
</view>
</view>
</view>
</view>
<!-- 无档位提示 -->
<view class="no-tier-hint" v-if="expandedTasks[task.id] && (!task.tiers || task.tiers.length === 0)">
<text>暂无可领取档位</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getTasks, getTaskProgress, claimTaskReward } from '../../api/appUser'
import { vibrateShort } from '@/utils/vibrate.js'
const tasks = ref([])
const loading = ref(false)
const isRefreshing = ref(false)
const expandedTasks = reactive({})
const claiming = reactive({})
// 用户进度 (汇总 - 用于顶部统计卡片显示)
const userProgress = reactive({
orderCount: 0,
orderAmount: 0,
inviteCount: 0,
firstOrder: false,
claimedTiers: {} // { taskId: [tierId1, tierId2] }
})
// BUG修复每个任务独立存储进度数据
const taskProgress = reactive({}) // { taskId: { orderCount, orderAmount, inviteCount, firstOrder } }
// 获取用户ID
function getUserId() {
return uni.getStorageSync('user_id')
}
// 检查登录状态
function checkAuth() {
const token = uni.getStorageSync('token')
const userId = getUserId()
if (!token || !userId) {
uni.showModal({
title: '提示',
content: '请先登录',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: '/pages/login/index' })
}
}
})
return false
}
return true
}
// 获取任务图标
function getTaskIcon(task) {
const name = (task.name || '').toLowerCase()
if (name.includes('首单') || name.includes('first')) return '🎁'
if (name.includes('订单') || name.includes('order')) return '📦'
if (name.includes('邀请') || name.includes('invite')) return '👥'
if (name.includes('签到') || name.includes('check')) return '📅'
if (name.includes('分享') || name.includes('share')) return '📣'
return '⭐'
}
// 获取任务状态类
function getTaskStatusClass(task) {
const progress = userProgress.claimedTiers[task.id] || []
const allTiers = task.tiers || []
if (allTiers.length === 0) return 'status-waiting'
// 检查是否全部完成
const allClaimed = allTiers.every(t => progress.includes(t.id))
if (allClaimed) return 'status-done'
// 任务级已领完
if (task.quota > 0 && task.quota <= task.claimed_count) return 'status-done'
// 检查是否有可领取的
if (allTiers.some(t => isTierClaimable(task, t) && !progress.includes(t.id))) {
return 'status-claimable'
}
return 'status-progress'
}
// 获取任务状态文字
function getTaskStatusText(task) {
const progress = userProgress.claimedTiers[task.id] || []
const allTiers = task.tiers || []
if (allTiers.length === 0) return '暂无档位'
const allClaimed = allTiers.every(t => progress.includes(t.id))
if (allClaimed) return '已完成'
// 任务级已领完
if (task.quota > 0 && task.quota <= task.claimed_count) return '已领完'
if (allTiers.some(t => isTierClaimable(task, t) && !progress.includes(t.id))) {
return '可领取'
}
return '进行中'
}
// 展开/收起任务
function toggleTask(taskId) {
expandedTasks[taskId] = !expandedTasks[taskId]
}
// 获取档位徽章
function getTierBadge(tier) {
const metric = tier.metric || ''
if (metric === 'first_order') return '首'
if (metric === 'order_count') return `${tier.threshold}单`
if (metric === 'order_amount') return `¥${tier.threshold / 100}`
if (metric === 'invite_count') return `${tier.threshold}人`
return tier.threshold || ''
}
// 获取档位条件文字
function getTierConditionText(tier) {
const metric = tier.metric || ''
if (metric === 'first_order') return '完成首笔订单'
if (metric === 'order_count') return `累计下单 ${tier.threshold} 笔`
if (metric === 'order_amount') return `累计消费 ¥${tier.threshold / 100}`
if (metric === 'invite_count') return `邀请 ${tier.threshold} 位好友`
return `达成 ${tier.threshold}`
}
// 获取档位奖励文字
function getTierRewardText(task, tier) {
const rewards = (task.rewards || []).filter(r => r.tier_id === tier.id)
if (rewards.length === 0) return '神秘奖励'
const texts = rewards.map(r => {
const type = r.reward_type || ''
const name = r.reward_name || ''
const payload = r.reward_payload || {}
const qty = r.quantity || 1
// 优先使用后端返回的 reward_name
if (name) {
if (type === 'points') {
const points = payload.points || qty
return points > 1 ? `${points}${name}` : name
}
if (type === 'coupon') {
const value = payload.value || payload.amount
return value ? `${name}(¥${value / 100})` : name
}
return qty > 1 ? `${name}×${qty}` : name
}
// 回退:从 payload 解析
if (type === 'points') {
const value = payload.points || payload.value || payload.amount || qty
return `${value}积分`
}
if (type === 'coupon') {
const value = payload.value || payload.amount
return value ? `¥${value / 100}优惠券` : '优惠券'
}
if (type === 'item_card') {
return payload.name || '道具卡'
}
if (type === 'title') {
return payload.name || '专属称号'
}
if (type === 'game_ticket') {
return payload.game_code ? `${payload.amount || 1}张抽奖券` : '抽奖券'
}
return '奖励'
})
return texts.join(' + ')
}
// 是否已领取
function isTierClaimed(taskId, tierId) {
const claimed = userProgress.claimedTiers[taskId] || []
return claimed.includes(tierId)
}
// 是否可领取 - BUG修复使用任务独立的进度数据
function isTierClaimable(task, tier) {
const metric = tier.metric || ''
const threshold = tier.threshold || 0
const operator = tier.operator || '>='
// 获取该任务独立的进度数据
const progress = taskProgress[task.id] || {}
// FIX: 如果档位关联了特定活动,优先使用活动独立进度 (sub_progress)
if (tier.activity_id > 0) {
if (progress.subProgress) {
const sub = progress.subProgress.find(s => s.activity_id === tier.activity_id)
if (sub) {
if (metric === 'order_amount') {
const current = sub.order_amount || 0
if (operator === '>=') return current >= threshold
if (operator === '==') return current === threshold
if (operator === '>') return current > threshold
return current >= threshold
} else if (metric === 'order_count') {
const current = sub.order_count || 0
if (operator === '>=') return current >= threshold
if (operator === '==') return current === threshold
if (operator === '>') return current > threshold
return current >= threshold
}
// 其他指标暂时没有拆分,回退到总进度校验
} else {
// 没找到该活动的进度记录 -> 视为 0 ->不可领取
return false
}
}
}
// 回退:使用任务总进度
let current = 0
if (metric === 'first_order') {
return progress.firstOrder || false
} else if (metric === 'order_count') {
current = progress.orderCount || 0
} else if (metric === 'order_amount') {
current = progress.orderAmount || 0
} else if (metric === 'invite_count') {
current = progress.inviteCount || 0
}
if (operator === '>=') return current >= threshold
if (operator === '==') return current === threshold
if (operator === '>') return current > threshold
return current >= threshold
}
// 获取进度文字 - BUG修复使用任务独立的进度数据
function getTierProgressText(task, tier) {
const metric = tier.metric || ''
const threshold = tier.threshold || 0
// 获取该任务独立的进度数据
const progress = taskProgress[task.id] || {}
// FIX: 如果档位关联了特定活动,优先显示活动独立进度
if (tier.activity_id > 0 && progress.subProgress) {
const sub = progress.subProgress.find(s => s.activity_id === tier.activity_id)
if (sub) {
if (metric === 'order_amount') {
const current = sub.order_amount || 0
return `¥${current / 100}/¥${threshold / 100}`
} else if (metric === 'order_count') {
const current = sub.order_count || 0
return `${current}/${threshold}`
}
} else {
// 没找到记录 -> 0
if (metric === 'order_amount') return `¥0/¥${threshold / 100}`
return `0/${threshold}`
}
}
// 回退:使用任务总进度
let current = 0
if (metric === 'first_order') {
return progress.firstOrder ? '已完成' : '未完成'
} else if (metric === 'order_count') {
current = progress.orderCount || 0
} else if (metric === 'order_amount') {
current = progress.orderAmount || 0
return `¥${current / 100}/¥${threshold / 100}`
} else if (metric === 'invite_count') {
current = progress.inviteCount || 0
}
return `${current}/${threshold}`
}
// 领取奖励
async function claimReward(task, tier) {
const key = `${task.id}_${tier.id}`
if (claiming[key]) return
vibrateShort()
claiming[key] = true
try {
const userId = getUserId()
await claimTaskReward(task.id, userId, tier.id)
// 更新本地状态
if (!userProgress.claimedTiers[task.id]) {
userProgress.claimedTiers[task.id] = []
}
userProgress.claimedTiers[task.id].push(tier.id)
uni.showToast({ title: '领取成功!', icon: 'success' })
} catch (e) {
console.error('领取失败:', e)
uni.showToast({ title: e.message || '领取失败', icon: 'none' })
} finally {
claiming[key] = false
}
}
// 下拉刷新
async function onRefresh() {
isRefreshing.value = true
await fetchData()
isRefreshing.value = false
}
// 获取数据
async function fetchData() {
if (!checkAuth()) return
loading.value = true
try {
const userId = getUserId()
// 获取任务列表
const res = await getTasks(1, 50)
const list = res.list || res.data || []
tasks.value = list
// 默认展开第一个任务
if (list.length > 0 && Object.keys(expandedTasks).length === 0) {
expandedTasks[list[0].id] = true
}
// 获取用户进度
if (list.length > 0) {
// 初始化汇总数据
userProgress.orderCount = 0
userProgress.orderAmount = 0
userProgress.inviteCount = 0
userProgress.firstOrder = false
userProgress.claimedTiers = {}
// 并行获取所有任务的进度
const progressPromises = list.map(t =>
getTaskProgress(t.id, userId).catch(err => {
console.warn(`[Tasks] 获取任务 ${t.id} 进度失败:`, err)
return null
})
)
const progressResults = await Promise.allSettled(progressPromises)
progressResults.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value) {
const p = result.value
const taskId = list[index].id
// BUG修复每个任务独立存储进度数据
taskProgress[taskId] = {
orderCount: p.order_count || 0,
orderAmount: p.order_amount || 0,
inviteCount: p.invite_count || 0,
firstOrder: p.first_order || false,
subProgress: p.sub_progress || [] // 新增:独立进度列表
}
// 聚合进度指标 (取各任务返回的最大值 - 仅用于顶部统计卡片显示)
userProgress.orderCount = Math.max(userProgress.orderCount, p.order_count || 0)
userProgress.orderAmount = Math.max(userProgress.orderAmount, p.order_amount || 0)
userProgress.inviteCount = Math.max(userProgress.inviteCount, p.invite_count || 0)
if (p.first_order) userProgress.firstOrder = true
// 记录各任务已领取的档位
userProgress.claimedTiers[taskId] = p.claimed_tiers || []
}
})
console.log('[Tasks] 汇总后的进度数据:', userProgress)
}
} catch (e) {
console.error('获取任务失败:', e)
} finally {
loading.value = false
}
}
onLoad(() => {
fetchData()
})
// 计算子进度条宽度
function getSubProgressWidth(sub, task) {
// 尝试找该任务的最大金额门槛作为分母
let maxThreshold = 0
if (task.tiers && task.tiers.length > 0) {
// 过滤出与金额相关的档位
const amountTiers = task.tiers.filter(t => t.metric === 'order_amount')
if (amountTiers.length > 0) {
maxThreshold = Math.max(...amountTiers.map(t => t.threshold || 0))
}
}
// 如果没有找到阈值默认100% (或者可以设一个默认值如 200元)
if (maxThreshold === 0) maxThreshold = 20000 // 默认200元
const percent = Math.min((sub.order_amount || 0) / maxThreshold * 100, 100)
return `${percent}%`
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: $bg-page;
position: relative;
overflow: hidden;
}
.header-area {
padding: $spacing-xl $spacing-lg;
padding-top: calc(env(safe-area-inset-top) + 20rpx);
position: relative;
z-index: 1;
}
.page-title {
font-size: 48rpx;
font-weight: 900;
color: $text-main;
margin-bottom: 8rpx;
letter-spacing: 1rpx;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.page-subtitle {
font-size: 24rpx;
color: $text-tertiary;
text-transform: uppercase;
letter-spacing: 2rpx;
font-weight: 600;
}
/* 进度统计卡片 */
.progress-card {
@extend .glass-card;
margin: 0 $spacing-lg $spacing-lg;
padding: 30rpx;
}
.progress-header {
margin-bottom: 24rpx;
}
.progress-title {
font-size: 26rpx;
font-weight: 700;
color: $text-sub;
}
.progress-stats {
display: flex;
align-items: center;
justify-content: space-around;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 48rpx;
font-weight: 900;
color: $text-main;
font-family: 'DIN Alternate', sans-serif;
line-height: 1.2;
}
.stat-value.highlight {
color: $brand-primary;
}
.stat-value.first-order-check {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: rgba(0, 0, 0, 0.05);
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
color: $text-tertiary;
&.done {
background: rgba($brand-primary, 0.1);
color: $brand-primary;
}
}
.stat-label {
font-size: 22rpx;
color: $text-tertiary;
margin-top: 8rpx;
font-weight: 500;
}
.stat-divider {
width: 1px;
height: 50rpx;
background: $border-color-light;
}
/* 内容滚动区 */
.content-scroll {
height: calc(100vh - 400rpx);
padding: 0 $spacing-lg $spacing-lg;
}
/* 加载状态 */
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
color: $text-tertiary;
font-size: 26rpx;
gap: 16rpx;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.empty-text {
color: $text-tertiary;
font-size: 28rpx;
margin-bottom: 12rpx;
}
.empty-hint {
color: $text-tertiary;
font-size: 24rpx;
opacity: 0.6;
}
/* 任务列表 */
.task-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
/* 任务卡片 */
.task-card {
background: #fff;
border-radius: $radius-lg;
overflow: hidden;
box-shadow: $shadow-sm;
animation: fadeInUp 0.5s ease-out backwards;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.task-header {
padding: 24rpx;
display: flex;
justify-content: space-between;
align-items: center;
&:active {
background: rgba(0, 0, 0, 0.02);
}
}
.task-info {
display: flex;
align-items: center;
flex: 1;
overflow: hidden;
}
.task-icon {
font-size: 40rpx;
margin-right: 16rpx;
flex-shrink: 0;
}
.task-meta {
flex: 1;
overflow: hidden;
}
.task-name {
font-size: 30rpx;
font-weight: 700;
color: $text-main;
display: block;
margin-bottom: 4rpx;
}
.task-desc {
font-size: 24rpx;
color: $text-sub;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 8rpx;
}
/* 独立进度条样式 */
.sub-progress-list {
display: flex;
flex-direction: column;
gap: 6rpx;
margin-top: 8rpx;
}
.sub-progress-item {
display: flex;
align-items: center;
font-size: 20rpx;
color: $text-tertiary;
}
.sub-label {
margin-right: 8rpx;
min-width: 80rpx;
}
.sub-bar-bg {
flex: 1;
height: 8rpx;
background: #f0f0f0;
border-radius: 4rpx;
margin-right: 8rpx;
overflow: hidden;
}
.sub-bar-fill {
height: 100%;
background: $brand-primary;
border-radius: 4rpx;
transition: width 0.3s ease;
}
.sub-value {
font-family: 'DIN Alternate';
font-weight: 700;
color: $text-sub;
min-width: 60rpx;
text-align: right;
}
.task-status-wrap {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 16rpx;
gap: 8rpx;
}
.task-quota-tag {
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 100rpx;
background: rgba(#ff9500, 0.1);
color: #ff9500;
font-weight: 600;
white-space: nowrap;
&.exhausted {
background: rgba(0, 0, 0, 0.05);
color: $text-tertiary;
}
}
.task-status {
font-size: 22rpx;
padding: 6rpx 16rpx;
border-radius: 100rpx;
margin-right: 8rpx;
&.status-done {
background: rgba($uni-color-success, 0.1);
color: $uni-color-success;
}
&.status-claimable {
background: rgba($brand-primary, 0.1);
color: $brand-primary;
font-weight: 700;
}
&.status-progress {
background: rgba($brand-primary, 0.05);
color: $text-sub;
}
&.status-waiting {
background: #f5f5f5;
color: $text-tertiary;
}
}
.expand-arrow {
font-size: 28rpx;
color: $text-tertiary;
transition: transform 0.3s;
&.expanded {
transform: rotate(90deg);
}
}
/* 档位列表 */
.tier-list {
border-top: 1rpx solid $border-color-light;
padding: 16rpx 24rpx 24rpx;
background: #fafafa;
}
.tier-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 20rpx;
background: #fff;
border-radius: $radius-md;
margin-bottom: 12rpx;
border: 1rpx solid $border-color-light;
&:last-child {
margin-bottom: 0;
}
&.tier-claimed {
background: #f5f5f5;
opacity: 0.7;
}
&.tier-claimable {
border-color: $brand-primary;
background: rgba($brand-primary, 0.02);
}
}
.tier-left {
flex: 1;
overflow: hidden;
}
.tier-condition {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.tier-badge {
background: $text-main;
color: #fff;
font-size: 18rpx;
padding: 4rpx 10rpx;
border-radius: 6rpx;
margin-right: 12rpx;
font-weight: 700;
}
.tier-text {
font-size: 26rpx;
color: $text-main;
font-weight: 500;
}
.tier-reward {
display: flex;
align-items: center;
}
.reward-icon {
font-size: 20rpx;
margin-right: 6rpx;
}
.reward-text {
font-size: 22rpx;
color: $brand-primary;
}
.tier-progress {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.progress-text {
font-size: 24rpx;
color: $text-sub;
font-weight: 600;
}
.quota-text {
font-size: 18rpx;
color: $brand-primary;
margin-top: 4rpx;
opacity: 0.8;
}
.tier-btn {
&.disabled {
background: #f0f0f0;
color: #ccc;
cursor: not-allowed;
}
}
.tier-btn {
padding: 10rpx 24rpx;
border-radius: 100rpx;
font-size: 24rpx;
font-weight: 600;
&.claimed {
background: #eee;
color: $text-tertiary;
}
&.claimable {
background: $brand-primary;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.3);
&:active {
transform: scale(0.95);
}
}
}
.tier-progress {
padding: 10rpx 16rpx;
}
.progress-text {
font-size: 24rpx;
color: $text-sub;
font-family: 'DIN Alternate', sans-serif;
}
.no-tier-hint {
padding: 30rpx;
text-align: center;
color: $text-tertiary;
font-size: 24rpx;
background: #fafafa;
border-top: 1rpx solid $border-color-light;
}
/* 加载动画 */
.spinner {
width: 28rpx;
height: 28rpx;
border: 3rpx solid $bg-secondary;
border-top-color: $text-tertiary;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>