From bed25c9317e55c33da3f7e919a43184f44733406 Mon Sep 17 00:00:00 2001 From: jiang Date: Wed, 4 Jun 2025 17:52:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=93=E5=8F=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 48 ++ preload.js | 9 +- src/App.js | 769 ++++++++++++++------------- src/components/ProjectWarningView.js | 172 +++++- 4 files changed, 599 insertions(+), 399 deletions(-) diff --git a/main.js b/main.js index f4f5844..2fd5ecd 100644 --- a/main.js +++ b/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 || []); + } + }); + }); +}); + diff --git a/preload.js b/preload.js index 6b27f92..e4c4510 100644 --- a/preload.js +++ b/preload.js @@ -4,20 +4,21 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { // 文件操作 selectExcelFile: () => ipcRenderer.invoke('select-excel-file'), - + // 数据库操作 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), deleteProject: (projectId) => ipcRenderer.invoke('delete-project', projectId), - + // Excel处理 importExcel: (filePath) => ipcRenderer.invoke('import-excel', filePath), - + // 数据清除 clearAllData: () => ipcRenderer.invoke('clear-all-data'), - + // 事件监听 onImportProgress: (callback) => { // 移除之前的监听器,避免重复 diff --git a/src/App.js b/src/App.js index 2fa5fe8..8a66c82 100644 --- a/src/App.js +++ b/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'; @@ -16,401 +16,406 @@ import ProjectDetailForm from './components/ProjectDetailForm'; import ProjectWarningView from "./components/ProjectWarningView"; function App() { - // 状态管理 - const [projects, setProjects] = useState([]); - const [treeData, setTreeData] = useState([]); - const [selectedNode, setSelectedNode] = useState(null); - const [selectedProjects, setSelectedProjects] = useState([]); - const [activeFilter, setActiveFilter] = useState(null); - const [searchText, setSearchText] = useState(''); - const [detailModalVisible, setDetailModalVisible] = useState(false); - const [currentProject, setCurrentProject] = useState(null); - const [loading, setLoading] = useState(false); - const [isDarkMode] = useState(true); // 固定使用深色主题 - const [lastImportedFilePath, setLastImportedFilePath] = useState(null); // 记录最后导入的文件路径 + // 状态管理 + const [projects, setProjects] = useState([]); + const [projectsDataRange, setProjectsDataRange] = useState([]); + const [treeData, setTreeData] = useState([]); + const [selectedNode, setSelectedNode] = useState(null); + const [selectedProjects, setSelectedProjects] = useState([]); + const [activeFilter, setActiveFilter] = useState(null); + const [searchText, setSearchText] = useState(''); + const [detailModalVisible, setDetailModalVisible] = useState(false); + const [currentProject, setCurrentProject] = useState(null); + const [loading, setLoading] = useState(false); + const [isDarkMode] = useState(true); // 固定使用深色主题 + const [lastImportedFilePath, setLastImportedFilePath] = useState(null); // 记录最后导入的文件路径 - // 初始化数据 - useEffect(() => { - loadData(); - }, []); - - // 加载数据 - const loadData = async () => { - setLoading(true); - try { - // 获取项目数据 - const projectsData = await window.electronAPI.getProjects(); - setProjects(projectsData); - - // 获取树状结构数据 - const treeStructureData = await window.electronAPI.getTreeStructure(); - - // 如果树状结构为空,则从项目数据构建树状结构 - if (treeStructureData.length === 0 && projectsData.length > 0) { - const tree = buildTreeFromProjects(projectsData); - setTreeData(tree); - } else { - setTreeData(treeStructureData); - } - } catch (error) { - console.error('加载数据失败:', error); - } finally { - setLoading(false); - } - }; - - // 从项目数据构建树状结构 - const buildTreeFromProjects = (projectsData) => { - // 总部节点 - const headquarters = { - key: 'headquarters', - title: '总部', - level: 1, - children: [] - }; - - // 单位映射 - const unitMap = {}; - - // 遍历项目数据,构建树状结构 - projectsData.forEach(project => { - const { unit, construction_unit } = project; - - // 如果单位不存在,则创建单位节点 - if (!unitMap[unit]) { - unitMap[unit] = { - key: `unit-${unit}`, - title: unit, - level: 2, - children: [] - }; - headquarters.children.push(unitMap[unit]); - } - - // 如果建设单位不为空,则添加建设单位节点 - if (construction_unit && !unitMap[unit].children.find(child => child.title === construction_unit)) { - unitMap[unit].children.push({ - key: `construction-${unit}-${construction_unit}`, - title: construction_unit, - level: 3, - isLeaf: true - }); - } - }); - - return [headquarters]; - }; - - // 处理树节点选择 - const handleTreeSelect = (selectedKeys, info) => { - if (selectedKeys.length > 0) { - const key = selectedKeys[0]; - setSelectedNode(key); - - // 根据选中的节点筛选项目数据 - let filters = {}; - - if (key === 'headquarters') { - // 总部节点,显示所有数据 - setProjects(projects); - } else if (key.startsWith('unit-')) { - // 单位节点,筛选该单位的数据 - const unit = key.replace('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 }; - } - - // 应用筛选 - filterProjects(filters); - } - }; - - // 筛选项目数据 - const filterProjects = async (filters) => { - setLoading(true); - try { - const filteredProjects = await window.electronAPI.filterProjects(filters); - setProjects(filteredProjects); - } catch (error) { - console.error('筛选项目数据失败:', error); - } finally { - setLoading(false); - } - }; - - // 处理工具栏筛选 - const handleToolbarFilter = (filter) => { - setActiveFilter(filter === activeFilter ? null : filter); - - // 根据筛选条件筛选项目数据 - let filters = {}; - - if (filter === 'risk') { - filters = { riskLevel: '高风险' }; - } - - // 应用筛选 - filterProjects(filters); - }; - - // 处理项目搜索 - const handleSearch = (text) => { - setSearchText(text); - - // 根据搜索文本筛选项目数据 - const filters = { subProjectName: text }; - filterProjects(filters); - }; - - // 处理项目选择 - const handleProjectSelect = (selectedRowKeys) => { - setSelectedProjects(selectedRowKeys); - }; - - // 处理项目点击,打开详情弹窗 - const handleProjectClick = (project) => { - setCurrentProject(project); - setDetailModalVisible(true); - }; - - // 处理项目更新 - const handleProjectUpdate = async (updatedProject) => { - setLoading(true); - try { - const result = await window.electronAPI.updateProject(updatedProject); - - if (result.success) { - // 更新成功,重新加载数据 + // 初始化数据 + useEffect(() => { loadData(); - setDetailModalVisible(false); - } else { - console.error('更新项目数据失败:', result.error); - } - } catch (error) { - console.error('更新项目数据失败:', error); - } finally { - setLoading(false); - } - }; + }, []); - // 处理Excel导入 - const handleImportExcel = async () => { - try { - // 选择Excel文件 - const filePath = await window.electronAPI.selectExcelFile(); - - if (filePath) { - // 检查是否重复导入同一个文件 - if (lastImportedFilePath === filePath) { - // 显示确认对话框 - Modal.confirm({ - title: '文件已导入', - content: '该文件已经导入过,重新导入会覆盖之前的记录。确定要继续吗?', - okText: '确定', - cancelText: '取消', - onOk: () => { - // 用户确认后继续导入 - importFile(filePath); - } - }); - } else { - // 直接导入文件 - importFile(filePath); - } - } - } catch (error) { - console.error('导入Excel文件失败:', error); - message.error(`导入失败: ${error.message}`); - setLoading(false); - } - }; - - // 导入文件的实际逻辑 - const importFile = async (filePath) => { - try { - setLoading(true); - - // 创建进度对话框 - const progressModal = Modal.info({ - title: '导入Excel', - content: ( -
-

正在准备导入...

- -
- ), - icon: null, - okButtonProps: { style: { display: 'none' } }, - maskClosable: false, - closable: false, - }); - - // 监听导入进度 - window.electronAPI.onImportProgress((data) => { - // 更新进度条和消息 - const progressBar = document.getElementById('import-progress-bar'); - const progressMessage = document.getElementById('import-progress-message'); - - if (progressBar && progressMessage) { - // 更新进度条 - const antProgress = progressBar.querySelector('.ant-progress-bg'); - if (antProgress) { - antProgress.style.width = `${data.progress}%`; - antProgress.setAttribute('aria-valuenow', data.progress); - } - - // 更新文本 - progressMessage.textContent = data.message; - - // 如果导入完成,关闭对话框并重新加载数据 - if (data.status === 'complete') { - setTimeout(() => { - progressModal.destroy(); - message.success('Excel导入成功'); - // 重新加载数据 - loadData(); - setLoading(false); - }, 1000); - } else if (data.status === 'error') { - setTimeout(() => { - progressModal.destroy(); - message.error(`导入失败: ${data.message}`); - setLoading(false); - }, 1000); - } - } - }); - - // 导入Excel文件 - const result = await window.electronAPI.importExcel(filePath); - - // 如果导入失败且没有进度事件处理,则显示错误消息 - if (!result.success) { - progressModal.destroy(); - message.error(`导入失败: ${result.error}`); - console.error('导入Excel文件失败:', result.error); - setLoading(false); - } else { - // 导入成功,更新最后导入的文件路径 - setLastImportedFilePath(filePath); - } - } catch (error) { - console.error('导入Excel文件失败:', error); - message.error(`导入失败: ${error.message}`); - setLoading(false); - } - }; - - // 处理清除数据 - const handleClearData = async () => { - // 显示确认对话框 - Modal.confirm({ - title: '确认清除数据', - content: '此操作将清除所有项目数据,无法恢复。确定要继续吗?', - okText: '确定', - okType: 'danger', - cancelText: '取消', - onOk: async () => { + // 加载数据 + const loadData = async () => { setLoading(true); try { - // 调用API清除数据 - const result = await window.electronAPI.clearAllData(); + // 获取项目数据 + const projectsData = await window.electronAPI.getProjects(); + setProjects(projectsData); - if (result.success) { - // 清除成功,重置状态 - setProjects([]); - setTreeData([]); - setSelectedNode(null); - setSelectedProjects([]); - setActiveFilter(null); - setSearchText(''); + const projectsDataRange = await window.electronAPI.getProjectsRange(); + setProjectsDataRange(projectsDataRange); - // 显示成功提示 - Modal.success({ - title: '操作成功', - content: '所有数据已清除' - }); - } else { - // 显示错误提示 - Modal.error({ - title: '操作失败', - content: result.error || '清除数据时发生错误' - }); - } + // 获取树状结构数据 + const treeStructureData = await window.electronAPI.getTreeStructure(); + + // 如果树状结构为空,则从项目数据构建树状结构 + if (treeStructureData.length === 0 && projectsData.length > 0) { + const tree = buildTreeFromProjects(projectsData); + setTreeData(tree); + } else { + setTreeData(treeStructureData); + } } catch (error) { - console.error('清除数据失败:', error); - // 显示错误提示 - Modal.error({ - title: '操作失败', - content: '清除数据时发生错误' - }); + console.error('加载数据失败:', error); } finally { - setLoading(false); + setLoading(false); } - } - }); - }; + }; - return ( - -
- {/* 工具栏 */} - + // 从项目数据构建树状结构 + const buildTreeFromProjects = (projectsData) => { + // 总部节点 + const headquarters = { + key: 'headquarters', + title: '总部', + level: 1, + children: [] + }; - {/* 树状结构区 */} - + // 单位映射 + const unitMap = {}; - {/* 数据展示区 */} - + // 遍历项目数据,构建树状结构 + projectsData.forEach(project => { + const {unit, construction_unit} = project; - {/* 树状结构区 */} - + // 如果单位不存在,则创建单位节点 + if (!unitMap[unit]) { + unitMap[unit] = { + key: `unit-${unit}`, + title: unit, + level: 2, + children: [] + }; + headquarters.children.push(unitMap[unit]); + } - {/* 项目详情表单 */} - setDetailModalVisible(false)} - onSave={handleProjectUpdate} - /> -
-
- ); + // 如果建设单位不为空,则添加建设单位节点 + if (construction_unit && !unitMap[unit].children.find(child => child.title === construction_unit)) { + unitMap[unit].children.push({ + key: `construction-${unit}-${construction_unit}`, + title: construction_unit, + level: 3, + isLeaf: true + }); + } + }); + + return [headquarters]; + }; + + // 处理树节点选择 + const handleTreeSelect = (selectedKeys, info) => { + if (selectedKeys.length > 0) { + const key = selectedKeys[0]; + setSelectedNode(key); + + // 根据选中的节点筛选项目数据 + let filters = {}; + + if (key === 'headquarters') { + // 总部节点,显示所有数据 + setProjects(projects); + } else if (key.startsWith('unit-')) { + // 单位节点,筛选该单位的数据 + const unit = key.replace('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}; + } + + // 应用筛选 + filterProjects(filters); + } + }; + + // 筛选项目数据 + const filterProjects = async (filters) => { + setLoading(true); + try { + const filteredProjects = await window.electronAPI.filterProjects(filters); + setProjects(filteredProjects); + } catch (error) { + console.error('筛选项目数据失败:', error); + } finally { + setLoading(false); + } + }; + + // 处理工具栏筛选 + const handleToolbarFilter = (filter) => { + setActiveFilter(filter === activeFilter ? null : filter); + + // 根据筛选条件筛选项目数据 + let filters = {}; + + if (filter === 'risk') { + filters = {riskLevel: '高风险'}; + } + + // 应用筛选 + filterProjects(filters); + }; + + // 处理项目搜索 + const handleSearch = (text) => { + setSearchText(text); + + // 根据搜索文本筛选项目数据 + const filters = {subProjectName: text}; + filterProjects(filters); + }; + + // 处理项目选择 + const handleProjectSelect = (selectedRowKeys) => { + setSelectedProjects(selectedRowKeys); + }; + + // 处理项目点击,打开详情弹窗 + const handleProjectClick = (project) => { + setCurrentProject(project); + setDetailModalVisible(true); + }; + + // 处理项目更新 + const handleProjectUpdate = async (updatedProject) => { + setLoading(true); + try { + const result = await window.electronAPI.updateProject(updatedProject); + + if (result.success) { + // 更新成功,重新加载数据 + loadData(); + setDetailModalVisible(false); + } else { + console.error('更新项目数据失败:', result.error); + } + } catch (error) { + console.error('更新项目数据失败:', error); + } finally { + setLoading(false); + } + }; + + // 处理Excel导入 + const handleImportExcel = async () => { + try { + // 选择Excel文件 + const filePath = await window.electronAPI.selectExcelFile(); + + if (filePath) { + // 检查是否重复导入同一个文件 + if (lastImportedFilePath === filePath) { + // 显示确认对话框 + Modal.confirm({ + title: '文件已导入', + content: '该文件已经导入过,重新导入会覆盖之前的记录。确定要继续吗?', + okText: '确定', + cancelText: '取消', + onOk: () => { + // 用户确认后继续导入 + importFile(filePath); + } + }); + } else { + // 直接导入文件 + importFile(filePath); + } + } + } catch (error) { + console.error('导入Excel文件失败:', error); + message.error(`导入失败: ${error.message}`); + setLoading(false); + } + }; + + // 导入文件的实际逻辑 + const importFile = async (filePath) => { + try { + setLoading(true); + + // 创建进度对话框 + const progressModal = Modal.info({ + title: '导入Excel', + content: ( +
+

正在准备导入...

+ +
+ ), + icon: null, + okButtonProps: {style: {display: 'none'}}, + maskClosable: false, + closable: false, + }); + + // 监听导入进度 + window.electronAPI.onImportProgress((data) => { + // 更新进度条和消息 + const progressBar = document.getElementById('import-progress-bar'); + const progressMessage = document.getElementById('import-progress-message'); + + if (progressBar && progressMessage) { + // 更新进度条 + const antProgress = progressBar.querySelector('.ant-progress-bg'); + if (antProgress) { + antProgress.style.width = `${data.progress}%`; + antProgress.setAttribute('aria-valuenow', data.progress); + } + + // 更新文本 + progressMessage.textContent = data.message; + + // 如果导入完成,关闭对话框并重新加载数据 + if (data.status === 'complete') { + setTimeout(() => { + progressModal.destroy(); + message.success('Excel导入成功'); + // 重新加载数据 + loadData(); + setLoading(false); + }, 1000); + } else if (data.status === 'error') { + setTimeout(() => { + progressModal.destroy(); + message.error(`导入失败: ${data.message}`); + setLoading(false); + }, 1000); + } + } + }); + + // 导入Excel文件 + const result = await window.electronAPI.importExcel(filePath); + + // 如果导入失败且没有进度事件处理,则显示错误消息 + if (!result.success) { + progressModal.destroy(); + message.error(`导入失败: ${result.error}`); + console.error('导入Excel文件失败:', result.error); + setLoading(false); + } else { + // 导入成功,更新最后导入的文件路径 + setLastImportedFilePath(filePath); + } + } catch (error) { + console.error('导入Excel文件失败:', error); + message.error(`导入失败: ${error.message}`); + setLoading(false); + } + }; + + // 处理清除数据 + const handleClearData = async () => { + // 显示确认对话框 + Modal.confirm({ + title: '确认清除数据', + content: '此操作将清除所有项目数据,无法恢复。确定要继续吗?', + okText: '确定', + okType: 'danger', + cancelText: '取消', + onOk: async () => { + setLoading(true); + try { + // 调用API清除数据 + const result = await window.electronAPI.clearAllData(); + + if (result.success) { + // 清除成功,重置状态 + setProjects([]); + setTreeData([]); + setSelectedNode(null); + setSelectedProjects([]); + setActiveFilter(null); + setSearchText(''); + + // 显示成功提示 + Modal.success({ + title: '操作成功', + content: '所有数据已清除' + }); + } else { + // 显示错误提示 + Modal.error({ + title: '操作失败', + content: result.error || '清除数据时发生错误' + }); + } + } catch (error) { + console.error('清除数据失败:', error); + // 显示错误提示 + Modal.error({ + title: '操作失败', + content: '清除数据时发生错误' + }); + } finally { + setLoading(false); + } + } + }); + }; + + return ( + +
+ {/* 工具栏 */} + + + {/* 树状结构区 */} + + + {/* 数据展示区 */} + + + {/* 树状结构区 */} + + + {/* 项目详情表单 */} + setDetailModalVisible(false)} + onSave={handleProjectUpdate} + /> +
+
+ ); } export default App; diff --git a/src/components/ProjectWarningView.js b/src/components/ProjectWarningView.js index b028be0..ce141e6 100644 --- a/src/components/ProjectWarningView.js +++ b/src/components/ProjectWarningView.js @@ -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 (
{
) : null )} + + {mismatchWarning(item) && ( +
+ 工程进展与下周作业计划不匹配,请注意。 +
+ )} + + {projectsRangeWarning(item) && ( +
+ 工程参建人数与作业内容不匹配,请注意。 +
+ )} + + {productionPlanScaleWarning(item) && ( +
+ 投产计划30天内导线展放进度不足80%,请关注。 +
+ )} + ))}