危险作业

This commit is contained in:
yunkexin 2026-05-21 17:10:47 +08:00
parent 8dd014e583
commit 147baceeb0
4 changed files with 367 additions and 103 deletions

View File

@ -1,6 +1,7 @@
// DangerJob.js - 危险作业页面组件 // DangerJob.js - 危险作业页面组件
import React from 'react'; import React from 'react';
import { Table, Select, Pagination } from 'antd'; import { Table, Select, Pagination, DatePicker } from 'antd';
import echarts from 'echarts';
import styles from './../fullinter.less'; import styles from './../fullinter.less';
const { Option } = Select; const { Option } = Select;
@ -10,9 +11,15 @@ class DangerJob extends React.Component {
super(props); super(props);
this.state = { this.state = {
currentPage: 1, currentPage: 1,
pageSize: 20, pageSize: 10,
}; };
this.echartsInstances = {
dangerOperationChart: null, // 当日工作票统计柱状图
};
this.chartResizeHandlers = {};
this.isUnmounted = false; this.isUnmounted = false;
// 添加缓存,用于比较数据是否真正变化
this.lastJobTodayQty = null;
} }
// 生成公司选项(与 TrainingContent 保持一致) // 生成公司选项(与 TrainingContent 保持一致)
@ -23,8 +30,6 @@ class DangerJob extends React.Component {
} }
return companyData.map((company, index) => ( return companyData.map((company, index) => (
<Option key={company.ID || index} value={company.ID}> <Option key={company.ID || index} value={company.ID}>
{' '}
{/* 使用 ID 作为 value与 TrainingContent 一致 */}
{company.NAME} {company.NAME}
</Option> </Option>
)); ));
@ -40,14 +45,222 @@ class DangerJob extends React.Component {
}); });
}; };
// 处理开始日期变化
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) => { handlePageChange = (page, pageSize) => {
this.setState({ currentPage: page, pageSize }); this.setState({ currentPage: page, pageSize });
}; };
// 处理每页条数变化 // 等待DOM元素加载完成
handleShowSizeChange = (current, size) => { waitForElement = (elementId, maxRetries = 10) => {
this.setState({ currentPage: 1, pageSize: size }); 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 = {};
}; };
// 获取过滤后的数据(添加类型检查和默认值) // 获取过滤后的数据(添加类型检查和默认值)
@ -88,35 +301,35 @@ class DangerJob extends React.Component {
}; };
}; };
// 添加 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 = () => { renderDangerTable = () => {
const { selectedCompany, companyData } = this.props; const { selectedCompany, companyData } = this.props;
const { currentPage, pageSize } = this.state; const { currentPage, pageSize } = this.state;
const { data: tableData, total } = this.getCurrentPageData(); const { data: tableData, total } = this.getCurrentPageData();
// 如果没有数据,返回空
if (!tableData || tableData.length === 0) { if (!tableData || tableData.length === 0) {
// 获取选中的公司名称用于显示 return <div style={{ textAlign: 'center', padding: '50px', color: '#999' }}>暂无危险作业数据</div>;
let companyName = '';
if (selectedCompany && companyData) {
const selectedCompanyObj = companyData.find((company) => company.ID === selectedCompany);
companyName = selectedCompanyObj?.NAME || '';
}
return (
<div
style={{
textAlign: 'center',
padding: '50px',
color: '#999',
fontSize: '26px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
}}
>
{selectedCompany ? `${companyName}暂无危险作业数据` : '暂无危险作业数据'}
</div>
);
} }
// 表格列配置 // 表格列配置
@ -196,10 +409,8 @@ class DangerJob extends React.Component {
}, },
]; ];
// 计算横向滚动宽度(如果列数过多) // 不设置 y 滚动,让表格自然高度,由外层容器控制滚动
const scrollX = columns.reduce((sum, col) => sum + (col.width || 120), 0); const scrollConfig = { x: columns.length * 100, y: 320 };
const scrollConfig = scrollX > 1200 ? { x: scrollX } : {};
// 表格数据转换添加唯一key // 表格数据转换添加唯一key
const dataSource = tableData.map((item, index) => ({ const dataSource = tableData.map((item, index) => ({
key: `${item.companyName}_${item.startDate}_${index}`, key: `${item.companyName}_${item.startDate}_${index}`,
@ -215,6 +426,7 @@ class DangerJob extends React.Component {
})); }));
return ( return (
<>
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}> <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<div <div
style={{ style={{
@ -227,13 +439,13 @@ class DangerJob extends React.Component {
> >
当日各公司危险作业清单 当日各公司危险作业清单
</div> </div>
<div style={{ flex: 1, padding: '10px' }}> <div style={{ flex: 1, minHeight: 0, padding: '10px' }}>
<Table <Table
columns={columns} columns={columns}
dataSource={dataSource} dataSource={dataSource}
pagination={false} pagination={false}
scroll={scrollConfig} scroll={scrollConfig}
size="small" size="middle"
bordered bordered
className={styles.certificateTable} className={styles.certificateTable}
/> />
@ -247,9 +459,9 @@ class DangerJob extends React.Component {
padding: '12px 16px', padding: '12px 16px',
borderTop: '1px solid #f0f0f0', borderTop: '1px solid #f0f0f0',
backgroundColor: '#fff', backgroundColor: '#fff',
flexShrink: 0,
}} }}
> >
{/* 自定义每页条数选择器 */}
<div> <div>
<span style={{ marginRight: '8px' }}>每页显示</span> <span style={{ marginRight: '8px' }}>每页显示</span>
<Select <Select
@ -267,20 +479,19 @@ class DangerJob extends React.Component {
current={currentPage} current={currentPage}
pageSize={pageSize} pageSize={pageSize}
total={total} total={total}
// showSizeChanger
showQuickJumper showQuickJumper
showTotal={(total) => `${total} 条记录`} showTotal={(total) => `${total} 条记录`}
onChange={this.handlePageChange} onChange={this.handlePageChange}
onShowSizeChange={false}
// pageSizeOptions={['10', '20']}
/> />
</div> </div>
</div> </div>
</>
); );
}; };
componentDidMount() { componentDidMount() {
this.isUnmounted = false; this.isUnmounted = false;
this.initAllCharts();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -288,6 +499,13 @@ class DangerJob extends React.Component {
if (prevProps.selectedCompany !== this.props.selectedCompany) { if (prevProps.selectedCompany !== this.props.selectedCompany) {
this.setState({ currentPage: 1 }); 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) { if (prevProps.dangerSubData !== this.props.dangerSubData) {
const { currentPage } = this.state; const { currentPage } = this.state;
@ -298,21 +516,31 @@ class DangerJob extends React.Component {
this.setState({ currentPage: 1 }); 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() { componentWillUnmount() {
this.isUnmounted = true; this.isUnmounted = true;
this.disposeAllCharts();
} }
render() { render() {
const { companyData, selectedCompany } = this.props; const { companyData, selectedCompany, selectedStartDate, selectedEndDate } = this.props;
return ( return (
<div className={styles.trainingContentWrapper}> <div className={styles.trainingContentWrapper}>
<div className={styles.trainingGrid}> <div className={styles.trainingGrid}>
<div className={styles.trainingRow}> <div className={styles.trainingRow}>
<div className={styles.trainingCard}> <div className={styles.trainingCard}>
{/* 公司筛选器 */} {/* 筛选器 */}
<div className={styles.monthSelectorWrapper}> <div className={styles.monthSelectorWrapper}>
<span className={styles.monthSelectorLabel}>选择公司</span> <span className={styles.monthSelectorLabel}>选择公司</span>
<Select <Select
@ -325,10 +553,24 @@ class DangerJob extends React.Component {
> >
{this.getCompanyOptions()} {this.getCompanyOptions()}
</Select> </Select>
<span className={styles.monthSelectorLabel} style={{ margin: '0 20px' }}>
开始日期
</span>
<DatePicker value={selectedStartDate} format="YYYY-MM-DD" onChange={this.startChange} />
<span className={styles.monthSelectorLabel} style={{ margin: '0 20px' }}>
结束日期
</span>
<DatePicker value={selectedEndDate} format="YYYY-MM-DD" onChange={this.endChange} />
</div> </div>
{this.renderDangerTable()} {this.renderDangerTable()}
</div> </div>
</div> </div>
{/* 第二行 - 当日工作票统计柱状图 */}
<div className={styles.trainingRow}>
<div className={styles.trainingCard}>
<div id="dangerOperationChart" className={styles.trainingChartContainer}></div>
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@ -575,7 +575,7 @@ class HomeContent extends React.Component {
if (this.isUnmounted) return; if (this.isUnmounted) return;
this.riskLevel(); this.riskLevel();
this.safeCheckChart(); this.safeCheckChart();
this.dangerOperation(); // this.dangerOperation();
this.backLog(); this.backLog();
}, 100); }, 100);
}; };
@ -970,15 +970,15 @@ class HomeContent extends React.Component {
> >
<div className={styles.infoSpan}> <div className={styles.infoSpan}>
<span>露天矿</span> <span>露天矿</span>
<span>1</span> <span>--</span>
</div> </div>
<div className={styles.infoSpan}> <div className={styles.infoSpan}>
<span>地下矿</span> <span>地下矿</span>
<span>12</span> <span>--</span>
</div> </div>
<div className={styles.infoSpan}> <div className={styles.infoSpan}>
<span>尾矿库</span> <span>尾矿库</span>
<span>13</span> <span>--</span>
</div> </div>
</div> </div>
)} )}

View File

@ -14,6 +14,7 @@ import RiskControl from './FullOther/RiskControl';
import ClassBuild from './FullOther/ClassBuild'; import ClassBuild from './FullOther/ClassBuild';
import DangerJob from './FullOther/DangerJob'; import DangerJob from './FullOther/DangerJob';
import HiddenSolve from './FullOther/HiddenSolve'; import HiddenSolve from './FullOther/HiddenSolve';
import moment from 'moment'; // 添加这一行
const getScale = () => { const getScale = () => {
const width = 1920, const width = 1920,
@ -30,7 +31,7 @@ class FullScreen extends React.Component {
nowDate: '', nowDate: '',
riskTypeRate: [], riskTypeRate: [],
riskTotal: [], riskTotal: [],
jobTodayQty: [], // jobTodayQty: [],
hiddenSummary: [], hiddenSummary: [],
taskTop3: [], taskTop3: [],
scale: getScale(), scale: getScale(),
@ -77,8 +78,11 @@ class FullScreen extends React.Component {
riskSubData: {}, riskSubData: {},
classSubData: [], classSubData: [],
dangerSubData: {}, dangerSubData: {},
jobTodayQty: [],
dangerCompanyData: [], dangerCompanyData: [],
selectedDangerCompany: '', selectedDangerCompany: '',
selectedStartDate: moment(),
selectedEndDate: moment(),
announcementDetail: null, announcementDetail: null,
announcementDetailLoading: false, announcementDetailLoading: false,
@ -289,7 +293,7 @@ class FullScreen extends React.Component {
if (ret && !this.isUnmounted) { if (ret && !this.isUnmounted) {
this.setState({ this.setState({
riskTypeRate: ret.riskTypeRate || [], riskTypeRate: ret.riskTypeRate || [],
jobTodayQty: ret.jobTodayQty || [], // jobTodayQty: ret.jobTodayQty || [],
hiddenSummary: ret.hiddenSummary || [], hiddenSummary: ret.hiddenSummary || [],
taskTop3: ret.taskTop3 || [], taskTop3: ret.taskTop3 || [],
}); });
@ -589,17 +593,29 @@ class FullScreen extends React.Component {
this.getDangerSubData(); this.getDangerSubData();
}); });
}; };
handleStartDateChange = (startDate) => {
this.setState({ selectedStartDate: startDate }, () => {
this.getDangerSubData();
});
};
handleEndDateChange = (endDate) => {
this.setState({ selectedEndDate: endDate }, () => {
this.getDangerSubData();
});
};
getDangerSubData = () => { getDangerSubData = () => {
const orgId = storage('lacal').getItem('webOrgId')?.val; const orgId = storage('lacal').getItem('webOrgId')?.val;
const json = initFilter(orgId); const json = initFilter(orgId);
json.Parameter1 = this.state.selectedStartDate.format('YYYY-MM-DD');
json.Parameter2 = this.state.selectedEndDate.format('YYYY-MM-DD');
this.props.dispatch({ this.props.dispatch({
type: 'app/getDataByPost', type: 'app/getDataByPost',
payload: json, payload: json,
url: 'BI/BIKanBanController/JobManage', url: 'BI/BIKanBanController/JobManage',
onComplete: (ret) => { onComplete: (ret) => {
if (ret && !this.isUnmounted) { if (ret && !this.isUnmounted) {
let filteredData = ret; let filteredData = ret.jobList;
if (this.state.selectedDangerCompany) { if (this.state.selectedDangerCompany) {
const selectedCompanyObj = this.state.dangerCompanyData?.find( const selectedCompanyObj = this.state.dangerCompanyData?.find(
(company) => company.ID === this.state.selectedDangerCompany (company) => company.ID === this.state.selectedDangerCompany
@ -612,6 +628,7 @@ class FullScreen extends React.Component {
this.setState({ this.setState({
dangerSubData: filteredData, dangerSubData: filteredData,
jobTodayQty: ret.jobTodayQty,
}); });
} }
}, },
@ -670,9 +687,14 @@ class FullScreen extends React.Component {
return ( return (
<DangerJob <DangerJob
dangerSubData={this.state.dangerSubData} dangerSubData={this.state.dangerSubData}
jobTodayQty={this.state.jobTodayQty}
companyData={this.state.dangerCompanyData} companyData={this.state.dangerCompanyData}
selectedCompany={this.state.selectedDangerCompany} selectedCompany={this.state.selectedDangerCompany}
onCompanyChange={this.handleDangerCompanyChange} onCompanyChange={this.handleDangerCompanyChange}
selectedStartDate={this.state.selectedStartDate}
onStartChange={this.handleStartDateChange}
selectedEndDate={this.state.selectedEndDate}
onEndChange={this.handleEndDateChange}
/> />
); );
} }
@ -761,7 +783,7 @@ class FullScreen extends React.Component {
riskTypeRate={riskTypeRate} riskTypeRate={riskTypeRate}
riskTotal={riskTotal} riskTotal={riskTotal}
hiddenSummary={hiddenSummary} hiddenSummary={hiddenSummary}
jobTodayQty={jobTodayQty} // jobTodayQty={jobTodayQty}
taskTop3={taskTop3} taskTop3={taskTop3}
mediaList={mediaList} mediaList={mediaList}
announcementList={announcementList} announcementList={announcementList}

View File

@ -174,7 +174,7 @@
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
color: #000; color: #000;
margin-bottom: 24px; margin-bottom: 10px;
} }
.infoCard { .infoCard {
display: flex; display: flex;