340 lines
8.6 KiB
Vue
340 lines
8.6 KiB
Vue
<template>
|
||
<div class="list-content">
|
||
<div v-if="title" class="list-title">
|
||
<span>{{ title }}</span>
|
||
<img width="50%" src="@/assets/images/line_1.png" />
|
||
</div>
|
||
<div class="list" :style="{ maxHeight: maxHeight }">
|
||
<!-- 表格头部 -->
|
||
<div v-if="tableTitle && tableTitle.length > 0" class="table-header">
|
||
<div class="header-item" v-for="(title, index) in tableTitle" :key="index">
|
||
{{ title.name }}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="list-wrapper" ref="listWrapperRef" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||
<!-- 表格模式 -->
|
||
<template v-if="tableTitle && tableTitle.length > 0">
|
||
<div @click="handleItemClick(item)" class="table-row cursor-pointer" v-for="(item, index) in listData" :key="`table-${index}`" @mouseenter="handleMouseEnter">
|
||
<div class="table-cell" v-for="(title, cellIndex) in tableTitle" :key="`cell-${index}-${cellIndex}`">
|
||
{{ item[title.key] || '-' }}
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 列表模式 -->
|
||
<template v-else>
|
||
<div @click="handleItemClick(item)" class="list-item cursor-pointer" v-for="(item, index) in listData" :key="`list-${index}`" @mouseenter="handleMouseEnter">
|
||
<span class="alert-text" :class="[{ error: item.alarm_level_code == 'severity' }, { warn: item.alarm_level_code == 'major' }]">
|
||
{{ (index + 1) }} {{ item.description }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||
|
||
interface AlertItem {
|
||
description: string
|
||
alarm_level_code: string
|
||
alarm_status: string
|
||
alarm_biz_id: string
|
||
}
|
||
|
||
interface TableTitle {
|
||
name: string
|
||
key: string
|
||
}
|
||
|
||
interface Props {
|
||
title?: string
|
||
listData: AlertItem[]
|
||
maxHeight?: string
|
||
autoScroll?: boolean
|
||
scrollSpeed?: number
|
||
scrollInterval?: number,
|
||
tableTitle?: TableTitle[]
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
title: '', // 标题
|
||
maxHeight: '23vh', // 最大高度
|
||
autoScroll: true, // 是否自动滚动
|
||
scrollSpeed: 1, // 每次滚动的像素数
|
||
scrollInterval: 3000 // 每次滚动的间隔时间,单位毫秒
|
||
})
|
||
|
||
const listWrapperRef = ref<HTMLElement | null>(null)
|
||
let scrollTimer: NodeJS.Timeout | null = null
|
||
let isScrolling = false
|
||
let scrollDirection: 'down' | 'up' = 'down' // 滚动方向:向下或向上
|
||
|
||
const handleItemClick = (item: AlertItem) => {
|
||
window.open(`http://10.0.64.20/configcenter/console/device-manage`, '_blank')
|
||
}
|
||
|
||
// 自动滚动功能
|
||
const startAutoScroll = (resetToTop: boolean = false) => {
|
||
if (!props.autoScroll || !listWrapperRef.value) return
|
||
|
||
const wrapper = listWrapperRef.value
|
||
const scrollHeight = wrapper.scrollHeight
|
||
const clientHeight = wrapper.clientHeight
|
||
|
||
// 只有当内容高度超过容器高度时才启动滚动
|
||
if (scrollHeight <= clientHeight) return
|
||
|
||
// 如果是首次启动或需要重置,从顶部开始向下滚动
|
||
if (resetToTop) {
|
||
wrapper.scrollTop = 0
|
||
scrollDirection = 'down'
|
||
} else {
|
||
// 否则根据当前位置判断滚动方向
|
||
const currentScrollTop = wrapper.scrollTop
|
||
const maxScrollTop = scrollHeight - clientHeight
|
||
|
||
if (currentScrollTop >= maxScrollTop - 1) {
|
||
// 在底部,向上滚动
|
||
scrollDirection = 'up'
|
||
} else if (currentScrollTop <= 1) {
|
||
// 在顶部,向下滚动
|
||
scrollDirection = 'down'
|
||
}
|
||
// 在中间位置,保持当前方向(或默认向下)
|
||
}
|
||
|
||
isScrolling = true
|
||
|
||
const scroll = () => {
|
||
if (!isScrolling || !wrapper) return
|
||
|
||
const currentScrollTop = wrapper.scrollTop
|
||
const maxScrollTop = scrollHeight - clientHeight
|
||
|
||
if (scrollDirection === 'down') {
|
||
// 向下滚动
|
||
const nextScrollTop = currentScrollTop + props.scrollSpeed
|
||
|
||
if (nextScrollTop >= maxScrollTop) {
|
||
// 滚动到底部,切换方向为向上
|
||
wrapper.scrollTop = maxScrollTop
|
||
scrollDirection = 'up'
|
||
// 在底部停留一小段时间后开始向上滚动
|
||
scrollTimer = setTimeout(scroll, 500)
|
||
return
|
||
} else {
|
||
wrapper.scrollTop = nextScrollTop
|
||
}
|
||
} else {
|
||
// 向上滚动
|
||
const nextScrollTop = currentScrollTop - props.scrollSpeed
|
||
|
||
if (nextScrollTop <= 0) {
|
||
// 滚动到顶部,切换方向为向下
|
||
wrapper.scrollTop = 0
|
||
scrollDirection = 'down'
|
||
// 在顶部停留一小段时间后开始向下滚动
|
||
scrollTimer = setTimeout(scroll, 500)
|
||
return
|
||
} else {
|
||
wrapper.scrollTop = nextScrollTop
|
||
}
|
||
}
|
||
|
||
scrollTimer = setTimeout(scroll, 50) // 每50ms滚动一次,实现平滑效果
|
||
}
|
||
|
||
scroll()
|
||
}
|
||
|
||
// 停止自动滚动
|
||
const stopAutoScroll = () => {
|
||
isScrolling = false
|
||
scrollTimer && clearTimeout(scrollTimer)
|
||
scrollTimer = null
|
||
}
|
||
|
||
// 鼠标悬停时暂停滚动
|
||
const handleMouseEnter = () => {
|
||
stopAutoScroll()
|
||
}
|
||
|
||
// 鼠标离开时恢复滚动
|
||
const handleMouseLeave = () => {
|
||
if (props.autoScroll) {
|
||
// 延迟启动滚动,避免鼠标快速进出
|
||
// 从当前位置继续,不重置
|
||
setTimeout(() => {
|
||
startAutoScroll(false)
|
||
}, 500)
|
||
}
|
||
}
|
||
|
||
// 监听数据变化,重新启动滚动
|
||
watch(() => props.listData, () => {
|
||
nextTick(() => {
|
||
if (props.autoScroll) {
|
||
stopAutoScroll()
|
||
setTimeout(() => {
|
||
// 数据更新后从顶部重新开始
|
||
startAutoScroll(true)
|
||
}, 1000) // 数据更新后1秒开始滚动
|
||
}
|
||
})
|
||
}, { deep: true })
|
||
|
||
onMounted(() => {
|
||
if (props.autoScroll) {
|
||
// 组件挂载后延迟启动滚动,从顶部开始
|
||
setTimeout(() => {
|
||
startAutoScroll(true)
|
||
}, 2000)
|
||
}
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
stopAutoScroll()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.cursor-pointer {
|
||
cursor: pointer;
|
||
}
|
||
.list-content {
|
||
display: flex;
|
||
width: 68%;
|
||
height: 100%;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
|
||
.list-title {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.list {
|
||
display: flex;
|
||
width: 100%;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
overflow: hidden;
|
||
|
||
.table-header {
|
||
display: flex;
|
||
width: 100%;
|
||
background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
|
||
border-radius: 0.37vh 0.37vh 0 0;
|
||
margin-bottom: 2px;
|
||
|
||
.header-item {
|
||
flex: 1;
|
||
padding: 0.5vh 0.4vw;
|
||
font-size: 0.75rem;
|
||
color: #ffffff;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||
|
||
&:last-child {
|
||
border-right: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
.list-wrapper {
|
||
display: flex;
|
||
width: 100%;
|
||
height: 100%;
|
||
margin-top: 10px;
|
||
overflow-y: auto;
|
||
flex-direction: column;
|
||
row-gap: 4px;
|
||
scroll-behavior: smooth; // 平滑滚动效果
|
||
overflow-x: hidden;
|
||
|
||
/* 自定义滚动条样式 */
|
||
&::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
&::-webkit-scrollbar-track {
|
||
background: rgb(51 65 85 / 30%);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
&::-webkit-scrollbar-thumb {
|
||
background: rgb(59 130 246 / 60%);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
&::-webkit-scrollbar-thumb:hover {
|
||
background: rgb(59 130 246 / 80%);
|
||
}
|
||
}
|
||
|
||
.table-row {
|
||
display: flex;
|
||
width: 100%;
|
||
background: rgb(51 65 85 / 30%);
|
||
border: 1px solid #1e40af;
|
||
border-radius: 0.37vh;
|
||
margin-bottom: 4px;
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
background: rgb(51 65 85 / 50%);
|
||
transform: translateX(2px);
|
||
}
|
||
|
||
.table-cell {
|
||
flex: 1;
|
||
padding: 0.5vh 0.4vw;
|
||
font-size: 0.75rem;
|
||
color: #ffffff;
|
||
text-align: center;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||
|
||
&:last-child {
|
||
border-right: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
.list-item {
|
||
display: inline-flex;
|
||
padding: 0.5vh 0.4vw;
|
||
font-size: 0.75rem;
|
||
background: rgb(51 65 85 / 30%);
|
||
border: 1px solid #1e40af;
|
||
border-radius: 0.37vh;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease; // 添加过渡效果
|
||
|
||
&:hover {
|
||
background: rgb(51 65 85 / 50%);
|
||
transform: translateX(2px); // 悬停时轻微右移
|
||
}
|
||
|
||
.alert-text.error {
|
||
color: #f00;
|
||
}
|
||
|
||
.alert-text.warn {
|
||
color: #ff0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|