// HomeContent.js import React, { useEffect, useRef } from 'react'; import { Row, Col, Icon, Modal, Button } from 'antd'; import Slider from 'react-slick'; import styles from './../fullinter.less'; import echarts from 'echarts'; import configc from '../../config'; import 'slick-carousel/slick/slick.css'; import 'slick-carousel/slick/slick-theme.css'; import { extendRule, showFileModal, showFiles, GetFileModel } from '../../utils/common'; import FormPage from '../../components/FormPage'; class HomeContent extends React.Component { constructor(props) { super(props); this.sliderRef = React.createRef(); this.echartsInstances = { riskLevel: null, safeCheckChart: null, dangerOperation: null, backLogChart: null, }; this.chartResizeHandlers = {}; this.isUnmounted = false; this.autoplayTimer = null; this.state = { fileForm: { title: '', visible: false, }, }; } waitForElement = (elementId, maxRetries = 10) => { return new Promise((resolve) => { let retries = 0; const checkInterval = setInterval(() => { const element = document.getElementById(elementId); if (element || retries >= maxRetries) { clearInterval(checkInterval); resolve(!!element); } retries++; }, 50); }); }; riskLevel = async () => { if (this.isUnmounted) return; const elementExists = await this.waitForElement('riskLevelFull'); if (!elementExists || this.isUnmounted) return; if (this.echartsInstances.riskLevel) { this.echartsInstances.riskLevel.dispose(); this.echartsInstances.riskLevel = null; } const riskLevels = document.getElementById('riskLevelFull'); if (!riskLevels) return; this.echartsInstances.riskLevel = echarts.init(riskLevels); const data = this.props.riskTypeRate || []; const option = { color: ['#c92a2a', '#ffa94d', '#ffe066', '#4285F4'], title: [ { text: '风险分级占比', x: 'center', y: '5%', textStyle: { fontSize: 16, color: '#000' }, }, ], tooltip: { trigger: 'item', formatter: function (params) { const color = params.color; return `
${params.name}: ${params.value}
`; }, backgroundColor: 'rgba(255, 255, 255, 0.5)', borderColor: '#FFFFFF', borderWidth: 2, textStyle: { color: '#000', fontSize: 14 }, }, series: [ { name: '访问来源', type: 'pie', minAngle: 20, radius: ['55%', '80%'], center: ['50%', '50%'], clockwise: true, avoidLabelOverlap: true, hoverOffset: 15, label: { show: true, position: 'inside', formatter: '{d}%', // 添加 \n 换行符 color: '#000', textBorderWidth: 0, rich: { a: { padding: [-15, 0, 0, 0], fontSize: 15, color: '#000', textBorderWidth: 0 }, e: { fontSize: 14, color: '#000', padding: [-15, 0, 0, 5], textBorderWidth: 0 }, }, }, labelLine: { normal: { show: false } }, data: data.map((item) => ({ name: item.riskType, value: item.count })), }, ], }; this.echartsInstances.riskLevel.setOption(option); const resizeHandler = () => { if (this.echartsInstances.riskLevel && !this.isUnmounted) { this.echartsInstances.riskLevel.resize(); } }; this.chartResizeHandlers.riskLevel = resizeHandler; window.addEventListener('resize', resizeHandler); }; safeCheckChart = async () => { if (this.isUnmounted) return; const elementExists = await this.waitForElement('safeCheckChart'); if (!elementExists || this.isUnmounted) return; if (this.echartsInstances.safeCheckChart) { this.echartsInstances.safeCheckChart.dispose(); this.echartsInstances.safeCheckChart = null; } let safeCheckCharts = document.getElementById('safeCheckChart'); if (!safeCheckCharts) return; const hiddenSummary = this.props.hiddenSummary || {}; // 检查是否有有效数据 const hasData = hiddenSummary.monthMajorQty !== undefined || hiddenSummary.unfinishMonthMajorQty !== undefined || hiddenSummary.monthGeneralQty !== undefined || hiddenSummary.unfinishMonthGeneralQty !== undefined; if (!hasData) { this.echartsInstances.safeCheckChart = echarts.init(safeCheckCharts); this.echartsInstances.safeCheckChart.setOption({ title: { text: '当月隐患统计', x: 'center', y: '25%', textStyle: { fontSize: 16, color: '#fff' }, }, graphic: { type: 'text', left: 'center', top: 'middle', style: { text: '暂无数据', fill: '#999', fontSize: 14, }, }, }); return; } // 定义图例和数据 const legendData = ['重大隐患数', '重大隐患未整改数', '一般隐患数', '一般隐患未整改数']; const seriesData = [ { name: '重大隐患数', value: hiddenSummary.monthMajorQty || 0, color: '#c92a2a' }, { name: '重大隐患未整改数', value: hiddenSummary.unfinishMonthMajorQty || 0, color: '#ffa94d' }, { name: '一般隐患数', value: hiddenSummary.monthGeneralQty || 0, color: '#ffe066' }, { name: '一般隐患未整改数', value: hiddenSummary.unfinishMonthGeneralQty || 0, color: '#4285F4' }, ]; this.echartsInstances.safeCheckChart = echarts.init(safeCheckCharts); this.echartsInstances.safeCheckChart.setOption({ title: { text: '当月隐患统计', x: 'center', y: '5%', textStyle: { fontSize: 16, color: '#000' }, }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, formatter: function (params) { if (!params || params.length === 0) return ''; let result = `${params[0].axisValue}
`; params.forEach((param) => { result += `${param.marker}${param.seriesName}: ${param.value}
`; }); return result; }, }, legend: { data: legendData, right: '3%', top: '10%', itemGap: 12, itemWidth: 16, itemHeight: 10, textStyle: { color: '#000', fontSize: 12 }, }, color: seriesData.map((item) => item.color), grid: { left: '5%', right: '8%', top: '28%', bottom: '5%', containLabel: true, }, xAxis: [ { type: 'category', data: ['当月隐患'], axisLine: { lineStyle: { color: '#c4c6c9' } }, axisLabel: { textStyle: { color: '#000', fontSize: 14 } }, axisTick: { show: false }, }, ], yAxis: [ { type: 'value', name: '数量', nameTextStyle: { color: '#000' }, axisLine: { show: false }, axisTick: { show: false }, splitLine: { show: false }, axisLabel: { textStyle: { color: '#000' } }, }, ], series: seriesData.map((item) => ({ name: item.name, type: 'bar', data: [item.value], itemStyle: { normal: { color: item.color, barBorderRadius: [8, 8, 0, 0], }, emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)', }, }, label: { show: true, position: 'top', textStyle: { color: '#000', fontSize: 12 }, formatter: '{c}', }, barWidth: '10%', barGap: '30%', barCategoryGap: '20%', })), }); const resizeHandler = () => { if (this.echartsInstances.safeCheckChart && !this.isUnmounted) { this.echartsInstances.safeCheckChart.resize(); } }; this.chartResizeHandlers.safeCheckChart = resizeHandler; window.addEventListener('resize', resizeHandler); }; dangerOperation = async () => { if (this.isUnmounted) return; const elementExists = await this.waitForElement('dangerOperationChart'); if (!elementExists || this.isUnmounted) return; if (this.echartsInstances.dangerOperation) { this.echartsInstances.dangerOperation.dispose(); this.echartsInstances.dangerOperation = null; } const dangerOperationCharts = document.getElementById('dangerOperationChart'); if (!dangerOperationCharts) return; const linkData = this.props.jobTodayQty || []; // 如果没有数据,显示“暂无数据”提示 if (linkData.length === 0) { this.echartsInstances.dangerOperation = echarts.init(dangerOperationCharts); this.echartsInstances.dangerOperation.setOption({ title: { text: '当日工作票的统计数量', x: 'center', y: '25%', textStyle: { fontSize: 16, color: '#999' }, }, graphic: { type: 'text', left: 'center', top: 'middle', style: { text: '暂无数据', fill: '#999', fontSize: 14, }, }, }); return; } // 有数据时正常渲染图表 this.echartsInstances.dangerOperation = echarts.init(dangerOperationCharts); const xAxisData = linkData.map((item) => item.name); const seriesData = linkData.map((item) => item.qty); const option = { color: ['#4285F4'], title: [ { text: '当日工作票的统计数量', x: 'center', y: '5%', textStyle: { fontSize: 16, color: '#000' }, }, ], tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, formatter: function (params) { const color = params[0].color; return `
${params[0].name}: ${params[0].value}
`; }, backgroundColor: 'rgba(255, 255, 255, 0.5)', borderColor: '#FFFFFF', borderWidth: 2, textStyle: { color: '#000', fontSize: 14 }, }, grid: { left: '10%', right: '5%', top: '15%', bottom: '10%', containLabel: true }, xAxis: [ { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#c4c6c9' } }, axisTick: { show: false }, axisLabel: { textStyle: { color: '#000' }, rotate: 30, interval: 0 }, }, ], yAxis: [ { type: 'value', name: '', nameTextStyle: { color: '#000' }, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { textStyle: { color: '#000' } }, splitLine: { show: false }, }, ], series: [ { name: '危险作业数量', type: 'bar', data: seriesData, itemStyle: { normal: { color: '#4285F4', barBorderRadius: 12 }, emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' }, }, label: { show: true, position: 'top', textStyle: { color: '#000', fontSize: 12 }, formatter: '{c}' }, barWidth: '60%', }, ], }; this.echartsInstances.dangerOperation.setOption(option); const resizeHandler = () => { if (this.echartsInstances.dangerOperation && !this.isUnmounted) { this.echartsInstances.dangerOperation.resize(); } }; this.chartResizeHandlers.dangerOperation = resizeHandler; window.addEventListener('resize', resizeHandler); }; backLog = async () => { if (this.isUnmounted) return; const elementExists = await this.waitForElement('backLogChart'); if (!elementExists || this.isUnmounted) return; if (this.echartsInstances.backLogChart) { this.echartsInstances.backLogChart.dispose(); this.echartsInstances.backLogChart = null; } let backLogCharts = document.getElementById('backLogChart'); if (!backLogCharts) return; // 使用培训数据 const { trainingData } = this.props; const listNAME = trainingData?.listNAME || []; const monthRecordCount = trainingData?.MonthRecordCount || []; // 培训场次 const monthPersonCount = trainingData?.MonthPersonCount || []; // 培训人次 if (listNAME.length === 0) { this.echartsInstances.backLogChart = echarts.init(backLogCharts); this.echartsInstances.backLogChart.setOption({ title: { text: '当月培训统计数量', x: 'center', y: '25%', textStyle: { fontSize: 16, color: '#999' }, }, graphic: { type: 'text', left: 'center', top: 'middle', style: { text: '暂无数据', fill: '#999', fontSize: 14, }, }, }); return; } this.echartsInstances.backLogChart = echarts.init(backLogCharts); const option = { title: { text: '当月培训统计数量', x: 'center', y: '5%', textStyle: { fontSize: 16, color: '#000' }, }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, formatter: function (params) { let result = `${params[0].axisValue}
`; params.forEach((param) => { result += `${param.marker}${param.seriesName}: ${param.value}
`; }); return result; }, }, legend: { data: ['培训人次', '培训场次'], right: '3%', top: '10%', itemGap: 12, itemWidth: 16, itemHeight: 10, textStyle: { color: '#000', fontSize: 12 }, }, grid: { left: '5%', right: '5%', top: '28%', bottom: '5%', containLabel: true, }, xAxis: [ { type: 'category', data: listNAME, axisLine: { lineStyle: { color: '#c4c6c9' } }, axisTick: { show: false }, axisLabel: { textStyle: { color: '#000' }, rotate: listNAME.length > 4 ? 15 : 0, interval: 0, }, }, ], yAxis: [ { type: 'value', show: true, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: true, textStyle: { color: '#000' }, }, splitLine: { show: false }, name: '', nameTextStyle: { show: false }, }, ], series: [ { name: '培训人次', type: 'bar', data: monthPersonCount, itemStyle: { normal: { color: '#4285F4', barBorderRadius: 12, }, }, label: { show: true, position: 'top', textStyle: { color: '#4285F4', fontSize: 12 }, formatter: '{c}', }, barWidth: '35%', }, { name: '培训场次', type: 'bar', data: monthRecordCount, itemStyle: { normal: { color: '#ffe066', barBorderRadius: 12, }, }, label: { show: true, position: 'top', textStyle: { color: '#ffe066', fontSize: 12 }, formatter: '{c}', }, barWidth: '35%', }, ], }; this.echartsInstances.backLogChart.setOption(option); const resizeHandler = () => { if (this.echartsInstances.backLogChart && !this.isUnmounted) { this.echartsInstances.backLogChart.resize(); } }; this.chartResizeHandlers.backLogChart = resizeHandler; window.addEventListener('resize', resizeHandler); }; // 启动自动轮播 startAutoplay = (intervalTime) => { this.stopAutoplay(); if (!intervalTime || intervalTime <= 0) return; this.autoplayTimer = setInterval(() => { if (this.sliderRef.current && !this.isUnmounted) { const nextIndex = (this.props.currentMediaIndex + 1) % (this.props.trainingData?.listVideoImg?.length || 1); this.props.onCarouselChange?.(nextIndex); this.sliderRef.current.slickGoTo(nextIndex); } }, intervalTime); }; // 停止自动轮播 stopAutoplay = () => { if (this.autoplayTimer) { clearInterval(this.autoplayTimer); this.autoplayTimer = null; } }; initAllCharts = () => { if (this.isUnmounted) return; setTimeout(() => { if (this.isUnmounted) return; this.riskLevel(); this.safeCheckChart(); this.dangerOperation(); this.backLog(); }, 100); }; disposeAllCharts = () => { Object.keys(this.echartsInstances).forEach((key) => { if (this.echartsInstances[key]) { try { this.echartsInstances[key].dispose(); } catch (e) { console.warn(`Dispose chart ${key} error:`, e); } this.echartsInstances[key] = null; } }); Object.keys(this.chartResizeHandlers).forEach((key) => { if (this.chartResizeHandlers[key]) { window.removeEventListener('resize', this.chartResizeHandlers[key]); } }); this.chartResizeHandlers = {}; }; handlePrev = () => { if (this.sliderRef.current) { this.sliderRef.current.slickPrev(); const nextIndex = this.props.currentMediaIndex - 1; if (nextIndex >= 0) { this.props.onCarouselChange?.(nextIndex); } // 鼠标点击时重启自动播放计时器 this.restartAutoplay(); } }; handleNext = () => { if (this.sliderRef.current) { this.sliderRef.current.slickNext(); const nextIndex = this.props.currentMediaIndex + 1; const total = this.props.trainingData?.listVideoImg?.length || 0; if (nextIndex < total) { this.props.onCarouselChange?.(nextIndex); } // 鼠标点击时重启自动播放计时器 this.restartAutoplay(); } }; // 重启自动播放 restartAutoplay = () => { const playSet = this.props.trainingData?.playSet || {}; const isAutoplay = playSet.IMG_ISRE !== undefined ? playSet.IMG_ISRE : true; const autoplaySpeed = playSet.IMG_TIMESPAN ? playSet.IMG_TIMESPAN * 1000 : 5000; if (isAutoplay && autoplaySpeed > 0) { this.startAutoplay(autoplaySpeed); } }; handleDotClick = (index) => { this.props.onDotClick?.(index); if (this.sliderRef.current) { this.sliderRef.current.slickGoTo(index); } // 点击圆点时重启自动播放 this.restartAutoplay(); }; handleCarouselChange = (current) => { this.props.onCarouselChange?.(current); }; componentDidMount() { this.isUnmounted = false; this.initAllCharts(); } componentDidUpdate(prevProps) { // 当 trainingData 变化时,重新初始化自动播放 if ( prevProps.trainingData?.playSet?.IMG_ISRE !== this.props.trainingData?.playSet?.IMG_ISRE || prevProps.trainingData?.playSet?.IMG_TIMESPAN !== this.props.trainingData?.playSet?.IMG_TIMESPAN ) { this.restartAutoplay(); } if ( prevProps.riskTypeRate !== this.props.riskTypeRate || prevProps.hiddenSummary !== this.props.hiddenSummary || prevProps.jobTodayQty !== this.props.jobTodayQty || prevProps.taskTop3 !== this.props.taskTop3 || prevProps.trainingData !== this.props.trainingData ) { this.disposeAllCharts(); this.initAllCharts(); } } componentWillUnmount() { this.isUnmounted = true; this.stopAutoplay(); this.disposeAllCharts(); } // 新增:下载附件方法 handleDownload = (file) => { const fileUrl = configc.picServerHost + file.Nav_ImgFile.FILE_PATH; const link = document.createElement('a'); link.href = fileUrl; link.download = file.Nav_ImgFile.FILE_NAME; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; // 新增:预览附件方法 handlePreview = (file) => { const fileUrl = configc.picServerHost + file.Nav_ImgFile.FILE_PATH; window.open(fileUrl, '_blank'); }; // 新增:渲染公告弹窗内容 renderAnnouncementModal = () => { const { currentAnnouncement, announcementDetailLoading } = this.props; if (announcementDetailLoading) { return (
); } if (!currentAnnouncement) return null; const { TITLE, ABSTRACT, START, END, CONTENT, CREATE_USER_NAME, Nav_Files = [] } = currentAnnouncement; return (
{/* 标题 */}
{TITLE}
{/* 摘要 */}
{ABSTRACT}
{/* 日期 */}
{START ? START : '--'} 至 {END ? END : '--'} {CREATE_USER_NAME ? CREATE_USER_NAME : ''}
{/* 分割线 */}
{/* 正文内容 */}
{/* 附件列表 */} {Nav_Files && Nav_Files.length > 0 && (
附件 ({Nav_Files.length}个)
{showFiles(Nav_Files, configc.picServerHost, this)} {GetFileModel(Modal, FormPage, this, this.state.fileForm.visible)}
)}
); }; render() { const { riskTypeRate = [], currentMediaIndex = 0, hiddenSummary = {}, trainingData = {}, // 新增 props onAnnouncementClick, announcementModalVisible, onAnnouncementModalClose, } = this.props; // 获取播放配置,提供默认值 const playSet = trainingData.playSet || {}; const videoConfig = { autoPlay: playSet.V_ISAUTO !== undefined ? playSet.V_ISAUTO : true, loop: playSet.V_ISRE !== undefined ? playSet.V_ISRE : true, muted: playSet.V_ISSILENT !== undefined ? playSet.V_ISSILENT : true, controls: playSet.V_ISSHOWCONTROL !== undefined ? playSet.V_ISSHOWCONTROL : false, }; const isAutoplay = playSet.IMG_ISRE !== undefined ? playSet.IMG_ISRE : true; const autoplaySpeed = playSet.IMG_TIMESPAN ? playSet.IMG_TIMESPAN * 1000 : 5000; const slickSettings = { dots: false, arrows: false, // 🔑 隐藏默认箭头 ← 添加这一行 infinite: true, speed: 500, slidesToShow: 1, slidesToScroll: 1, autoplay: isAutoplay, autoplaySpeed: autoplaySpeed, fade: true, pauseOnHover: true, afterChange: this.handleCarouselChange, }; return (
{/* 左侧区域 */}
{riskTypeRate.map((item, index) => (
{item.riskType}
{item.count}
))}
年度隐患数据
年度重大隐患数量
{hiddenSummary.majorQty}
年度一般隐患数量
{hiddenSummary.generalQty}
未整改隐患数量
{hiddenSummary.unfinishQty}
{/* 中间区域 */}
{trainingData.listVideoImg && trainingData.listVideoImg[0] ? ( trainingData.listVideoImg[0].TYPE == 10 ? (
公司公告
共 {trainingData.listAnnourcement?.length || 0} 条公告
{trainingData.listAnnourcement?.length > 0 ? (
    {trainingData.listAnnourcement.map((item, index) => (
  • onAnnouncementClick && onAnnouncementClick(item)} > {item.TITLE} {item.START}
  • ))}
) : (
暂无公告
)}
{/* 右侧区域 */}
年度培训数据
{trainingData?.listNAME?.map((name, index) => (
{name}
{trainingData?.YearCount?.[index] || 0}
))} {(!trainingData?.listNAME || trainingData.listNAME.length === 0) && (
暂无培训数据
)}
{/* 公告详情弹窗 */} 关闭 , ]} width="80%" bodyStyle={{ padding: '20px', maxHeight: '70vh', overflowY: 'auto' }} > {this.renderAnnouncementModal()}
); } } export default HomeContent;