topfans/frontend/pages/tasks/daily-tasks.vue

977 lines
22 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 v-if="visible" class="modal-wrapper" @touchmove.stop.prevent="handlePreventMove" @click.stop>
<transition name="fade">
<view v-if="visible" class="modal-mask" @touchstart.stop="handleMaskTouchStart" @touchmove.stop.prevent="handlePreventMove" @click.stop>
</view>
</transition>
<transition name="scale">
<view v-if="visible" class="modal-container" @click.stop @touchmove.stop="handleContainerMove">
<!-- 背景图片 -->
<image class="modal-background" src="/static/nft/beijingban.png" mode="aspectFill"></image>
<!-- 蒙层 -->
<view class="modal-overlay"></view>
<!-- 内容区域 -->
<view class="modal-content" @touchstart.stop @touchmove.stop @touchend.stop @click.stop>
<!-- 顶部区域返回按钮和Tab -->
<view class="top-bar">
<!-- 返回按钮 -->
<view class="back-button" @touchstart.stop="handleCloseTouchStart" @touchend.stop="handleCloseTouchEnd" @click="handleCloseClick">
<image class="back-icon" src="/static/icon/back.png" mode="aspectFit" />
</view>
<!-- Tab 切换 -->
<view class="tab-bar">
<view class="tab-item active">
<text class="tab-text">每日任务</text>
</view>
<view class="tab-item">
<text class="tab-text">每周任务</text>
</view>
</view>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 错误状态 -->
<view v-else-if="errorMessage" class="error-state">
<text class="error-text">{{ errorMessage }}</text>
<button class="retry-btn" @click="loadTasks">重试</button>
</view>
<!-- 任务列表 -->
<scroll-view v-else class="task-list" scroll-y :show-scrollbar="false">
<!-- 空状态 -->
<view v-if="sortedTasks.length === 0" class="empty-state">
<text class="empty-text">暂无每日任务</text>
</view>
<!-- 任务项 -->
<view v-for="task in sortedTasks" :key="task.task_key" class="task-item">
<view class="task-info">
<text class="task-name">{{ task.name }}</text>
<text class="task-progress" v-if="task.current_count !== undefined">{{ task.current_count }}/{{ task.target_count }}</text>
</view>
<view class="task-right">
<!-- 礼盒图标 - 点击展开/收起 -->
<view class="reward-gift" @click.stop="toggleReward(task.task_key)">
<image
class="gift-icon"
:class="{ 'gift-open': expandedTaskKey === task.task_key }"
:src="expandedTaskKey === task.task_key ? '/static/nft/lihe_kaiqi.png' : '/static/nft/lihe.png'"
mode="aspectFit"
></image>
<!-- 奖励详情弹出 -->
<view v-if="expandedTaskKey === task.task_key" class="reward-popup">
<view class="reward-item">
<image class="reward-icon-img" src="/static/icon/crystal.png" mode="aspectFit"></image>
<text class="reward-value">{{ task.crystal_reward }}</text>
</view>
<view class="reward-item">
<image class="reward-icon-img" src="/static/nft/jingyanzhi.png" mode="aspectFit"></image>
<text class="reward-value">{{ task.exp_reward }}</text>
</view>
<view class="reward-item">
<image class="reward-icon-img" src="/static/nft/huoyuezhitubiao.png" mode="aspectFit"></image>
<text class="reward-value">{{ task.activity_reward || 10 }}</text>
</view>
</view>
</view>
<!-- 状态按钮/标签 -->
<button
class="claim-btn"
:class="getStatusClass(task.status)"
:loading="claimingTask === task.task_key"
:disabled="!task.can_claim"
@click="task.can_claim && handleClaim(task)"
>
{{ task.can_claim ? '待领取' : getStatusText(task.status) }}
</button>
</view>
</view>
</scroll-view>
<!-- 底部进度区域和一键领取 -->
<view v-if="!loading && !errorMessage" class="bottom-section">
<!-- 进度条 -->
<view class="progress-section">
<view class="progress-bar">
<view
v-for="(milestone, index) in milestones"
:key="index"
class="progress-node"
:class="{ 'active': index < completedCount }"
>
<!-- 奖励图标 -->
<image
class="milestone-icon"
:src="[20, 40, 80].includes(milestone.value) ? '/static/icon/crystal.png' : '/static/nft/lihe.png'"
mode="aspectFit"
></image>
<!-- 进度节点 -->
<image
class="node-circle"
:class="{ 'node-inactive': index >= completedCount }"
src="/static/nft/huoyuezhi_jiedian.png"
mode="aspectFit"
></image>
<text class="node-label">{{ milestone.value }}</text>
</view>
</view>
</view>
<!-- 一键领取按钮 -->
<view class="claim-all-bar">
<button
class="claim-all-btn"
:class="{ 'disabled': !hasClaimableTasks }"
:loading="claimingAll"
:disabled="!hasClaimableTasks"
@click="handleClaimAll"
>
一键领取 ({{ claimableCount }})
</button>
</view>
</view>
</view>
</view>
</transition>
</view>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { getDailyTasks, claimDailyTask, claimAllDailyTasks } from '@/utils/task-api.js'
const props = defineProps({
visible: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['close', 'updated'])
const loading = ref(true)
const errorMessage = ref('')
const tasks = ref([])
const claimingTask = ref('')
const claimingAll = ref(false)
const starId = ref(1)
const expandedTaskKey = ref('') // 记录当前展开的任务
// 排序后的任务列表:待领取 > 进行中 > 已完成
const sortedTasks = computed(() => {
const tasksCopy = [...tasks.value]
return tasksCopy.sort((a, b) => {
// 定义优先级can_claim(待领取) > pending(进行中) > claimed(已完成)
const getPriority = (task) => {
if (task.can_claim) return 1
if (task.status === 'pending') return 2
if (task.status === 'claimed') return 3
return 4
}
return getPriority(a) - getPriority(b)
})
})
const hasClaimableTasks = computed(() => tasks.value.some(t => t.can_claim))
const claimableCount = computed(() => tasks.value.filter(t => t.can_claim).length)
// 切换奖励展开/收起
function toggleReward(taskKey) {
if (expandedTaskKey.value === taskKey) {
expandedTaskKey.value = ''
} else {
expandedTaskKey.value = taskKey
}
}
// 进度里程碑
const milestones = ref([
{ value: 20 },
{ value: 40 },
{ value: 60 },
{ value: 80 },
{ value: 100 }
])
// 已完成的里程碑数量
const completedCount = computed(() => {
// 计算已领取的任务数量
return tasks.value.filter(t => t.status === 'claimed').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) {
loadTasks()
// 锁定背景页面滚动
lockBodyScroll()
} else {
// 解锁背景页面滚动
unlockBodyScroll()
}
}, { immediate: true })
function getStatusClass(status) {
switch (status) {
case 'pending': return 'status-pending'
case 'completed': return 'status-completed'
case 'claimed': return 'status-claimed'
default: return ''
}
}
function getStatusText(status) {
switch (status) {
case 'pending': return '未完成'
case 'completed': return '可领取'
case 'claimed': return '已完成'
default: return status
}
}
async function loadTasks() {
try {
loading.value = true
errorMessage.value = ''
starId.value = uni.getStorageSync('star_id')
const res = await getDailyTasks(starId.value)
tasks.value = res.data?.tasks || []
} catch (err) {
console.error('loadTasks error:', err)
errorMessage.value = err.message || '加载失败'
} finally {
loading.value = false
}
}
async function handleClaim(task) {
if (claimingTask.value) return
try {
claimingTask.value = task.task_key
const res = await claimDailyTask(task.task_key, starId.value)
// 直接更新本地状态,而不是重新加载整个列表
const index = tasks.value.findIndex(t => t.task_key === task.task_key)
if (index !== -1) {
tasks.value[index].status = 'claimed'
tasks.value[index].can_claim = false
}
emit('updated')
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
uni.showToast({ title: '领取成功', icon: 'success' })
} catch (err) {
console.error('handleClaim error:', err)
uni.showToast({ title: err.message || '领取失败', icon: 'none' })
} finally {
claimingTask.value = ''
}
}
async function handleClaimAll() {
if (claimingAll.value) return
try {
claimingAll.value = true
const res = await claimAllDailyTasks(starId.value)
// 直接更新所有已领取的任务为 claimed 状态
tasks.value.forEach(task => {
if (task.can_claim) {
task.status = 'claimed'
task.can_claim = false
}
})
emit('updated')
uni.$emit('balanceUpdated', { crystal_balance: res.data?.crystal_balance, experience: res.data?.experience })
uni.showToast({ title: '领取成功', icon: 'success' })
} catch (err) {
console.error('handleClaimAll error:', err)
uni.showToast({ title: err.message || '领取失败', icon: 'none' })
} finally {
claimingAll.value = false
}
}
// 遮罩层触摸处理
const handleMaskTouchStart = (e) => {
e.preventDefault()
e.stopPropagation()
}
// 阻止移动事件(完全阻止背景滚动)
const handlePreventMove = (e) => {
e.preventDefault()
e.stopPropagation()
return false
}
// 容器移动处理(允许内部滚动)
const handleContainerMove = (e) => {
e.stopPropagation()
}
// 关闭按钮触摸处理
let closeTouchStartTime = 0
let closeTouchLocked = false
const handleCloseTouchStart = (e) => {
closeTouchStartTime = Date.now()
closeTouchLocked = false
}
const handleCloseTouchEnd = (e) => {
if (closeTouchLocked) return
closeTouchLocked = true
const touchDuration = Date.now() - closeTouchStartTime
if (touchDuration < 300) {
e.preventDefault()
e.stopPropagation()
emit('close')
}
}
const handleCloseClick = (e) => {
e.preventDefault()
e.stopPropagation()
emit('close')
}
</script>
<style scoped>
.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;
max-height: 85vh;
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;
}
.close-button {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.close-icon {
font-size: 50rpx;
color: #e6e6e6;
line-height: 1;
font-weight: 300;
}
.modal-content {
position: relative;
z-index: 2;
padding: 64rpx;
display: flex;
flex-direction: column;
max-height: 85vh;
overflow: hidden;
touch-action: pan-y;
}
/* 顶部区域 */
.top-bar {
display: flex;
align-items: center;
margin-bottom: 30rpx;
position: relative;
}
/* 返回按钮 */
.back-button {
position: absolute;
left: 0;
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.back-icon {
width: 64rpx;
height: 64rpx;
}
/* Tab 样式 */
.tab-bar {
display: flex;
gap: 20rpx;
justify-content: center;
flex: 1;
}
.tab-item {
padding: 24rpx 32rpx;
border-radius: 50rpx;
background-image: url('/static/nft/dingbutubiao_an.png');
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
border: none;
}
.tab-item.active {
background-image: url('/static/nft/dingbutubiao_liang.png');
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
border: none;
box-shadow: 0 8rpx 20rpx rgba(240, 131, 153, 0.3);
}
.tab-text {
font-size: 24rpx;
font-weight: bold;
color: #fff;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.4);
}
.loading-state,
.error-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
border-top-color: #F08399;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text,
.error-text {
color: #e6e6e6;
font-size: 28rpx;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
}
.retry-btn {
margin-top: 20rpx;
padding: 16rpx 40rpx;
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
color: #fff;
border-radius: 50rpx;
font-size: 28rpx;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 0;
}
.empty-text {
color: #e6e6e6;
font-size: 28rpx;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
}
.task-list {
max-height: 448rpx;
margin-bottom: 30rpx;
touch-action: pan-y;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
.task-list .task-item {
margin-bottom: 16rpx;
}
.task-list .task-item:last-child {
margin-bottom: 0;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 30rpx;
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%);
/* backdrop-filter: blur(10rpx); */
border-radius: 24rpx;
/* border: 2rpx solid rgba(255, 255, 255, 0.3); */
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.task-info {
flex: 1;
display: flex;
align-items: center;
gap: 8rpx;
}
.task-name {
font-size: 28rpx;
font-weight: bold;
color: #fff;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.task-progress {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.task-right {
display: flex;
align-items: center;
gap: 12rpx;
}
.reward-gift {
position: relative;
display: flex;
align-items: center;
cursor: pointer;
}
.gift-icon {
width: 50rpx;
height: 50rpx;
transition: transform 0.3s ease;
}
.gift-icon.gift-open {
transform: scale(1.1);
}
.reward-popup {
position: absolute;
right: 60rpx;
top: 50%;
transform: translateY(-50%);
display: flex;
gap: 12rpx;
padding: 12rpx 20rpx;
background: linear-gradient(135deg, rgba(255, 182, 193, 0.95) 0%, rgba(173, 216, 230, 0.95) 100%);
backdrop-filter: blur(10rpx);
border-radius: 30rpx;
border: 2rpx solid rgba(255, 255, 255, 0.5);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
z-index: 10;
animation: popupShow 0.3s ease;
}
@keyframes popupShow {
from {
opacity: 0;
transform: translateY(-50%) scale(0.8);
}
to {
opacity: 1;
transform: translateY(-50%) scale(1);
}
}
.reward-item {
display: flex;
align-items: center;
gap: 4rpx;
}
.reward-icon-img {
width: 32rpx;
height: 32rpx;
}
.reward-value {
font-size: 22rpx;
color: #FFD700;
font-weight: bold;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
min-width: 30rpx;
}
.status-badge {
min-width: 100rpx;
height: 50rpx;
border-radius: 25rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 20rpx;
}
.status-badge.status-pending {
background: rgba(255, 255, 255, 0.3);
border: 2rpx solid rgba(255, 255, 255, 0.4);
}
.status-badge.status-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);
}
.status-badge.status-claimed {
background: rgba(255, 255, 255, 0.2);
border: 2rpx solid rgba(255, 255, 255, 0.3);
}
.status-text {
font-size: 24rpx;
color: #fff;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.claim-btn {
min-width: 120rpx;
width: 120rpx;
height: 50rpx;
border-radius: 25rpx;
/* 渐变:左浅橙粉 → 右柔粉红 */
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);
color: #fff;
font-size: 24rpx;
font-weight: bold;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
display: flex;
align-items: center;
justify-content: center;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
padding: 0;
}
.claim-btn.status-pending {
background: rgba(255, 255, 255, 0.3);
border: 2rpx solid rgba(255, 255, 255, 0.4);
box-shadow: none;
color: #fff;
}
.claim-btn.status-claimed {
background: rgba(150, 150, 150, 0.5);
border: 2rpx solid rgba(150, 150, 150, 0.6);
box-shadow: none;
color: #fff;
}
.bottom-section {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-top: 20rpx;
}
.progress-section {
width: 100%;
padding: 30rpx 20rpx;
/* backdrop-filter: blur(10rpx); */
}
.progress-bar {
display: flex;
justify-content: space-evenly;
align-items: flex-end;
position: relative;
padding: 0 10rpx;
}
.progress-bar::before {
content: '';
position: absolute;
bottom: 40rpx;
left: 0;
right: 10%;
height: 32rpx;
background-image: url('/static/nft/huoyuezhi_changtiao.png');
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
z-index: 0;
}
.progress-node {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index: 1;
gap: 8rpx;
/* flex: 1; */
}
.milestone-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 8rpx;
}
.node-circle {
width: 50rpx;
height: 50rpx;
transition: all 0.3s ease;
}
.node-circle.node-inactive {
filter: grayscale(100%) brightness(0.6);
/* opacity: 0.5; */
}
.progress-node.active .milestone-icon {
filter: drop-shadow(0 4rpx 12rpx rgba(240, 131, 153, 0.6));
}
.node-label {
font-size: 22rpx;
color: #fff;
font-weight: bold;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.claim-all-bar {
margin-top: 10rpx;
}
.claim-all-btn {
width: 100%;
padding: 24rpx 0;
background: linear-gradient(135deg, rgba(240, 228, 177, 0.8) 0%, rgba(240, 131, 153, 0.8) 50%, rgba(185, 78, 115, 0.8) 100%);
/* backdrop-filter: blur(10rpx); */
/* border: 2rpx solid rgba(255, 255, 255, 0.5); */
color: #fff;
border-radius: 50rpx;
font-size: 32rpx;
font-weight: bold;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
box-shadow: 0 8rpx 20rpx rgba(240, 131, 153, 0.4);
}
.claim-all-btn.disabled {
background: rgba(150, 150, 150, 0.5);
border: 2rpx solid rgba(150, 150, 150, 0.6);
box-shadow: none;
color: #fff;
}
/* 动画效果 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.scale-enter-active,
.scale-leave-active {
transition: all 0.3s ease;
}
.scale-enter-from,
.scale-leave-to {
opacity: 0;
transform: scale(0.8);
}
</style>