抓变化
This commit is contained in:
parent
90245dbb80
commit
7d839712ca
777
main.js
777
main.js
|
|
@ -1,12 +1,12 @@
|
|||
const { app, BrowserWindow, ipcMain, dialog, Menu } = require('electron');
|
||||
const {app, BrowserWindow, ipcMain, dialog, Menu} = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
let isDev;
|
||||
import('electron-is-dev').then(module => {
|
||||
isDev = module.default;
|
||||
isDev = module.default;
|
||||
}).catch(err => {
|
||||
console.error('Failed to load electron-is-dev:', err);
|
||||
isDev = false;
|
||||
console.error('Failed to load electron-is-dev:', err);
|
||||
isDev = false;
|
||||
});
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const ExcelService = require('./src/services/ExcelService');
|
||||
|
|
@ -18,88 +18,88 @@ let mainWindow;
|
|||
let db;
|
||||
|
||||
function createWindow() {
|
||||
// 创建浏览器窗口
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
title: '值班助手工具',
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
});
|
||||
|
||||
// 将mainWindow设为全局变量,以便其他模块可以访问
|
||||
global.mainWindow = mainWindow;
|
||||
// 创建浏览器窗口
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
title: '值班助手工具',
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
});
|
||||
|
||||
// 加载应用
|
||||
const loadAppUrl = () => {
|
||||
const devUrl = 'http://localhost:3000';
|
||||
const prodUrl = `file://${path.join(__dirname, 'build/index.html')}`;
|
||||
const url = isDev ? devUrl : prodUrl;
|
||||
// 将mainWindow设为全局变量,以便其他模块可以访问
|
||||
global.mainWindow = mainWindow;
|
||||
|
||||
mainWindow.loadURL(url).catch((err) => {
|
||||
if (isDev) {
|
||||
console.log(`首次加载失败,10秒后重试...`);
|
||||
let retries = 3;
|
||||
const retryLoad = () => {
|
||||
mainWindow.loadURL(devUrl).catch((retryErr) => {
|
||||
if (retries-- > 0) {
|
||||
console.log(`剩余重试次数:${retries}`);
|
||||
setTimeout(retryLoad, 10000);
|
||||
} else {
|
||||
mainWindow.loadURL(`data:text/html,<h1 style="color:red">无法连接开发服务器,请检查:</h1>
|
||||
// 加载应用
|
||||
const loadAppUrl = () => {
|
||||
const devUrl = 'http://localhost:3000';
|
||||
const prodUrl = `file://${path.join(__dirname, 'build/index.html')}`;
|
||||
const url = isDev ? devUrl : prodUrl;
|
||||
|
||||
mainWindow.loadURL(url).catch((err) => {
|
||||
if (isDev) {
|
||||
console.log(`首次加载失败,10秒后重试...`);
|
||||
let retries = 3;
|
||||
const retryLoad = () => {
|
||||
mainWindow.loadURL(devUrl).catch((retryErr) => {
|
||||
if (retries-- > 0) {
|
||||
console.log(`剩余重试次数:${retries}`);
|
||||
setTimeout(retryLoad, 10000);
|
||||
} else {
|
||||
mainWindow.loadURL(`data:text/html,<h1 style="color:red">无法连接开发服务器,请检查:</h1>
|
||||
<ol>
|
||||
<li>React开发服务器是否启动(npm run react-start)</li>
|
||||
<li>端口3000是否被占用</li>
|
||||
<li>网络连接是否正常</li>
|
||||
</ol>`);
|
||||
}
|
||||
});
|
||||
};
|
||||
setTimeout(retryLoad, 10000);
|
||||
} else {
|
||||
mainWindow.loadURL(prodUrl).catch(() => {
|
||||
mainWindow.loadURL(`data:text/html,<h1 style="color:red">生产文件加载失败,请重新编译(npm run react-build)</h1>`);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
setTimeout(retryLoad, 10000);
|
||||
} else {
|
||||
mainWindow.loadURL(prodUrl).catch(() => {
|
||||
mainWindow.loadURL(`data:text/html,<h1 style="color:red">生产文件加载失败,请重新编译(npm run react-build)</h1>`);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// 开发模式立即尝试加载,生产模式直接加载
|
||||
if (isDev) {
|
||||
setTimeout(loadAppUrl, 3000); // 给开发服务器更多启动时间
|
||||
} else {
|
||||
loadAppUrl();
|
||||
}
|
||||
|
||||
// 开发者工具已禁用
|
||||
|
||||
// 当window被关闭时,触发下面的事件
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
// 关闭数据库连接
|
||||
if (db) {
|
||||
db.close();
|
||||
// 开发模式立即尝试加载,生产模式直接加载
|
||||
if (isDev) {
|
||||
setTimeout(loadAppUrl, 3000); // 给开发服务器更多启动时间
|
||||
} else {
|
||||
loadAppUrl();
|
||||
}
|
||||
});
|
||||
|
||||
// 开发者工具已禁用
|
||||
|
||||
// 当window被关闭时,触发下面的事件
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
// 关闭数据库连接
|
||||
if (db) {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
function initDatabase() {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const dbPath = path.join(userDataPath, 'datatools.db');
|
||||
|
||||
// 确保目录存在
|
||||
fs.ensureDirSync(path.dirname(dbPath));
|
||||
|
||||
// 连接数据库
|
||||
db = new sqlite3.Database(dbPath);
|
||||
|
||||
// 创建表(如果不存在)
|
||||
db.serialize(() => {
|
||||
db.run(`
|
||||
const userDataPath = app.getPath('userData');
|
||||
const dbPath = path.join(userDataPath, 'datatools.db');
|
||||
|
||||
// 确保目录存在
|
||||
fs.ensureDirSync(path.dirname(dbPath));
|
||||
|
||||
// 连接数据库
|
||||
db = new sqlite3.Database(dbPath);
|
||||
|
||||
// 创建表(如果不存在)
|
||||
db.serialize(() => {
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
unit TEXT, -- 单位
|
||||
|
|
@ -131,11 +131,24 @@ function initDatabase() {
|
|||
risk_tips TEXT, -- 隐患提示/工作要求
|
||||
completion_time TEXT, -- 完成时间
|
||||
next_review_time TEXT, -- 下次梳理时间
|
||||
remarks TEXT -- 备注
|
||||
|
||||
new_team INTEGER, --新进班组数量
|
||||
new_members INTEGER, --新进班组骨干数量
|
||||
new_high_altitude INTEGER, --新进高空人员数量
|
||||
new_hired_general INTEGER, --新进一般人员数量
|
||||
new_homework_content TEXT, --新的作业内容
|
||||
change_homework_method TEXT, --改变作业方法
|
||||
changes_geographical TEXT, --地理环境的变化
|
||||
changes_meteorological TEXT, --气象环境的变化
|
||||
changes_social TEXT, --社会环境的变化
|
||||
changes_management TEXT, --管理要求的变化
|
||||
changes_homework_plan TEXT, --作业计划的变化
|
||||
changes_management_personnel TEXT, --管理人员的变化
|
||||
remarks TEXT -- 备注
|
||||
)
|
||||
`);
|
||||
|
||||
db.run(`
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS tree_structure (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
headquarters TEXT, -- 总部
|
||||
|
|
@ -147,298 +160,298 @@ function initDatabase() {
|
|||
)
|
||||
`);
|
||||
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_unit ON projects(unit)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_construction_unit ON projects(construction_unit)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_sub_project_name ON projects(sub_project_name)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_current_risk_level ON projects(current_risk_level)`);
|
||||
});
|
||||
|
||||
return db;
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_unit ON projects(unit)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_construction_unit ON projects(construction_unit)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_sub_project_name ON projects(sub_project_name)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_current_risk_level ON projects(current_risk_level)`);
|
||||
});
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
// 创建中文菜单模板
|
||||
const menuTemplate = [
|
||||
{
|
||||
label: '文件',
|
||||
submenu: [
|
||||
{ role: 'quit', label: '退出' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{ role: 'undo', label: '撤销' },
|
||||
{ role: 'redo', label: '恢复' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'cut', label: '剪切' },
|
||||
{ role: 'copy', label: '复制' },
|
||||
{ role: 'paste', label: '粘贴' },
|
||||
{ role: 'delete', label: '删除' },
|
||||
{ role: 'selectAll', label: '全选' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '视图',
|
||||
submenu: [
|
||||
{ role: 'reload', label: '重新加载' },
|
||||
{ role: 'forceReload', label: '强制重新加载' },
|
||||
{ role: 'toggleDevTools', label: '开发者工具' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'resetZoom', label: '重置缩放' },
|
||||
{ role: 'zoomIn', label: '放大' },
|
||||
{ role: 'zoomOut', label: '缩小' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'togglefullscreen', label: '全屏' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
submenu: [
|
||||
{ role: 'minimize', label: '最小化' },
|
||||
{ role: 'zoom', label: '缩放' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'front', label: '前置所有窗口' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '帮助',
|
||||
submenu: [
|
||||
{
|
||||
label: '关于',
|
||||
click: () => {
|
||||
dialog.showMessageBox({
|
||||
title: '关于',
|
||||
message: '值班助手工具 v1.0.0'
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
label: '文件',
|
||||
submenu: [
|
||||
{role: 'quit', label: '退出'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{role: 'undo', label: '撤销'},
|
||||
{role: 'redo', label: '恢复'},
|
||||
{type: 'separator'},
|
||||
{role: 'cut', label: '剪切'},
|
||||
{role: 'copy', label: '复制'},
|
||||
{role: 'paste', label: '粘贴'},
|
||||
{role: 'delete', label: '删除'},
|
||||
{role: 'selectAll', label: '全选'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '视图',
|
||||
submenu: [
|
||||
{role: 'reload', label: '重新加载'},
|
||||
{role: 'forceReload', label: '强制重新加载'},
|
||||
{role: 'toggleDevTools', label: '开发者工具'},
|
||||
{type: 'separator'},
|
||||
{role: 'resetZoom', label: '重置缩放'},
|
||||
{role: 'zoomIn', label: '放大'},
|
||||
{role: 'zoomOut', label: '缩小'},
|
||||
{type: 'separator'},
|
||||
{role: 'togglefullscreen', label: '全屏'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
submenu: [
|
||||
{role: 'minimize', label: '最小化'},
|
||||
{role: 'zoom', label: '缩放'},
|
||||
{type: 'separator'},
|
||||
{role: 'front', label: '前置所有窗口'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '帮助',
|
||||
submenu: [
|
||||
{
|
||||
label: '关于',
|
||||
click: () => {
|
||||
dialog.showMessageBox({
|
||||
title: '关于',
|
||||
message: '值班助手工具 v1.0.0'
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 当Electron完成初始化并准备创建浏览器窗口时调用此方法
|
||||
app.whenReady().then(() => {
|
||||
// 设置中文菜单
|
||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
createWindow();
|
||||
db = initDatabase();
|
||||
|
||||
// 初始化Excel服务
|
||||
const excelService = new ExcelService(db);
|
||||
|
||||
// 注册import-excel事件处理器(确保只注册一次)
|
||||
ipcMain.removeHandler('import-excel'); // 先移除已有的处理器
|
||||
ipcMain.handle('import-excel', async (event, filePath) => {
|
||||
try {
|
||||
console.log('处理Excel导入请求...');
|
||||
return await excelService.importExcel(filePath);
|
||||
} catch (error) {
|
||||
console.error('导入Excel错误:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// 注册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: '所有数据已清除' });
|
||||
});
|
||||
// 设置中文菜单
|
||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
createWindow();
|
||||
db = initDatabase();
|
||||
|
||||
// 初始化Excel服务
|
||||
const excelService = new ExcelService(db);
|
||||
|
||||
// 注册import-excel事件处理器(确保只注册一次)
|
||||
ipcMain.removeHandler('import-excel'); // 先移除已有的处理器
|
||||
ipcMain.handle('import-excel', async (event, filePath) => {
|
||||
try {
|
||||
console.log('处理Excel导入请求...');
|
||||
return await excelService.importExcel(filePath);
|
||||
} catch (error) {
|
||||
console.error('导入Excel错误:', error);
|
||||
return {success: false, error: error.message};
|
||||
}
|
||||
});
|
||||
|
||||
// 注册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) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('清除数据错误:', error);
|
||||
return {success: false, error: error.message};
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// 在macOS上,当点击dock图标并且没有其他窗口打开时,通常在应用程序中重新创建一个窗口
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 当所有窗口关闭时退出应用
|
||||
app.on('window-all-closed', () => {
|
||||
// 在macOS上,除非用户用Cmd + Q确定地退出,否则绝大部分应用及其菜单栏会保持激活
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
// 在macOS上,除非用户用Cmd + Q确定地退出,否则绝大部分应用及其菜单栏会保持激活
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
// 处理Excel文件选择
|
||||
ipcMain.handle('select-excel-file', async () => {
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Excel Files', extensions: ['xlsx', 'xls'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (canceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return filePaths[0];
|
||||
const {canceled, filePaths} = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{name: 'Excel Files', extensions: ['xlsx', 'xls']}
|
||||
]
|
||||
});
|
||||
|
||||
if (canceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return filePaths[0];
|
||||
});
|
||||
|
||||
// 获取数据库中的项目数据
|
||||
ipcMain.handle('get-projects', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all('SELECT * FROM projects', (err, rows) => {
|
||||
if (err) {
|
||||
console.error('获取项目数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all('SELECT * FROM projects', (err, rows) => {
|
||||
if (err) {
|
||||
console.error('获取项目数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 获取树状结构数据
|
||||
ipcMain.handle('get-tree-structure', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all('SELECT * FROM tree_structure ORDER BY node_level, id', (err, rows) => {
|
||||
if (err) {
|
||||
console.error('获取树状结构数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
// 转换为Ant Design Tree组件所需的格式
|
||||
const treeData = [];
|
||||
const nodeMap = {};
|
||||
|
||||
// 首先找到总部节点
|
||||
const headquartersNodes = rows.filter(node => node.node_level === 1);
|
||||
|
||||
// 处理每个总部节点
|
||||
headquartersNodes.forEach(hqNode => {
|
||||
const headquartersTreeNode = {
|
||||
key: `headquarters-${hqNode.id}`,
|
||||
title: hqNode.headquarters,
|
||||
level: 1,
|
||||
children: []
|
||||
};
|
||||
|
||||
// 添加到树数据和节点映射
|
||||
treeData.push(headquartersTreeNode);
|
||||
nodeMap[hqNode.id] = headquartersTreeNode;
|
||||
|
||||
// 找到该总部下的所有单位节点
|
||||
const unitNodes = rows.filter(node =>
|
||||
node.node_level === 2 && node.parent_id === hqNode.id
|
||||
);
|
||||
|
||||
// 处理每个单位节点
|
||||
unitNodes.forEach(unitNode => {
|
||||
const unitTreeNode = {
|
||||
key: `unit-${unitNode.unit}`,
|
||||
title: unitNode.unit,
|
||||
level: 2,
|
||||
children: []
|
||||
};
|
||||
|
||||
// 添加到总部节点的子节点和节点映射
|
||||
headquartersTreeNode.children.push(unitTreeNode);
|
||||
nodeMap[unitNode.id] = unitTreeNode;
|
||||
|
||||
// 找到该单位下的所有建设单位节点
|
||||
const constructionNodes = rows.filter(node =>
|
||||
node.node_level === 3 && node.parent_id === unitNode.id
|
||||
);
|
||||
|
||||
// 处理每个建设单位节点
|
||||
constructionNodes.forEach(constructionNode => {
|
||||
const constructionTreeNode = {
|
||||
key: `construction-${unitNode.unit}-${constructionNode.construction_unit}`,
|
||||
title: constructionNode.construction_unit,
|
||||
level: 3,
|
||||
isLeaf: true
|
||||
};
|
||||
|
||||
// 添加到单位节点的子节点
|
||||
unitTreeNode.children.push(constructionTreeNode);
|
||||
});
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all('SELECT * FROM tree_structure ORDER BY node_level, id', (err, rows) => {
|
||||
if (err) {
|
||||
console.error('获取树状结构数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
// 转换为Ant Design Tree组件所需的格式
|
||||
const treeData = [];
|
||||
const nodeMap = {};
|
||||
|
||||
// 首先找到总部节点
|
||||
const headquartersNodes = rows.filter(node => node.node_level === 1);
|
||||
|
||||
// 处理每个总部节点
|
||||
headquartersNodes.forEach(hqNode => {
|
||||
const headquartersTreeNode = {
|
||||
key: `headquarters-${hqNode.id}`,
|
||||
title: hqNode.headquarters,
|
||||
level: 1,
|
||||
children: []
|
||||
};
|
||||
|
||||
// 添加到树数据和节点映射
|
||||
treeData.push(headquartersTreeNode);
|
||||
nodeMap[hqNode.id] = headquartersTreeNode;
|
||||
|
||||
// 找到该总部下的所有单位节点
|
||||
const unitNodes = rows.filter(node =>
|
||||
node.node_level === 2 && node.parent_id === hqNode.id
|
||||
);
|
||||
|
||||
// 处理每个单位节点
|
||||
unitNodes.forEach(unitNode => {
|
||||
const unitTreeNode = {
|
||||
key: `unit-${unitNode.unit}`,
|
||||
title: unitNode.unit,
|
||||
level: 2,
|
||||
children: []
|
||||
};
|
||||
|
||||
// 添加到总部节点的子节点和节点映射
|
||||
headquartersTreeNode.children.push(unitTreeNode);
|
||||
nodeMap[unitNode.id] = unitTreeNode;
|
||||
|
||||
// 找到该单位下的所有建设单位节点
|
||||
const constructionNodes = rows.filter(node =>
|
||||
node.node_level === 3 && node.parent_id === unitNode.id
|
||||
);
|
||||
|
||||
// 处理每个建设单位节点
|
||||
constructionNodes.forEach(constructionNode => {
|
||||
const constructionTreeNode = {
|
||||
key: `construction-${unitNode.unit}-${constructionNode.construction_unit}`,
|
||||
title: constructionNode.construction_unit,
|
||||
level: 3,
|
||||
isLeaf: true
|
||||
};
|
||||
|
||||
// 添加到单位节点的子节点
|
||||
unitTreeNode.children.push(constructionTreeNode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log('树状结构数据转换完成,节点数量:', treeData.length);
|
||||
resolve(treeData);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('树状结构数据转换完成,节点数量:', treeData.length);
|
||||
resolve(treeData);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 根据条件筛选项目数据
|
||||
ipcMain.handle('filter-projects', (event, filters) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let query = 'SELECT * FROM projects WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (filters.unit) {
|
||||
query += ' AND unit = ?';
|
||||
params.push(filters.unit);
|
||||
}
|
||||
|
||||
if (filters.constructionUnit) {
|
||||
query += ' AND construction_unit = ?';
|
||||
params.push(filters.constructionUnit);
|
||||
}
|
||||
|
||||
if (filters.subProjectName) {
|
||||
query += ' AND sub_project_name LIKE ?';
|
||||
params.push(`%${filters.subProjectName}%`);
|
||||
}
|
||||
|
||||
if (filters.riskLevel) {
|
||||
query += ' AND current_risk_level = ?';
|
||||
params.push(filters.riskLevel);
|
||||
}
|
||||
|
||||
db.all(query, params, (err, rows) => {
|
||||
if (err) {
|
||||
console.error('筛选项目数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let query = 'SELECT * FROM projects WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (filters.unit) {
|
||||
query += ' AND unit = ?';
|
||||
params.push(filters.unit);
|
||||
}
|
||||
|
||||
if (filters.constructionUnit) {
|
||||
query += ' AND construction_unit = ?';
|
||||
params.push(filters.constructionUnit);
|
||||
}
|
||||
|
||||
if (filters.subProjectName) {
|
||||
query += ' AND sub_project_name LIKE ?';
|
||||
params.push(`%${filters.subProjectName}%`);
|
||||
}
|
||||
|
||||
if (filters.riskLevel) {
|
||||
query += ' AND current_risk_level = ?';
|
||||
params.push(filters.riskLevel);
|
||||
}
|
||||
|
||||
db.all(query, params, (err, rows) => {
|
||||
if (err) {
|
||||
console.error('筛选项目数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 更新项目数据
|
||||
ipcMain.handle('update-project', (event, project) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `
|
||||
UPDATE projects SET
|
||||
unit = ?,
|
||||
project_number = ?,
|
||||
|
|
@ -469,49 +482,75 @@ ipcMain.handle('update-project', (event, project) => {
|
|||
risk_tips = ?,
|
||||
completion_time = ?,
|
||||
next_review_time = ?,
|
||||
|
||||
new_team = ?,
|
||||
new_members = ?,
|
||||
new_high_altitude = ?,
|
||||
new_hired_general = ?,
|
||||
new_homework_content= ?,
|
||||
change_homework_method = ?,
|
||||
changes_geographical = ?,
|
||||
changes_meteorological = ?,
|
||||
changes_social = ?,
|
||||
changes_management = ?,
|
||||
changes_homework_plan = ?,
|
||||
changes_management_personnel = ?,
|
||||
|
||||
remarks = ?
|
||||
WHERE id = ?
|
||||
`;
|
||||
|
||||
db.run(query, [
|
||||
project.unit,
|
||||
project.project_number,
|
||||
project.safety_code,
|
||||
project.major_project_name,
|
||||
project.sub_project_name,
|
||||
project.construction_scope,
|
||||
project.project_scale,
|
||||
project.safety_director,
|
||||
project.construction_unit,
|
||||
project.supervision_unit,
|
||||
project.construction_company,
|
||||
project.project_location,
|
||||
project.actual_start_time,
|
||||
project.planned_completion_time,
|
||||
project.current_progress,
|
||||
project.current_status,
|
||||
project.participants_count,
|
||||
project.new_team_count,
|
||||
project.new_person_count,
|
||||
project.leader_info,
|
||||
project.next_week_plan,
|
||||
project.next_week_condition,
|
||||
project.is_schedule_tight,
|
||||
project.has_off_book_matters,
|
||||
project.current_risk_level,
|
||||
project.risk_judgment_reason,
|
||||
project.risk_tips,
|
||||
project.completion_time,
|
||||
project.next_review_time,
|
||||
project.remarks,
|
||||
project.id
|
||||
], function(err) {
|
||||
if (err) {
|
||||
console.error('更新项目数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ success: true, changes: this.changes });
|
||||
}
|
||||
|
||||
db.run(query, [
|
||||
project.unit,
|
||||
project.project_number,
|
||||
project.safety_code,
|
||||
project.major_project_name,
|
||||
project.sub_project_name,
|
||||
project.construction_scope,
|
||||
project.project_scale,
|
||||
project.safety_director,
|
||||
project.construction_unit,
|
||||
project.supervision_unit,
|
||||
project.construction_company,
|
||||
project.project_location,
|
||||
project.actual_start_time,
|
||||
project.planned_completion_time,
|
||||
project.current_progress,
|
||||
project.current_status,
|
||||
project.participants_count,
|
||||
project.new_team_count,
|
||||
project.new_person_count,
|
||||
project.leader_info,
|
||||
project.next_week_plan,
|
||||
project.next_week_condition,
|
||||
project.is_schedule_tight,
|
||||
project.has_off_book_matters,
|
||||
project.current_risk_level,
|
||||
project.risk_judgment_reason,
|
||||
project.risk_tips,
|
||||
project.completion_time,
|
||||
project.next_review_time,
|
||||
project.new_team,
|
||||
project.new_members,
|
||||
project.new_high_altitude,
|
||||
project.new_hired_general,
|
||||
project.new_homework_content,
|
||||
project.change_homework_method,
|
||||
project.changes_geographical,
|
||||
project.changes_meteorological,
|
||||
project.changes_social,
|
||||
project.changes_management,
|
||||
project.changes_homework_plan,
|
||||
project.changes_management_personnel,
|
||||
project.remarks,
|
||||
project.id
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error('更新项目数据失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({success: true, changes: this.changes});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
78
src/App.js
78
src/App.js
|
|
@ -13,6 +13,7 @@ import Toolbar from './components/Toolbar';
|
|||
import TreeView from './components/TreeView';
|
||||
import DataView from './components/DataView';
|
||||
import ProjectDetailForm from './components/ProjectDetailForm';
|
||||
import ProjectWarningView from "./components/ProjectWarningView";
|
||||
|
||||
function App() {
|
||||
// 状态管理
|
||||
|
|
@ -40,10 +41,10 @@ function App() {
|
|||
// 获取项目数据
|
||||
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);
|
||||
|
|
@ -67,14 +68,14 @@ function App() {
|
|||
level: 1,
|
||||
children: []
|
||||
};
|
||||
|
||||
|
||||
// 单位映射
|
||||
const unitMap = {};
|
||||
|
||||
|
||||
// 遍历项目数据,构建树状结构
|
||||
projectsData.forEach(project => {
|
||||
const { unit, construction_unit } = project;
|
||||
|
||||
|
||||
// 如果单位不存在,则创建单位节点
|
||||
if (!unitMap[unit]) {
|
||||
unitMap[unit] = {
|
||||
|
|
@ -85,7 +86,7 @@ function App() {
|
|||
};
|
||||
headquarters.children.push(unitMap[unit]);
|
||||
}
|
||||
|
||||
|
||||
// 如果建设单位不为空,则添加建设单位节点
|
||||
if (construction_unit && !unitMap[unit].children.find(child => child.title === construction_unit)) {
|
||||
unitMap[unit].children.push({
|
||||
|
|
@ -96,7 +97,7 @@ function App() {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return [headquarters];
|
||||
};
|
||||
|
||||
|
|
@ -105,10 +106,10 @@ function App() {
|
|||
if (selectedKeys.length > 0) {
|
||||
const key = selectedKeys[0];
|
||||
setSelectedNode(key);
|
||||
|
||||
|
||||
// 根据选中的节点筛选项目数据
|
||||
let filters = {};
|
||||
|
||||
|
||||
if (key === 'headquarters') {
|
||||
// 总部节点,显示所有数据
|
||||
setProjects(projects);
|
||||
|
|
@ -123,7 +124,7 @@ function App() {
|
|||
const constructionUnit = parts.slice(2).join('-');
|
||||
filters = { unit, constructionUnit };
|
||||
}
|
||||
|
||||
|
||||
// 应用筛选
|
||||
filterProjects(filters);
|
||||
}
|
||||
|
|
@ -145,14 +146,14 @@ function App() {
|
|||
// 处理工具栏筛选
|
||||
const handleToolbarFilter = (filter) => {
|
||||
setActiveFilter(filter === activeFilter ? null : filter);
|
||||
|
||||
|
||||
// 根据筛选条件筛选项目数据
|
||||
let filters = {};
|
||||
|
||||
|
||||
if (filter === 'risk') {
|
||||
filters = { riskLevel: '高风险' };
|
||||
}
|
||||
|
||||
|
||||
// 应用筛选
|
||||
filterProjects(filters);
|
||||
};
|
||||
|
|
@ -160,7 +161,7 @@ function App() {
|
|||
// 处理项目搜索
|
||||
const handleSearch = (text) => {
|
||||
setSearchText(text);
|
||||
|
||||
|
||||
// 根据搜索文本筛选项目数据
|
||||
const filters = { subProjectName: text };
|
||||
filterProjects(filters);
|
||||
|
|
@ -170,7 +171,7 @@ function App() {
|
|||
const handleProjectSelect = (selectedRowKeys) => {
|
||||
setSelectedProjects(selectedRowKeys);
|
||||
};
|
||||
|
||||
|
||||
// 处理项目点击,打开详情弹窗
|
||||
const handleProjectClick = (project) => {
|
||||
setCurrentProject(project);
|
||||
|
|
@ -182,7 +183,7 @@ function App() {
|
|||
setLoading(true);
|
||||
try {
|
||||
const result = await window.electronAPI.updateProject(updatedProject);
|
||||
|
||||
|
||||
if (result.success) {
|
||||
// 更新成功,重新加载数据
|
||||
loadData();
|
||||
|
|
@ -202,7 +203,7 @@ function App() {
|
|||
try {
|
||||
// 选择Excel文件
|
||||
const filePath = await window.electronAPI.selectExcelFile();
|
||||
|
||||
|
||||
if (filePath) {
|
||||
// 检查是否重复导入同一个文件
|
||||
if (lastImportedFilePath === filePath) {
|
||||
|
|
@ -233,7 +234,7 @@ function App() {
|
|||
const importFile = async (filePath) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
|
||||
// 创建进度对话框
|
||||
const progressModal = Modal.info({
|
||||
title: '导入Excel',
|
||||
|
|
@ -248,13 +249,13 @@ function App() {
|
|||
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');
|
||||
|
|
@ -262,10 +263,10 @@ function App() {
|
|||
antProgress.style.width = `${data.progress}%`;
|
||||
antProgress.setAttribute('aria-valuenow', data.progress);
|
||||
}
|
||||
|
||||
|
||||
// 更新文本
|
||||
progressMessage.textContent = data.message;
|
||||
|
||||
|
||||
// 如果导入完成,关闭对话框并重新加载数据
|
||||
if (data.status === 'complete') {
|
||||
setTimeout(() => {
|
||||
|
|
@ -284,10 +285,10 @@ function App() {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 导入Excel文件
|
||||
const result = await window.electronAPI.importExcel(filePath);
|
||||
|
||||
|
||||
// 如果导入失败且没有进度事件处理,则显示错误消息
|
||||
if (!result.success) {
|
||||
progressModal.destroy();
|
||||
|
|
@ -319,7 +320,7 @@ function App() {
|
|||
try {
|
||||
// 调用API清除数据
|
||||
const result = await window.electronAPI.clearAllData();
|
||||
|
||||
|
||||
if (result.success) {
|
||||
// 清除成功,重置状态
|
||||
setProjects([]);
|
||||
|
|
@ -328,7 +329,7 @@ function App() {
|
|||
setSelectedProjects([]);
|
||||
setActiveFilter(null);
|
||||
setSearchText('');
|
||||
|
||||
|
||||
// 显示成功提示
|
||||
Modal.success({
|
||||
title: '操作成功',
|
||||
|
|
@ -367,24 +368,24 @@ function App() {
|
|||
>
|
||||
<div className="app-container">
|
||||
{/* 工具栏 */}
|
||||
<Toolbar
|
||||
activeFilter={activeFilter}
|
||||
<Toolbar
|
||||
activeFilter={activeFilter}
|
||||
onFilterChange={handleToolbarFilter}
|
||||
onImportExcel={handleImportExcel}
|
||||
onClearData={handleClearData}
|
||||
/>
|
||||
|
||||
|
||||
{/* 树状结构区 */}
|
||||
<TreeView
|
||||
treeData={treeData}
|
||||
<TreeView
|
||||
treeData={treeData}
|
||||
selectedNode={selectedNode}
|
||||
onSelect={handleTreeSelect}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
|
||||
{/* 数据展示区 */}
|
||||
<DataView
|
||||
projects={projects}
|
||||
<DataView
|
||||
projects={projects}
|
||||
selectedProjects={selectedProjects}
|
||||
onSelect={handleProjectSelect}
|
||||
onClick={handleProjectClick} // 传入点击事件处理函数
|
||||
|
|
@ -392,7 +393,14 @@ function App() {
|
|||
searchText={searchText}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
|
||||
{/* 树状结构区 */}
|
||||
<ProjectWarningView
|
||||
projects={projects}
|
||||
loading={loading}
|
||||
onClick={handleProjectClick} // 传入点击事件处理函数
|
||||
/>
|
||||
|
||||
{/* 项目详情表单 */}
|
||||
<ProjectDetailForm
|
||||
visible={detailModalVisible}
|
||||
|
|
|
|||
|
|
@ -1,146 +1,248 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Modal, Form, Input, Select, Switch, Button } from 'antd';
|
||||
import React, {useEffect} from 'react';
|
||||
import {Modal, Form, Input, Select, Switch, Button} from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
const {Option} = Select;
|
||||
const {TextArea} = Input;
|
||||
|
||||
const ProjectDetailForm = ({ visible, project, onCancel, onSave }) => {
|
||||
const [form] = Form.useForm();
|
||||
const ProjectDetailForm = ({visible, project, onCancel, onSave}) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (project && visible) {
|
||||
// 格式化日期字段为字符串
|
||||
const formattedProject = { ...project };
|
||||
['actual_start_time', 'planned_completion_time', 'completion_time', 'next_review_time'].forEach(field => {
|
||||
if (formattedProject[field]) {
|
||||
formattedProject[field] = dayjs(formattedProject[field]).format('YYYY-MM-DD');
|
||||
useEffect(() => {
|
||||
if (project && visible) {
|
||||
// 格式化日期字段为字符串
|
||||
const formattedProject = {...project};
|
||||
['actual_start_time', 'planned_completion_time', 'completion_time', 'next_review_time'].forEach(field => {
|
||||
if (formattedProject[field]) {
|
||||
formattedProject[field] = dayjs(formattedProject[field]).format('YYYY-MM-DD');
|
||||
}
|
||||
});
|
||||
form.setFieldsValue(formattedProject);
|
||||
}
|
||||
});
|
||||
form.setFieldsValue(formattedProject);
|
||||
}
|
||||
}, [form, project, visible]);
|
||||
}, [form, project, visible]);
|
||||
|
||||
const handleSave = () => {
|
||||
form.validateFields()
|
||||
.then(values => {
|
||||
onSave({ ...project, ...values });
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('验证失败:', info);
|
||||
});
|
||||
};
|
||||
const handleSave = () => {
|
||||
form.validateFields()
|
||||
.then(values => {
|
||||
onSave({...project, ...values});
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('验证失败:', info);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="项目详情"
|
||||
open={visible}
|
||||
width={800}
|
||||
onCancel={onCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="save" type="primary" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item label="单位" name="unit">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="项目编号" name="project_number">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="安全编码" name="safety_code">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="大项工程名称" name="major_project_name">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="单项工程名称" name="sub_project_name">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="在施工程作业范围" name="construction_scope">
|
||||
<TextArea rows={2} />
|
||||
</Form.Item>
|
||||
<Form.Item label="工程规模" name="project_scale">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="安全总监" name="safety_director">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="建设单位" name="construction_unit">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="监理单位" name="supervision_unit">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="施工单位" name="construction_company">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="工程位置" name="project_location">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="实际开工时间" name="actual_start_time">
|
||||
<Input placeholder="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item label="计划竣工时间" name="planned_completion_time">
|
||||
<Input placeholder="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item label="完成时间" name="completion_time">
|
||||
<Input placeholder="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item label="下次梳理时间" name="next_review_time">
|
||||
<Input placeholder="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item label="参建人数" name="participants_count">
|
||||
<Input type="number" />
|
||||
</Form.Item>
|
||||
<Form.Item label="新班组进场数量" name="new_team_count">
|
||||
<Input type="number" />
|
||||
</Form.Item>
|
||||
<Form.Item label="新人进场数量" name="new_person_count">
|
||||
<Input type="number" />
|
||||
</Form.Item>
|
||||
<Form.Item label="带班人姓名、电话" name="leader_info">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="下周作业计划" name="next_week_plan">
|
||||
<TextArea rows={2} />
|
||||
</Form.Item>
|
||||
<Form.Item label="下周8+2工况内容" name="next_week_condition">
|
||||
<TextArea rows={2} />
|
||||
</Form.Item>
|
||||
<Form.Item label="工期是否紧张" name="is_schedule_tight" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label="是否存在账外事" name="has_off_book_matters" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label="当前风险等级" name="current_risk_level">
|
||||
<Select>
|
||||
<Option value="高风险">高风险</Option>
|
||||
<Option value="中风险">中风险</Option>
|
||||
<Option value="低风险">低风险</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="当前风险判断理由" name="risk_judgment_reason">
|
||||
<TextArea rows={2} />
|
||||
</Form.Item>
|
||||
<Form.Item label="隐患提示/工作要求" name="risk_tips">
|
||||
<TextArea rows={2} />
|
||||
</Form.Item>
|
||||
<Form.Item label="备注" name="remarks">
|
||||
<TextArea rows={2} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
return (
|
||||
<Modal
|
||||
title="项目详情"
|
||||
open={visible}
|
||||
width={800}
|
||||
onCancel={onCancel}
|
||||
bodyStyle={{
|
||||
maxHeight: '60vh',
|
||||
overflowY: 'scroll',
|
||||
msOverflowStyle: 'none', // IE, Edge
|
||||
scrollbarWidth: 'none' // Firefox
|
||||
}}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="save" type="primary" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item label="单位" name="unit">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="项目编号" name="project_number">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="安全编码" name="safety_code">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="大项工程名称" name="major_project_name">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="单项工程名称" name="sub_project_name">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="在施工程作业范围" name="construction_scope">
|
||||
<TextArea rows={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="工程规模" name="project_scale">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="安全总监" name="safety_director">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="建设单位" name="construction_unit">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="监理单位" name="supervision_unit">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="施工单位" name="construction_company">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="工程位置" name="project_location">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="实际开工时间" name="actual_start_time">
|
||||
<Input placeholder="YYYY-MM-DD"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="计划竣工时间" name="planned_completion_time">
|
||||
<Input placeholder="YYYY-MM-DD"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="完成时间" name="completion_time">
|
||||
<Input placeholder="YYYY-MM-DD"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="下次梳理时间" name="next_review_time">
|
||||
<Input placeholder="YYYY-MM-DD"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="参建人数" name="participants_count">
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="新班组进场数量" name="new_team_count">
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="新人进场数量" name="new_person_count">
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="带班人姓名、电话" name="leader_info">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="下周作业计划" name="next_week_plan">
|
||||
<TextArea rows={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="下周8+2工况内容" name="next_week_condition">
|
||||
<TextArea rows={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="工期是否紧张" name="is_schedule_tight" valuePropName="checked">
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item label="是否存在账外事" name="has_off_book_matters" valuePropName="checked">
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item label="当前风险等级" name="current_risk_level">
|
||||
<Select>
|
||||
<Option value="高风险">高风险</Option>
|
||||
<Option value="中风险">中风险</Option>
|
||||
<Option value="低风险">低风险</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="当前风险判断理由" name="risk_judgment_reason">
|
||||
<TextArea rows={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="隐患提示/工作要求" name="risk_tips">
|
||||
<TextArea rows={2}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<div>
|
||||
<p style={{fontSize: '20px', fontWeight: 'bold'}}>人的变化</p>
|
||||
<Form.Item label="新进班组数量" name="new_team">
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="新进班组骨干数量" name="new_members">
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="新进高空人员数量" name="new_high_altitude">
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="新进一般人员数量" name="new_hired_general">
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<div>
|
||||
<p style={{fontSize: '20px', fontWeight: 'bold'}}>机的变化</p>
|
||||
<Form.Item label="新的作业内容" name="new_homework_content">
|
||||
<Select allowClear placeholder="请选择新的作业内容">
|
||||
<Option value="同一班组作业类型调整">同一班组作业类型调整</Option>
|
||||
<Option value="在运变电站新增作业">在运变电站新增作业</Option>
|
||||
<Option value="工程转序">工程转序</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="改变作业方法" name="change_homework_method">
|
||||
<Select allowClear placeholder="请选择改变作业方法">
|
||||
<Option value="同一部位基础开挖由机械变为人工">同一部位基础开挖由机械变为人工</Option>
|
||||
<Option
|
||||
value="同一部位铁塔组立由起重机变为抱杆">同一部位铁塔组立由起重机变为抱杆</Option>
|
||||
<Option value="同一跨越物改变跨越方式">同一跨越物改变跨越方式</Option>
|
||||
<Option value="同一放线段改变放线方式">同一放线段改变放线方式</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<div>
|
||||
<p style={{fontSize: '20px', fontWeight: 'bold'}}>环境的变化</p>
|
||||
<Form.Item label="地理环境的变化" name="changes_geographical">
|
||||
<Select allowClear placeholder="请选择地理环境的变化">
|
||||
<Option value="新增带电线路(引起近电作业)">新增带电线路(引起近电作业)</Option>
|
||||
<Option value="新增带电线路(未引起近电作业)">新增带电线路(未引起近电作业)</Option>
|
||||
<Option
|
||||
value="新增跨越物(新投入使用铁路、高速、电力线等线性工程)">新增跨越物(新投入使用铁路、高速、电力线等线性工程)</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="气象环境的变化" name="changes_meteorological">
|
||||
<Select allowClear placeholder="请选择气象环境的变化">
|
||||
<Option value="降雪预警">降雪预警</Option>
|
||||
<Option value="降雨预警">降雨预警</Option>
|
||||
<Option value="大风预警">大风预警</Option>
|
||||
<Option value="高温预警">高温预警</Option>
|
||||
<Option value="低温预警">低温预警</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="社会环境的变化" name="changes_social">
|
||||
<Select allowClear placeholder="请选择社会环境的变化">
|
||||
<Option value="外部协调引起阻工(属地协调不畅)">外部协调引起阻工(属地协调不畅)</Option>
|
||||
<Option value="物资供应滞后">物资供应滞后</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<div>
|
||||
<p style={{fontSize: '20px', fontWeight: 'bold'}}>管理的变化</p>
|
||||
<Form.Item label="管理要求的变化" name="changes_management">
|
||||
<Select allowClear placeholder="请选择管理要求的变化">
|
||||
<Option value="投产计划提前(工期紧张)">投产计划提前(工期紧张)</Option>
|
||||
<Option value="特殊时段作业">特殊时段作业</Option>
|
||||
<Option value="停电计划调整">停电计划调整</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="作业计划的变化" name="changes_homework_plan">
|
||||
<Select allowClear placeholder="请选择作业计划的变化">
|
||||
<Option value="原作业计划未按期执行,顺延至今">原作业计划未按期执行,顺延至今</Option>
|
||||
<Option value="下周作业计划无法执行,临时变更">下周作业计划无法执行,临时变更</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="管理人员的变化" name="changes_management_personnel">
|
||||
<Select allowClear placeholder="请选择管理人员变化">
|
||||
<Option value="施工项目部关键人员变化">施工项目部关键人员变化</Option>
|
||||
<Option value="监理项目部关键人员变化">监理项目部关键人员变化</Option>
|
||||
<Option value="业主项目部关键人员变化">业主项目部关键人员变化</Option>
|
||||
<Option value="施工单位主要负责人、分管领导调整">施工单位主要负责人、分管领导调整</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="备注" name="remarks">
|
||||
<TextArea rows={2}/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectDetailForm;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
|
||||
const ProjectWarningView = ({ projects = [], 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 filteredProjects = projects.filter(hasWarnings);
|
||||
|
||||
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
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: '#aaa',
|
||||
fontSize: '15px',
|
||||
marginTop: '50px',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
暂无项目预警
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectWarningView;
|
||||
|
|
@ -1,62 +1,62 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Tree, Spin } from 'antd';
|
||||
import { FolderOutlined, ApartmentOutlined, HomeOutlined } from '@ant-design/icons';
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import {Tree, Spin} from 'antd';
|
||||
import {FolderOutlined, ApartmentOutlined, HomeOutlined} from '@ant-design/icons';
|
||||
|
||||
const TreeView = ({ treeData, selectedNode, onSelect, loading }) => {
|
||||
const [expandedKeys, setExpandedKeys] = useState([]);
|
||||
const TreeView = ({treeData, selectedNode, onSelect, loading}) => {
|
||||
const [expandedKeys, setExpandedKeys] = useState([]);
|
||||
|
||||
// 默认只展开一级目录
|
||||
useEffect(() => {
|
||||
if (!treeData || treeData.length === 0) return;
|
||||
|
||||
// 只获取第一级节点的key
|
||||
const firstLevelKeys = treeData.map(node => node.key);
|
||||
setExpandedKeys(firstLevelKeys);
|
||||
}, [treeData]);
|
||||
// 自定义树节点图标
|
||||
const getIcon = (node) => {
|
||||
if (node.level === 1) {
|
||||
return <HomeOutlined />;
|
||||
} else if (node.level === 2) {
|
||||
return <ApartmentOutlined />;
|
||||
} else {
|
||||
return <FolderOutlined />;
|
||||
}
|
||||
};
|
||||
// 默认只展开一级目录
|
||||
useEffect(() => {
|
||||
if (!treeData || treeData.length === 0) return;
|
||||
|
||||
// 自定义树节点标题
|
||||
const titleRender = (nodeData) => {
|
||||
return (
|
||||
<span>
|
||||
// 只获取第一级节点的key
|
||||
const firstLevelKeys = treeData.map(node => node.key);
|
||||
setExpandedKeys(firstLevelKeys);
|
||||
}, [treeData]);
|
||||
// 自定义树节点图标
|
||||
const getIcon = (node) => {
|
||||
if (node.level === 1) {
|
||||
return <HomeOutlined/>;
|
||||
} else if (node.level === 2) {
|
||||
return <ApartmentOutlined/>;
|
||||
} else {
|
||||
return <FolderOutlined/>;
|
||||
}
|
||||
};
|
||||
|
||||
// 自定义树节点标题
|
||||
const titleRender = (nodeData) => {
|
||||
return (
|
||||
<span>
|
||||
{getIcon(nodeData)} {nodeData.title}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tree-view">
|
||||
<div style={{ padding: '10px', fontWeight: 'bold', borderBottom: '1px solid var(--vscode-sidebar-border)' }}>
|
||||
项目结构
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
|
||||
<Spin />
|
||||
return (
|
||||
<div className="tree-view">
|
||||
<div style={{padding: '10px', fontWeight: 'bold', borderBottom: '1px solid var(--vscode-sidebar-border)'}}>
|
||||
项目预警
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div style={{display: 'flex', justifyContent: 'center', padding: '20px'}}>
|
||||
<Spin/>
|
||||
</div>
|
||||
) : (
|
||||
<Tree
|
||||
treeData={treeData}
|
||||
selectedKeys={selectedNode ? [selectedNode] : []}
|
||||
onSelect={onSelect}
|
||||
titleRender={titleRender}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={keys => setExpandedKeys(keys)}
|
||||
defaultExpandedKeys={expandedKeys}
|
||||
blockNode
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Tree
|
||||
treeData={treeData}
|
||||
selectedKeys={selectedNode ? [selectedNode] : []}
|
||||
onSelect={onSelect}
|
||||
titleRender={titleRender}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={keys => setExpandedKeys(keys)}
|
||||
defaultExpandedKeys={expandedKeys}
|
||||
blockNode
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default TreeView;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ code {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
.project-warning-view {
|
||||
width: 400px;
|
||||
background-color: var(--vscode-sidebar-bg);
|
||||
border-right: 1px solid var(--vscode-sidebar-border);
|
||||
}
|
||||
|
||||
.data-view {
|
||||
flex: 1;
|
||||
background-color: var(--vscode-editor-bg);
|
||||
|
|
|
|||
Loading…
Reference in New Issue