2026-04-22 13:47:29 +08:00
|
|
|
|
// HomeContent.js
|
|
|
|
|
|
import React, { useEffect, useRef } from 'react';
|
|
|
|
|
|
import { Row, Col, Icon, Carousel } from 'antd';
|
|
|
|
|
|
import styles from './../fullinter.less';
|
|
|
|
|
|
import echarts from 'echarts';
|
|
|
|
|
|
|
|
|
|
|
|
class HomeContent extends React.Component {
|
|
|
|
|
|
constructor(props) {
|
|
|
|
|
|
super(props);
|
|
|
|
|
|
this.carouselRef = React.createRef();
|
|
|
|
|
|
this.echartsInstances = {
|
|
|
|
|
|
riskLevel: null,
|
|
|
|
|
|
safeCheckChart: null,
|
|
|
|
|
|
dangerOperation: null,
|
|
|
|
|
|
backLogChart: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
this.chartResizeHandlers = {};
|
|
|
|
|
|
this.isUnmounted = 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 `<div style="display: flex; align-items: center;">
|
|
|
|
|
|
<span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${color}; margin-right: 8px;"></span>
|
|
|
|
|
|
<span>${params.name}:</span>
|
|
|
|
|
|
<span style="font-weight: bold; margin-left: 8px; font-size: 16px;">${params.value}</span>
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
},
|
|
|
|
|
|
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: '{a|{b}:{c}}{e|({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);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
transformDat = (originalData, barTopColor, num) => {
|
|
|
|
|
|
if (!originalData || !Array.isArray(originalData) || originalData.length === 0) {
|
|
|
|
|
|
return { companyNames: [], series: [], legendData: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let allTypes = [];
|
|
|
|
|
|
if (num == 1) {
|
|
|
|
|
|
allTypes = [...new Set(originalData.flatMap((item) => item.details?.map((detail) => detail.jobName)))];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
allTypes = [...new Set(originalData.flatMap((item) => item.details?.map((detail) => detail.name)))];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
allTypes = allTypes.filter(Boolean);
|
|
|
|
|
|
const companyNames = originalData?.map((item) => item.company);
|
|
|
|
|
|
const series = allTypes?.map((typeName, index) => ({
|
|
|
|
|
|
name: typeName,
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
normal: {
|
|
|
|
|
|
color: function (params) {
|
|
|
|
|
|
if (num == 1) {
|
|
|
|
|
|
return barTopColor[index];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
|
{ offset: 0, color: barTopColor[index][0] },
|
|
|
|
|
|
{ offset: 1, color: barTopColor[index][1] },
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
data: originalData?.map((company) => {
|
|
|
|
|
|
const detail = company.details?.find((d) => (num == 1 ? d.jobName : d.name) === typeName);
|
|
|
|
|
|
return detail ? detail.qty : 0;
|
|
|
|
|
|
}),
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
return { companyNames, series, legendData: allTypes };
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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 barTopColor = ['#02c3f1', '#53e568', '#a154e9'];
|
|
|
|
|
|
const jobData = this.props.jobTodayTop3 || [];
|
|
|
|
|
|
|
|
|
|
|
|
if (jobData.length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.echartsInstances.safeCheckChart = echarts.init(safeCheckCharts);
|
|
|
|
|
|
let xdata = this.transformDat(jobData, barTopColor, 1);
|
|
|
|
|
|
|
|
|
|
|
|
this.echartsInstances.safeCheckChart.setOption({
|
|
|
|
|
|
title: {
|
|
|
|
|
|
text: '当日工作票排名前三家的数据',
|
|
|
|
|
|
textStyle: { fontSize: 16, color: '#fff' },
|
|
|
|
|
|
},
|
|
|
|
|
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
data: xdata.legendData,
|
|
|
|
|
|
align: 'right',
|
|
|
|
|
|
right: 10,
|
|
|
|
|
|
itemGap: 16,
|
|
|
|
|
|
itemWidth: 18,
|
|
|
|
|
|
itemHeight: 10,
|
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 14 },
|
|
|
|
|
|
},
|
|
|
|
|
|
color: barTopColor,
|
|
|
|
|
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
|
|
|
|
|
xAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: xdata.companyNames,
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#3eb2e8' } },
|
|
|
|
|
|
axisLabel: { textStyle: { color: '#fff' } },
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
yAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
axisLine: { show: false },
|
|
|
|
|
|
axisLabel: { textStyle: { color: '#fff' } },
|
|
|
|
|
|
splitLine: { lineStyle: { color: '#4784e8' } },
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
series: xdata.series,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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.linkSum || [];
|
|
|
|
|
|
if (linkData.length === 0) 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 `<div style="display: flex; align-items: center;">
|
|
|
|
|
|
<span style="display: inline-block; width: 12px; height: 12px; background-color: ${color}; margin-right: 8px;"></span>
|
|
|
|
|
|
<span>${params[0].name}:</span>
|
|
|
|
|
|
<span style="font-weight: bold; margin-left: 8px; font-size: 16px;">${params[0].value}</span>
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
},
|
|
|
|
|
|
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: '#000' } },
|
|
|
|
|
|
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: {
|
2026-04-22 16:14:55 +08:00
|
|
|
|
normal: { color: '#4285F4', barBorderRadius: 12 },
|
2026-04-22 13:47:29 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-04-22 16:14:55 +08:00
|
|
|
|
// 使用培训数据
|
|
|
|
|
|
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',
|
2026-04-23 11:29:34 +08:00
|
|
|
|
y: '25%',
|
2026-04-22 16:14:55 +08:00
|
|
|
|
textStyle: { fontSize: 16, color: '#999' },
|
|
|
|
|
|
},
|
|
|
|
|
|
graphic: {
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
left: 'center',
|
|
|
|
|
|
top: 'middle',
|
|
|
|
|
|
style: {
|
2026-04-23 11:29:34 +08:00
|
|
|
|
text: '暂无数据',
|
2026-04-22 16:14:55 +08:00
|
|
|
|
fill: '#999',
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-22 13:47:29 +08:00
|
|
|
|
|
|
|
|
|
|
this.echartsInstances.backLogChart = echarts.init(backLogCharts);
|
|
|
|
|
|
|
2026-04-22 16:14:55 +08:00
|
|
|
|
const option = {
|
2026-04-22 13:47:29 +08:00
|
|
|
|
title: {
|
2026-04-22 16:14:55 +08:00
|
|
|
|
text: '当月工作培训统计数量',
|
2026-04-22 13:47:29 +08:00
|
|
|
|
x: 'center',
|
|
|
|
|
|
y: '5%',
|
|
|
|
|
|
textStyle: { fontSize: 16, color: '#000' },
|
|
|
|
|
|
},
|
2026-04-22 16:14:55 +08:00
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
axisPointer: { type: 'shadow' },
|
|
|
|
|
|
formatter: function (params) {
|
|
|
|
|
|
let result = `${params[0].axisValue}<br/>`;
|
|
|
|
|
|
params.forEach((param) => {
|
|
|
|
|
|
result += `${param.marker}${param.seriesName}: ${param.value}<br/>`;
|
|
|
|
|
|
});
|
|
|
|
|
|
return result;
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2026-04-22 13:47:29 +08:00
|
|
|
|
legend: {
|
2026-04-22 16:14:55 +08:00
|
|
|
|
data: ['培训人次', '培训场次'],
|
|
|
|
|
|
orient: 'vertical', // 垂直排列
|
|
|
|
|
|
right: 0, // 水平居中
|
|
|
|
|
|
top: 'middle', // 垂直居中
|
|
|
|
|
|
itemGap: 16, // 图例项间隔
|
2026-04-22 13:47:29 +08:00
|
|
|
|
itemWidth: 18,
|
|
|
|
|
|
itemHeight: 12,
|
2026-04-22 16:14:55 +08:00
|
|
|
|
textStyle: { color: '#000', fontSize: 14 },
|
|
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
left: '5%',
|
|
|
|
|
|
right: '5%', // 为右侧垂直图例留出空间
|
|
|
|
|
|
top: '18%',
|
|
|
|
|
|
bottom: '5%',
|
|
|
|
|
|
containLabel: true,
|
2026-04-22 13:47:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
xAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'category',
|
2026-04-22 16:14:55 +08:00
|
|
|
|
data: listNAME,
|
|
|
|
|
|
axisLine: { show: false }, // 隐藏x轴线
|
|
|
|
|
|
axisTick: { show: false }, // 隐藏x轴刻度线
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
textStyle: { color: '#000' },
|
|
|
|
|
|
rotate: listNAME.length > 4 ? 15 : 0,
|
|
|
|
|
|
interval: 0,
|
|
|
|
|
|
},
|
2026-04-22 13:47:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
yAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
2026-04-22 16:14:55 +08:00
|
|
|
|
show: true, // 显示y轴
|
|
|
|
|
|
axisLine: { show: false }, // 隐藏y轴线
|
|
|
|
|
|
axisTick: { show: false }, // 隐藏y轴刻度线
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
show: true, // 显示数值标签
|
|
|
|
|
|
textStyle: { color: '#000' },
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: { show: false }, // 隐藏横向网格线
|
|
|
|
|
|
name: '', // 不显示单位名称
|
|
|
|
|
|
nameTextStyle: { show: false }, // 隐藏单位文字
|
2026-04-22 13:47:29 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-04-22 16:14:55 +08:00
|
|
|
|
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);
|
2026-04-22 13:47:29 +08:00
|
|
|
|
|
|
|
|
|
|
const resizeHandler = () => {
|
|
|
|
|
|
if (this.echartsInstances.backLogChart && !this.isUnmounted) {
|
|
|
|
|
|
this.echartsInstances.backLogChart.resize();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
this.chartResizeHandlers.backLogChart = resizeHandler;
|
|
|
|
|
|
window.addEventListener('resize', resizeHandler);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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.carouselRef.current) {
|
|
|
|
|
|
this.carouselRef.current.prev();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
handleNext = () => {
|
|
|
|
|
|
if (this.carouselRef.current) {
|
|
|
|
|
|
this.carouselRef.current.next();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
handleCarouselChange = (current) => {
|
|
|
|
|
|
this.props.onCarouselChange?.(current);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
handleDotClick = (index) => {
|
|
|
|
|
|
this.props.onDotClick?.(index);
|
|
|
|
|
|
if (this.carouselRef.current) {
|
|
|
|
|
|
this.carouselRef.current.goTo(index);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
|
this.isUnmounted = false;
|
|
|
|
|
|
this.initAllCharts();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps) {
|
|
|
|
|
|
if (
|
|
|
|
|
|
prevProps.riskTypeRate !== this.props.riskTypeRate ||
|
|
|
|
|
|
prevProps.jobTodayTop3 !== this.props.jobTodayTop3 ||
|
|
|
|
|
|
prevProps.linkSum !== this.props.linkSum ||
|
2026-04-22 16:14:55 +08:00
|
|
|
|
prevProps.taskTop3 !== this.props.taskTop3 ||
|
|
|
|
|
|
prevProps.trainingData !== this.props.trainingData // 新增
|
2026-04-22 13:47:29 +08:00
|
|
|
|
) {
|
|
|
|
|
|
this.disposeAllCharts();
|
|
|
|
|
|
this.initAllCharts();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
|
|
this.isUnmounted = true;
|
|
|
|
|
|
this.disposeAllCharts();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
|
const {
|
|
|
|
|
|
riskTypeRate = [],
|
|
|
|
|
|
riskTotal = [],
|
|
|
|
|
|
mediaList = [],
|
|
|
|
|
|
announcementList = [],
|
|
|
|
|
|
currentMediaIndex = 0,
|
|
|
|
|
|
} = this.props;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className={styles.homeContentWrapper}>
|
|
|
|
|
|
<Row className={styles.homeContentRow} gutter={0}>
|
|
|
|
|
|
{/* 左侧区域 */}
|
|
|
|
|
|
<Col span={7} className={styles.leftCol}>
|
|
|
|
|
|
<div className={styles.riskCard}>
|
|
|
|
|
|
<Row className={styles.riskRow}>
|
|
|
|
|
|
<Col span={8} className={styles.riskStatsCol}>
|
|
|
|
|
|
{riskTypeRate.map((item, index) => (
|
|
|
|
|
|
<div key={index} className={`${styles.riskStatItem} ${styles['riskLevel' + item.riskType]}`}>
|
|
|
|
|
|
<div className={styles.riskStatName}>{item.riskType}</div>
|
|
|
|
|
|
<div className={styles.riskStatValue}>{item.count}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={16} className={styles.chartCol}>
|
|
|
|
|
|
<div id="riskLevelFull" className={styles.chartContainer}></div>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className={styles.spacer}></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className={styles.hiddenCard}>
|
|
|
|
|
|
<Row className={styles.riskRow}>
|
|
|
|
|
|
<Col span={8} className={styles.hiddenStatsCol}>
|
|
|
|
|
|
<div className={styles.hiddenTitle}>年度隐患数据</div>
|
|
|
|
|
|
{riskTotal.map((item, index) => (
|
|
|
|
|
|
<div key={index} className={styles.hiddenStatItem}>
|
|
|
|
|
|
<div className={styles.hiddenStatName}>{item.name}</div>
|
|
|
|
|
|
<div className={styles.hiddenStatValue}>{item.value}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={16} className={styles.chartCol}>
|
|
|
|
|
|
<div id="safeCheckChart" className={styles.chartContainer}></div>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 中间区域 */}
|
|
|
|
|
|
<Col span={10} className={styles.middleCol}>
|
|
|
|
|
|
<div className={styles.sloganCard}>
|
|
|
|
|
|
<div className={styles.sloganText}>安全方针:以人为本、关注健康、依法治企、安全发展。</div>
|
|
|
|
|
|
<div className={styles.sloganSubText}>安全理念:一切风险皆可控,一切事故皆可防!</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className={styles.carouselCard}>
|
|
|
|
|
|
<div className={styles.carouselWrapper}>
|
|
|
|
|
|
<Carousel
|
|
|
|
|
|
ref={this.carouselRef}
|
|
|
|
|
|
autoplay
|
|
|
|
|
|
autoplaySpeed={5000}
|
|
|
|
|
|
effect="fade"
|
|
|
|
|
|
dots={false}
|
|
|
|
|
|
pauseOnHover={true}
|
|
|
|
|
|
afterChange={this.handleCarouselChange}
|
|
|
|
|
|
className={styles.carousel}
|
|
|
|
|
|
>
|
|
|
|
|
|
{mediaList.map((item, index) => (
|
|
|
|
|
|
<div key={index} className={styles.carouselItem}>
|
|
|
|
|
|
{item.type === 'video' ? (
|
|
|
|
|
|
<video src={item.url} className={styles.mediaVideo} autoPlay muted loop playsInline />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<img src={item.url} alt={`轮播图 ${index + 1}`} className={styles.mediaImage} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Carousel>
|
|
|
|
|
|
<div className={styles.customArrowLeft} onClick={this.handlePrev}>
|
|
|
|
|
|
<Icon type="left" style={{ fontSize: '24px', color: 'rgba(0, 0, 0, 0.5)' }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className={styles.customArrowRight} onClick={this.handleNext}>
|
|
|
|
|
|
<Icon type="right" style={{ fontSize: '24px', color: 'rgba(0, 0, 0, 0.5)' }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className={styles.customDots}>
|
|
|
|
|
|
{mediaList.map((_, index) => (
|
|
|
|
|
|
<span
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
className={`${styles.dot} ${currentMediaIndex === index ? styles.activeDot : ''}`}
|
|
|
|
|
|
onClick={() => this.handleDotClick(index)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className={styles.announcementCard}>
|
|
|
|
|
|
<div className={styles.announcementHeader}>
|
|
|
|
|
|
<div className={styles.announcementTitle}>
|
|
|
|
|
|
<Icon type="sound" className={styles.announcementIcon} />
|
|
|
|
|
|
<span>公司公告</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className={styles.announcementCount}>共 {announcementList.length} 条公告</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className={styles.announcementList}>
|
|
|
|
|
|
{announcementList.length > 0 ? (
|
|
|
|
|
|
<ul className={styles.announcementUl}>
|
|
|
|
|
|
{announcementList.map((item, index) => (
|
|
|
|
|
|
<li
|
|
|
|
|
|
key={item.id || index}
|
|
|
|
|
|
className={styles.announcementItem}
|
|
|
|
|
|
onClick={() => item.url && window.open(item.url)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className={styles.announcementItemTitle} title={item.title}>
|
|
|
|
|
|
{item.title}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className={styles.announcementItemTime}>
|
|
|
|
|
|
{item.publishTime || item.createTime || item.date}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className={styles.emptyAnnouncement}>
|
|
|
|
|
|
<Icon type="inbox" className={styles.emptyIcon} />
|
|
|
|
|
|
<span>暂无公告</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 右侧区域 */}
|
|
|
|
|
|
<Col span={7} className={styles.rightCol}>
|
|
|
|
|
|
<div className={styles.riskCard}>
|
|
|
|
|
|
<Row className={styles.riskRow}>
|
|
|
|
|
|
<Col span={8} className={styles.trainingStatsCol}>
|
|
|
|
|
|
<div className={styles.trainingTitle}>年度培训数据</div>
|
2026-04-22 16:14:55 +08:00
|
|
|
|
{this.props.trainingData?.listNAME?.map((name, index) => (
|
2026-04-22 13:47:29 +08:00
|
|
|
|
<div key={index} className={styles.trainingStatItem}>
|
2026-04-22 16:14:55 +08:00
|
|
|
|
<div className={styles.trainingStatName}>{name}</div>
|
|
|
|
|
|
<div className={styles.trainingStatValue}>{this.props.trainingData?.YearCount?.[index] || 0}</div>
|
2026-04-22 13:47:29 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
2026-04-22 16:14:55 +08:00
|
|
|
|
{/* 如果 listNAME 为空,显示默认提示 */}
|
|
|
|
|
|
{(!this.props.trainingData?.listNAME || this.props.trainingData.listNAME.length === 0) && (
|
|
|
|
|
|
<div style={{ textAlign: 'center', color: '#999', padding: '20px 0' }}>暂无培训数据</div>
|
|
|
|
|
|
)}
|
2026-04-22 13:47:29 +08:00
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={16} className={styles.chartCol}>
|
|
|
|
|
|
<div id="backLogChart" className={styles.chartContainer}></div>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className={styles.spacer}></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className={styles.dangerCard}>
|
|
|
|
|
|
<div id="dangerOperationChart" className={styles.fullChartContainer}></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default HomeContent;
|