Files
simple-memo-wx/pages/growth/rules.vue
T

1351 lines
28 KiB
Vue

<template>
<view class="rules-page theme-transition" :style="{ backgroundColor: themeConfig.bgColor }">
<scroll-view class="rules-content" scroll-y>
<view class="page-header" :style="{ background: `linear-gradient(135deg, ${themeConfig.primaryColor} 0%, ${themeConfig.secondaryColor} 100%)` }">
<text class="page-title">{{ pageData.title || '成长规则' }}</text>
<text class="page-subtitle" v-if="pageData.subtitle">{{ pageData.subtitle }}</text>
</view>
<view class="content-wrapper">
<view v-for="(section, index) in pageData.sections" :key="index" class="section-card" :style="{ backgroundColor: themeConfig.cardBgColor }">
<view class="section-header" v-if="section.title">
<view class="section-icon-wrap" :style="{ background: `linear-gradient(135deg, ${themeConfig.primaryColor}15 0%, ${themeConfig.secondaryColor}10 100%)` }">
<FaIcon :icon="section.iconName || getSectionIcon(section.type)" :size="22" :color="themeConfig.primaryColor" />
</view>
<text class="section-title">{{ section.title }}</text>
</view>
<view class="section-content" v-if="section.content">
<text class="content-text">{{ section.content }}</text>
</view>
<!-- 等级系统 -->
<view v-if="section.type === 'level_system' && section.levels" class="level-section">
<view v-for="(level, lIndex) in section.levels" :key="lIndex" class="level-item">
<view class="level-badge-wrap">
<view class="level-badge" :style="{ background: `linear-gradient(135deg, ${level.color}, ${level.color}90)` }">
<FaIcon :icon="level.iconName || 'award'" :size="22" color="#fff" />
</view>
<text class="level-number">Lv.{{ level.level }}</text>
</view>
<view class="level-info">
<text class="level-title">{{ level.title }}</text>
<view class="level-privileges" v-if="level.privileges && level.privileges.length">
<text class="privilege-item" v-for="(priv, pIndex) in level.privileges" :key="pIndex">{{ priv }}</text>
</view>
<view class="level-progress">
<view class="progress-track" :style="{ backgroundColor: themeConfig.borderColor }">
<view class="progress-fill" :style="{
width: Math.min(100, (lIndex / (section.levels.length - 1)) * 100) + '%',
background: `linear-gradient(90deg, ${level.color}, ${level.color}70)`
}"></view>
</view>
</view>
</view>
<text class="level-exp" :style="{ color: level.color }">{{ level.exp }} EXP</text>
</view>
</view>
<!-- 经验规则 -->
<view v-if="section.type === 'exp_rules'" class="exp-section">
<view class="exp-limit" v-if="section.limit" :style="{ background: `linear-gradient(135deg, ${themeConfig.primaryColor}15 0%, ${themeConfig.secondaryColor}10 100%)` }">
<FaIcon icon="star" :size="18" :color="themeConfig.warningColor || '#ffaa33'" />
<text class="limit-text">{{ section.limit }}</text>
</view>
<view class="exp-rules-list">
<view v-for="(rule, rIndex) in (section.rules || [])" :key="rIndex" class="exp-rule-card">
<view class="rule-icon-box" :style="{ background: getExpRuleBg(rIndex) }">
<FaIcon :icon="getExpRuleIcon(rIndex)" :size="22" :color="themeConfig.textPrimary" />
</view>
<view class="rule-content">
<view class="rule-header">
<text class="rule-title">{{ rule.desc }}</text>
<text class="rule-exp" v-if="rule.exp" :style="{ color: themeConfig.primaryColor }">+{{ rule.exp }} EXP</text>
</view>
<text class="rule-detail">{{ rule.detail }}</text>
</view>
</view>
</view>
</view>
<!-- 连续打卡加成 -->
<view v-if="section.type === 'streak_bonus'" class="streak-section">
<view v-if="section.formula" class="streak-formula">
<text class="formula-text">{{ section.formula }}</text>
</view>
<view class="streak-rules">
<view v-for="(streak, sIndex) in (section.rules || [])" :key="sIndex" class="streak-card">
<view class="streak-line">
<view class="line-dot" :style="{ background: streak.color }"></view>
<text class="streak-range">{{ streak.range }}</text>
</view>
<text class="streak-bonus" :style="{ color: streak.color }">{{ streak.bonus }}</text>
</view>
</view>
<view v-if="section.tip" class="streak-tip">
<text class="tip-text">{{ section.tip }}</text>
</view>
</view>
<!-- 新手保护 -->
<view v-if="section.type === 'protection'" class="protection-section">
<view class="protection-main">
<text class="main-title">{{ section.content }}</text>
<view class="bonus-tag" :style="{ background: `linear-gradient(135deg, ${themeConfig.primaryColor}20, ${themeConfig.secondaryColor}10)` }">
<text class="tag-text" :style="{ color: themeConfig.primaryColor }">{{ section.bonus }}</text>
</view>
</view>
<view class="protection-detail">
<text class="detail-text">{{ section.detail }}</text>
</view>
<view class="protection-example">
<text class="example-text">{{ section.example }}</text>
</view>
</view>
<!-- 成就系统 -->
<view v-if="section.type === 'achievement_system'" class="achievement-section">
<view v-if="section.content" class="achievement-desc">
<text class="desc-text">{{ section.content }}</text>
</view>
<view v-if="section.rarityDesc" class="rarity-info">
<text class="rarity-text">{{ section.rarityDesc }}</text>
</view>
<view class="achievement-toggle" @click="toggleAchievements">
<view class="toggle-left">
<FaIcon icon="list" :size="18" :color="themeConfig.textSecondary" />
<text class="toggle-text">查看所有成就</text>
</view>
<text class="toggle-arrow" :class="{ expanded: showAchievements }"></text>
</view>
<view v-if="showAchievements" class="achievement-content">
<view class="category-tabs">
<view v-for="(cat, cIndex) in ['all', 'streak', 'task', 'mood', 'bill', 'review']" :key="cIndex"
class="category-tab"
:class="{ active: currentCategory === cat }"
@click="currentCategory = cat">
<text class="tab-emoji">{{ getCategoryEmoji(cat) }}</text>
<text class="tab-name">{{ getCatName(cat) }}</text>
</view>
</view>
<view class="achievement-grid">
<view v-for="(achievement, aIndex) in getFilteredAchievements(section)" :key="achievement.id || aIndex" :class="['achievement-card', achievement.rarity && achievement.rarity.toLowerCase()]">
<view class="card-icon-wrap">
<view class="card-icon" :style="{ background: getCategoryColor(achievement.category) }">
<text class="card-emoji">{{ achievement.icon }}</text>
</view>
<view class="card-rarity-flag" v-if="achievement.rarity" :class="['rarity-flag', achievement.rarity.toLowerCase()]">
<text>{{ achievement.rarity }}</text>
</view>
</view>
<text class="card-desc">{{ achievement.desc || achievement.description }}</text>
<view class="card-footer">
<view class="cat-tag" :style="{ background: getCategoryBg(achievement.category), color: getCategoryColor(achievement.category) }">
<text class="cat-tag-text">{{ getCategoryName(achievement.category) }}</text>
</view>
<text class="exp-text">+{{ achievement.exp || achievement.expReward }} EXP</text>
</view>
</view>
</view>
</view>
</view>
<!-- 特权列表 -->
<view v-if="section.type === 'privileges' && section.privileges" class="privileges-section">
<view v-for="(privilege, pIndex) in section.privileges" :key="pIndex" class="privilege-card">
<FaIcon :icon="privilege.iconName || 'wand-magic-sparkles'" :size="22" :color="themeConfig.primaryColor" />
<view class="privilege-info">
<text class="privilege-name">{{ privilege.name || privilege.title }}</text>
<text class="privilege-desc" v-if="privilege.desc || privilege.description">{{ privilege.desc || privilege.description }}</text>
</view>
</view>
</view>
<!-- 技巧提示 -->
<view v-if="section.type === 'tips' && section.tips" class="tips-section">
<view v-for="(tip, tIndex) in section.tips" :key="tIndex" class="tip-card">
<view class="tip-icon-wrap" :style="{ background: `linear-gradient(135deg, ${getTipColor(tIndex)}30, ${getTipColor(tIndex)}15)` }">
<FaIcon icon="star" :size="18" :color="getTipColor(tIndex)" />
</view>
<view class="tip-content">
<text class="tip-text">{{ tip.content || tip.text || tip }}</text>
</view>
</view>
</view>
<!-- FAQ问答 -->
<view v-if="section.type === 'faq' && section.questions" class="faq-section">
<view v-for="(qa, qIndex) in section.questions" :key="qIndex" class="faq-card">
<view class="faq-item" @click="toggleFaq(qIndex)">
<view class="faq-q">
<FaIcon :icon="expandedFaq === qIndex ? 'thumbtack' : 'circle-question'" :size="18" :color="expandedFaq === qIndex ? themeConfig.primaryColor : themeConfig.textSecondary" />
<text class="faq-q-text">{{ qa.Q || qa.q || qa.question }}</text>
</view>
<text class="faq-arrow" :class="{ expanded: expandedFaq === qIndex }"></text>
</view>
<view class="faq-a" :class="{ expanded: expandedFaq === qIndex }">
<view class="answer-content">
<text class="faq-a-text">{{ qa.A || qa.a || qa.answer }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="safe-area-bottom"></view>
</scroll-view>
</view>
</template>
<script>
import { getThemeConfigFromStorage, THEME_PRESETS } from '@/utils/theme'
import { refreshNavBarTheme, forceApplyTabBarTheme } from '@/utils/themeGlobal'
import { getGrowthRules } from '@/api/index'
import FaIcon from '@/components/FaIcon.vue'
export default {
components: { FaIcon },
data() {
return {
themeConfig: getThemeConfigFromStorage() || THEME_PRESETS['default'],
pageData: {
title: '成长规则',
subtitle: '',
sections: []
},
currentCategory: 'all',
showAchievements: false,
expandedFaq: null
}
},
onLoad() {
const latestTheme = getThemeConfigFromStorage() || THEME_PRESETS['default']
if (JSON.stringify(latestTheme) !== JSON.stringify(this.themeConfig)) {
this.themeConfig = latestTheme
}
this.loadRules()
},
onShow() {
const latestTheme = getThemeConfigFromStorage() || THEME_PRESETS['default']
if (JSON.stringify(latestTheme) !== JSON.stringify(this.themeConfig)) {
this.themeConfig = latestTheme
}
refreshNavBarTheme()
setTimeout(() => {
forceApplyTabBarTheme(latestTheme)
}, 100)
},
methods: {
async loadRules() {
try {
const res = await getGrowthRules()
if (res) {
this.pageData = {
title: res.title || '成长规则',
subtitle: res.subtitle || '',
sections: res.sections || []
}
}
} catch (e) {
console.log('加载规则失败:', e)
}
},
getSectionIcon(type) {
const iconMap = {
'level_system': 'arrow-up',
'exp_rules': 'wand-magic-sparkles',
'streak_bonus': 'fire',
'protection': 'shield-halved',
'achievement_system': 'medal',
'privileges': 'crown',
'tips': 'lightbulb',
'faq': 'circle-question'
}
return iconMap[type] || 'list'
},
getTipColor(index) {
const colors = ['#4cb8c6', '#667eea', '#764ba2', '#ff9f43', '#10b981', '#f59e0b']
return colors[index % colors.length]
},
getExpRuleBg(index) {
const colors = [
'linear-gradient(135deg, #667eea20 0%, #764ba215 100%)',
'linear-gradient(135deg, #f59e0b20 0%, #d9770615 100%)',
'linear-gradient(135deg, #ec489920 0%, #be185d15 100%)'
]
return colors[index % colors.length]
},
getExpRuleIcon(index) {
const icons = ['book-open', 'money-bill', 'face-smile']
return icons[index % icons.length]
},
getCategoryColor(category) {
const colors = {
'streak': 'linear-gradient(135deg, #F59E0B 0%, #D97706 100%)',
'task': 'linear-gradient(135deg, #9B8AFB 0%, #7B6CEB 100%)',
'mood': 'linear-gradient(135deg, #EC4899 0%, #BE185D 100%)',
'bill': 'linear-gradient(135deg, #4ECDC4 0%, #3BA9A1 100%)',
'review': 'linear-gradient(135deg, #7B6CEB 0%, #5A4BD1 100%)'
}
return colors[category] || colors['task']
},
getCategoryBg(category) {
const colors = {
'streak': 'rgba(245, 158, 11, 0.15)',
'task': 'rgba(155, 138, 251, 0.15)',
'mood': 'rgba(236, 72, 153, 0.15)',
'bill': 'rgba(78, 205, 196, 0.15)',
'review': 'rgba(123, 108, 235, 0.15)'
}
return colors[category] || colors['task']
},
getCategoryName(category) {
const names = {
'streak': '坚持',
'task': '任务',
'mood': '心情',
'bill': '账单',
'review': '复盘'
}
return names[category] || '其他'
},
getFilteredAchievements(section) {
const list = section.achievements || section.data || []
if (this.currentCategory === 'all') {
return list
}
return list.filter(item => item.category === this.currentCategory)
},
getCategoryIcon(category) {
// 兼容旧调用:返回 emoji 字符替代 FaIcon
return this.getCategoryEmoji(category)
},
getCategoryEmoji(category) {
const emojis = {
'all': '🏆',
'streak': '🔥',
'task': '📋',
'mood': '🌈',
'bill': '💰',
'review': '✨'
}
return emojis[category] || '🏆'
},
getCatName(category) {
const names = {
'all': '全部',
'streak': '坚持',
'task': '任务',
'mood': '心情',
'bill': '账单',
'review': '复盘'
}
return names[category] || '全部'
},
toggleAchievements() {
this.showAchievements = !this.showAchievements
},
toggleFaq(index) {
this.expandedFaq = this.expandedFaq === index ? null : index
}
}
}
</script>
<style lang="scss" scoped>
.rules-page {
min-height: 100vh;
padding: 20rpx 24rpx;
padding-bottom: 40px;
box-sizing: border-box;
position: relative;
}
.theme-transition {
transition: background-color 0.3s ease;
}
.rules-content {
height: 100vh;
}
.page-header {
padding: 60rpx 24rpx 48rpx;
text-align: center;
border-radius: 0 0 20rpx 20rpx;
margin-bottom: 20rpx;
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 50%);
pointer-events: none;
}
.page-title {
font-size: 42rpx;
font-weight: 700;
color: #fff;
display: block;
margin-bottom: 12rpx;
letter-spacing: 2rpx;
}
.page-subtitle {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.85);
display: block;
}
.section-card {
border-radius: 14rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
}
.section-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 16rpx;
}
.section-icon-wrap {
width: 56rpx;
height: 56rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.section-icon {
display: flex;
align-items: center;
justify-content: center;
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #2c3e50;
line-height: 1.3;
}
.section-content {
padding: 12rpx 16rpx;
background: rgba(0, 0, 0, 0.025);
border-radius: 12rpx;
margin-bottom: 16rpx;
}
.content-text {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
/* 等级系统 */
.level-section {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.level-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 18rpx 20rpx;
background: rgba(0, 0, 0, 0.025);
border-radius: 14rpx;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.level-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transition: left 0.5s ease;
}
.level-item:active::before {
left: 100%;
}
.level-item:active {
transform: scale(0.99);
background: rgba(0, 0, 0, 0.05);
}
.level-badge-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
width: 90rpx;
}
.level-badge {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.12);
position: relative;
}
.level-badge::before {
content: '';
position: absolute;
top: -3rpx;
left: -3rpx;
right: -3rpx;
bottom: -3rpx;
border-radius: 18rpx;
background: linear-gradient(135deg, rgba(255,255,255,0.35), transparent);
}
.level-icon {
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
}
.level-number {
font-size: 20rpx;
font-weight: 600;
color: #666;
}
.level-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.level-title {
font-size: 26rpx;
font-weight: 600;
}
.level-progress {
width: 100%;
}
.progress-track {
height: 6rpx;
border-radius: 100rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
border-radius: 100rpx;
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50%;
background: linear-gradient(to bottom, rgba(255,255,255,0.25), transparent);
border-radius: 100rpx 100rpx 0 0;
}
.level-exp {
font-size: 28rpx;
font-weight: 700;
width: 120rpx;
text-align: right;
}
.level-privileges {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-top: 6rpx;
}
.privilege-item {
font-size: 20rpx;
padding: 4rpx 10rpx;
background: rgba(0, 0, 0, 0.05);
border-radius: 8rpx;
color: #666;
}
.level-name {
display: block;
font-size: 26rpx;
font-weight: 600;
margin-bottom: 6rpx;
}
/* 经验规则 */
.exp-section {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.exp-limit {
display: flex;
align-items: center;
gap: 12rpx;
padding: 14rpx 18rpx;
border-radius: 12rpx;
}
.limit-icon {
display: flex;
align-items: center;
justify-content: center;
}
.limit-text {
font-size: 24rpx;
font-weight: 600;
color: #666;
}
.exp-rules-list {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.exp-rule-card {
display: flex;
align-items: flex-start;
gap: 14rpx;
padding: 16rpx 18rpx;
background: rgba(0, 0, 0, 0.025);
border-radius: 14rpx;
}
.rule-icon-box {
width: 56rpx;
height: 56rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.rule-icon {
display: flex;
align-items: center;
justify-content: center;
}
.rule-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.rule-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.rule-title {
font-size: 26rpx;
font-weight: 600;
}
.rule-exp {
font-size: 24rpx;
font-weight: 700;
}
.rule-detail {
font-size: 22rpx;
color: #999;
line-height: 1.6;
}
.rule-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.rule-name {
font-size: 26rpx;
font-weight: 600;
}
.rule-reward {
font-size: 24rpx;
font-weight: 700;
}
.rule-desc {
font-size: 22rpx;
color: #999;
line-height: 1.5;
}
/* 连续打卡加成 */
.streak-section {
display: flex;
flex-direction: column;
gap: 14rpx;
}
.streak-formula {
padding: 14rpx 18rpx;
background: rgba(0, 0, 0, 0.025);
border-radius: 12rpx;
}
.formula-text {
font-size: 24rpx;
font-weight: 600;
color: #666;
}
.streak-rules {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.streak-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14rpx 18rpx;
background: rgba(0, 0, 0, 0.025);
border-radius: 12rpx;
}
.streak-line {
display: flex;
align-items: center;
gap: 12rpx;
}
.line-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
}
.streak-range {
font-size: 24rpx;
color: #666;
}
.streak-bonus {
font-size: 24rpx;
font-weight: 600;
}
.streak-tip {
padding: 16rpx 20rpx;
background: rgba(255, 193, 7, 0.1);
border-radius: 12rpx;
border-left: 4rpx solid #ffc107;
}
.tip-text {
font-size: 22rpx;
color: #856404;
line-height: 1.6;
}
/* 新手保护 */
.protection-section {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.protection-main {
display: flex;
flex-direction: column;
gap: 10rpx;
padding: 18rpx;
background: rgba(0, 0, 0, 0.025);
border-radius: 14rpx;
}
.main-title {
font-size: 26rpx;
font-weight: 600;
}
.bonus-tag {
display: inline-flex;
padding: 6rpx 14rpx;
border-radius: 10rpx;
width: fit-content;
}
.tag-text {
font-size: 22rpx;
font-weight: 600;
}
.protection-detail {
padding: 14rpx 18rpx;
background: #fff;
border-radius: 12rpx;
}
.detail-text {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
.protection-example {
padding: 14rpx 18rpx;
background: rgba(255, 235, 59, 0.08);
border-radius: 12rpx;
border-left: 4rpx solid #ffc107;
}
.example-text {
font-size: 22rpx;
color: #856404;
line-height: 1.6;
}
.protection-name {
font-size: 26rpx;
font-weight: 600;
}
.protection-desc {
font-size: 22rpx;
color: #999;
line-height: 1.5;
}
.protection-highlight {
font-size: 24rpx;
font-weight: 700;
}
.protection-tip {
padding: 20rpx;
border-radius: 14rpx;
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
/* 成就系统 */
.achievement-section {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.achievement-toggle {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 20rpx;
background: rgba(0, 0, 0, 0.03);
border-radius: 12rpx;
}
.toggle-left {
display: flex;
align-items: center;
gap: 12rpx;
}
.toggle-icon {
display: flex;
align-items: center;
justify-content: center;
}
.toggle-text {
font-size: 26rpx;
color: #666;
}
.toggle-arrow {
font-size: 20rpx;
color: #999;
transition: transform 0.3s ease;
}
.toggle-arrow.expanded {
transform: rotate(180deg);
}
.achievement-content {
display: flex;
flex-direction: column;
gap: 14rpx;
}
.category-tabs {
display: flex;
gap: 8rpx;
}
.category-tab {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
padding: 12rpx 8rpx;
background: rgba(0, 0, 0, 0.03);
border-radius: 12rpx;
flex: 1;
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.category-tab:active {
transform: scale(0.95);
}
.category-tab.active {
background: linear-gradient(135deg, rgba(155, 138, 251, 0.18) 0%, rgba(123, 108, 235, 0.12) 100%);
box-shadow: 0 4rpx 12rpx rgba(155, 138, 251, 0.18);
}
.tab-emoji {
font-size: 28rpx;
line-height: 1;
display: block;
transition: transform 0.2s ease;
}
.category-tab.active .tab-emoji {
transform: scale(1.15);
}
.tab-name {
font-size: 20rpx;
color: #666;
}
.category-tab.active .tab-name {
color: #7B6CEB;
font-weight: 700;
}
.achievement-desc {
padding: 16rpx 20rpx;
background: rgba(0, 0, 0, 0.03);
border-radius: 12rpx;
}
.desc-text {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
.rarity-info {
padding: 12rpx 20rpx;
background: rgba(255, 235, 59, 0.1);
border-radius: 12rpx;
}
.rarity-text {
font-size: 22rpx;
color: #856404;
line-height: 1.5;
}
.achievement-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10rpx;
}
.achievement-card {
display: flex;
flex-direction: column;
gap: 12rpx;
padding: 18rpx 16rpx;
background: rgba(0, 0, 0, 0.025);
border-radius: 16rpx;
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
overflow: hidden;
}
.achievement-card:active {
transform: scale(0.96);
background: rgba(0, 0, 0, 0.04);
}
.achievement-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 6rpx;
height: 100%;
background: linear-gradient(180deg, #9B8AFB 0%, #7B6CEB 100%);
}
.achievement-card.legendary::before {
background: linear-gradient(180deg, #FFD700 0%, #FFA500 50%, #FFD700 100%);
}
.achievement-card.epic::before {
background: linear-gradient(180deg, #A330C9 0%, #7B2CBF 50%, #A330C9 100%);
}
.achievement-card.rare::before {
background: linear-gradient(180deg, #3B82F6 0%, #1D4ED8 50%, #3B82F6 100%);
}
.card-icon-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: center;
align-self: center;
}
.card-icon {
width: 76rpx;
height: 76rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.12);
}
.card-icon::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.18) 0%, transparent 60%);
pointer-events: none;
}
.card-emoji {
font-size: 44rpx;
line-height: 1;
display: block;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.15));
}
.card-rarity-flag {
position: absolute;
top: -8rpx;
right: -14rpx;
padding: 2rpx 10rpx;
border-radius: 100rpx;
font-size: 18rpx;
font-weight: 700;
background: #999;
color: #fff;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.15);
}
.card-rarity-flag text {
color: #fff;
}
.rarity-flag.legendary {
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
}
.rarity-flag.epic {
background: linear-gradient(135deg, #A330C9 0%, #7B2CBF 100%);
}
.rarity-flag.rare {
background: linear-gradient(135deg, #3B82F6 0%, #1D4ED8 100%);
}
.card-desc {
font-size: 22rpx;
color: #333;
text-align: center;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 6rpx;
}
.cat-tag {
display: flex;
align-items: center;
gap: 6rpx;
font-size: 19rpx;
padding: 4rpx 12rpx;
border-radius: 100rpx;
font-weight: 600;
}
.cat-tag-text {
line-height: 1.2;
}
.rarity-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #999;
}
.rarity-dot.legendary {
background: #FFD700;
box-shadow: 0 0 8rpx #FFD700;
}
.rarity-dot.epic {
background: #A330C9;
box-shadow: 0 0 8rpx #A330C9;
}
.rarity-dot.rare {
background: #3B82F6;
box-shadow: 0 0 8rpx #3B82F6;
}
.exp-text {
font-size: 22rpx;
font-weight: 600;
color: #666;
}
/* 特权列表 */
.privileges-section {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.privilege-card {
display: flex;
align-items: center;
gap: 14rpx;
padding: 16rpx 18rpx;
background: rgba(16, 185, 129, 0.06);
border-radius: 14rpx;
}
.privilege-icon {
display: flex;
align-items: center;
justify-content: center;
}
.privilege-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.privilege-name {
font-size: 26rpx;
font-weight: 600;
}
.privilege-desc {
font-size: 22rpx;
color: #999;
}
/* 技巧提示 */
.tips-section {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.tip-card {
display: flex;
align-items: center;
gap: 14rpx;
padding: 14rpx 18rpx;
background: rgba(255, 235, 59, 0.08);
border-radius: 12rpx;
border-left: 4rpx solid #ffc107;
}
.tip-icon-wrap {
width: 50rpx;
height: 50rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.tip-icon {
display: flex;
align-items: center;
justify-content: center;
}
.tip-content {
flex: 1;
}
.tip-text {
font-size: 24rpx;
line-height: 1.6;
color: #666;
}
.tip-reward {
font-size: 22rpx;
font-weight: 600;
}
/* FAQ问答 */
.faq-section {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.faq-card {
background: rgba(0, 0, 0, 0.025);
border-radius: 14rpx;
overflow: hidden;
transition: all 0.2s ease;
}
.faq-card:active {
transform: scale(0.995);
background: rgba(0, 0, 0, 0.04);
}
.faq-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18rpx;
}
.faq-q {
display: flex;
align-items: flex-start;
gap: 12rpx;
flex: 1;
}
.faq-q-icon {
margin-top: 2rpx;
transition: transform 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.faq-q-text {
font-size: 26rpx;
font-weight: 600;
line-height: 1.5;
}
.faq-arrow {
font-size: 18rpx;
color: #999;
transition: transform 0.3s ease;
margin-left: 12rpx;
}
.faq-arrow.expanded {
transform: rotate(180deg);
}
.faq-a {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease, opacity 0.2s ease;
opacity: 0;
}
.faq-a.expanded {
max-height: 500rpx;
padding: 0 18rpx 18rpx 58rpx;
opacity: 1;
}
.answer-content {
background: rgba(255, 255, 255, 0.8);
border-radius: 12rpx;
padding: 16rpx;
}
.faq-a-text {
flex: 1;
font-size: 24rpx;
line-height: 1.6;
color: #666;
}
.safe-area-bottom {
height: env(safe-area-inset-bottom);
}
</style>