抓变化
This commit is contained in:
parent
7d839712ca
commit
bed25c9317
48
main.js
48
main.js
|
|
@ -554,3 +554,51 @@ ipcMain.handle('update-project', (event, project) => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 获取数据库中的项目数据
|
||||
ipcMain.handle('get-projects-range', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = `
|
||||
WITH filtered AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN major_project_name LIKE '%变电工程%' THEN '变电工程'
|
||||
WHEN major_project_name LIKE '%线路工程%' THEN '线路工程'
|
||||
END AS project_type,
|
||||
participants_count
|
||||
FROM projects
|
||||
WHERE (major_project_name LIKE '%变电工程%'
|
||||
OR major_project_name LIKE '%线路工程%')
|
||||
AND current_status = '在施'
|
||||
),
|
||||
ordered AS (
|
||||
SELECT
|
||||
project_type,
|
||||
participants_count,
|
||||
ROW_NUMBER() OVER (PARTITION BY project_type ORDER BY participants_count) AS rn,
|
||||
COUNT(*) OVER (PARTITION BY project_type) AS total_count
|
||||
FROM filtered
|
||||
),
|
||||
percentiles AS (
|
||||
SELECT
|
||||
project_type,
|
||||
MAX(CASE WHEN rn = MAX(1, CAST(total_count * 0.05 AS INTEGER)) THEN participants_count END) AS p5,
|
||||
MAX(CASE WHEN rn = MAX(1, CAST(total_count * 0.95 AS INTEGER)) THEN participants_count END) AS p95
|
||||
FROM ordered
|
||||
GROUP BY project_type
|
||||
)
|
||||
SELECT * FROM percentiles;
|
||||
`;
|
||||
|
||||
db.all(sql, (err, rows) => {
|
||||
if (err) {
|
||||
console.error('获取项目数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
|
||||
// 数据库操作
|
||||
getProjects: () => ipcRenderer.invoke('get-projects'),
|
||||
getProjectsRange: () => ipcRenderer.invoke('get-projects-range'),
|
||||
getTreeStructure: () => ipcRenderer.invoke('get-tree-structure'),
|
||||
filterProjects: (filters) => ipcRenderer.invoke('filter-projects', filters),
|
||||
updateProject: (project) => ipcRenderer.invoke('update-project', project),
|
||||
|
|
|
|||
23
src/App.js
23
src/App.js
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { ConfigProvider, theme, Switch, Space, Modal, Progress, message } from 'antd';
|
||||
import React, {useState, useEffect, useRef} from 'react';
|
||||
import {ConfigProvider, theme, Switch, Space, Modal, Progress, message} from 'antd';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
|
|
@ -18,6 +18,7 @@ import ProjectWarningView from "./components/ProjectWarningView";
|
|||
function App() {
|
||||
// 状态管理
|
||||
const [projects, setProjects] = useState([]);
|
||||
const [projectsDataRange, setProjectsDataRange] = useState([]);
|
||||
const [treeData, setTreeData] = useState([]);
|
||||
const [selectedNode, setSelectedNode] = useState(null);
|
||||
const [selectedProjects, setSelectedProjects] = useState([]);
|
||||
|
|
@ -42,6 +43,9 @@ function App() {
|
|||
const projectsData = await window.electronAPI.getProjects();
|
||||
setProjects(projectsData);
|
||||
|
||||
const projectsDataRange = await window.electronAPI.getProjectsRange();
|
||||
setProjectsDataRange(projectsDataRange);
|
||||
|
||||
// 获取树状结构数据
|
||||
const treeStructureData = await window.electronAPI.getTreeStructure();
|
||||
|
||||
|
|
@ -74,7 +78,7 @@ function App() {
|
|||
|
||||
// 遍历项目数据,构建树状结构
|
||||
projectsData.forEach(project => {
|
||||
const { unit, construction_unit } = project;
|
||||
const {unit, construction_unit} = project;
|
||||
|
||||
// 如果单位不存在,则创建单位节点
|
||||
if (!unitMap[unit]) {
|
||||
|
|
@ -116,13 +120,13 @@ function App() {
|
|||
} else if (key.startsWith('unit-')) {
|
||||
// 单位节点,筛选该单位的数据
|
||||
const unit = key.replace('unit-', '');
|
||||
filters = { unit };
|
||||
filters = {unit};
|
||||
} else if (key.startsWith('construction-')) {
|
||||
// 建设单位节点,筛选该建设单位的数据
|
||||
const parts = key.split('-');
|
||||
const unit = parts[1];
|
||||
const constructionUnit = parts.slice(2).join('-');
|
||||
filters = { unit, constructionUnit };
|
||||
filters = {unit, constructionUnit};
|
||||
}
|
||||
|
||||
// 应用筛选
|
||||
|
|
@ -151,7 +155,7 @@ function App() {
|
|||
let filters = {};
|
||||
|
||||
if (filter === 'risk') {
|
||||
filters = { riskLevel: '高风险' };
|
||||
filters = {riskLevel: '高风险'};
|
||||
}
|
||||
|
||||
// 应用筛选
|
||||
|
|
@ -163,7 +167,7 @@ function App() {
|
|||
setSearchText(text);
|
||||
|
||||
// 根据搜索文本筛选项目数据
|
||||
const filters = { subProjectName: text };
|
||||
const filters = {subProjectName: text};
|
||||
filterProjects(filters);
|
||||
};
|
||||
|
||||
|
|
@ -241,11 +245,11 @@ function App() {
|
|||
content: (
|
||||
<div>
|
||||
<p id="import-progress-message">正在准备导入...</p>
|
||||
<Progress id="import-progress-bar" percent={0} status="active" />
|
||||
<Progress id="import-progress-bar" percent={0} status="active"/>
|
||||
</div>
|
||||
),
|
||||
icon: null,
|
||||
okButtonProps: { style: { display: 'none' } },
|
||||
okButtonProps: {style: {display: 'none'}},
|
||||
maskClosable: false,
|
||||
closable: false,
|
||||
});
|
||||
|
|
@ -397,6 +401,7 @@ function App() {
|
|||
{/* 树状结构区 */}
|
||||
<ProjectWarningView
|
||||
projects={projects}
|
||||
projectsDataRange={projectsDataRange}
|
||||
loading={loading}
|
||||
onClick={handleProjectClick} // 传入点击事件处理函数
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,129 @@
|
|||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import {Spin} from 'antd';
|
||||
|
||||
const ProjectWarningView = ({ projects = [], loading = false,onClick}) => {
|
||||
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: '存在管理人员的变化,请加强现场管控。' },
|
||||
{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]);
|
||||
return warningFields.some(({key}) => item[key]);
|
||||
};
|
||||
|
||||
const filteredProjects = projects.filter(hasWarnings);
|
||||
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
|
||||
|
|
@ -130,6 +233,49 @@ const ProjectWarningView = ({ projects = [], loading = false,onClick}) => {
|
|||
</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',
|
||||
}}
|
||||
>
|
||||
投产计划30天内导线展放进度不足80%,请关注。
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue