WholeProcessPlatform/frontend/src/modules/dianxingcuoshijieshao/index.vue

419 lines
9.8 KiB
Vue
Raw Normal View History

<!-- SidePanelItem.vue -->
<template>
<SidePanelItem
title="典型设施介绍"
:select="selectConfig"
@update-values="handlePanelChange"
>
<a-spin :spinning="loading">
<template v-if="originalMediaData.length > 0">
<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"
@click="handleItemClick"
>
<img :src="item.image" :alt="item.title" />
<!-- 说明文字随媒体项移动 -->
<!-- <div class="text">{{ item.title }}</div> -->
</div>
</div>
<!-- 面板指示器固定在底部右侧 -->
<div class="pagination-dots-fixed">
<span
v-for="(dot, index) in originalMediaData"
:key="index"
class="dot"
:class="{ active: getCurrentRealIndex() === index }"
@click="goToSlide(index)"
></span>
</div>
2026-04-10 11:11:30 +08:00
</div>
<!-- 独立的文字说明区域随跑马灯切换而变化 -->
<div class="description-text">
{{ currentDescription }}
2026-04-10 11:11:30 +08:00
</div>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
</SidePanelItem>
<!-- 设施详情弹框 -->
<a-modal v-model:open="modalVisible" :title="'设施详情'" width="1536px" :footer="null">
<div v-if="currentItem" class="detail-container">
<ArtsDetail :dataSource="currentItem" :index="getCurrentRealIndex()" />
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import ArtsDetail from '@/components/carouselIntroduce/ArtsDetail.vue';
import { getMsstbprptKendoList } from '@/api/home';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
// 定义组件名(便于调试和递归)
defineOptions({
name: 'dianxingcuoshijieshao'
});
// 基地选择 store
const JidiSelectEventStore = useJidiSelectEventStore();
const baseid = ref('');
// 媒体类型定义
interface MediaItem {
title: string;
description: string;
image: string;
}
// 弹框控制
const modalVisible = ref(false);
const currentItem = ref<any>(null);
// 加载状态
const loading = ref(false);
// 下拉选择配置
const selectConfig = ref({
show: true,
value: 'all',
width: '142px',
options: [
{ label: '全部', value: 'all' },
{ label: '鱼类增殖站', value: 'FB' },
{ label: '过鱼设施', value: 'FP' },
{ label: '低温水减缓设施', value: 'DW' }
]
});
// 当前选择的设施类型
const selectValue = ref('all');
// 原始媒体数据
const originalMediaData = ref<MediaItem[]>([]);
// 克隆首尾项后的渲染数组(用于无缝循环)
const renderMediaData = ref<MediaItem[]>([]);
// 当前显示索引指向renderMediaData
const currentIndex = ref(1);
// 定时器引用
let timer: any = null;
// 鼠标悬停状态
const isHovering = ref(false);
// 是否正在切换动画中
const isTransitioning = ref(false);
// 设施类型对应的 sttpCode 列表
const dw = ['DW', 'DW_1', 'DW_2', 'DW_3', 'DW_4', 'DW_5', 'DW_6', 'DW_9'];
const fp = ['FP', 'FP_1', 'FP_2', 'FP_3', 'FP_4', 'FP_5'];
// 初始化渲染数组(克隆首尾项)
const initRenderData = () => {
const length = originalMediaData.value.length;
if (length === 0) {
renderMediaData.value = [];
return;
}
renderMediaData.value = [
originalMediaData.value[length - 1],
...originalMediaData.value,
originalMediaData.value[0]
];
currentIndex.value = 1;
};
// 启动自动轮播
const startAutoPlay = () => {
if (timer) clearInterval(timer);
timer = setInterval(() => {
if (!isHovering.value && !isTransitioning.value && originalMediaData.value.length > 0) {
nextSlide();
}
}, 4000);
};
// 切换到下一张
const nextSlide = () => {
currentIndex.value++;
setTimeout(() => {
checkSeamlessJump();
}, 500);
};
// 检查是否需要无缝跳转
const checkSeamlessJump = () => {
const realLength = originalMediaData.value.length;
if (currentIndex.value >= realLength + 1) {
isTransitioning.value = true;
currentIndex.value = 1;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
isTransitioning.value = false;
});
});
}
};
// 处理鼠标进入
const handleMouseEnter = () => {
isHovering.value = true;
};
// 处理鼠标离开
const handleMouseLeave = () => {
isHovering.value = false;
};
// 计算当前显示的描述文字
const currentDescription = computed(() => {
const realIndex = getCurrentRealIndex();
return originalMediaData.value[realIndex]?.description || '';
});
// 获取当前真实的索引
const getCurrentRealIndex = () => {
const realLength = originalMediaData.value.length;
let realIndex = currentIndex.value - 1;
if (realIndex < 0) realIndex = realLength - 1;
if (realIndex >= realLength) realIndex = 0;
return realIndex;
};
// 跳转到指定幻灯片
const goToSlide = (targetIndex: number) => {
if (isTransitioning.value) return;
currentIndex.value = targetIndex + 1;
};
// 处理项目点击事件
const handleItemClick = () => {
currentItem.value = originalMediaData.value.map((item: any) => ({
url: item.image || '',
description: item.description || '',
title: item.title || ''
}));
modalVisible.value = true;
};
// 处理下拉选择变化
const handlePanelChange = (payload: any) => {
if (payload.select !== undefined && payload.select !== selectValue.value) {
selectValue.value = payload.select;
getData();
}
};
// 获取数据
const getData = async () => {
loading.value = true;
try {
// 根据选择确定 sttpCode 列表
let sttpCodes: string[] = ['FB', ...dw, ...fp];
if (selectValue.value === 'FB') {
sttpCodes = ['FB'];
} else if (selectValue.value === 'DW') {
sttpCodes = [...dw];
} else if (selectValue.value === 'FP') {
sttpCodes = [...fp];
}
const filters: any[] = [
{
field: 'sttpCode',
operator: 'in',
dataType: 'string',
value: sttpCodes
},
{
logic: 'and',
filters: [
{ field: 'logo', dataType: 'string', operator: 'isnotnull' },
{ field: 'introduce', dataType: 'string', operator: 'isnotnull' }
]
}
];
// 添加基地过滤
if (baseid.value && baseid.value !== 'all') {
filters.push({
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: baseid.value
});
}
const params = {
filter: {
logic: 'and',
filters: filters
},
select: ['introduce', 'logo', 'stnm', 'precis']
};
const res = await getMsstbprptKendoList(params);
if (res?.data?.data && Array.isArray(res.data.data)) {
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL || '';
originalMediaData.value = res.data.data
.filter((item: any) => item.logo || item.introduce)
.map((item: any) => ({
image: item.logo ? baseUrl + '?' + item.logo + '&view=jpg' : '',
title: item.stnm || '',
description: item.introduce || ''
}));
} else {
originalMediaData.value = [];
}
initRenderData();
} catch (error) {
console.error('获取设施介绍数据失败:', error);
originalMediaData.value = [];
initRenderData();
} finally {
loading.value = false;
}
};
// 监听基地变化
watch(
() => JidiSelectEventStore.selectedItem,
(newVal) => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
getData();
}
},
{ deep: true, immediate: true }
);
// 页面加载时执行
onMounted(() => {
startAutoPlay();
});
// 组件卸载时清理
onUnmounted(() => {
if (timer) clearInterval(timer);
});
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 228px;
position: relative;
overflow: hidden;
.carousel-track {
display: flex;
width: 100%;
height: 100%;
transition: transform 0.5s ease-in-out;
&.no-transition {
transition: none;
}
.carousel-item {
min-width: 100%;
height: 100%;
position: relative;
flex-shrink: 0;
cursor: pointer;
img {
2026-04-10 11:11:30 +08:00
width: 100%;
height: 100%;
object-fit: cover;
}
.text {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 28px;
line-height: 28px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
padding-left: 12px;
font-size: 14px;
}
2026-04-10 11:11:30 +08:00
}
}
.pagination-dots-fixed {
position: absolute;
bottom: 10px;
right: 10px;
display: flex;
gap: 6px;
z-index: 10;
.dot {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #d8d8d8;
cursor: pointer;
transition: background-color 0.3s ease;
&.active {
background-color: #005293;
}
&:hover {
opacity: 0.8;
}
}
}
}
.description-text {
font-size: 14px;
line-height: 1.5;
transition: all 0.3s ease;
margin-top: 12px;
min-height: 44px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
}
.detail-container {
width: 100%;
height: 100%;
}
</style>