Browse Source

调整了一下任务接取列表的样式,改成carousel

master
秦汉 1 month ago
parent
commit
fc516aa42b
  1. 2
      Build_God_Game/index.html
  2. 307
      Build_God_Game/src/components/Carousel/Carousel.vue
  3. 175
      Build_God_Game/src/components/MissionCarousel/MissionCarousel.vue
  4. 4
      Build_God_Game/src/views/CharacterView.vue
  5. 390
      Build_God_Game/src/views/DailyMissionView.vue
  6. 4
      Build_God_Game/src/views/GameView.vue
  7. 4
      Build_God_Game/src/views/LoginView.vue
  8. 4
      Build_God_Game/src/views/RegisterView.vue

2
Build_God_Game/index.html

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CUPOWER</title> <title>God</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

307
Build_God_Game/src/components/Carousel/Carousel.vue

@ -0,0 +1,307 @@
<template>
<div
ref="containerRef"
:class="[
'relative overflow-hidden p-4',
round ? 'rounded-full border border-[#333]' : 'rounded-[24px] border border-[#333]'
]"
:style="{
width: `${baseWidth}px`,
...(round && { height: `${baseWidth}px` })
}"
>
<Motion
tag="div"
class="flex"
drag="x"
:dragConstraints="dragConstraints"
:style="{
width: itemWidth + 'px',
gap: `${GAP}px`,
perspective: 1000,
perspectiveOrigin: `${currentIndex * trackItemOffset + itemWidth / 2}px 50%`,
x: motionX
}"
@dragEnd="handleDragEnd"
:animate="{ x: -(currentIndex * trackItemOffset) }"
:transition="effectiveTransition"
@animationComplete="handleAnimationComplete"
>
<Motion
v-for="(item, index) in carouselItems"
:key="index"
tag="div"
:class="[
'relative shrink-0 flex flex-col overflow-hidden cursor-grab active:cursor-grabbing',
round
? 'items-center justify-center text-center bg-[#111] border border-[#333] rounded-full'
: 'items-start justify-between bg-[#111] border border-[#333] rounded-[12px]'
]"
:style="{
width: itemWidth + 'px',
height: round ? itemWidth + 'px' : '100%',
rotateY: getRotateY(index),
...(round && { borderRadius: '50%' })
}"
:transition="effectiveTransition"
>
<div :class="round ? 'p-0 m-0' : 'mb-4 p-5'">
<span class="flex h-[28px] w-[28px] items-center justify-center rounded-full bg-[#0b0b0b]">
<i :class="item.icon" class="text-white text-base"></i>
</span>
</div>
<div class="p-5">
<div class="mb-1 font-black text-lg text-white">{{ item.title }}</div>
<p class="text-sm text-white">{{ item.description }}</p>
</div>
</Motion>
</Motion>
<div :class="['flex w-full justify-center', round ? 'absolute z-20 bottom-12 left-1/2 -translate-x-1/2' : '']">
<div class="mt-4 flex w-[150px] justify-between px-8">
<Motion
v-for="(_, index) in items"
:key="index"
tag="div"
:class="[
'h-2 w-2 rounded-full cursor-pointer transition-colors duration-150',
currentIndex % items.length === index
? round
? 'bg-white'
: 'bg-[#333333]'
: round
? 'bg-[#555]'
: 'bg-[rgba(51,51,51,0.4)]'
]"
:animate="{
scale: currentIndex % items.length === index ? 1.2 : 1
}"
@click="() => setCurrentIndex(index)"
:transition="{ duration: 0.15 }"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
export interface CarouselItem {
title: string;
description: string;
id: number;
icon: string;
}
export interface CarouselProps {
items?: CarouselItem[];
baseWidth?: number;
autoplay?: boolean;
autoplayDelay?: number;
pauseOnHover?: boolean;
loop?: boolean;
round?: boolean;
}
export const DEFAULT_ITEMS: CarouselItem[] = [
{
title: 'Text Animations',
description: 'Cool text animations for your projects.',
id: 1,
icon: 'pi pi-file'
},
{
title: 'Animations',
description: 'Smooth animations for your projects.',
id: 2,
icon: 'pi pi-circle'
},
{
title: 'Components',
description: 'Reusable components for your projects.',
id: 3,
icon: 'pi pi-objects-column'
},
{
title: 'Backgrounds',
description: 'Beautiful backgrounds and patterns for your projects.',
id: 4,
icon: 'pi pi-table'
},
{
title: 'Common UI',
description: 'Common UI components are coming soon!',
id: 5,
icon: 'pi pi-code'
}
];
</script>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';
import { Motion, useMotionValue, useTransform } from 'motion-v';
const DRAG_BUFFER = 0;
const VELOCITY_THRESHOLD = 500;
const GAP = 16;
const SPRING_OPTIONS = { type: 'spring' as const, stiffness: 300, damping: 30 };
const props = withDefaults(defineProps<CarouselProps>(), {
items: () => DEFAULT_ITEMS,
baseWidth: 300,
autoplay: false,
autoplayDelay: 3000,
pauseOnHover: false,
loop: false,
round: false
});
const containerPadding = 16;
const itemWidth = computed(() => props.baseWidth - containerPadding * 2);
const trackItemOffset = computed(() => itemWidth.value + GAP);
const carouselItems = computed(() => (props.loop ? [...props.items, props.items[0]] : props.items));
const currentIndex = ref<number>(0);
const motionX = useMotionValue(0);
const isHovered = ref<boolean>(false);
const isResetting = ref<boolean>(false);
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
let autoplayTimer: number | null = null;
const dragConstraints = computed(() => {
return props.loop
? {}
: {
left: -trackItemOffset.value * (carouselItems.value.length - 1),
right: 0
};
});
const effectiveTransition = computed(() => (isResetting.value ? { duration: 0 } : SPRING_OPTIONS));
const maxItems = Math.max(props.items.length + 1, 10);
const rotateYTransforms = Array.from({ length: maxItems }, (_, index) => {
const range = computed(() => [
-(index + 1) * trackItemOffset.value,
-index * trackItemOffset.value,
-(index - 1) * trackItemOffset.value
]);
const outputRange = [90, 0, -90];
return useTransform(motionX, range, outputRange, { clamp: false });
});
const getRotateY = (index: number) => {
return rotateYTransforms[index] || rotateYTransforms[0];
};
const setCurrentIndex = (index: number) => {
currentIndex.value = index;
};
const handleAnimationComplete = () => {
if (props.loop && currentIndex.value === carouselItems.value.length - 1) {
isResetting.value = true;
motionX.set(0);
currentIndex.value = 0;
setTimeout(() => {
isResetting.value = false;
}, 50);
}
};
interface DragInfo {
offset: { x: number; y: number };
velocity: { x: number; y: number };
}
const handleDragEnd = (event: Event, info: DragInfo) => {
const offset = info.offset.x;
const velocity = info.velocity.x;
if (offset < -DRAG_BUFFER || velocity < -VELOCITY_THRESHOLD) {
if (props.loop && currentIndex.value === props.items.length - 1) {
currentIndex.value = currentIndex.value + 1;
} else {
currentIndex.value = Math.min(currentIndex.value + 1, carouselItems.value.length - 1);
}
} else if (offset > DRAG_BUFFER || velocity > VELOCITY_THRESHOLD) {
if (props.loop && currentIndex.value === 0) {
currentIndex.value = props.items.length - 1;
} else {
currentIndex.value = Math.max(currentIndex.value - 1, 0);
}
}
};
const startAutoplay = () => {
if (props.autoplay && (!props.pauseOnHover || !isHovered.value)) {
autoplayTimer = window.setInterval(() => {
currentIndex.value = (() => {
const prev = currentIndex.value;
if (prev === props.items.length - 1 && props.loop) {
return prev + 1;
}
if (prev === carouselItems.value.length - 1) {
return props.loop ? 0 : prev;
}
return prev + 1;
})();
}, props.autoplayDelay);
}
};
const stopAutoplay = () => {
if (autoplayTimer) {
clearInterval(autoplayTimer);
autoplayTimer = null;
}
};
const handleMouseEnter = () => {
isHovered.value = true;
if (props.pauseOnHover) {
stopAutoplay();
}
};
const handleMouseLeave = () => {
isHovered.value = false;
if (props.pauseOnHover) {
startAutoplay();
}
};
watch(
[
() => props.autoplay,
() => props.autoplayDelay,
isHovered,
() => props.loop,
() => props.items.length,
() => carouselItems.value.length,
() => props.pauseOnHover
],
() => {
stopAutoplay();
startAutoplay();
}
);
onMounted(() => {
if (props.pauseOnHover && containerRef.value) {
containerRef.value.addEventListener('mouseenter', handleMouseEnter);
containerRef.value.addEventListener('mouseleave', handleMouseLeave);
}
startAutoplay();
});
onUnmounted(() => {
if (containerRef.value) {
containerRef.value.removeEventListener('mouseenter', handleMouseEnter);
containerRef.value.removeEventListener('mouseleave', handleMouseLeave);
}
stopAutoplay();
});
</script>

175
Build_God_Game/src/components/MissionCarousel/MissionCarousel.vue

@ -0,0 +1,175 @@
<template>
<div ref="containerRef" class="relative overflow-hidden rounded-[24px] border border-[#333] bg-[#111]"
:style="{ width: `${baseWidth}px` }">
<!-- 添加顶部占位区域与底部导航按钮区域对称 -->
<div class="h-[10px]"></div> <!-- pb-3(12px) + 按钮高度(约22px) 34px -->
<div class="p-3 pt-6 flex flex-col justify-between items-center">
<Motion tag="div" class="flex" drag="x" :dragConstraints="dragConstraints" :style="{
width: itemWidth + 'px',
gap: `${GAP}px`,
perspective: 1000,
perspectiveOrigin: `${currentIndex * trackItemOffset + itemWidth / 2}px 50%`,
x: motionX
}" @dragEnd="handleDragEnd" :animate="{ x: -(currentIndex * trackItemOffset) }" :transition="effectiveTransition"
@animationComplete="handleAnimationComplete">
<Motion v-for="(item, index) in carouselItems" :key="item.id" tag="div"
class="relative shrink-0 flex flex-col overflow-hidden cursor-grab active:cursor-grabbing bg-[#1a1a1a] rounded-[12px]"
:style="{
width: itemWidth + 'px',
height: `${cardHeight}px`,
rotateY: getRotateY(index)
}" :transition="effectiveTransition">
<slot :item="item" :index="index" :isActive="currentIndex === index" />
</Motion>
</Motion>
</div>
<div class="flex w-full justify-center pb-3 border-2 border-transparent">
<div class="flex w-[150px] justify-between px-8 border-2 border-transparent">
<Motion v-for="(_, index) in items" :key="index" tag="div"
class="h-2 w-2 rounded-full cursor-pointer transition-colors duration-150"
:class="currentIndex % items.length === index ? 'bg-white' : 'bg-[rgba(51,51,51,0.4)]'" :animate="{
scale: currentIndex % items.length === index ? 1.2 : 1
}" @click="() => setCurrentIndex(index)" :transition="{ duration: 0.15 }" />
</div>
</div>
</div>
</template>
<script lang="ts">
export interface MissionCarouselItem {
id: number;
}
</script>
<script setup lang="ts" generic="T extends MissionCarouselItem">
import { ref, computed, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';
import { Motion, useMotionValue, useTransform } from 'motion-v';
const DRAG_BUFFER = 0;
const VELOCITY_THRESHOLD = 500;
const GAP = 16;
const SPRING_OPTIONS = { type: 'spring' as const, stiffness: 300, damping: 30 };
interface Props {
items?: T[];
baseWidth?: number;
cardHeight?: number;
loop?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
items: () => [],
baseWidth: 320,
cardHeight: 200,
loop: false
});
const emit = defineEmits<{
(e: 'select', item: T): void;
}>();
const containerPadding = 12;
const itemWidth = computed(() => props.baseWidth - containerPadding * 2);
const trackItemOffset = computed(() => itemWidth.value + GAP);
const carouselItems = computed(() => (props.loop ? [...props.items, props.items[0]] : props.items));
const currentIndex = ref<number>(0);
const motionX = useMotionValue(0);
const isResetting = ref<boolean>(false);
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
const dragConstraints = computed(() => {
return props.loop
? {}
: {
left: -trackItemOffset.value * (carouselItems.value.length - 1),
right: 0
};
});
const effectiveTransition = computed(() => (isResetting.value ? { duration: 0 } : SPRING_OPTIONS));
const maxItems = Math.max(props.items.length + 1, 10);
const rotateYTransforms = Array.from({ length: maxItems }, (_, index) => {
const range = computed(() => [
-(index + 1) * trackItemOffset.value,
-index * trackItemOffset.value,
-(index - 1) * trackItemOffset.value
]);
const outputRange = [90, 0, -90];
return useTransform(motionX, range, outputRange, { clamp: false });
});
const getRotateY = (index: number) => {
return rotateYTransforms[index] || rotateYTransforms[0];
};
const setCurrentIndex = (index: number) => {
currentIndex.value = index;
if (props.items[index]) {
emit('select', props.items[index]);
}
};
const handleAnimationComplete = () => {
if (props.loop && currentIndex.value === carouselItems.value.length - 1) {
isResetting.value = true;
motionX.set(0);
currentIndex.value = 0;
setTimeout(() => {
isResetting.value = false;
}, 50);
}
};
interface DragInfo {
offset: { x: number; y: number };
velocity: { x: number; y: number };
}
const handleDragEnd = (event: Event, info: DragInfo) => {
const offset = info.offset.x;
const velocity = info.velocity.x;
if (offset < -DRAG_BUFFER || velocity < -VELOCITY_THRESHOLD) {
if (props.loop && currentIndex.value === props.items.length - 1) {
currentIndex.value = currentIndex.value + 1;
} else {
currentIndex.value = Math.min(currentIndex.value + 1, carouselItems.value.length - 1);
}
} else if (offset > DRAG_BUFFER || velocity > VELOCITY_THRESHOLD) {
if (props.loop && currentIndex.value === 0) {
currentIndex.value = props.items.length - 1;
} else {
currentIndex.value = Math.max(currentIndex.value - 1, 0);
}
}
const realIndex = props.loop && currentIndex.value === props.items.length ? 0 : currentIndex.value;
if (props.items[realIndex]) {
emit('select', props.items[realIndex]);
}
};
watch(
() => props.items.length,
() => {
if (currentIndex.value >= props.items.length && props.items.length > 0) {
currentIndex.value = props.items.length - 1;
}
}
);
onMounted(() => {
if (props.items.length > 0) {
emit('select', props.items[0]);
}
});
onUnmounted(() => {
// cleanup
});
</script>

4
Build_God_Game/src/views/CharacterView.vue

@ -465,8 +465,8 @@ const formatNumber = (num: number) => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px dashed red;
border-radius: 12px; border-radius: 15px;
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;

390
Build_God_Game/src/views/DailyMissionView.vue

@ -3,9 +3,28 @@ import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { dailyMissionApi, type DailyMission, DailyMissionStatus, RewardType } from '@/api/dailyMission' import { dailyMissionApi, type DailyMission, DailyMissionStatus, RewardType } from '@/api/dailyMission'
import Particles from '@/components/Particles/Particles.vue' import Particles from '@/components/Particles/Particles.vue'
import MissionCarousel from '@/components/MissionCarousel/MissionCarousel.vue'
import Carousel from '@/components/Carousel/Carousel.vue'
import type { CarouselItem } from '@/components/Carousel/Carousel.vue'
const router = useRouter() const router = useRouter()
const carouselItems: CarouselItem[] = [
{
title: "Custom Item",
description: "A custom carousel item.",
id: 1,
icon: "circle",
},
{
title: "Another Item",
description: "Another carousel item.",
id: 2,
icon: "layers",
},
// Add more items as needed
];
const missions = ref<DailyMission[]>([]) const missions = ref<DailyMission[]>([])
const todayStats = ref({ claimed: 0, total: 0 }) const todayStats = ref({ claimed: 0, total: 0 })
const loading = ref(false) const loading = ref(false)
@ -63,12 +82,6 @@ const getRewardIcon = (type: RewardType) => {
} }
} }
const formatTime = (dateStr?: string) => {
if (!dateStr) return ''
const date = new Date(dateStr)
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
const formatRemainingTime = (endTime?: string) => { const formatRemainingTime = (endTime?: string) => {
if (!endTime) return '' if (!endTime) return ''
const end = new Date(endTime) const end = new Date(endTime)
@ -139,14 +152,21 @@ const handleGoBack = () => {
router.push('/game') router.push('/game')
} }
const handleMissionSelect = (mission: DailyMission) => {
selectedMission.value = mission
}
onMounted(() => { onMounted(() => {
loadMissions() loadMissions()
timer = window.setInterval(updateRemainingTimes, 1000) timer = window.setInterval(updateRemainingTimes, 1000)
}) })
const activeMissions = computed(() => missions.value.filter(m => m.status !== DailyMissionStatus.Claimed)) const selectedMission = ref<DailyMission | null>(null)
const yesterdayMissions = computed(() => missions.value.filter(m => m.isFromYesterday && m.status === DailyMissionStatus.Completed)) const yesterdayMissions = computed(() => missions.value.filter(m => m.isFromYesterday && m.status === DailyMissionStatus.Completed))
const todayMissions = computed(() => missions.value.filter(m => !m.isFromYesterday)) const todayMissions = computed(() => missions.value.filter(m => !m.isFromYesterday))
const hasAnyMissions = computed(() => yesterdayMissions.value.length > 0 || todayMissions.value.length > 0)
</script> </script>
<template> <template>
@ -171,105 +191,112 @@ const todayMissions = computed(() => missions.value.filter(m => !m.isFromYesterd
</div> </div>
<div v-else class="missions-content"> <div v-else class="missions-content">
<div v-if="yesterdayMissions.length > 0" class="mission-section yesterday-section">
<!-- <div v-if="yesterdayMissions.length > 0" class="mission-section yesterday-section">
<div class="section-header"> <div class="section-header">
<span class="section-title">昨日任务</span> <span class="section-title">昨日任务</span>
<span class="section-badge highlight">待领取</span> <span class="section-badge highlight">待领取</span>
</div> </div>
<div class="mission-list"> <div class="carousel-wrapper">
<div v-for="mission in yesterdayMissions" :key="mission.id" class="mission-card highlight"> <MissionCarousel :items="yesterdayMissions" :base-width="340" :card-height="220"
<div class="mission-header"> @select="handleMissionSelect">
<span class="mission-name">{{ mission.missionTitle }}</span> <template #default="{ item }">
<span class="mission-status" :class="getStatusClass(mission.status)"> <div class="mission-card-content">
{{ getStatusText(mission.status) }} <div class="card-header">
</span> <span class="card-title">{{ item.missionTitle }}</span>
</div> <span class="card-status" :class="getStatusClass(item.status)">
<div class="mission-desc">{{ mission.missionDescription }}</div> {{ getStatusText(item.status) }}
<div class="mission-time"> </span>
<span v-if="mission.status === DailyMissionStatus.InProgress"> </div>
剩余时间: {{ remainingTimes[mission.id] || formatRemainingTime(mission.endTime) }} <div class="card-desc">{{ item.missionDescription }}</div>
</span> <div class="card-time">
<span v-else-if="mission.status === DailyMissionStatus.Completed"> <span v-if="item.status === DailyMissionStatus.InProgress">
已完成请领取奖励 剩余: {{ remainingTimes[item.id] || formatRemainingTime(item.endTime) }}
</span> </span>
<span v-else> <span v-else>
挂机时间: {{ mission.spendTimeMinutes }}分钟 {{ item.spendTimeMinutes }}分钟
</span> </span>
</div> </div>
<div class="mission-rewards"> <div class="card-rewards">
<span class="rewards-label">奖励:</span> <span class="reward-item exp-reward">{{ item.expReward }}</span>
<span class="reward-item exp-reward"> 经验 ×{{ mission.expReward }}</span> <span v-for="(reward, idx) in item.rewards.slice(0, 2)" :key="idx" class="reward-item">
<span v-for="(reward, idx) in mission.rewards" :key="idx" class="reward-item"> {{ getRewardIcon(reward.rewardType) }}{{ reward.count }}
{{ getRewardIcon(reward.rewardType) }}{{ reward.itemName || reward.rewardTypeName }}×{{ reward.count </span>
}} <span v-if="item.rewards.length > 2" class="reward-more">+{{ item.rewards.length - 2 }}</span>
</span> </div>
</div> <div class="card-actions">
<div class="mission-actions"> <button v-if="canAccept(item)" class="card-btn accept-btn" @click.stop="handleAccept(item)">
<button v-if="canAccept(mission)" class="action-btn accept-btn" @click="handleAccept(mission)"> 接取
接取任务 </button>
</button> <button v-if="canClaim(item)" class="card-btn claim-btn" @click.stop="handleClaim(item)">
<button v-if="canClaim(mission)" class="action-btn claim-btn" @click="handleClaim(mission)"> 领取
领取奖励 </button>
</button> <button v-if="item.status === DailyMissionStatus.InProgress" class="card-btn disabled-btn" disabled>
<button v-if="mission.status === DailyMissionStatus.InProgress" class="action-btn disabled-btn" 挂机中
disabled> </button>
挂机中... <button v-if="item.status === DailyMissionStatus.Claimed" class="card-btn disabled-btn" disabled>
</button> 已完成
</div> </button>
</div> </div>
</div> </div>
</div> </template>
</MissionCarousel>
</div>
</div> -->
<div v-if="todayMissions.length > 0" class="mission-section"> <div v-if="todayMissions.length > 0" class="mission-section">
<div class="section-header"> <div class="section-header">
<span class="section-title">今日任务</span> <span class="section-title">今日任务</span>
<span class="section-badge">{{ todayStats.claimed }}/{{ todayStats.total }}</span> <span class="section-badge">{{ todayStats.claimed }}/{{ todayStats.total }}</span>
</div> </div>
<div class="mission-list"> <div class="carousel-wrapper">
<div v-for="mission in todayMissions" :key="mission.id" class="mission-card" :class="{ claimed: mission.status === DailyMissionStatus.Claimed }"> <MissionCarousel :items="todayMissions" :base-width="340" :card-height="180" @select="handleMissionSelect"
<div class="mission-header"> :round="true" :loop="true">
<span class="mission-name">{{ mission.missionTitle }}</span> <template #default="{ item }">
<span class="mission-status" :class="getStatusClass(mission.status)"> <div class="mission-card-content">
{{ getStatusText(mission.status) }} <div class="card-header">
</span> <span class="card-title">{{ item.missionTitle }}</span>
</div> <span class="card-status" :class="getStatusClass(item.status)">
<div class="mission-desc">{{ mission.missionDescription }}</div> {{ getStatusText(item.status) }}
<div class="mission-time"> </span>
<span v-if="mission.status === DailyMissionStatus.InProgress"> </div>
剩余时间: {{ remainingTimes[mission.id] || formatRemainingTime(mission.endTime) }} <div class="card-desc">{{ item.missionDescription }}</div>
</span> <div class="card-time">
<span v-else-if="mission.status === DailyMissionStatus.Completed"> <span v-if="item.status === DailyMissionStatus.InProgress">
已完成请领取奖励 剩余: {{ remainingTimes[item.id] || formatRemainingTime(item.endTime) }}
</span> </span>
<span v-else> <span v-else>
挂机时间: {{ mission.spendTimeMinutes }}分钟 {{ item.spendTimeMinutes }}分钟
</span> </span>
</div> </div>
<div class="mission-rewards"> <div class="card-rewards">
<span class="rewards-label">奖励:</span> <span class="reward-item exp-reward">{{ item.expReward }}</span>
<span class="reward-item exp-reward"> 经验 ×{{ mission.expReward }}</span> <span v-for="(reward, idx) in item.rewards.slice(0, 2)" :key="idx" class="reward-item">
<span v-for="(reward, idx) in mission.rewards" :key="idx" class="reward-item"> {{ getRewardIcon(reward.rewardType) }}{{ reward.count }}
{{ getRewardIcon(reward.rewardType) }}{{ reward.itemName || reward.rewardTypeName }}×{{ reward.count </span>
}} <span v-if="item.rewards.length > 2" class="reward-more">+{{ item.rewards.length - 2 }}</span>
</span> </div>
</div> <div class="card-actions">
<div class="mission-actions"> <button v-if="canAccept(item)" class="card-btn accept-btn" @click.stop="handleAccept(item)">
<button v-if="canAccept(mission)" class="action-btn accept-btn" @click="handleAccept(mission)"> 接取
接取任务 </button>
</button> <button v-if="canClaim(item)" class="card-btn claim-btn" @click.stop="handleClaim(item)">
<button v-if="canClaim(mission)" class="action-btn claim-btn" @click="handleClaim(mission)"> 领取
领取奖励 </button>
</button> <button v-if="item.status === DailyMissionStatus.InProgress" class="card-btn disabled-btn" disabled>
<button v-if="mission.status === DailyMissionStatus.InProgress" class="action-btn disabled-btn" 挂机中
disabled> </button>
挂机中... <button v-if="item.status === DailyMissionStatus.Claimed" class="card-btn disabled-btn" disabled>
</button> 已完成
</div> </button>
</div> </div>
</div>
</template>
</MissionCarousel>
</div> </div>
</div> </div>
<div v-if="activeMissions.length === 0" class="empty-state"> <div v-if="!hasAnyMissions" class="empty-state">
<div class="empty-icon">📋</div> <div class="empty-icon">📋</div>
<div class="empty-text">今日暂无任务</div> <div class="empty-text">今日暂无任务</div>
<div class="empty-hint">请明日再来</div> <div class="empty-hint">请明日再来</div>
@ -409,160 +436,157 @@ const todayMissions = computed(() => missions.value.filter(m => !m.isFromYesterd
color: #ff4444; color: #ff4444;
} }
.mission-list { .carousel-wrapper {
display: flex; display: flex;
flex-direction: column; justify-content: center;
gap: 12px;
} }
.mission-card { .mission-card-content {
background: rgba(255, 255, 255, 0.03); width: 100%;
border: 1px solid rgba(255, 255, 255, 0.06); height: 100%;
border-radius: 10px; padding: 12px;
padding: 14px; display: flex;
transition: all 0.3s ease; flex-direction: column;
} box-sizing: border-box;
.mission-card.highlight {
background: rgba(255, 68, 68, 0.08);
border-color: rgba(255, 68, 68, 0.3);
box-shadow: 0 0 20px rgba(255, 68, 68, 0.1);
}
.mission-card.claimed {
opacity: 0.5;
}
.mission-card.claimed .mission-name {
text-decoration: line-through;
color: #666666;
}
.mission-card.claimed .mission-desc {
text-decoration: line-through;
} }
.mission-header { .card-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 8px; margin-bottom: 6px;
} }
.mission-name { .card-title {
color: #ffffff; color: #ffffff;
font-size: 0.95rem; font-size: 0.9rem;
font-weight: 500; font-weight: 600;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 8px;
} }
.mission-status { .card-status {
padding: 3px 10px; padding: 2px 8px;
border-radius: 10px; border-radius: 8px;
font-size: 0.75rem; font-size: 0.65rem;
font-weight: 500;
white-space: nowrap;
} }
.status-pending { .card-desc {
background: rgba(100, 100, 100, 0.3);
color: #888888; color: #888888;
font-size: 0.7rem;
line-height: 1.3;
margin-bottom: 6px;
flex: 1;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
} }
.status-progress { .card-time {
background: rgba(59, 130, 246, 0.2);
color: #3b82f6;
}
.status-completed {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
}
.status-claimed {
background: rgba(255, 255, 255, 0.1);
color: #666666;
}
.mission-desc {
color: #666666;
font-size: 0.85rem;
margin-bottom: 8px;
}
.mission-time {
color: #ff8844; color: #ff8844;
font-size: 0.8rem; font-size: 0.7rem;
margin-bottom: 10px; margin-bottom: 8px;
} }
.mission-rewards { .card-rewards {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 3px;
margin-bottom: 12px; margin-bottom: 8px;
}
.rewards-label {
color: #888888;
font-size: 0.8rem;
} }
.reward-item { .card-rewards .reward-item {
background: rgba(255, 136, 68, 0.1); background: rgba(255, 136, 68, 0.1);
border: 1px solid rgba(255, 136, 68, 0.2); border: 1px solid rgba(255, 136, 68, 0.2);
color: #ff8844; color: #ff8844;
padding: 3px 8px; padding: 1px 5px;
border-radius: 6px; border-radius: 4px;
font-size: 0.75rem; font-size: 0.65rem;
} }
.exp-reward { .card-rewards .exp-reward {
background: rgba(255, 215, 0, 0.1); background: rgba(255, 215, 0, 0.1);
border-color: rgba(255, 215, 0, 0.3); border-color: rgba(255, 215, 0, 0.3);
color: #ffd700; color: #ffd700;
} }
.mission-actions { .reward-more {
background: rgba(255, 255, 255, 0.1);
color: #888888;
padding: 1px 5px;
border-radius: 4px;
font-size: 0.65rem;
}
.card-actions {
display: flex; display: flex;
gap: 10px; gap: 6px;
} }
.action-btn { .card-btn {
flex: 1; flex: 1;
padding: 10px 16px; padding: 6px 10px;
border-radius: 8px; border-radius: 6px;
font-size: 0.85rem; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.2s ease;
border: none; border: none;
} }
.accept-btn { .card-btn.accept-btn {
background: linear-gradient(135deg, #ff8844 0%, #ff6644 100%); background: linear-gradient(135deg, #ff8844 0%, #ff6644 100%);
color: #ffffff; color: #ffffff;
} }
.accept-btn:hover { .card-btn.accept-btn:hover {
transform: scale(1.02); transform: scale(1.02);
box-shadow: 0 4px 15px rgba(255, 136, 68, 0.3); box-shadow: 0 2px 10px rgba(255, 136, 68, 0.3);
} }
.claim-btn { .card-btn.claim-btn {
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%); background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
color: #ffffff; color: #ffffff;
} }
.claim-btn:hover { .card-btn.claim-btn:hover {
transform: scale(1.02); transform: scale(1.02);
box-shadow: 0 4px 15px rgba(34, 197, 94, 0.3); box-shadow: 0 2px 10px rgba(34, 197, 94, 0.3);
} }
.disabled-btn { .card-btn.disabled-btn {
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
color: #555555; color: #555555;
cursor: not-allowed; cursor: not-allowed;
} }
.status-pending {
background: rgba(100, 100, 100, 0.3);
color: #888888;
}
.status-progress {
background: rgba(59, 130, 246, 0.2);
color: #3b82f6;
}
.status-completed {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
}
.status-claimed {
background: rgba(255, 255, 255, 0.1);
color: #666666;
}
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;

4
Build_God_Game/src/views/GameView.vue

@ -462,8 +462,8 @@ const handleBreakthrough = async () => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px dashed red;
border-radius: 12px; border-radius: 15px;
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;
width: 200px; width: 200px;

4
Build_God_Game/src/views/LoginView.vue

@ -77,7 +77,7 @@ const handleLogin = async () => {
{{ errorMsg }} {{ errorMsg }}
</div> </div>
<StarBorder as="div" color="Magenta" speed="3s" :thickness="3" @click="handleLogin"> <StarBorder as="div" color="greenyellow" speed="3s" :thickness="3" @click="handleLogin">
<div class="btn-login"> <div class="btn-login">
<span v-if="isLoading" class="loading-text">登录中...</span> <span v-if="isLoading" class="loading-text">登录中...</span>
<span v-else class="button-text"> </span> <span v-else class="button-text"> </span>
@ -233,6 +233,8 @@ const handleLogin = async () => {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border: 1px dashed greenyellow;
border-radius: 15px;
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;

4
Build_God_Game/src/views/RegisterView.vue

@ -120,7 +120,7 @@ const handleRegister = async () => {
{{ errorMsg }} {{ errorMsg }}
</div> </div>
<StarBorder as="div" color="Magenta" speed="3s" :thickness="3" @click="handleRegister"> <StarBorder as="div" color="yellowgreen" speed="3s" :thickness="3" @click="handleRegister">
<div class="btn-register"> <div class="btn-register">
<span v-if="isLoading" class="loading-text">注册中...</span> <span v-if="isLoading" class="loading-text">注册中...</span>
<span v-else-if="registerSuccess" class="button-text">注册成功</span> <span v-else-if="registerSuccess" class="button-text">注册成功</span>
@ -260,6 +260,8 @@ const handleRegister = async () => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
border: 1px dashed yellowgreen;
border-radius: 15px;
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;
height: 100%; height: 100%;

Loading…
Cancel
Save