|
|
|
@ -4,6 +4,7 @@ import { ElProgress } from 'element-plus' |
|
|
|
import { useAuthStore } from '@/stores/auth' |
|
|
|
import { useCharacterStore } from '@/stores/character' |
|
|
|
import { useRouter } from 'vue-router' |
|
|
|
import { defineAsyncComponent } from 'vue' |
|
|
|
import Particles from '@/components/Particles/Particles.vue' |
|
|
|
import GlareHover from '@/components/GlareHover/GlareHover.vue' |
|
|
|
import ChatBox from '@/components/ChatBox.vue' |
|
|
|
@ -16,6 +17,15 @@ import bagIcon from '@/assets/images/bag.svg' |
|
|
|
import monsterIcon from '@/assets/images/monster.svg' |
|
|
|
import shopIcon from '@/assets/images/shop.svg' |
|
|
|
import catalogIcon from '@/assets/images/catalog.svg' |
|
|
|
import guidanceIcon from '@/assets/images/guidance.svg' |
|
|
|
|
|
|
|
const DailyMissionView = defineAsyncComponent(() => import('@/views/DailyMissionView.vue')) |
|
|
|
const TrainingView = defineAsyncComponent(() => import('@/views/TrainingView.vue')) |
|
|
|
const BagView = defineAsyncComponent(() => import('@/views/BagView.vue')) |
|
|
|
const ScrapView = defineAsyncComponent(() => import('@/views/ScrapView.vue')) |
|
|
|
const ShopView = defineAsyncComponent(() => import('@/views/ShopView.vue')) |
|
|
|
const MonsterListView = defineAsyncComponent(() => import('@/views/MonsterListView.vue')) |
|
|
|
const CatalogView = defineAsyncComponent(() => import('@/views/CatalogView.vue')) |
|
|
|
|
|
|
|
const authStore = useAuthStore() |
|
|
|
const characterStore = useCharacterStore() |
|
|
|
@ -41,14 +51,32 @@ const expProgress = computed(() => { |
|
|
|
const breakthroughMessage = ref('') |
|
|
|
const showBreakthroughMessage = ref(false) |
|
|
|
|
|
|
|
//默认选中第一个 |
|
|
|
const activePanel = ref<string>('mission') |
|
|
|
|
|
|
|
const panelComponents = { |
|
|
|
'mission': DailyMissionView, |
|
|
|
'training': TrainingView, |
|
|
|
'bag': BagView, |
|
|
|
'scrap': ScrapView, |
|
|
|
'shop': ShopView, |
|
|
|
'monster': MonsterListView, |
|
|
|
'catalog': CatalogView |
|
|
|
} |
|
|
|
|
|
|
|
const currentPanelComponent = computed(() => { |
|
|
|
return panelComponents[activePanel.value as keyof typeof panelComponents] || null |
|
|
|
}) |
|
|
|
|
|
|
|
const menuItems = computed(() => [ |
|
|
|
{ label: '任务', icon: missionIcon, useImage: true }, |
|
|
|
{ label: isTraining.value ? '打坐中' : '打坐', icon: meditationIcon, useImage: true, isTraining: isTraining.value }, |
|
|
|
{ label: '背包', icon: bagIcon, useImage: true }, |
|
|
|
{ label: '捡垃圾', icon: scrapIcon, useImage: true }, |
|
|
|
{ label: '商店', icon: shopIcon, useImage: true }, |
|
|
|
{ label: '挑战', icon: monsterIcon, useImage: true }, |
|
|
|
{ label: '图鉴', icon: catalogIcon, useImage: true } |
|
|
|
{ id: 'mission', label: '任务', icon: missionIcon, useImage: true }, |
|
|
|
{ id: 'training', label: isTraining.value ? '打坐中' : '打坐', icon: meditationIcon, useImage: true, isTraining: isTraining.value, isActive: activePanel.value === 'training' }, |
|
|
|
{ id: 'bag', label: '背包', icon: bagIcon, useImage: true }, |
|
|
|
{ id: 'scrap', label: '捡垃圾', icon: scrapIcon, useImage: true }, |
|
|
|
{ id: 'shop', label: '商店', icon: shopIcon, useImage: true }, |
|
|
|
{ id: 'monster', label: '挑战', icon: monsterIcon, useImage: true }, |
|
|
|
{ id: 'guide', label: '指南', icon: guidanceIcon, useImage: true }, |
|
|
|
{ id: 'catalog', label: '图鉴', icon: catalogIcon, useImage: true } |
|
|
|
]) |
|
|
|
|
|
|
|
const formatNumber = (num: number) => { |
|
|
|
@ -69,21 +97,23 @@ const navigateToTutorial = () => { |
|
|
|
router.push('/tutorial') |
|
|
|
} |
|
|
|
|
|
|
|
const navigateTo = (item: { label: string }) => { |
|
|
|
if (item.label === '打坐' || item.label === '打坐中') { |
|
|
|
router.push('/training') |
|
|
|
} else if (item.label === '任务') { |
|
|
|
router.push('/daily-mission') |
|
|
|
} else if (item.label === '背包') { |
|
|
|
router.push('/bag') |
|
|
|
} else if (item.label === '捡垃圾') { |
|
|
|
router.push('/scrap') |
|
|
|
} else if (item.label === '挑战') { |
|
|
|
router.push('/monster-list') |
|
|
|
} else if (item.label === '商店') { |
|
|
|
router.push('/shop') |
|
|
|
} else if (item.label === '图鉴') { |
|
|
|
router.push('/catalog') |
|
|
|
const navigateTo = (item: { id: string; label: string }) => { |
|
|
|
if (item.id === 'training') { |
|
|
|
activePanel.value = 'training' |
|
|
|
} else if (item.id === 'mission') { |
|
|
|
activePanel.value = 'mission' |
|
|
|
} else if (item.id === 'bag') { |
|
|
|
activePanel.value = 'bag' |
|
|
|
} else if (item.id === 'scrap') { |
|
|
|
activePanel.value = 'scrap' |
|
|
|
} else if (item.id === 'monster') { |
|
|
|
activePanel.value = 'monster' |
|
|
|
} else if (item.id === 'shop') { |
|
|
|
activePanel.value = 'shop' |
|
|
|
} else if (item.id === 'catalog') { |
|
|
|
activePanel.value = 'catalog' |
|
|
|
} else if (item.id === 'guide') { |
|
|
|
router.push('/tutorial') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -96,6 +126,10 @@ const handleBreakthrough = async () => { |
|
|
|
}, 3000) |
|
|
|
} |
|
|
|
|
|
|
|
const goBack = () => { |
|
|
|
activePanel.value = 'welcome' |
|
|
|
} |
|
|
|
|
|
|
|
onMounted(async () => { |
|
|
|
await characterStore.fetchCharacters() |
|
|
|
const current = characterStore.characters.find(c => c.id === characterStore.currentCharacter?.id) |
|
|
|
@ -109,8 +143,14 @@ onMounted(async () => { |
|
|
|
<template> |
|
|
|
<div class="game-page"> |
|
|
|
<Particles :particle-count="50" :particle-colors="['#ffffff', '#cccccc']" class="particles-bg" /> |
|
|
|
|
|
|
|
<div class="top-log"> |
|
|
|
<ShinyText text="✨ 欢迎来到我的世界" :speed="2" :delay="0.5" :disabled="false" :color="'#b5b5b5'" :shine-color="'#34fef1'" |
|
|
|
:spread="120" :direction="'left'" :yoyo="false" :pause-on-hover="false" /> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="game-shell"> |
|
|
|
<aside id="character-sidebar" class="character-sidebar"> |
|
|
|
<aside class="character-sidebar"> |
|
|
|
<div class="sidebar-card"> |
|
|
|
<div class="sidebar-header"> |
|
|
|
<div class="sidebar-title-block"> |
|
|
|
@ -163,55 +203,14 @@ onMounted(async () => { |
|
|
|
<div class="attr-row"> |
|
|
|
<span class="attr-label"><span class="attr-icon crit-icon">💥</span>暴击率</span> |
|
|
|
<span class="attr-value"> |
|
|
|
<span class="base-value">{{ (characterStore.currentCharacter?.baseCriticalRate || 0).toFixed(1) }}%</span> |
|
|
|
<span class="base-value">{{ (characterStore.currentCharacter?.baseCriticalRate || 0).toFixed(1) |
|
|
|
}}%</span> |
|
|
|
<span v-if="(characterStore.currentCharacter?.bonusCriticalRate || 0) > 0" class="bonus-value">+{{ |
|
|
|
(characterStore.currentCharacter?.bonusCriticalRate || 0).toFixed(1) }}%</span> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="detail-section detail-section--last"> |
|
|
|
<div class="section-title">经验</div> |
|
|
|
<div class="exp-row"> |
|
|
|
<span class="exp-label">当前经验</span> |
|
|
|
<span class="exp-value">{{ formatNumber(characterStore.currentCharacter?.currentExp || 0) }}</span> |
|
|
|
</div> |
|
|
|
<div v-if="characterStore.currentCharacter?.nextLevelMinExp" class="exp-row"> |
|
|
|
<span class="exp-label">升级所需</span> |
|
|
|
<span class="exp-value">{{ formatNumber(characterStore.currentCharacter?.nextLevelMinExp || 0) }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</aside> |
|
|
|
|
|
|
|
<div class="chat-panel"> |
|
|
|
<ChatBox embedded /> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="game-main"> |
|
|
|
<div class="game-container"> |
|
|
|
<div style="text-align: center;"> |
|
|
|
<ShinyText class="welcome-text" text="✨ 欢迎来到我的世界" :speed="2" :delay="0.5" :disabled="false" :color="'#b5b5b5'" |
|
|
|
:shine-color="'#34fef1'" :spread="120" :direction="'left'" :yoyo="false" :pause-on-hover="false" /> |
|
|
|
</div> |
|
|
|
<div style="text-align: center; margin-bottom: 16px;"> |
|
|
|
<button class="tutorial-btn" @click="navigateToTutorial">游戏指南</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="menu-grid"> |
|
|
|
<GlareHover v-for="item in menuItems" :key="item.label" width="100%" height="100px" |
|
|
|
:background="item.isTraining ? 'rgba(255,136,68,0.1)' : 'rgba(255,255,255,0.02)'" border-radius="16px" |
|
|
|
:border-color="item.isTraining ? 'rgba(255,136,68,0.3)' : 'rgba(255,255,255,0.08)'" |
|
|
|
:glare-color="item.isTraining ? '#ff8844' : '#ffffff'" :glare-opacity="0.1" class="menu-card" |
|
|
|
:class="{ 'training-active': item.isTraining }" @click="navigateTo(item)"> |
|
|
|
<div class="menu-content"> |
|
|
|
<img v-if="item.useImage" :src="item.icon" class="menu-icon-img" /> |
|
|
|
<span v-else class="menu-icon">{{ item.icon }}</span> |
|
|
|
<span class="menu-label">{{ item.label }}</span> |
|
|
|
</div> |
|
|
|
</GlareHover> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="nextLevelName" class="breakthrough-section"> |
|
|
|
<div class="breakthrough-header"> |
|
|
|
<span class="breakthrough-title">境界突破</span> |
|
|
|
@ -224,7 +223,8 @@ onMounted(async () => { |
|
|
|
</div> |
|
|
|
<div class="exp-info"> |
|
|
|
<span class="exp-text">{{ formatNumber(currentExp) }} / {{ formatNumber(nextLevelMinExp) }} 经验</span> |
|
|
|
<el-progress :percentage="Math.floor(expProgress)" color="#ff8844" :show-text="false" :stroke-width="8" /> |
|
|
|
<el-progress :percentage="Math.floor(expProgress)" color="#ff8844" :show-text="false" |
|
|
|
:stroke-width="8" /> |
|
|
|
<span class="exp-percent">{{ Math.floor(expProgress) }}%</span> |
|
|
|
</div> |
|
|
|
<div class="rate-info"> |
|
|
|
@ -236,8 +236,8 @@ onMounted(async () => { |
|
|
|
<span class="pill-cost-label">突破消耗</span> |
|
|
|
<span class="pill-cost-value">{{ requiredPillName }} ×{{ requiredPillQuantity }}</span> |
|
|
|
</div> |
|
|
|
<button class="breakthrough-btn" :class="{ 'can-breakthrough': canBreakthrough }" :disabled="!canBreakthrough" |
|
|
|
@click="handleBreakthrough"> |
|
|
|
<button class="breakthrough-btn" :class="{ 'can-breakthrough': canBreakthrough }" |
|
|
|
:disabled="!canBreakthrough" @click="handleBreakthrough"> |
|
|
|
{{ canBreakthrough ? '突破' : '未满足突破条件' }} |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
@ -246,14 +246,34 @@ onMounted(async () => { |
|
|
|
<div v-if="showBreakthroughMessage" class="toast-message"> |
|
|
|
{{ breakthroughMessage }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</aside> |
|
|
|
|
|
|
|
<div class="page-footer"> |
|
|
|
<StarBorder as="div" color="#e63d3d" speed="3s" :thickness="3"> |
|
|
|
<div class="btn-out" @click="handleLogout"> |
|
|
|
退出登录 |
|
|
|
</div> |
|
|
|
</StarBorder> |
|
|
|
<div class="chat-panel"> |
|
|
|
<ChatBox embedded /> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="game-main"> |
|
|
|
<div class="main-panel"> |
|
|
|
<div v-if="activePanel === 'welcome'" class="welcome-panel"> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-else class="panel-content"> |
|
|
|
<!-- <div class="panel-header"> |
|
|
|
<button class="back-btn" @click="goBack" style="visibility: hidden;">← 返回</button> |
|
|
|
</div> --> |
|
|
|
<component :is="currentPanelComponent" /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="dock-bar"> |
|
|
|
<div class="dock-container"> |
|
|
|
<div v-for="item in menuItems" :key="item.id" class="dock-item" |
|
|
|
:class="{ 'dock-item--active': activePanel === item.id }" @click="navigateTo(item)"> |
|
|
|
<img v-if="item.useImage" :src="item.icon" class="dock-icon" /> |
|
|
|
<span class="dock-label">{{ item.label }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -262,12 +282,15 @@ onMounted(async () => { |
|
|
|
|
|
|
|
<style scoped> |
|
|
|
.game-page { |
|
|
|
min-height: 100vh; |
|
|
|
height: 100vh; |
|
|
|
background: #000000; |
|
|
|
padding: 16px; |
|
|
|
padding-bottom: 24px; |
|
|
|
position: relative; |
|
|
|
overflow-x: hidden; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
gap: 16px; |
|
|
|
box-sizing: border-box; |
|
|
|
} |
|
|
|
|
|
|
|
.particles-bg { |
|
|
|
@ -278,6 +301,19 @@ onMounted(async () => { |
|
|
|
height: 100% !important; |
|
|
|
} |
|
|
|
|
|
|
|
.top-log { |
|
|
|
position: relative; |
|
|
|
z-index: 10; |
|
|
|
text-align: center; |
|
|
|
padding: 12px; |
|
|
|
background: rgba(16, 18, 24, 0.8); |
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
|
border-radius: 12px; |
|
|
|
width: 100%; |
|
|
|
box-sizing: border-box; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.game-shell { |
|
|
|
position: relative; |
|
|
|
z-index: 10; |
|
|
|
@ -285,19 +321,19 @@ onMounted(async () => { |
|
|
|
flex-direction: row; |
|
|
|
align-items: stretch; |
|
|
|
gap: 16px; |
|
|
|
max-width: 1380px; |
|
|
|
margin: 0 auto; |
|
|
|
height: calc(100vh - 32px); |
|
|
|
min-height: calc(100vh - 32px); |
|
|
|
margin: 0; |
|
|
|
flex: 1; |
|
|
|
min-height: 0; |
|
|
|
width: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.character-sidebar { |
|
|
|
flex: 1 1 0; |
|
|
|
flex: 0 0 280px; |
|
|
|
min-width: 0; |
|
|
|
height: 100%; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
min-height: 0; |
|
|
|
height: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.sidebar-card { |
|
|
|
@ -359,7 +395,7 @@ onMounted(async () => { |
|
|
|
} |
|
|
|
|
|
|
|
.game-main { |
|
|
|
flex: 1 1 0; |
|
|
|
flex: 0 0 400px; |
|
|
|
min-width: 0; |
|
|
|
height: 100%; |
|
|
|
display: flex; |
|
|
|
@ -383,7 +419,7 @@ onMounted(async () => { |
|
|
|
} |
|
|
|
|
|
|
|
.chat-panel { |
|
|
|
flex: 1 1 0; |
|
|
|
flex: 1; |
|
|
|
min-width: 0; |
|
|
|
height: 100%; |
|
|
|
display: flex; |
|
|
|
@ -803,4 +839,178 @@ onMounted(async () => { |
|
|
|
color: #cccccc; |
|
|
|
font-size: 0.85rem; |
|
|
|
} |
|
|
|
|
|
|
|
/* Main Panel */ |
|
|
|
.main-panel { |
|
|
|
flex: 1; |
|
|
|
min-height: 0; |
|
|
|
overflow-y: auto; |
|
|
|
width: 100%; |
|
|
|
margin: 0; |
|
|
|
padding: 16px; |
|
|
|
position: relative; |
|
|
|
z-index: 10; |
|
|
|
background: rgba(16, 18, 24, 0.92); |
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
|
border-radius: 16px; |
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35); |
|
|
|
} |
|
|
|
|
|
|
|
.welcome-panel { |
|
|
|
padding: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.panel-content { |
|
|
|
width: 100%; |
|
|
|
min-height: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.panel-header { |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.back-btn { |
|
|
|
padding: 8px 16px; |
|
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
|
border: 1px solid rgba(255, 255, 255, 0.15); |
|
|
|
border-radius: 8px; |
|
|
|
color: #aaaaaa; |
|
|
|
font-size: 0.85rem; |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.2s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.back-btn:hover { |
|
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
|
border-color: rgba(255, 255, 255, 0.25); |
|
|
|
color: #ffffff; |
|
|
|
} |
|
|
|
|
|
|
|
/* Dock Bar */ |
|
|
|
.dock-bar { |
|
|
|
position: relative; |
|
|
|
z-index: 10; |
|
|
|
width: 100%; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-container { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
gap: 8px; |
|
|
|
padding: 12px 16px; |
|
|
|
background: rgba(16, 18, 24, 0.92); |
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
|
border-radius: 16px; |
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35); |
|
|
|
} |
|
|
|
|
|
|
|
.dock-item { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
gap: 4px; |
|
|
|
padding: 10px 14px; |
|
|
|
cursor: pointer; |
|
|
|
border-radius: 12px; |
|
|
|
transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); |
|
|
|
border: 1px solid transparent; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-item:hover { |
|
|
|
background: rgba(255, 255, 255, 0.08); |
|
|
|
transform: translateY(-4px); |
|
|
|
} |
|
|
|
|
|
|
|
.dock-item--active { |
|
|
|
background: rgba(52, 254, 241, 0.12); |
|
|
|
border-color: rgba(52, 254, 241, 0.3); |
|
|
|
} |
|
|
|
|
|
|
|
.dock-item--active .dock-label { |
|
|
|
color: #34fef1; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-icon { |
|
|
|
width: 2.4rem; |
|
|
|
height: 2.4rem; |
|
|
|
object-fit: contain; |
|
|
|
opacity: 0.85; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-item:hover .dock-icon { |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-item--active .dock-icon { |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-label { |
|
|
|
color: #999999; |
|
|
|
font-size: 0.75rem; |
|
|
|
font-weight: 400; |
|
|
|
} |
|
|
|
|
|
|
|
@media (max-width: 900px) { |
|
|
|
.game-shell { |
|
|
|
flex-direction: column; |
|
|
|
height: auto; |
|
|
|
min-height: auto; |
|
|
|
gap: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.character-sidebar { |
|
|
|
flex: none; |
|
|
|
width: 100%; |
|
|
|
max-width: 560px; |
|
|
|
margin: 0 auto; |
|
|
|
height: auto; |
|
|
|
} |
|
|
|
|
|
|
|
.sidebar-card { |
|
|
|
overflow-y: visible; |
|
|
|
flex: none; |
|
|
|
} |
|
|
|
|
|
|
|
.chat-panel { |
|
|
|
flex: none; |
|
|
|
width: 100%; |
|
|
|
max-width: none; |
|
|
|
height: auto; |
|
|
|
min-height: 240px; |
|
|
|
} |
|
|
|
|
|
|
|
.game-main { |
|
|
|
flex: none; |
|
|
|
width: 100%; |
|
|
|
height: auto; |
|
|
|
} |
|
|
|
|
|
|
|
.main-panel { |
|
|
|
flex: none; |
|
|
|
overflow-y: visible; |
|
|
|
max-width: 560px; |
|
|
|
margin: 0 auto; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-container { |
|
|
|
gap: 4px; |
|
|
|
padding: 6px 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-item { |
|
|
|
padding: 8px 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-icon { |
|
|
|
width: 2rem; |
|
|
|
height: 2rem; |
|
|
|
} |
|
|
|
|
|
|
|
.dock-label { |
|
|
|
font-size: 0.65rem; |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |
|
|
|
|