浏览代码

通关后的弹窗

丸子 4 月之前
父节点
当前提交
5a0968732a

二进制
src/assets/blockly/coppercup.png


二进制
src/assets/blockly/copperstar.png


二进制
src/assets/blockly/cupbg.png


二进制
src/assets/blockly/goldcup.png


二进制
src/assets/blockly/goldstar.png


二进制
src/assets/blockly/nocup.png


二进制
src/assets/blockly/nocupbg.png


二进制
src/assets/blockly/nostar.png


二进制
src/assets/blockly/silvercup.png


二进制
src/assets/blockly/silverstar.png


二进制
src/assets/blockly/star01.png


二进制
src/assets/blockly/star02.png


+ 253 - 7
src/components/blockly/MapGame.vue

@@ -1,4 +1,43 @@
 <template>
+  <!-- 全屏遮罩盒子 通关后弹框得奖杯 -->
+  <div class="fullscreen-overlay" v-if="showOverlay">
+    <div 
+      class="centered-box" 
+      :style="{
+        backgroundImage: `url(${cupbg})`,
+         backgroundSize: '110%', 
+         backgroundPosition: 'center',
+         backgroundRepeat: 'no-repeat'
+      }">
+      <!-- 彩带飘落效果 -->
+      <div class="confetti-container">
+        <div v-for="n in 50" :key="n" class="confetti" :class="`confetti-${n % 12 + 1}`"></div>
+      </div>
+      <!-- 关闭按钮 -->
+      <div class="close-button" @click="closeOverlay">×</div>
+      <!-- 题目 -->
+      <div class="top-box">
+        <div class="title-text">YOU WIN!</div>
+      </div>
+      <!-- 奖杯 -->
+      <div class="middle-box">
+        <img :src="goldcup" alt="奖杯" class="gold-cup-image" />
+      </div>
+      <!-- 星星 -->
+      <div class="bottom-box">
+        <img 
+          v-for="index in starCount" 
+          :key="index" 
+          :src="star02" 
+          alt="星星" 
+          class="gold-star-image" 
+          :class="index % 2 === 0 ? 'star-bottom' : 'star-top'" 
+        />
+      </div>
+      
+    </div>
+  </div>
+  
   <div class="title-box">
     <!-- 左侧标题部分 -->
     <div class="left-container">
@@ -81,7 +120,7 @@
       <div class="workspace-section">
         <div class="controls">
           <button id="runCode" @click="runCode" :disabled="isRunning">运行代码</button>
-<!--          <button @click="generatePythonCode">生成Python代码</button>-->
+            <!-- <button @click="generatePythonCode">生成Python代码</button>-->
           <button @click="resetPlayer" >重置玩家</button>
           <button @click="clearWorkspace">清空工作区</button>
         </div>
@@ -90,6 +129,7 @@
       </div>
     </div>
   </div>
+  
 </template>
 
 <script setup>
@@ -107,6 +147,21 @@ import errorMp3 from '@/assets/music/blockly/error.MP3'
 import { registerCustomBlocks, registerJavaScriptGenerators, registerPythonGenerators, initBlockly } from '@/api/blockly/blockly.js';
 import {playMp3} from "@/api/blockly/music.js";
 
+
+import cupbg from '@/assets/blockly/cupbg.png' // 通关后奖杯底图
+import nocupbg from '@/assets/blockly/nocupbg.png' // 通关失败后奖杯底图
+import goldcup from '@/assets/blockly/goldcup.png' // 金杯
+import silvercup from '@/assets/blockly/silvercup.png' // 银杯
+import coppercup from '@/assets/blockly/coppercup.png' // 铜杯
+import nocup from '@/assets/blockly/nocup.png' // 无杯状态
+import star02 from '@/assets/blockly/star02.png' // 星星
+import star01 from '@/assets/blockly/star01.png' // 金星
+
+// 星星数量,可根据需要调整
+const starCount = ref(3);
+
+
+
 // 定义emits
 const emits = defineEmits(['saveProgress'])
 // 定义组件属性
@@ -178,6 +233,12 @@ const props = defineProps({
   }
 });
 
+// 关闭遮罩层
+const closeOverlay = () => {
+  gameState.player.hasReachedEnd = false;
+  showOverlay.value = false;
+};
+
 // 配置常量
 const CONFIG = {
   // 动画时长配置(毫秒)
@@ -310,6 +371,9 @@ const gameState = reactive({
     originalWalkablePoints: [],
   }
 });
+
+// 控制遮罩层显示的状态
+const showOverlay = ref(false);
 // 地图元素类型字典
 const BLOCKLY_MAP_TYPE_DICT = {
   ICE: 'ice',//冰块
@@ -754,6 +818,7 @@ const resetPlayer = async () => {
   gameState.player.isColliding = false; //碰撞标志
   gameState.player.hasReachedEnd = false;
   gameState.player.isSliding = false; // 重置滑行状态
+  showOverlay.value = false; // 隐藏遮罩层
 };
 
 // 清空工作区
@@ -1186,32 +1251,30 @@ window.isFinish = async function() {
   if (isColliding.value) {
     return;
   }
-
   if (gameState.player.position.x === endPoint.value.x && gameState.player.position.y === endPoint.value.y) {
-
     // 统计所有类型为TASK的任务点总数||必须完成拾取物品的人物总数
     const totalTasks = gameState.mapData.walkablePoints.filter(
         p => p.type === BLOCKLY_MAP_TYPE_DICT.TASK ||
             p.type === BLOCKLY_MAP_TYPE_DICT.ITEM && p.must === true
     ).length;
-
     // 统计其中status为true的完成任务数||完成拾取物品的人物总数
     const completedTasks = gameState.mapData.walkablePoints.filter(
         p => p.type === BLOCKLY_MAP_TYPE_DICT.TASK && p.status === true
                 || p.type === BLOCKLY_MAP_TYPE_DICT.ITEM && p.must === true && p.status === true
     ).length;
-
     //blockly总星星数量
     const starCount = 3;
-
     //无任务情况下直接完成
     if (totalTasks === 0 || completedTasks === totalTasks) {
       gameState.player.hasReachedEnd = true;
+      // 延迟显示遮罩层,玩家动画完成后显示
+      setTimeout(() => {
+        showOverlay.value = true;
+      }, 1500);
       emits('saveProgress', 'blockly', starCount * 100)
       showGameMessage(CONFIG.TIPS.FINISH, 'success' );
       return;
     }
-
     //任务失败
     // 计算完成百分比
     const completionPercentage = totalTasks > 0 ? Math.round(completedTasks / totalTasks * starCount) : starCount;
@@ -1702,6 +1765,189 @@ onUnmounted(() => {
   @return math.div($px, 750) * 100vw;
 }
 
+/* 全屏遮罩盒子样式 */
+.fullscreen-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5); /* 半透明黑色 遮罩 */
+  z-index: 9999; /* 确保在最上层 */
+  // pointer-events: none; /* 允许点击穿透 */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+/* 垂直居中盒子样式 */
+.centered-box {
+  border-radius: rpx(10);
+  z-index: 10000;
+  width: rpx(220);
+  height: rpx(255);
+  overflow: auto;
+  pointer-events: auto;
+  // display: flex;
+  flex-direction: column;
+  position: relative;
+  /* 初始状态:缩小、透明 */
+  transform: scale(0.7);
+  opacity: 0;
+  /* 动画效果 */
+  animation: popUp 0.5s ease-out forwards;
+}
+
+/* 弹出动画定义 */
+@keyframes popUp {
+  0% {
+    transform: scale(0.7);
+    opacity: 0;
+  }
+  70% {
+    transform: scale(1.05);
+    opacity: 0.9;
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+/* 彩带容器样式 */
+.confetti-container {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  z-index: 9999;
+  pointer-events: none;
+}
+
+/* 彩带基本样式 */
+.confetti {
+  position: absolute;
+  width: rpx(5);
+  height: rpx(15);
+  opacity: 0;
+  animation-timing-function: ease-in-out;
+  animation-iteration-count: infinite;
+  animation-fill-mode: forwards;
+}
+
+/* 彩带颜色和动画 */
+.confetti-1 { background-color: #FF5252; animation: confetti-fall 2s 0.2s linear infinite; left: 5%; }
+.confetti-2 { background-color: #536DFE; animation: confetti-fall 2.7s 0.9s linear infinite; left: 15%; }
+.confetti-3 { background-color: #FFC107; animation: confetti-fall 2.3s 1.8s linear infinite; left: 25%; }
+.confetti-4 { background-color: #4CAF50; animation: confetti-fall 2.1s 1.4s linear infinite; left: 35%; }
+.confetti-5 { background-color: #9C27B0; animation: confetti-fall 2.5s 1.8s linear infinite; left: 45%; }
+.confetti-6 { background-color: #2196F3; animation: confetti-fall 2.2s 0.2s linear infinite; left: 55%; }
+.confetti-7 { background-color: #FF9800; animation: confetti-fall 2.4s 1.8s linear infinite; left: 65%; }
+.confetti-8 { background-color: #795548; animation: confetti-fall 2s 1.4s linear infinite; left: 75%; }
+.confetti-9 { background-color: #607D8B; animation: confetti-fall 2.6s 3.4s linear infinite; left: 85%; }
+.confetti-10 { background-color: #E91E63; animation: confetti-fall 2.3s 1.8s linear infinite; left: 95%; }
+
+/* 彩带飘落动画 */
+@keyframes confetti-fall {
+  0% {
+    top: -10%;
+    opacity: 0;
+    transform: rotate(0deg);
+  }
+  10% {
+    opacity: 1;
+  }
+  90% {
+    opacity: 1;
+  }
+  100% {
+    top: 100%;
+    opacity: 0;
+    transform: rotate(360deg);
+  }
+}
+
+/* 关闭按钮样式 */
+.close-button {
+  position: absolute;
+  top: rpx(0);
+  right: rpx(2);
+  font-size: rpx(20);
+  color: #2b8fdd;
+  font-weight: bold;
+  cursor: pointer;
+  width: rpx(25);
+  height: rpx(25);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 10001;
+  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
+  // -webkit-text-stroke: 1px white;  // 描
+}
+.close-button:hover {
+  transform: scale(1.1);
+}
+
+.top-box {
+  width: 100%;
+  height: rpx(60);
+  border-radius: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.title-text {
+  font-size: rpx(20);
+  font-weight: bold;
+  color: white;
+  font-family: 'SourceHanSansCN-Bold_0';
+  text-align: center;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+.middle-box {
+  width: 100%;
+  height: rpx(130);
+  border-radius: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.gold-cup-image{
+  height: 100%;
+  width: 100%;
+  object-fit: contain;
+}
+
+.bottom-box {
+  width: 100%;
+  height: rpx(40);
+  border-radius: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: flex-start;
+  gap: rpx(12);
+  margin-top: -rpx(10); /* 向上调整位置 */
+}
+
+.gold-star-image {
+  width: rpx(35);
+  height: 100%;
+  object-fit: contain;
+}
+.star-top {
+  object-position: top;
+}
+.star-bottom {
+  object-position: bottom;
+}
+
 //将tileSize属性绑定到CSS变量上
 :root {
   --tile-size: v-bind('tileSize + "px"');

+ 2 - 2
src/style.css

@@ -2,8 +2,8 @@
 @font-face {
   font-family: 'SourceHanSansCN-Normal';
   src: url('@/assets/typeface/SourceHanSansCN-Medium_0.otf') format('opentype');
-  font-weight: normal;
-  font-style: normal;
+  /* font-weight: normal;
+  font-style: normal; */
 }
 :root {
   font-family: 'SourceHanSansCN-Normal';

+ 1 - 1
src/views/programming/ProgrammingGame.vue

@@ -87,7 +87,7 @@ import { getThemeList } from '@/api/programming/index.js'
 // 创建路由实例
 const router = useRouter()
 // 当前激活的按钮索引
-const activeButton = ref(0)
+const activeButton = ref(-1)
 // 定义圆形按钮数据
 const circleButtons = reactive([])
 // 定义课程类别数据