Commit fad6862c by Hantao

feat: 新增第四关卡并优化关卡导航与进度管理

- 新增第四关卡页面,包含完成弹窗和徽章展示
- 在游戏状态管理中添加第四关卡状态跟踪
- 优化第二关导航流程,调整part4起始页跳转逻辑
- 在第二、三关卡添加成功组件展示徽章
- 更新首页进度计算逻辑,支持第四关卡显示
- 添加静态资源文件支持新关卡
parent 0abe465b
...@@ -81,6 +81,12 @@ ...@@ -81,6 +81,12 @@
"style": { "style": {
"navigationStyle": "custom" "navigationStyle": "custom"
} }
},
{
"path": "pages/fourth/index",
"style": {
"navigationStyle": "custom"
}
} }
], ],
"globalStyle": { "globalStyle": {
......
<script setup>
import { ref } from 'vue';
import { useGameStore } from '@/stores/game';
import navBar from '@/components/navBar.vue';
import success from '@/components/success.vue';
const gameStore = useGameStore();
const ASSETS = Object.freeze({
completionPopup: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/d204d71cf2a54b18a6404e6132618cd6%E5%AE%8C%E6%88%90%E5%BC%B9%E7%AA%97.webp',
});
const showCompletionModal = ref(false);
const showSuccess1 = ref(false);
const showSuccess2 = ref(false);
const handleFinish = () => {
showCompletionModal.value = true;
};
const closeCompletionModal = () => {
showCompletionModal.value = false;
showSuccess1.value = true;
};
const handleSuccess1Confirm = () => {
showSuccess1.value = false;
showSuccess2.value = true;
};
const handleSuccess2Confirm = () => {
showSuccess2.value = false;
gameStore.setFourthLevelStatus(true);
uni.reLaunch({ url: '/pages/start/index' });
};
</script>
<template>
<navBar />
<view class="next" @click="handleFinish">完成</view>
<!-- 完成弹窗 - 全屏覆盖 -->
<view class="completion-modal" v-if="showCompletionModal" @click="closeCompletionModal">
<image
class="modal-image"
:src="ASSETS.completionPopup"
mode="scaleToFill"
/>
</view>
<!-- 第一个徽章 -->
<success
:show="showSuccess1"
:badge-index="4"
title="精准控水达人"
sub-title="已完成第四关卡"
@close="showSuccess1 = false"
@confirm="handleSuccess1Confirm"
/>
<!-- 第二个徽章 -->
<success
:show="showSuccess2"
:badge-index="5"
title="水质专家"
sub-title="已完成全部关卡"
@close="showSuccess2 = false"
@confirm="handleSuccess2Confirm"
/>
</template>
<style scoped lang="scss">
.next {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 40rpx;
color: #333;
}
.completion-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
.modal-image {
width: 100%;
height: 100%;
}
}
</style>
\ No newline at end of file
<script setup> <script setup>
import navBar from '@/components/navBar.vue'; import navBar from '@/components/navBar.vue';
import success from '@/components/success.vue';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { onShow } from '@dcloudio/uni-app'; import { onShow } from '@dcloudio/uni-app';
import { useGameStore } from '@/stores/game'; import { useGameStore } from '@/stores/game';
const gameStore = useGameStore(); const gameStore = useGameStore();
const showSuccess = ref(false);
const ASSETS = Object.freeze({ const ASSETS = Object.freeze({
bg: '/static/second/bg.webp', bg: '/static/second/bg.webp',
...@@ -34,7 +36,8 @@ const currentStep = computed(() => { ...@@ -34,7 +36,8 @@ const currentStep = computed(() => {
}); });
const currentInfo = computed(() => { const currentInfo = computed(() => {
const step = currentStep.value >= 4 ? 3 : currentStep.value; const step = currentStep.value;
if (step >= 4) return ASSETS.info; // 全部完成显示 info
return INFO_MAP[step] || ASSETS.info1; return INFO_MAP[step] || ASSETS.info1;
}); });
...@@ -46,8 +49,7 @@ const currentJdt = computed(() => { ...@@ -46,8 +49,7 @@ const currentJdt = computed(() => {
const handleNext = () => { const handleNext = () => {
const step = currentStep.value; const step = currentStep.value;
if (step >= 4) { if (step >= 4) {
// 如果已经全部完成,跳转到第三关 showSuccess.value = true;
uni.navigateTo({ url: '/pages/third/index' });
return; return;
} }
...@@ -55,11 +57,16 @@ const handleNext = () => { ...@@ -55,11 +57,16 @@ const handleNext = () => {
0: '/pages/second/part1', 0: '/pages/second/part1',
1: '/pages/second/part2', 1: '/pages/second/part2',
2: '/pages/second/part3', 2: '/pages/second/part3',
3: '/pages/second/part4/index', 3: '/pages/second/part4/start',
}; };
const url = urlMap[step]; const url = urlMap[step];
uni.navigateTo({ url }); uni.navigateTo({ url });
}; };
const handleSuccessConfirm = () => {
showSuccess.value = false;
uni.reLaunch({ url: '/pages/start/index' });
};
</script> </script>
<template> <template>
...@@ -89,6 +96,14 @@ const handleNext = () => { ...@@ -89,6 +96,14 @@ const handleNext = () => {
</view> </view>
</view> </view>
</view> </view>
<success
:show="showSuccess"
:badge-index="2"
title="滤芯工程师"
sub-title="已完成第二关卡"
@confirm="handleSuccessConfirm"
@close="showSuccess = false"
/>
</view> </view>
</template> </template>
......
<script setup> <script setup>
import { useGameStore } from '@/stores/game'; import { useGameStore } from '@/stores/game';
import navBar from '@/components/navBar.vue';
const gameStore = useGameStore(); const gameStore = useGameStore();
...@@ -10,6 +11,7 @@ const handleNext = () => { ...@@ -10,6 +11,7 @@ const handleNext = () => {
</script> </script>
<template> <template>
<navBar />
<view class="next" @click="handleNext">下一关</view> <view class="next" @click="handleNext">下一关</view>
</template> </template>
......
...@@ -122,9 +122,7 @@ const handleComplete = () => isComplete.value && (showCompletion.value = true); ...@@ -122,9 +122,7 @@ const handleComplete = () => isComplete.value && (showCompletion.value = true);
const closeCompletion = () => { const closeCompletion = () => {
showCompletion.value = false; showCompletion.value = false;
gameStore.setSecondLevelPartStatus('part4', true); gameStore.setSecondLevelPartStatus('part4', true);
uni.reLaunch({ uni.reLaunch({ url: '/pages/second/index' });
url: '/pages/second/index'
});
}; };
// 获取电池图标 // 获取电池图标
...@@ -662,4 +660,4 @@ onMounted(() => { ...@@ -662,4 +660,4 @@ onMounted(() => {
height: 100%; height: 100%;
} }
} }
</style> </style>
\ No newline at end of file
<script setup> <script setup>
import navBar from '@/components/navBar.vue';
const ASSETS = Object.freeze({ const ASSETS = Object.freeze({
bg: '/static/second/part4/start/bg.webp', bg: '/static/second/part4/start/bg.webp',
battery1: '/static/second/part4/start/1.webp', battery1: '/static/second/part4/start/1.webp',
...@@ -15,10 +16,15 @@ const ASSETS = Object.freeze({ ...@@ -15,10 +16,15 @@ const ASSETS = Object.freeze({
sz: '/static/sz.webp', sz: '/static/sz.webp',
jt: '/static/second/part4/start/jt.webp', jt: '/static/second/part4/start/jt.webp',
}); });
const handleStart = () => {
uni.navigateTo({ url: '/pages/second/part4/index' });
};
</script> </script>
<template> <template>
<view class="container"> <view class="container">
<navBar />
<image class="bg-img" :src="ASSETS.bg" mode="aspectFill" /> <image class="bg-img" :src="ASSETS.bg" mode="aspectFill" />
<view class="title">滤芯排序游戏</view> <view class="title">滤芯排序游戏</view>
<view class="info"> <view class="info">
...@@ -39,7 +45,7 @@ const ASSETS = Object.freeze({ ...@@ -39,7 +45,7 @@ const ASSETS = Object.freeze({
<view class="battery-container"></view> <view class="battery-container"></view>
<image class="drag-bg" :src="ASSETS.dragBg" mode="scaleToFill" /> <image class="drag-bg" :src="ASSETS.dragBg" mode="scaleToFill" />
<view class="hint-text">拖到这里!</view> <view class="hint-text">拖到这里!</view>
<image class="right-icon" :src="ASSETS.right" mode="scaleToFill" /> <image class="right-icon" :src="ASSETS.right" mode="scaleToFill" @click="handleStart" />
</view> </view>
<view class="desc"> <view class="desc">
<view class="desc-text">后置碳棒在第四层!</view> <view class="desc-text">后置碳棒在第四层!</view>
...@@ -259,4 +265,4 @@ const ASSETS = Object.freeze({ ...@@ -259,4 +265,4 @@ const ASSETS = Object.freeze({
text-shadow: 1rpx 1rpx 0 #FFF, -1rpx -1rpx 0 #FFF, 1rpx -1rpx 0 #FFF, -1rpx 1rpx 0 #FFF; text-shadow: 1rpx 1rpx 0 #FFF, -1rpx -1rpx 0 #FFF, 1rpx -1rpx 0 #FFF, -1rpx 1rpx 0 #FFF;
} }
} }
</style> </style>
\ No newline at end of file
...@@ -31,18 +31,32 @@ const ASSETS = Object.freeze ({ ...@@ -31,18 +31,32 @@ const ASSETS = Object.freeze ({
// 根据 store 计算进度 // 根据 store 计算进度
const progressLevel = computed(() => { const progressLevel = computed(() => {
if (gameStore.levels.third.completed) return 5; const { first, second, third, fourth } = gameStore.levels;
if (gameStore.levels.second.completed) return 4;
if (fourth.completed) return 5;
// 第二关的部分进度 if (fourth.started) return 4;
const parts = gameStore.levels.second.parts;
const completedParts = Object.values(parts).filter(p => p).length; if (third.completed) return 4;
if (completedParts > 0) { if (third.started) return 3;
// 映射到 1-4 之间的进度
return 1 + completedParts; if (second.completed) return 3;
} if (second.started) return 2;
if (gameStore.levels.first.completed) return 1; if (first.completed) return 2;
if (first.started) return 1;
return 0;
});
// 根据 store 计算获得的勋章数
const badgeLevel = computed(() => {
const { first, second, third, fourth } = gameStore.levels;
if (fourth.completed) return 5;
if (third.completed) return 3;
if (second.completed) return 2;
if (first.completed) return 1;
return 0; return 0;
}); });
...@@ -60,15 +74,14 @@ const getProgressImage = () => { ...@@ -60,15 +74,14 @@ const getProgressImage = () => {
}; };
const getBadgeImage = () => { const getBadgeImage = () => {
const level = Math.floor(progressLevel.value); const level = badgeLevel.value;
switch (level) { switch (level) {
case 0: return;
case 1: return ASSETS.badge1; case 1: return ASSETS.badge1;
case 2: return ASSETS.badge2; case 2: return ASSETS.badge2;
case 3: return ASSETS.badge3; case 3: return ASSETS.badge3;
case 4: return ASSETS.badge4; case 4: return ASSETS.badge4;
case 5: return ASSETS.badge5; case 5: return ASSETS.badge5;
default: return ASSETS.badge1; default: return;
} }
}; };
...@@ -76,13 +89,21 @@ const getBadgeImage = () => { ...@@ -76,13 +89,21 @@ const getBadgeImage = () => {
const isSidebarExpanded = ref(false); const isSidebarExpanded = ref(false);
const handleStart = () => { const handleStart = () => {
const level = Math.floor(progressLevel.value); const { first, second, third, fourth } = gameStore.levels;
if (level === 0) {
if (!first.completed) {
gameStore.setLevelStarted('first');
uni.navigateTo({ url: '/pages/first/index' }); uni.navigateTo({ url: '/pages/first/index' });
} else if (level < 4) { } else if (!second.completed) {
gameStore.setLevelStarted('second');
uni.navigateTo({ url: '/pages/second/index' }); uni.navigateTo({ url: '/pages/second/index' });
} else { } else if (!third.completed) {
gameStore.setLevelStarted('third');
uni.navigateTo({ url: '/pages/third/index' }); uni.navigateTo({ url: '/pages/third/index' });
} else if (!fourth.completed) {
gameStore.setLevelStarted('fourth');
// 跳转到第四关的起始页
uni.navigateTo({ url: '/pages/fourth/index' });
} }
}; };
...@@ -93,15 +114,18 @@ const handleRestart = () => { ...@@ -93,15 +114,18 @@ const handleRestart = () => {
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
gameStore.resetAllProgress(); gameStore.resetAllProgress();
uni.showToast({
title: '进度已重置',
icon: 'success'
});
} }
} }
}); });
}; };
const navToBadgePage = () => { const navToBadgePage = () => {
const badge = Math.floor(progressLevel.value);
uni.navigateTo({ uni.navigateTo({
url: `/pages/badge/index?badge=${badge}` url: `/pages/badge/index?badge=${badgeLevel.value}`
}); });
}; };
...@@ -166,7 +190,7 @@ onShareAppMessage(() => { ...@@ -166,7 +190,7 @@ onShareAppMessage(() => {
<!-- 按钮区域 --> <!-- 按钮区域 -->
<view class="btn-group"> <view class="btn-group">
<view class="btn-item" @click="handleStart"> <view class="btn-item" @click="handleStart">
<image class="btn-img start-btn" :src="progressImgs === 5 ? ASSETS.finishBtn : ASSETS.startBtn" mode="widthFix" /> <image class="btn-img start-btn" :src="progressLevel === 5 ? ASSETS.finishBtn : ASSETS.startBtn" mode="widthFix" />
</view> </view>
<view class="btn-item" @click="handleRestart"> <view class="btn-item" @click="handleRestart">
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad } from '@dcloudio/uni-app';
import navBar from '@/components/navBar.vue'; import navBar from '@/components/navBar.vue';
import success from '@/components/success.vue';
import Beaker from './components/beaker.vue'; import Beaker from './components/beaker.vue';
import TabsInstructionsPanel from './components/TabsInstructionsPanel.vue'; import TabsInstructionsPanel from './components/TabsInstructionsPanel.vue';
import BottomActionBar from './components/BottomActionBar.vue'; import BottomActionBar from './components/BottomActionBar.vue';
import { useGameStore } from '@/stores/game'; import { useGameStore } from '@/stores/game';
const gameStore = useGameStore(); const gameStore = useGameStore();
const showSuccess = ref(false);
const assets = { const assets = {
bg: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/963079c98b914f349fe9ff5e600d0f65%E6%B0%B4%E8%B4%A8%E6%A3%80%E6%B5%8B%E7%AB%99_%E7%BA%AF%E8%83%8C%E6%99%AF%201.webp', bg: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/963079c98b914f349fe9ff5e600d0f65%E6%B0%B4%E8%B4%A8%E6%A3%80%E6%B5%8B%E7%AB%99_%E7%BA%AF%E8%83%8C%E6%99%AF%201.webp',
...@@ -61,9 +63,12 @@ const handleShowCompletion = () => { ...@@ -61,9 +63,12 @@ const handleShowCompletion = () => {
const closeCompletion = () => { const closeCompletion = () => {
showCompletion.value = false; showCompletion.value = false;
gameStore.setThirdLevelStatus(true); gameStore.setThirdLevelStatus(true);
uni.reLaunch({ showSuccess.value = true;
url: '/pages/start/index' };
});
const handleSuccessConfirm = () => {
showSuccess.value = false;
uni.reLaunch({ url: '/pages/start/index' });
}; };
</script> </script>
...@@ -107,6 +112,15 @@ const closeCompletion = () => { ...@@ -107,6 +112,15 @@ const closeCompletion = () => {
@click.stop="closeCompletion" @click.stop="closeCompletion"
/> />
</view> </view>
<success
:show="showSuccess"
:badge-index="3"
title="水质分析高手"
sub-title="已完成第三关卡"
@confirm="handleSuccessConfirm"
@close="showSuccess = false"
/>
</view> </view>
</template> </template>
......
...@@ -5,10 +5,12 @@ export const useGameStore = defineStore('game', { ...@@ -5,10 +5,12 @@ export const useGameStore = defineStore('game', {
// 关卡完成状态 // 关卡完成状态
levels: { levels: {
first: { first: {
started: false,
completed: false, completed: false,
lastStage: 'garbageCleanup', // 当前完成到的阶段 lastStage: 'garbageCleanup', // 当前完成到的阶段
}, },
second: { second: {
started: false,
completed: false, completed: false,
parts: { parts: {
part1: false, part1: false,
...@@ -18,11 +20,22 @@ export const useGameStore = defineStore('game', { ...@@ -18,11 +20,22 @@ export const useGameStore = defineStore('game', {
} }
}, },
third: { third: {
started: false,
completed: false
},
fourth: {
started: false,
completed: false completed: false
} }
} }
}), }),
actions: { actions: {
// 设置关卡开始状态
setLevelStarted(level) {
if (this.levels[level]) {
this.levels[level].started = true;
}
},
// 设置第一关状态 // 设置第一关状态
setFirstLevelStatus(status, stage) { setFirstLevelStatus(status, stage) {
this.levels.first.completed = status; this.levels.first.completed = status;
...@@ -46,14 +59,20 @@ export const useGameStore = defineStore('game', { ...@@ -46,14 +59,20 @@ export const useGameStore = defineStore('game', {
setThirdLevelStatus(status) { setThirdLevelStatus(status) {
this.levels.third.completed = status; this.levels.third.completed = status;
}, },
// 设置第四关状态
setFourthLevelStatus(status) {
this.levels.fourth.completed = status;
},
// 重置所有进度 // 重置所有进度
resetAllProgress() { resetAllProgress() {
this.levels = { this.levels = {
first: { first: {
started: false,
completed: false, completed: false,
lastStage: 'garbageCleanup', lastStage: 'garbageCleanup',
}, },
second: { second: {
started: false,
completed: false, completed: false,
parts: { parts: {
part1: false, part1: false,
...@@ -63,6 +82,11 @@ export const useGameStore = defineStore('game', { ...@@ -63,6 +82,11 @@ export const useGameStore = defineStore('game', {
} }
}, },
third: { third: {
started: false,
completed: false
},
fourth: {
started: false,
completed: false completed: false
} }
}; };
...@@ -75,4 +99,4 @@ export const useGameStore = defineStore('game', { ...@@ -75,4 +99,4 @@ export const useGameStore = defineStore('game', {
setItem: (key, value) => uni.setStorageSync(key, value), setItem: (key, value) => uni.setStorageSync(key, value),
}, },
}, },
}); });
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment