|
|
@@ -0,0 +1,250 @@
|
|
|
+<template>
|
|
|
+ <div v-if="tanks.length > 0" class="equipment_nav">
|
|
|
+ <div class="nav_title">罐体导航</div>
|
|
|
+
|
|
|
+ <!-- 搜索框 -->
|
|
|
+ <div class="nav_search">
|
|
|
+ <input type="text" v-model="searchKeyword" placeholder="搜索罐体..." @input="handleSearch">
|
|
|
+ <i class="search_icon">🔍</i>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <ul class="nav_list">
|
|
|
+ <li v-for="(tank, index) in filteredTanks" :key="tank.uniqueId" :class="{ active: currentIndex === index }"
|
|
|
+ @click="handleTankClick(tank, index)">
|
|
|
+ <span class="nav_item_text">{{ tank.shortName }}</span>
|
|
|
+ </li>
|
|
|
+
|
|
|
+ <!-- 搜索无结果提示 -->
|
|
|
+ <li v-if="filteredTanks.length === 0 && searchKeyword" class="no_results">
|
|
|
+ 没有找到匹配的罐体
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, watch } from 'vue';
|
|
|
+
|
|
|
+// 接收父组件传入的参数
|
|
|
+const props = defineProps({
|
|
|
+ // 所有罐体数据
|
|
|
+ tanks: {
|
|
|
+ type: Array,
|
|
|
+ required: true,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ // 当前激活的索引
|
|
|
+ currentIndex: {
|
|
|
+ type: Number,
|
|
|
+ default: -1
|
|
|
+ },
|
|
|
+ // 固定头部的高度,用于计算滚动位置
|
|
|
+ headerHeight: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 子组件向父组件传递事件
|
|
|
+const emit = defineEmits(['tankClick', 'scrollPositionChange']);
|
|
|
+
|
|
|
+// 搜索关键词
|
|
|
+const searchKeyword = ref('');
|
|
|
+// 过滤后的罐体列表
|
|
|
+const filteredTanks = ref([...props.tanks]);
|
|
|
+
|
|
|
+// 监听tanks变化,更新过滤列表
|
|
|
+watch(() => props.tanks, (newVal) => {
|
|
|
+ filteredTanks.value = [...newVal];
|
|
|
+ // 清空搜索关键词
|
|
|
+ searchKeyword.value = '';
|
|
|
+});
|
|
|
+
|
|
|
+// 处理搜索
|
|
|
+const handleSearch = () => {
|
|
|
+ if (!searchKeyword.value.trim()) {
|
|
|
+ filteredTanks.value = [...props.tanks];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const keyword = searchKeyword.value.trim().toLowerCase();
|
|
|
+ filteredTanks.value = props.tanks.filter(tank =>
|
|
|
+ tank.tankName.toLowerCase().includes(keyword)
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 处理罐体点击
|
|
|
+const handleTankClick = (tank, index) => {
|
|
|
+ emit('tankClick', tank, index);
|
|
|
+};
|
|
|
+
|
|
|
+// 监听滚动,判断当前可见的罐体
|
|
|
+const handleScroll = () => {
|
|
|
+ if (props.tanks.length === 0) return;
|
|
|
+
|
|
|
+ const scrollPosition = window.scrollY + props.headerHeight + 20;
|
|
|
+
|
|
|
+ // 从后往前检查,找到当前可见的罐体
|
|
|
+ for (let i = props.tanks.length - 1; i >= 0; i--) {
|
|
|
+ const tank = props.tanks[i];
|
|
|
+ const elementId = `tank-${tank.tankName}`;
|
|
|
+
|
|
|
+ const element = document.getElementById(elementId);
|
|
|
+ if (element && element.offsetTop <= scrollPosition) {
|
|
|
+ // 找到在过滤列表中的索引
|
|
|
+ const filteredIndex = filteredTanks.value.findIndex(
|
|
|
+ item => item.uniqueId === tank.uniqueId
|
|
|
+ );
|
|
|
+ emit('scrollPositionChange', filteredIndex);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 监听滚动事件
|
|
|
+window.addEventListener('scroll', handleScroll);
|
|
|
+
|
|
|
+// 组件卸载时移除事件监听
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ window.removeEventListener('scroll', handleScroll);
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.equipment_nav {
|
|
|
+ width: 240px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ position: sticky;
|
|
|
+ top: 160px;
|
|
|
+ /* 调整顶部距离,考虑固定header的高度 */
|
|
|
+ max-height: calc(100vh - 240px);
|
|
|
+ overflow-y: auto;
|
|
|
+ background-color: rgba(26, 26, 26, 0.8);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid rgba(114, 224, 255, 0.3);
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.nav_title {
|
|
|
+ padding: 10px 15px;
|
|
|
+ background: #192846;
|
|
|
+ color: #76E1FF;
|
|
|
+ font-weight: normal;
|
|
|
+ font-size: 14px;
|
|
|
+ border-radius: 7px 7px 0 0;
|
|
|
+ border-bottom: 1px solid rgba(114, 224, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+/* 搜索框样式 */
|
|
|
+.nav_search {
|
|
|
+ position: relative;
|
|
|
+ padding: 10px;
|
|
|
+ border-bottom: 1px solid rgba(114, 224, 255, 0.1);
|
|
|
+
|
|
|
+ input {
|
|
|
+ width: 100%;
|
|
|
+ padding: 6px 10px 6px 30px;
|
|
|
+ background-color: rgba(255, 255, 255, 0.1);
|
|
|
+ border: 1px solid rgba(114, 224, 255, 0.2);
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 13px;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ &::placeholder {
|
|
|
+ color: rgba(255, 255, 255, 0.5);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .search_icon {
|
|
|
+ position: absolute;
|
|
|
+ left: 18px;
|
|
|
+ top: 16px;
|
|
|
+ color: rgba(255, 255, 255, 0.5);
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.nav_list {
|
|
|
+ margin: 0;
|
|
|
+ padding: 5px 0;
|
|
|
+ list-style: none;
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.nav_list li {
|
|
|
+ padding: 10px 15px;
|
|
|
+ color: #b8cee5;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ border-bottom: 1px solid rgba(114, 224, 255, 0.1);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.nav_list li.no_results {
|
|
|
+ color: rgba(255, 255, 255, 0.5);
|
|
|
+ cursor: default;
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.nav_list li:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.nav_list li:hover:not(.no_results) {
|
|
|
+ background-color: rgba(114, 224, 255, 0.08);
|
|
|
+ color: #76E1FF;
|
|
|
+}
|
|
|
+
|
|
|
+.nav_list li.active {
|
|
|
+ background-color: rgba(114, 224, 255, 0.15);
|
|
|
+ color: #76E1FF;
|
|
|
+ font-weight: normal;
|
|
|
+}
|
|
|
+
|
|
|
+.nav_item_text {
|
|
|
+ display: block;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+/* 滚动条样式优化 */
|
|
|
+.equipment_nav::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.equipment_nav::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.equipment_nav::-webkit-scrollbar-thumb {
|
|
|
+ background: rgba(114, 224, 255, 0.3);
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.equipment_nav::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: rgba(114, 224, 255, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 992px) {
|
|
|
+ .equipment_nav {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 300px;
|
|
|
+ overflow-y: auto;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ top: 160px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .equipment_nav {
|
|
|
+ top: 140px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|