WholeProcessPlatform/frontend/src/modules/ZhenXiZhiWuYuanMod/Dwjzqk/index.vue
2026-05-09 17:04:48 +08:00

221 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 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>