echarts滚动效果

This commit is contained in:
Teo
2025-05-12 18:31:23 +08:00
parent 8890fcfd95
commit 055a87d2c2
12 changed files with 591 additions and 309 deletions

View File

@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { QualityVO, Query, ConstructionUserVO, MachineryrVO, MaterialsVO } from './type';
import { QualityVO, Query, ConstructionUserVO, MachineryrVO, MaterialsVO, projectNewsVO, safetyInspectionVO, projectNewsDetailVO } from './type';
/**
* 查询大屏质量信息
* @param query
@ -15,6 +15,47 @@ export const getQualityList = (query?: Query): AxiosPromise<QualityVO> => {
});
};
/**
* 查询大屏项目新闻列表
* @param query
* @returns {*}
*/
export const getprojectNewsList = (query?: Query): AxiosPromise<projectNewsVO[]> => {
return request({
url: '/project/projectNews/list/gis',
method: 'get',
params: query
});
};
/**
* 获取项目新闻详细信息
* @param query
* @returns {*}
*/
export const getprojectNewsDetailList = (id: number): AxiosPromise<projectNewsDetailVO> => {
return request({
url: '/project/projectNews/' + id,
method: 'get'
});
};
/**
* 查询大屏安全信息
* @param query
* @returns {*}
*/
export const getsafetyInspectionList = (query?: Query): AxiosPromise<safetyInspectionVO> => {
return request({
url: '/safety/safetyInspection/gis',
method: 'get',
params: query
});
};
/**
* 查询施工人员大屏数据
* @param query

View File

@ -41,3 +41,35 @@ export interface MaterialsVO {
outCount: string;
value: number;
}
export interface projectNewsVO {
id: string;
title: string;
show?: boolean;
}
export interface projectNewsDetailVO {
id: string;
title: string;
content: string;
file: string;
}
export interface safetyInspectionVO {
//站班会总数
teamMeetingCount: string;
//安全巡检总数
safetyInspectionCount: string;
//整改情况
correctSituationCount: string;
//站班会列表
teamMeetingList: safetyInspectionlist[];
}
export interface safetyInspectionlist {
id: string;
teamName: string;
name: string;
meetingDate: string;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@ -0,0 +1,75 @@
import * as echarts from 'echarts';
/**
* 为 ECharts 实例添加自动滚动功能(精确固定窗口项数 + 用户操作暂停)
* @param chartInstance ECharts 实例
* @param totalItems x轴总项数
* @param windowSize 显示窗口项数,默认 6
* @param interval 滚动间隔,默认 1500ms
* @returns 停止函数
*/
export function enableEchartsAutoScroll(
chartInstance: echarts.ECharts,
totalItems: number,
windowSize: number = 6,
interval: number = 1500
): () => void {
let index = 0;
let isUserInteracting = false;
let lastInteractionTime = Date.now();
let currentWindowSize = windowSize;
// 主动初始化窗口大小(如果设置了 dataZoom
const option = chartInstance.getOption();
const zoom = option?.dataZoom?.[0];
if (zoom && zoom.start != null && zoom.end != null) {
const startIndex = Math.round((zoom.start / 100) * totalItems);
const endIndex = Math.round((zoom.end / 100) * totalItems);
currentWindowSize = endIndex - startIndex;
}
// 监听用户操作
const dataZoomHandler = (params: any) => {
const zoom = params.batch ? params.batch[0] : params;
const startIndex = Math.round((zoom.start / 100) * totalItems);
const endIndex = Math.round((zoom.end / 100) * totalItems);
currentWindowSize = endIndex - startIndex;
isUserInteracting = true;
lastInteractionTime = Date.now();
};
chartInstance.on('dataZoom', dataZoomHandler);
// 自动滚动定时器
const timer = setInterval(() => {
const now = Date.now();
if (isUserInteracting && now - lastInteractionTime < 1000) return;
isUserInteracting = false;
if (index + currentWindowSize > totalItems) {
index = 0;
}
const startPercent = (index / totalItems) * 100;
const endPercent = ((index + currentWindowSize) / totalItems) * 100;
chartInstance.setOption({
dataZoom: [
{
start: startPercent,
end: endPercent
}
]
});
index++;
}, interval);
// 返回停止方法
return () => {
clearInterval(timer);
chartInstance.off('dataZoom', dataZoomHandler);
};
}

View File

@ -1,62 +1,55 @@
<template>
<div class="auto-scroll-container" @mouseenter="pauseScroll" @mouseleave="resumeScroll" ref="container">
<div class="auto-scroll-content" ref="content">
<div class="auto-scroll-item" v-for="(item, index) in duplicatedList" :key="index">
<slot :item="item">{{ item }}</slot>
<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 setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
// Props
const props = defineProps({
items: {
type: Array,
required: true
},
speed: {
type: Number,
default: 0.5 // px/frame
},
height: {
type: Number,
default: 100 // px
},
minItems: {
type: Number,
default: 2 // 小于这个数量不滚动
},
autoScroll: {
type: Boolean,
default: true // 控制是否自动滚动
}
});
// Refs and Computed
const container = ref(null);
const content = ref(null);
const duplicatedList = computed(() => [...props.items, ...props.items]);
const shouldScroll = computed(() => props.items.length >= props.minItems);
let scrollY = 0;
let animationFrameId = null;
let manualPaused = false; // 记录是否因为滚轮手动停止
// 滚动核心逻辑
function normalizeScrollY(contentHeight) {
if (scrollY <= -contentHeight) scrollY += contentHeight;
if (scrollY >= 0) scrollY -= contentHeight;
<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 = `translateY(${Math.round(scrollY)}px)`;
content.value.style.transform = `translate3d(0, ${Math.round(scrollY)}px, 0)`;
animationFrameId = requestAnimationFrame(step);
}
@ -67,49 +60,79 @@ function startScroll() {
}
function pauseScroll() {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}
// 鼠标滚轮事件:手动滚动并停止自动滚动
function onWheel(e) {
function onMouseEnter() {
if (!manualControl) pauseScroll();
}
function onMouseLeave() {
if (!manualControl && props.autoScroll) 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();
manualPaused = true; // 标记为手动停止
const contentHeight = content.value.offsetHeight / 2;
scrollY -= e.deltaY;
normalizeScrollY(contentHeight);
content.value.style.transform = `translateY(${Math.round(scrollY)}px)`;
}
// 鼠标移出时恢复自动滚动(如果不是手动暂停)
function resumeScroll() {
if (props.autoScroll && shouldScroll.value) {
manualPaused = false; // 重置手动暂停标志
startScroll();
}
content.value.style.transform = `translate3d(0, ${Math.round(scrollY)}px, 0)`;
}
// 生命周期
onMounted(() => {
if (shouldScroll.value && props.autoScroll) {
if (scrollEnabled.value && props.autoScroll) {
startScroll();
}
container.value.addEventListener('wheel', onWheel);
container.value?.addEventListener('wheel', onWheel, { passive: false });
});
onUnmounted(() => {
pauseScroll();
container.value.removeEventListener('wheel', onWheel);
container.value?.removeEventListener('wheel', onWheel);
});
// 响应 items 数量变化
watch(shouldScroll, (newVal) => {
// 监听数据是否滚动条件满足
watch(scrollEnabled, (newVal) => {
if (!newVal) {
pauseScroll();
} else if (props.autoScroll && !manualPaused) {
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>
@ -122,4 +145,7 @@ watch(shouldScroll, (newVal) => {
flex-direction: column;
will-change: transform;
}
.safety:nth-child(odd) {
background-color: rgba(67, 226, 203, 0.2);
}
</style>

View File

@ -47,6 +47,7 @@ console.log(window.innerWidth);
bottom: vh(31);
left: vw(507);
padding-top: vh(10);
z-index: 2;
}
.carousellist {

View File

@ -22,8 +22,7 @@
</div>
<p>总人数</p>
<div class="peopleNum">
<span>{{ constructionUserData?.peopleCount }}</span
>
<span>{{ constructionUserData?.peopleCount + ' ' }} </span>
</div>
</div>
<div class="card">
@ -32,8 +31,7 @@
</div>
<p>出勤人</p>
<div class="peopleNum">
<span>{{ constructionUserData?.attendanceCount }}</span
>
<span>{{ constructionUserData?.attendanceCount + ' ' }} </span>
</div>
</div>
<div class="card">
@ -42,8 +40,7 @@
</div>
<p>出勤率</p>
<div class="peopleNum">
<span>{{ constructionUserData?.attendanceRate }}</span
>%
<span>{{ constructionUserData?.attendanceRate + ' ' }} </span>%
</div>
</div>
</div>
@ -88,6 +85,7 @@ import { getConstructionUserList, getMachineryrList, getMaterialsList } from '@/
import { ConstructionUserVO, MachineryrVO, MaterialsVO } from '@/api/gis/type';
import * as echarts from 'echarts';
import { useUserStoreHook } from '@/store/modules/user';
import { enableEchartsAutoScroll } from '@/plugins/scrollEcharts';
const userStore = useUserStoreHook();
//echarts节点
const myMachineryChart = ref(null);
@ -97,6 +95,8 @@ type EChartsOption = echarts.EChartsOption;
const constructionUserData = ref<ConstructionUserVO>(null);
const machineryOption = ref<MachineryrVO[]>([]); //机械
const orderOption = ref<MaterialsVO[]>([]); //材料
const stopMachineryScroll = ref(null);
const stopOrderScroll = ref(null);
const machineryDataAxis = computed(() => machineryOption.value.map((item) => item.machineryName)); //x轴数据
const machineryData = computed(() => machineryOption.value.map((item) => item.machineryCount)); //柱状图数据
const orderDataAxis = computed(() => orderOption.value.map((item) => item.materialsName)); //材料x轴数据
@ -148,7 +148,10 @@ const initMachinerycharts = () => {
data: machineryDataAxis.value,
axisLabel: {
// inside: true,
color: 'rgba(202, 218, 226, 1)'
color: 'rgba(202, 218, 226, 1)',
fontSize: 10,
interval: 0
},
axisTick: {
show: false
@ -167,11 +170,11 @@ const initMachinerycharts = () => {
show: false
},
axisLabel: {
color: '#999'
color: '#999',
fontSize: 12
},
min: 0,
max: 40,
interval: 10,
interval: 1,
splitNumber: 5,
splitLine: {
show: true,
lineStyle: {
@ -256,6 +259,7 @@ const initMachinerycharts = () => {
};
option && myMachineryChart.value.setOption(option);
stopMachineryScroll.value = enableEchartsAutoScroll(myMachineryChart.value, machineryDataAxis.value.length, 6, 2000);
};
const initOrderChart = () => {
@ -500,6 +504,7 @@ const initOrderChart = () => {
]
};
option && myOrderChart.value.setOption(option);
stopOrderScroll.value = enableEchartsAutoScroll(myOrderChart.value, orderDataAxis.value.length, 5, 2000);
};
// 防抖函数
@ -528,6 +533,12 @@ onMounted(() => {
getMachineryData();
window.addEventListener('resize', debouncedHandleResize); //监听窗口变化重新生成echarts
});
onUnmounted(() => {
window.removeEventListener('resize', debouncedHandleResize);
stopMachineryScroll.value();
stopOrderScroll.value();
});
</script>
<style lang="scss" scoped>
@ -560,10 +571,11 @@ onMounted(() => {
span {
text-shadow: 0 0 vw(8) rgba(2, 3, 7, 0.35);
font-size: vw(20);
font-weight: 500;
font-size: vw(16);
letter-spacing: 0;
color: rgba(255, 255, 255, 1);
font-family: 'DOUYUFont';
padding-top: vh(5);
}
}
@ -572,6 +584,7 @@ onMounted(() => {
font-weight: 400;
color: rgba(204, 204, 204, 0.5);
margin-right: vw(20);
font-family: 'Roboto';
}
.mark {
@ -595,6 +608,7 @@ onMounted(() => {
position: absolute;
top: vh(122);
left: vw(21);
z-index: 2;
.main {
padding-top: vh(30);
@ -615,7 +629,7 @@ onMounted(() => {
img {
width: vw(44);
margin-bottom: vh(20);
margin-bottom: vh(10);
}
> p {
@ -631,7 +645,7 @@ onMounted(() => {
text-shadow: 0 vw(1.24) vw(6.21) rgba(0, 190, 247, 1);
color: rgba(230, 247, 255, 1);
font-family: 'Roboto';
span {
font-size: vw(24);
font-weight: 700;

View File

@ -28,8 +28,12 @@ onMounted(async () => {
</script>
<style lang="scss" scoped>
@import '../css/gis.scss';
.ol-map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: calc(100vh - 123px);
height: calc(100vh);
}
</style>

View File

@ -17,22 +17,16 @@
</div>
<div class="events">
<div class="content events-content event_s">
<ul class="events-list">
<!-- <li v-for="(item, index) in events" :key="index">
<span class="text detail" style="display: inline"> {{ item.headline }}...</span>
<span class="more" v-if="!item.show" @click="onMore(item, true)">查看详情</span>
<span class="more" style="color: #ffb100eb" v-else @click="onMore(item, false)">关闭详情</span>
</li> -->
</ul>
<AutoScroller :items="events" class="events-list">
<template #default="{ item }">
<AutoScroller :items="projectNewsData" class="events-list" ref="newsScroller">
<template #default="{ item, index }">
<li>
<span class="text detail" style="display: inline"> {{ item.headline }}...</span>
<span class="more" v-if="!item.show" @click="onMore(item, true)">查看详情</span>
<span class="more" style="color: #ffb100eb" v-else @click="onMore(item, false)">关闭详情</span>
<span class="text detail" style="display: inline"> {{ item.title }}</span>
<span class="more" v-if="!item.show" @click="onMore(index, true, item.id)">查看详情</span>
<span class="more" style="color: #ffb100eb" v-else @click="onMore(index, false)">关闭详情</span>
</li>
</template>
</AutoScroller>
<div class="detail-content" v-if="newsDetailDialog" v-html="NewsDetailMain"></div>
<!-- <span v-else style="font-size: 20px; letter-spacing: 10px">暂无数据</span> -->
</div>
</div>
@ -57,8 +51,8 @@
<img src="@/assets/images/danggerNum.png" alt="" />
</div>
<div>
<span>危险源数量</span>
<p>14<span></span></p>
<span>站班会情况</span>
<p>{{ safetyInspectionData.teamMeetingCount }}<span> </span></p>
</div>
</div>
<div class="safetyData-item flex items-center">
@ -67,7 +61,7 @@
</div>
<div>
<span>巡检记录</span>
<p>14<span></span></p>
<p>{{ safetyInspectionData.safetyInspectionCount }}<span> </span></p>
</div>
</div>
<div class="safetyData-item flex items-center">
@ -76,46 +70,25 @@
</div>
<div>
<span>整改情况</span>
<p>14<span></span></p>
<p>{{ safetyInspectionData.correctSituationCount }}<span> </span></p>
</div>
</div>
</div>
<div class="tables">
<div class="thead">
<el-table
:data="[]"
header-row-class-name="bg-transparent"
header-cell-class-name="bg-transparent"
style="--el-table-border-color: none"
empty-text=""
height="3.7vh"
>
<el-table-column prop="teamName" label="班组" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="meetingDate" label="时间" />
<el-table-column prop="status" label="操作" />
</el-table>
<span class="teamWidth">班组</span>
<span class="teamWidth">名称</span>
<span class="teamWidth">时间</span>
<span class="statusWidth">操作</span>
</div>
<AutoScroller :items="safetyData" class="tbody">
<AutoScroller :items="safetyInspectionData.teamMeetingList" class="tbody" safety>
<template #default="{ item }">
<el-table
:data="safetyData"
stripe
row-class-name="bg-transparent"
cell-class-name="bg-transparent"
header-row-class-name="header-row-bg-transparent"
header-cell-class-name="bg-transparent"
style="--el-table-border-color: none"
>
<el-table-column prop="teamName" label="" class-name="teamNameWidth" />
<el-table-column prop="name" label="" class-name="nameWidth" />
<el-table-column prop="meetingDate" label="" class-name="meetingDateWidth" />
<el-table-column prop="status" label="" class-name="statusWidth">
<template #default="scope">
<el-link :underline="false">查看</el-link>
</template>
</el-table-column>
</el-table>
<div class="flex">
<span class="teamWidth">{{ item.teamName }}</span>
<span class="teamWidth">{{ item.name }}</span>
<span class="teamWidth time">{{ item.meetingDate }}</span>
<span class="statusWidth">查看</span>
</div>
</template>
</AutoScroller>
</div>
@ -141,11 +114,11 @@
</div>
<div class="qualityNum">
<div>巡检记录 <b></b></div>
<p>{{ qualityData?.count }}<span></span></p>
<p>{{ qualityData?.count }}<span> </span></p>
</div>
<div class="qualityNum ml-15">
<div>整改情况 <b></b></div>
<p>{{ qualityData?.correctSituation }}<span>%</span></p>
<p>{{ qualityData?.correctSituation }}<span> %</span></p>
</div>
</div>
<AutoScroller :items="qualityData?.list" class="qualityList">
@ -153,7 +126,7 @@
<div class="qualityItem flex items-center">
<div>
<img src="@/assets/images/timeIcon.png" alt="" />
<span class="text-white">{{ item.createTime }}</span>
<span class="text-white qualityTime">{{ item.createTime }}</span>
</div>
<div class="text-#43E2CB record">{{ item.inspectionTypeLabel }}</div>
<div class="text-#E6F7FF text-truncate">
@ -187,9 +160,9 @@
<script lang="ts" setup>
import * as echarts from 'echarts';
import { QualityVO } from '@/api/gis/type';
import { projectNewsVO, QualityVO, safetyInspectionVO } from '@/api/gis/type';
import { useUserStoreHook } from '@/store/modules/user';
import { getQualityList } from '@/api/gis';
import { getQualityList, getprojectNewsDetailList, getprojectNewsList, getsafetyInspectionList } from '@/api/gis';
import AutoScroller from './autoScroller.vue';
const userStore = useUserStoreHook();
type EChartsOption = echarts.EChartsOption;
@ -212,10 +185,19 @@ const scrollList = reactive({
intervalId: null
}
});
const newsScroller = ref();
const newsDetailDialog = ref<boolean>(false);
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const qualityData = ref<QualityVO>({ list: [], correctSituation: null, count: null });
const projectNewsData = ref<projectNewsVO[]>([]);
const NewsDetailMain = ref<string>(null);
const safetyInspectionData = ref<safetyInspectionVO>({
teamMeetingCount: null,
safetyInspectionCount: null,
correctSituationCount: null,
teamMeetingList: []
});
const classOptions = {
limitMoveNum: 3,
hoverStop: true,
@ -229,81 +211,19 @@ const getQualityData = async () => {
qualityData.value = res.data;
};
const events = ref([
{
'id': 23,
'headline': '公司获评2024年重庆市“专精特新”中小企业荣誉称号',
'content':
'<p><img src="/file/upload_file/2024-07-03/d2ft1zure0bqnw0rh5.jpg" title="63e222cfec0fb1fae48ce8133a28836.jpg" alt="63e222cfec0fb1fae48ce8133a28836.jpg"/></p><p>近日重庆市经济和信息化委员会发布了《2024年重庆市专精特新中小企业申报和复核通过名单》。公司获评2024年重庆市“专精特新”中小企业荣誉称号。</p>',
'createdBy': '1',
'createdAt': '2024-07-03 17:44:18'
},
{
'id': 22,
'headline': '锦州市副市长缪徵阁一行到公司调研交流',
'content':
'<p><img src="/file/upload_file/2024-07-03/d2ft1eczdr6wvlatai.jpg" title="b32af2041363db9bb4706de22ae7b6a.jpg" alt="b32af2041363db9bb4706de22ae7b6a.jpg"/></p><p>2024年5月10日锦州市副市长缪徵阁一行到公司调研交流。双方将以此次交流为契机在城市更新、乡村振兴、合同能源管理等重点领域充分挖掘合作潜力共同推动锦州市高质量发展迈上新台阶。</p>',
'createdBy': '1',
'createdAt': '2024-07-03 17:43:12'
},
{
'id': 20,
'headline':
'贵州省都匀市委副书记、市长、都匀经济开发区党工委副书记、管委会主任张宗良到墨冲镇丙午村实地调研沙包堡匀东农业光伏电站项目推进情况并作出工作指示。',
'content':
'<p><img src="/file/upload_file/2024-04-23/d0raj374dj6liccveg.jpg" title="890390a01d3486ef67660c3aa4da2e8.jpg" alt="890390a01d3486ef67660c3aa4da2e8.jpg"/></p><p>2024年3月20日贵州省都匀市委副书记、市长、都匀经济开发区党工委副书记、管委会主任张宗良到墨冲镇丙午村实地调研沙包堡匀东农业光伏电站项目推进情况并作出工作指示。</p>',
'createdBy': '1',
'createdAt': '2024-04-23 14:34:06'
},
{
'id': 19,
'headline': '2024年2月27日公司党支部书记、董事长车志文一行到广州拜访中电建新能源集团股份有限公司南方分公司',
'content':
'<p><img src="/file/upload_file/2024-03-29/d064pukanea8b175xt.png" title="image.png" alt="image.png"/></p><p style="text-indent:43px;text-autospace:ideograph-numeric;line-height:37px"><span style=";font-family:仿宋_GB2312;font-size:21px">2024年2月27日公司党支部书记、董事长车志文一行到广州拜访中电建新能源集团股份有限公司南方分公司双方围绕广西壮族自治区百色市德保县乡村振兴光伏发电项目二批、三批进展情况及下一步的战略合作进行了深入交流。双方将共同深耕能源领域</span><span style="font-family: 仿宋_GB2312;font-size: 21px">在广西、广东、海南片区继续深化合作,</span><span style=";font-family:仿宋_GB2312;font-size:21px">为加快促进产业转型升级赋予新的动能。</span></p><p><br/></p>',
'createdBy': '1',
'createdAt': '2024-03-29 17:33:57'
},
{
'id': 18,
'headline': '公司通过2023年重庆市第二批国家高新技术企业和重庆市中小企业技术研发中心认定',
'content':
'<p><img src="/file/upload_file/2024-03-29/d064p845trsoi5jmnq.png" title="image.png" alt="image.png"/></p><p style="text-indent:43px;text-autospace:ideograph-numeric"><span style=";font-family:仿宋_GB2312;font-size:21px"><span style="font-family:仿宋_GB2312">日前,科学技术部火炬高技术产业开发中心发布了《对重庆市认定机构</span><span style="font-family:仿宋_GB2312">2023年认定报备的第二批高新技术企业进行备案的公告》重庆市经济和信息化委员会发布了《关于公布2023年重庆市中小企业技术研发中心认定及复核结果的通知》公司通过2023年重庆市第二批国家高新技术企业和重庆市中小企业技术研发中心认定。</span></span></p><p><br/></p>',
'createdBy': '1',
'createdAt': '2024-03-29 17:33:25'
},
{
'id': 17,
'headline': '公司顺利中标广西德保县乡村振兴光伏发电项目第三批EPC总承包项目、贵州都匀市沙包堡匀东农业光伏电站项目',
'content':
'<p><img src="/file/upload_file/2024-03-29/d064o8rjjjgkde3qiv.png" title="image.png" alt="image.png"/></p><p><span style=";font-family:仿宋_GB2312;font-size:21px"><span style="font-family:仿宋_GB2312">新年新气象,实干开新局,公司顺利中标广西德保县乡村振兴光伏发电项目(第三批)</span><span style="font-family:仿宋_GB2312">EPC总承包项目、贵州都匀市沙包堡匀东农业光伏电站项目</span></span><span style=";font-family:仿宋_GB2312;font-size:21px"><span style="font-family:仿宋_GB2312">喜迎</span><span style="font-family:仿宋_GB2312">2024“开门红”为公司全年高质量发展拉开序幕。</span></span></p><p><br/></p>',
'createdBy': '1',
'createdAt': '2024-03-29 17:32:15'
},
{
'id': 16,
'headline': '2023年8月16日陕西省汉中市南郑区区委副书记袁蕊一行到公司调研交流。',
'content':
'<p><img src="/file/upload_file/2024-03-29/d064kp9obru4igua4t.png" title="image.png" alt="image.png"/></p><p style="text-indent:43px;text-autospace:ideograph-numeric"><span style=";font-family:仿宋_GB2312;font-size:21px">2023年8月16日陕西省汉中市南郑区区委副书记袁蕊一行到公司调研交流。双方将进一步深化战略合作围绕南郑区发展所需充分发挥公司在绿色能源、城市提升、生态修复、乡村振兴等领域的综合实力助力南郑区高质量发展。南郑区也将积极提供政策帮助和支持双方共同以保护绿水青山为己任共赴绿色发展之路。</span></p><p><br/></p>',
'createdBy': '1',
'createdAt': '2024-03-29 17:27:14'
},
{
'id': 15,
'headline': '公司成功中标中电建新能源集团股份有限公司投资建设的广西德保县乡村振兴光伏发电项目第二批EPC总承包。',
'content':
'<p><img src="/file/upload_file/2024-03-29/d064k1u1oky3e7fdfk.png" title="image.png" alt="image.png"/></p><p style="text-indent:43px;text-autospace:ideograph-numeric"><span style=";font-family:仿宋_GB2312;font-size:21px"><span style="font-family:仿宋_GB2312">公司成功中标中电建新能源集团股份有限公司投资建设的广西德保县乡村振兴光伏发电项目(第二批)</span><span style="font-family:仿宋_GB2312">EPC总承包。该项目采用“农光互补”发电模式利用农村闲置土地、废弃矿区等闲置资源布置光伏发电。运用自主开发的新能源智能化全过程管理系统提高工程实施过程中的质量管控和安全防控。该项目是贯彻落实碳达峰、碳中和与乡村振兴两大国家战略一体推进的生动实践建成投运后将改善德保县废弃矿区及石漠化地区生态环境及村民用电难问题。</span></span></p><p><br/></p>',
'createdBy': '1',
'createdAt': '2024-03-29 17:26:32'
},
{
'id': 14,
'headline': '2023年7月18日浪潮集团有限公司高级副总裁、浪潮院士、山东浪潮数据库技术有限公司总经理张晖一行莅临公司考察交流',
'content':
'<p><img src="/file/upload_file/2024-03-29/d064b2gqr80hnrxsqw.png" title="image.png" alt="image.png"/></p><p style="text-indent:43px;text-autospace:ideograph-numeric"><span style=";font-family:仿宋_GB2312;font-size:21px">2023年7月18日浪潮集团有限公司高级副总裁、浪潮院士、山东浪潮数据库技术有限公司总经理张晖一行莅临公司考察交流并与公司签订战略合作框架协议。双方将充分发挥各自领域优势实现在行业降碳、绿色能源、智慧城市、智能管理等领域的全方位合作。</span></p><p><br/></p>',
'createdBy': '1',
'createdAt': '2024-03-29 17:15:17'
}
]);
//获取大屏项目新闻列表
const getProjectNewsData = async () => {
const res = await getprojectNewsList({ projectId: currentProject.value.id });
if (res.code !== 200) return;
projectNewsData.value = res.data;
};
//查询大屏安全信息
const getsafetyInspectionData = async () => {
const res = await getsafetyInspectionList({ projectId: currentProject.value.id });
if (res.code !== 200) return;
safetyInspectionData.value = res.data;
};
const safetyData = ref([]);
new Array(10).fill(0).forEach((item) => {
@ -315,13 +235,23 @@ new Array(10).fill(0).forEach((item) => {
});
});
const onMore = (item, isShow) => {};
const onMore = async (index, isShow, id?) => {
if (isShow) {
newsScroller.value.pause();
projectNewsData.value[index].show = true;
const res = await getprojectNewsDetailList(id);
NewsDetailMain.value = res.data.content;
newsDetailDialog.value = true;
} else {
newsScroller.value.resume();
projectNewsData.value[index].show = false;
newsDetailDialog.value = false;
}
};
//初始化形象进度图表
const initUserChart = () => {
let chartDom = document.getElementById('userMain');
myMachineryChart.value = echarts.init(chartDom);
console.log(123123);
// prettier-ignore
let dataAxis = ['桩点浇筑', '支架安装', '组件安装', '箱变安装'];
option.value = {
@ -425,6 +355,8 @@ const debouncedHandleResize = debounce(handleResize, 300);
onMounted(() => {
initUserChart();
getQualityData();
getProjectNewsData();
getsafetyInspectionData();
window.addEventListener('resize', debouncedHandleResize);
});
@ -440,7 +372,7 @@ onUnmounted(() => {
width: 100%;
padding-right: vw(14);
margin-top: vh(19);
height: vh(150);
height: vh(170);
}
.management {
@ -451,7 +383,7 @@ onUnmounted(() => {
position: absolute;
top: vh(122);
right: vw(21);
z-index: 2;
.main {
padding-top: vh(17);
}
@ -509,10 +441,11 @@ onUnmounted(() => {
span {
text-shadow: 0 0 vw(8) rgba(2, 3, 7, 0.35);
font-size: vw(20);
font-weight: 500;
font-size: vw(16);
letter-spacing: 0;
color: rgba(255, 255, 255, 1);
font-family: 'DOUYUFont';
padding-top: vh(5);
}
}
@ -521,6 +454,7 @@ onUnmounted(() => {
font-weight: 400;
color: rgba(204, 204, 204, 0.5);
margin-right: vw(20);
font-family: 'Roboto';
}
.mark {
@ -559,6 +493,7 @@ onUnmounted(() => {
color: rgba(255, 255, 255, 1);
margin-bottom: vh(5);
position: relative;
font-family: '思源黑体';
b {
position: absolute;
top: vh(5);
@ -595,8 +530,10 @@ onUnmounted(() => {
color: #fff;
text-shadow: 0 vw(1.24) vw(6.21) rgba(0, 190, 247, 1);
line-height: vh(27);
font-family: 'Roboto';
span {
font-size: vw(12);
font-family: '思源黑体';
}
}
}
@ -611,6 +548,10 @@ onUnmounted(() => {
font-size: vw(14);
.qualityItem {
margin-bottom: vh(13);
font-family: '思源黑体';
.qualityTime {
font-family: 'Roboto';
}
img {
width: vw(12);
margin-right: vw(8);
@ -641,6 +582,7 @@ onUnmounted(() => {
font-size: vw(14);
font-weight: 400;
color: rgba(204, 204, 204, 1);
font-family: '思源黑体';
}
> p {
@ -648,11 +590,12 @@ onUnmounted(() => {
font-size: vw(24);
font-weight: 700;
color: rgba(255, 255, 255, 1);
font-family: 'Roboto';
> span {
font-size: vw(12);
font-weight: 400;
color: rgba(255, 255, 255, 1);
font-family: '思源黑体';
}
}
}
@ -667,14 +610,16 @@ p {
.events {
margin: vh(20) 0 vh(15);
width: 100%;
font-family: '思源黑体';
position: relative;
height: vh(82);
.events-list {
width: 100%;
height: 100%;
overflow: hidden;
padding-right: vw(10);
margin: 0;
padding: 0;
li {
width: 100%;
padding-left: vw(20);
@ -698,10 +643,8 @@ p {
font-size: vw(12);
font-weight: 400;
letter-spacing: 0;
line-height: vh(24);
color: rgba(230, 247, 255, 1);
text-align: justify;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
@ -709,6 +652,7 @@ p {
-webkit-line-clamp: 2;
max-height: 4em;
height: 4em;
margin-right: vw(5);
}
}
@ -732,6 +676,32 @@ p {
// border-left: none;
// }
}
.detail-content {
position: absolute;
top: 0;
left: vw(-275);
width: vw(250);
height: vh(328);
background: rgba(0, 0, 0, 0.5);
overflow: auto;
padding: vh(15) vw(15);
color: rgba(255, 255, 255, 1);
backdrop-filter: blur(vw(8));
border: 1px solid;
border-image: linear-gradient(to bottom, rgba(67, 226, 203, 1), rgba(67, 226, 203, 0.5)) 1;
::v-deep(p) {
margin: vh(10) 0;
img {
width: 100%;
border-radius: vw(8);
}
span {
font-size: vw(12) !important;
line-height: 18px;
}
}
}
}
.content {
@ -748,68 +718,45 @@ p {
.tables {
padding: 0 vw(13) 0 vw(14);
font-weight: 400;
font-size: vw(14);
font-family: '思源黑体';
.tbody {
height: vh(94);
overflow: hidden;
padding-right: vw(14);
color: rgba(255, 255, 255, 1);
font-size: 13px;
font-family: '思源黑体';
.flex {
padding-left: vw(16);
height: vh(26);
line-height: vh(26);
font-size: vw(13);
margin: vh(8) 0;
}
.time {
font-family: 'Roboto';
}
}
.thead {
padding-right: vw(20);
margin-top: vh(12);
}
}
:deep(.el-table) {
background: transparent; //这是设置透明背景色
.bg-transparent {
background: rgba(67, 226, 203, 0.2) !important; //这是设置透明背景色
border: none; //这是设置透明边框
color: rgba(255, 255, 255, 1); //这是设置字体颜色
font-size: vw(12);
text-align: left;
padding: vh(4) 0;
height: vh(26) !important;
}
.nameWidth {
width: vw(114);
}
.meetingDateWidth {
width: vw(114);
display: flex;
color: #43e2cb;
height: vh(40);
padding-left: vw(16);
line-height: vh(40);
}
.statusWidth {
width: vw(44);
color: rgba(67, 226, 203, 1);
cursor: pointer;
}
.teamNameWidth {
.teamWidth {
width: vw(114);
}
.el-table__row--striped {
background: transparent !important; //这是设置透明背景色
.bg-transparent {
background: transparent !important; //这是设置透明背景色
}
}
.header-row-bg-transparent {
display: none;
}
thead {
.bg-transparent {
background: transparent !important; //这是设置透明背景色
color: #43e2cb;
font-weight: 400;
font-size: vw(14);
}
}
.el-table__empty-block {
display: none;
}
}
/* 滚动条整体样式 */

View File

@ -0,0 +1,110 @@
<template>
<div class="marquee-container" @mouseenter="requestPause" @mouseleave="resumeScroll">
<ul class="marquee-list" :style="{ transform: `translateY(-${offset}px)`, transition: transition }">
<li v-for="(item, i) in fullList" :key="i" class="marquee-item" :ref="i === 0 ? 'firstItem' : null">
<slot :item="item" :index="i % items.length">{{ item }}</slot>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue';
const props = defineProps<{
items: string[];
interval?: number;
height?: number;
}>();
const currentIndex = ref(0);
const offset = ref(0);
const transition = ref('transform 0.5s ease');
const pendingPause = ref(false);
const firstItem = ref<HTMLElement | null>(null);
let timer: number | null = null;
// 使用 vh 单位计算的高度
const itemHeight = ref(0);
// 拼接列表:结尾添加一项首项实现无缝
const fullList = computed(() => [...props.items, props.items[0]]);
function scrollStep() {
currentIndex.value++;
transition.value = 'transform 0.3s ease';
offset.value = currentIndex.value * itemHeight.value;
if (currentIndex.value === props.items.length) {
setTimeout(() => {
transition.value = 'none';
currentIndex.value = 0;
offset.value = 0;
}, 350);
}
// 如果鼠标悬停请求正在等待,滚动结束后执行暂停
if (pendingPause.value) {
clearInterval(timer!);
timer = null;
pendingPause.value = false;
}
}
function startScroll() {
if (timer) clearInterval(timer);
timer = window.setInterval(scrollStep, props.interval ?? 2000);
}
function requestPause() {
pendingPause.value = true;
}
function resumeScroll() {
pendingPause.value = false;
startScroll();
}
function updateItemHeight() {
if (firstItem.value) {
itemHeight.value = firstItem.value[0].getBoundingClientRect().height;
offset.value = currentIndex.value * itemHeight.value;
}
}
onMounted(() => {
// 等待 DOM 加载后获取第一个项目的实际高度
if (firstItem.value) {
updateItemHeight();
startScroll();
}
window.addEventListener('resize', updateItemHeight);
});
onUnmounted(() => {
if (timer) clearInterval(timer);
window.removeEventListener('resize', updateItemHeight);
});
</script>
<style scoped lang="scss">
@import '../css/gis.scss'; // 引入 vh 单位
.marquee-container {
height: vh(50); /* 控制容器高度为视口高度的 10% */
width: vw(287);
overflow: hidden;
position: relative;
}
.marquee-list {
list-style: none;
padding: 0;
margin: 0;
}
.marquee-item {
height: vh(50); /* 控制每项高度为视口高度的 10% */
line-height: vh(50);
}
</style>

View File

@ -1,21 +1,29 @@
<template>
<div class="h100vh bg-black w100vw">
<div class="h100vh w100vw">
<div class="header flex items-center justify-between">
<div class="productionday flex items-center">
<img src="@/assets/images/projectLogo.png" alt="" />
<img src="@/assets/images/projectday.png" alt="" />
<img src="@/assets/images/day.png" alt="" />
1,235
<img src="@/assets/images/days.png" alt="" />
</div>
<div class="title">XXX智慧工地管理平台</div>
<div class="calendar flex items-center">
<div class="Weather text-white flex items-center">
<img src="@/assets/images/Weather.png" alt="" />
<div><span class="textBlack">多云</span><span class="robotocondensed">9°/18°</span></div>
</div>
<div class="weeks">
<span class="textBlack">周一(</span> <span class="robotocondensed">2024.3.11 08:35:46</span> <span class="textBlack">)</span>
</div>
<WeatherListScroll :items="['3', '3', '3']" class="weatherList" :interval="3500">
<template #default="{ item, index }">
<div class="flex items-center">
<div class="Weather text-white flex items-center">
<img src="@/assets/images/Weather.png" alt="" />
<div><span class="textBlack">多云</span><span class="robotocondensed"> 9°/18°</span></div>
</div>
<div class="weeks">
<span class="textBlack">周一(</span>
<span class="robotocondensed">2024.3.11</span>
<span class="textBlack">)</span>
</div>
</div>
</template>
</WeatherListScroll>
<div class="Segmentation">
<div class="bg-#43E2CB"></div>
<div class="bg-#43E2CB"></div>
@ -31,6 +39,10 @@
<leftMain></leftMain>
<RightMain></RightMain>
<Carousel></Carousel>
<div class="topShaow"></div>
<div class="bottomShaow"></div>
<div class="leftShaow"></div>
<div class="rightShaow"></div>
</div>
</div>
</template>
@ -41,7 +53,7 @@ import Carousel from './component/carousel.vue';
import LeftMain from './component/leftMain.vue';
import Map from './component/map.vue';
import RightMain from './component/rightMain.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import WeatherListScroll from './component/weatherListScroll.vue';
const goHome = () => {
let routeUrl = router.resolve({
@ -62,6 +74,45 @@ const goHome = () => {
background-repeat: no-repeat;
background-size: 100% 100%;
position: relative;
z-index: 2;
}
.leftShaow {
position: absolute;
left: 0px;
top: 0;
width: vw(548);
height: 100vh;
opacity: 1;
background: linear-gradient(90deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.39) 77.51%, rgba(0, 0, 0, 0.2) 92.05%, rgba(0, 0, 0, 0) 100%);
}
.bottomShaow {
position: absolute;
right: 0px;
bottom: 0;
height: vh(158);
width: 100vw;
opacity: 1;
background: linear-gradient(0deg, rgba(8, 14, 15, 1) 0%, rgba(8, 14, 15, 0.4) 60.42%, rgba(8, 14, 15, 0) 100%);
}
.rightShaow {
position: absolute;
right: 0px;
top: 0;
width: vw(515);
height: 100vh;
opacity: 1;
background: linear-gradient(270deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.39) 77.51%, rgba(0, 0, 0, 0.2) 92.05%, rgba(0, 0, 0, 0) 100%);
}
.topShaow {
position: absolute;
left: 0px;
top: 0;
width: 100vw;
height: vh(131);
opacity: 1;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, rgba(1, 26, 33, 0) 100%);
}
.calendar {
padding-top: vh(14);
@ -103,6 +154,14 @@ const goHome = () => {
.productionday {
height: vh(48);
> img:first-child {
margin-right: vw(8);
}
font-family: 'Roboto Condensed';
font-size: vw(28);
font-weight: 700;
color: rgba(255, 255, 255, 1);
text-shadow: 0px 1.24px 6.21px rgba(25, 179, 250, 1);
}
.goHome {
@ -122,7 +181,6 @@ const goHome = () => {
}
.content {
padding: 0 vw(20);
}
.robotocondensed {

View File

@ -17,40 +17,14 @@
</div>
</div>
</div>
<div>
<AutoScroller :items="list2" :speed="0.7" class="h25" />
<!-- <AutoScroller :items="list2" :height="150" :speed="1.2" /> -->
</div>
</template>
<script setup name="Index" lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import AutoScroller from './gisHome/component/autoScroller.vue';
const list1 = ['列表1 - 内容 A', '列表1 - 内容 B', '列表1 - 内容 C'];
const list2 = [
'列表2 - 第一条长内容测试',
'列表2 - 第二条长长长内容继续测试',
'列表2 - 第三条内容',
'列表2 - 第一条长内容测试',
'列表2 - 第二条长长长内容继续测试',
'列表2 - 第三条内容',
'列表2 - 第一条长内容测试',
'列表2 - 第二条长长长内容继续测试',
'列表2 - 第三条内容'
];
const router = useRouter();
// 模拟数据
const userCount = ref(1234);
const orderCount = ref(567);
const visitCount = ref(8901);
const goToPage = (page: string) => {
router.push(`/${page}`);
};
</script>
<style scoped lang="scss">