hanqin 7 days ago
parent
commit
2716ff7443
  1. 3
      Build_God_Game/src/App.vue
  2. 1
      Build_God_Game/src/assets/images/guidance.svg
  3. 20
      Build_God_Game/src/components/GooeyNav/GooeyNav.vue
  4. 188
      Build_God_Game/src/components/TextCursor/TextCursor.vue
  5. 5
      Build_God_Game/src/views/BagView.vue
  6. 32
      Build_God_Game/src/views/CatalogView.vue
  7. 5
      Build_God_Game/src/views/DailyMissionView.vue
  8. 378
      Build_God_Game/src/views/GameView.vue
  9. 4
      Build_God_Game/src/views/MonsterListView.vue
  10. 9
      Build_God_Game/src/views/ScrapView.vue
  11. 4
      Build_God_Game/src/views/ShopView.vue
  12. 6
      Build_God_Game/src/views/TrainingView.vue

3
Build_God_Game/src/App.vue

@ -1,6 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import TextCursor from './components/TextCursor/TextCursor.vue';
</script> </script>
<template> <template>
<TextCursor class="text-cursor-overlay" text="💀" :delay="0.01" :spacing="100" :exit-duration="0.5"
:removal-interval="30" :max-points="8" />
<router-view /> <router-view />
</template> </template>

1
Build_God_Game/src/assets/images/guidance.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1778075460087" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7480" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 1024a512 512 0 1 1 512-512 512 512 0 0 1-512 512z m0-960a448 448 0 1 0 448 448A448 448 0 0 0 512 64z" fill="#ffffff" p-id="7481" data-spm-anchor-id="a313x.search_index.0.i1.458d3a81vl8o2K" class="selected"></path><path d="M636.8 285.44A136.32 136.32 0 0 1 680.96 392a146.88 146.88 0 0 1-27.52 90.88 726.72 726.72 0 0 1-64 58.24 130.56 130.56 0 0 0-32 38.08 101.76 101.76 0 0 0-11.84 49.92v12.8h-78.08v-12.8a157.12 157.12 0 0 1 14.08-70.72 408 408 0 0 1 85.12-92.48l12.8-14.08a88 88 0 0 0 20.16-54.4 85.76 85.76 0 0 0-21.76-61.12 84.8 84.8 0 0 0-64-22.4 83.52 83.52 0 0 0-73.28 32 126.08 126.08 0 0 0-20.16 76.8h-77.44a171.84 171.84 0 0 1 47.68-128 173.12 173.12 0 0 1 128-47.68 168.64 168.64 0 0 1 118.08 38.4z m-90.24 411.84A48.96 48.96 0 0 1 562.24 736a52.8 52.8 0 0 1-15.68 38.72 58.56 58.56 0 0 1-39.68 15.04A54.72 54.72 0 0 1 452.8 736a47.68 47.68 0 0 1 16.32-38.08 49.92 49.92 0 0 1 38.08-15.04 53.44 53.44 0 0 1 39.36 14.4z" fill="#ffffff" p-id="7482" data-spm-anchor-id="a313x.search_index.0.i2.458d3a81vl8o2K" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

20
Build_God_Game/src/components/GooeyNav/GooeyNav.vue

@ -1,5 +1,5 @@
<template> <template>
<div class="gooey-nav-root"> <div class="gooey-nav-root" :class="`size-${props.size}`">
<div class="relative" ref="containerRef"> <div class="relative" ref="containerRef">
<nav class="flex relative" :style="{ transform: 'translate3d(0,0,0.01px)' }"> <nav class="flex relative" :style="{ transform: 'translate3d(0,0,0.01px)' }">
<ul <ul
@ -47,6 +47,7 @@ interface GooeyNavItem {
interface GooeyNavProps { interface GooeyNavProps {
items: GooeyNavItem[]; items: GooeyNavItem[];
size?: 'small' | 'medium' | 'large';
animationTime?: number; animationTime?: number;
particleCount?: number; particleCount?: number;
particleDistances?: [number, number]; particleDistances?: [number, number];
@ -62,6 +63,7 @@ const emit = defineEmits<{
}>(); }>();
const props = withDefaults(defineProps<GooeyNavProps>(), { const props = withDefaults(defineProps<GooeyNavProps>(), {
size: 'medium',
animationTime: 600, animationTime: 600,
particleCount: 15, particleCount: 15,
particleDistances: () => [90, 10], particleDistances: () => [90, 10],
@ -225,11 +227,25 @@ onUnmounted(() => {
.gooey-nav-root .gooey-nav-link { .gooey-nav-root .gooey-nav-link {
display: inline-block; display: inline-block;
outline: none; outline: none;
padding: 12px 26px;
line-height: 1.3; line-height: 1.3;
box-sizing: border-box; box-sizing: border-box;
} }
.gooey-nav-root.size-small .gooey-nav-link {
font-size: 11px;
padding: 6px 14px;
}
.gooey-nav-root.size-medium .gooey-nav-link {
font-size: 14px;
padding: 12px 26px;
}
.gooey-nav-root.size-large .gooey-nav-link {
font-size: 16px;
padding: 14px 32px;
}
:root { :root {
--linear-ease: linear( --linear-ease: linear(
0, 0,

188
Build_God_Game/src/components/TextCursor/TextCursor.vue

@ -0,0 +1,188 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { Motion } from 'motion-v';
interface TextCursorProps {
text?: string;
delay?: number;
spacing?: number;
followMouseDirection?: boolean;
randomFloat?: boolean;
exitDuration?: number;
removalInterval?: number;
maxPoints?: number;
}
interface TrailItem {
id: number;
x: number;
y: number;
angle: number;
randomX?: number;
randomY?: number;
randomRotate?: number;
}
const props = withDefaults(defineProps<TextCursorProps>(), {
text: '⚛️',
delay: 0.01,
spacing: 100,
followMouseDirection: true,
randomFloat: true,
exitDuration: 0.5,
removalInterval: 30,
maxPoints: 5
});
const trail = ref<TrailItem[]>([]);
const lastMoveTime = ref(Date.now());
const idCounter = ref(0);
let removalIntervalId: ReturnType<typeof setInterval> | null = null;
const handleMouseMove = (e: MouseEvent) => {
// 使 window pointer-events:none
const mouseX = e.clientX;
const mouseY = e.clientY;
let newTrail = [...trail.value];
if (newTrail.length === 0) {
newTrail.push({
id: idCounter.value++,
x: mouseX,
y: mouseY,
angle: 0,
...(props.randomFloat && {
randomX: Math.random() * 10 - 5,
randomY: Math.random() * 10 - 5,
randomRotate: Math.random() * 10 - 5
})
});
} else {
const last = newTrail[newTrail.length - 1];
const dx = mouseX - last.x;
const dy = mouseY - last.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance >= props.spacing) {
let rawAngle = (Math.atan2(dy, dx) * 180) / Math.PI;
if (rawAngle > 90) rawAngle -= 180;
else if (rawAngle < -90) rawAngle += 180;
const computedAngle = props.followMouseDirection ? rawAngle : 0;
const steps = Math.floor(distance / props.spacing);
for (let i = 1; i <= steps; i++) {
const t = (props.spacing * i) / distance;
const newX = last.x + dx * t;
const newY = last.y + dy * t;
newTrail.push({
id: idCounter.value++,
x: newX,
y: newY,
angle: computedAngle,
...(props.randomFloat && {
randomX: Math.random() * 10 - 5,
randomY: Math.random() * 10 - 5,
randomRotate: Math.random() * 10 - 5
})
});
}
}
}
if (newTrail.length > props.maxPoints) {
newTrail = newTrail.slice(newTrail.length - props.maxPoints);
}
trail.value = newTrail;
lastMoveTime.value = Date.now();
};
const startRemovalInterval = () => {
if (removalIntervalId) {
clearInterval(removalIntervalId);
}
removalIntervalId = setInterval(() => {
if (Date.now() - lastMoveTime.value > 100) {
if (trail.value.length > 0) {
trail.value = trail.value.slice(1);
}
}
}, props.removalInterval);
};
onMounted(() => {
window.addEventListener('mousemove', handleMouseMove, { passive: true });
startRemovalInterval();
});
onUnmounted(() => {
window.removeEventListener('mousemove', handleMouseMove);
if (removalIntervalId) {
clearInterval(removalIntervalId);
}
});
</script>
<template>
<div class="text-cursor-container">
<div class="text-cursor-content">
<Motion
v-for="item in trail"
:key="item.id"
:initial="{ opacity: 0, scale: 0.5, rotate: item.angle }"
:animate="{
opacity: 1,
scale: 1,
x: props.randomFloat ? [0, item.randomX || 0, 0] : 0,
y: props.randomFloat ? [0, item.randomY || 0, 0] : 0,
rotate: props.randomFloat ? [item.angle, item.angle + (item.randomRotate || 0), item.angle] : item.angle
}"
:transition="{
duration: props.randomFloat ? 2 : props.exitDuration,
repeat: props.randomFloat ? Infinity : 0,
repeatType: props.randomFloat ? 'mirror' : 'loop'
}"
class="text-cursor-item"
:style="{ left: `${item.x}px`, top: `${item.y}px` }"
>
{{ props.text }}
</Motion>
</div>
</div>
</template>
<style scoped>
.text-cursor-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9999;
overflow: hidden;
}
.text-cursor-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.text-cursor-item {
position: absolute;
font-size: 24px;
white-space: nowrap;
user-select: none;
transform: translate(-50%, -50%);
}
.text-cursor-item:hover {
cursor: none;
}
</style>

5
Build_God_Game/src/views/BagView.vue

@ -3,7 +3,6 @@ import { onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useBagStore } from '@/stores/bag' import { useBagStore } from '@/stores/bag'
import StarBorder from '@/components/StarBorder/StarBorder.vue' import StarBorder from '@/components/StarBorder/StarBorder.vue'
import Particles from '@/components/Particles/Particles.vue'
import { ArrowLeft } from '@element-plus/icons-vue' import { ArrowLeft } from '@element-plus/icons-vue'
import bagIcon from '@/assets/images/bag.svg' import bagIcon from '@/assets/images/bag.svg'
import itemDefaultIcon from '@/assets/images/item-default.svg' import itemDefaultIcon from '@/assets/images/item-default.svg'
@ -81,10 +80,8 @@ onMounted(() => {
<template> <template>
<div class="bag-page"> <div class="bag-page">
<Particles :particle-count="30" :particle-colors="['#ffffff', '#cccccc']" class="particles-bg" />
<div class="bag-container"> <div class="bag-container">
<StarBorder as="div" color="#8b5cf6" speed="5s" :thickness="2" class="back-btn" @click="handleBack"> <StarBorder as="div" color="#8b5cf6" speed="5s" :thickness="2" class="back-btn" style="visibility: hidden;">
<div class="back-content"> <div class="back-content">
<el-icon> <el-icon>
<ArrowLeft /> <ArrowLeft />

32
Build_God_Game/src/views/CatalogView.vue

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Particles from '@/components/Particles/Particles.vue'
import GooeyNav from '@/components/GooeyNav/GooeyNav.vue' import GooeyNav from '@/components/GooeyNav/GooeyNav.vue'
import { import {
catalogApi, catalogApi,
@ -115,11 +114,9 @@ onMounted(() => {
<template> <template>
<div class="catalog-page"> <div class="catalog-page">
<Particles :particle-count="40" :particle-colors="['#ffffff', '#888888']" class="particles-bg" />
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<span class="back-btn" @click="goBack"> 返回</span> <span class="back-btn" style="visibility: hidden;"> 返回</span>
<span class="title">图鉴</span> <span class="title">图鉴</span>
<span class="header-placeholder"></span> <span class="header-placeholder"></span>
</div> </div>
@ -128,6 +125,7 @@ onMounted(() => {
<div class="catalog-nav-wrap"> <div class="catalog-nav-wrap">
<GooeyNav :items="gooeyNavItems" :initial-active-index="0" @select="onGooeyNavSelect" :animation-time="600" <GooeyNav :items="gooeyNavItems" :initial-active-index="0" @select="onGooeyNavSelect" :animation-time="600"
:size="'small'"
:particle-count="15" :particle-distances="[90, 10]" :particle-r="100" :time-variance="300" :particle-count="15" :particle-distances="[90, 10]" :particle-r="100" :time-variance="300"
:colors="[1, 2, 3, 1, 2, 3, 1, 4]" /> :colors="[1, 2, 3, 1, 2, 3, 1, 4]" />
</div> </div>
@ -310,32 +308,6 @@ onMounted(() => {
padding: 6px 0 14px; padding: 6px 0 14px;
} }
/* .catalog-nav-wrap :deep(ul) {
flex-wrap: wrap;
justify-content: center;
row-gap: 0.35rem;
column-gap: 0.45rem !important;
padding-left: 0.25rem !important;
padding-right: 0.25rem !important;
max-width: 100%;
box-sizing: border-box;
text-shadow: none !important;
}
.catalog-nav-wrap :deep(li a),
.catalog-nav-wrap :deep(li.active a) {
text-shadow: none !important;
}
.catalog-nav-wrap :deep(.effect.text) {
text-shadow: none !important;
}
.catalog-nav-wrap :deep(a) {
font-size: 0.82rem;
padding: 0.55em 1.25em !important;
} */
.state-msg { .state-msg {
text-align: center; text-align: center;
color: #888; color: #888;

5
Build_God_Game/src/views/DailyMissionView.vue

@ -2,7 +2,6 @@
import { ref, computed, onMounted, defineComponent, type PropType, h } from 'vue' import { ref, computed, onMounted, defineComponent, type PropType, h } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { dailyMissionApi, type DailyMission, DailyMissionStatus, RewardType, MissionType, MissionDifficulty, ProgressTargetType } from '@/api/dailyMission' import { dailyMissionApi, type DailyMission, DailyMissionStatus, RewardType, MissionType, MissionDifficulty, ProgressTargetType } from '@/api/dailyMission'
import Particles from '@/components/Particles/Particles.vue'
import ElectricBorder from '@/components/ElectricBorder/ElectricBorder.vue' import ElectricBorder from '@/components/ElectricBorder/ElectricBorder.vue'
import StarBorder from '@/components/StarBorder/StarBorder.vue' import StarBorder from '@/components/StarBorder/StarBorder.vue'
import collectionIcon from '@/assets/images/collection.svg?raw' import collectionIcon from '@/assets/images/collection.svg?raw'
@ -307,11 +306,9 @@ const MissionCardContent = defineComponent({
<template> <template>
<div class="daily-mission-page"> <div class="daily-mission-page">
<Particles :particle-count="50" :particle-colors="['#ffffff', '#aaaaaa']" class="particles-bg" />
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<span class="back-btn" @click="handleGoBack"> 返回</span> <span class="back-btn" style="visibility: hidden;"> 返回</span>
<span class="title">每日任务</span> <span class="title">每日任务</span>
<span class="placeholder"></span> <span class="placeholder"></span>
</div> </div>

378
Build_God_Game/src/views/GameView.vue

@ -4,6 +4,7 @@ import { ElProgress } from 'element-plus'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useCharacterStore } from '@/stores/character' import { useCharacterStore } from '@/stores/character'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { defineAsyncComponent } from 'vue'
import Particles from '@/components/Particles/Particles.vue' import Particles from '@/components/Particles/Particles.vue'
import GlareHover from '@/components/GlareHover/GlareHover.vue' import GlareHover from '@/components/GlareHover/GlareHover.vue'
import ChatBox from '@/components/ChatBox.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 monsterIcon from '@/assets/images/monster.svg'
import shopIcon from '@/assets/images/shop.svg' import shopIcon from '@/assets/images/shop.svg'
import catalogIcon from '@/assets/images/catalog.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 authStore = useAuthStore()
const characterStore = useCharacterStore() const characterStore = useCharacterStore()
@ -41,14 +51,32 @@ const expProgress = computed(() => {
const breakthroughMessage = ref('') const breakthroughMessage = ref('')
const showBreakthroughMessage = ref(false) 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(() => [ const menuItems = computed(() => [
{ label: '任务', icon: missionIcon, useImage: true }, { id: 'mission', label: '任务', icon: missionIcon, useImage: true },
{ label: isTraining.value ? '打坐中' : '打坐', icon: meditationIcon, useImage: true, isTraining: isTraining.value }, { id: 'training', label: isTraining.value ? '打坐中' : '打坐', icon: meditationIcon, useImage: true, isTraining: isTraining.value, isActive: activePanel.value === 'training' },
{ label: '背包', icon: bagIcon, useImage: true }, { id: 'bag', label: '背包', icon: bagIcon, useImage: true },
{ label: '捡垃圾', icon: scrapIcon, useImage: true }, { id: 'scrap', label: '捡垃圾', icon: scrapIcon, useImage: true },
{ label: '商店', icon: shopIcon, useImage: true }, { id: 'shop', label: '商店', icon: shopIcon, useImage: true },
{ label: '挑战', icon: monsterIcon, useImage: true }, { id: 'monster', label: '挑战', icon: monsterIcon, useImage: true },
{ label: '图鉴', icon: catalogIcon, useImage: true } { id: 'guide', label: '指南', icon: guidanceIcon, useImage: true },
{ id: 'catalog', label: '图鉴', icon: catalogIcon, useImage: true }
]) ])
const formatNumber = (num: number) => { const formatNumber = (num: number) => {
@ -69,21 +97,23 @@ const navigateToTutorial = () => {
router.push('/tutorial') router.push('/tutorial')
} }
const navigateTo = (item: { label: string }) => { const navigateTo = (item: { id: string; label: string }) => {
if (item.label === '打坐' || item.label === '打坐中') { if (item.id === 'training') {
router.push('/training') activePanel.value = 'training'
} else if (item.label === '任务') { } else if (item.id === 'mission') {
router.push('/daily-mission') activePanel.value = 'mission'
} else if (item.label === '背包') { } else if (item.id === 'bag') {
router.push('/bag') activePanel.value = 'bag'
} else if (item.label === '捡垃圾') { } else if (item.id === 'scrap') {
router.push('/scrap') activePanel.value = 'scrap'
} else if (item.label === '挑战') { } else if (item.id === 'monster') {
router.push('/monster-list') activePanel.value = 'monster'
} else if (item.label === '商店') { } else if (item.id === 'shop') {
router.push('/shop') activePanel.value = 'shop'
} else if (item.label === '图鉴') { } else if (item.id === 'catalog') {
router.push('/catalog') activePanel.value = 'catalog'
} else if (item.id === 'guide') {
router.push('/tutorial')
} }
} }
@ -96,6 +126,10 @@ const handleBreakthrough = async () => {
}, 3000) }, 3000)
} }
const goBack = () => {
activePanel.value = 'welcome'
}
onMounted(async () => { onMounted(async () => {
await characterStore.fetchCharacters() await characterStore.fetchCharacters()
const current = characterStore.characters.find(c => c.id === characterStore.currentCharacter?.id) const current = characterStore.characters.find(c => c.id === characterStore.currentCharacter?.id)
@ -109,8 +143,14 @@ onMounted(async () => {
<template> <template>
<div class="game-page"> <div class="game-page">
<Particles :particle-count="50" :particle-colors="['#ffffff', '#cccccc']" class="particles-bg" /> <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"> <div class="game-shell">
<aside id="character-sidebar" class="character-sidebar"> <aside class="character-sidebar">
<div class="sidebar-card"> <div class="sidebar-card">
<div class="sidebar-header"> <div class="sidebar-header">
<div class="sidebar-title-block"> <div class="sidebar-title-block">
@ -163,55 +203,14 @@ onMounted(async () => {
<div class="attr-row"> <div class="attr-row">
<span class="attr-label"><span class="attr-icon crit-icon">💥</span>暴击率</span> <span class="attr-label"><span class="attr-icon crit-icon">💥</span>暴击率</span>
<span class="attr-value"> <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">+{{ <span v-if="(characterStore.currentCharacter?.bonusCriticalRate || 0) > 0" class="bonus-value">+{{
(characterStore.currentCharacter?.bonusCriticalRate || 0).toFixed(1) }}%</span> (characterStore.currentCharacter?.bonusCriticalRate || 0).toFixed(1) }}%</span>
</span> </span>
</div> </div>
</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 v-if="nextLevelName" class="breakthrough-section">
<div class="breakthrough-header"> <div class="breakthrough-header">
<span class="breakthrough-title">境界突破</span> <span class="breakthrough-title">境界突破</span>
@ -224,7 +223,8 @@ onMounted(async () => {
</div> </div>
<div class="exp-info"> <div class="exp-info">
<span class="exp-text">{{ formatNumber(currentExp) }} / {{ formatNumber(nextLevelMinExp) }} 经验</span> <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> <span class="exp-percent">{{ Math.floor(expProgress) }}%</span>
</div> </div>
<div class="rate-info"> <div class="rate-info">
@ -236,8 +236,8 @@ onMounted(async () => {
<span class="pill-cost-label">突破消耗</span> <span class="pill-cost-label">突破消耗</span>
<span class="pill-cost-value">{{ requiredPillName }} ×{{ requiredPillQuantity }}</span> <span class="pill-cost-value">{{ requiredPillName }} ×{{ requiredPillQuantity }}</span>
</div> </div>
<button class="breakthrough-btn" :class="{ 'can-breakthrough': canBreakthrough }" :disabled="!canBreakthrough" <button class="breakthrough-btn" :class="{ 'can-breakthrough': canBreakthrough }"
@click="handleBreakthrough"> :disabled="!canBreakthrough" @click="handleBreakthrough">
{{ canBreakthrough ? '突破' : '未满足突破条件' }} {{ canBreakthrough ? '突破' : '未满足突破条件' }}
</button> </button>
</div> </div>
@ -246,14 +246,34 @@ onMounted(async () => {
<div v-if="showBreakthroughMessage" class="toast-message"> <div v-if="showBreakthroughMessage" class="toast-message">
{{ breakthroughMessage }} {{ breakthroughMessage }}
</div> </div>
</div>
</aside>
<div class="page-footer"> <div class="chat-panel">
<StarBorder as="div" color="#e63d3d" speed="3s" :thickness="3"> <ChatBox embedded />
<div class="btn-out" @click="handleLogout">
退出登录
</div> </div>
</StarBorder>
<div class="game-main">
<div class="main-panel">
<div v-if="activePanel === 'welcome'" class="welcome-panel">
</div> </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> </div>
</div> </div>
@ -262,12 +282,15 @@ onMounted(async () => {
<style scoped> <style scoped>
.game-page { .game-page {
min-height: 100vh; height: 100vh;
background: #000000; background: #000000;
padding: 16px; padding: 16px;
padding-bottom: 24px;
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
display: flex;
flex-direction: column;
gap: 16px;
box-sizing: border-box;
} }
.particles-bg { .particles-bg {
@ -278,6 +301,19 @@ onMounted(async () => {
height: 100% !important; 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 { .game-shell {
position: relative; position: relative;
z-index: 10; z-index: 10;
@ -285,19 +321,19 @@ onMounted(async () => {
flex-direction: row; flex-direction: row;
align-items: stretch; align-items: stretch;
gap: 16px; gap: 16px;
max-width: 1380px; margin: 0;
margin: 0 auto; flex: 1;
height: calc(100vh - 32px); min-height: 0;
min-height: calc(100vh - 32px); width: 100%;
} }
.character-sidebar { .character-sidebar {
flex: 1 1 0; flex: 0 0 320px;
min-width: 0; min-width: 0;
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
height: 100%;
} }
.sidebar-card { .sidebar-card {
@ -359,7 +395,7 @@ onMounted(async () => {
} }
.game-main { .game-main {
flex: 1 1 0; flex: 0 0 480px;
min-width: 0; min-width: 0;
height: 100%; height: 100%;
display: flex; display: flex;
@ -383,7 +419,7 @@ onMounted(async () => {
} }
.chat-panel { .chat-panel {
flex: 1 1 0; flex: 1;
min-width: 0; min-width: 0;
height: 100%; height: 100%;
display: flex; display: flex;
@ -803,4 +839,178 @@ onMounted(async () => {
color: #cccccc; color: #cccccc;
font-size: 0.85rem; 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> </style>

4
Build_God_Game/src/views/MonsterListView.vue

@ -4,7 +4,6 @@ import { useRouter } from 'vue-router'
import { useMonsterStore } from '@/stores/monster' import { useMonsterStore } from '@/stores/monster'
import { useCharacterStore } from '@/stores/character' import { useCharacterStore } from '@/stores/character'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import Particles from '@/components/Particles/Particles.vue'
import defaultIcon from '@/assets/images/item-default.svg' import defaultIcon from '@/assets/images/item-default.svg'
const router = useRouter() const router = useRouter()
@ -62,10 +61,9 @@ onMounted(async () => {
<template> <template>
<div class="monster-list-page"> <div class="monster-list-page">
<Particles :particle-count="50" :particle-colors="['#ffffff', '#aaaaaa']" class="particles-bg" />
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<span class="back-btn" @click="handleGoBack"> 返回</span> <span class="back-btn" style="visibility: hidden;"> 返回</span>
<span class="title">怪物挑战</span> <span class="title">怪物挑战</span>
<span class="player-level">Lv.{{ playerLevel }}</span> <span class="player-level">Lv.{{ playerLevel }}</span>
</div> </div>

9
Build_God_Game/src/views/ScrapView.vue

@ -3,7 +3,6 @@ import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useCharacterStore } from '@/stores/character' import { useCharacterStore } from '@/stores/character'
import { scrapApi, type ScrapScanResultDto } from '@/api/scrap' import { scrapApi, type ScrapScanResultDto } from '@/api/scrap'
import Particles from '@/components/Particles/Particles.vue'
import GlareHover from '@/components/GlareHover/GlareHover.vue' import GlareHover from '@/components/GlareHover/GlareHover.vue'
const router = useRouter() const router = useRouter()
@ -97,15 +96,9 @@ const getLevelColor = (color: string) => {
<template> <template>
<div class="scrap-page"> <div class="scrap-page">
<Particles
:particle-count="50"
:particle-colors="['#ffffff', '#aaaaaa']"
class="particles-bg"
/>
<div class="scrap-container"> <div class="scrap-container">
<div class="header"> <div class="header">
<button class="back-btn" @click="goBack"> <button class="back-btn" style="visibility: hidden;">
<span class="back-arrow"></span> <span class="back-arrow"></span>
<span>返回</span> <span>返回</span>
</button> </button>

4
Build_God_Game/src/views/ShopView.vue

@ -5,7 +5,6 @@ import { useCharacterStore } from '@/stores/character'
import { getShop, buyItem, type Shop, type ShopItemDisplay } from '@/api/shop' import { getShop, buyItem, type Shop, type ShopItemDisplay } from '@/api/shop'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import StarBorder from '@/components/StarBorder/StarBorder.vue' import StarBorder from '@/components/StarBorder/StarBorder.vue'
import Particles from '@/components/Particles/Particles.vue'
const router = useRouter() const router = useRouter()
const characterStore = useCharacterStore() const characterStore = useCharacterStore()
@ -116,12 +115,11 @@ onMounted(async () => {
<template> <template>
<div class="shop-page"> <div class="shop-page">
<Particles />
<StarBorder /> <StarBorder />
<div class="page-container"> <div class="page-container">
<div class="page-header"> <div class="page-header">
<span class="back-btn" @click="goBack"> 返回</span> <span class="back-btn" style="visibility: hidden;"> 返回</span>
<span class="title">商店</span> <span class="title">商店</span>
<span class="placeholder"></span> <span class="placeholder"></span>
</div> </div>

6
Build_God_Game/src/views/TrainingView.vue

@ -2,7 +2,6 @@
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useCharacterStore } from '@/stores/character' import { useCharacterStore } from '@/stores/character'
import Particles from '@/components/Particles/Particles.vue'
import MeditationIcon from '@/components/MeditationIcon/MeditationIcon.vue' import MeditationIcon from '@/components/MeditationIcon/MeditationIcon.vue'
const router = useRouter() const router = useRouter()
@ -82,12 +81,9 @@ const handleStopTraining = async () => {
<template> <template>
<div class="training-page"> <div class="training-page">
<Particles :particle-count="50" :particle-colors="['#ffffff', '#aaaaaa']" class="particles-bg" />
<div class="page-container"> <div class="page-container">
<div class="training-header"> <div class="training-header">
<span class="back-btn" @click="handleGoBack"> 返回</span> <span class="back-btn" style="visibility: hidden;"> 返回</span>
<span class="title">打坐</span> <span class="title">打坐</span>
<span class="placeholder"></span> <span class="placeholder"></span>
</div> </div>

Loading…
Cancel
Save