WholeProcessPlatform/frontend/src/modules/ZhenXiZhiWuYuanMod/Dwjzqk/index.vue

221 lines
6.2 KiB
Vue
Raw Normal View History

2026-05-09 17:04:48 +08:00
<!-- SidePanelItem.vue -->
<template>
<SidePanelItem title="动物救助情况">
<div
class="container"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<!-- 跑马灯轨道容器 -->
<div
class="carousel-track"
:class="{ 'no-transition': isTransitioning }"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
>
<!-- 遍历所有媒体项包含克隆项 -->
<div
v-for="(item, index) in renderMediaData"
:key="index"
class="carousel-item"
>
<!-- 图片 -->
<img
v-if="item.type === 'image'"
:src="item.url"
alt=""
/>
<!-- 视频 -->
<video
v-else
:src="item.url"
autoplay
muted
loop
playsinline
></video>
<!-- 说明文字 -->
<div class="text">{{ item.text }}</div>
</div>
</div>
</div>
</SidePanelItem>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
// 定义组件名(便于调试和递归)
defineOptions({
name: 'ZhiWuYuanJianSheJiJieRuQingKuangBar'
});
// 媒体类型定义
interface MediaItem {
type: 'image' | 'video';
url: string;
text: string;
}
// 原始媒体数据3条测试数据
const originalMediaData = ref<MediaItem[]>([
{
type: 'image',
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
text: '糯扎渡动物救护站 2025-04-11 15'
},
{
type: 'video',
url: 'https://example.com/video1.mp4', // 视频URL示例您可替换为真实地址
text: '野生动物救助中心 2025-04-12 10'
},
{
type: 'image',
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
text: '珍稀动物保护基地 2025-04-13 14'
}
]);
// 克隆首尾项后的渲染数组(用于无缝循环)
// 结构:[最后一项克隆, 原始数据..., 第一项克隆]
const renderMediaData = ref<MediaItem[]>([]);
// 当前显示索引指向renderMediaData
const currentIndex = ref(1); // 从1开始跳过克隆的首项
// 定时器引用
let timer: any = null;
// 鼠标悬停状态
const isHovering = ref(false);
// 是否正在切换动画中用于禁用transition
const isTransitioning = ref(false);
// 初始化渲染数组(克隆首尾项)
const initRenderData = () => {
const length = originalMediaData.value.length;
if (length === 0) return;
renderMediaData.value = [
originalMediaData.value[length - 1], // 克隆最后一项
...originalMediaData.value, // 原始数据
originalMediaData.value[0] // 克隆第一项
];
};
// 启动自动轮播
const startAutoPlay = () => {
if (timer) clearInterval(timer);
timer = setInterval(() => {
if (!isHovering.value && !isTransitioning.value) {
nextSlide();
}
}, 2000);
};
// 切换到下一张
const nextSlide = () => {
currentIndex.value++;
// 延迟检查是否需要无缝跳转
setTimeout(() => {
checkSeamlessJump();
}, 500); // 与transition时间一致
};
// 检查是否需要无缝跳转(克隆项处理)
const checkSeamlessJump = () => {
const realLength = originalMediaData.value.length;
// 如果到达克隆的最后一项(索引 = realLength + 1
if (currentIndex.value >= realLength + 1) {
// 1. 禁用过渡动画,实现瞬间跳转
isTransitioning.value = true;
// 2. 瞬间跳转到真实的第二项索引1用户看不到跳变
currentIndex.value = 1;
// 3. 等待两帧后恢复过渡动画确保DOM已更新
requestAnimationFrame(() => {
requestAnimationFrame(() => {
isTransitioning.value = false;
});
});
}
};
// 处理鼠标进入
const handleMouseEnter = () => {
isHovering.value = true;
};
// 处理鼠标离开
const handleMouseLeave = () => {
isHovering.value = false;
};
// 页面加载时执行
onMounted(() => {
initRenderData();
startAutoPlay();
});
// 组件卸载时清理
onUnmounted(() => {
if (timer) clearInterval(timer);
});
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 290px;
border: 1px solid #7fd6ff;
border-radius: 5px;
position: relative;
overflow: hidden; // 隐藏超出容器的内容
// 跑马灯轨道
.carousel-track {
display: flex; // 横向排列所有媒体项
width: 100%;
height: 100%;
transition: transform 0.5s ease-in-out; // 0.5秒平滑过渡
// 禁用过渡动画(用于无缝跳转)
&.no-transition {
transition: none;
}
// 单个媒体项
.carousel-item {
min-width: 100%; // 每个项目占满容器宽度
height: 100%;
position: relative;
flex-shrink: 0; // 防止被压缩
img, video {
width: 100%;
height: 100%;
object-fit: cover; // 保持比例填充
}
.text {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 40px;
align-items: center;
line-height: 40px;
background: rgba(0, 0, 0, 0.4);
color: #fff;
padding-left: 10px;
}
}
}
}
</style>