Commit d3256357 by CHEN\chenXi

第一关,水源危机

parent 585effb8
......@@ -20,6 +20,18 @@
}
},
{
"path": "pages/miniGame-one/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/miniGame-two/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/miniGame-three/index",
"style": {
"navigationStyle": "custom"
......
......@@ -90,6 +90,13 @@
/>
</view>
</view>
<!--优惠券弹窗-->
<view class="popup-coupon" v-if="showCouponPopup">
<!-- 优惠券弹窗内容 -->
<view class="coupon-content">
<image class="coupon-image" src="../../static/game-four/waterCoupon.png" mode="aspectFit" style="width: 626rpx; height: 780rpx;margin: 0 auto;"/>
</view>
</view>
</view>
</template>
......@@ -103,7 +110,7 @@ const timer = ref(null) // 定时器
const speed = 0.5 // 每100ms增加的进度百分比
const showPopup = ref(false) // 是否显示弹窗
const timeoutId = ref(null) // 定时器ID
const showCouponPopup = ref(false) // 是否显示优惠券弹窗(初始设为false)
// 当前显示的水杯图片
const currentShuibeiImage = ref('../../static/game-four/35water.png')
......@@ -242,11 +249,20 @@ const resetProgress = () => {
currentShuibeiImage.value = SHUIBEI_IMAGES.PERCENT_35
// 关闭弹窗
closePopup()
// 同时关闭优惠券弹窗
showCouponPopup.value = false
}
// 关闭弹窗
const closePopup = () => {
// 关闭主弹窗
showPopup.value = false
// 延迟300ms后显示优惠券弹窗
setTimeout(() => {
showCouponPopup.value = true
}, 300)
// 清除定时器
if (timeoutId.value) {
clearTimeout(timeoutId.value)
......@@ -516,6 +532,20 @@ onUnmounted(() => {
}
}
}
/* 优惠券弹窗样式 - 添加优惠券弹窗的样式 */
.popup-coupon {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
}
/* 为所有图片添加公共样式 */
image {
......
<template>
<view class="container">
<view class="content">
<view class="top">
<image src="../../../static/game-one/water.png" style="width: 48rpx; height: 48rpx;" />
<view class="title">小知识点:</view>
</view>
<view class="text">“我国80%的城市水源面临污染挑战,安吉尔RO膜能够过滤99%的污染物,守护千万家饮水与用水的安全。”</view>
</view>
</view>
</template>
<script setup></script>
<style scoped lang="scss">
.container {
width: 100%;
height: 100%;
position: relative;
.content {
position: absolute;
top: 500rpx;
left: 50%;
transform: translate(-50%, -50%);
width: 456rpx;
height: 260rpx;
border-radius: 24rpx;
border: 3rpx solid #FFF;
background: #F0F9FC;
box-shadow: 0 8rpx 8rpx 0 #0b768266, 0 0 39.6rpx 0 #C8EEFF inset;
.top {
display: flex;
align-items: center;
padding: 20rpx 20rpx 12rpx;
.title {
font-size: 34rpx;
color: #2d4b5d;
font-weight: 600;
margin-left: 12rpx;
}
}
.text {
padding: 0 20rpx;
color: #2d4b5d;
font-size: 28rpx;
font-weight: 400;
}
}
}
</style>
\ No newline at end of file
<template>
<view class="garbage-cleanup">
<view class="content">
<view class="cleartitle">清理垃圾</view>
<view class="clearContent">
<view class="clearItem">
<!-- one垃圾图片 -->
<image class="item one" :class="{ removed: isOneRemoved }"
src="../../static/game-one/rubbish-one.png" mode="aspectFill"></image>
<image class="item itemArrow" src="../../static/game-one/arrowLeft.png" mode="aspectFill"></image>
<!-- 垃圾桶 -->
<image class="item trashCan" :class="{ enlarged: isTrashCanEnlarged }"
:style="{ transform: `translateX(${trashCanOffsetX}rpx)` }" @touchstart="onTrashCanTouchStart"
@touchmove="onTrashCanTouchMove" @touchend="onTrashCanTouchEnd"
src="../../static/game-one/trashCan.png" mode="aspectFill"></image>
<image class="item itemArrow" src="../../static/game-one/arrowRight.png" mode="aspectFill"></image>
<!-- two垃圾图片 -->
<image class="item two" :class="{ removed: isTwoRemoved }"
src="../../static/game-one/rubbish-two.png" mode="aspectFill"></image>
</view>
<!-- 手部跟随 -->
<view class="hand">
<image class="item" :style="{
transform: `translateX(${handOffsetX}rpx)`,
opacity: isHandVisible ? 1 : 0
}" src="../../static/game-one/hand.png" mode="aspectFill"></image>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
isTransitioning: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['all-cleared', 'show-purification'])
// 垃圾桶移动相关状态
const trashCanOffsetX = ref(0)
const trashCanStartX = ref(0)
const isDragging = ref(false)
const isTrashCanEnlarged = ref(false)
// 手部跟随相关状态
const handOffsetX = ref(0)
const isHandVisible = ref(false)
// 垃圾清除状态
const isOneRemoved = ref(false)
const isTwoRemoved = ref(false)
// 移动阈值
const LEFT_THRESHOLD = -100
const RIGHT_THRESHOLD = 100
const MAX_OFFSET = 120
// 触摸开始
const onTrashCanTouchStart = (e) => {
if (props.isTransitioning) return
isDragging.value = true
trashCanStartX.value = e.touches[0].clientX
isTrashCanEnlarged.value = true
isHandVisible.value = true
}
// 触摸移动
const onTrashCanTouchMove = (e) => {
if (!isDragging.value || props.isTransitioning) return
const touchX = e.touches[0].clientX
const deltaX = touchX - trashCanStartX.value
const screenWidth = uni.getSystemInfoSync().windowWidth
const deltaRpx = (deltaX / screenWidth) * 750
// 限制移动范围
if (deltaRpx < -MAX_OFFSET) {
trashCanOffsetX.value = -MAX_OFFSET
} else if (deltaRpx > MAX_OFFSET) {
trashCanOffsetX.value = MAX_OFFSET
} else {
trashCanOffsetX.value = deltaRpx
}
// 手部跟随移动
handOffsetX.value = trashCanOffsetX.value
// 检查是否到达阈值并清除垃圾
checkAndClearTrash()
}
// 触摸结束
const onTrashCanTouchEnd = () => {
isDragging.value = false
isTrashCanEnlarged.value = false
isHandVisible.value = false
setTimeout(() => {
trashCanOffsetX.value = 0
handOffsetX.value = 0
}, 300)
}
// 检查并清除垃圾
const checkAndClearTrash = () => {
// 向左移动清除one
if (trashCanOffsetX.value <= LEFT_THRESHOLD && !isOneRemoved.value) {
isOneRemoved.value = true
emit('show-purification')
}
// 向右移动清除two
if (trashCanOffsetX.value >= RIGHT_THRESHOLD && !isTwoRemoved.value) {
isTwoRemoved.value = true
emit('show-purification')
}
}
// 监听清除状态变化
watch([isOneRemoved, isTwoRemoved], ([oneRemoved, twoRemoved]) => {
if (oneRemoved && twoRemoved) {
emit('all-cleared')
}
})
</script>
<style scoped lang="scss">
.garbage-cleanup {
.content {
width: 100%;
margin-top: 150rpx;
.cleartitle {
margin: 0 auto;
width: 240rpx;
padding: 5rpx 0 15rpx;
text-align: center;
font-size: 40rpx;
color: #FFFFFF;
font-weight: 600;
background: #376069;
border: 2rpx solid #ffffff;
border-radius: 34rpx;
}
.clearContent {
background: #ffffff8f;
border: 4rpx dashed #FFF;
border-radius: 32rpx;
width: 558rpx;
height: 166rpx;
margin: 30rpx auto;
position: relative;
.clearItem {
display: flex;
align-items: center;
justify-content: space-between;
padding: 34rpx 32rpx;
position: relative;
.item {
transition: all 0.3s ease;
position: relative;
z-index: 2;
}
.one {
width: 108rpx;
height: 106rpx;
opacity: 1;
transition: opacity 0.5s ease;
&.removed {
opacity: 0;
transform: scale(0.8);
}
}
.two {
width: 98rpx;
height: 106rpx;
opacity: 1;
transition: opacity 0.5s ease;
&.removed {
opacity: 0;
transform: scale(0.8);
}
}
.trashCan {
width: 100rpx;
height: 110rpx;
z-index: 10;
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
&.enlarged {
transform: translateX(v-bind(trashCanOffsetX + 'rpx')) scale(1.2);
}
}
.itemArrow {
width: 70rpx;
height: 50rpx;
}
}
.hand {
position: absolute;
left: 55%;
top: 100rpx;
transform: translateX(-50%);
z-index: 15;
transition: all 0.3s ease;
.item {
width: 130rpx;
height: 130rpx;
transition: all 0.3s ease;
}
}
}
}
}
</style>
\ No newline at end of file
<!-- components/oilFilmSeparationZone.vue -->
<template>
<view class="oil-film-separation">
<!-- 主内容区域 -->
<view class="content">
<view class="gameTitle">油膜分离区</view>
<view class="gameContent">
<!-- 进度条背景 -->
<view class="progressBar">
<!-- 填充的进度 -->
<view
class="progressFill"
:style="{ width: `${progressPercentage}%` }"
>
<!-- 斜杠图案层 -->
<view class="diagonalPattern"></view>
<!-- 渐变颜色层 -->
<view class="gradientLayer"></view>
</view>
<!-- 进度条边框 -->
<view class="progressBorder"></view>
</view>
<!-- 拖动提示区域(hint) -->
<view
class="hint"
:style="{ left: `${hintPosition}%` }"
@touchstart="onHintTouchStart"
@touchmove="onHintTouchMove"
@touchend="onHintTouchEnd"
>
<!-- 拖动手柄 -->
<view class="hintHandle"></view>
<!-- 手部跟随(放在hint内部中下位置) -->
<view class="handInsideHint">
<image
class="handImage"
src="/static/game-one/hand.png"
mode="aspectFill"
></image>
</view>
</view>
</view>
</view>
<!-- 净化值显示区域 -->
<view class="purificationValue" :class="{ show: showPurificationValue }">
<image
class="purificationValueImg"
src="/static/game-one/purificationValue.png"
mode="aspectFill"
></image>
</view>
</view>
</template>
<script setup>
import { ref, watch, defineEmits } from 'vue'
// 定义组件事件
const emit = defineEmits(['stage-complete'])
// 进度相关状态
const progressPercentage = ref(0) // 进度百分比 0-100
const hintPosition = ref(0) // hint位置百分比
// 触摸相关状态
const isDragging = ref(false)
const touchStartX = ref(0)
const hintStartPosition = ref(0)
// 净化值显示状态
const showPurificationValue = ref(false)
const hasShownAt50 = ref(false) // 是否已经在50%显示过一次
const isCompleted = ref(false) // 是否已经完成
// 触摸开始
const onHintTouchStart = (e) => {
if (isCompleted.value) return // 如果已完成,不再响应触摸
isDragging.value = true
touchStartX.value = e.touches[0].clientX
hintStartPosition.value = hintPosition.value
}
// 触摸移动
const onHintTouchMove = (e) => {
if (!isDragging.value || isCompleted.value) return
const touchX = e.touches[0].clientX
const screenWidth = uni.getSystemInfoSync().windowWidth
// 计算移动距离(百分比)
const deltaX = touchX - touchStartX.value
const deltaPercentage = (deltaX / screenWidth) * 100
// 计算新的位置
let newPosition = hintStartPosition.value + deltaPercentage
// 限制在0-100%范围内
if (newPosition < 0) {
newPosition = 0
} else if (newPosition > 100) {
newPosition = 100
}
// 更新位置
hintPosition.value = newPosition
// 更新进度(进度跟随hint位置)
progressPercentage.value = newPosition
// 检查进度并触发净化值显示
checkProgressForPurification()
}
// 触摸结束
const onHintTouchEnd = () => {
isDragging.value = false
// 如果未完成,可以添加自动回弹效果
if (progressPercentage.value < 100 && !isCompleted.value) {
// 可选:自动回弹到当前位置的附近
// setTimeout(() => {
// hintPosition.value = progressPercentage.value
// }, 100)
}
}
// 检查进度并触发净化值显示
const checkProgressForPurification = () => {
const progress = progressPercentage.value
// 达到50%时显示净化值
if (progress >= 50 && progress < 51 && !hasShownAt50.value) {
showPurificationValueEffect()
hasShownAt50.value = true
}
// 达到100%时再次显示净化值并触发完成事件
if (progress >= 100 && progress < 101 && !isCompleted.value) {
isCompleted.value = true
showPurificationValueEffect()
// 触发完成事件
emit('stage-complete')
// 完成时的额外处理
setTimeout(() => {
uni.showToast({
title: '油膜分离完成!',
icon: 'success',
duration: 2000
})
}, 1000)
}
}
// 显示净化值效果
const showPurificationValueEffect = () => {
showPurificationValue.value = true
// 3秒后隐藏净化值
setTimeout(() => {
showPurificationValue.value = false
}, 3000)
}
// 监听进度变化(可选)
watch(progressPercentage, (newValue) => {
console.log('当前进度:', newValue.toFixed(1) + '%')
// 如果进度减少到50%以下,重置标志
if (newValue < 50) {
hasShownAt50.value = false
}
})
</script>
<style scoped lang="scss">
.oil-film-separation {
width: 100%;
.content {
width: 100%;
margin-top: 150rpx;
.gameTitle {
margin: 0 auto;
width: 240rpx;
padding: 5rpx 0 15rpx;
text-align: center;
font-size: 40rpx;
color: #FFFFFF;
font-weight: 600;
background: #376069;
border: 2rpx solid #ffffff;
border-radius: 34rpx;
}
.gameContent {
position: relative;
width: 566rpx;
height: 200rpx;
margin: 112rpx auto 0;
// 进度条容器
.progressBar {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 100%;
height: 52rpx;
// 进度条边框(背景)
.progressBorder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #376069;
border: 4rpx solid #FFF;
border-radius: 80rpx;
z-index: 1;
}
// 进度条填充
.progressFill {
position: absolute;
top: 8rpx;
left: 4rpx;
height: 44rpx;
border-radius: 76rpx;
transition: width 0.1s ease;
z-index: 2;
overflow: hidden; // 确保子元素不会超出边界
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2);
// 斜杠图案层
.diagonalPattern {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: repeating-linear-gradient(
135deg, // 135度斜角
rgba(255, 255, 255, 0.15) 0, // 斜杠颜色和透明度
rgba(255, 255, 255, 0.15) 12rpx, // 斜杠宽度
transparent 6rpx, // 斜杠结束
transparent 30rpx // 斜杠间隔
);
z-index: 1;
animation: patternMove 3s linear infinite; // 添加动画效果
}
// 渐变颜色层
.gradientLayer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, #9C83E5 0%, #45AAE8 36.54%, #5AC3D0 61.06%, #EBDB66 87.98%);
z-index: 0;
}
}
}
// hint拖动区域
.hint {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 34rpx;
height: 88rpx;
z-index: 10;
touch-action: none; // 防止页面滚动
// hint手柄
.hintHandle {
position: relative;
width: 100%;
height: 100%;
background: #D5E9FC;
border: 4rpx solid #376069;
border-radius: 28rpx;
box-shadow: 4rpx 4rpx 8rpx 0 #FFF inset;
cursor: pointer;
z-index: 11;
&:active {
background: #b8d5f8;
transform: scale(0.95);
}
}
// 手部(放在hint内部中下位置)
.handInsideHint {
position: absolute;
top: calc(100% - 40rpx); // 放在hint下方,向上偏移一些
left: 50%;
transform: translateX(-20%);
z-index: 12;
.handImage {
width: 130rpx;
height: 130rpx;
filter: drop-shadow(0 8rpx 16rpx rgba(0, 0, 0, 0.3));
transform: translateY(-10rpx); // 向上偏移,让手部与hint重叠一部分
}
}
// 添加拖动指示
&::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: 4rpx;
height: 20rpx;
background: #376069;
border-radius: 2rpx;
z-index: 1;
}
}
}
}
// 净化值显示区域
.purificationValue {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
opacity: 0;
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 100;
pointer-events: none;
&.show {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.purificationValueImg {
width: 368rpx;
height: 362rpx;
filter: drop-shadow(0 10rpx 20rpx rgba(0, 0, 0, 0.3));
animation: float 3s ease-in-out infinite;
}
}
}
// 漂浮动画
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-20rpx);
}
}
// 斜杠图案移动动画
@keyframes patternMove {
0% {
background-position: 0 0;
}
100% {
background-position: 24rpx 24rpx; // 移动距离等于斜杠间隔的两倍
}
}
// 添加拖动时的动画效果
.hint:active {
.hintHandle {
animation: pulse 0.5s ease-in-out infinite alternate;
}
}
@keyframes pulse {
from {
box-shadow: 4rpx 4rpx 8rpx 0 #FFF inset, 0 0 0 rgba(157, 131, 229, 0.4);
}
to {
box-shadow: 4rpx 4rpx 8rpx 0 #FFF inset, 0 0 20rpx rgba(157, 131, 229, 0.8);
}
}
</style>
\ No newline at end of file
<!--选择试剂组件-->
<template>
<view class="container" @touchmove="handleGlobalTouchMove" @touchstart="handleTouchStart">
<view class="content">
<view class="gameContent">
<view class="hint">
<!-- 显示当前选中的图片 -->
<image :src="currentImage" style="height: 134rpx; padding: 0 62rpx;" />
<!-- 三个可点击区域 -->
<view
class="click-area"
v-for="i in 3"
:key="i"
:class="{active: hoverArea === i && !isSelected}"
:style="{left: (i-1) * 186 + 'rpx'}"
@click="selectArea(i)"
@touchstart="handleAreaTouchStart($event, i)"
></view>
</view>
</view>
<!-- 手部跟随 -->
<view class="hand" :style="handStyle">
<image class="item" src="../../static/game-one/hand.png" mode="aspectFill"></image>
</view>
<view class="textTips">
<view class="text">
当前<br />
经检测该河流呈酸性<br />
请选择合适的试剂进行中和
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
// 定义emit事件
const emit = defineEmits(['complete'])
// 当前显示的图片
const currentImage = ref('../../../static/game-one/reagents.png')
// 当前悬停的区域 (0表示没有悬停,1,2,3分别对应三个区域)
const hoverArea = ref(0)
// 是否已选择区域
const isSelected = ref(false)
// 手势位置
const handPosition = ref({ x: 200, y: 300 })
// 图片映射关系
const imageMap = {
1: '../../../static/game-one/reagentsOne.png',
2: '../../../static/game-one/reagentsTwo.png',
3: '../../../static/game-one/reagentsThree.png'
}
// 计算手部样式
const handStyle = computed(() => {
return {
left: handPosition.value.x + 'px',
top: handPosition.value.y + 'px'
}
})
// 选择区域
const selectArea = (areaIndex) => {
isSelected.value = true
currentImage.value = imageMap[areaIndex]
// 选择后清空悬停状态
hoverArea.value = 0
// 如果选择了第一个区域(正确的试剂),则触发完成事件
if (areaIndex === 1) {
emit('stage-complete', { selectedIndex: areaIndex, isCorrect: true })
} else {
// 选择错误的试剂,显示提示
uni.showToast({
title: '这不是正确的试剂,请重新选择',
icon: 'none',
duration: 2000
})
// 2秒后重置选择
setTimeout(() => {
resetSelection()
}, 2000)
}
}
// 重置选择
const resetSelection = () => {
isSelected.value = false
currentImage.value = '../../../static/game-one/reagents.png'
hoverArea.value = 0
}
// 处理区域触摸开始事件
const handleAreaTouchStart = (e, areaIndex) => {
const touch = e.touches[0]
if (touch) {
updateHandPosition(touch.clientX, touch.clientY)
hoverArea.value = areaIndex
}
}
// 处理全局触摸移动事件
const handleGlobalTouchMove = (e) => {
const touch = e.touches[0]
if (touch) {
updateHandPosition(touch.clientX, touch.clientY)
// 更新悬停区域(仅在未选择时)
if (!isSelected.value) {
updateHoverArea(touch.clientX, touch.clientY)
}
}
}
// 处理触摸开始事件
const handleTouchStart = (e) => {
const touch = e.touches[0]
if (touch && !isSelected.value) {
updateHoverArea(touch.clientX, touch.clientY)
}
}
// 更新手势位置
const updateHandPosition = (clientX, clientY) => {
handPosition.value = {
x: clientX - 32,
y: clientY - 32
}
}
// 更新悬停区域
const updateHoverArea = (clientX, clientY) => {
const query = uni.createSelectorQuery()
query.select('.gameContent').boundingClientRect(data => {
if (data) {
const rect = data
const areaWidth = 48.75 // 186rpx ≈ 48.75px
const areaHeight = 37.8 // 144rpx ≈ 37.8px
// 计算区域的垂直中心位置
const areaTop = rect.top + (rect.height - areaHeight) / 2
const areaBottom = areaTop + areaHeight
// 检查是否在某个区域内
for (let i = 1; i <= 3; i++) {
const areaLeft = rect.left + (i-1) * areaWidth
const areaRight = areaLeft + 34.125 // 130rpx ≈ 34.125px
if (clientX >= areaLeft && clientX <= areaRight &&
clientY >= areaTop && clientY <= areaBottom) {
hoverArea.value = i
return
}
}
hoverArea.value = 0
}
}).exec()
}
// 暴露重置方法给父组件
defineExpose({
resetSelection
})
</script>
<style scoped lang="scss">
.container {
width: 100%;
min-height: 100vh;
position: relative;
touch-action: none;
.content {
.gameContent {
background: #ffffff8f;
border: 4rpx dashed #FFF;
border-radius: 32rpx;
width: 558rpx;
height: 196rpx;
margin: 30rpx auto;
position: relative;
.hint {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.click-area {
position: absolute;
width: 130rpx;
height: 144rpx;
border-radius: 12rpx;
background: transparent;
z-index: 10;
cursor: pointer;
&.active {
background: rgba(48, 195, 57, 0.3);
border: 2rpx solid #30C339;
box-shadow: 0 0 20rpx rgba(48, 195, 57, 0.5);
transition: all 0.2s ease;
}
}
}
}
.hand {
position: fixed;
pointer-events: none;
z-index: 15;
transition: all 0.1s ease;
transform: translate(-50%, -50%);
.item {
width: 130rpx;
height: 130rpx;
transition: all 0.1s ease;
}
}
.textTips {
border-radius: 24rpx;
border: 2rpx solid #FFF;
background: #F0F9FC;
box-shadow: 0 0 39.6rpx 0 #C8EEFF inset;
width: 520rpx;
height: 196rpx;
position: fixed;
bottom: 280rpx;
left: 15%;
pointer-events: none;
.text {
color: #2d4b5d;
text-align: center;
font-size: 36rpx;
font-weight: 600;
margin-top: 20rpx;
}
}
}
}
</style>
\ No newline at end of file
<!--主页面-->
<template>
<view class="container">
<!-- 根据当前显示模式显示不同背景 -->
<image class="bg"
v-if="!showCompleteComponent"
:src="currentBg"
mode="aspectFill"
/>
<image class="bg"
v-else
src="/static/game-one/completeBg.png"
mode="aspectFill"
/>
<!-- 顶部标题容器 -->
<view class="top">
<image class="topTitle" :src="currentTitle" mode="aspectFill"></image>
</view>
<!-- 根据当前阶段显示不同内容 -->
<template v-if="currentStage === 'garbageCleanup' && !showCompleteComponent">
<!-- 垃圾清理组件 -->
<garbage-cleanup-zone
@all-cleared="onGarbageAllCleared"
@show-purification="showPurificationValueEffect"
/>
</template>
<template v-else-if="currentStage === 'oilFilmSeparation' && !showCompleteComponent">
<!-- 油膜分离组件 -->
<oil-film-separation-zone
ref="oilFilmZoneRef"
@stage-complete="onOilSeparationComplete"
/>
</template>
<template v-else-if="currentStage === 'acidBaseNeutralization' && !showCompleteComponent">
<!-- 酸碱中和组件 -->
<select-reagents
ref="acidBaseZoneRef"
@stage-complete="onAcidBaseNeutralizationComplete"
/>
</template>
<!-- 完成后的组件(显示在completeBg上) -->
<template v-if="showCompleteComponent">
<complete-component />
</template>
<!-- 净化值显示区域 -->
<view class="purificationValue" :class="{ show: showPurificationValue }">
<image class="purificationValueImg" src="/static/game-one/purificationValue.png" mode="aspectFill"></image>
</view>
<!-- 完成弹窗 - 全屏覆盖 -->
<view class="completion-modal" v-if="showCompletionModal" @click="closeCompletionModal">
<image class="modal-image"
src="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"
mode="scaleToFill" />
</view>
<!-- 底部内容(始终显示) -->
<view class="bottomContent">
<view class="progress">
<image :src="currentProgress" class="progressPic" />
</view>
<view class="nextStep" @click="handleNextStep">
<image src="/static/game-one/nextStep.png" class="nextStepPic"
:class="{ disabled: !isNextStepEnabled }" />
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import GarbageCleanupZone from './components/garbageCleanup.vue'
import OilFilmSeparationZone from './components/oilFilmSeparationZone.vue'
import SelectReagents from './components/selectReagents.vue'
import CompleteComponent from './components/complete.vue'
// 当前游戏阶段
const currentStage = ref('garbageCleanup')
// 各阶段完成状态
const isGarbageCleaned = ref(false)
const isOilSeparationComplete = ref(false)
const isAcidBaseNeutralizationComplete = ref(false)
// 其他状态
const showPurificationValue = ref(false)
const showCompletionModal = ref(false)
const showCompleteComponent = ref(false)
// 引用子组件
const oilFilmZoneRef = ref(null)
const acidBaseZoneRef = ref(null)
// 根据当前阶段计算使用的资源
const currentBg = computed(() => {
switch (currentStage.value) {
case 'garbageCleanup':
return '/static/game-one/oneBackground.png'
case 'oilFilmSeparation':
return '/static/game-one/oilBackground.png'
case 'acidBaseNeutralization':
return '/static/game-one/reagentsBackground.png'
default:
return '/static/game-one/oneBackground.png'
}
})
const currentTitle = computed(() => {
return '/static/game-one/oneTitle.png'
})
// 进度条图片
const currentProgress = computed(() => {
if (showCompleteComponent.value) {
// 显示complete组件时,进度条用第三阶段
return '/static/game-one/progressThree.png'
}
switch (currentStage.value) {
case 'garbageCleanup':
return '/static/game-one/progressOne.png'
case 'oilFilmSeparation':
return '/static/game-one/progressTwo.png'
case 'acidBaseNeutralization':
return '/static/game-one/progressThree.png'
default:
return '/static/game-one/progressOne.png'
}
})
// 下一步按钮是否可用
const isNextStepEnabled = computed(() => {
if (showCompletionModal.value) {
// 显示弹窗时,下一步按钮不可用
return false
}
switch (currentStage.value) {
case 'garbageCleanup':
return isGarbageCleaned.value
case 'oilFilmSeparation':
return isOilSeparationComplete.value
case 'acidBaseNeutralization':
return isAcidBaseNeutralizationComplete.value
default:
return false
}
})
// 垃圾清理完成回调
const onGarbageAllCleared = () => {
isGarbageCleaned.value = true
uni.showToast({
title: '清理完成!可以进入下一步',
icon: 'success',
duration: 2000
})
}
// 显示净化值效果
const showPurificationValueEffect = () => {
showPurificationValue.value = true
setTimeout(() => {
showPurificationValue.value = false
}, 3000)
}
// 油膜分离完成回调
const onOilSeparationComplete = () => {
isOilSeparationComplete.value = true
uni.showToast({
title: '油膜分离完成!可以进入下一步',
icon: 'success',
duration: 2000
})
}
// 酸碱中和完成回调
const onAcidBaseNeutralizationComplete = () => {
isAcidBaseNeutralizationComplete.value = true
uni.showToast({
title: '酸碱中和完成!可以进入下一步',
icon: 'success',
duration: 2000
})
}
// 关闭完成弹窗
const closeCompletionModal = () => {
showCompletionModal.value = false
// 显示complete组件
showCompleteComponent.value = true
}
// 下一步按钮点击
const handleNextStep = () => {
// 根据当前阶段执行不同的逻辑
switch (currentStage.value) {
case 'garbageCleanup':
if (!isGarbageCleaned.value) {
uni.showToast({
title: '请先清理所有垃圾',
icon: 'none',
duration: 2000
})
return
}
transitionToOilSeparation()
break
case 'oilFilmSeparation':
if (!isOilSeparationComplete.value) {
uni.showToast({
title: '请先完成油膜分离',
icon: 'none',
duration: 2000
})
return
}
transitionToAcidBaseNeutralization()
break
case 'acidBaseNeutralization':
if (!isAcidBaseNeutralizationComplete.value) {
uni.showToast({
title: '请先完成酸碱中和',
icon: 'none',
duration: 2000
})
return
}
// 酸碱中和完成后,点击下一步显示完成弹窗
showCompletionModal.value = true
break
}
}
// 过渡到油膜分离阶段
const transitionToOilSeparation = () => {
uni.showLoading({
title: '正在进入油膜分离区...',
mask: true
})
setTimeout(() => {
currentStage.value = 'oilFilmSeparation'
uni.hideLoading()
uni.showToast({
title: '已进入油膜分离区',
icon: 'success',
duration: 1500
})
}, 1000)
}
// 过渡到酸碱中和阶段
const transitionToAcidBaseNeutralization = () => {
uni.showLoading({
title: '正在进入酸碱中和区...',
mask: true
})
setTimeout(() => {
currentStage.value = 'acidBaseNeutralization'
uni.hideLoading()
uni.showToast({
title: '已进入酸碱中和区',
icon: 'success',
duration: 1500
})
}, 1000)
}
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
width: 100%;
position: relative;
overflow: hidden;
.bg {
position: absolute;
width: 100%;
height: 100%;
z-index: -1;
transition: opacity 0.8s ease;
}
.top {
width: 100%;
height: 380rpx;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
.topTitle {
width: 240rpx;
height: 128rpx;
text-align: center;
}
}
.purificationValue {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
opacity: 0;
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 20;
pointer-events: none;
&.show {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.purificationValueImg {
width: 368rpx;
height: 362rpx;
filter: drop-shadow(0 10rpx 20rpx rgba(0, 0, 0, 0.3));
}
}
// 完成弹窗样式
.completion-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
.modal-image {
width: 100%;
height: 100%;
}
}
.bottomContent {
position: fixed;
bottom: 0;
width: 100%;
height: 200rpx;
border-radius: 40rpx 40rpx 0 0;
background: linear-gradient(180deg, #73D8FF 0%, #3498F4 100%);
border-top: 4rpx solid #CCECFD;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 15;
.progress {
padding: 0 32rpx;
.progressPic {
width: 428rpx;
height: 56rpx;
transition: all 0.5s ease;
}
}
.nextStep {
padding: 0 32rpx;
.nextStepPic {
width: 208rpx;
height: 96rpx;
transition: all 0.3s ease;
&.disabled {
opacity: 0.5;
filter: grayscale(0.5);
}
&:not(.disabled):active {
transform: scale(0.95);
}
}
}
}
}
</style>
\ No newline at end of file
......@@ -51,6 +51,13 @@
src="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"
mode="scaleToFill" />
</view>
<!--优惠券弹窗-->
<view class="popup-coupon" v-if="showCouponPopup">
<!-- 优惠券弹窗内容 -->
<view class="coupon-content">
<image class="coupon-image" src="../../static/game-four/testCoupon.png" mode="aspectFit" style="width: 626rpx; height: 780rpx;margin: 0 auto;"/>
</view>
</view>
</view>
</template>
......@@ -69,6 +76,8 @@ const phCurrentStep = ref(0);
const showCompletionModal = ref(false);
// 是否所有测试都已完成
const isAllTestsCompleted = ref(false);
// 是否显示优惠券弹窗
const showCouponPopup = ref(false);
// 获取烧杯图片
const getBeakerImage = (type) => {
......@@ -149,7 +158,13 @@ const handlePhButtonClick = () => {
const closeCompletionModal = () => {
console.log('关闭完成弹窗');
showCompletionModal.value = false;
// 点击弹窗后重置所有状态
// 延迟300ms后显示优惠券弹窗
setTimeout(() => {
showCouponPopup.value = true;
}, 300);
// 重置所有测试状态
resetAllTests();
}
......@@ -268,5 +283,19 @@ const resetAllTests = () => {
display: block;
}
}
// 优惠券弹窗样式
.popup-coupon {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 100000; // 比完成弹窗层级更高
}
}
</style>
\ 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