1
This commit is contained in:
151
src/components/AutoScroll/index.vue
Normal file
151
src/components/AutoScroll/index.vue
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="auto-scroll-container" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" ref="container">
|
||||||
|
<div class="auto-scroll-content" ref="content" :style="{ transform: scrollEnabled ? `translate3d(0, ${Math.round(scrollY)}px, 0)` : 'none' }">
|
||||||
|
<div class="auto-scroll-item" :class="safety ? 'safety' : ''" v-for="(item, index) in displayList" :key="index">
|
||||||
|
<slot :item="item" :index="index">{{ item }}</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
interface Props {
|
||||||
|
items: any[];
|
||||||
|
speed?: number;
|
||||||
|
height?: number;
|
||||||
|
minItems?: number;
|
||||||
|
autoScroll?: boolean;
|
||||||
|
safety?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
speed: 0.4,
|
||||||
|
minItems: 2,
|
||||||
|
autoScroll: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const container = ref<HTMLElement | null>(null);
|
||||||
|
const content = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
let scrollY = 0;
|
||||||
|
let animationFrameId: number | null = null;
|
||||||
|
let manualPaused = false;
|
||||||
|
let manualControl = false;
|
||||||
|
|
||||||
|
// 是否满足滚动条件
|
||||||
|
const scrollEnabled = computed(() => props.items.length >= props.minItems);
|
||||||
|
|
||||||
|
// 展示数据列表(数据足够时复制一份)
|
||||||
|
const displayList = computed(() => (scrollEnabled.value ? [...props.items, ...props.items] : props.items));
|
||||||
|
|
||||||
|
// 修正 scrollY 范围
|
||||||
|
function normalizeScrollY(contentHeight: number) {
|
||||||
|
scrollY = (((scrollY % contentHeight) + contentHeight) % contentHeight) - contentHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动逻辑
|
||||||
|
function step() {
|
||||||
|
if (!content.value) return;
|
||||||
|
const contentHeight = content.value.offsetHeight / 2;
|
||||||
|
scrollY -= props.speed;
|
||||||
|
normalizeScrollY(contentHeight);
|
||||||
|
content.value.style.transform = `translate3d(0, ${Math.round(scrollY)}px, 0)`;
|
||||||
|
animationFrameId = requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startScroll() {
|
||||||
|
if (!animationFrameId) {
|
||||||
|
animationFrameId = requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pauseScroll() {
|
||||||
|
if (animationFrameId) {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
animationFrameId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseEnter() {
|
||||||
|
if (!manualControl) pauseScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseLeave() {
|
||||||
|
if (!manualControl && props.autoScroll && scrollEnabled.value) startScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWheel(e: WheelEvent) {
|
||||||
|
if (!content.value || !container.value) return;
|
||||||
|
|
||||||
|
const contentHeight = content.value.offsetHeight / (scrollEnabled.value ? 2 : 1);
|
||||||
|
const containerHeight = container.value.offsetHeight;
|
||||||
|
|
||||||
|
if (contentHeight <= containerHeight) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manualPaused = true;
|
||||||
|
pauseScroll();
|
||||||
|
scrollY -= e.deltaY;
|
||||||
|
normalizeScrollY(contentHeight);
|
||||||
|
content.value.style.transform = `translate3d(0, ${Math.round(scrollY)}px, 0)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
if (scrollEnabled.value && props.autoScroll) {
|
||||||
|
startScroll();
|
||||||
|
}
|
||||||
|
container.value?.addEventListener('wheel', onWheel, { passive: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
pauseScroll();
|
||||||
|
container.value?.removeEventListener('wheel', onWheel);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听数据是否滚动条件满足
|
||||||
|
watch(scrollEnabled, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
pauseScroll();
|
||||||
|
scrollY = 0;
|
||||||
|
if (content.value) content.value.style.transform = 'none';
|
||||||
|
} else if (props.autoScroll && !manualPaused && !manualControl) {
|
||||||
|
startScroll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露控制方法
|
||||||
|
defineExpose({
|
||||||
|
pause: (): void => {
|
||||||
|
manualControl = true;
|
||||||
|
pauseScroll();
|
||||||
|
},
|
||||||
|
resume: (): void => {
|
||||||
|
manualControl = false;
|
||||||
|
if (scrollEnabled.value) startScroll();
|
||||||
|
},
|
||||||
|
reset: (): void => {
|
||||||
|
scrollY = 0;
|
||||||
|
if (content.value) {
|
||||||
|
content.value.style.transform = 'translate3d(0, 0, 0)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.auto-scroll-container {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.auto-scroll-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.safety:nth-child(odd) {
|
||||||
|
background-color: rgba(67, 226, 203, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,34 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="right_box">
|
<div class="right_box">
|
||||||
<div class="data_box">
|
<div class="top_box">
|
||||||
<Title title="年发电量" />
|
<Title title="年发电量" />
|
||||||
<div class="echarts">
|
<div class="echarts">
|
||||||
<EchartBoxTwo :option="option_fdssgl" ref="barChart" />
|
<EchartBoxTwo :option="option_fdssgl" ref="barChart" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<div class="data_box">
|
<div class="end_box">
|
||||||
<Title title="报警统计" />
|
<Title title="报警统计" />
|
||||||
|
<div class="bj-list">
|
||||||
|
<div class="bj-grid">
|
||||||
|
<div>报警ID</div>
|
||||||
|
<div>报警类型</div>
|
||||||
|
<div>报警详情</div>
|
||||||
|
<div>报警时间</div>
|
||||||
|
<div>操作</div>
|
||||||
|
</div>
|
||||||
|
<AutoScroller :items="bjList" class="bj-content" :minItems="10">
|
||||||
|
<template #default="{ item }">
|
||||||
|
<div class="bj-grid item" :class="{ 'active': activebj === item.id }">
|
||||||
|
<div class="cell-text">报警ID</div>
|
||||||
|
<div class="cell-text">
|
||||||
|
报警类型
|
||||||
|
</div>
|
||||||
|
<div class="cell-text">
|
||||||
|
报警详情
|
||||||
|
</div>
|
||||||
|
<div class="cell-text">{{ dayjs(item.createTime).format('YYYY-MM-DD') }}</div>
|
||||||
|
<div class="cell-text cursor-pointer">查看</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</AutoScroller>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import EchartBoxTwo from '@/components/EchartBox/index.vue';
|
import EchartBoxTwo from '@/components/EchartBox/index.vue';
|
||||||
import { option1, option2, option3 } from './options';
|
import AutoScroller from '@/components/AutoScroll/index.vue';
|
||||||
|
import { option1 } from './options';
|
||||||
import Title from './title.vue';
|
import Title from './title.vue';
|
||||||
|
import { dayjs } from 'element-plus';
|
||||||
|
|
||||||
const option_fdssgl = ref(option1);
|
const option_fdssgl = ref(option1);
|
||||||
const option_fdzlqs = ref(option2);
|
const bjList = ref([
|
||||||
const handleWheel = (event: WheelEvent) => {
|
{createTime: '2025-11-11 11:11:11'},
|
||||||
const errorList = (event.target as HTMLElement).closest('.error_list');
|
{createTime: '2025-11-11 11:11:11'},
|
||||||
|
{createTime: '2025-11-11 11:11:11'},
|
||||||
if (errorList) {
|
{createTime: '2025-11-11 11:11:11'},
|
||||||
event.preventDefault(); // 阻止页面或父元素的垂直滚动
|
{createTime: '2025-11-11 11:11:11'},
|
||||||
errorList.scrollLeft += event.deltaY; // 横向滚动
|
])
|
||||||
}
|
const activebj = ref('')
|
||||||
};
|
|
||||||
|
|
||||||
const reszieFont = () => {
|
const reszieFont = () => {
|
||||||
const fontSize = Math.sqrt(window.innerWidth) / 3;
|
const fontSize = Math.sqrt(window.innerWidth) / 3;
|
||||||
@ -36,27 +60,14 @@ const reszieFont = () => {
|
|||||||
option_fdssgl.value.xAxis.axisLabel.fontSize = fontSize;
|
option_fdssgl.value.xAxis.axisLabel.fontSize = fontSize;
|
||||||
option_fdssgl.value.yAxis.axisLabel.fontSize = fontSize;
|
option_fdssgl.value.yAxis.axisLabel.fontSize = fontSize;
|
||||||
option_fdssgl.value.legend.textStyle.fontSize = fontSize;
|
option_fdssgl.value.legend.textStyle.fontSize = fontSize;
|
||||||
|
|
||||||
option_fdzlqs.value.xAxis.axisLabel.fontSize = fontSize;
|
|
||||||
option_fdzlqs.value.yAxis.axisLabel.fontSize = fontSize;
|
|
||||||
option_fdzlqs.value.legend.textStyle.fontSize = fontSize;
|
|
||||||
option_fdzlqs.value.yAxis.nameTextStyle.fontSize = fontSize;
|
|
||||||
|
|
||||||
option_dzfhqx.value.xAxis.axisLabel.fontSize = fontSize;
|
|
||||||
option_dzfhqx.value.yAxis.axisLabel.fontSize = fontSize;
|
|
||||||
option_dzfhqx.value.legend.textStyle.fontSize = fontSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', reszieFont);
|
window.addEventListener('resize', reszieFont);
|
||||||
|
|
||||||
window.addEventListener('wheel', handleWheel, { passive: false });
|
|
||||||
// passive: false 是关键,允许我们在事件处理函数中调用 event.preventDefault()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', reszieFont);
|
window.removeEventListener('resize', reszieFont);
|
||||||
window.removeEventListener('wheel', handleWheel);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -85,15 +96,7 @@ $vh_base: 1080;
|
|||||||
margin-right: vw(20);
|
margin-right: vw(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
.statistic {
|
.top_box {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: vh(10);
|
|
||||||
background: $background-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data_box {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -107,60 +110,58 @@ $vh_base: 1080;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.error_tip_box {
|
.end_box {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: vw(5);
|
|
||||||
margin-right: vw(8);
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: vw(20);
|
|
||||||
height: vh(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error_tip {
|
|
||||||
height: vh(26);
|
|
||||||
width: vw(81);
|
|
||||||
border-radius: vw(13);
|
|
||||||
background: rgba(0, 255, 238, 0.12);
|
|
||||||
color: rgba(0, 255, 238, 1);
|
|
||||||
font-size: vw(10);
|
|
||||||
text-align: center;
|
|
||||||
line-height: vh(26);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error_list {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
background-color: $background-color;
|
||||||
gap: vw(10);
|
}
|
||||||
margin: vh(10) vw(15);
|
|
||||||
overflow: hidden;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
cursor: grabbing;
|
|
||||||
|
|
||||||
.error_item {
|
.bj-list {
|
||||||
flex: 0 0 auto;
|
margin: vh(10) vw(10);
|
||||||
width: vw(300);
|
margin-top: vh(16);
|
||||||
padding: vh(10) vw(10);
|
font-family: 'Roboto Condensed';
|
||||||
color: #fff;
|
font-size: vw(12);
|
||||||
background: rgba(255, 255, 255, 0.09);
|
|
||||||
border: 1px solid rgba(0, 255, 238, 0.5);
|
|
||||||
border-radius: vw(8);
|
|
||||||
|
|
||||||
.item_info {
|
&>div:first-child {
|
||||||
display: flex;
|
padding: vh(6) 0 vh(6) vw(8);
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: vw(14);
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: vh(10);
|
margin-bottom: vh(10);
|
||||||
|
background: rgba(67, 226, 203, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item_headline {
|
.bj-grid {
|
||||||
font-size: vw(16);
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1.6fr 0.7fr;
|
||||||
|
gap: vw(8);
|
||||||
|
place-items: center;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bj-content {
|
||||||
|
height: vh(180);
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
padding: vh(6) 0;
|
||||||
|
padding-left: vw(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-text {
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: rgb(66, 227, 203);
|
||||||
|
background: rgba(67, 226, 203, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsis {
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user