332 lines
6.4 KiB
Vue
332 lines
6.4 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="handleMaskClick">
|
|
</view>
|
|
</transition>
|
|
|
|
<transition name="slide-up">
|
|
<view v-if="visible" class="modal-container" @click.stop>
|
|
<!-- 背景图片 -->
|
|
<image class="modal-background" src="/static/rank/activity-support-icon/beijingkuang1.png"
|
|
mode="aspectFill"></image>
|
|
|
|
<!-- 蒙层 -->
|
|
<view class="modal-overlay"></view>
|
|
|
|
<!-- 关闭按钮 -->
|
|
<view class="close-button" @touchstart.stop="handleCloseTouchStart" @touchend.stop="handleCloseTouchEnd"
|
|
@click="handleCloseClick">
|
|
<image class="back-icon" src="/static/starbookcontent/tuichu.png" mode="aspectFit"></image>
|
|
</view>
|
|
|
|
<!-- 内容区域 -->
|
|
<view class="modal-content" @touchstart.stop @touchend.stop @click.stop>
|
|
<!-- 标题区域 -->
|
|
<view class="title-section">
|
|
<text class="modal-title">TA们最近为你点过赞</text>
|
|
<image class="title-image" src="/static/rank/activity-support-icon/tubiao.png"
|
|
mode="aspectFill"></image>
|
|
</view>
|
|
|
|
<!-- 胶囊按钮区域 -->
|
|
<view class="tab-section">
|
|
<view v-for="(tab, index) in tabs" :key="index" class="tab-item"
|
|
:class="{ 'active': activeTabIndex === index }" @tap="handleTabClick(index)">
|
|
<text class="tab-text">{{ tab }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 可滚动列表区域 -->
|
|
<scroll-view class="scrollable-content" scroll-y="true" :show-scrollbar="false"
|
|
:lower-threshold="100" @scrolltolower="handleScrollToLower">
|
|
<!-- 列表内容插槽 -->
|
|
<slot name="content"></slot>
|
|
</scroll-view>
|
|
</view>
|
|
</view>
|
|
</transition>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue';
|
|
|
|
const props = defineProps({
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
tabs: {
|
|
type: Array,
|
|
default: () => ['今日', '历史']
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['close', 'tab-change', 'scroll-to-lower']);
|
|
|
|
// 当前选中的标签索引
|
|
const activeTabIndex = ref(0);
|
|
|
|
// 遮罩层触摸处理
|
|
let maskTouchStartTime = 0;
|
|
const handleMaskTouchStart = (e) => {
|
|
maskTouchStartTime = Date.now();
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
};
|
|
|
|
const handleMaskClick = () => {
|
|
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;
|
|
if (touchDuration < 300) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
emit('close');
|
|
}
|
|
};
|
|
|
|
const handleCloseClick = (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
emit('close');
|
|
};
|
|
|
|
// 处理标签点击
|
|
const handleTabClick = (index) => {
|
|
if (activeTabIndex.value === index) return;
|
|
activeTabIndex.value = index;
|
|
emit('tab-change', {
|
|
tabName: props.tabs[index],
|
|
tabIndex: index
|
|
});
|
|
};
|
|
|
|
// 处理滚动到底部
|
|
const handleScrollToLower = () => {
|
|
emit('scroll-to-lower');
|
|
};
|
|
|
|
// 暴露方法给父组件
|
|
defineExpose({
|
|
switchToTab: (indexOrName) => {
|
|
if (typeof indexOrName === 'number') {
|
|
activeTabIndex.value = indexOrName;
|
|
} else {
|
|
const index = props.tabs.indexOf(indexOrName);
|
|
if (index !== -1) {
|
|
activeTabIndex.value = index;
|
|
}
|
|
}
|
|
},
|
|
getActiveTab: () => props.tabs[activeTabIndex.value],
|
|
getActiveTabIndex: () => activeTabIndex.value
|
|
});
|
|
</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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.back-icon {
|
|
width: 56rpx;
|
|
height: 56rpx;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* 标题区域 */
|
|
.title-section {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 16rpx;
|
|
margin-bottom: 32rpx;
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #e6e6e6;
|
|
font-family: 'yt', sans-serif;
|
|
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.title-image {
|
|
width: 112rpx;
|
|
height: 80rpx;
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
/* 胶囊按钮区域 */
|
|
.tab-section {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
gap: 32rpx;
|
|
margin-bottom: 24rpx;
|
|
padding: 12rpx 24rpx;
|
|
position: relative;
|
|
}
|
|
|
|
.tab-item {
|
|
padding: 16rpx 40rpx;
|
|
border-radius: 999rpx;
|
|
background: transparent;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
}
|
|
|
|
.tab-section::before {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -4rpx;
|
|
left: 96rpx;
|
|
right: 96rpx;
|
|
height: 6rpx;
|
|
background: #ccc;
|
|
border-radius: 4rpx;
|
|
}
|
|
|
|
.tab-item.active {
|
|
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
|
box-shadow:
|
|
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
|
}
|
|
|
|
.tab-text {
|
|
font-size: 28rpx;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
font-family: 'yt', sans-serif;
|
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.tab-item.active .tab-text {
|
|
color: #FFFFFF;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* 可滚动内容区域 */
|
|
.scrollable-content {
|
|
flex: 1;
|
|
max-height: 500rpx;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* 动画效果 */
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.slide-up-enter-active,
|
|
.slide-up-leave-active {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.slide-up-enter-from,
|
|
.slide-up-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(100rpx);
|
|
}
|
|
</style> |