599 lines
18 KiB
JavaScript
599 lines
18 KiB
JavaScript
// HiddenSolve.js - 隐患解决页面组件
|
||
import React from 'react';
|
||
import { Select, Table } from 'antd';
|
||
import styles from './../fullinter.less';
|
||
import echarts from 'echarts';
|
||
|
||
const { Option } = Select;
|
||
|
||
class HiddenSolve extends React.Component {
|
||
constructor(props) {
|
||
super(props);
|
||
this.echartsInstances = {
|
||
hiddenBarChart: null, // 各公司隐患统计柱状图(使用 hiddenList)
|
||
hiddenRectifyChart: null, // 隐患整改情况柱状图(使用 hiddenRectifyList)
|
||
};
|
||
this.chartResizeHandlers = {};
|
||
this.isUnmounted = false;
|
||
// 添加缓存,用于比较数据是否真正变化
|
||
this.lastHiddenList = null;
|
||
this.lastHiddenRectifyList = null;
|
||
}
|
||
|
||
// 获取公司选项(使用 props 传入的 companyData,与 TrainingContent 一致)
|
||
getCompanyOptions = () => {
|
||
const { companyData } = this.props;
|
||
if (!companyData || companyData.length === 0) {
|
||
return [];
|
||
}
|
||
return companyData.map((company, index) => (
|
||
<Option key={company.ID || index} value={company.ID}>
|
||
{company.NAME}
|
||
</Option>
|
||
));
|
||
};
|
||
|
||
// 根据选中的公司ID获取公司名称(用于筛选模拟数据)
|
||
getSelectedCompanyName = () => {
|
||
const { companyData, selectedCompany } = this.props;
|
||
if (!selectedCompany || !companyData || companyData.length === 0) {
|
||
return null;
|
||
}
|
||
const selectedCompanyObj = companyData.find((company) => company.ID === selectedCompany);
|
||
return selectedCompanyObj ? selectedCompanyObj.NAME : null;
|
||
};
|
||
|
||
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);
|
||
});
|
||
};
|
||
|
||
// 图表1: 各公司隐患统计柱状图(使用 hiddenList 数据)
|
||
renderHiddenBarChart = async () => {
|
||
if (this.isUnmounted) return;
|
||
const elementExists = await this.waitForElement('hiddenBarChart');
|
||
if (!elementExists || this.isUnmounted) return;
|
||
|
||
if (this.echartsInstances.hiddenBarChart) {
|
||
this.echartsInstances.hiddenBarChart.dispose();
|
||
this.echartsInstances.hiddenBarChart = null;
|
||
}
|
||
|
||
const chartDom = document.getElementById('hiddenBarChart');
|
||
if (!chartDom) return;
|
||
|
||
this.echartsInstances.hiddenBarChart = echarts.init(chartDom);
|
||
|
||
const { hiddenSubData } = this.props;
|
||
let hiddenList = hiddenSubData?.hiddenList || [];
|
||
|
||
if (hiddenList.length === 0) {
|
||
this.echartsInstances.hiddenBarChart.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;
|
||
}
|
||
|
||
const companyNames = hiddenList.map((item) => item.companyName);
|
||
const majorCounts = hiddenList.map((item) => item.majorCount);
|
||
const generalCounts = hiddenList.map((item) => item.generalCount);
|
||
|
||
const option = {
|
||
title: {
|
||
text: '各公司累计隐患统计数据',
|
||
x: 'center',
|
||
y: '5%',
|
||
textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' },
|
||
},
|
||
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;
|
||
},
|
||
},
|
||
legend: {
|
||
data: ['重大隐患', '一般隐患'],
|
||
right: '3%',
|
||
top: '10%',
|
||
itemGap: 12,
|
||
itemWidth: 16,
|
||
itemHeight: 10,
|
||
textStyle: { color: '#000', fontSize: 12 },
|
||
},
|
||
grid: {
|
||
left: '8%',
|
||
right: '5%',
|
||
top: '28%',
|
||
bottom: '8%',
|
||
containLabel: true,
|
||
},
|
||
xAxis: [
|
||
{
|
||
type: 'category',
|
||
data: companyNames,
|
||
axisLine: { show: false },
|
||
axisTick: { show: false },
|
||
axisLabel: {
|
||
textStyle: { color: '#000' },
|
||
rotate: companyNames.length > 4 ? 15 : 0,
|
||
interval: 0,
|
||
fontSize: 12,
|
||
},
|
||
},
|
||
],
|
||
yAxis: [
|
||
{
|
||
type: 'value',
|
||
show: true,
|
||
axisLine: { show: false },
|
||
axisTick: { show: false },
|
||
axisLabel: {
|
||
show: true,
|
||
textStyle: { color: '#000' },
|
||
},
|
||
splitLine: { show: false },
|
||
name: '隐患数量',
|
||
nameTextStyle: { fontSize: 12 },
|
||
},
|
||
],
|
||
series: [
|
||
{
|
||
name: '重大隐患',
|
||
type: 'bar',
|
||
data: majorCounts,
|
||
itemStyle: {
|
||
normal: {
|
||
color: '#c92a2a', // 红色
|
||
},
|
||
},
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
textStyle: { color: '#c92a2a', fontSize: 12 },
|
||
formatter: (params) => `${params.value}`,
|
||
},
|
||
barWidth: '35%',
|
||
},
|
||
{
|
||
name: '一般隐患',
|
||
type: 'bar',
|
||
data: generalCounts,
|
||
itemStyle: {
|
||
normal: {
|
||
color: '#4285F4', // 蓝色
|
||
},
|
||
},
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
textStyle: { color: '#4285F4', fontSize: 12 },
|
||
formatter: (params) => `${params.value}`,
|
||
},
|
||
barWidth: '35%',
|
||
},
|
||
],
|
||
};
|
||
|
||
this.echartsInstances.hiddenBarChart.setOption(option);
|
||
this.setupResizeHandler('hiddenBarChart', this.renderHiddenBarChart);
|
||
};
|
||
|
||
// 图表2: 隐患整改情况柱状图(使用 hiddenRectifyList 数据)
|
||
renderHiddenRectifyChart = async () => {
|
||
if (this.isUnmounted) return;
|
||
const elementExists = await this.waitForElement('hiddenRectifyChart');
|
||
if (!elementExists || this.isUnmounted) return;
|
||
|
||
if (this.echartsInstances.hiddenRectifyChart) {
|
||
this.echartsInstances.hiddenRectifyChart.dispose();
|
||
this.echartsInstances.hiddenRectifyChart = null;
|
||
}
|
||
|
||
const chartDom = document.getElementById('hiddenRectifyChart');
|
||
if (!chartDom) return;
|
||
|
||
this.echartsInstances.hiddenRectifyChart = echarts.init(chartDom);
|
||
|
||
const { hiddenSubData } = this.props;
|
||
let hiddenRectifyList = hiddenSubData?.hiddenRectifyList || [];
|
||
|
||
if (hiddenRectifyList.length === 0) {
|
||
this.echartsInstances.hiddenRectifyChart.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;
|
||
}
|
||
|
||
const companyNames = hiddenRectifyList.map((item) => item.companyName);
|
||
const majorTotal = hiddenRectifyList.map((item) => item.majorCount);
|
||
const majorRectified = hiddenRectifyList.map((item) => (item.majorCount || 0) - (item.majorCountNo || 0));
|
||
const generalTotal = hiddenRectifyList.map((item) => item.generalCount);
|
||
const generalRectified = hiddenRectifyList.map((item) => (item.generalCount || 0) - (item.generalCountNo || 0));
|
||
|
||
const option = {
|
||
title: {
|
||
text: '当月各公司隐患统计数据',
|
||
x: 'center',
|
||
y: '5%',
|
||
textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' },
|
||
},
|
||
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;
|
||
},
|
||
},
|
||
legend: {
|
||
data: ['重大隐患量', '重大隐患未整改量', '一般隐患量', '一般隐患未整改量'],
|
||
right: '3%',
|
||
top: '10%',
|
||
itemGap: 12,
|
||
itemWidth: 16,
|
||
itemHeight: 10,
|
||
textStyle: { color: '#000', fontSize: 12 },
|
||
},
|
||
grid: {
|
||
left: '8%',
|
||
right: '5%',
|
||
top: '28%',
|
||
bottom: '8%',
|
||
containLabel: true,
|
||
},
|
||
xAxis: [
|
||
{
|
||
type: 'category',
|
||
data: companyNames,
|
||
axisLine: { show: false },
|
||
axisTick: { show: false },
|
||
axisLabel: {
|
||
textStyle: { color: '#000' },
|
||
rotate: companyNames.length > 4 ? 15 : 0,
|
||
interval: 0,
|
||
fontSize: 12,
|
||
},
|
||
},
|
||
],
|
||
yAxis: [
|
||
{
|
||
type: 'value',
|
||
show: true,
|
||
axisLine: { show: false },
|
||
axisTick: { show: false },
|
||
axisLabel: {
|
||
show: true,
|
||
textStyle: { color: '#000' },
|
||
},
|
||
splitLine: { show: false },
|
||
name: '隐患数量',
|
||
nameTextStyle: { fontSize: 12 },
|
||
},
|
||
],
|
||
series: [
|
||
{
|
||
name: '重大隐患量',
|
||
type: 'bar',
|
||
data: majorTotal,
|
||
itemStyle: {
|
||
normal: {
|
||
color: '#c92a2a', // 红色
|
||
},
|
||
},
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
textStyle: { color: '#c92a2a', fontSize: 11 },
|
||
formatter: (params) => `${params.value}`,
|
||
},
|
||
barWidth: '20%',
|
||
},
|
||
{
|
||
name: '重大隐患未整改量',
|
||
type: 'bar',
|
||
data: majorRectified,
|
||
itemStyle: {
|
||
normal: {
|
||
color: '#ffa94d', // 橙色
|
||
},
|
||
},
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
textStyle: { color: '#ffa94d', fontSize: 11 },
|
||
formatter: (params) => `${params.value}`,
|
||
},
|
||
barWidth: '20%',
|
||
},
|
||
{
|
||
name: '一般隐患量',
|
||
type: 'bar',
|
||
data: generalTotal,
|
||
itemStyle: {
|
||
normal: {
|
||
color: '#4285F4', // 蓝色
|
||
},
|
||
},
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
textStyle: { color: '#4285F4', fontSize: 11 },
|
||
formatter: (params) => `${params.value}`,
|
||
},
|
||
barWidth: '20%',
|
||
},
|
||
{
|
||
name: '一般隐患未整改量',
|
||
type: 'bar',
|
||
data: generalRectified,
|
||
itemStyle: {
|
||
normal: {
|
||
color: '#ffe066', // 黄色
|
||
},
|
||
},
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
textStyle: { color: '#d4a000', fontSize: 11 },
|
||
formatter: (params) => `${params.value}`,
|
||
},
|
||
barWidth: '20%',
|
||
},
|
||
],
|
||
};
|
||
|
||
this.echartsInstances.hiddenRectifyChart.setOption(option);
|
||
this.setupResizeHandler('hiddenRectifyChart', this.renderHiddenRectifyChart);
|
||
};
|
||
|
||
// 排名列表组件(支持公司筛选)
|
||
renderRankingList = () => {
|
||
const { hiddenSubData, selectedCompany, onCompanyChange, companyData } = this.props;
|
||
let hiddenRanking = hiddenSubData?.hiddenRanking || [];
|
||
|
||
// 根据选中的公司筛选数据(前端筛选)
|
||
const selectedCompanyName = this.getSelectedCompanyName();
|
||
let filteredRanking = hiddenRanking;
|
||
if (selectedCompanyName) {
|
||
filteredRanking = hiddenRanking.filter((item) => item.companyName === selectedCompanyName);
|
||
}
|
||
|
||
// 如果没选中公司或筛选后没有数据,显示全部或空状态
|
||
if (!selectedCompanyName || filteredRanking.length === 0) {
|
||
filteredRanking = selectedCompanyName ? [] : hiddenRanking;
|
||
}
|
||
|
||
// 表格列配置(与 RiskControl 样式保持一致)
|
||
const columns = [
|
||
{
|
||
title: '序号',
|
||
dataIndex: 'index',
|
||
key: 'index',
|
||
align: 'center',
|
||
width: 80,
|
||
render: (text, record, index) => <span style={{ fontWeight: 'bold' }}>{index + 1}</span>,
|
||
},
|
||
{
|
||
title: '隐患名称',
|
||
dataIndex: 'hiddenName',
|
||
key: 'hiddenName',
|
||
align: 'center',
|
||
// 中间列自适应,不设置固定宽度
|
||
render: (text) => <span style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}>{text}</span>,
|
||
},
|
||
{
|
||
title: '数量',
|
||
dataIndex: 'qty',
|
||
key: 'qty',
|
||
align: 'center',
|
||
width: 80,
|
||
render: (text) => <span style={{ fontWeight: 'bold' }}>{text}</span>,
|
||
},
|
||
];
|
||
|
||
const tableData = filteredRanking.map((item, index) => ({
|
||
key: index,
|
||
index: index + 1,
|
||
hiddenName: item.hiddenName,
|
||
qty: item.qty,
|
||
}));
|
||
const scrollConfig = filteredRanking.length > 5 ? { y: 300 } : {};
|
||
return (
|
||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||
<div
|
||
style={{
|
||
textAlign: 'center',
|
||
padding: '20px 0 0 0',
|
||
fontWeight: 'bold',
|
||
fontSize: '17px',
|
||
color: '#000',
|
||
}}
|
||
>
|
||
隐患次数排名列表
|
||
</div>
|
||
<div style={{ textAlign: 'right', padding: '10px 20px' }}>
|
||
<span style={{ marginRight: '8px', fontSize: '14px', color: '#000' }}>选择公司:</span>
|
||
<Select
|
||
value={selectedCompany || undefined}
|
||
onChange={onCompanyChange}
|
||
style={{ width: 150 }}
|
||
allowClear
|
||
placeholder="全部公司"
|
||
>
|
||
{this.getCompanyOptions()}
|
||
</Select>
|
||
</div>
|
||
<div style={{ flex: 1, padding: '10px' }}>
|
||
<Table
|
||
columns={columns}
|
||
dataSource={tableData}
|
||
pagination={false}
|
||
scroll={scrollConfig}
|
||
size="small"
|
||
bordered={false}
|
||
className={styles.hiddenTable}
|
||
style={{ border: 'none' }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
// 添加 shouldComponentUpdate 优化性能
|
||
shouldComponentUpdate(nextProps, nextState) {
|
||
// 只有当这些 props 变化时才重新渲染
|
||
if (
|
||
this.props.hiddenSubData?.hiddenRanking !== nextProps.hiddenSubData?.hiddenRanking ||
|
||
this.props.selectedCompany !== nextProps.selectedCompany ||
|
||
this.props.companyData !== nextProps.companyData
|
||
) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
setupResizeHandler = (chartName, renderMethod) => {
|
||
const resizeHandler = () => {
|
||
if (this.echartsInstances[chartName] && !this.isUnmounted) {
|
||
this.echartsInstances[chartName].resize();
|
||
}
|
||
};
|
||
this.chartResizeHandlers[chartName] = resizeHandler;
|
||
window.addEventListener('resize', resizeHandler);
|
||
};
|
||
|
||
initAllCharts = () => {
|
||
if (this.isUnmounted) return;
|
||
setTimeout(() => {
|
||
if (this.isUnmounted) return;
|
||
this.renderHiddenBarChart();
|
||
this.renderHiddenRectifyChart();
|
||
}, 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 = {};
|
||
};
|
||
|
||
componentDidMount() {
|
||
this.isUnmounted = false;
|
||
this.initAllCharts();
|
||
}
|
||
|
||
componentDidUpdate(prevProps) {
|
||
const prevHiddenList = prevProps.hiddenSubData?.hiddenList;
|
||
const currentHiddenList = this.props.hiddenSubData?.hiddenList;
|
||
const prevHiddenRectifyList = prevProps.hiddenSubData?.hiddenRectifyList;
|
||
const currentHiddenRectifyList = this.props.hiddenSubData?.hiddenRectifyList;
|
||
|
||
// 检查 hiddenList 是否变化
|
||
const hiddenListChanged = JSON.stringify(prevHiddenList) !== JSON.stringify(currentHiddenList);
|
||
// 检查 hiddenRectifyList 是否变化
|
||
const hiddenRectifyListChanged = JSON.stringify(prevHiddenRectifyList) !== JSON.stringify(currentHiddenRectifyList);
|
||
|
||
// 只在数据真正变化时重新渲染图表
|
||
if (hiddenListChanged) {
|
||
this.renderHiddenBarChart();
|
||
}
|
||
if (hiddenRectifyListChanged) {
|
||
this.renderHiddenRectifyChart();
|
||
}
|
||
}
|
||
|
||
componentWillUnmount() {
|
||
this.isUnmounted = true;
|
||
this.disposeAllCharts();
|
||
}
|
||
|
||
render() {
|
||
const { selectedCompany, onCompanyChange } = this.props;
|
||
|
||
return (
|
||
<div className={styles.trainingContentWrapper}>
|
||
<div className={styles.trainingGrid}>
|
||
{/* 第一行 - 柱状图(left)+ 排名列表(right) */}
|
||
<div className={styles.trainingRow}>
|
||
<div className={styles.trainingCard} style={{ flex: '0 0 70%' }}>
|
||
{/* 添加公司筛选器(左上角,与 TrainingContent 样式一致) */}
|
||
<div id="hiddenBarChart" className={styles.trainingChartContainer}></div>
|
||
</div>
|
||
<div className={styles.trainingCard} style={{ flex: '0 0 30%' }}>
|
||
{this.renderRankingList()}
|
||
</div>
|
||
</div>
|
||
{/* 第二行 - 隐患整改情况柱状图 */}
|
||
<div className={styles.trainingRow}>
|
||
<div className={styles.trainingCard}>
|
||
<div id="hiddenRectifyChart" className={styles.trainingChartContainer}></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default HiddenSolve;
|