Files
lc_frontend/src/views/screen/components/AlertList.vue
2025-12-13 18:14:32 +08:00

340 lines
8.6 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.

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