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
<!--选择试剂组件-->
<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
......@@ -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