dataTool/src/components/ProjectWarningView.js

304 lines
15 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.

import React from 'react';
import {Spin} from 'antd';
const ProjectWarningView = ({projects = [], projectsDataRange = [], loading = false, onClick}) => {
const warningFields = [
{key: 'new_team', label: '新班组', message: '存在新班组,请做好班组入场管理。'},
{key: 'new_homework_content', label: '新的作业内容', message: '存在新的作业内容,请加强现场管控。'},
{
key: 'change_homework_method',
label: '改变作业方法',
message: '存在改变作业方法,请及时核查施工方案编审、方案交底及人员、机具准备情况。'
},
{
key: 'changes_geographical',
label: '地理环境的变化',
message: '存在作业环境的变化,请及时核查施工方案编审、方案交底及人员、机具准备情况。'
},
{key: 'changes_meteorological', label: '气象环境的变化', message: '存在气象预警,请关注天气变化,做好应对措施。'},
{key: 'changes_social', label: '社会环境的变化', message: '存在社会环境变化,请合理安排作业计划,避免人员失控。'},
{
key: 'changes_management',
label: '管理要求的变化',
message: '存在管理要求的变化,请加强现场巡查力度,严防无计划作业。'
},
{key: 'changes_homework_plan', label: '作业计划的变化', message: '存在作业计划的变化,请做好施工力量配备。'},
{key: 'changes_management_personnel', label: '管理人员的变化', message: '存在管理人员的变化,请加强现场管控。'},
];
const hasWarnings = (item) => {
if (typeof item !== 'object') return false;
if (item.new_members || item.new_high_altitude || item.new_hired_general) return true;
return warningFields.some(({key}) => item[key]);
};
const mismatchWarning = (item) => {
if (typeof item !== 'object') return false;
if (item.current_status !== '在施') return false;
const progressText = item.current_progress || '';
const planText = item.next_week_plan || '';
// 识别当前进度中的“完成数/总数”结构例如325.5/620
const extractRates = (text) => {
const matches = [...text.matchAll(/(\d+(\.\d+)?)\s*\/\s*(\d+(\.\d+)?)/g)];
return matches.map(match => {
const done = parseFloat(match[1]);
const total = parseFloat(match[3]);
if (!isNaN(done) && !isNaN(total) && total > 0) {
return done / total;
}
return null;
}).filter(r => r !== null);
};
const progressRates = extractRates(progressText);
const hasLowProgress = !progressRates.some(rate => rate > 0.7 && rate != null);
// 如果下周计划中包含关键作业,就说明任务已经安排了
const criticalTasks = [
'组塔', '导线展放'
];
const hasHeavyNextPlan = criticalTasks.some(task => planText.includes(task));
// 核心判断逻辑
return hasLowProgress && hasHeavyNextPlan;
};
// 转成以项目类型为key的对象方便查找
const rangeMap = projectsDataRange.reduce((acc, item) => {
acc[item.project_type] = {min: item.p5, max: item.p95};
return acc;
}, {});
function getRangeByProjectName(projectName) {
if (typeof projectName !== 'string') return null;
if (projectName.includes('变电工程')) {
return rangeMap['变电工程'];
} else if (projectName.includes('线路工程')) {
return rangeMap['线路工程'];
} else {
return null;
}
}
const projectsRangeWarning = (item) => {
if (typeof item !== 'object') return false;
if (item.current_status !== '在施') return false;
const range = getRangeByProjectName(item.major_project_name);
if (!range) return false; // 未知工程类型,不判断
if (item.participants_count == 0) return false;
return !(item.participants_count > range.min && item.participants_count < range.max);
}
const productionPlanScaleWarning = (item) => {
if (typeof item !== 'object') return false;
if (item.current_status !== '在施') return false;
if (!item.planned_completion_time) return false;
const completionDate = new Date(item.planned_completion_time);
const now = new Date();
const daysToCompletion = (completionDate - now) / (1000 * 60 * 60 * 24);
if (daysToCompletion < 0 || daysToCompletion > 30) return false;
// 仅判断“导线展放”类型
const scale = item.project_scale || '';
if (!scale.includes('导线')) return false;
// 判断进度是否 < 80%
const progressStr = item.current_progress || '';
const progressMatch = progressStr.match(/(\d+(\.\d+)?)%/);
if (!progressMatch) return false;
const progress = parseFloat(progressMatch[1]);
return progress < 80;
};
const filteredProjects = projects.filter(item =>
hasWarnings(item) ||
mismatchWarning(item) ||
projectsRangeWarning(item) ||
productionPlanScaleWarning(item)
);
return (
<div
className="project-warning-view"
>
<div
style={{
padding: '16px 20px',
fontWeight: '700',
fontSize: '18px',
userSelect: 'none',
borderBottom: '1px solid var(--vscode-sidebar-border)'
}}
>
工程预警
</div>
<div
style={{
padding: '20px',
overflowY: 'auto',
flexGrow: 1,
maxHeight: '90%',
scrollbarWidth: 'thin',
scrollbarColor: '#c1c1c1 transparent',
}}
>
<Spin spinning={loading} tip="加载中...">
{!loading && (
<>
{filteredProjects.length > 0 ? (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: '24px',
}}
>
{filteredProjects.map((item, index) => (
<div
key={index}
style={{
border: '1px solid #ddd',
borderRadius: '12px',
padding: '20px',
boxShadow: '0 6px 15px rgba(0,0,0,0.07)',
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
gap: '12px',
userSelect: 'none',
}}
onClick={() => onClick(item)}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-6px) scale(1.02)';
e.currentTarget.style.boxShadow = '0 12px 24px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0) scale(1)';
e.currentTarget.style.boxShadow = '0 6px 15px rgba(0,0,0,0.07)';
}}
title={typeof item === 'string' ? item : item.sub_project_name || '未命名项目'}
>
<div
style={{
fontWeight: '700',
fontSize: '16px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '100%',
}}
>
{typeof item === 'string' ? item : item.sub_project_name || '未命名项目'}
</div>
{(item.new_members || item.new_high_altitude || item.new_hired_general) && (
<div
style={{
fontSize: '14px',
color: '#e64c3c',
fontWeight: '600',
borderLeft: '4px solid #e64c3c',
paddingLeft: '10px',
}}
>
新人: 存在新人员请做好人员面对面核实
</div>
)}
{warningFields.map(({key, label, message}) =>
item[key] ? (
<div
key={key}
style={{
fontSize: '14px',
color: '#e64c3c',
fontWeight: '600',
borderLeft: '4px solid #e64c3c',
paddingLeft: '10px',
}}
>
{label}: {message}
</div>
) : null
)}
{mismatchWarning(item) && (
<div
style={{
fontSize: '14px',
color: '#e64c3c',
fontWeight: '600',
borderLeft: '4px solid #e64c3c',
paddingLeft: '10px',
}}
>
工程进展与下周作业计划不匹配请注意
</div>
)}
{projectsRangeWarning(item) && (
<div
style={{
fontSize: '14px',
color: '#e64c3c',
fontWeight: '600',
borderLeft: '4px solid #e64c3c',
paddingLeft: '10px',
}}
>
工程参建人数与作业内容不匹配请注意
</div>
)}
{productionPlanScaleWarning(item) && (
<div
style={{
fontSize: '14px',
color: '#e64c3c',
fontWeight: '600',
borderLeft: '4px solid #e64c3c',
paddingLeft: '10px',
}}
>
投产计划与当前工程进度不匹配请注意
</div>
)}
</div>
))}
</div>
) : (
<div
style={{
textAlign: 'center',
color: '#aaa',
fontSize: '15px',
marginTop: '50px',
userSelect: 'none',
}}
>
暂无项目预警
</div>
)}
</>
)}
</Spin>
</div>
</div>
);
};
export default ProjectWarningView;