386 lines
7.6 KiB
Vue
386 lines
7.6 KiB
Vue
<template>
|
||
<view v-if="visible" class="modal-wrapper" @touchmove.stop.prevent @click.stop>
|
||
<transition name="fade">
|
||
<view v-if="visible" class="modal-mask" @touchstart.stop="handleMaskTouchStart" @click.stop>
|
||
</view>
|
||
</transition>
|
||
|
||
<transition name="scale">
|
||
<view v-if="visible" class="modal-container" @click.stop>
|
||
<!-- 背景图片 -->
|
||
<image class="modal-background" src="/static/background/friend-list-bg.png" mode="aspectFill"></image>
|
||
|
||
<!-- 蒙层 -->
|
||
<view class="modal-overlay"></view>
|
||
|
||
<!-- 关闭按钮 -->
|
||
<view class="close-button" @touchstart.stop="handleCloseTouchStart" @touchend.stop="handleCloseTouchEnd" @click="handleCloseClick">
|
||
<text class="close-icon">×</text>
|
||
</view>
|
||
|
||
<!-- 内容区域 -->
|
||
<view class="modal-content" @touchstart.stop @touchend.stop @click.stop>
|
||
<!-- 标题 -->
|
||
<text class="modal-title">每日任务</text>
|
||
|
||
<!-- 任务列表 -->
|
||
<view class="task-list">
|
||
<view
|
||
v-for="task in tasks"
|
||
:key="task.id"
|
||
class="task-item"
|
||
>
|
||
<!-- 任务名称 -->
|
||
<text class="task-name">{{ task.name }}</text>
|
||
|
||
<!-- 奖励金额 -->
|
||
<text class="task-reward">+{{ task.reward }}</text>
|
||
|
||
<!-- 状态按钮 -->
|
||
<view
|
||
class="task-status-btn"
|
||
:class="{ 'completed': task.completed }"
|
||
>
|
||
<text class="status-text">{{ task.completed ? '已完成' : '未完成' }}</text>
|
||
<image v-if="task.completed" class="check-icon" src="/static/icon/check.png" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部进度区域 -->
|
||
<view class="progress-section">
|
||
|
||
<!-- 进度条 -->
|
||
<view class="progress-bar">
|
||
<view
|
||
v-for="(milestone, index) in milestones"
|
||
:key="index"
|
||
class="progress-node"
|
||
:class="{ 'active': index < completedCount }"
|
||
>
|
||
<view class="node-circle"></view>
|
||
<text class="node-label">{{ milestone }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</transition>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue';
|
||
|
||
const props = defineProps({
|
||
visible: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
});
|
||
|
||
const emit = defineEmits(['close']);
|
||
|
||
// 任务数据(硬编码)
|
||
const tasks = ref([
|
||
{ id: 1, name: '登录一次', reward: 5, completed: true },
|
||
{ id: 2, name: '拜访好友小屋一次', reward: 10, completed: true },
|
||
{ id: 3, name: '抢占好友展位3次', reward: 10, completed: true },
|
||
{ id: 4, name: '抢占好友展位5次', reward: 20, completed: false },
|
||
]);
|
||
|
||
// 进度里程碑
|
||
const milestones = [20, 40, 60, 80, 100];
|
||
|
||
// 计算已完成任务数
|
||
const completedCount = computed(() => {
|
||
return tasks.value.filter(task => task.completed).length;
|
||
});
|
||
|
||
// 遮罩层触摸处理
|
||
let maskTouchStartTime = 0;
|
||
const handleMaskTouchStart = (e) => {
|
||
maskTouchStartTime = Date.now();
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
};
|
||
|
||
const handleMaskTouchEnd = () => {
|
||
const touchDuration = Date.now() - maskTouchStartTime;
|
||
// 快速点击(小于300ms)才关闭弹窗
|
||
if (touchDuration < 300) {
|
||
emit('close');
|
||
}
|
||
};
|
||
|
||
// 关闭按钮触摸处理
|
||
let closeTouchStartTime = 0;
|
||
let closeTouchLocked = false;
|
||
|
||
const handleCloseTouchStart = (e) => {
|
||
closeTouchStartTime = Date.now();
|
||
closeTouchLocked = false;
|
||
};
|
||
|
||
const handleCloseTouchEnd = (e) => {
|
||
if (closeTouchLocked) return;
|
||
closeTouchLocked = true;
|
||
|
||
const touchDuration = Date.now() - closeTouchStartTime;
|
||
// 快速点击(小于300ms)才关闭弹窗
|
||
if (touchDuration < 300) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
emit('close');
|
||
}
|
||
};
|
||
|
||
const handleCloseClick = (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
emit('close');
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.modal-wrapper {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 9999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
z-index: 1;
|
||
}
|
||
|
||
.modal-container {
|
||
position: relative;
|
||
width: 580rpx;
|
||
max-height: 85vh;
|
||
border-radius: 40rpx;
|
||
overflow: hidden;
|
||
z-index: 2;
|
||
}
|
||
|
||
.modal-background {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 1;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.close-button {
|
||
position: absolute;
|
||
top: 20rpx;
|
||
right: 20rpx;
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 10;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.close-icon {
|
||
font-size: 50rpx;
|
||
color: #e6e6e6;
|
||
line-height: 1;
|
||
font-weight: 300;
|
||
}
|
||
|
||
.modal-content {
|
||
position: relative;
|
||
z-index: 2;
|
||
padding: 60rpx 40rpx 40rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
color: #e6e6e6;
|
||
text-align: center;
|
||
margin-bottom: 40rpx;
|
||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.task-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.task-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx 0;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.task-name {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
color: #e6e6e6;
|
||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.task-reward {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #FFB800;
|
||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||
margin-right: 10rpx;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.task-status-btn {
|
||
min-width: 140rpx;
|
||
height: 50rpx;
|
||
border-radius: 25rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
padding: 0 20rpx;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.task-status-btn.completed {
|
||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 24rpx;
|
||
min-height: 30rpx;
|
||
color: #e6e6e6;
|
||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.check-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.progress-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
position: relative;
|
||
padding: 0 10rpx;
|
||
}
|
||
|
||
.progress-bar::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 10rpx;
|
||
right: 10rpx;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
height: 4rpx;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
z-index: 0;
|
||
}
|
||
|
||
.progress-node {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.node-circle {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
border: 3rpx solid rgba(255, 255, 255, 0.5);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.progress-node.active .node-circle {
|
||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||
border-color: #e6e6e6;
|
||
box-shadow: 0 0 10rpx rgba(240, 131, 153, 0.6);
|
||
}
|
||
|
||
.node-label {
|
||
font-size: 20rpx;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||
}
|
||
|
||
.progress-node.active .node-label {
|
||
color: #e6e6e6;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 动画效果 */
|
||
.fade-enter-active,
|
||
.fade-leave-active {
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.fade-enter-from,
|
||
.fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
.scale-enter-active,
|
||
.scale-leave-active {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.scale-enter-from,
|
||
.scale-leave-to {
|
||
opacity: 0;
|
||
transform: scale(0.8);
|
||
}
|
||
</style>
|