781 lines
19 KiB
Vue
781 lines
19 KiB
Vue
<template>
|
||
<view v-if="visible" class="guide-modal-wrapper" @touchmove.stop.prevent="handlePreventMove">
|
||
<!-- 背景遮罩 -->
|
||
<view class="modal-mask" @click="handleClose"></view>
|
||
|
||
<!-- 弹窗容器 -->
|
||
<view class="modal-container" @click.stop>
|
||
<!-- 背景图片 -->
|
||
<image class="modal-background" src="/static/nft/beijingban.png" mode="aspectFill"></image>
|
||
|
||
<!-- 蒙层 -->
|
||
<view class="modal-overlay"></view>
|
||
|
||
<!-- 内容区域 -->
|
||
<view class="modal-content">
|
||
<!-- 顶部区域:返回按钮和标题 -->
|
||
<view class="modal-header">
|
||
<!-- 返回按钮 -->
|
||
<view class="back-button" @click="handleClose">
|
||
<image class="back-icon" src="/static/starbookcontent/tuichu.png" mode="aspectFit" />
|
||
</view>
|
||
|
||
<!-- 标题 -->
|
||
<view class="modal-title">新手引导</view>
|
||
</view>
|
||
|
||
<!-- 引导列表 -->
|
||
<scroll-view class="modal-body" scroll-y :show-scrollbar="false">
|
||
<view
|
||
v-for="item in guideList"
|
||
:key="item.key"
|
||
class="guide-item"
|
||
:class="{ 'in-progress': item.status === 'in_progress' }"
|
||
>
|
||
<view class="guide-item-header">
|
||
<view class="guide-status-badge" :class="item.status">
|
||
{{ item.statusText }}
|
||
</view>
|
||
<text class="guide-name">{{ item.name }}</text>
|
||
</view>
|
||
|
||
<view class="guide-item-body">
|
||
<text class="guide-desc">{{ item.desc }}</text>
|
||
</view>
|
||
|
||
<!-- 进度条(进行中状态显示) -->
|
||
<view v-if="item.status === 'in_progress'" class="guide-progress">
|
||
<view class="progress-bar">
|
||
<view
|
||
class="progress-fill"
|
||
:style="{ width: item.progress.percentage + '%' }"
|
||
></view>
|
||
</view>
|
||
<text class="progress-text">{{ item.progress.completed }}/{{ item.progress.total }} 步骤</text>
|
||
</view>
|
||
|
||
<view class="guide-item-footer">
|
||
<!-- 未开始 -->
|
||
<view
|
||
v-if="item.buttonType === 'start'"
|
||
class="guide-btn start-btn"
|
||
@click="handleStartGuide(item.key)"
|
||
>
|
||
{{ item.buttonText }}
|
||
</view>
|
||
<!-- 进行中 -->
|
||
<view
|
||
v-else-if="item.buttonType === 'continue'"
|
||
class="guide-btn continue-btn"
|
||
@click="handleContinueGuide(item.key)"
|
||
>
|
||
{{ item.buttonText }}
|
||
</view>
|
||
<!-- 已完成(可领取) -->
|
||
<view
|
||
v-else-if="item.buttonType === 'claim'"
|
||
class="guide-btn claim-btn"
|
||
@click="handleClaimReward(item.key)"
|
||
>
|
||
{{ item.buttonText }}
|
||
</view>
|
||
<!-- 已领取 -->
|
||
<view
|
||
v-else
|
||
class="guide-btn disabled-btn"
|
||
>
|
||
{{ item.buttonText }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部统计 -->
|
||
<view class="modal-footer">
|
||
<view class="footer-stats">
|
||
已完成 {{ doneCount }}/{{ totalCount }}
|
||
</view>
|
||
<view v-if="claimableCount > 0" class="footer-claim-tip">
|
||
有 {{ claimableCount }} 个奖励可领取
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch } from 'vue'
|
||
import { getOnboardingStatus, claimOnboardingReward, completeGuide } from '@/utils/task-api.js'
|
||
import {
|
||
getGuideConfig,
|
||
getGuideStatusList,
|
||
getStepProgress,
|
||
hasGuideProgress,
|
||
getNextIncompleteStep,
|
||
getStepPage,
|
||
clearSubStepProgress,
|
||
resetGuide,
|
||
claimGuideReward,
|
||
markGuideDone,
|
||
isGuideDone,
|
||
markGuideRewardClaimed,
|
||
onboardingStages
|
||
} from '@/utils/guideConfig.js'
|
||
|
||
const props = defineProps({
|
||
visible: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['close', 'updated'])
|
||
|
||
const loading = ref(true)
|
||
const guideList = ref([])
|
||
const backendStages = ref([])
|
||
|
||
// 统计数据
|
||
const totalCount = computed(() => guideList.value.length)
|
||
const doneCount = computed(() => guideList.value.filter(item =>
|
||
item.status === 'completed' || item.status === 'reward_claimed'
|
||
).length)
|
||
const claimableCount = computed(() => guideList.value.filter(item => item.status === 'completed').length)
|
||
|
||
// 保存滚动位置和页面状态
|
||
let scrollTop = 0
|
||
let bodyOverflow = ''
|
||
let pageScrollEnabled = true
|
||
|
||
// 锁定背景页面滚动
|
||
function lockBodyScroll() {
|
||
// 获取当前页面滚动位置
|
||
uni.createSelectorQuery().selectViewport().scrollOffset(res => {
|
||
if (res) {
|
||
scrollTop = res.scrollTop || 0
|
||
}
|
||
}).exec()
|
||
|
||
// #ifdef H5
|
||
// H5 平台:锁定 body 滚动
|
||
const body = document.body
|
||
bodyOverflow = body.style.overflow
|
||
body.style.overflow = 'hidden'
|
||
body.style.position = 'fixed'
|
||
body.style.top = `-${scrollTop}px`
|
||
body.style.width = '100%'
|
||
// #endif
|
||
|
||
// #ifdef MP
|
||
// 小程序平台:禁用页面滚动
|
||
uni.pageScrollTo({
|
||
scrollTop: scrollTop,
|
||
duration: 0
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
// APP 平台:禁用页面滚动
|
||
const currentWebview = plus.webview.currentWebview()
|
||
if (currentWebview) {
|
||
pageScrollEnabled = currentWebview.isScrollEnabled()
|
||
currentWebview.setStyle({
|
||
scrollIndicator: 'none',
|
||
bounce: 'none'
|
||
})
|
||
// 尝试禁用滚动
|
||
try {
|
||
currentWebview.setStyle({ scrollEnabled: false })
|
||
} catch (e) {
|
||
console.log('APP 平台禁用滚动失败:', e)
|
||
}
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
// 解锁背景页面滚动
|
||
function unlockBodyScroll() {
|
||
// #ifdef H5
|
||
// H5 平台:恢复 body 滚动
|
||
const body = document.body
|
||
body.style.overflow = bodyOverflow
|
||
body.style.position = ''
|
||
body.style.top = ''
|
||
body.style.width = ''
|
||
// 恢复滚动位置
|
||
window.scrollTo(0, scrollTop)
|
||
// #endif
|
||
|
||
// #ifdef MP
|
||
// 小程序平台:恢复页面滚动
|
||
uni.pageScrollTo({
|
||
scrollTop: scrollTop,
|
||
duration: 0
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
// APP 平台:恢复页面滚动
|
||
const currentWebview = plus.webview.currentWebview()
|
||
if (currentWebview) {
|
||
currentWebview.setStyle({
|
||
scrollIndicator: 'auto',
|
||
bounce: 'vertical'
|
||
})
|
||
// 恢复滚动
|
||
try {
|
||
currentWebview.setStyle({ scrollEnabled: pageScrollEnabled })
|
||
} catch (e) {
|
||
console.log('APP 平台恢复滚动失败:', e)
|
||
}
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
// 监听 visible 变化
|
||
watch(() => props.visible, (newVal) => {
|
||
if (newVal) {
|
||
initBackend()
|
||
// 锁定背景页面滚动
|
||
lockBodyScroll()
|
||
} else {
|
||
// 解锁背景页面滚动
|
||
unlockBodyScroll()
|
||
}
|
||
}, { immediate: true })
|
||
|
||
// 刷新引导列表
|
||
function refreshList() {
|
||
const rawList = getGuideStatusList()
|
||
guideList.value = rawList.map(item => {
|
||
const progress = getStepProgress(item.key)
|
||
const status = calculateStatus(item.key, item.done, item.claimed)
|
||
|
||
return {
|
||
...item,
|
||
progress,
|
||
status,
|
||
statusText: getStatusText(status, progress),
|
||
buttonText: getButtonText(status),
|
||
buttonType: getButtonType(status)
|
||
}
|
||
})
|
||
}
|
||
|
||
function calculateStatus(key, done, claimed) {
|
||
// 优先使用后端状态判断
|
||
const backendStage = backendStages.value.find(s =>
|
||
s.required_task_keys && s.required_task_keys.includes(key)
|
||
)
|
||
if (backendStage) {
|
||
// 后端状态为 in_progress 表示已领取
|
||
if (backendStage.status === 'in_progress') {
|
||
return 'reward_claimed'
|
||
}
|
||
// 后端状态为 completed 表示任务完成但未领取(可领取按钮)
|
||
if (backendStage.status === 'completed' || backendStage.all_tasks_completed) {
|
||
return 'completed'
|
||
}
|
||
// 后端状态为 locked 表示未开始
|
||
if (backendStage.status === 'locked') {
|
||
return 'not_started'
|
||
}
|
||
}
|
||
// 回退到本地存储判断
|
||
if (claimed) return 'reward_claimed'
|
||
if (done && !claimed) return 'completed'
|
||
if (hasGuideProgress(key)) return 'in_progress'
|
||
return 'not_started'
|
||
}
|
||
|
||
function getStatusText(status, progress) {
|
||
if (status === 'in_progress') {
|
||
return `${progress.completed}/${progress.total} 步骤`
|
||
}
|
||
const map = {
|
||
not_started: '未开始',
|
||
completed: '已完成',
|
||
reward_claimed: '已领取'
|
||
}
|
||
return map[status] || '未开始'
|
||
}
|
||
|
||
function getButtonText(status) {
|
||
const map = {
|
||
not_started: '开始',
|
||
in_progress: '继续',
|
||
completed: '领取奖励',
|
||
reward_claimed: '已领取'
|
||
}
|
||
return map[status] || '开始'
|
||
}
|
||
|
||
function getButtonType(status) {
|
||
const map = {
|
||
not_started: 'start',
|
||
in_progress: 'continue',
|
||
completed: 'claim',
|
||
reward_claimed: 'claimed'
|
||
}
|
||
return map[status] || 'start'
|
||
}
|
||
|
||
// 开始引导
|
||
function handleStartGuide(key) {
|
||
console.log('[GuideModal] handleStartGuide:', key)
|
||
clearSubStepProgress(key)
|
||
resetGuide(key)
|
||
const targetPage = getStepPage(key, 0)
|
||
|
||
if (targetPage) {
|
||
emit('close')
|
||
uni.navigateTo({
|
||
url: targetPage + '?guide_key=' + key + '&guide_step=0'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 继续引导
|
||
function handleContinueGuide(key) {
|
||
console.log('[GuideModal] handleContinueGuide:', key)
|
||
const resumeStep = getNextIncompleteStep(key)
|
||
const targetPage = getStepPage(key, resumeStep)
|
||
|
||
if (targetPage) {
|
||
emit('close')
|
||
uni.navigateTo({
|
||
url: targetPage + '?guide_key=' + key + '&guide_step=' + resumeStep
|
||
})
|
||
}
|
||
}
|
||
|
||
// 完成引导并同步后端
|
||
async function completeGuideAndSync(key) {
|
||
try {
|
||
markGuideDone(key)
|
||
await completeGuide(key, onboardingStages)
|
||
console.log('[GuideModal] completeGuideAndSync success:', key)
|
||
} catch (err) {
|
||
console.error('[GuideModal] completeGuideAndSync error:', err)
|
||
}
|
||
}
|
||
|
||
// 检查并同步所有完成的引导到后端
|
||
async function checkAndSyncCompletedGuides() {
|
||
const rawList = getGuideStatusList()
|
||
for (const item of rawList) {
|
||
if (item.done && !item.claimed) {
|
||
await completeGuideAndSync(item.key)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 领取奖励
|
||
async function handleClaimReward(key) {
|
||
try {
|
||
const config = getGuideConfig(key)
|
||
if (!config) return
|
||
|
||
// 从 onboardingStages 找到对应 stage
|
||
let stageNum = -1
|
||
for (const s of onboardingStages) {
|
||
if (s.required_task_keys.includes(key)) {
|
||
stageNum = s.stage
|
||
break
|
||
}
|
||
}
|
||
|
||
if (stageNum >= 0) {
|
||
const res = await claimOnboardingReward(stageNum)
|
||
// 更新本地存储的余额并通知 Header 组件
|
||
if (res.data?.crystal_balance !== undefined) {
|
||
try {
|
||
const userStr = uni.getStorageSync('user')
|
||
if (userStr) {
|
||
const user = JSON.parse(userStr)
|
||
user.crystal_balance = parseInt(res.data.crystal_balance)
|
||
uni.setStorageSync('user', JSON.stringify(user))
|
||
}
|
||
} catch (e) {
|
||
console.error('更新本地存储失败:', e)
|
||
}
|
||
uni.$emit('balanceUpdated', { crystal_balance: res.data.crystal_balance, experience: res.data.experience })
|
||
}
|
||
}
|
||
|
||
// 本地标记已领取
|
||
claimGuideReward(key)
|
||
refreshList()
|
||
emit('updated')
|
||
} catch (err) {
|
||
console.error('handleClaimReward error:', err)
|
||
uni.showToast({ title: '领取失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
// 初始化时同步后端
|
||
async function initBackend() {
|
||
try {
|
||
loading.value = true
|
||
|
||
const res = await getOnboardingStatus()
|
||
const data = res.data || {}
|
||
|
||
// 保存后端stages数据
|
||
backendStages.value = data.stages || []
|
||
|
||
// 从后端同步已完成状态到本地
|
||
if (data.stages && data.stages.length > 0) {
|
||
for (const stage of data.stages) {
|
||
// all_tasks_completed 是后端返回的字段名(下划线格式)
|
||
// 同时检查 status === 'completed' 作为备用判断
|
||
if ((stage.all_tasks_completed || stage.status === 'completed') && stage.required_task_keys) {
|
||
for (const taskKey of stage.required_task_keys) {
|
||
// 无论本地是否已标记,都同步后端状态
|
||
if (!isGuideDone(taskKey)) {
|
||
console.log('[GuideModal] 从后端同步任务完成状态:', taskKey)
|
||
markGuideDone(taskKey)
|
||
}
|
||
}
|
||
}
|
||
// status === 'in_progress' 表示已领取奖励
|
||
if (stage.status === 'in_progress' && stage.required_task_keys) {
|
||
for (const taskKey of stage.required_task_keys) {
|
||
markGuideRewardClaimed(taskKey)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
refreshList()
|
||
await checkAndSyncCompletedGuides()
|
||
} catch (err) {
|
||
console.error('initBackend error:', err)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 阻止移动事件
|
||
const handlePreventMove = (e) => {
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
return false
|
||
}
|
||
|
||
// 关闭弹窗
|
||
const handleClose = () => {
|
||
emit('close')
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.guide-modal-wrapper {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 9999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
touch-action: none;
|
||
-webkit-overflow-scrolling: auto;
|
||
}
|
||
|
||
.modal-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
z-index: 1;
|
||
touch-action: none;
|
||
-webkit-overflow-scrolling: auto;
|
||
overscroll-behavior: contain;
|
||
}
|
||
|
||
.modal-container {
|
||
position: relative;
|
||
width: 748rpx;
|
||
min-height: 66vh;
|
||
border-radius: 40rpx;
|
||
overflow: hidden;
|
||
z-index: 2;
|
||
touch-action: pan-y;
|
||
-webkit-overflow-scrolling: touch;
|
||
overscroll-behavior: contain;
|
||
}
|
||
|
||
.modal-background {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 1;
|
||
background: #00000021;
|
||
}
|
||
|
||
.modal-content {
|
||
position: relative;
|
||
z-index: 2;
|
||
padding: 64rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: 85vh;
|
||
overflow: hidden;
|
||
touch-action: pan-y;
|
||
}
|
||
|
||
/* 顶部区域 */
|
||
.modal-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
position: relative;
|
||
}
|
||
|
||
/* 返回按钮 */
|
||
.back-button {
|
||
position: absolute;
|
||
left: 0;
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 10;
|
||
}
|
||
|
||
.back-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
/* 标题 */
|
||
.modal-title {
|
||
flex: 1;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
text-align: center;
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
/* 列表区域 */
|
||
.modal-body {
|
||
flex: 1;
|
||
max-height: 800rpx;
|
||
overflow-y: auto;
|
||
margin-bottom: 20rpx;
|
||
touch-action: pan-y;
|
||
-webkit-overflow-scrolling: touch;
|
||
overscroll-behavior: contain;
|
||
}
|
||
|
||
.guide-item {
|
||
background: linear-gradient(45deg, rgba(255, 136, 109, 0.6) 0%, rgba(202, 88, 180, 0.6) 33%, rgba(235, 230, 178, 0.6) 100%);
|
||
border-radius: 24rpx;
|
||
padding: 16rpx 30rpx;
|
||
margin-bottom: 16rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.guide-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.guide-item.in-progress {
|
||
border-left: 4rpx solid #4a90e2;
|
||
}
|
||
|
||
.guide-item-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.guide-status-badge {
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 22rpx;
|
||
margin-right: 12rpx;
|
||
font-family: 'yt', sans-serif;
|
||
}
|
||
|
||
.guide-status-badge.not_started {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.4);
|
||
color: #fff;
|
||
}
|
||
|
||
.guide-status-badge.in_progress {
|
||
background: rgba(74, 144, 226, 0.6);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||
color: #fff;
|
||
}
|
||
|
||
.guide-status-badge.completed {
|
||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.6) 0%, rgba(60, 179, 113, 0.6) 100%);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||
color: #fff;
|
||
}
|
||
|
||
.guide-status-badge.reward_claimed {
|
||
background: rgba(150, 150, 150, 0.5);
|
||
border: 2rpx solid rgba(150, 150, 150, 0.6);
|
||
color: #fff;
|
||
}
|
||
|
||
.guide-name {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
flex: 1;
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.guide-item-body {
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.guide-desc {
|
||
font-size: 24rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.guide-progress {
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 8rpx;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
border-radius: 4rpx;
|
||
overflow: hidden;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 4rpx;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 22rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.guide-item-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.guide-btn {
|
||
min-width: 120rpx;
|
||
width: 120rpx;
|
||
height: 50rpx;
|
||
padding: 0;
|
||
color: #fff;
|
||
border-radius: 25rpx;
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
font-family: 'yt', sans-serif;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.start-btn {
|
||
background: linear-gradient(to bottom right, #F0E4B1 0%, #F08399 50%, #B94E73 100%);
|
||
box-shadow:
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4),
|
||
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.continue-btn {
|
||
background: linear-gradient(to bottom right, #F0E4B1 0%, #F08399 50%, #B94E73 100%);
|
||
box-shadow:
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4),
|
||
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.claim-btn {
|
||
background: linear-gradient(to bottom right, #F0E4B1 0%, #F08399 50%, #B94E73 100%);
|
||
box-shadow:
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4),
|
||
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.disabled-btn {
|
||
min-width: 120rpx;
|
||
width: 120rpx;
|
||
height: 50rpx;
|
||
background: rgba(150, 150, 150, 0.5);
|
||
border: 2rpx solid rgba(150, 150, 150, 0.6);
|
||
box-shadow: none;
|
||
color: #fff;
|
||
}
|
||
|
||
/* 底部统计 */
|
||
.modal-footer {
|
||
padding: 24rpx 0 0;
|
||
border-top: 2rpx solid rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.footer-stats {
|
||
font-size: 28rpx;
|
||
color: #fff;
|
||
text-align: center;
|
||
margin-bottom: 8rpx;
|
||
font-weight: bold;
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.footer-claim-tip {
|
||
font-size: 26rpx;
|
||
color: #FFD700;
|
||
text-align: center;
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
</style>
|