424 lines
11 KiB
Vue
424 lines
11 KiB
Vue
|
|
<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>
|
||
|
|
<text v-if="player.equipment.weapon" class="equipment-slot__item">
|
||
|
|
{{ player.equipment.weapon.icon }} {{ player.equipment.weapon.name }}
|
||
|
|
</text>
|
||
|
|
<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>
|
||
|
|
<text v-if="player.equipment.armor" class="equipment-slot__item">
|
||
|
|
{{ player.equipment.armor.icon }} {{ player.equipment.armor.name }}
|
||
|
|
</text>
|
||
|
|
<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>
|
||
|
|
<text v-if="player.equipment.shield" class="equipment-slot__item">
|
||
|
|
{{ player.equipment.shield.icon }} {{ player.equipment.shield.name }}
|
||
|
|
</text>
|
||
|
|
<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>
|
||
|
|
<text v-if="player.equipment.accessory" class="equipment-slot__item">
|
||
|
|
{{ player.equipment.accessory.icon }} {{ player.equipment.accessory.name }}
|
||
|
|
</text>
|
||
|
|
<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">
|
||
|
|
<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 }}</text>
|
||
|
|
</view>
|
||
|
|
<view class="skill-item__bar">
|
||
|
|
<ProgressBar
|
||
|
|
:value="skill.exp"
|
||
|
|
:max="skill.maxExp"
|
||
|
|
height="8rpx"
|
||
|
|
:showText="false"
|
||
|
|
/>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
<view v-if="filteredSkills.length === 0" class="skill-empty">
|
||
|
|
<text class="skill-empty__text">暂无技能</text>
|
||
|
|
</view>
|
||
|
|
</scroll-view>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { computed, ref } from 'vue'
|
||
|
|
import { usePlayerStore } from '@/store/player'
|
||
|
|
import { SKILL_CONFIG } from '@/config/skills'
|
||
|
|
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'
|
||
|
|
|
||
|
|
const player = usePlayerStore()
|
||
|
|
const showMoreStats = ref(false)
|
||
|
|
const currentSkillFilter = ref('all')
|
||
|
|
|
||
|
|
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, skill]) => ({
|
||
|
|
id,
|
||
|
|
name: SKILL_CONFIG[id]?.name || id,
|
||
|
|
type: SKILL_CONFIG[id]?.type || 'passive',
|
||
|
|
icon: SKILL_CONFIG[id]?.icon || '',
|
||
|
|
level: skill.level,
|
||
|
|
exp: skill.exp,
|
||
|
|
maxExp: SKILL_CONFIG[id]?.expPerLevel(skill.level + 1) || 100
|
||
|
|
}))
|
||
|
|
.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 = 0
|
||
|
|
let defense = 0
|
||
|
|
let critRate = 5
|
||
|
|
|
||
|
|
// 武器攻击力
|
||
|
|
if (player.equipment.weapon) {
|
||
|
|
attack += player.equipment.weapon.baseDamage || 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// 防具防御力
|
||
|
|
if (player.equipment.armor) {
|
||
|
|
defense += player.equipment.armor.baseDefense || 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// 计算AP和EP
|
||
|
|
const ap = player.baseStats.dexterity + player.baseStats.intuition * 0.2
|
||
|
|
const ep = player.baseStats.agility + player.baseStats.intuition * 0.2
|
||
|
|
|
||
|
|
return {
|
||
|
|
attack: Math.floor(attack),
|
||
|
|
defense: Math.floor(defense),
|
||
|
|
critRate: critRate,
|
||
|
|
ap: Math.floor(ap),
|
||
|
|
ep: Math.floor(ep)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
</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;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12rpx;
|
||
|
|
padding: 12rpx;
|
||
|
|
background-color: $bg-secondary;
|
||
|
|
border-radius: 8rpx;
|
||
|
|
margin-bottom: 8rpx;
|
||
|
|
|
||
|
|
&__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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.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 {
|
||
|
|
color: $text-primary;
|
||
|
|
font-size: 22rpx;
|
||
|
|
}
|
||
|
|
|
||
|
|
&__empty {
|
||
|
|
color: $text-muted;
|
||
|
|
font-size: 22rpx;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|