diff --git a/preload.js b/preload.js index e4c4510..7357e02 100644 --- a/preload.js +++ b/preload.js @@ -1,29 +1,29 @@ -const { contextBridge, ipcRenderer } = require('electron'); +const {contextBridge, ipcRenderer} = require('electron'); // 暴露安全的API给渲染进程 contextBridge.exposeInMainWorld('electronAPI', { - // 文件操作 - selectExcelFile: () => ipcRenderer.invoke('select-excel-file'), + // 文件操作 + 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), + // 数据库操作 + 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), + // Excel处理 + importExcel: (filePath) => ipcRenderer.invoke('import-excel', filePath), - // 数据清除 - clearAllData: () => ipcRenderer.invoke('clear-all-data'), + // 数据清除 + clearAllData: () => ipcRenderer.invoke('clear-all-data'), - // 事件监听 - onImportProgress: (callback) => { - // 移除之前的监听器,避免重复 - ipcRenderer.removeAllListeners('import-progress'); - // 添加新的监听器 - ipcRenderer.on('import-progress', (event, data) => callback(data)); - } + // 事件监听 + onImportProgress: (callback) => { + // 移除之前的监听器,避免重复 + ipcRenderer.removeAllListeners('import-progress'); + // 添加新的监听器 + ipcRenderer.on('import-progress', (event, data) => callback(data)); + } }); diff --git a/src/App.js b/src/App.js index 8a66c82..ea29b36 100644 --- a/src/App.js +++ b/src/App.js @@ -29,6 +29,8 @@ function App() { const [loading, setLoading] = useState(false); const [isDarkMode] = useState(true); // 固定使用深色主题 const [lastImportedFilePath, setLastImportedFilePath] = useState(null); // 记录最后导入的文件路径 +// 根据选中的节点筛选项目数据 + let select = {}; // 初始化数据 useEffect(() => { @@ -111,8 +113,6 @@ function App() { const key = selectedKeys[0]; setSelectedNode(key); - // 根据选中的节点筛选项目数据 - let filters = {}; if (key === 'headquarters') { // 总部节点,显示所有数据 @@ -120,17 +120,17 @@ function App() { } else if (key.startsWith('unit-')) { // 单位节点,筛选该单位的数据 const unit = key.replace('unit-', ''); - filters = {unit}; + select = {unit}; } else if (key.startsWith('construction-')) { // 建设单位节点,筛选该建设单位的数据 const parts = key.split('-'); const unit = parts[1]; const constructionUnit = parts.slice(2).join('-'); - filters = {unit, constructionUnit}; + select = {unit, constructionUnit}; } // 应用筛选 - filterProjects(filters); + filterProjects(select); } }; @@ -159,16 +159,17 @@ function App() { } // 应用筛选 - filterProjects(filters); + filterProjects({ ...filters, ...select }); }; // 处理项目搜索 const handleSearch = (text) => { - setSearchText(text); + setSearchText(text); + console.log(select) // 根据搜索文本筛选项目数据 const filters = {subProjectName: text}; - filterProjects(filters); + filterProjects({ ...filters, ...select }); }; // 处理项目选择 @@ -261,6 +262,7 @@ function App() { const progressMessage = document.getElementById('import-progress-message'); if (progressBar && progressMessage) { + const antProgressText = progressBar.querySelector('.ant-progress-text'); // 更新进度条 const antProgress = progressBar.querySelector('.ant-progress-bg'); if (antProgress) { @@ -270,7 +272,7 @@ function App() { // 更新文本 progressMessage.textContent = data.message; - + antProgressText.textContent = `${data.progress}%`; // 如果导入完成,关闭对话框并重新加载数据 if (data.status === 'complete') { setTimeout(() => { diff --git a/src/components/ProjectDetailForm.js b/src/components/ProjectDetailForm.js index e8a74c1..b8203dd 100644 --- a/src/components/ProjectDetailForm.js +++ b/src/components/ProjectDetailForm.js @@ -1,5 +1,5 @@ import React, {useEffect} from 'react'; -import {Modal, Form, Input, Select, Switch, Button} from 'antd'; +import {Modal, Form, Input, Select, Switch, Button, InputNumber} from 'antd'; import dayjs from 'dayjs'; const {Option} = Select; @@ -104,14 +104,26 @@ const ProjectDetailForm = ({visible, project, onCancel, onSave}) => { - - + + + - - + + - - + + + @@ -144,19 +156,50 @@ const ProjectDetailForm = ({visible, project, onCancel, onSave}) => {
-

人的变化

- - +

人的变化

+ + + + + - - + + + - - - - - + + + +
diff --git a/src/services/ExcelService.js b/src/services/ExcelService.js index c36d28e..5b9ae65 100644 --- a/src/services/ExcelService.js +++ b/src/services/ExcelService.js @@ -6,7 +6,7 @@ const path = require('path'); class ExcelService { constructor(db) { this.db = db; - + // 不在构造函数中注册事件,避免重复注册 // 事件处理器现在由main.js处理 } @@ -15,11 +15,11 @@ class ExcelService { async importExcel(filePath) { try { console.log('正在导入Excel文件:', filePath); - + if (!filePath) { throw new Error('文件路径不能为空'); } - + // 发送开始导入进度事件 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -28,21 +28,21 @@ class ExcelService { message: '正在读取Excel文件...' }); } - + const workbook = new ExcelJS.Workbook(); await workbook.xlsx.readFile(filePath); - + // 读取第一个工作表 const worksheet = workbook.getWorksheet(1); const data = []; const totalRows = worksheet.rowCount - 1; // 减去表头行 - + // 读取表头 const headers = []; worksheet.getRow(1).eachCell((cell, colNumber) => { headers[colNumber - 1] = cell.value; }); - + // 发送读取表头完成进度事件 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -51,7 +51,7 @@ class ExcelService { message: '表头读取完成,正在读取数据...' }); } - + // 读取数据 let processedRows = 0; worksheet.eachRow((row, rowNumber) => { @@ -61,7 +61,7 @@ class ExcelService { rowData[headers[colNumber - 1]] = cell.value; }); data.push(rowData); - + // 计算并发送进度 processedRows++; if (processedRows % 10 === 0 || processedRows === totalRows) { // 每10行或最后一行发送一次进度 @@ -76,7 +76,7 @@ class ExcelService { } } }); - + // 发送数据读取完成进度事件 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -85,11 +85,11 @@ class ExcelService { message: `数据读取完成,正在保存到数据库...` }); } - + // 将数据保存到数据库(这里可以根据实际需求修改) if (data.length > 0 && this.db) { await this.saveDataToDatabase(data); - + // 发送数据保存完成进度事件 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -98,10 +98,10 @@ class ExcelService { message: `数据保存完成,正在构建树状结构...` }); } - + // 构建树状结构 await this.buildTreeStructure(data); - + // 发送导入完成进度事件 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -120,7 +120,7 @@ class ExcelService { }); } } - + return { success: true, message: `成功读取 ${data.length} 条数据`, @@ -128,7 +128,7 @@ class ExcelService { }; } catch (error) { console.error('Excel导入错误:', error); - + // 发送导入错误进度事件 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -137,14 +137,14 @@ class ExcelService { message: `导入失败: ${error.message}` }); } - - return { - success: false, - error: error.message + + return { + success: false, + error: error.message }; } } - + // 将数据保存到数据库 saveDataToDatabase(data) { return new Promise((resolve, reject) => { @@ -183,35 +183,68 @@ class ExcelService { remarks ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); - + // 开始事务 this.db.serialize(() => { this.db.run('BEGIN TRANSACTION'); - + // 发送进度更新 let processedRows = 0; const totalRows = data.length; - + for (const row of data) { // 处理字段值,确保所有字段都能正确导入 const getValue = (fieldName, defaultValue = null) => { - + // 检查字段是否存在,并处理空值 const value = row[fieldName]; if (value === undefined || value === null || value === '') { return defaultValue; } // 如果是日期类型,转换为ISO格式 - if (fieldName === '实际开工时间' || fieldName === '计划竣工时间' || fieldName === '完成时间' || fieldName === '下次梳理时间(注意与 隐患提示/工作要求 对应)') { - const date = new Date(value); - if (!isNaN(date.getTime())) { - - return date.toISOString().split('T')[0]; // 只保留日期部分 + if ( + fieldName === '实际开工时间' || + fieldName === '计划竣工时间' || + fieldName === '完成时间' || + fieldName === '下次梳理时间(注意与 隐患提示/工作要求 对应)' + ) { + if (value == null) return ''; // 空值处理 + + // ✅ Excel 日期序列号(一般从 25569 开始) + if (typeof value === 'number') { + if (value > 1e12 && value < 1e14) { + // ✅ 毫秒级时间戳 + const date = new Date(value); + if (!isNaN(date.getTime())) return date.toISOString().split('T')[0]; + } else { + // ✅ Excel 序列号转日期(从 1900-01-01 起,第1天是 1) + const utcDays = Math.floor(value - 25569); + const utcValue = utcDays * 86400; // 转秒 + const date = new Date(utcValue * 1000); + if (!isNaN(date.getTime())) return date.toISOString().split('T')[0]; + } } + + if (typeof value === 'string') { + let parsed = new Date(value); + if (!isNaN(parsed.getTime())) return parsed.toISOString().split('T')[0]; + + // ✅ 支持“5-12”、“5月12日” 等格式 + const matched = value.match(/^(\d{1,2})[月/-](\d{1,2})/); + if (matched) { + const year = new Date().getFullYear(); // 默认今年 + const mm = matched[1].padStart(2, '0'); + const dd = matched[2].padStart(2, '0'); + return `${year}-${mm}-${dd}`; + } + } + + return ''; // 未能解析,返回空字符串 } + return value; }; - + // 处理布尔值字段 const getBooleanValue = (fieldName) => { const value = row[fieldName]; @@ -220,7 +253,7 @@ class ExcelService { } return 0; }; - + // 处理数字字段 const getNumberValue = (fieldName, defaultValue = 0) => { const value = row[fieldName]; @@ -231,7 +264,7 @@ class ExcelService { const num = Number(value); return isNaN(num) ? defaultValue : num; }; - + // 使用处理函数获取字段值 stmt.run( getValue('单位'), @@ -265,12 +298,15 @@ class ExcelService { getValue('下次梳理时间(注意与 隐患提示/工作要求 对应)'), getValue('备注') ); - + // 更新进度 processedRows++; if (processedRows % 10 === 0 || processedRows === totalRows) { const progress = Math.floor(50 + (processedRows / totalRows) * 20); // 50%-70%的进度 + if (global.mainWindow) { + + console.log("进度+++",progress) global.mainWindow.webContents.send('import-progress', { status: 'saving', progress: progress, @@ -279,7 +315,7 @@ class ExcelService { } } } - + this.db.run('COMMIT', (err) => { if (err) { console.error('提交事务失败:', err); @@ -291,12 +327,12 @@ class ExcelService { } }); }); - + // 完成后关闭准备好的语句 stmt.finalize(); }); } - + // 构建树状结构 buildTreeStructure(data) { return new Promise((resolve, reject) => { @@ -307,10 +343,10 @@ class ExcelService { reject(err); return; } - + // 使用Promise和事务来确保操作的原子性和顺序性 const headquarters = '总部'; - + // 开始事务 this.db.run('BEGIN TRANSACTION', (err) => { if (err) { @@ -318,7 +354,7 @@ class ExcelService { reject(err); return; } - + // 发送进度更新 - 开始创建树状结构 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -327,7 +363,7 @@ class ExcelService { message: `正在创建总部节点...` }); } - + // 插入总部节点 this.db.run( `INSERT INTO tree_structure (headquarters, unit, construction_unit, parent_id, node_level) @@ -340,7 +376,7 @@ class ExcelService { reject(err); return; } - + // 获取总部节点ID this.db.get('SELECT last_insert_rowid() as id', (err, row) => { if (err) { @@ -349,16 +385,16 @@ class ExcelService { reject(err); return; } - + const headquartersId = row.id; console.log(`创建总部节点成功, ID: ${headquartersId}`); - + // 单位映射 const unitMap = {}; - + // 首先处理所有单位节点 const uniqueUnits = [...new Set(data.filter(row => row['单位']).map(row => row['单位']))]; - + // 发送进度更新 - 开始创建单位节点 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -367,16 +403,16 @@ class ExcelService { message: `正在创建单位节点...` }); } - + const processUnits = () => { return new Promise((resolve, reject) => { let unitProcessed = 0; - + if (uniqueUnits.length === 0) { resolve(); return; } - + uniqueUnits.forEach(unit => { this.db.run( `INSERT INTO tree_structure (headquarters, unit, construction_unit, parent_id, node_level) @@ -388,13 +424,13 @@ class ExcelService { reject(err); return; } - + const unitId = this.lastID; unitMap[unit] = { id: unitId, constructionUnits: {} }; console.log(`创建单位节点: ${unit}, ID: ${unitId}`); - + unitProcessed++; - + // 更新进度 if (unitProcessed % 5 === 0 || unitProcessed === uniqueUnits.length) { const progress = Math.floor(80 + (unitProcessed / uniqueUnits.length) * 10); // 80%-90%的进度 @@ -406,7 +442,7 @@ class ExcelService { }); } } - + if (unitProcessed === uniqueUnits.length) { resolve(); } @@ -415,18 +451,18 @@ class ExcelService { }); }); }; - + // 然后处理所有建设单位节点 const processConstructionUnits = () => { return new Promise((resolve, reject) => { // 收集所有有效的建设单位数据 const constructionUnitData = []; - + data.forEach(row => { const unit = row['单位']; const constructionUnit = row['建设单位']; - - if (unit && constructionUnit && unitMap[unit] && + + if (unit && constructionUnit && unitMap[unit] && !unitMap[unit].constructionUnits[constructionUnit]) { constructionUnitData.push({ unit, @@ -436,7 +472,7 @@ class ExcelService { unitMap[unit].constructionUnits[constructionUnit] = true; } }); - + // 发送进度更新 - 开始创建建设单位节点 if (global.mainWindow) { global.mainWindow.webContents.send('import-progress', { @@ -445,14 +481,14 @@ class ExcelService { message: `正在创建建设单位节点...` }); } - + if (constructionUnitData.length === 0) { resolve(); return; } - + let processed = 0; - + constructionUnitData.forEach(item => { this.db.run( `INSERT INTO tree_structure (headquarters, unit, construction_unit, parent_id, node_level) @@ -465,9 +501,9 @@ class ExcelService { } else { console.log(`创建建设单位节点: ${item.constructionUnit}, 父节点: ${item.unit}`); } - + processed++; - + // 更新进度 if (processed % 10 === 0 || processed === constructionUnitData.length) { const progress = Math.floor(90 + (processed / constructionUnitData.length) * 10); // 90%-100%的进度 @@ -479,7 +515,7 @@ class ExcelService { }); } } - + if (processed === constructionUnitData.length) { resolve(); } @@ -488,7 +524,7 @@ class ExcelService { }); }); }; - + // 按顺序执行:先处理单位,再处理建设单位 processUnits() .then(() => processConstructionUnits()) diff --git a/src/styles/index.css b/src/styles/index.css index c0d61c9..3e79876 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -86,7 +86,7 @@ code { .ant-tree { background-color: transparent !important; color: var(--vscode-text) !important; -} +}mm .ant-tree-node-content-wrapper:hover { background-color: var(--vscode-hover-item) !important;