Files
text-adventure-game/components/panels/StatusPanel.vue
Claude ccfd6a5e75 fix: 修复武器经验获取并完善义体系统
- 修复战斗胜利后未获得武器技能经验的问题 (initCombat未传递skillExpReward)
- 每级武器技能提供5%武器伤害加成(已实现,无需修改)
- 实现义体安装/卸载功能,支持NPC对话交互
- StatusPanel添加义体装备槽显示
- MapPanel修复NPC对话import问题
- 新增成就系统框架
- 添加项目文档CLAUDE.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 15:52:32 +08:00

1080 lines
29 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="status-panel">
<!-- 资源进度条 -->
<view class="status-panel__resources">
<view class="resource-row">
<text class="resource-label">HP</text>
<view class="resource-bar">
<ProgressBar
:value="player.currentStats.health"
:max="player.currentStats.maxHealth"
color="#ff6b6b"
:showText="true"
height="24rpx"
/>
</view>
</view>
<view class="resource-row">
<text class="resource-label">耐力</text>
<view class="resource-bar">
<ProgressBar
:value="player.currentStats.stamina"
:max="player.currentStats.maxStamina"
color="#ffe66d"
:showText="true"
height="24rpx"
/>
</view>
</view>
<view class="resource-row">
<text class="resource-label">精神</text>
<view class="resource-bar">
<ProgressBar
:value="player.currentStats.sanity"
:max="player.currentStats.maxSanity"
color="#4ecdc4"
:showText="true"
height="24rpx"
/>
</view>
</view>
</view>
<!-- 主属性摘要 -->
<view class="status-panel__stats">
<StatItem label="力量" :value="player.baseStats.strength" icon="💪" />
<StatItem label="敏捷" :value="player.baseStats.agility" icon="🏃" />
<StatItem label="灵巧" :value="player.baseStats.dexterity" icon="🎯" />
<StatItem label="智力" :value="player.baseStats.intuition" icon="🧠" />
<StatItem label="体质" :value="player.baseStats.vitality" icon="❤️" />
</view>
<!-- 等级信息 -->
<view class="status-panel__level">
<text class="level-text">Lv.{{ player.level.current }}</text>
<ProgressBar
:value="player.level.exp"
:max="player.level.maxExp"
color="#a855f7"
:showText="true"
height="16rpx"
/>
</view>
<!-- 货币 -->
<view class="status-panel__currency">
<text class="currency-icon">💰</text>
<text class="currency-text">{{ currencyText }}</text>
</view>
<!-- 装备栏 -->
<view class="status-panel__equipment">
<text class="section-title">🎽 装备</text>
<view class="equipment-slots">
<view class="equipment-slot" :class="{ 'equipment-slot--empty': !player.equipment.weapon }">
<text class="equipment-slot__label">武器</text>
<view v-if="player.equipment.weapon" class="equipment-slot__item">
<text class="equipment-slot__name" :style="{ color: player.equipment.weapon.qualityColor }">
{{ player.equipment.weapon.icon }} {{ player.equipment.weapon.name }}
</text>
<text class="equipment-slot__stats">{{ player.equipment.weapon.finalDamage }}</text>
<text class="equipment-slot__quality">[{{ player.equipment.weapon.qualityName }}]</text>
</view>
<text v-else class="equipment-slot__empty"></text>
</view>
<view class="equipment-slot" :class="{ 'equipment-slot--empty': !player.equipment.armor }">
<text class="equipment-slot__label">防具</text>
<view v-if="player.equipment.armor" class="equipment-slot__item">
<text class="equipment-slot__name" :style="{ color: player.equipment.armor.qualityColor }">
{{ player.equipment.armor.icon }} {{ player.equipment.armor.name }}
</text>
<text class="equipment-slot__stats">{{ player.equipment.armor.finalDefense }}</text>
<text class="equipment-slot__quality">[{{ player.equipment.armor.qualityName }}]</text>
</view>
<text v-else class="equipment-slot__empty"></text>
</view>
<view class="equipment-slot" :class="{ 'equipment-slot--empty': !player.equipment.shield }">
<text class="equipment-slot__label">盾牌</text>
<view v-if="player.equipment.shield" class="equipment-slot__item">
<text class="equipment-slot__name" :style="{ color: player.equipment.shield.qualityColor }">
{{ player.equipment.shield.icon }} {{ player.equipment.shield.name }}
</text>
<text class="equipment-slot__stats">格挡{{ player.equipment.shield.finalShield }}</text>
<text class="equipment-slot__quality">[{{ player.equipment.shield.qualityName }}]</text>
</view>
<text v-else class="equipment-slot__empty"></text>
</view>
<view class="equipment-slot" :class="{ 'equipment-slot--empty': !player.equipment.accessory }">
<text class="equipment-slot__label">饰品</text>
<view v-if="player.equipment.accessory" class="equipment-slot__item">
<text class="equipment-slot__name" :style="{ color: player.equipment.accessory.qualityColor }">
{{ player.equipment.accessory.icon }} {{ player.equipment.accessory.name }}
</text>
<text class="equipment-slot__quality">[{{ player.equipment.accessory.qualityName }}]</text>
</view>
<text v-else class="equipment-slot__empty"></text>
</view>
<view class="equipment-slot" :class="{ 'equipment-slot--empty': !player.equipment.prosthetic }">
<text class="equipment-slot__label">义体</text>
<view v-if="player.equipment.prosthetic" class="equipment-slot__item">
<text class="equipment-slot__name" style="color: #ff6b9d;">
{{ player.equipment.prosthetic.icon }} {{ player.equipment.prosthetic.name }}
</text>
<text v-if="player.equipment.prosthetic.stats" class="equipment-slot__stats">
{{ formatProstheticStats(player.equipment.prosthetic.stats) }}
</text>
<text v-if="player.equipment.prosthetic.skill" class="equipment-slot__quality">[{{ getProstheticSkillName(player.equipment.prosthetic.skill) }}]</text>
</view>
<text v-else class="equipment-slot__empty"></text>
</view>
</view>
</view>
<!-- 更多属性折叠 -->
<Collapse
title="更多属性"
:expanded="showMoreStats"
@toggle="showMoreStats = $event"
>
<view class="status-panel__more-stats">
<StatItem label="攻击力" :value="finalStats.attack" />
<StatItem label="防御力" :value="finalStats.defense" />
<StatItem label="暴击率" :value="finalStats.critRate + '%'" />
<StatItem label="AP" :value="finalStats.ap" />
<StatItem label="EP" :value="finalStats.ep" />
</view>
</Collapse>
<!-- 技能筛选 -->
<FilterTabs
:tabs="skillFilterTabs"
:modelValue="currentSkillFilter"
@update:modelValue="currentSkillFilter = $event"
/>
<!-- 技能列表 -->
<scroll-view class="status-panel__skills" scroll-y>
<view v-for="skill in filteredSkills" :key="skill.id" class="skill-item">
<view class="skill-item__main" @click="toggleSkillDetail(skill.id)">
<text class="skill-item__icon">{{ skill.icon || '⚡' }}</text>
<view class="skill-item__info">
<text class="skill-item__name">{{ skill.name }}</text>
<text class="skill-item__level">Lv.{{ skill.level }}/{{ skill.maxLevel }}</text>
</view>
<view class="skill-item__bar">
<ProgressBar
:value="skill.exp"
:max="skill.maxExp"
height="8rpx"
:showText="false"
/>
<text class="skill-item__progress">{{ Math.floor(skill.exp) }}/{{ Math.floor(skill.maxExp) }}</text>
</view>
<text class="skill-item__expand">{{ expandedSkills.includes(skill.id) ? '▼' : '▶' }}</text>
</view>
<!-- 技能详情展开后显示 -->
<view v-if="expandedSkills.includes(skill.id)" class="skill-item__detail">
<!-- 已获得的增幅 -->
<view v-if="getActiveMilestones(skill).length > 0" class="skill-detail__section">
<text class="skill-detail__title"> 已获得增幅</text>
<view v-for="m in getActiveMilestones(skill)" :key="m.level" class="skill-detail__bonus">
<text class="skill-detail__bonus-level">Lv.{{ m.level }}</text>
<text class="skill-detail__bonus-desc">{{ m.desc }}</text>
</view>
</view>
<!-- 即将获得的增幅 -->
<view v-if="getUpcomingMilestones(skill).length > 0" class="skill-detail__section">
<text class="skill-detail__title">即将解锁</text>
<view v-for="m in getUpcomingMilestones(skill)" :key="m.level" class="skill-detail__bonus upcoming">
<text class="skill-detail__bonus-level">Lv.{{ m.level }}</text>
<text class="skill-detail__bonus-desc">{{ m.desc }}</text>
</view>
</view>
<view v-if="getActiveMilestones(skill).length === 0 && getUpcomingMilestones(skill).length === 0" class="skill-detail__empty">
<text class="skill-detail__empty-text">暂无增幅效果</text>
</view>
</view>
</view>
<view v-if="filteredSkills.length === 0" class="skill-empty">
<text class="skill-empty__text">暂无技能</text>
</view>
</scroll-view>
<!-- 书籍区域 -->
<Collapse title="📚 书籍" :expanded="showBooks" @toggle="showBooks = $event">
<!-- 活动中的阅读任务 -->
<view v-if="readingTask" class="reading-task">
<text class="reading-task__title">正在阅读{{ readingTask.bookName }}</text>
<ProgressBar
:value="readingTask.progress"
:max="readingTask.totalTime"
color="#4ecdc4"
:showText="true"
height="16rpx"
/>
<text class="reading-task__time">{{ readingTimeText }}</text>
</view>
<!-- 可阅读的书籍列表 -->
<view v-if="!readingTask && ownedBooks.length > 0" class="books-list">
<view
v-for="book in ownedBooks"
:key="book.id"
class="book-item"
@click="startReading(book)"
>
<text class="book-item__icon">{{ book.icon || '📖' }}</text>
<view class="book-item__info">
<text class="book-item__name">{{ book.name }}</text>
<text class="book-item__desc">阅读需 {{ book.readingTime }}</text>
</view>
<TextButton text="阅读" type="primary" size="small" />
</view>
</view>
<view v-if="!readingTask && ownedBooks.length === 0" class="books-empty">
<text class="books-empty__text">暂无书籍去商店看看吧</text>
</view>
</Collapse>
<!-- 训练区域 -->
<Collapse title="⚔️ 训练" :expanded="showTraining" @toggle="showTraining = $event">
<!-- 活动中的训练任务 -->
<view v-if="trainingTask" class="training-task">
<text class="training-task__title">正在训练{{ trainingTask.skillName }}</text>
<ProgressBar
:value="trainingTask.progress"
:max="trainingTask.totalTime || 300"
color="#ff6b6b"
:showText="true"
height="16rpx"
/>
<TextButton text="停止训练" type="warning" size="small" @click="stopTraining" />
</view>
<!-- 可训练的技能列表 -->
<view v-if="!trainingTask && trainableSkills.length > 0" class="trainable-list">
<view
v-for="skill in trainableSkills"
:key="skill.id"
class="trainable-item"
>
<text class="trainable-item__icon">{{ skill.icon || '⚔️' }}</text>
<view class="trainable-item__info">
<text class="trainable-item__name">{{ skill.name }}</text>
<text class="trainable-item__level">Lv.{{ skill.level }}</text>
</view>
<TextButton
text="训练"
type="primary"
size="small"
:disabled="player.currentStats.stamina < 10"
@click="startTraining(skill)"
/>
</view>
</view>
<view v-if="!trainingTask && trainableSkills.length === 0" class="training-empty">
<text class="training-empty__text">暂无可训练技能</text>
</view>
</Collapse>
<!-- 活动任务面板 -->
<Collapse title="📋 活动任务" :expanded="showTasks" @toggle="showTasks = $event">
<view v-if="activeTasksList.length > 0" class="active-tasks-list">
<view v-for="task in activeTasksList" :key="task.id" class="active-task-item">
<view class="active-task-item__header">
<text class="active-task-item__name">{{ task.name }}</text>
<TextButton
text="取消"
type="warning"
size="small"
@click="cancelActiveTask(task)"
/>
</view>
<ProgressBar
:value="task.progress"
:max="task.total || 100"
color="#4ecdc4"
:showText="true"
height="12rpx"
/>
<text class="active-task-item__time">{{ task.timeText }}</text>
</view>
</view>
<view v-else class="tasks-empty">
<text class="tasks-empty__text">暂无活动任务</text>
</view>
</Collapse>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { usePlayerStore } from '@/store/player'
import { useGameStore } from '@/store/game'
import { SKILL_CONFIG } from '@/config/skills'
import { ITEM_CONFIG } from '@/config/items'
import { startTask, endTask } from '@/utils/taskSystem'
import { getSkillDisplayInfo } from '@/utils/skillSystem'
import ProgressBar from '@/components/common/ProgressBar.vue'
import StatItem from '@/components/common/StatItem.vue'
import Collapse from '@/components/common/Collapse.vue'
import FilterTabs from '@/components/common/FilterTabs.vue'
import TextButton from '@/components/common/TextButton.vue'
const player = usePlayerStore()
const game = useGameStore()
const showMoreStats = ref(false)
const currentSkillFilter = ref('all')
const showBooks = ref(false)
const showTraining = ref(false)
const showTasks = ref(true)
const expandedSkills = ref([]) // 展开的技能ID列表
const skillFilterTabs = [
{ id: 'all', label: '全部' },
{ id: 'combat', label: '战斗' },
{ id: 'life', label: '生活' },
{ id: 'passive', label: '被动' }
]
const currencyText = computed(() => {
const c = player.currency.copper
const gold = Math.floor(c / 10000)
const silver = Math.floor((c % 10000) / 100)
const copper = c % 100
const parts = []
if (gold > 0) parts.push(`${gold}`)
if (silver > 0 || gold > 0) parts.push(`${silver}`)
parts.push(`${copper}`)
return parts.join(' ')
})
const filteredSkills = computed(() => {
const skills = Object.entries(player.skills || {})
.filter(([_, skill]) => skill.unlocked)
.map(([id, _]) => getSkillDisplayInfo(player, id))
.filter(Boolean)
.sort((a, b) => b.level - a.level || b.exp - a.exp)
if (currentSkillFilter.value === 'all') {
return skills
}
return skills.filter(s => s.type === currentSkillFilter.value)
})
const finalStats = computed(() => {
// 计算最终属性(基础值 + 装备加成 + 全局加成)
let attack = player.baseStats?.strength || 0
let defense = 0
let critRate = (player.baseStats?.dexterity || 0) + (player.globalBonus?.critRate || 0)
// 装备属性加成
if (player.equipmentStats) {
attack += player.equipmentStats.attack || 0
defense += player.equipmentStats.defense || 0
critRate += player.equipmentStats.critRate || 0
}
// 如果没有equipmentStats直接从装备计算
if (!player.equipmentStats) {
// 武器攻击力使用finalDamage如果存在
if (player.equipment.weapon) {
const weapon = player.equipment.weapon
attack += weapon.finalDamage || weapon.baseDamage || 0
}
// 防具防御力使用finalDefense如果存在
if (player.equipment.armor) {
const armor = player.equipment.armor
defense += armor.finalDefense || armor.baseDefense || 0
}
// 盾牌格挡
if (player.equipment.shield) {
const shield = player.equipment.shield
defense += shield.finalDefense || shield.baseDefense || 0
}
}
// 计算AP和EP包含装备加成
const ap = (player.baseStats?.dexterity || 0) + (player.baseStats?.intuition || 0) * 0.2
const ep = (player.baseStats?.agility || 0) + (player.baseStats?.intuition || 0) * 0.2
return {
attack: Math.floor(attack),
defense: Math.floor(defense),
critRate: Math.min(80, Math.floor(critRate)),
ap: Math.floor(ap),
ep: Math.floor(ep)
}
})
// 书籍相关
const ownedBooks = computed(() => {
return (player.inventory || []).filter(item => item.type === 'book')
})
const readingTask = computed(() => {
const task = (game.activeTasks || []).find(t => t.type === 'reading')
if (!task) return null
const bookConfig = ITEM_CONFIG[task.data.itemId]
return {
bookName: bookConfig?.name || '未知书籍',
progress: task.progress,
totalTime: bookConfig?.readingTime || 60
}
})
const readingTimeText = computed(() => {
if (!readingTask.value) return ''
const remaining = Math.max(0, readingTask.value.totalTime - readingTask.value.progress)
const minutes = Math.floor(remaining / 60)
const seconds = Math.floor(remaining % 60)
return `剩余 ${minutes}:${String(seconds).padStart(2, '0')}`
})
// 技能详情相关函数
function toggleSkillDetail(skillId) {
const index = expandedSkills.value.indexOf(skillId)
if (index > -1) {
expandedSkills.value.splice(index, 1)
} else {
expandedSkills.value.push(skillId)
}
}
function getActiveMilestones(skill) {
const config = SKILL_CONFIG[skill.id]
if (!config || !config.milestones) return []
return Object.entries(config.milestones)
.filter(([level]) => parseInt(level) <= skill.level)
.map(([level, milestone]) => ({
level: parseInt(level),
desc: milestone.desc
}))
.sort((a, b) => a.level - b.level)
}
function getUpcomingMilestones(skill) {
const config = SKILL_CONFIG[skill.id]
if (!config || !config.milestones) return []
return Object.entries(config.milestones)
.filter(([level]) => parseInt(level) > skill.level)
.map(([level, milestone]) => ({
level: parseInt(level),
desc: milestone.desc
}))
.sort((a, b) => a.level - b.level)
}
function startReading(book) {
const result = startTask(game, player, 'reading', {
itemId: book.id,
duration: book.readingTime || 60
})
if (result.success) {
game.addLog(`开始阅读《${book.name}`, 'info')
} else {
game.addLog(result.message, 'error')
}
}
// 训练相关
const trainableSkills = computed(() => {
return Object.entries(player.skills || {})
.filter(([_, skill]) => skill.unlocked && SKILL_CONFIG[_]?.type === 'combat')
.map(([id, _]) => {
const info = getSkillDisplayInfo(player, id)
return {
id,
name: info?.name || id,
icon: info?.icon || '⚔️',
level: info?.level || 0
}
})
.filter(Boolean)
})
const trainingTask = computed(() => {
const task = (game.activeTasks || []).find(t => t.type === 'training')
if (!task) return null
const info = getSkillDisplayInfo(player, task.data.skillId)
return {
skillName: info?.name || '未知技能',
progress: task.progress,
totalTime: task.data.duration || 300,
taskId: task.id
}
})
function startTraining(skill) {
const result = startTask(game, player, 'training', {
skillId: skill.id,
duration: 60 // 1分钟
})
if (result.success) {
game.addLog(`开始训练 ${skill.name}`, 'info')
} else {
game.addLog(result.message, 'error')
}
}
function stopTraining() {
if (trainingTask.value) {
endTask(game, player, trainingTask.value.taskId, false)
game.addLog('停止了训练', 'info')
}
}
// 活动任务相关
const activeTasksList = computed(() => {
const tasks = (game.activeTasks || []).map(task => {
let name = '未知任务'
let total = 100
if (task.type === 'reading') {
const book = ITEM_CONFIG[task.data.itemId]
name = `阅读《${book?.name || '未知书籍'}`
total = book?.readingTime || 60
} else if (task.type === 'training') {
const skill = SKILL_CONFIG[task.data.skillId]
name = `训练 ${skill?.name || '技能'}`
total = task.data.duration || 300
} else if (task.type === 'resting') {
name = '休息'
total = task.data.duration || 60
}
const progress = Math.min(task.progress, total)
const percentage = Math.floor((progress / total) * 100)
// 计算剩余时间
const remaining = Math.max(0, total - progress)
const minutes = Math.floor(remaining / 60)
const seconds = Math.floor(remaining % 60)
let timeText = ''
if (minutes > 0) {
timeText = `剩余 ${minutes}${seconds}`
} else {
timeText = `剩余 ${seconds}`
}
return {
id: task.id,
name,
progress,
total,
percentage,
timeText
}
})
return tasks.sort((a, b) => b.progress - a.progress)
})
function cancelActiveTask(task) {
const { endTask } = require('@/utils/taskSystem')
endTask(game, player, task.id, false)
game.addLog(`取消了 ${task.name}`, 'info')
}
// 义体相关辅助函数
function formatProstheticStats(stats) {
if (!stats) return ''
const parts = []
if (stats.strength) parts.push(`力+${stats.strength}`)
if (stats.agility) parts.push(`敏+${stats.agility}`)
if (stats.dexterity) parts.push(`灵+${stats.dexterity}`)
if (stats.intuition) parts.push(`智+${stats.intuition}`)
if (stats.vitality) parts.push(`体+${stats.vitality}`)
if (stats.attack) parts.push(`攻+${stats.attack}`)
if (stats.defense) parts.push(`防+${stats.defense}`)
return parts.join(' ')
}
function getProstheticSkillName(skillId) {
const { SKILL_CONFIG } = require('@/config/skills')
const skill = SKILL_CONFIG[skillId]
return skill ? skill.name : skillId
}
</script>
<style lang="scss" scoped>
.status-panel {
display: flex;
flex-direction: column;
height: 100%;
padding: 16rpx;
background-color: $bg-primary;
&__resources {
display: flex;
flex-direction: column;
gap: 12rpx;
padding: 16rpx;
background-color: $bg-secondary;
border-radius: 12rpx;
margin-bottom: 16rpx;
}
&__stats {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
padding: 16rpx;
background-color: $bg-secondary;
border-radius: 12rpx;
margin-bottom: 16rpx;
}
&__level {
padding: 16rpx;
background-color: $bg-secondary;
border-radius: 12rpx;
margin-bottom: 16rpx;
}
&__currency {
display: flex;
align-items: center;
gap: 12rpx;
padding: 16rpx;
background-color: $bg-secondary;
border-radius: 12rpx;
margin-bottom: 16rpx;
}
&__equipment {
padding: 16rpx;
background-color: $bg-secondary;
border-radius: 12rpx;
margin-bottom: 16rpx;
}
&__more-stats {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
&__skills {
flex: 1;
margin-top: 16rpx;
}
}
.resource-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.resource-bar {
flex: 1;
min-width: 0;
}
.resource-label {
min-width: 80rpx;
color: $text-secondary;
font-size: 24rpx;
}
.level-text {
display: block;
color: $accent;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.currency-icon {
font-size: 32rpx;
}
.currency-text {
color: $warning;
font-size: 28rpx;
font-weight: bold;
}
.skill-item {
display: flex;
flex-direction: column;
background-color: $bg-secondary;
border-radius: 8rpx;
margin-bottom: 8rpx;
overflow: hidden;
&__main {
display: flex;
align-items: center;
gap: 12rpx;
padding: 12rpx;
cursor: pointer;
}
&__icon {
font-size: 32rpx;
width: 48rpx;
text-align: center;
}
&__info {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
&__name {
color: $text-primary;
font-size: 26rpx;
}
&__level {
color: $accent;
font-size: 24rpx;
}
&__bar {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4rpx;
}
&__progress {
color: $text-muted;
font-size: 20rpx;
text-align: right;
}
&__expand {
color: $text-muted;
font-size: 20rpx;
padding-left: 8rpx;
}
&__detail {
padding: 12rpx;
padding-top: 0;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
}
.skill-detail {
&__section {
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
}
&__title {
color: $text-primary;
font-size: 24rpx;
font-weight: bold;
margin-bottom: 8rpx;
display: block;
}
&__bonus {
display: flex;
align-items: center;
gap: 12rpx;
padding: 6rpx 12rpx;
background-color: rgba(78, 205, 196, 0.1);
border-radius: 4rpx;
margin-bottom: 6rpx;
&.upcoming {
background-color: rgba(255, 107, 107, 0.1);
}
}
&__bonus-level {
color: $accent;
font-size: 22rpx;
font-weight: bold;
min-width: 60rpx;
}
&__bonus-desc {
color: $text-secondary;
font-size: 22rpx;
}
&__empty {
padding: 16rpx;
text-align: center;
}
&__empty-text {
color: $text-muted;
font-size: 24rpx;
}
}
.skill-empty {
padding: 48rpx;
text-align: center;
&__text {
color: $text-muted;
font-size: 28rpx;
}
}
.section-title {
display: block;
color: $text-primary;
font-size: 24rpx;
font-weight: bold;
margin-bottom: 12rpx;
}
.equipment-slots {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8rpx;
}
.equipment-slot {
display: flex;
flex-direction: column;
align-items: center;
padding: 12rpx;
background-color: $bg-primary;
border-radius: 8rpx;
border: 1rpx solid $border-color;
&--empty {
opacity: 0.6;
}
&__label {
color: $text-secondary;
font-size: 20rpx;
margin-bottom: 4rpx;
}
&__item {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rpx;
}
&__name {
color: $text-primary;
font-size: 22rpx;
text-align: center;
}
&__stats {
color: $accent;
font-size: 20rpx;
}
&__quality {
color: $text-muted;
font-size: 18rpx;
}
&__empty {
color: $text-muted;
font-size: 22rpx;
}
}
// 书籍相关样式
.reading-task {
padding: 16rpx;
background-color: $bg-tertiary;
border-radius: 8rpx;
margin-bottom: 12rpx;
&__title {
display: block;
color: $text-primary;
font-size: 24rpx;
margin-bottom: 8rpx;
}
&__time {
display: block;
color: $text-secondary;
font-size: 20rpx;
margin-top: 8rpx;
text-align: right;
}
}
.books-list {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.book-item {
display: flex;
align-items: center;
gap: 12rpx;
padding: 12rpx;
background-color: $bg-primary;
border-radius: 8rpx;
&:active {
background-color: $bg-tertiary;
}
&__icon {
font-size: 32rpx;
}
&__info {
flex: 1;
}
&__name {
display: block;
color: $text-primary;
font-size: 26rpx;
}
&__desc {
display: block;
color: $text-secondary;
font-size: 22rpx;
margin-top: 4rpx;
}
}
.books-empty {
padding: 32rpx;
text-align: center;
&__text {
color: $text-muted;
font-size: 24rpx;
}
}
// 训练相关样式
.training-task {
padding: 16rpx;
background-color: $bg-tertiary;
border-radius: 8rpx;
margin-bottom: 12rpx;
&__title {
display: block;
color: $text-primary;
font-size: 24rpx;
margin-bottom: 8rpx;
}
}
.trainable-list {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.trainable-item {
display: flex;
align-items: center;
gap: 12rpx;
padding: 12rpx;
background-color: $bg-primary;
border-radius: 8rpx;
&__icon {
font-size: 32rpx;
}
&__info {
flex: 1;
}
&__name {
display: block;
color: $text-primary;
font-size: 26rpx;
}
&__level {
display: block;
color: $accent;
font-size: 22rpx;
margin-top: 4rpx;
}
}
.training-empty {
padding: 32rpx;
text-align: center;
&__text {
color: $text-muted;
font-size: 24rpx;
}
}
// 活动任务样式
.active-tasks-list {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.active-task-item {
padding: 12rpx;
background-color: $bg-primary;
border-radius: 8rpx;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
&__name {
color: $text-primary;
font-size: 24rpx;
}
&__time {
display: block;
color: $text-secondary;
font-size: 20rpx;
margin-top: 4rpx;
}
}
.tasks-empty {
padding: 32rpx;
text-align: center;
&__text {
color: $text-muted;
font-size: 24rpx;
}
}
</style>