Commit b521a783 by Hantao

完成第二关的part2

parent 70741af1
...@@ -3,7 +3,31 @@ ...@@ -3,7 +3,31 @@
{ {
"path": "pages/index/index", "path": "pages/index/index",
"style": { "style": {
"navigationBarTitleText": "uni-app" "navigationStyle": "custom"
}
},
{
"path": "pages/start/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/second/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/second/part1",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/second/part2",
"style": {
"navigationStyle": "custom"
} }
}, },
{ {
......
import { ref, onMounted, onUnmounted } from 'vue';
export function useProgress() {
const progress = ref(0);
const isPaused = ref(false);
// 速度控制参数
const speed = ref(50); // 速度滑块值 (0-100)
const targetFPS = ref(60); // 目标帧率 (FPS)
let lastTime = 0;
let animationTimer = null;
const updateProgress = () => {
if (isPaused.value || progress.value >= 100) {
return;
}
const now = Date.now();
const interval = 1000 / targetFPS.value;
const elapsed = now - lastTime;
if (elapsed > interval) {
// 基础速度:speed 值为 50 时,每秒约增加 10%
const increment = (speed.value / 100) * (elapsed / 1000) * 20;
progress.value = Math.min(progress.value + increment, 100);
lastTime = now - (elapsed % interval);
}
animationTimer = setTimeout(updateProgress, interval);
};
const start = () => {
progress.value = 0;
isPaused.value = false;
lastTime = Date.now();
if (animationTimer) clearTimeout(animationTimer);
updateProgress();
};
// 暂停/继续控制
const togglePause = () => {
isPaused.value = !isPaused.value;
if (!isPaused.value) {
lastTime = Date.now();
updateProgress();
}
};
// 设置速度
const setSpeed = (value) => {
speed.value = Math.max(0, Math.min(100, value));
};
// 设置FPS
const setFPS = (value) => {
targetFPS.value = Math.max(1, Math.min(120, value));
};
onUnmounted(() => {
if (animationTimer) clearTimeout(animationTimer);
});
return {
progress,
isPaused,
speed,
targetFPS,
start,
togglePause,
setSpeed,
setFPS
};
}
<script setup>
import { onMounted, watch } from 'vue';
import { useProgress } from './hook/speed';
// 图片路径
const ASSETS = {
bg: '/static/shouping/bg.webp',
logo: '/static/shouping/logo.webp',
title: '/static/shouping/title.webp',
water: '/static/shouping/water.webp',
};
const { progress, start } = useProgress();
watch(progress, (newVal) => {
if (newVal >= 100) {
setTimeout(handleNavigation, 1000);
}
});
const handleNavigation = () => {
uni.reLaunch({
url: '/pages/start/index',
fail: (err) => {
console.error('Navigation failed:', err);
uni.showToast({
title: '页面跳转失败,请重试',
icon: 'none',
duration: 2000
});
}
});
};
onMounted(() => {
start();
});
</script>
<template> <template>
<view class="content"> <view class="landing-container">
<image class="logo" src="/static/logo.png"></image> <!-- 背景图片 -->
<view class="text-area"> <image class="bg-image" :src="ASSETS.bg" mode="scaleToFill" />
<text class="title">{{title}}</text>
</view> <!-- 内容区域 -->
</view> <view class="content-wrapper">
<!-- Logo -->
<view class="logo-box">
<image class="logo-img" :src="ASSETS.logo" mode="widthFix" />
</view>
<!-- 标题 -->
<view class="title-box">
<image class="title-img" :src="ASSETS.title" mode="widthFix" />
</view>
<!-- 进度条区域 -->
<view class="progress-section">
<view class="progress-track">
<view class="progress-bar" :style="{ width: `${progress}%` }">
<view class="water-drop-wrapper">
<image class="water-drop" :src="ASSETS.water" mode="widthFix" />
</view>
</view>
</view>
<view class="loading-info">
<text class="loading-text">加载资源中 {{ Math.floor(progress) }}%</text>
</view>
</view>
</view>
</view>
</template> </template>
<script> <style scoped lang="scss">
export default { .landing-container {
data() { position: relative;
return { width: 100vw;
title: 'Hello' height: 100vh;
} overflow: hidden;
}, display: flex;
onLoad() { flex-direction: column;
}
}, .bg-image {
methods: { position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
} .content-wrapper {
} position: relative;
</script> z-index: 1;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 15vh;
}
.logo-box {
margin-bottom: 40rpx;
.logo-img {
width: 280rpx;
height: auto;
}
}
.title-box {
margin-bottom: 100rpx;
display: flex;
justify-content: center;
.title-img {
width: 600rpx;
height: auto;
}
}
.progress-section {
width: 600rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 3vh;
margin-bottom: 25vh;
.progress-track {
width: 100%;
height: 40rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 20rpx;
position: relative;
border: 2rpx solid #000;
box-sizing: border-box;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #2facff 0%, #6cd9ff 100%);
border-radius: 18rpx;
position: relative;
transition: width 0.1s linear;
.water-drop-wrapper {
position: absolute;
right: -18rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
}
.water-drop {
width: 48rpx;
height: 48rpx;
filter: drop-shadow(0 2rpx 4rpx rgba(0,0,0,0.2));
}
}
<style> .loading-text {
.content { margin-top: 20rpx;
display: flex; font-size: 28rpx;
flex-direction: column; color: #333;
align-items: center; font-weight: 600;
justify-content: center; letter-spacing: 2rpx;
} text-shadow: 0 2rpx 0 rgba(255,255,255,0.5);
}
.logo { }
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style> </style>
<script setup>
const ASSETS = {
bg: '/static/second/bg.webp',
info: '/static/second/info.webp',
title: '/static/second/title.webp',
jdt: '/static/second/jdt.webp',
next: '/static/second/next.webp',
};
const handleNext = () => {
uni.navigateTo({
url: '/pages/second/part1'
});
};
</script>
<template>
<view class="container">
<!-- 主体内容 -->
<view class="main-content">
<!-- 背景 -->
<image class="bg-img" :src="ASSETS.bg" mode="aspectFill" />
<!-- 标题 -->
<view class="title-area">
<image class="title-img" :src="ASSETS.title" mode="widthFix" />
</view>
<!-- 右侧信息区域 -->
<view class="info-area">
<image class="info-img" :src="ASSETS.info" mode="scaleToFill" />
</view>
<!-- 底部区域 -->
<view class="bottom-area">
<image class="progress-bar" :src="ASSETS.jdt" mode="scaleToFill" />
<view class="next-btn-wrapper" @click="handleNext">
<image class="next-btn" :src="ASSETS.next" mode="scaleToFill" />
</view>
</view>
</view>
</view>
</template>
<style scoped lang="scss">
.container {
width: 100vw;
height: 100vh;
position: relative;
overflow: hidden;
}
.main-content {
width: 100%;
height: 100%;
position: relative;
}
.bg-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.title-area {
position: absolute;
top: 7vh;
left: 0;
width: 100%;
display: flex;
justify-content: center;
z-index: 10;
.title-img {
width: 240rpx;
height: auto;
}
}
.info-area {
position: absolute;
top: 48%;
right: 22rpx;
transform: translateY(-45%);
z-index: 10;
display: flex;
justify-content: flex-end;
.info-img {
width: 440rpx;
height: 780rpx;
}
}
.bottom-area {
position: absolute;
bottom: 0;
width: 100%;
height: 180rpx;
padding: 40rpx;
padding-bottom: calc(40rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
z-index: 20;
background: linear-gradient(180deg, #73D8FF 0%, #3498F4 100%);
border-radius: 40rpx 40rpx 0 0;
border-top: 4rpx solid #CCECFD;
.progress-bar {
position: absolute;
left: 40rpx;
top: 50%;
transform: translateY(-50%);
width: 428rpx;
height: 56rpx;
}
.next-btn-wrapper {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
width: 230rpx;
height: 72px;
transition: transform 0.2s ease;
align-self: auto;
margin: 0;
.next-btn {
width: 100%;
height: 100%;
}
}
}
@media screen and (min-width: 768px) {
.title-area {
top: 10vh;
.title-img {
width: 20vw;
}
}
.info-area {
right: 10vw;
.info-img {
width: 440rpx;
height: 740rpx;
}
}
.bottom-area {
bottom: 10vh;
.progress-bar {
width: 30vw;
}
.next-btn-wrapper {
right: 10vw;
.next-btn {
width: 15vw;
}
}
}
}
</style>
<script setup>
const ASSETS = {
title: '/static/second/part1/title.webp',
center: '/static/second/part1/center.webp',
zzjd: '/static/second/zzjd1.webp',
panzi: '/static/second/panzi.webp',
resetIcon: '/static/second/reset.webp',
blockIcon: '/static/second/block.webp',
arrow: '/static/second/jt.webp',
};
</script>
<template>
<view class="part1-container">
<!-- 顶部标题 -->
<view class="header-section">
<image class="title-img" :src="ASSETS.title" mode="widthFix" />
</view>
<!-- 主体区域 -->
<view class="main-area">
<!-- 中间拼图区域 -->
<view class="center-wrapper">
<image class="center-img" :src="ASSETS.center" mode="widthFix" />
</view>
<!-- 右侧进度条 -->
<view class="progress-wrapper">
<text class="progress-title">组装进度</text>
<image class="progress-img" :src="ASSETS.zzjd" mode="scaleToFill" />
</view>
</view>
<!-- 下方托盘区域 -->
<view class="tray-area">
<image class="tray-img" :src="ASSETS.panzi" mode="scaleToFill" />
</view>
<!-- 底部按钮 -->
<view class="btn-group">
<view class="action-btn reset-btn" @click="handleReset">
<image class="btn-icon" :src="ASSETS.resetIcon" mode="widthFix" />
<text>重置</text>
</view>
<view class="action-btn complete-btn" @click="handleComplete">
<image class="btn-icon block-icon" :src="ASSETS.blockIcon" mode="widthFix" />
<view class="btn-text-col">
<text class="main-text">完成</text>
<text class="sub-text">完成0/6吸附</text>
</view>
</view>
</view>
<image class="arrow-icon" :src="ASSETS.arrow" mode="scaleToFill" />
</view>
</template>
<style scoped lang="scss">
.part1-container {
width: 100vw;
height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
background-color: #77CFF8;
box-sizing: border-box;
}
.header-section {
margin-top: 200rpx;
z-index: 10;
flex-shrink: 0;
.title-img {
width: 344rpx;
height: auto;
}
}
.main-area {
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: flex-start;
margin-top: 60rpx;
flex-shrink: 0;
}
.center-wrapper {
position: relative;
margin-left: -130rpx;
.center-img {
width: 580rpx;
height: auto;
}
}
.progress-wrapper {
position: absolute;
right: 20rpx;
top: -28rpx;
display: flex;
flex-direction: column;
align-items: center;
.progress-title {
font-size: 32rpx;
color: #000;
margin-bottom: 24rpx;
font-weight: 600;
}
.progress-img {
width: 94rpx;
height: 548rpx;
}
}
.tray-area {
width: 88%;
display: flex;
justify-content: center;
margin-top: 44rpx;
margin-bottom: 20rpx;
position: relative;
z-index: 10;
flex-shrink: 0;
.tray-img {
width: 100%;
height: 466rpx;
}
}
.btn-group {
display: flex;
justify-content: center;
gap: 80rpx;
margin-top: 20rpx;
margin-bottom: 60rpx;
z-index: 20;
flex-shrink: 0;
.action-btn {
display: flex;
justify-content: center;
align-items: center;
border-radius: 42rpx;
height: 84rpx;
box-sizing: border-box;
}
.reset-btn {
width: 230rpx;
background: #BAC4CA;
box-shadow: 0 6rpx 2rpx 0 #529bbda6, 0 4rpx 4rpx 0 #ffffff8c inset;
border: 2rpx solid #000000;
color: #5E5C5C;
font-size: 34rpx;
font-weight: bold;
.btn-icon {
width: 44rpx;
height: 44rpx;
margin-right: 10rpx;
}
}
.complete-btn {
width: 250rpx;
background: #5EB6F0;
border: 2rpx solid #000000;
box-shadow: 0 6rpx 2rpx 0 #529bbda6, 0 4rpx 2rpx 0 #76DDEE inset;
color: #134571;
.block-icon {
width: 44rpx;
height: auto;
margin-right: 16rpx;
}
.btn-text-col {
display: flex;
flex-direction: column;
align-items: flex-start;
.main-text {
font-size: 32rpx;
font-weight: bold;
line-height: 1;
margin-bottom: 4rpx;
margin-left: 20rpx;
}
.sub-text {
font-size: 20rpx;
line-height: 1;
}
}
}
}
.arrow-icon {
position: absolute;
top: 48%;
left: 62%;
width: 116rpx;
height: 144rpx;
z-index: 100;
}
</style>
<script setup>
import { ref, onMounted, getCurrentInstance, computed } from 'vue';
const ASSETS = {
title: '/static/second/part2/title.webp',
center: '/static/second/part2/center.webp',
zzjd: '/static/second/zzjd1.webp',
zzjd2: '/static/second/zzjd2.webp',
zzjd3: '/static/second/zzjd3.webp',
zzjd4: '/static/second/zzjd4.webp',
zzjd5: '/static/second/zzjd5.webp',
zzjd6: '/static/second/zzjd6.webp',
panzi: '/static/second/panzi.webp',
resetIcon: '/static/second/reset.webp',
blockIcon: '/static/second/block.webp',
arrow: '/static/second/jt.webp',
lv: '/static/second/part2/lv.webp',
ys: '/static/second/part2/ys.webp',
yw: '/static/second/part2/yw.webp',
lv1: '/static/second/part2/lv1.webp',
ys1: '/static/second/part2/ys1.webp',
yw1: '/static/second/part2/yw1.webp',
lv2: '/static/second/part2/lv2.webp',
ys2: '/static/second/part2/ys2.webp',
yw2: '/static/second/part2/yw2.webp',
lv3: '/static/second/part2/lv3.webp',
ys3: '/static/second/part2/ys3.webp',
yw3: '/static/second/part2/yw3.webp',
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 trayItems = ref([
{ id: 1, type: 'lv', icon: ASSETS.lv1, label: '点击拖动', status: 'default' },
{ id: 2, type: 'lv', icon: ASSETS.lv1, label: '点击拖动', status: 'default' },
{ id: 3, type: 'ys', icon: ASSETS.ys1, label: '点击拖动', status: 'default' },
{ id: 4, type: 'yw', icon: ASSETS.yw1, label: '点击拖动', status: 'default' },
{ id: 5, type: 'ys', icon: ASSETS.ys1, label: '点击拖动', status: 'default' },
{ id: 6, type: 'yw', icon: ASSETS.yw1, label: '点击拖动', status: 'default' },
]);
const placedCount = computed(() => {
return trayItems.value.filter(item => item.status === 'placed').length;
});
const isComplete = computed(() => {
return placedCount.value === 6;
});
const currentProgressImg = computed(() => {
const count = placedCount.value;
switch (count) {
case 0: return ASSETS.zzjd;
case 1: return ASSETS.zzjd2;
case 2: return ASSETS.zzjd3;
case 3: return ASSETS.zzjd4;
case 4: return ASSETS.zzjd5;
case 5: return ASSETS.zzjd6;
case 6: return ASSETS.zzjd6;
default: return ASSETS.zzjd;
}
});
const showCompletion = ref(false);
const handleItemClick = (item) => {
console.log('Item clicked:', item);
};
const handleReset = () => {
trayItems.value.forEach(item => {
item.status = 'default';
});
console.log('Reset clicked');
};
const handleComplete = () => {
if (isComplete.value) {
console.log('Complete clicked - unlocked');
showCompletion.value = true;
} else {
console.log('Complete clicked - locked');
}
};
const closeCompletion = () => {
showCompletion.value = false;
};
const isDragging = ref(false);
const draggedItem = ref(null);
const dragPosition = ref({ x: 0, y: 0 });
const dragTimer = ref(null);
const startPosition = ref({ x: 0, y: 0 });
const centerRect = ref(null);
const instance = getCurrentInstance();
onMounted(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.center-wrapper').boundingClientRect(data => {
if (data) {
centerRect.value = data;
console.log('Center rect:', data);
}
}).exec();
});
const handleStart = (clientX, clientY, item) => {
if (item.status === 'placed') return;
startPosition.value = { x: clientX, y: clientY };
dragPosition.value = { x: clientX, y: clientY };
dragTimer.value = setTimeout(() => {
isDragging.value = true;
draggedItem.value = item;
console.log('Drag started', item);
}, 500);
};
const handleMove = (clientX, clientY) => {
if (!isDragging.value) {
const dx = Math.abs(clientX - startPosition.value.x);
const dy = Math.abs(clientY - startPosition.value.y);
if (dx > 10 || dy > 10) {
if (dragTimer.value) {
clearTimeout(dragTimer.value);
dragTimer.value = null;
}
}
} else {
dragPosition.value = { x: clientX, y: clientY };
}
};
const handleEnd = () => {
if (dragTimer.value) {
clearTimeout(dragTimer.value);
dragTimer.value = null;
}
if (isDragging.value) {
if (centerRect.value) {
const { left, right, top, bottom } = centerRect.value;
const { x, y } = dragPosition.value;
if (x >= left && x <= right && y >= top && y <= bottom) {
if (draggedItem.value) {
draggedItem.value.status = 'placed';
console.log('Item placed:', draggedItem.value);
}
}
}
isDragging.value = false;
draggedItem.value = null;
}
};
const handleTouchStart = (event, item) => {
if (event.touches && event.touches[0]) {
const { clientX, clientY } = event.touches[0];
handleStart(clientX, clientY, item);
}
};
const handleTouchMove = (event) => {
if (event.touches && event.touches[0]) {
const { clientX, clientY } = event.touches[0];
handleMove(clientX, clientY);
}
};
const handleTouchEnd = (item) => {
handleEnd();
};
const handleMouseDown = (event, item) => {
handleStart(event.clientX, event.clientY, item);
};
const handleMouseMove = (event) => {
if (dragTimer.value || isDragging.value) {
handleMove(event.clientX, event.clientY);
}
};
const handleMouseUp = () => {
handleEnd();
};
const getTrayItemIcon = (item) => {
if (item.status === 'placed') {
if (item.type === 'ys') return ASSETS.ys2;
if (item.type === 'yw') return ASSETS.yw2;
return ASSETS.lv2;
}
if (draggedItem.value && draggedItem.value.id === item.id) {
if (item.type === 'ys') return ASSETS.ys2;
if (item.type === 'yw') return ASSETS.yw2;
return ASSETS.lv2;
}
return item.icon;
};
const getGhostIcon = () => {
if (!draggedItem.value) return '';
if (draggedItem.value.type === 'ys') return ASSETS.ys3;
if (draggedItem.value.type === 'yw') return ASSETS.yw3;
return ASSETS.lv3;
};
</script>
<template>
<view class="part2-container">
<!-- 拖拽时的镜像 -->
<view
v-if="isDragging"
class="drag-ghost"
:style="{ left: dragPosition.x + 'px', top: dragPosition.y + 'px' }"
>
<image :src="getGhostIcon()" mode="widthFix" class="ghost-icon" />
</view>
<!-- 顶部标题 -->
<view class="header-section">
<image class="title-img" :src="ASSETS.title" mode="widthFix" />
</view>
<view class="status-bar">
<view class="status-item">
<text class="label">余氯:</text>
<image class="icon" :src="ASSETS.lv" mode="widthFix" />
</view>
<view class="status-item">
<text class="label">异色:</text>
<image class="icon" :src="ASSETS.ys" mode="widthFix" />
</view>
<view class="status-item">
<text class="label">异味:</text>
<image class="icon" :src="ASSETS.yw" mode="widthFix" />
</view>
</view>
<!-- 主体区域 -->
<view class="main-area">
<!-- 中间吸附区域 -->
<view class="center-wrapper">
<image class="center-img" :src="ASSETS.center" mode="scaleToFill" />
</view>
</view>
<!-- 右侧进度条 -->
<view class="progress-wrapper">
<text class="progress-title">组装进度</text>
<image class="progress-img" :src="currentProgressImg" mode="scaleToFill" />
</view>
<!-- 下方托盘区域 -->
<view class="tray-area">
<image class="tray-img" :src="ASSETS.panzi" mode="scaleToFill" />
<view class="tray-items-grid">
<view
v-for="item in trayItems"
:key="item.id"
class="tray-item"
@click="handleItemClick(item)"
@touchstart="handleTouchStart($event, item)"
@touchmove.stop.prevent="handleTouchMove"
@touchend="handleTouchEnd(item)"
@mousedown="handleMouseDown($event, item)"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseUp"
>
<image class="item-icon" :src="getTrayItemIcon(item)" mode="aspectFit" />
<text class="item-label">{{ item.label }}</text>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="btn-group">
<view class="action-btn reset-btn" @click="handleReset">
<image class="btn-icon" :src="ASSETS.resetIcon" mode="widthFix" />
<text>重置</text>
</view>
<view
class="action-btn complete-btn"
@click="handleComplete"
>
<image class="btn-icon block-icon" :src="ASSETS.blockIcon" mode="widthFix" />
<view class="btn-text-col">
<text class="main-text">完成</text>
<text class="sub-text">完成{{ placedCount }}/6吸附</text>
</view>
</view>
</view>
<image class="arrow-icon" :src="ASSETS.arrow" mode="scaleToFill" />
<view class="completion-overlay" v-if="showCompletion" @click="closeCompletion">
<image
class="completion-img"
:src="ASSETS.completionPopup"
mode="scaleToFill"
@click.stop="closeCompletion"
/>
</view>
</view>
</template>
<style scoped lang="scss">
.part2-container {
width: 100vw;
height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
background-color: #77CFF8;
box-sizing: border-box;
}
.header-section {
margin-top: 200rpx;
z-index: 10;
flex-shrink: 0;
.title-img {
width: 400rpx;
height: auto;
}
}
.status-bar {
position: absolute;
top: 280rpx;
left: 42%;
transform: translateX(-50%);
display: flex;
justify-content: center;
align-items: center;
gap: 10rpx;
width: 494rpx;
height: 114rpx;
background: #ffffff8a;
border-radius: 20rpx;
padding: 0 20rpx;
z-index: 10;
box-sizing: border-box;
.status-item {
display: flex;
align-items: center;
.label {
font-size: 28rpx;
color: #000000;
font-weight: bold;
white-space: nowrap;
}
.icon {
width: 80rpx;
height: 80rpx;
margin-left: -12rpx;
}
}
}
.main-area {
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: flex-start;
margin-top: 180rpx;
flex-shrink: 0;
}
.center-wrapper {
position: relative;
margin-left: -130rpx;
.center-img {
width: 512rpx;
height: 512rpx;
}
}
.progress-wrapper {
position: absolute;
top: 298rpx;
right: 26rpx;
display: flex;
flex-direction: column;
align-items: center;
z-index: 20;
.progress-title {
font-size: 32rpx;
color: #000;
margin-bottom: 24rpx;
font-weight: 600;
}
.progress-img {
width: 94rpx;
height: 548rpx;
}
}
.tray-area {
width: 88%;
display: flex;
justify-content: center;
margin-top: 16rpx;
margin-bottom: 20rpx;
position: relative;
z-index: 10;
flex-shrink: 0;
.tray-img {
width: 100%;
height: 466rpx;
}
.tray-items-grid {
position: absolute;
top: 50rpx;
left: 50%;
transform: translateX(-50%);
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 30rpx 60rpx;
width: 540rpx;
.tray-item {
display: flex;
flex-direction: column;
align-items: center;
.item-icon {
width: 136rpx;
height: 120rpx;
margin-bottom: 6rpx;
}
.item-label {
font-size: 26rpx;
color: #333;
font-weight: bold;
}
}
}
}
.btn-group {
display: flex;
justify-content: center;
gap: 80rpx;
margin-top: 20rpx;
margin-bottom: 60rpx;
z-index: 20;
flex-shrink: 0;
.action-btn {
display: flex;
justify-content: center;
align-items: center;
border-radius: 42rpx;
height: 84rpx;
box-sizing: border-box;
}
.reset-btn {
width: 230rpx;
background: #BAC4CA;
box-shadow: 0 6rpx 2rpx 0 #529bbda6, 0 4rpx 4rpx 0 #ffffff8c inset;
border: 2rpx solid #000000;
color: #5E5C5C;
font-size: 34rpx;
font-weight: bold;
.btn-icon {
width: 44rpx;
height: 44rpx;
margin-right: 10rpx;
}
}
.complete-btn {
width: 250rpx;
background: #5EB6F0;
border: 2rpx solid #000000;
box-shadow: 0 6rpx 2rpx 0 #529bbda6, 0 4rpx 2rpx 0 #76DDEE inset;
color: #134571;
.block-icon {
width: 44rpx;
height: auto;
margin-right: 16rpx;
}
.btn-text-col {
display: flex;
flex-direction: column;
align-items: flex-start;
.main-text {
font-size: 32rpx;
font-weight: bold;
line-height: 1;
margin-bottom: 4rpx;
margin-left: 20rpx;
}
.sub-text {
font-size: 20rpx;
line-height: 1;
}
}
}
}
.arrow-icon {
position: absolute;
top: 50%;
left: 68%;
width: 116rpx;
height: 144rpx;
z-index: 100;
}
.drag-ghost {
position: fixed;
z-index: 9999;
transform: translate(-50%, -50%);
pointer-events: none;
.ghost-icon {
width: 260rpx;
height: auto;
}
}
.completion-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
.completion-img {
width: 100%;
height: 100%;
}
}
</style>
<script setup>
import { ref } from 'vue';
// 图片路径
const ASSETS = {
bg: '/static/start/bg.webp',
title: '/static/start/title.webp',
startBtn: '/static/start/start.webp',
restartBtn: '/static/start/restart.webp',
progress: '/static/start/yxjd.webp',
top: '/static/start/top.webp',
bottom: '/static/start/bottom.webp',
sidebarIcon: '/static/start/cbl.webp',
sidebarMenu: '/static/start/all.webp',
};
// 侧边栏状态
const isSidebarExpanded = ref(false);
const handleStart = () => {
};
const handleRestart = () => {
};
const toggleSidebar = () => {
isSidebarExpanded.value = !isSidebarExpanded.value;
};
const closeSidebar = () => {
if (isSidebarExpanded.value) {
isSidebarExpanded.value = false;
}
};
</script>
<template>
<view class="start-container" @click="closeSidebar">
<!-- 背景 -->
<image class="bg-image" :src="ASSETS.bg" mode="scaleToFill" />
<!-- 顶部用户信息 -->
<view class="top-section">
<image class="top-info" :src="ASSETS.top" mode="widthFix" />
</view>
<!-- 主要内容 -->
<view class="content-wrapper">
<!-- 标题 -->
<view class="title-box">
<image class="title-img" :src="ASSETS.title" mode="widthFix" />
</view>
<!-- 游戏进度展示 -->
<view class="progress-box">
<image class="progress-img" :src="ASSETS.progress" mode="widthFix" />
</view>
<!-- 按钮区域 -->
<view class="btn-group">
<view class="btn-item" @click="handleStart">
<image class="btn-img start-btn" :src="ASSETS.startBtn" mode="widthFix" />
</view>
<view class="btn-item" @click="handleRestart">
<image class="btn-img restart-btn" :src="ASSETS.restartBtn" mode="widthFix" />
</view>
</view>
</view>
<!-- 侧边栏 -->
<view class="sidebar-container" :class="{ expanded: isSidebarExpanded }">
<view class="sidebar-wrapper" @click.stop="toggleSidebar">
<image
v-if="!isSidebarExpanded"
class="sidebar-icon"
:src="ASSETS.sidebarIcon"
mode="heightFix"
/>
<image
v-else
class="sidebar-menu"
:src="ASSETS.sidebarMenu"
mode="heightFix"
/>
</view>
</view>
<image class="bottom-decoration" :src="ASSETS.bottom" mode="widthFix" />
</view>
</template>
<style scoped lang="scss">
.start-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.top-section {
position: relative;
z-index: 1;
width: 95%;
padding-top: 162rpx;
padding-left: 20rpx;
padding-right: 20rpx;
box-sizing: border-box;
display: flex;
justify-content: center;
.top-info {
width: 100%;
height: auto;
}
}
.bottom-decoration {
position: absolute;
bottom: 90rpx;
left: 40rpx;
width: 90%;
z-index: 1;
}
.content-wrapper {
position: relative;
z-index: 2;
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 40rpx;
}
.title-box {
.title-img {
width: 500rpx;
height: auto;
animation: bounceIn 1s ease-out;
}
}
.progress-box {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
margin-top: -40rpx;
position: relative;
z-index: 5;
.progress-img {
width: 100%;
height: auto;
}
}
.btn-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 30rpx;
margin-top: -60rpx;
position: relative;
z-index: 10;
}
.btn-item {
transition: transform 0.1s;
&:active {
transform: scale(0.95);
}
.btn-img {
height: auto;
}
.start-btn {
width: 380rpx;
}
.restart-btn {
width: 300rpx;
}
}
.sidebar-container {
position: absolute;
right: 0;
top: 408rpx;
z-index: 20;
display: flex;
justify-content: flex-end;
&.expanded {
top: 408rpx;
}
.sidebar-wrapper {
display: flex;
justify-content: flex-end;
}
.sidebar-icon {
width: auto;
height: 700rpx;
}
.sidebar-menu {
width: auto;
height: 700rpx;
animation: slideInRight 0.3s ease-out;
}
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes bounceIn {
0% {
transform: scale(0.5);
opacity: 0;
}
60% {
transform: scale(1.05);
opacity: 1;
}
100% {
transform: scale(1);
}
}
</style>
\ No newline at end of file
<template>
<view class="bottom-bar">
<image :src="IMAGES.testing"
style="width:0;height:0;opacity:0;position:absolute;pointer-events:none;"></image>
<image class="bar-bg" :src="IMAGES.background" mode="scaleToFill"></image>
<text class="test-status">当前测试: <text class="highlight">{{ statusText }}</text></text>
<view class="start-btn-wrapper" @click="handleStartTest" :class="{ 'disabled': isDisabled }">
<image class="start-btn-img" :src="btnImage" mode="scaleToFill"></image>
</view>
</view>
</template>
<script setup> <script setup>
import { computed } from 'vue'; import { computed } from 'vue';
...@@ -87,6 +74,19 @@ const handleStartTest = () => { ...@@ -87,6 +74,19 @@ const handleStartTest = () => {
}; };
</script> </script>
<template>
<view class="bottom-bar">
<image :src="IMAGES.testing"
style="width:0;height:0;opacity:0;position:absolute;pointer-events:none;"></image>
<image class="bar-bg" :src="IMAGES.background" mode="scaleToFill"></image>
<text class="test-status">当前测试: <text class="highlight">{{ statusText }}</text></text>
<view class="start-btn-wrapper" @click="handleStartTest" :class="{ 'disabled': isDisabled }">
<image class="start-btn-img" :src="btnImage" mode="scaleToFill"></image>
</view>
</view>
</template>
<style scoped lang="scss"> <style scoped lang="scss">
.bottom-bar { .bottom-bar {
position: absolute; position: absolute;
......
<script setup>
import { computed, ref, watch } from 'vue';
const TEXTS = {
TDS: {
instruction: '点击"开始检测"按钮测试TDS值',
tips: {
default: 'TDS代表总溶解固体,数值越低水质越纯净',
testing: '安吉尔RO反渗透技术可将TDS降至15以下',
result: '优质饮用水TDS值应低于50ppm',
},
label: 'TDS值',
polluted: '450',
purified: '15',
},
PH: {
instruction: "点击‘开始检测’测试水的酸碱度",
tips: {
default: '理想饮用水pH值应在6.5-8.5之间',
testing: 'pH试纸通过颜色变化判断酸碱性',
result: '中性水(pH 7.0)对人体最温和,是理想的饮用水',
},
label: 'PH值',
polluted: '8.5',
purified: '7.0',
},
placeholder: '???',
};
const IMAGES = {
// 两个选项图片
tabActiveTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/b91478ab6f2d4c2186a948ae9b614f45Group%20348447426.webp',
tabActivePh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/b9c5667e95ea48a1b4c10f2d9ee84db6Group%20348447426.webp',
tabInactiveTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/a4bd654c5fd4401bb4d09ce1a8e08aa3Group%20348447427.webp',
tabInactivePh: '/static/1.webp',
// 介绍内部图片
instructionTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/1a62746fad3743d19f01e8b612ed8d42Group%20348447435.webp',
instructionPh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/32ac38db03994072aca53ae518b996eeGroup%20348447450.webp',
phLabel: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/536133e8a44f4482b2848016385d79cbPH.webp',
cardBg: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/87e28ba2f4764f33a36b0d1cfd47d74aRectangle%203589.webp',
// 进度条
progressResult: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/45cebdfeb99a44049d18ab3b04024ac2Group%20348447442.webp',
progressInitial: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/7b21b166605d4c7c9bb4377280202da7Group%20348447442.webp',
// 结果内部图片
warningIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/0c6358f526fd4a72a098e5e46c9a8759Frame%2017.webp',
successIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/dd15bc1646cb4fcb86670b2b1ae04809Group%20348447459.webp',
successIconFrame: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/e7e6a286a568480fada2cafab781cc3cFrame.webp',
badgeIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/44463cae0882429ebd84049240ff5330Group%20348447451.webp',
phChart: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/3df0383e2218465c83af9aeeae10e1baGroup%20348447462.webp',
phBefore: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/8756b9fccb61450a96f34e2b0e938552Group%20348447464.webp',
phAfter: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/200ccfb61dce458799879093341107f9Group%20348447463.webp',
// 灯泡
bulbIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/d2a7b1b81bff44c2b9099a8d48d9d6f1Frame%282%29.webp',
};
const props = defineProps({
isTesting: {
type: Boolean,
default: false,
},
isSecondTest: {
type: Boolean,
default: false,
},
triggerShowFinalCard: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['resultShown', 'progressFinished']);
const showResults = ref(false);
const showFinalCard = ref(false);
const activeTabImage = computed(() => {
return props.isSecondTest ? IMAGES.tabInactivePh : IMAGES.tabActiveTds;
});
const inactiveTabImage = computed(() => {
return props.isSecondTest ? IMAGES.tabActivePh : IMAGES.tabInactiveTds;
});
const instructionText = computed(() => {
return props.isSecondTest ? TEXTS.PH.instruction : TEXTS.TDS.instruction;
});
const instructionIcon = computed(() => {
return props.isSecondTest ? IMAGES.instructionPh : IMAGES.instructionTds;
});
const iconMode = computed(() => {
return props.isSecondTest ? 'aspectFit' : 'heightFix';
});
const tipsText = computed(() => {
const content = props.isSecondTest ? TEXTS.PH : TEXTS.TDS;
if (showFinalCard.value) {
return content.tips.result;
}
if (props.isTesting) {
return content.tips.testing;
}
return content.tips.default;
});
const resultLabel = computed(() => {
return props.isSecondTest ? TEXTS.PH.label : TEXTS.TDS.label;
});
const pollutedValue = computed(() => {
if (!showResults.value) return TEXTS.placeholder;
return props.isSecondTest ? TEXTS.PH.polluted : TEXTS.TDS.polluted;
});
const purifiedValue = computed(() => {
if (!showResults.value) return TEXTS.placeholder;
return props.isSecondTest ? TEXTS.PH.purified : TEXTS.TDS.purified;
});
const handleImageError = (e) => {
console.error('Image load failed:', e.detail.errMsg);
};
watch(
() => props.isTesting,
(newVal) => {
if (newVal) {
showResults.value = false;
showFinalCard.value = false;
setTimeout(() => {
showResults.value = true;
emit('progressFinished');
}, 1000);
} else {
showResults.value = false;
showFinalCard.value = false;
}
}
);
watch(
() => props.triggerShowFinalCard,
(newVal) => {
if (newVal) {
showFinalCard.value = true;
emit('resultShown');
}
}
);
</script>
<template> <template>
<view class="tabs-instructions-container"> <view class="tabs-instructions-container">
<view class="tabs-row"> <view class="tabs-row">
...@@ -202,161 +356,6 @@ ...@@ -202,161 +356,6 @@
</view> </view>
</template> </template>
<script setup>
import { computed, ref, watch } from 'vue';
const TEXTS = {
TDS: {
instruction: '点击"开始检测"按钮测试TDS值',
tips: {
default: 'TDS代表总溶解固体,数值越低水质越纯净',
testing: '安吉尔RO反渗透技术可将TDS降至15以下',
result: '优质饮用水TDS值应低于50ppm',
},
label: 'TDS值',
polluted: '450',
purified: '15',
},
PH: {
instruction: "点击‘开始检测’测试水的酸碱度",
tips: {
default: '理想饮用水pH值应在6.5-8.5之间',
testing: 'pH试纸通过颜色变化判断酸碱性',
result: '中性水(pH 7.0)对人体最温和,是理想的饮用水',
},
label: 'PH值',
polluted: '8.5',
purified: '7.0',
},
placeholder: '???',
};
const IMAGES = {
// 两个选项图片
tabActiveTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/b91478ab6f2d4c2186a948ae9b614f45Group%20348447426.webp',
tabActivePh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/b9c5667e95ea48a1b4c10f2d9ee84db6Group%20348447426.webp',
tabInactiveTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/a4bd654c5fd4401bb4d09ce1a8e08aa3Group%20348447427.webp',
tabInactivePh: '/static/1.webp',
// 介绍内部图片
instructionTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/1a62746fad3743d19f01e8b612ed8d42Group%20348447435.webp',
instructionPh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/32ac38db03994072aca53ae518b996eeGroup%20348447450.webp',
phLabel: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/536133e8a44f4482b2848016385d79cbPH.webp',
cardBg: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/87e28ba2f4764f33a36b0d1cfd47d74aRectangle%203589.webp',
// 进度条
progressResult: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/45cebdfeb99a44049d18ab3b04024ac2Group%20348447442.webp',
progressInitial: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/7b21b166605d4c7c9bb4377280202da7Group%20348447442.webp',
// 结果内部图片
warningIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/0c6358f526fd4a72a098e5e46c9a8759Frame%2017.webp',
successIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/dd15bc1646cb4fcb86670b2b1ae04809Group%20348447459.webp',
successIconFrame: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/e7e6a286a568480fada2cafab781cc3cFrame.webp',
badgeIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/44463cae0882429ebd84049240ff5330Group%20348447451.webp',
phChart: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/3df0383e2218465c83af9aeeae10e1baGroup%20348447462.webp',
phBefore: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/8756b9fccb61450a96f34e2b0e938552Group%20348447464.webp',
phAfter: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/200ccfb61dce458799879093341107f9Group%20348447463.webp',
// 灯泡
bulbIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/d2a7b1b81bff44c2b9099a8d48d9d6f1Frame%282%29.webp',
};
const props = defineProps({
isTesting: {
type: Boolean,
default: false,
},
isSecondTest: {
type: Boolean,
default: false,
},
triggerShowFinalCard: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['resultShown', 'progressFinished']);
const showResults = ref(false);
const showFinalCard = ref(false);
const activeTabImage = computed(() => {
return props.isSecondTest ? IMAGES.tabInactivePh : IMAGES.tabActiveTds;
});
const inactiveTabImage = computed(() => {
return props.isSecondTest ? IMAGES.tabActivePh : IMAGES.tabInactiveTds;
});
const instructionText = computed(() => {
return props.isSecondTest ? TEXTS.PH.instruction : TEXTS.TDS.instruction;
});
const instructionIcon = computed(() => {
return props.isSecondTest ? IMAGES.instructionPh : IMAGES.instructionTds;
});
const iconMode = computed(() => {
return props.isSecondTest ? 'aspectFit' : 'heightFix';
});
const tipsText = computed(() => {
const content = props.isSecondTest ? TEXTS.PH : TEXTS.TDS;
if (showFinalCard.value) {
return content.tips.result;
}
if (props.isTesting) {
return content.tips.testing;
}
return content.tips.default;
});
const resultLabel = computed(() => {
return props.isSecondTest ? TEXTS.PH.label : TEXTS.TDS.label;
});
const pollutedValue = computed(() => {
if (!showResults.value) return TEXTS.placeholder;
return props.isSecondTest ? TEXTS.PH.polluted : TEXTS.TDS.polluted;
});
const purifiedValue = computed(() => {
if (!showResults.value) return TEXTS.placeholder;
return props.isSecondTest ? TEXTS.PH.purified : TEXTS.TDS.purified;
});
const handleImageError = (e) => {
console.error('Image load failed:', e.detail.errMsg);
};
watch(
() => props.isTesting,
(newVal) => {
if (newVal) {
showResults.value = false;
showFinalCard.value = false;
setTimeout(() => {
showResults.value = true;
emit('progressFinished');
}, 1000);
} else {
showResults.value = false;
showFinalCard.value = false;
}
}
);
watch(
() => props.triggerShowFinalCard,
(newVal) => {
if (newVal) {
showFinalCard.value = true;
emit('resultShown');
}
}
);
</script>
<style scoped lang="scss"> <style scoped lang="scss">
.tabs-instructions-container { .tabs-instructions-container {
width: 100%; width: 100%;
......
<script setup>
import { computed } from 'vue';
const props = defineProps({
isTesting: {
type: Boolean,
default: false,
},
isSecondTest: {
type: Boolean,
default: false,
},
});
//烧杯图片
const IMAGES = {
pollutedDefault: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/d2729bd9ca6249728c29c66a6e7f1baf%E7%83%A7%E6%9D%AF_%E6%B1%A1%E6%9F%93%E6%B0%B4%E7%89%88%202.webp',
purifiedDefault: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/048d81b1821442c3b80d312e4ef7c1e4%E7%83%A7%E6%9D%AF_%E6%B0%B4%E9%9D%A2%E5%B9%B3%E6%95%B4%E7%89%88%201.webp',
vsIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/8678ceab4c7b41ac80cc1982054c45f4VS.webp',
pollutedPh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/a277ce4fb175493c9cedd60f6aa788d1%E7%83%A7%E6%9D%AF_%E6%9C%AA%E4%BD%BF%E7%94%A8%E9%85%B8%E7%A2%B1%E8%AF%95%E7%BA%B8%201.webp',
pollutedTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/d775e2166f2d426f857e69dc3d115d47%E7%83%A7%E6%9D%AF_%E6%97%A0%E6%B0%B4%E6%BB%B4%E5%9B%9B%E8%82%A2_%E5%9B%BE2%201.webp',
purifiedPh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/7b1c0214a1344946908e8de7ddaaea5e%E7%83%A7%E6%9D%AF_%E6%9C%AA%E4%BD%BF%E7%94%A8%E9%85%B8%E7%A2%B1%E8%AF%95%E7%BA%B8_%E5%87%80%E5%8C%96%E6%B0%B4%201.webp',
purifiedTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/c18364684a82445382105028ec6d8f44%E7%83%A7%E6%9D%AF_%E6%97%A0%E6%B0%B4%E6%BB%B4%E5%9B%9B%E8%82%A2_%E5%9B%BE1%201.webp',
};
const pollutedImageTesting = computed(() => {
return props.isSecondTest ? IMAGES.pollutedPh : IMAGES.pollutedTds;
});
const purifiedImageTesting = computed(() => {
return props.isSecondTest ? IMAGES.purifiedPh : IMAGES.purifiedTds;
});
</script>
<template> <template>
<view class="main-content"> <view class="main-content">
<view class="beaker-section"> <view class="beaker-section">
...@@ -42,40 +76,6 @@ ...@@ -42,40 +76,6 @@
</view> </view>
</template> </template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
isTesting: {
type: Boolean,
default: false,
},
isSecondTest: {
type: Boolean,
default: false,
},
});
//烧杯图片
const IMAGES = {
pollutedDefault: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/d2729bd9ca6249728c29c66a6e7f1baf%E7%83%A7%E6%9D%AF_%E6%B1%A1%E6%9F%93%E6%B0%B4%E7%89%88%202.webp',
purifiedDefault: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/048d81b1821442c3b80d312e4ef7c1e4%E7%83%A7%E6%9D%AF_%E6%B0%B4%E9%9D%A2%E5%B9%B3%E6%95%B4%E7%89%88%201.webp',
vsIcon: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/8678ceab4c7b41ac80cc1982054c45f4VS.webp',
pollutedPh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/a277ce4fb175493c9cedd60f6aa788d1%E7%83%A7%E6%9D%AF_%E6%9C%AA%E4%BD%BF%E7%94%A8%E9%85%B8%E7%A2%B1%E8%AF%95%E7%BA%B8%201.webp',
pollutedTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/d775e2166f2d426f857e69dc3d115d47%E7%83%A7%E6%9D%AF_%E6%97%A0%E6%B0%B4%E6%BB%B4%E5%9B%9B%E8%82%A2_%E5%9B%BE2%201.webp',
purifiedPh: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/7b1c0214a1344946908e8de7ddaaea5e%E7%83%A7%E6%9D%AF_%E6%9C%AA%E4%BD%BF%E7%94%A8%E9%85%B8%E7%A2%B1%E8%AF%95%E7%BA%B8_%E5%87%80%E5%8C%96%E6%B0%B4%201.webp',
purifiedTds: 'https://userone-oss-cdn.angelgroup.com.cn/static/2026-01-23/c18364684a82445382105028ec6d8f44%E7%83%A7%E6%9D%AF_%E6%97%A0%E6%B0%B4%E6%BB%B4%E5%9B%9B%E8%82%A2_%E5%9B%BE1%201.webp',
};
const pollutedImageTesting = computed(() => {
return props.isSecondTest ? IMAGES.pollutedPh : IMAGES.pollutedTds;
});
const purifiedImageTesting = computed(() => {
return props.isSecondTest ? IMAGES.purifiedPh : IMAGES.purifiedTds;
});
</script>
<style scoped lang="scss"> <style scoped lang="scss">
.main-content { .main-content {
flex: 1; flex: 1;
......
<template>
<view class="container">
<image class="bg" :src="assets.bg" mode="aspectFill" />
<view class="header" :style="{ paddingTop: statusBarHeight + 'px' }">
<image class="title" :src="assets.title" mode="heightFix" />
</view>
<Beaker :isTesting="isTesting" :isSecondTest="isSecondTest" />
<view class="bottom-panel">
<TabsInstructionsPanel
:isTesting="isTesting"
:isSecondTest="isSecondTest"
:triggerShowFinalCard="triggerShowFinalCard"
@resultShown="handleResultShown"
@progressFinished="handleProgressFinished"
/>
<BottomActionBar
:isTesting="isTesting"
:isSecondTest="isSecondTest"
:isResultShown="isResultShown"
:isProgressFinished="isProgressFinished"
@startTest="startTest"
@restartTest="restartTest"
@showFinalCard="handleShowFinalCard"
@showCompletionPopup="handleShowCompletion"
/>
</view>
<view class="completion-overlay" v-if="showCompletion" @click="closeCompletion">
<image
class="completion-img"
:src="assets.completionPopup"
mode="scaleToFill"
@click.stop="closeCompletion"
/>
</view>
</view>
</template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad } from '@dcloudio/uni-app';
...@@ -103,6 +61,48 @@ const closeCompletion = () => { ...@@ -103,6 +61,48 @@ const closeCompletion = () => {
}; };
</script> </script>
<template>
<view class="container">
<image class="bg" :src="assets.bg" mode="aspectFill" />
<view class="header" :style="{ paddingTop: statusBarHeight + 'px' }">
<image class="title" :src="assets.title" mode="heightFix" />
</view>
<Beaker :isTesting="isTesting" :isSecondTest="isSecondTest" />
<view class="bottom-panel">
<TabsInstructionsPanel
:isTesting="isTesting"
:isSecondTest="isSecondTest"
:triggerShowFinalCard="triggerShowFinalCard"
@resultShown="handleResultShown"
@progressFinished="handleProgressFinished"
/>
<BottomActionBar
:isTesting="isTesting"
:isSecondTest="isSecondTest"
:isResultShown="isResultShown"
:isProgressFinished="isProgressFinished"
@startTest="startTest"
@restartTest="restartTest"
@showFinalCard="handleShowFinalCard"
@showCompletionPopup="handleShowCompletion"
/>
</view>
<view class="completion-overlay" v-if="showCompletion" @click="closeCompletion">
<image
class="completion-img"
:src="assets.completionPopup"
mode="scaleToFill"
@click.stop="closeCompletion"
/>
</view>
</view>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100vw; width: 100vw;
......
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