Commit 9f5c3d25 by Hantao

feat: 新增徽章页面并优化首页布局和交互

- 新增徽章展示页面,包含徽章解锁进度和展示功能
- 重构首页用户信息区域,添加用户头像和徽章展示
- 新增通用拖拽钩子函数,优化第二关拖拽交互逻辑
- 添加进度条图片资源,优化加载进度显示
- 移除调试日志并修复定时器内存泄漏问题
- 优化侧边栏布局,添加徽章页面导航入口
parent b521a783
...@@ -13,6 +13,12 @@ ...@@ -13,6 +13,12 @@
} }
}, },
{ {
"path": "pages/badge/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/second/index", "path": "pages/second/index",
"style": { "style": {
"navigationStyle": "custom" "navigationStyle": "custom"
......
<script setup>
import { ref, computed } from 'vue';
const ASSETS = Object.freeze({
blank: '/static/badge/blank.webp',
badge1: '/static/badge/badge1.webp',
badge2: '/static/badge/badge2.webp',
badge3: '/static/badge/badge3.webp',
badge4: '/static/badge/badge4.webp',
badge5: '/static/badge/badge5.webp',
badge1Gray: '/static/badge/badge1-grey.webp',
badge2Gray: '/static/badge/badge2-grey.webp',
badge3Gray: '/static/badge/badge3-grey.webp',
badge4Gray: '/static/badge/badge4-grey.webp',
badge5Gray: '/static/badge/badge5-grey.webp',
display1: '/static/badge/display1.webp',
display2: '/static/badge/display2.webp',
display3: '/static/badge/display3.webp',
display4: '/static/badge/display4.webp',
display5: '/static/badge/display5.webp',
})
const badges = computed(() => [
{ id: 1, title: '水源净化卫士', sub: '完成第一关', iconKey: 'badge1', unlocked: badgeImgs.value >= 1 },
{ id: 2, title: '滤芯工程师', sub: '完成第二关', iconKey: 'badge2', unlocked: badgeImgs.value >= 2 },
{ id: 3, title: '水质分析高手', sub: '完成第三关', iconKey: 'badge3', unlocked: badgeImgs.value >= 3 },
{ id: 4, title: '精准控水达人', sub: '完成第四关', iconKey: 'badge4', unlocked: badgeImgs.value >= 4 },
{ id: 5, title: '水质专家', sub: '完成全部关卡', iconKey: 'badge5', unlocked: badgeImgs.value >= 5 }
]);
const badgeImgs = ref(1);
const unlockedCount = computed(() => badgeImgs.value);
const currentDisplayImg = computed(() => {
switch (badgeImgs.value) {
case 0: return ASSETS.blank;
case 1: return ASSETS.display1;
case 2: return ASSETS.display2;
case 3: return ASSETS.display3;
case 4: return ASSETS.display4;
case 5: return ASSETS.display5;
default: return ASSETS.blank;
}
});
const continueText = computed(() => {
return badgeImgs.value === 5 ? '已完成全部闯关' : '继续闯关';
});
const toggleBadge = () => {
if (badgeImgs.value < 5) {
badgeImgs.value++;
} else {
badgeImgs.value = 0;
}
};
const navToContinuePage = () => {
uni.navigateTo({
url: '/pages/start/index'
});
}
</script>
<template>
<view class="container">
<view class="title-box">
<view class="title-content">
<view class="title">我的徽章</view>
<view class="badge-count">已获得{{ unlockedCount }}/5徽章</view>
</view>
<image :src="currentDisplayImg" mode="aspectFit" class="badge-display-img" />
</view>
<view class="badge-box box1">
<view
class="badge-item"
v-for="item in badges.slice(0, 3)"
:key="item.id"
@click="toggleBadge"
>
<image
:src="item.unlocked ? ASSETS[item.iconKey] : ASSETS[item.iconKey + 'Gray']"
mode="aspectFit"
class="badge-image"
/>
<text class="badge-title">{{ item.title }}</text>
<text class="badge-sub">{{ item.sub }}</text>
</view>
</view>
<view class="badge-box box2">
<view
class="badge-item"
v-for="item in badges.slice(3, 5)"
:key="item.id"
@click="toggleBadge"
>
<image
:src="item.unlocked ? ASSETS[item.iconKey] : ASSETS[item.iconKey + 'Gray']"
mode="aspectFit"
class="badge-image"
/>
<text class="badge-title">{{ item.title }}</text>
<text class="badge-sub">{{ item.sub }}</text>
</view>
</view>
<view class="continue" @click="navToContinuePage">
<text class="continue-text">{{ continueText }}</text>
</view>
</view>
</template>
<style lang="scss" scoped>
.container {
width: 100vw;
height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
background-color: #73D8FF;
box-sizing: border-box;
}
.title-box {
margin-top: 180rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
color: #2D4B5D;
width: 100%;
padding: 0 60rpx;
box-sizing: border-box;
text-shadow: 1rpx 1rpx 0 #FFF, -1rpx -1rpx 0 #FFF, 1rpx -1rpx 0 #FFF, -1rpx 1rpx 0 #FFF;
.title-content {
display: flex;
flex-direction: column;
}
.title {
font-size: 60rpx;
margin-bottom: 20rpx;
font-weight: 700;
}
.badge-count {
font-size: 28rpx;
font-weight: 600;
}
.badge-display-img {
width: 258rpx;
height: 258rpx;
margin-right: -40rpx;
}
}
.badge-box {
width: 100%;
height: 344rpx;
border-radius: 24rpx;
background: linear-gradient(180deg, #D2F2FF 0%, #e4f8ff73 69.08%, #ecfaff00 100%);
display: flex;
justify-content: flex-start;
align-items: flex-start;
padding-top: 20rpx;
box-sizing: border-box;
.badge-item {
width: 33.33%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
transition: transform 0.2s;
&:active {
transform: scale(0.95);
}
.badge-image {
width: 160rpx;
height: 160rpx;
margin-bottom: 6rpx;
}
.badge-title {
font-size: 28rpx;
font-weight: bold;
color: #2D4B5D;
margin-bottom: 4rpx;
text-align: center;
text-shadow: 1rpx 1rpx 0 #FFF;
}
.badge-sub {
font-size: 22rpx;
color: #2D4B5D;
text-align: center;
}
}
}
.box1 {
position: absolute;
top: 480rpx;
}
.box2 {
position: absolute;
top: 760rpx;
}
.continue {
display: flex;
align-items: center;
justify-content: center;
width: 486rpx;
height: 40rpx;
padding: 24rpx 0;
background: #3A9FDC;
border: 4rpx solid #ffffff99;
border-radius: 60rpx;
box-shadow: 0 6rpx 2rpx #529bbda6;
position: absolute;
bottom: 124rpx;
.continue-text {
color: #ffffffe6;
font-size: 36rpx;
font-weight: 600;
}
}
</style>
\ No newline at end of file
<script setup> <script setup>
import { onMounted, watch } from 'vue'; import { onMounted, watch, onUnmounted, computed } from 'vue';
import { useProgress } from './hook/speed'; import { useProgress } from './hook/speed';
// 图片路径 // 图片路径
const ASSETS = { const ASSETS = Object.freeze({
bg: '/static/shouping/bg.webp', bg: '/static/shouping/bg.webp',
logo: '/static/shouping/logo.webp', logo: '/static/shouping/logo.webp',
title: '/static/shouping/title.webp', title: '/static/shouping/title.webp',
water: '/static/shouping/water.webp', water: '/static/shouping/water.webp',
}; });
const { progress, start } = useProgress(); const { progress, start } = useProgress();
let navTimer = null;
const progressPercent = computed(() => Math.floor(progress.value));
watch(progress, (newVal) => { watch(progress, (newVal) => {
if (newVal >= 100) { if (newVal >= 100) {
setTimeout(handleNavigation, 1000); if (!navTimer) {
navTimer = setTimeout(handleNavigation, 1000);
}
} }
}); });
...@@ -28,6 +33,7 @@ const handleNavigation = () => { ...@@ -28,6 +33,7 @@ const handleNavigation = () => {
icon: 'none', icon: 'none',
duration: 2000 duration: 2000
}); });
navTimer = null;
} }
}); });
}; };
...@@ -35,6 +41,13 @@ const handleNavigation = () => { ...@@ -35,6 +41,13 @@ const handleNavigation = () => {
onMounted(() => { onMounted(() => {
start(); start();
}); });
onUnmounted(() => {
if (navTimer) {
clearTimeout(navTimer);
navTimer = null;
}
});
</script> </script>
<template> <template>
...@@ -64,7 +77,7 @@ onMounted(() => { ...@@ -64,7 +77,7 @@ onMounted(() => {
</view> </view>
</view> </view>
<view class="loading-info"> <view class="loading-info">
<text class="loading-text">加载资源中 {{ Math.floor(progress) }}%</text> <text class="loading-text">加载资源中 {{ progressPercent }}%</text>
</view> </view>
</view> </view>
</view> </view>
......
import { ref } from 'vue';
/**
* 通用拖拽逻辑 Hook
* @param {Object} options 配置项
* @param {Function} [options.onDragStart] 拖拽开始回调
* @param {Function} [options.onDragEnd] 拖拽结束回调
* @param {Function} [options.onDrop] 拖拽释放回调,参数为 (item, position)
* @returns {Object} 拖拽状态与事件处理函数
*/
export function useDrag(options = {}) {
const { onDrop, onDragStart, onDragEnd, delay = 100 } = options;
const isDragging = ref(false);
const draggedItem = ref(null);
const dragPosition = ref({ x: 0, y: 0 });
const startPosition = ref({ x: 0, y: 0 });
const dragTimer = ref(null);
const handleStart = (clientX, clientY, item) => {
// 如果物品已放置,不可拖拽
if (item && item.status === 'placed') return;
startPosition.value = { x: clientX, y: clientY };
dragPosition.value = { x: clientX, y: clientY };
if (dragTimer.value) clearTimeout(dragTimer.value);
// 延时触发拖拽
dragTimer.value = setTimeout(() => {
isDragging.value = true;
draggedItem.value = item;
if (onDragStart) onDragStart(item);
}, delay);
};
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 (onDrop) {
onDrop(draggedItem.value, dragPosition.value);
}
if (onDragEnd) onDragEnd(draggedItem.value);
isDragging.value = false;
draggedItem.value = null;
}
};
// Touch Event Handlers
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 = () => {
handleEnd();
};
// Mouse Event Handlers
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();
};
return {
isDragging,
draggedItem,
dragPosition,
handleTouchStart,
handleTouchMove,
handleTouchEnd,
handleMouseDown,
handleMouseMove,
handleMouseUp,
handleMouseLeave: handleMouseUp
};
}
<script setup> <script setup>
const ASSETS = { const ASSETS = Object.freeze({
bg: '/static/second/bg.webp', bg: '/static/second/bg.webp',
info: '/static/second/info.webp', info: '/static/second/info.webp',
title: '/static/second/title.webp', title: '/static/second/title.webp',
jdt: '/static/second/jdt.webp', jdt: '/static/second/jdt.webp',
next: '/static/second/next.webp', next: '/static/second/next.webp',
}; });
const handleNext = () => { const handleNext = () => {
uni.navigateTo({ uni.navigateTo({
......
<script setup> <script setup>
const ASSETS = { const ASSETS = Object.freeze({
title: '/static/second/part1/title.webp', title: '/static/second/part1/title.webp',
center: '/static/second/part1/center.webp', center: '/static/second/part1/center.webp',
zzjd: '/static/second/zzjd1.webp', zzjd: '/static/second/zzjd1.webp',
...@@ -7,7 +7,7 @@ const ASSETS = { ...@@ -7,7 +7,7 @@ const ASSETS = {
resetIcon: '/static/second/reset.webp', resetIcon: '/static/second/reset.webp',
blockIcon: '/static/second/block.webp', blockIcon: '/static/second/block.webp',
arrow: '/static/second/jt.webp', arrow: '/static/second/jt.webp',
}; });
</script> </script>
<template> <template>
......
<script setup> <script setup>
import { ref, onMounted, getCurrentInstance, computed } from 'vue'; import { ref, onMounted, getCurrentInstance, computed } from 'vue';
import { useDrag } from './hooks/useDrag';
const ASSETS = { const ASSETS = Object.freeze({
title: '/static/second/part2/title.webp', title: '/static/second/part2/title.webp',
center: '/static/second/part2/center.webp', center: '/static/second/part2/center.webp',
zzjd: '/static/second/zzjd1.webp', zzjd1: '/static/second/zzjd1.webp',
zzjd2: '/static/second/zzjd2.webp', zzjd2: '/static/second/zzjd2.webp',
zzjd3: '/static/second/zzjd3.webp', zzjd3: '/static/second/zzjd3.webp',
zzjd4: '/static/second/zzjd4.webp', zzjd4: '/static/second/zzjd4.webp',
zzjd5: '/static/second/zzjd5.webp', zzjd5: '/static/second/zzjd5.webp',
zzjd6: '/static/second/zzjd6.webp', zzjd6: '/static/second/zzjd6.webp',
zzjd: '/static/second/zzjd.webp',
panzi: '/static/second/panzi.webp', panzi: '/static/second/panzi.webp',
resetIcon: '/static/second/reset.webp', resetIcon: '/static/second/reset.webp',
blockIcon: '/static/second/block.webp', blockIcon: '/static/second/block.webp',
...@@ -17,7 +19,7 @@ const ASSETS = { ...@@ -17,7 +19,7 @@ const ASSETS = {
lv: '/static/second/part2/lv.webp', lv: '/static/second/part2/lv.webp',
ys: '/static/second/part2/ys.webp', ys: '/static/second/part2/ys.webp',
yw: '/static/second/part2/yw.webp', yw: '/static/second/part2/yw.webp',
lv1: '/static/second/part2/lv1.webp', lv1: '/static/second/part2/lv1.webp',
ys1: '/static/second/part2/ys1.webp', ys1: '/static/second/part2/ys1.webp',
yw1: '/static/second/part2/yw1.webp', yw1: '/static/second/part2/yw1.webp',
lv2: '/static/second/part2/lv2.webp', lv2: '/static/second/part2/lv2.webp',
...@@ -27,7 +29,7 @@ const ASSETS = { ...@@ -27,7 +29,7 @@ const ASSETS = {
ys3: '/static/second/part2/ys3.webp', ys3: '/static/second/part2/ys3.webp',
yw3: '/static/second/part2/yw3.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', 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([ const trayItems = ref([
{ id: 1, type: 'lv', icon: ASSETS.lv1, label: '点击拖动', status: 'default' }, { id: 1, type: 'lv', icon: ASSETS.lv1, label: '点击拖动', status: 'default' },
...@@ -46,39 +48,31 @@ const isComplete = computed(() => { ...@@ -46,39 +48,31 @@ const isComplete = computed(() => {
return placedCount.value === 6; return placedCount.value === 6;
}); });
const PROGRESS_MAP = [
ASSETS.zzjd1, // 0
ASSETS.zzjd2, // 1
ASSETS.zzjd3, // 2
ASSETS.zzjd4, // 3
ASSETS.zzjd5, // 4
ASSETS.zzjd6, // 5
ASSETS.zzjd, // 6
];
const currentProgressImg = computed(() => { const currentProgressImg = computed(() => {
const count = placedCount.value; return PROGRESS_MAP[placedCount.value] || ASSETS.zzjd1;
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 showCompletion = ref(false);
const handleItemClick = (item) => {
console.log('Item clicked:', item);
};
const handleReset = () => { const handleReset = () => {
trayItems.value.forEach(item => { trayItems.value.forEach(item => {
item.status = 'default'; item.status = 'default';
}); });
console.log('Reset clicked');
}; };
const handleComplete = () => { const handleComplete = () => {
if (isComplete.value) { if (isComplete.value) {
console.log('Complete clicked - unlocked');
showCompletion.value = true; showCompletion.value = true;
} else {
console.log('Complete clicked - locked');
} }
}; };
...@@ -86,11 +80,6 @@ const closeCompletion = () => { ...@@ -86,11 +80,6 @@ const closeCompletion = () => {
showCompletion.value = false; 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 centerRect = ref(null);
const instance = getCurrentInstance(); const instance = getCurrentInstance();
...@@ -99,94 +88,33 @@ onMounted(() => { ...@@ -99,94 +88,33 @@ onMounted(() => {
query.select('.center-wrapper').boundingClientRect(data => { query.select('.center-wrapper').boundingClientRect(data => {
if (data) { if (data) {
centerRect.value = data; centerRect.value = data;
console.log('Center rect:', data);
} }
}).exec(); }).exec();
}); });
const handleStart = (clientX, clientY, item) => { const {
if (item.status === 'placed') return; isDragging,
draggedItem,
startPosition.value = { x: clientX, y: clientY }; dragPosition,
dragPosition.value = { x: clientX, y: clientY }; handleTouchStart,
handleTouchMove,
dragTimer.value = setTimeout(() => { handleTouchEnd,
isDragging.value = true; handleMouseDown,
draggedItem.value = item; handleMouseMove,
console.log('Drag started', item); handleMouseUp,
}, 500); handleMouseLeave
}; } = useDrag({
onDrop: (item, position) => {
const handleMove = (clientX, clientY) => { if (centerRect.value && item) {
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 { left, right, top, bottom } = centerRect.value;
const { x, y } = dragPosition.value; const { x, y } = position;
if (x >= left && x <= right && y >= top && y <= bottom) { if (x >= left && x <= right && y >= top && y <= bottom) {
if (draggedItem.value) { item.status = 'placed';
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) => { const getTrayItemIcon = (item) => {
if (item.status === 'placed') { if (item.status === 'placed') {
...@@ -263,7 +191,6 @@ const getGhostIcon = () => { ...@@ -263,7 +191,6 @@ const getGhostIcon = () => {
v-for="item in trayItems" v-for="item in trayItems"
:key="item.id" :key="item.id"
class="tray-item" class="tray-item"
@click="handleItemClick(item)"
@touchstart="handleTouchStart($event, item)" @touchstart="handleTouchStart($event, item)"
@touchmove.stop.prevent="handleTouchMove" @touchmove.stop.prevent="handleTouchMove"
@touchend="handleTouchEnd(item)" @touchend="handleTouchEnd(item)"
...@@ -284,15 +211,20 @@ const getGhostIcon = () => { ...@@ -284,15 +211,20 @@ const getGhostIcon = () => {
<image class="btn-icon" :src="ASSETS.resetIcon" mode="widthFix" /> <image class="btn-icon" :src="ASSETS.resetIcon" mode="widthFix" />
<text>重置</text> <text>重置</text>
</view> </view>
<view <view
class="action-btn complete-btn" :class="['action-btn', 'complete-btn', { 'complete-btn-active': placedCount === 6 }]"
@click="handleComplete" @click="handleComplete"
> >
<image class="btn-icon block-icon" :src="ASSETS.blockIcon" mode="widthFix" /> <template v-if="placedCount < 6">
<view class="btn-text-col"> <image class="btn-icon block-icon" :src="ASSETS.blockIcon" mode="widthFix" />
<text class="main-text">完成</text> <view class="btn-text-col">
<text class="sub-text">完成{{ placedCount }}/6吸附</text> <text class="main-text">完成</text>
</view> <text class="sub-text">完成{{ placedCount }}/6吸附</text>
</view>
</template>
<template v-else>
<text class="complete-text">完成</text>
</template>
</view> </view>
</view> </view>
...@@ -520,6 +452,17 @@ const getGhostIcon = () => { ...@@ -520,6 +452,17 @@ const getGhostIcon = () => {
line-height: 1; line-height: 1;
} }
} }
.complete-text {
font-size: 34rpx;
font-weight: bold;
}
&.complete-btn-active {
border-radius: 40rpx;
background: #FAD43E;
box-shadow: 0 4rpx 2rpx 0 #ffffff80 inset, 0 6rpx 2rpx 0 #3d330ba6;
}
} }
} }
......
...@@ -8,10 +8,60 @@ const ASSETS = { ...@@ -8,10 +8,60 @@ const ASSETS = {
startBtn: '/static/start/start.webp', startBtn: '/static/start/start.webp',
restartBtn: '/static/start/restart.webp', restartBtn: '/static/start/restart.webp',
progress: '/static/start/yxjd.webp', progress: '/static/start/yxjd.webp',
progress1: '/static/start/yxjd1.webp',
progress2: '/static/start/yxjd2.webp',
progress3: '/static/start/yxjd3.webp',
progress4: '/static/start/yxjd4.webp',
finish: '/static/start/finish.webp',
top: '/static/start/top.webp', top: '/static/start/top.webp',
bottom: '/static/start/bottom.webp', bottom: '/static/start/bottom.webp',
sidebarIcon: '/static/start/cbl.webp', sidebarIcon: '/static/start/cbl.webp',
sidebarMenu: '/static/start/all.webp', sidebarMenu: '/static/start/all.webp',
badge1: '/static/start/badge1.webp',
badge2: '/static/start/badge2.webp',
badge3: '/static/start/badge3.webp',
badge4: '/static/start/badge4.webp',
badge5: '/static/start/badge5.webp',
};
const progressImgs = ref(0);
const getProgressImage = () => {
switch (progressImgs.value) {
case 0:
return ASSETS.progress;
case 1:
return ASSETS.progress1;
case 2:
return ASSETS.progress2;
case 3:
return ASSETS.progress3;
case 4:
return ASSETS.progress4;
case 5:
return ASSETS.finish;
default:
return ASSETS.progress;
}
};
const badgeImgs = ref(0);
const getBadgeImage = () => {
switch (badgeImgs.value) {
case 0:
return ASSETS.badge1;
case 1:
return ASSETS.badge2;
case 2:
return ASSETS.badge3;
case 3:
return ASSETS.badge4;
case 4:
return ASSETS.badge5;
default:
return ASSETS.badge1;
}
}; };
// 侧边栏状态 // 侧边栏状态
...@@ -25,6 +75,12 @@ const handleRestart = () => { ...@@ -25,6 +75,12 @@ const handleRestart = () => {
}; };
const navToBadgePage = () => {
uni.navigateTo({
url: '/pages/badge/index'
});
};
const toggleSidebar = () => { const toggleSidebar = () => {
isSidebarExpanded.value = !isSidebarExpanded.value; isSidebarExpanded.value = !isSidebarExpanded.value;
}; };
...@@ -43,7 +99,18 @@ const closeSidebar = () => { ...@@ -43,7 +99,18 @@ const closeSidebar = () => {
<!-- 顶部用户信息 --> <!-- 顶部用户信息 -->
<view class="top-section"> <view class="top-section">
<image class="top-info" :src="ASSETS.top" mode="widthFix" /> <view class="top-info">
<view class="user-info">
<view class="user-name">用户198xxxxx890</view>
<view class="user-level">钻石会员</view>
</view>
</view>
<view class="user-avatar">
<image class="avatar" src="" mode="aspectFill" />
</view>
<view class="user-badge">
<image class="badge" :src="getBadgeImage()" mode="aspectFill" />
</view>
</view> </view>
<!-- 主要内容 --> <!-- 主要内容 -->
...@@ -55,7 +122,7 @@ const closeSidebar = () => { ...@@ -55,7 +122,7 @@ const closeSidebar = () => {
<!-- 游戏进度展示 --> <!-- 游戏进度展示 -->
<view class="progress-box"> <view class="progress-box">
<image class="progress-img" :src="ASSETS.progress" mode="widthFix" /> <image class="progress-img" :src="getProgressImage()" mode="widthFix" />
</view> </view>
<!-- 按钮区域 --> <!-- 按钮区域 -->
...@@ -72,6 +139,10 @@ const closeSidebar = () => { ...@@ -72,6 +139,10 @@ const closeSidebar = () => {
<!-- 侧边栏 --> <!-- 侧边栏 -->
<view class="sidebar-container" :class="{ expanded: isSidebarExpanded }"> <view class="sidebar-container" :class="{ expanded: isSidebarExpanded }">
<view v-if="isSidebarExpanded" class="sidebar-custom-content">
<view class="badge-nav-btn" @click.stop="navToBadgePage">
</view>
</view>
<view class="sidebar-wrapper" @click.stop="toggleSidebar"> <view class="sidebar-wrapper" @click.stop="toggleSidebar">
<image <image
v-if="!isSidebarExpanded" v-if="!isSidebarExpanded"
...@@ -116,7 +187,7 @@ const closeSidebar = () => { ...@@ -116,7 +187,7 @@ const closeSidebar = () => {
position: relative; position: relative;
z-index: 1; z-index: 1;
width: 95%; width: 95%;
padding-top: 162rpx; padding-top: 180rpx;
padding-left: 20rpx; padding-left: 20rpx;
padding-right: 20rpx; padding-right: 20rpx;
box-sizing: border-box; box-sizing: border-box;
...@@ -124,14 +195,82 @@ const closeSidebar = () => { ...@@ -124,14 +195,82 @@ const closeSidebar = () => {
justify-content: center; justify-content: center;
.top-info { .top-info {
width: 100%; width: 80%;
height: auto; height: 132rpx;
background: linear-gradient(270deg, #1eaafe00 0%, #0298F3 100%);
.user-info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
padding-left: 80rpx;
height: 100%;
box-sizing: border-box;
.user-name {
margin-left: 8rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 600;
}
.user-level {
margin-top: 8rpx;
display: flex;
justify-content: center;
align-items: center;
width: 116rpx;
height: 32rpx;
border-radius: 72rpx;
border: 1rpx solid #ffffff80;
background: linear-gradient(180deg, #ACAEEE 0%, #c0c0ff80 100%);
color: #0E015F;
font-size: 20rpx;
}
}
}
.user-avatar {
position: absolute;
top: 57%;
left: 24rpx;
width: 132rpx;
height: 132rpx;
border-radius: 50%;
border: 1.72rpx solid #4495E7;
background: linear-gradient(180deg, #8DDAFF 0%, #4FA4E5 46.15%, #A1E1FF 100%);
box-shadow: 0 3.42rpx 1.72px 0 #00000040;
display: flex;
justify-content: center;
align-items: center;
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background: #D9D9D9;
border: 1.72rpx solid #4495E7;
}
}
.user-badge {
position: absolute;
top: 42%;
right: 20rpx;
width: 240rpx;
height: 240rpx;
.badge {
width: 100%;
height: 100%;
}
} }
} }
.bottom-decoration { .bottom-decoration {
position: absolute; position: absolute;
bottom: 90rpx; bottom: 100rpx;
left: 40rpx; left: 40rpx;
width: 90%; width: 90%;
z-index: 1; z-index: 1;
...@@ -160,7 +299,7 @@ const closeSidebar = () => { ...@@ -160,7 +299,7 @@ const closeSidebar = () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
width: 100%; width: 95%;
margin-top: -40rpx; margin-top: -40rpx;
position: relative; position: relative;
z-index: 5; z-index: 5;
...@@ -208,6 +347,7 @@ const closeSidebar = () => { ...@@ -208,6 +347,7 @@ const closeSidebar = () => {
z-index: 20; z-index: 20;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: flex-start;
&.expanded { &.expanded {
top: 408rpx; top: 408rpx;
...@@ -217,6 +357,21 @@ const closeSidebar = () => { ...@@ -217,6 +357,21 @@ const closeSidebar = () => {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
} }
.sidebar-custom-content {
position: absolute;
top: 10rpx;
z-index: 25;
animation: fadeIn 0.5s ease-out;
.badge-nav-btn {
width: 170rpx;
height: 90rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
.sidebar-icon { .sidebar-icon {
width: auto; width: auto;
...@@ -241,6 +396,11 @@ const closeSidebar = () => { ...@@ -241,6 +396,11 @@ const closeSidebar = () => {
} }
} }
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes bounceIn { @keyframes bounceIn {
0% { 0% {
transform: scale(0.5); transform: scale(0.5);
......
...@@ -986,6 +986,7 @@ watch( ...@@ -986,6 +986,7 @@ watch(
font-size: 32rpx; font-size: 32rpx;
font-weight: 600; font-weight: 600;
margin-left: 28rpx; margin-left: 28rpx;
text-shadow: 2rpx 2rpx 0 #1B5CA3, -2rpx -2rpx 0 #1B5CA3, 2rpx -2rpx 0 #1B5CA3, -2rpx 2rpx 0 #1B5CA3;
} }
} }
......
...@@ -25,7 +25,6 @@ onLoad(() => { ...@@ -25,7 +25,6 @@ onLoad(() => {
}); });
const startTest = () => { const startTest = () => {
console.log('Start detection');
isTesting.value = true; isTesting.value = true;
isProgressFinished.value = false; isProgressFinished.value = false;
triggerShowFinalCard.value = false; triggerShowFinalCard.value = false;
...@@ -44,7 +43,6 @@ const handleShowFinalCard = () => { ...@@ -44,7 +43,6 @@ const handleShowFinalCard = () => {
}; };
const restartTest = () => { const restartTest = () => {
console.log('Restart detection');
isTesting.value = false; isTesting.value = false;
isResultShown.value = false; isResultShown.value = false;
isProgressFinished.value = false; isProgressFinished.value = false;
......
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