删除分页和结果分析功能,优化了表格导入的功能
This commit is contained in:
parent
f045fc0d37
commit
879ee40f82
|
|
@ -1 +1,4 @@
|
||||||
node_modules
|
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 loadAppUrl = () => {
|
||||||
const devUrl = 'http://localhost:3000';
|
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', () => {
|
app.on('activate', () => {
|
||||||
// 在macOS上,当点击dock图标并且没有其他窗口打开时,通常在应用程序中重新创建一个窗口
|
// 在macOS上,当点击dock图标并且没有其他窗口打开时,通常在应用程序中重新创建一个窗口
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
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\"",
|
"dev": "concurrently \"npm run start\" \"cross-env BROWSER=none npm run react-start\"",
|
||||||
"react-start": "react-scripts start",
|
"react-start": "react-scripts start",
|
||||||
"react-build": "react-scripts build",
|
"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",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
|
|
@ -32,26 +34,36 @@
|
||||||
"preload.js"
|
"preload.js"
|
||||||
],
|
],
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "assets"
|
"buildResources": "assets",
|
||||||
|
"output": "release"
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": "nsis"
|
"target": "nsis",
|
||||||
}
|
"sign": null
|
||||||
|
},
|
||||||
|
"npmRebuild": false,
|
||||||
|
"extraMetadata": {
|
||||||
|
"main": "main.js"
|
||||||
|
},
|
||||||
|
"asar": true,
|
||||||
|
"forceCodeSigning": false
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^5.7.1",
|
"antd": "^5.7.1",
|
||||||
"sqlite3": "^5.1.6",
|
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
"exceljs": "^4.3.0",
|
"exceljs": "^4.3.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"sqlite3": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.0",
|
"concurrently": "^8.2.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"electron": "^27.3.8",
|
"electron": "^27.3.8",
|
||||||
"electron-builder": "^24.6.3",
|
"electron-builder": "^24.6.3",
|
||||||
|
"electron-packager": "^17.1.2",
|
||||||
|
"electron-rebuild": "^3.2.9",
|
||||||
"react-scripts": "5.0.1"
|
"react-scripts": "5.0.1"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|
|
||||||
12
preload.js
12
preload.js
|
|
@ -10,7 +10,19 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
getTreeStructure: () => ipcRenderer.invoke('get-tree-structure'),
|
getTreeStructure: () => ipcRenderer.invoke('get-tree-structure'),
|
||||||
filterProjects: (filters) => ipcRenderer.invoke('filter-projects', filters),
|
filterProjects: (filters) => ipcRenderer.invoke('filter-projects', filters),
|
||||||
updateProject: (project) => ipcRenderer.invoke('update-project', project),
|
updateProject: (project) => ipcRenderer.invoke('update-project', project),
|
||||||
|
deleteProject: (projectId) => ipcRenderer.invoke('delete-project', projectId),
|
||||||
|
|
||||||
// Excel处理
|
// Excel处理
|
||||||
importExcel: (filePath) => ipcRenderer.invoke('import-excel', filePath),
|
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 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 zhCN from 'antd/locale/zh_CN';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import 'dayjs/locale/zh-cn';
|
import 'dayjs/locale/zh-cn';
|
||||||
|
|
@ -12,7 +12,6 @@ dayjs.locale('zh-cn');
|
||||||
import Toolbar from './components/Toolbar';
|
import Toolbar from './components/Toolbar';
|
||||||
import TreeView from './components/TreeView';
|
import TreeView from './components/TreeView';
|
||||||
import DataView from './components/DataView';
|
import DataView from './components/DataView';
|
||||||
import AnalysisView from './components/AnalysisView';
|
|
||||||
import ProjectDetailForm from './components/ProjectDetailForm';
|
import ProjectDetailForm from './components/ProjectDetailForm';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
@ -206,21 +205,123 @@ function App() {
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
setLoading(true);
|
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文件
|
// 导入Excel文件
|
||||||
const result = await window.electronAPI.importExcel(filePath);
|
const result = await window.electronAPI.importExcel(filePath);
|
||||||
|
|
||||||
if (result.success) {
|
// 如果导入失败且没有进度事件处理,则显示错误消息
|
||||||
// 导入成功,重新加载数据
|
if (!result.success) {
|
||||||
loadData();
|
progressModal.destroy();
|
||||||
} else {
|
message.error(`导入失败: ${result.error}`);
|
||||||
console.error('导入Excel文件失败:', result.error);
|
console.error('导入Excel文件失败:', result.error);
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('导入Excel文件失败:', 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 {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -239,6 +340,7 @@ function App() {
|
||||||
activeFilter={activeFilter}
|
activeFilter={activeFilter}
|
||||||
onFilterChange={handleToolbarFilter}
|
onFilterChange={handleToolbarFilter}
|
||||||
onImportExcel={handleImportExcel}
|
onImportExcel={handleImportExcel}
|
||||||
|
onClearData={handleClearData}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 树状结构区 */}
|
{/* 树状结构区 */}
|
||||||
|
|
@ -260,11 +362,6 @@ function App() {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 分析结果区 */}
|
|
||||||
<AnalysisView
|
|
||||||
selectedProjects={projects.filter(p => selectedProjects.includes(p.id))}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 项目详情表单 */}
|
{/* 项目详情表单 */}
|
||||||
<ProjectDetailForm
|
<ProjectDetailForm
|
||||||
visible={detailModalVisible}
|
visible={detailModalVisible}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,13 @@ const DataView = ({
|
||||||
key: 'current_progress',
|
key: 'current_progress',
|
||||||
width: 120,
|
width: 120,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
render: (text) => {
|
||||||
|
// 如果进度文本过长,只显示前15个字符加省略号
|
||||||
|
if (text && text.length > 15) {
|
||||||
|
return <span title={text}>{text.substring(0, 15)}...</span>;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '当前工程状态',
|
title: '当前工程状态',
|
||||||
|
|
@ -115,17 +122,15 @@ const DataView = ({
|
||||||
selectedRowKeys: selectedProjects,
|
selectedRowKeys: selectedProjects,
|
||||||
onChange: onSelect,
|
onChange: onSelect,
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={false}
|
||||||
pageSize: 20,
|
|
||||||
showSizeChanger: true,
|
|
||||||
showTotal: (total) => `共 ${total} 条数据`,
|
|
||||||
}}
|
|
||||||
scroll={{ y: 'calc(100vh - 150px)', x: 'max-content' }}
|
scroll={{ y: 'calc(100vh - 150px)', x: 'max-content' }}
|
||||||
size="small"
|
size="small"
|
||||||
|
bordered
|
||||||
onRow={(record) => ({
|
onRow={(record) => ({
|
||||||
onClick: () => onClick(record), // 点击行时调用传入的onClick函数
|
onClick: () => onClick(record), // 点击行时调用传入的onClick函数
|
||||||
style: { cursor: 'pointer' } // 鼠标悬停时显示指针样式
|
style: { cursor: 'pointer' } // 鼠标悬停时显示指针样式
|
||||||
})}
|
})}
|
||||||
|
style={{ margin: '10px' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ import {
|
||||||
WarningOutlined,
|
WarningOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
ImportOutlined,
|
ImportOutlined,
|
||||||
SettingOutlined
|
SettingOutlined,
|
||||||
|
ClearOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
const Toolbar = ({ activeFilter, onFilterChange, onImportExcel }) => {
|
const Toolbar = ({ activeFilter, onFilterChange, onImportExcel, onClearData }) => {
|
||||||
// 显示"正在开发中"的提示
|
// 显示"正在开发中"的提示
|
||||||
const showDevelopingMessage = () => {
|
const showDevelopingMessage = () => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
|
|
@ -48,6 +49,12 @@ const Toolbar = ({ activeFilter, onFilterChange, onImportExcel }) => {
|
||||||
icon: <SettingOutlined />,
|
icon: <SettingOutlined />,
|
||||||
title: '设置',
|
title: '设置',
|
||||||
onClick: showDevelopingMessage
|
onClick: showDevelopingMessage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clear',
|
||||||
|
icon: <ClearOutlined />,
|
||||||
|
title: '清除数据',
|
||||||
|
onClick: onClearData
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,22 @@ class ExcelService {
|
||||||
throw new Error('文件路径不能为空');
|
throw new Error('文件路径不能为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送开始导入进度事件
|
||||||
|
if (global.mainWindow) {
|
||||||
|
global.mainWindow.webContents.send('import-progress', {
|
||||||
|
status: 'reading',
|
||||||
|
progress: 0,
|
||||||
|
message: '正在读取Excel文件...'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
await workbook.xlsx.readFile(filePath);
|
await workbook.xlsx.readFile(filePath);
|
||||||
|
|
||||||
// 读取第一个工作表
|
// 读取第一个工作表
|
||||||
const worksheet = workbook.getWorksheet(1);
|
const worksheet = workbook.getWorksheet(1);
|
||||||
const data = [];
|
const data = [];
|
||||||
|
const totalRows = worksheet.rowCount - 1; // 减去表头行
|
||||||
|
|
||||||
// 读取表头
|
// 读取表头
|
||||||
const headers = [];
|
const headers = [];
|
||||||
|
|
@ -33,7 +43,17 @@ class ExcelService {
|
||||||
headers[colNumber - 1] = cell.value;
|
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) => {
|
worksheet.eachRow((row, rowNumber) => {
|
||||||
if (rowNumber > 1) { // 跳过表头
|
if (rowNumber > 1) { // 跳过表头
|
||||||
const rowData = {};
|
const rowData = {};
|
||||||
|
|
@ -41,12 +61,64 @@ class ExcelService {
|
||||||
rowData[headers[colNumber - 1]] = cell.value;
|
rowData[headers[colNumber - 1]] = cell.value;
|
||||||
});
|
});
|
||||||
data.push(rowData);
|
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) {
|
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 {
|
return {
|
||||||
|
|
@ -56,6 +128,16 @@ class ExcelService {
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Excel导入错误:', error);
|
console.error('Excel导入错误:', error);
|
||||||
|
|
||||||
|
// 发送导入错误进度事件
|
||||||
|
if (global.mainWindow) {
|
||||||
|
global.mainWindow.webContents.send('import-progress', {
|
||||||
|
status: 'error',
|
||||||
|
progress: 0,
|
||||||
|
message: `导入失败: ${error.message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error.message
|
error: error.message
|
||||||
|
|
@ -65,10 +147,12 @@ class ExcelService {
|
||||||
|
|
||||||
// 将数据保存到数据库
|
// 将数据保存到数据库
|
||||||
saveDataToDatabase(data) {
|
saveDataToDatabase(data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
// 清空现有数据
|
// 清空现有数据
|
||||||
this.db.run('DELETE FROM projects', (err) => {
|
this.db.run('DELETE FROM projects', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('清空项目数据失败:', err);
|
console.error('清空项目数据失败:', err);
|
||||||
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,49 +196,97 @@ class ExcelService {
|
||||||
this.db.serialize(() => {
|
this.db.serialize(() => {
|
||||||
this.db.run('BEGIN TRANSACTION');
|
this.db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
// 发送进度更新
|
||||||
|
let processedRows = 0;
|
||||||
|
const totalRows = data.length;
|
||||||
|
|
||||||
for (const row of data) {
|
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(
|
stmt.run(
|
||||||
row['单位'] || null,
|
getValue('单位'),
|
||||||
row['项目编号'] || null,
|
getValue('项目编号'),
|
||||||
row['安全编码'] || null,
|
getValue('安全编码'),
|
||||||
row['大项工程名称'] || null,
|
getValue('大项工程名称'),
|
||||||
row['单项工程名称'] || null,
|
getValue('单项工程名称'),
|
||||||
row['在施工程作业范围'] || null,
|
getValue('在施工程作业范围'),
|
||||||
row['工程规模'] || null,
|
getValue('工程规模'),
|
||||||
row['安全总监'] || null,
|
getValue('安全总监'),
|
||||||
row['建设单位'] || null,
|
getValue('建设单位'),
|
||||||
row['监理单位'] || null,
|
getValue('监理单位'),
|
||||||
row['施工单位'] || null,
|
getValue('施工单位'),
|
||||||
row['工程位置'] || null,
|
getValue('工程位置'),
|
||||||
row['实际开工时间'] || null,
|
getValue('实际开工时间'),
|
||||||
row['计划竣工时间'] || null,
|
getValue('计划竣工时间'),
|
||||||
row['当前工程进度'] || null,
|
getValue('当前工程进度'),
|
||||||
row['当前工程状态'] || null,
|
getValue('当前工程状态'),
|
||||||
row['参建人数'] || 0,
|
getNumberValue('参建人数'),
|
||||||
row['新班组进场数量'] || 0,
|
getNumberValue('新班组进场数量'),
|
||||||
row['新人进场数量'] || 0,
|
getNumberValue('新人进场数量'),
|
||||||
row['带班人姓名、电话'] || null,
|
getValue('带班人姓名、电话'),
|
||||||
row['下周作业计划'] || null,
|
getValue('下周作业计划'),
|
||||||
row['下周8+2工况内容'] || null,
|
getValue('下周8+2工况内容'),
|
||||||
row['工期是否紧张'] === '是' ? 1 : 0,
|
getBooleanValue('工期是否紧张'),
|
||||||
row['是否存在"账外事"'] === '是' ? 1 : 0,
|
getBooleanValue('是否存在"账外事"'),
|
||||||
row['当前风险等级'] || null,
|
getValue('当前风险等级'),
|
||||||
row['当前风险判断理由'] || null,
|
getValue('当前风险判断理由'),
|
||||||
row['隐患提示/工作要求'] || null,
|
getValue('隐患提示/工作要求'),
|
||||||
row['完成时间'] || null,
|
getValue('完成时间'),
|
||||||
row['下次梳理时间'] || null,
|
getValue('下次梳理时间'),
|
||||||
row['备注'] || null
|
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) => {
|
this.db.run('COMMIT', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('提交事务失败:', err);
|
console.error('提交事务失败:', err);
|
||||||
this.db.run('ROLLBACK');
|
this.db.run('ROLLBACK');
|
||||||
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
console.log(`成功导入 ${data.length} 条数据到数据库`);
|
console.log(`成功导入 ${data.length} 条数据到数据库`);
|
||||||
// 构建树状结构
|
resolve();
|
||||||
this.buildTreeStructure(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -162,55 +294,98 @@ class ExcelService {
|
||||||
// 完成后关闭准备好的语句
|
// 完成后关闭准备好的语句
|
||||||
stmt.finalize();
|
stmt.finalize();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建树状结构
|
// 构建树状结构
|
||||||
buildTreeStructure(data) {
|
buildTreeStructure(data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
// 清空现有树状结构
|
// 清空现有树状结构
|
||||||
this.db.run('DELETE FROM tree_structure', (err) => {
|
this.db.run('DELETE FROM tree_structure', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('清空树状结构失败:', err);
|
console.error('清空树状结构失败:', err);
|
||||||
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建总部节点
|
// 使用Promise和事务来确保操作的原子性和顺序性
|
||||||
const headquarters = '总部';
|
const headquarters = '总部';
|
||||||
const insertHeadquarters = this.db.prepare(`
|
|
||||||
INSERT INTO tree_structure (
|
// 开始事务
|
||||||
headquarters,
|
this.db.run('BEGIN TRANSACTION', (err) => {
|
||||||
unit,
|
if (err) {
|
||||||
construction_unit,
|
console.error('开始事务失败:', err);
|
||||||
parent_id,
|
reject(err);
|
||||||
node_level
|
return;
|
||||||
) VALUES (?, ?, ?, ?, ?)
|
}
|
||||||
`);
|
|
||||||
|
// 发送进度更新 - 开始创建树状结构
|
||||||
|
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) {
|
if (err) {
|
||||||
console.error('插入总部节点失败:', err);
|
console.error('插入总部节点失败:', err);
|
||||||
|
this.db.run('ROLLBACK');
|
||||||
|
reject(err);
|
||||||
return;
|
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}`);
|
console.log(`创建总部节点成功, ID: ${headquartersId}`);
|
||||||
|
|
||||||
// 单位映射
|
// 单位映射
|
||||||
const unitMap = {};
|
const unitMap = {};
|
||||||
|
|
||||||
// 遍历数据,构建树状结构
|
// 首先处理所有单位节点
|
||||||
for (const row of data) {
|
const uniqueUnits = [...new Set(data.filter(row => row['单位']).map(row => row['单位']))];
|
||||||
const unit = row['单位'];
|
|
||||||
const constructionUnit = row['建设单位'];
|
|
||||||
|
|
||||||
// 检查单位是否为空
|
// 发送进度更新 - 开始创建单位节点
|
||||||
if (!unit) continue;
|
if (global.mainWindow) {
|
||||||
|
global.mainWindow.webContents.send('import-progress', {
|
||||||
|
status: 'building',
|
||||||
|
progress: 80,
|
||||||
|
message: `正在创建单位节点...`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 如果单位不存在,则创建单位节点
|
const processUnits = () => {
|
||||||
if (!unitMap[unit]) {
|
return new Promise((resolve, reject) => {
|
||||||
insertHeadquarters.run(headquarters, unit, null, headquartersId, 2, function(err) {
|
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) {
|
if (err) {
|
||||||
console.error(`创建单位节点失败: ${unit}`, err);
|
console.error(`创建单位节点失败: ${unit}`, err);
|
||||||
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,34 +393,127 @@ class ExcelService {
|
||||||
unitMap[unit] = { id: unitId, constructionUnits: {} };
|
unitMap[unit] = { id: unitId, constructionUnits: {} };
|
||||||
console.log(`创建单位节点: ${unit}, ID: ${unitId}`);
|
console.log(`创建单位节点: ${unit}, ID: ${unitId}`);
|
||||||
|
|
||||||
// 如果建设单位不为空且不存在,则添加建设单位节点
|
unitProcessed++;
|
||||||
if (constructionUnit && !unitMap[unit].constructionUnits[constructionUnit]) {
|
|
||||||
insertHeadquarters.run(headquarters, unit, constructionUnit, unitId, 3, function(err) {
|
// 更新进度
|
||||||
if (err) {
|
if (unitProcessed % 5 === 0 || unitProcessed === uniqueUnits.length) {
|
||||||
console.error(`创建建设单位节点失败: ${constructionUnit}`, err);
|
const progress = Math.floor(80 + (unitProcessed / uniqueUnits.length) * 10); // 80%-90%的进度
|
||||||
} else {
|
if (global.mainWindow) {
|
||||||
unitMap[unit].constructionUnits[constructionUnit] = true;
|
global.mainWindow.webContents.send('import-progress', {
|
||||||
console.log(`创建建设单位节点: ${constructionUnit}, 父节点: ${unit}`);
|
status: 'building',
|
||||||
}
|
progress: progress,
|
||||||
});
|
message: `正在创建单位节点...${unitProcessed}/${uniqueUnits.length}`
|
||||||
}
|
|
||||||
});
|
|
||||||
} 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}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完成后关闭准备好的语句
|
if (unitProcessed === uniqueUnits.length) {
|
||||||
insertHeadquarters.finalize();
|
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('树状结构构建完成');
|
console.log('树状结构构建完成');
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('构建树状结构失败:', err);
|
||||||
|
this.db.run('ROLLBACK');
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,6 @@ code {
|
||||||
overflow-y: auto;
|
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 {
|
.toolbar-icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
|
@ -163,3 +155,61 @@ code {
|
||||||
border-color: #40a9ff !important;
|
border-color: #40a9ff !important;
|
||||||
color: white !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