mh_jy_safe_web/src/layout/FullOther/DangerJob.js

613 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// DangerJob.js - 危险作业页面组件
import React from 'react';
import { Table, Select, Pagination, DatePicker } from 'antd';
import echarts from 'echarts';
import styles from './../fullinter.less';
const { Option } = Select;
class DangerJob extends React.Component {
constructor(props) {
super(props);
this.state = {
currentPage: 1,
pageSize: 10,
};
this.echartsInstances = {
dangerOperationChart: null, // 当日工作票统计柱状图
};
this.chartResizeHandlers = {};
this.isUnmounted = false;
// 添加缓存,用于比较数据是否真正变化
this.lastJobTodayQty = null;
}
// 生成公司选项(与 TrainingContent 保持一致)
getCompanyOptions = () => {
const { companyData } = this.props;
if (!companyData || companyData.length === 0) {
return <Option value="">暂无公司</Option>;
}
return companyData.map((company, index) => (
<Option key={company.ID || index} value={company.ID}>
{company.NAME}
</Option>
));
};
// 处理公司筛选变化
handleCompanyChange = (value) => {
const { onCompanyChange } = this.props;
this.setState({ currentPage: 1 }, () => {
if (onCompanyChange) {
onCompanyChange(value);
}
});
};
// 处理开始日期变化
startChange = (value) => {
const { onStartChange } = this.props;
this.setState({ currentPage: 1 }, () => {
if (onStartChange) {
onStartChange(value);
}
});
};
// 处理结束日期变化
endChange = (value) => {
const { onEndChange } = this.props;
this.setState({ currentPage: 1 }, () => {
if (onEndChange) {
onEndChange(value);
}
});
};
// 处理页码变化
handlePageChange = (page, pageSize) => {
this.setState({ currentPage: page, pageSize });
};
// 等待DOM元素加载完成
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);
});
};
// 设置resize监听
setupResizeHandler = (chartName, renderMethod) => {
const resizeHandler = () => {
if (this.echartsInstances[chartName] && !this.isUnmounted) {
this.echartsInstances[chartName].resize();
}
};
this.chartResizeHandlers[chartName] = resizeHandler;
window.addEventListener('resize', resizeHandler);
};
// 图表: 当日工作票统计柱状图
renderDangerOperationChart = async () => {
if (this.isUnmounted) return;
const elementExists = await this.waitForElement('dangerOperationChart');
if (!elementExists || this.isUnmounted) return;
if (this.echartsInstances.dangerOperationChart) {
this.echartsInstances.dangerOperationChart.dispose();
this.echartsInstances.dangerOperationChart = null;
}
const chartDom = document.getElementById('dangerOperationChart');
if (!chartDom) return;
this.echartsInstances.dangerOperationChart = echarts.init(chartDom);
const { jobTodayQty } = this.props;
let linkData = jobTodayQty || [];
// 如果没有数据,显示“暂无数据”提示
if (linkData.length === 0) {
this.echartsInstances.dangerOperationChart.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 xAxisData = linkData.map((item) => item.name);
const seriesData = linkData.map((item) => item.qty);
const option = {
title: {
text: '当日工作票的统计数量',
x: 'center',
y: '5%',
textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' },
},
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.9)',
borderColor: '#4285F4',
borderWidth: 1,
textStyle: { color: '#000', fontSize: 14 },
},
grid: {
left: '8%',
right: '5%',
top: '18%',
bottom: '8%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: xAxisData,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
textStyle: { color: '#000' },
rotate: xAxisData.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: seriesData,
itemStyle: {
normal: {
color: '#4285F4',
borderRadius: [12, 12, 0, 0],
},
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.3)',
},
},
label: {
show: true,
position: 'top',
textStyle: { color: '#4285F4', fontSize: 12, fontWeight: 'bold' },
formatter: (params) => `${params.value}`,
},
barWidth: '50%',
barMaxWidth: 60,
},
],
};
this.echartsInstances.dangerOperationChart.setOption(option);
this.setupResizeHandler('dangerOperationChart', this.renderDangerOperationChart);
};
// 初始化所有图表
initAllCharts = () => {
if (this.isUnmounted) return;
setTimeout(() => {
if (this.isUnmounted) return;
this.renderDangerOperationChart();
}, 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 = {};
};
// 获取过滤后的数据(添加类型检查和默认值)
getFilteredData = () => {
const { dangerSubData, selectedCompany, companyData } = this.props;
// 确保 dangerSubData 是数组
let dataArray = Array.isArray(dangerSubData) ? dangerSubData : [];
if (dataArray.length === 0) {
return [];
}
// 根据选中的公司进行过滤(使用 ID 进行匹配)
if (selectedCompany) {
// 查找选中的公司名称
const selectedCompanyObj = companyData?.find((company) => company.ID === selectedCompany);
const selectedCompanyName = selectedCompanyObj?.NAME;
if (selectedCompanyName) {
return dataArray.filter((item) => item.companyName === selectedCompanyName);
}
}
return dataArray;
};
// 获取当前页数据
getCurrentPageData = () => {
const { currentPage, pageSize } = this.state;
const filteredData = this.getFilteredData();
// 确保 filteredData 是数组
const dataArray = Array.isArray(filteredData) ? filteredData : [];
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return {
data: dataArray.slice(startIndex, endIndex),
total: dataArray.length,
};
};
// 添加 shouldComponentUpdate 优化性能
shouldComponentUpdate(nextProps, nextState) {
// 检查状态变化
if (this.state.currentPage !== nextState.currentPage || this.state.pageSize !== nextState.pageSize) {
return true;
}
// 检查 props 变化
if (
this.props.jobTodayQty !== nextProps.jobTodayQty ||
this.props.dangerSubData !== nextProps.dangerSubData ||
this.props.selectedCompany !== nextProps.selectedCompany ||
this.props.selectedStartDate !== nextProps.selectedStartDate ||
this.props.selectedEndDate !== nextProps.selectedEndDate ||
this.props.companyData !== nextProps.companyData
) {
return true;
}
return false;
}
// 渲染危险作业表格
renderDangerTable = () => {
const { selectedCompany, companyData } = this.props;
const { currentPage, pageSize } = this.state;
const { data: tableData, total } = this.getCurrentPageData();
// 如果没有数据,返回空
if (!tableData || tableData.length === 0) {
return (
<div
style={{
textAlign: 'center',
padding: '50px',
color: '#999',
fontSize: '20px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flex: 1,
}}
>
暂无危险作业数据
</div>
);
}
// 表格列配置
const columns = [
{
title: '公司',
dataIndex: 'companyName',
key: 'companyName',
align: 'center',
width: 120,
fixed: 'left',
render: (text) => <strong>{text}</strong>,
},
{
title: '开始时间',
dataIndex: 'startDate',
key: 'startDate',
align: 'center',
width: 160,
render: (text) => <span>{text || '-'}</span>,
},
{
title: '结束时间',
dataIndex: 'endDate',
key: 'endDate',
align: 'center',
width: 160,
render: (text) => <span>{text || '-'}</span>,
},
{
title: '作业名称',
dataIndex: 'jobName',
key: 'jobName',
align: 'center',
minWidth: 120,
render: (text) => <span style={{ fontWeight: 'bold' }}>{text || '-'}</span>,
},
{
title: '作业区域',
dataIndex: 'areaName',
key: 'areaName',
align: 'center',
minWidth: 120,
render: (text) => <span>{text || '-'}</span>,
},
{
title: '作业地点',
dataIndex: 'place',
key: 'place',
align: 'center',
minWidth: 150,
render: (text) => <span>{text || '-'}</span>,
},
{
title: '作业人员',
dataIndex: 'users',
key: 'users',
align: 'center',
minWidth: 150,
render: (text) => <span>{text || '-'}</span>,
},
{
title: '监护人',
dataIndex: 'monitor',
key: 'monitor',
align: 'center',
width: 120,
render: (text) => <span>{text || '-'}</span>,
},
{
title: '审批领导',
dataIndex: 'approveUsers',
key: 'approveUsers',
align: 'center',
minWidth: 150,
render: (text) => <span>{text || '-'}</span>,
},
];
// 不设置 y 滚动,让表格自然高度,由外层容器控制滚动
const scrollConfig = { x: columns.length * 100, y: 320 };
// 表格数据转换添加唯一key
const dataSource = tableData.map((item, index) => ({
key: `${item.companyName}_${item.startDate}_${index}`,
companyName: item.companyName,
startDate: item.startDate,
endDate: item.endDate,
jobName: item.jobName,
areaName: item.areaName,
place: item.place,
users: item.users,
monitor: item.monitor,
approveUsers: item.approveUsers,
}));
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={{ flex: 1, minHeight: 0, padding: '10px' }}>
<Table
columns={columns}
dataSource={dataSource}
pagination={false}
scroll={scrollConfig}
size="middle"
bordered
className={styles.certificateTable}
/>
</div>
{/* 分页组件 */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '12px 16px',
borderTop: '1px solid #f0f0f0',
backgroundColor: '#fff',
flexShrink: 0,
}}
>
<div>
<span style={{ marginRight: '8px' }}>每页显示</span>
<Select
value={pageSize}
onChange={(size) => {
this.setState({ currentPage: 1, pageSize: size });
}}
style={{ width: 80 }}
>
<Option value={10}>10</Option>
<Option value={20}>20</Option>
</Select>
</div>
<Pagination
current={currentPage}
pageSize={pageSize}
total={total}
showQuickJumper
showTotal={(total) => `${total} 条记录`}
onChange={this.handlePageChange}
/>
</div>
</div>
</>
);
};
componentDidMount() {
this.isUnmounted = false;
this.initAllCharts();
}
componentDidUpdate(prevProps) {
// 当筛选条件变化时,重置到第一页
if (prevProps.selectedCompany !== this.props.selectedCompany) {
this.setState({ currentPage: 1 });
}
if (prevProps.selectedStartDate !== this.props.selectedStartDate) {
this.setState({ currentPage: 1 });
}
if (prevProps.selectedEndDate !== this.props.selectedEndDate) {
this.setState({ currentPage: 1 });
}
// 当数据变化时,如果当前页没有数据且不是第一页,重置到第一页
if (prevProps.dangerSubData !== this.props.dangerSubData) {
const { currentPage } = this.state;
const filteredData = this.getFilteredData();
const dataArray = Array.isArray(filteredData) ? filteredData : [];
const maxPage = Math.ceil(dataArray.length / this.state.pageSize) || 1;
if (currentPage > maxPage) {
this.setState({ currentPage: 1 });
}
}
// 检查 jobTodayQty 是否变化,只在数据真正变化时重新渲染图表
const prevJobTodayQty = prevProps.jobTodayQty;
const currentJobTodayQty = this.props.jobTodayQty;
const jobTodayQtyChanged = JSON.stringify(prevJobTodayQty) !== JSON.stringify(currentJobTodayQty);
if (jobTodayQtyChanged) {
this.renderDangerOperationChart();
}
}
componentWillUnmount() {
this.isUnmounted = true;
this.disposeAllCharts();
}
disabledCurrentYearDate = (current) => {
if (!current) return false;
const currentYear = new Date().getFullYear();
const selectedYear = current.year();
// 只能选择当前年份
return selectedYear !== currentYear;
};
render() {
const { companyData, selectedCompany, selectedStartDate, selectedEndDate } = this.props;
return (
<div className={styles.trainingContentWrapper}>
<div className={styles.trainingGrid}>
<div className={styles.trainingRow}>
<div className={styles.trainingCard}>
{/* 筛选器 */}
<div className={styles.monthSelectorWrapper}>
<span className={styles.monthSelectorLabel}>选择公司</span>
<Select
value={selectedCompany}
onChange={this.handleCompanyChange}
style={{ width: 150 }}
className={styles.monthSelect}
allowClear
placeholder="全部公司"
>
{this.getCompanyOptions()}
</Select>
<span className={styles.monthSelectorLabel} style={{ margin: '0 20px' }}>
开始日期
</span>
<DatePicker
value={selectedStartDate}
format="YYYY-MM-DD"
onChange={this.startChange}
disabledDate={this.disabledCurrentYearDate}
/>
<span className={styles.monthSelectorLabel} style={{ margin: '0 20px' }}>
结束日期
</span>
<DatePicker
value={selectedEndDate}
format="YYYY-MM-DD"
onChange={this.endChange}
disabledDate={this.disabledCurrentYearDate}
/>
</div>
{this.renderDangerTable()}
</div>
</div>
{/* 第二行 - 当日工作票统计柱状图 */}
<div className={styles.trainingRow}>
<div className={styles.trainingCard}>
<div id="dangerOperationChart" className={styles.trainingChartContainer}></div>
</div>
</div>
</div>
</div>
);
}
}
export default DangerJob;