删除分页和结果分析功能,优化了表格导入的功能
This commit is contained in:
parent
f045fc0d37
commit
879ee40f82
|
|
@ -1 +1,4 @@
|
|||
node_modules
|
||||
build
|
||||
dist
|
||||
release
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"appId": "com.datatools.app",
|
||||
"productName": "DataTools",
|
||||
"files": [
|
||||
"build/**/*",
|
||||
"node_modules/**/*",
|
||||
"main.js",
|
||||
"preload.js"
|
||||
],
|
||||
"directories": {
|
||||
"buildResources": "assets",
|
||||
"output": "release"
|
||||
},
|
||||
"win": {
|
||||
"target": "dir"
|
||||
},
|
||||
"npmRebuild": false,
|
||||
"asar": false,
|
||||
"extraMetadata": {
|
||||
"main": "main.js"
|
||||
}
|
||||
}
|
||||
43
main.js
43
main.js
|
|
@ -30,6 +30,9 @@ function createWindow() {
|
|||
}
|
||||
});
|
||||
|
||||
// 将mainWindow设为全局变量,以便其他模块可以访问
|
||||
global.mainWindow = mainWindow;
|
||||
|
||||
// 加载应用
|
||||
const loadAppUrl = () => {
|
||||
const devUrl = 'http://localhost:3000';
|
||||
|
|
@ -236,6 +239,46 @@ app.whenReady().then(() => {
|
|||
}
|
||||
});
|
||||
|
||||
// 注册clear-all-data事件处理器
|
||||
ipcMain.removeHandler('clear-all-data'); // 先移除已有的处理器
|
||||
ipcMain.handle('clear-all-data', async () => {
|
||||
try {
|
||||
console.log('处理清除数据请求...');
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('DELETE FROM projects', function(err) {
|
||||
if (err) {
|
||||
console.error('清除项目数据失败:', err);
|
||||
reject({ success: false, error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
db.run('DELETE FROM tree_structure', function(err) {
|
||||
if (err) {
|
||||
console.error('清除树状结构数据失败:', err);
|
||||
reject({ success: false, error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
// 重置自增ID
|
||||
db.run('DELETE FROM sqlite_sequence WHERE name IN (\'projects\', \'tree_structure\')', function(err) {
|
||||
if (err) {
|
||||
console.error('重置自增ID失败:', err);
|
||||
// 不影响主要功能,继续执行
|
||||
}
|
||||
|
||||
resolve({ success: true, message: '所有数据已清除' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('清除数据错误:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// 在macOS上,当点击dock图标并且没有其他窗口打开时,通常在应用程序中重新创建一个窗口
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
|
|
@ -8,7 +8,9 @@
|
|||
"dev": "concurrently \"npm run start\" \"cross-env BROWSER=none npm run react-start\"",
|
||||
"react-start": "react-scripts start",
|
||||
"react-build": "react-scripts build",
|
||||
"build": "electron-builder",
|
||||
"rebuild": "electron-rebuild",
|
||||
"build": "electron-builder --publish never",
|
||||
"package": "npm run react-build && npm run rebuild && npm run build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
|
|
@ -32,26 +34,36 @@
|
|||
"preload.js"
|
||||
],
|
||||
"directories": {
|
||||
"buildResources": "assets"
|
||||
"buildResources": "assets",
|
||||
"output": "release"
|
||||
},
|
||||
"win": {
|
||||
"target": "nsis"
|
||||
}
|
||||
"target": "nsis",
|
||||
"sign": null
|
||||
},
|
||||
"npmRebuild": false,
|
||||
"extraMetadata": {
|
||||
"main": "main.js"
|
||||
},
|
||||
"asar": true,
|
||||
"forceCodeSigning": false
|
||||
},
|
||||
"dependencies": {
|
||||
"antd": "^5.7.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
"exceljs": "^4.3.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^27.3.8",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron-packager": "^17.1.2",
|
||||
"electron-rebuild": "^3.2.9",
|
||||
"react-scripts": "5.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
|
|
|
|||
12
preload.js
12
preload.js
|
|
@ -10,7 +10,19 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
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) => {
|
||||
// 移除之前的监听器,避免重复
|
||||
ipcRenderer.removeAllListeners('import-progress');
|
||||
// 添加新的监听器
|
||||
ipcRenderer.on('import-progress', (event, data) => callback(data));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
119
src/App.js
119
src/App.js
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { ConfigProvider, theme, Switch, Space } from 'antd';
|
||||
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';
|
||||
|
|
@ -12,7 +12,6 @@ dayjs.locale('zh-cn');
|
|||
import Toolbar from './components/Toolbar';
|
||||
import TreeView from './components/TreeView';
|
||||
import DataView from './components/DataView';
|
||||
import AnalysisView from './components/AnalysisView';
|
||||
import ProjectDetailForm from './components/ProjectDetailForm';
|
||||
|
||||
function App() {
|
||||
|
|
@ -206,21 +205,123 @@ function App() {
|
|||
if (filePath) {
|
||||
setLoading(true);
|
||||
|
||||
// 创建进度对话框
|
||||
const progressModal = Modal.info({
|
||||
title: '导入Excel',
|
||||
content: (
|
||||
<div>
|
||||
<p id="import-progress-message">正在准备导入...</p>
|
||||
<Progress id="import-progress-bar" percent={0} status="active" />
|
||||
</div>
|
||||
),
|
||||
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) {
|
||||
// 导入成功,重新加载数据
|
||||
loadData();
|
||||
} else {
|
||||
// 如果导入失败且没有进度事件处理,则显示错误消息
|
||||
if (!result.success) {
|
||||
progressModal.destroy();
|
||||
message.error(`导入失败: ${result.error}`);
|
||||
console.error('导入Excel文件失败:', result.error);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
} 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 (
|
||||
|
|
@ -239,6 +340,7 @@ function App() {
|
|||
activeFilter={activeFilter}
|
||||
onFilterChange={handleToolbarFilter}
|
||||
onImportExcel={handleImportExcel}
|
||||
onClearData={handleClearData}
|
||||
/>
|
||||
|
||||
{/* 树状结构区 */}
|
||||
|
|
@ -260,11 +362,6 @@ function App() {
|
|||
loading={loading}
|
||||
/>
|
||||
|
||||
{/* 分析结果区 */}
|
||||
<AnalysisView
|
||||
selectedProjects={projects.filter(p => selectedProjects.includes(p.id))}
|
||||
/>
|
||||
|
||||
{/* 项目详情表单 */}
|
||||
<ProjectDetailForm
|
||||
visible={detailModalVisible}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ const DataView = ({
|
|||
key: 'current_progress',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
render: (text) => {
|
||||
// 如果进度文本过长,只显示前15个字符加省略号
|
||||
if (text && text.length > 15) {
|
||||
return <span title={text}>{text.substring(0, 15)}...</span>;
|
||||
}
|
||||
return text;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '当前工程状态',
|
||||
|
|
@ -115,17 +122,15 @@ const DataView = ({
|
|||
selectedRowKeys: selectedProjects,
|
||||
onChange: onSelect,
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 20,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条数据`,
|
||||
}}
|
||||
pagination={false}
|
||||
scroll={{ y: 'calc(100vh - 150px)', x: 'max-content' }}
|
||||
size="small"
|
||||
bordered
|
||||
onRow={(record) => ({
|
||||
onClick: () => onClick(record), // 点击行时调用传入的onClick函数
|
||||
style: { cursor: 'pointer' } // 鼠标悬停时显示指针样式
|
||||
})}
|
||||
style={{ margin: '10px' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import {
|
|||
WarningOutlined,
|
||||
SearchOutlined,
|
||||
ImportOutlined,
|
||||
SettingOutlined
|
||||
SettingOutlined,
|
||||
ClearOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const Toolbar = ({ activeFilter, onFilterChange, onImportExcel }) => {
|
||||
const Toolbar = ({ activeFilter, onFilterChange, onImportExcel, onClearData }) => {
|
||||
// 显示"正在开发中"的提示
|
||||
const showDevelopingMessage = () => {
|
||||
Modal.info({
|
||||
|
|
@ -48,6 +49,12 @@ const Toolbar = ({ activeFilter, onFilterChange, onImportExcel }) => {
|
|||
icon: <SettingOutlined />,
|
||||
title: '设置',
|
||||
onClick: showDevelopingMessage
|
||||
},
|
||||
{
|
||||
key: 'clear',
|
||||
icon: <ClearOutlined />,
|
||||
title: '清除数据',
|
||||
onClick: onClearData
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,22 @@ class ExcelService {
|
|||
throw new Error('文件路径不能为空');
|
||||
}
|
||||
|
||||
// 发送开始导入进度事件
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'reading',
|
||||
progress: 0,
|
||||
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 = [];
|
||||
|
|
@ -33,7 +43,17 @@ class ExcelService {
|
|||
headers[colNumber - 1] = cell.value;
|
||||
});
|
||||
|
||||
// 发送读取表头完成进度事件
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'reading',
|
||||
progress: 10,
|
||||
message: '表头读取完成,正在读取数据...'
|
||||
});
|
||||
}
|
||||
|
||||
// 读取数据
|
||||
let processedRows = 0;
|
||||
worksheet.eachRow((row, rowNumber) => {
|
||||
if (rowNumber > 1) { // 跳过表头
|
||||
const rowData = {};
|
||||
|
|
@ -41,12 +61,64 @@ class ExcelService {
|
|||
rowData[headers[colNumber - 1]] = cell.value;
|
||||
});
|
||||
data.push(rowData);
|
||||
|
||||
// 计算并发送进度
|
||||
processedRows++;
|
||||
if (processedRows % 10 === 0 || processedRows === totalRows) { // 每10行或最后一行发送一次进度
|
||||
const progress = Math.floor(10 + (processedRows / totalRows) * 40); // 10%-50%的进度
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'reading',
|
||||
progress: progress,
|
||||
message: `正在读取数据...${processedRows}/${totalRows}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 发送数据读取完成进度事件
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'saving',
|
||||
progress: 50,
|
||||
message: `数据读取完成,正在保存到数据库...`
|
||||
});
|
||||
}
|
||||
|
||||
// 将数据保存到数据库(这里可以根据实际需求修改)
|
||||
if (data.length > 0 && this.db) {
|
||||
this.saveDataToDatabase(data);
|
||||
await this.saveDataToDatabase(data);
|
||||
|
||||
// 发送数据保存完成进度事件
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'building',
|
||||
progress: 70,
|
||||
message: `数据保存完成,正在构建树状结构...`
|
||||
});
|
||||
}
|
||||
|
||||
// 构建树状结构
|
||||
await this.buildTreeStructure(data);
|
||||
|
||||
// 发送导入完成进度事件
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'complete',
|
||||
progress: 100,
|
||||
message: `导入完成,共导入 ${data.length} 条数据`
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 如果没有数据,直接发送完成事件
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'complete',
|
||||
progress: 100,
|
||||
message: `导入完成,没有数据需要导入`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -56,6 +128,16 @@ class ExcelService {
|
|||
};
|
||||
} catch (error) {
|
||||
console.error('Excel导入错误:', error);
|
||||
|
||||
// 发送导入错误进度事件
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: `导入失败: ${error.message}`
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
|
|
@ -65,10 +147,12 @@ class ExcelService {
|
|||
|
||||
// 将数据保存到数据库
|
||||
saveDataToDatabase(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 清空现有数据
|
||||
this.db.run('DELETE FROM projects', (err) => {
|
||||
if (err) {
|
||||
console.error('清空项目数据失败:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -112,49 +196,97 @@ class ExcelService {
|
|||
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;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
// 处理布尔值字段
|
||||
const getBooleanValue = (fieldName) => {
|
||||
const value = row[fieldName];
|
||||
if (value === '是' || value === 'true' || value === true || value === 1) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// 处理数字字段
|
||||
const getNumberValue = (fieldName, defaultValue = 0) => {
|
||||
const value = row[fieldName];
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return defaultValue;
|
||||
}
|
||||
// 尝试将值转换为数字
|
||||
const num = Number(value);
|
||||
return isNaN(num) ? defaultValue : num;
|
||||
};
|
||||
|
||||
// 使用处理函数获取字段值
|
||||
stmt.run(
|
||||
row['单位'] || null,
|
||||
row['项目编号'] || null,
|
||||
row['安全编码'] || null,
|
||||
row['大项工程名称'] || null,
|
||||
row['单项工程名称'] || null,
|
||||
row['在施工程作业范围'] || null,
|
||||
row['工程规模'] || null,
|
||||
row['安全总监'] || null,
|
||||
row['建设单位'] || null,
|
||||
row['监理单位'] || null,
|
||||
row['施工单位'] || null,
|
||||
row['工程位置'] || null,
|
||||
row['实际开工时间'] || null,
|
||||
row['计划竣工时间'] || null,
|
||||
row['当前工程进度'] || null,
|
||||
row['当前工程状态'] || null,
|
||||
row['参建人数'] || 0,
|
||||
row['新班组进场数量'] || 0,
|
||||
row['新人进场数量'] || 0,
|
||||
row['带班人姓名、电话'] || null,
|
||||
row['下周作业计划'] || null,
|
||||
row['下周8+2工况内容'] || null,
|
||||
row['工期是否紧张'] === '是' ? 1 : 0,
|
||||
row['是否存在"账外事"'] === '是' ? 1 : 0,
|
||||
row['当前风险等级'] || null,
|
||||
row['当前风险判断理由'] || null,
|
||||
row['隐患提示/工作要求'] || null,
|
||||
row['完成时间'] || null,
|
||||
row['下次梳理时间'] || null,
|
||||
row['备注'] || null
|
||||
getValue('单位'),
|
||||
getValue('项目编号'),
|
||||
getValue('安全编码'),
|
||||
getValue('大项工程名称'),
|
||||
getValue('单项工程名称'),
|
||||
getValue('在施工程作业范围'),
|
||||
getValue('工程规模'),
|
||||
getValue('安全总监'),
|
||||
getValue('建设单位'),
|
||||
getValue('监理单位'),
|
||||
getValue('施工单位'),
|
||||
getValue('工程位置'),
|
||||
getValue('实际开工时间'),
|
||||
getValue('计划竣工时间'),
|
||||
getValue('当前工程进度'),
|
||||
getValue('当前工程状态'),
|
||||
getNumberValue('参建人数'),
|
||||
getNumberValue('新班组进场数量'),
|
||||
getNumberValue('新人进场数量'),
|
||||
getValue('带班人姓名、电话'),
|
||||
getValue('下周作业计划'),
|
||||
getValue('下周8+2工况内容'),
|
||||
getBooleanValue('工期是否紧张'),
|
||||
getBooleanValue('是否存在"账外事"'),
|
||||
getValue('当前风险等级'),
|
||||
getValue('当前风险判断理由'),
|
||||
getValue('隐患提示/工作要求'),
|
||||
getValue('完成时间'),
|
||||
getValue('下次梳理时间'),
|
||||
getValue('备注')
|
||||
);
|
||||
|
||||
// 更新进度
|
||||
processedRows++;
|
||||
if (processedRows % 10 === 0 || processedRows === totalRows) {
|
||||
const progress = Math.floor(50 + (processedRows / totalRows) * 20); // 50%-70%的进度
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'saving',
|
||||
progress: progress,
|
||||
message: `正在保存数据到数据库...${processedRows}/${totalRows}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.db.run('COMMIT', (err) => {
|
||||
if (err) {
|
||||
console.error('提交事务失败:', err);
|
||||
this.db.run('ROLLBACK');
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`成功导入 ${data.length} 条数据到数据库`);
|
||||
// 构建树状结构
|
||||
this.buildTreeStructure(data);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -162,55 +294,98 @@ class ExcelService {
|
|||
// 完成后关闭准备好的语句
|
||||
stmt.finalize();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 构建树状结构
|
||||
buildTreeStructure(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 清空现有树状结构
|
||||
this.db.run('DELETE FROM tree_structure', (err) => {
|
||||
if (err) {
|
||||
console.error('清空树状结构失败:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建总部节点
|
||||
// 使用Promise和事务来确保操作的原子性和顺序性
|
||||
const headquarters = '总部';
|
||||
const insertHeadquarters = this.db.prepare(`
|
||||
INSERT INTO tree_structure (
|
||||
headquarters,
|
||||
unit,
|
||||
construction_unit,
|
||||
parent_id,
|
||||
node_level
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
// 开始事务
|
||||
this.db.run('BEGIN TRANSACTION', (err) => {
|
||||
if (err) {
|
||||
console.error('开始事务失败:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送进度更新 - 开始创建树状结构
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'building',
|
||||
progress: 75,
|
||||
message: `正在创建总部节点...`
|
||||
});
|
||||
}
|
||||
|
||||
// 插入总部节点
|
||||
insertHeadquarters.run(headquarters, null, null, null, 1, function(err) {
|
||||
this.db.run(
|
||||
`INSERT INTO tree_structure (headquarters, unit, construction_unit, parent_id, node_level)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[headquarters, null, null, null, 1],
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error('插入总部节点失败:', err);
|
||||
this.db.run('ROLLBACK');
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const headquartersId = this.lastID;
|
||||
// 获取总部节点ID
|
||||
this.db.get('SELECT last_insert_rowid() as id', (err, row) => {
|
||||
if (err) {
|
||||
console.error('获取总部节点ID失败:', err);
|
||||
this.db.run('ROLLBACK');
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const headquartersId = row.id;
|
||||
console.log(`创建总部节点成功, ID: ${headquartersId}`);
|
||||
|
||||
// 单位映射
|
||||
const unitMap = {};
|
||||
|
||||
// 遍历数据,构建树状结构
|
||||
for (const row of data) {
|
||||
const unit = row['单位'];
|
||||
const constructionUnit = row['建设单位'];
|
||||
// 首先处理所有单位节点
|
||||
const uniqueUnits = [...new Set(data.filter(row => row['单位']).map(row => row['单位']))];
|
||||
|
||||
// 检查单位是否为空
|
||||
if (!unit) continue;
|
||||
// 发送进度更新 - 开始创建单位节点
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'building',
|
||||
progress: 80,
|
||||
message: `正在创建单位节点...`
|
||||
});
|
||||
}
|
||||
|
||||
// 如果单位不存在,则创建单位节点
|
||||
if (!unitMap[unit]) {
|
||||
insertHeadquarters.run(headquarters, unit, null, headquartersId, 2, function(err) {
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[headquarters, unit, null, headquartersId, 2],
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error(`创建单位节点失败: ${unit}`, err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -218,34 +393,127 @@ class ExcelService {
|
|||
unitMap[unit] = { id: unitId, constructionUnits: {} };
|
||||
console.log(`创建单位节点: ${unit}, ID: ${unitId}`);
|
||||
|
||||
// 如果建设单位不为空且不存在,则添加建设单位节点
|
||||
if (constructionUnit && !unitMap[unit].constructionUnits[constructionUnit]) {
|
||||
insertHeadquarters.run(headquarters, unit, constructionUnit, unitId, 3, function(err) {
|
||||
if (err) {
|
||||
console.error(`创建建设单位节点失败: ${constructionUnit}`, err);
|
||||
} else {
|
||||
unitMap[unit].constructionUnits[constructionUnit] = true;
|
||||
console.log(`创建建设单位节点: ${constructionUnit}, 父节点: ${unit}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (constructionUnit && !unitMap[unit].constructionUnits[constructionUnit]) {
|
||||
// 如果单位已存在但建设单位不存在,则添加建设单位节点
|
||||
insertHeadquarters.run(headquarters, unit, constructionUnit, unitMap[unit].id, 3, function(err) {
|
||||
if (err) {
|
||||
console.error(`创建建设单位节点失败: ${constructionUnit}`, err);
|
||||
} else {
|
||||
unitMap[unit].constructionUnits[constructionUnit] = true;
|
||||
console.log(`创建建设单位节点: ${constructionUnit}, 父节点: ${unit}`);
|
||||
}
|
||||
unitProcessed++;
|
||||
|
||||
// 更新进度
|
||||
if (unitProcessed % 5 === 0 || unitProcessed === uniqueUnits.length) {
|
||||
const progress = Math.floor(80 + (unitProcessed / uniqueUnits.length) * 10); // 80%-90%的进度
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'building',
|
||||
progress: progress,
|
||||
message: `正在创建单位节点...${unitProcessed}/${uniqueUnits.length}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 完成后关闭准备好的语句
|
||||
insertHeadquarters.finalize();
|
||||
if (unitProcessed === uniqueUnits.length) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 然后处理所有建设单位节点
|
||||
const processConstructionUnits = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 收集所有有效的建设单位数据
|
||||
const constructionUnitData = [];
|
||||
|
||||
data.forEach(row => {
|
||||
const unit = row['单位'];
|
||||
const constructionUnit = row['建设单位'];
|
||||
|
||||
if (unit && constructionUnit && unitMap[unit] &&
|
||||
!unitMap[unit].constructionUnits[constructionUnit]) {
|
||||
constructionUnitData.push({
|
||||
unit,
|
||||
constructionUnit,
|
||||
unitId: unitMap[unit].id
|
||||
});
|
||||
unitMap[unit].constructionUnits[constructionUnit] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 发送进度更新 - 开始创建建设单位节点
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'building',
|
||||
progress: 90,
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[headquarters, item.unit, item.constructionUnit, item.unitId, 3],
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error(`创建建设单位节点失败: ${item.constructionUnit}`, err);
|
||||
// 不中断整个过程,继续处理其他节点
|
||||
} 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%的进度
|
||||
if (global.mainWindow) {
|
||||
global.mainWindow.webContents.send('import-progress', {
|
||||
status: 'building',
|
||||
progress: progress,
|
||||
message: `正在创建建设单位节点...${processed}/${constructionUnitData.length}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (processed === constructionUnitData.length) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 按顺序执行:先处理单位,再处理建设单位
|
||||
processUnits()
|
||||
.then(() => processConstructionUnits())
|
||||
.then(() => {
|
||||
// 提交事务
|
||||
this.db.run('COMMIT', (err) => {
|
||||
if (err) {
|
||||
console.error('提交事务失败:', err);
|
||||
this.db.run('ROLLBACK');
|
||||
reject(err);
|
||||
} else {
|
||||
console.log('树状结构构建完成');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('构建树状结构失败:', err);
|
||||
this.db.run('ROLLBACK');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,14 +55,6 @@ code {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.analysis-view {
|
||||
width: 300px;
|
||||
background-color: var(--vscode-sidebar-bg);
|
||||
border-left: 1px solid var(--vscode-sidebar-border);
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 工具栏图标样式 */
|
||||
.toolbar-icon {
|
||||
width: 32px;
|
||||
|
|
@ -163,3 +155,61 @@ code {
|
|||
border-color: #40a9ff !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--vscode-sidebar-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
/* 表格滚动条样式 */
|
||||
.ant-table-body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.ant-table-body::-webkit-scrollbar-track {
|
||||
background: var(--vscode-sidebar-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ant-table-body::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ant-table-body::-webkit-scrollbar-thumb:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
/* 树状结构滚动条样式 */
|
||||
.tree-view::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.tree-view::-webkit-scrollbar-track {
|
||||
background: var(--vscode-sidebar-bg);
|
||||
}
|
||||
|
||||
.tree-view::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tree-view::-webkit-scrollbar-thumb:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue