Electron桌面应用完整方案
完整代码结构
text
project-management-system/
├── package.json
├── main.js
├── preload.js
├── index.html
├── styles.css
└── renderer.js
1. package.json
json
{
"name": "project-management-system",
"version": "1.0.0",
"description": "项目管理信息系统 - 桌面版",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder",
"dev": "electron . --dev"
},
"keywords": ["project", "management", "electron"],
"author": "Your Name",
"license": "MIT",
"devDependencies": {
"electron": "^22.0.0",
"electron-builder": "^24.0.0"
},
"dependencies": {
"fs-extra": "^11.0.0"
},
"build": {
"appId": "com.yourcompany.projectmanagementsystem",
"productName": "项目管理系统",
"directories": {
"output": "dist"
},
"files": [
"**/*",
"!node_modules",
"!dist"
],
"win": {
"target": "nsis",
"icon": "icon.ico"
},
"mac": {
"target": "dmg",
"icon": "icon.icns"
},
"linux": {
"target": "AppImage",
"icon": "icon.png"
}
}
}
2. main.js (Electron主进程)
javascript
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const fs = require('fs-extra');
// 保持对窗口对象的全局引用,避免被垃圾回收
let mainWindow;
// 设置数据存储路径
const DATA_DIR = path.join(app.getPath('documents'), 'ProjectManagementSystem');
const DATA_FILE = path.join(DATA_DIR, 'data.json');
const FILES_DIR = path.join(DATA_DIR, 'uploaded_files');
// 确保目录存在
function ensureDirectories() {
try {
fs.ensureDirSync(DATA_DIR);
fs.ensureDirSync(FILES_DIR);
console.log('数据目录已创建:', DATA_DIR);
console.log('文件目录已创建:', FILES_DIR);
} catch (error) {
console.error('创建目录失败:', error);
}
}
function createWindow() {
// 创建浏览器窗口
mainWindow = new BrowserWindow({
width: 1400,
height: 900,
minWidth: 1200,
minHeight: 700,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, 'icon.png'), // 应用图标
title: '项目管理系统'
});
// 加载应用的 index.html
mainWindow.loadFile('index.html');
// 开发时打开开发者工具
if (process.argv.includes('--dev')) {
mainWindow.webContents.openDevTools();
}
// 当窗口被关闭时触发
mainWindow.on('closed', () => {
// 取消引用 window 对象
mainWindow = null;
});
}
// 当 Electron 完成初始化并准备创建浏览器窗口时调用此方法
app.whenReady().then(() => {
ensureDirectories();
createWindow();
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();
}
});
// IPC 通信处理
// 保存应用数据
ipcMain.handle('save-app-data', async (event, data) => {
try {
await fs.writeJson(DATA_FILE, data, { spaces: 2 });
console.log('数据已保存到:', DATA_FILE);
return { success: true };
} catch (error) {
console.error('保存数据失败:', error);
return { success: false, error: error.message };
}
});
// 加载应用数据
ipcMain.handle('load-app-data', async () => {
try {
if (await fs.pathExists(DATA_FILE)) {
const data = await fs.readJson(DATA_FILE);
console.log('数据已从文件加载:', DATA_FILE);
return { success: true, data };
} else {
console.log('数据文件不存在,使用默认数据');
return { success: true, data: null };
}
} catch (error) {
console.error('加载数据失败:', error);
return { success: false, error: error.message };
}
});
// 保存上传的文件
ipcMain.handle('save-file', async (event, fileInfo) => {
try {
const { fileName, fileData, projectId, description } = fileInfo;
// 生成唯一文件名,避免冲突
const fileExt = path.extname(fileName);
const baseName = path.basename(fileName, fileExt);
const timestamp = Date.now();
const uniqueFileName = `${baseName}_${timestamp}${fileExt}`;
const filePath = path.join(FILES_DIR, uniqueFileName);
// 解码Base64数据并写入文件
const base64Data = fileData.replace(/^data:.*?;base64,/, '');
const buffer = Buffer.from(base64Data, 'base64');
await fs.writeFile(filePath, buffer);
// 获取文件大小
const stats = await fs.stat(filePath);
const fileSize = (stats.size / (1024 * 1024)).toFixed(2) + 'MB';
console.log('文件已保存:', filePath);
return {
success: true,
fileInfo: {
id: timestamp,
name: fileName,
uniqueName: uniqueFileName,
projectId: parseInt(projectId),
uploadDate: new Date().toISOString().split('T')[0],
description: description || '',
size: fileSize,
type: fileExt.substring(1), // 去掉点号
path: filePath
}
};
} catch (error) {
console.error('保存文件失败:', error);
return { success: false, error: error.message };
}
});
// 下载文件
ipcMain.handle('download-file', async (event, fileInfo) => {
try {
const { uniqueName, name } = fileInfo;
const filePath = path.join(FILES_DIR, uniqueName);
if (!await fs.pathExists(filePath)) {
return { success: false, error: '文件不存在' };
}
// 显示保存对话框
const result = await dialog.showSaveDialog(mainWindow, {
defaultPath: name,
filters: [
{ name: '所有文件', extensions: ['*'] }
]
});
if (result.canceled) {
return { success: false, error: '用户取消操作' };
}
// 复制文件到用户选择的位置
await fs.copy(filePath, result.filePath);
console.log('文件已下载到:', result.filePath);
return { success: true, path: result.filePath };
} catch (error) {
console.error('下载文件失败:', error);
return { success: false, error: error.message };
}
});
// 获取存储路径信息
ipcMain.handle('get-storage-paths', () => {
return {
dataPath: DATA_DIR,
filesPath: FILES_DIR
};
});
// 导出数据到文件
ipcMain.handle('export-data', async (event, data) => {
try {
const result = await dialog.showSaveDialog(mainWindow, {
defaultPath: `project_data_${new Date().toISOString().split('T')[0]}.json`,
filters: [
{ name: 'JSON文件', extensions: ['json'] }
]
});
if (result.canceled) {
return { success: false, error: '用户取消操作' };
}
await fs.writeJson(result.filePath, data, { spaces: 2 });
console.log('数据已导出到:', result.filePath);
return { success: true, path: result.filePath };
} catch (error) {
console.error('导出数据失败:', error);
return { success: false, error: error.message };
}
});
// 从文件导入数据
ipcMain.handle('import-data', async () => {
try {
const result = await dialog.showOpenDialog(mainWindow, {
filters: [
{ name: 'JSON文件', extensions: ['json'] }
],
properties: ['openFile']
});
if (result.canceled) {
return { success: false, error: '用户取消操作' };
}
const filePath = result.filePaths[0];
const data = await fs.readJson(filePath);
console.log('数据已从文件导入:', filePath);
return { success: true, data };
} catch (error) {
console.error('导入数据失败:', error);
return { success: false, error: error.message };
}
});
3. preload.js (预加载脚本)
javascript
const { contextBridge, ipcRenderer } = require('electron');
// 暴露受保护的API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 数据操作
saveAppData: (data) => ipcRenderer.invoke('save-app-data', data),
loadAppData: () => ipcRenderer.invoke('load-app-data'),
// 文件操作
saveFile: (fileInfo) => ipcRenderer.invoke('save-file', fileInfo),
downloadFile: (fileInfo) => ipcRenderer.invoke('download-file', fileInfo),
// 数据导入导出
exportData: (data) => ipcRenderer.invoke('export-data', data),
importData: () => ipcRenderer.invoke('import-data'),
// 获取存储路径
getStoragePaths: () => ipcRenderer.invoke('get-storage-paths')
});
4. styles.css (样式文件)
css
:root {
--primary: #00f3ff;
--secondary: #9d4edd;
--accent: #ff006e;
--success: #38b000;
--warning: #ff9e00;
--danger: #ff0054;
--dark: #0a0a1a;
--darker: #050510;
--light: #e2e2e2;
--gray: #8a8a9a;
--card-bg: rgba(26, 26, 46, 0.7);
--card-border: rgba(0, 243, 255, 0.2);
--glow: 0 0 10px rgba(0, 243, 255, 0.5);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: var(--darker);
color: var(--light);
overflow-x: hidden;
min-height: 100vh;
}
/* 科幻背景 */
.sci-fi-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 30%, rgba(157, 78, 221, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(0, 243, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 80%, rgba(255, 0, 110, 0.1) 0%, transparent 50%);
z-index: -1;
}
.sci-fi-bg::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(90deg, transparent 98%, rgba(0, 243, 255, 0.03) 100%),
linear-gradient(0deg, transparent 98%, rgba(0, 243, 255, 0.03) 100%);
background-size: 30px 30px;
}
/* 主应用容器 */
.container {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 250px;
background: rgba(10, 10, 26, 0.9);
border-right: 1px solid var(--card-border);
height: 100vh;
position: fixed;
left: 0;
top: 0;
padding: 20px 0;
backdrop-filter: blur(10px);
z-index: 100;
}
.logo {
padding: 0 20px 30px;
border-bottom: 1px solid var(--card-border);
margin-bottom: 20px;
}
.logo h1 {
color: var(--primary);
font-size: 1.4rem;
}
.nav-links {
list-style: none;
padding: 0 15px;
}
.nav-link {
display: flex;
align-items: center;
padding: 12px 15px;
color: var(--light);
text-decoration: none;
border-radius: 8px;
margin-bottom: 5px;
transition: all 0.3s ease;
}
.nav-link i {
margin-right: 10px;
width: 20px;
text-align: center;
}
.nav-link:hover, .nav-link.active {
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
box-shadow: var(--glow);
}
.main-content {
margin-left: 250px;
padding: 20px;
min-height: 100vh;
width: calc(100% - 250px);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid var(--card-border);
}
.page-title h2 {
color: var(--primary);
margin-bottom: 5px;
}
.page-title p {
color: var(--gray);
}
.user-info {
display: flex;
align-items: center;
gap: 15px;
}
.search-box {
position: relative;
}
.search-box i {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--gray);
}
.search-box input {
padding: 10px 15px 10px 40px;
background: rgba(10, 10, 26, 0.7);
border: 1px solid var(--card-border);
border-radius: 8px;
color: var(--light);
width: 250px;
transition: all 0.3s ease;
}
.search-box input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 8px rgba(0, 243, 255, 0.5);
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(45deg, var(--primary), var(--secondary));
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
cursor: pointer;
}
.user-avatar.floating {
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-5px); }
100% { transform: translateY(0px); }
}
/* 统计卡片 */
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: var(--glow);
}
.stat-card h3 {
color: var(--gray);
font-size: 0.9rem;
margin-bottom: 10px;
}
.stat-card .value {
font-size: 2rem;
font-weight: bold;
color: var(--primary);
margin-bottom: 10px;
}
/* 项目卡片 */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.project-card {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: var(--glow);
}
.project-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.project-title {
flex: 1;
position: relative;
}
.project-title h4 {
color: var(--light);
margin-bottom: 5px;
padding-right: 20px;
}
.project-title p {
color: var(--gray);
font-size: 0.9rem;
}
.project-actions {
display: flex;
gap: 5px;
}
.action-btn {
background: transparent;
border: 1px solid var(--card-border);
color: var(--gray);
width: 30px;
height: 30px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.action-btn:hover {
color: var(--primary);
border-color: var(--primary);
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
}
.project-status {
padding: 4px 10px;
border-radius: 20px;
font-size: 0.7rem;
font-weight: bold;
margin-top: 5px;
}
.status-planning {
background: rgba(255, 158, 0, 0.1);
color: var(--warning);
border: 1px solid rgba(255, 158, 0, 0.3);
}
.status-progress {
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
border: 1px solid rgba(0, 243, 255, 0.3);
}
.status-review {
background: rgba(157, 78, 221, 0.1);
color: var(--secondary);
border: 1px solid rgba(157, 78, 221, 0.3);
}
.status-completed {
background: rgba(56, 176, 0, 0.1);
color: var(--success);
border: 1px solid rgba(56, 176, 0, 0.3);
}
.project-progress {
margin-bottom: 15px;
}
.progress-bar {
height: 8px;
background: rgba(10, 10, 26, 0.7);
border-radius: 4px;
overflow: hidden;
margin-bottom: 8px;
}
.progress {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--secondary));
border-radius: 4px;
transition: width 0.5s ease;
}
.progress-info {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: var(--gray);
}
.project-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.project-team {
display: flex;
}
.team-member {
width: 28px;
height: 28px;
border-radius: 50%;
background: linear-gradient(45deg, var(--primary), var(--secondary));
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
margin-right: -8px;
border: 2px solid var(--card-bg);
}
/* 项目变更记录样式 */
.change-log {
margin-top: 15px;
max-height: 120px;
overflow-y: auto;
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: 10px;
}
.change-log h4 {
color: var(--primary);
font-size: 0.8rem;
margin-bottom: 8px;
}
.change-log-item {
padding: 5px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
font-size: 0.7rem;
line-height: 1.3;
}
.change-log-item:last-child {
border-bottom: none;
}
.change-log-item span {
color: var(--gray);
}
.change-log-item .date {
color: var(--primary);
font-weight: bold;
}
.change-log-item .user {
color: var(--secondary);
}
/* 滚动条样式 */
.change-log::-webkit-scrollbar {
width: 4px;
}
.change-log::-webkit-scrollbar-track {
background: rgba(10, 10, 26, 0.5);
border-radius: 2px;
}
.change-log::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 2px;
}
.change-log::-webkit-scrollbar-thumb:hover {
background: var(--secondary);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.section-header h3 {
color: var(--light);
}
.btn {
background: linear-gradient(45deg, var(--primary), var(--secondary));
border: none;
color: var(--darker);
padding: 10px 15px;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 243, 255, 0.4);
}
.btn-secondary {
background: rgba(26, 26, 46, 0.7);
color: var(--light);
border: 1px solid var(--card-border);
}
.btn-secondary:hover {
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
border-color: var(--primary);
box-shadow: 0 0 8px rgba(0, 243, 255, 0.5);
}
/* 任务和团队布局 */
.tasks-section {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 30px;
}
.task-list, .team-section {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
max-height: 400px;
overflow-y: auto;
}
.task-items {
max-height: 300px;
overflow-y: auto;
}
.task-item {
padding: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.task-item:last-child {
border-bottom: none;
}
.task-info {
flex: 1;
}
.task-info h4 {
color: var(--light);
margin-bottom: 5px;
padding-right: 20px;
}
.task-info p {
color: var(--gray);
font-size: 0.8rem;
}
.task-dates {
font-size: 0.7rem;
color: var(--gray);
margin-top: 3px;
}
.task-priority {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: bold;
}
.priority-high {
background: rgba(255, 0, 84, 0.1);
color: var(--danger);
border: 1px solid rgba(255, 0, 84, 0.3);
}
.priority-medium {
background: rgba(255, 158, 0, 0.1);
color: var(--warning);
border: 1px solid rgba(255, 158, 0, 0.3);
}
.priority-low {
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
border: 1px solid rgba(0, 243, 255, 0.3);
}
.team-members {
display: flex;
flex-direction: column;
gap: 15px;
max-height: 300px;
overflow-y: auto;
}
.team-member-card {
display: flex;
align-items: center;
padding: 15px;
background: rgba(10, 10, 26, 0.5);
border-radius: 8px;
border: 1px solid var(--card-border);
transition: all 0.3s ease;
}
.team-member-card:hover {
transform: translateX(5px);
border-color: var(--primary);
}
.member-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(45deg, var(--primary), var(--secondary));
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 15px;
}
.member-info {
flex: 1;
}
.member-info h4 {
color: var(--light);
margin-bottom: 5px;
}
.member-info p {
color: var(--gray);
font-size: 0.8rem;
}
.member-role {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.7rem;
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
border: 1px solid rgba(0, 243, 255, 0.3);
}
/* 任务看板 */
.task-board {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.task-column {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
}
.column-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--card-border);
}
.column-header h3 {
color: var(--light);
font-size: 1rem;
}
.task-count {
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
width: 25px;
height: 25px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: bold;
}
.column-tasks {
display: flex;
flex-direction: column;
gap: 15px;
}
/* 日历 */
.calendar-container {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.calendar-nav {
display: flex;
gap: 10px;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
}
.calendar-day-header {
text-align: center;
padding: 10px;
color: var(--gray);
font-weight: bold;
}
.calendar-day {
height: 80px;
border: 1px solid var(--card-border);
border-radius: 8px;
padding: 8px;
background: rgba(10, 10, 26, 0.5);
transition: all 0.3s ease;
position: relative;
}
.calendar-day:hover {
background: rgba(0, 243, 255, 0.1);
border-color: var(--primary);
}
.calendar-day.today {
border-color: var(--primary);
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
}
.calendar-event {
background: rgba(157, 78, 221, 0.2);
border-left: 3px solid var(--secondary);
padding: 3px 5px;
margin-top: 5px;
border-radius: 3px;
font-size: 0.7rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 横道图容器样式 - 优化版 */
.gantt-container {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
overflow: hidden;
}
.gantt-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.gantt-scroll-container {
overflow-x: auto;
position: relative;
padding-bottom: 10px;
min-height: 400px;
border: 1px solid var(--card-border);
border-radius: 8px;
background: rgba(10, 10, 26, 0.5);
}
.gantt-timeline {
display: flex;
margin-bottom: 10px;
position: relative;
height: 30px;
min-width: 100%;
}
.gantt-timeline-item {
flex: 0 0 60px;
text-align: center;
font-size: 0.7rem;
color: var(--gray);
border-left: 1px solid var(--card-border);
padding-left: 5px;
position: relative;
display: flex;
align-items: flex-end;
justify-content: center;
height: 100%;
}
.gantt-timeline-item.major {
font-weight: bold;
color: var(--primary);
border-left: 1px solid var(--primary);
}
.gantt-chart {
position: relative;
min-width: 100%;
}
.gantt-task {
display: flex;
margin-bottom: 25px;
align-items: center;
position: relative;
min-height: 30px;
}
.gantt-task-name {
width: 200px;
padding-right: 15px;
flex-shrink: 0;
color: var(--light);
font-size: 0.9rem;
font-weight: bold;
}
.gantt-task-bar-container {
flex: 1;
height: 25px;
position: relative;
overflow: visible;
border-left: 1px solid var(--card-border);
border-right: 1px solid var(--card-border);
background: rgba(10, 10, 26, 0.3);
}
.gantt-task-bar {
height: 25px;
background: rgba(10, 10, 26, 0.7);
border-radius: 4px;
position: absolute;
top: 0;
overflow: hidden;
min-width: 20px;
}
.gantt-progress {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--secondary));
border-radius: 4px;
}
.gantt-date-start, .gantt-date-end {
position: absolute;
top: -20px;
font-size: 0.7rem;
color: var(--gray);
white-space: nowrap;
background: rgba(10, 10, 26, 0.8);
padding: 2px 5px;
border-radius: 3px;
z-index: 5;
}
.gantt-date-start {
left: 0;
transform: translateX(-50%);
}
.gantt-date-end {
right: 0;
transform: translateX(50%);
}
.gantt-current-date {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
background-color: var(--accent);
z-index: 10;
}
.gantt-current-date::after {
content: '今天';
position: absolute;
top: -20px;
left: -10px;
font-size: 0.7rem;
color: var(--accent);
white-space: nowrap;
background: rgba(10, 10, 26, 0.8);
padding: 2px 5px;
border-radius: 3px;
}
.gantt-scroll-controls {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 15px;
}
.gantt-scroll-btn {
padding: 5px 10px;
background: rgba(26, 26, 46, 0.7);
border: 1px solid var(--card-border);
border-radius: 6px;
color: var(--light);
cursor: pointer;
transition: all 0.3s ease;
}
.gantt-scroll-btn:hover {
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
border-color: var(--primary);
}
/* 数据管理样式 */
.data-management-section {
margin-top: 20px;
padding: 20px;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
}
.data-actions {
display: flex;
gap: 15px;
margin-top: 15px;
}
.data-info {
margin-top: 15px;
padding: 15px;
background: rgba(10, 10, 26, 0.5);
border-radius: 8px;
font-size: 0.9rem;
}
.data-info h4 {
color: var(--primary);
margin-bottom: 10px;
}
.data-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin-top: 10px;
}
.data-stat {
padding: 10px;
background: rgba(0, 243, 255, 0.1);
border-radius: 6px;
text-align: center;
}
.data-stat .value {
font-size: 1.2rem;
font-weight: bold;
color: var(--primary);
}
.data-stat .label {
font-size: 0.8rem;
color: var(--gray);
}
/* 文件上传区域样式 */
.file-upload-area {
border: 2px dashed var(--card-border);
border-radius: 8px;
padding: 30px;
text-align: center;
margin: 15px 0;
transition: all 0.3s ease;
cursor: pointer;
}
.file-upload-area:hover {
border-color: var(--primary);
background: rgba(0, 243, 255, 0.05);
}
.file-upload-area i {
font-size: 2rem;
color: var(--primary);
margin-bottom: 10px;
}
.file-upload-area p {
color: var(--gray);
margin-bottom: 10px;
}
/* 文件列表样式 */
.file-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 20px;
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
background: rgba(10, 10, 26, 0.5);
border-radius: 8px;
border: 1px solid var(--card-border);
transition: all 0.3s ease;
}
.file-item:hover {
border-color: var(--primary);
transform: translateY(-2px);
}
.file-info {
display: flex;
align-items: center;
gap: 15px;
}
.file-icon {
font-size: 1.5rem;
color: var(--primary);
}
.file-details h4 {
color: var(--light);
margin-bottom: 5px;
}
.file-details p {
color: var(--gray);
font-size: 0.8rem;
}
.file-actions {
display: flex;
gap: 10px;
}
/* 延期提醒样式 */
.project-overdue::after {
content: '!';
position: absolute;
top: 5px;
right: 5px;
width: 16px;
height: 16px;
background: var(--danger);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: bold;
}
.project-soon-due::after {
content: '!';
position: absolute;
top: 5px;
right: 5px;
width: 16px;
height: 16px;
background: var(--warning);
color: var(--darker);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: bold;
}
.task-overdue::after {
content: '!';
position: absolute;
top: 5px;
right: 5px;
width: 16px;
height: 16px;
background: var(--danger);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: bold;
}
.task-soon-due::after {
content: '!';
position: absolute;
top: 5px;
right: 5px;
width: 16px;
height: 16px;
background: var(--warning);
color: var(--darker);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: bold;
}
/* 任务过滤器 */
.task-filter {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.filter-btn {
padding: 8px 16px;
background: rgba(26, 26, 46, 0.7);
border: 1px solid var(--card-border);
border-radius: 6px;
color: var(--light);
cursor: pointer;
transition: all 0.3s ease;
}
.filter-btn.active {
background: rgba(0, 243, 255, 0.1);
color: var(--primary);
border-color: var(--primary);
}
/* 模态框 */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(5, 5, 16, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(5px);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 15px;
width: 90%;
max-width: 500px;
box-shadow: var(--glow);
transform: translateY(-20px);
transition: transform 0.3s ease;
}
.modal.active .modal-content {
transform: translateY(0);
}
.modal-header {
padding: 20px;
border-bottom: 1px solid var(--card-border);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
color: var(--primary);
}
.close-modal {
background: transparent;
border: none;
color: var(--gray);
font-size: 1.5rem;
cursor: pointer;
transition: color 0.3s ease;
}
.close-modal:hover {
color: var(--danger);
}
.modal-body {
padding: 20px;
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
/* 表单组样式 */
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: var(--light);
font-size: 0.9rem;
}
.form-control {
width: 100%;
padding: 10px 12px;
background: rgba(10, 10, 26, 0.7);
border: 1px solid var(--card-border);
border-radius: 6px;
color: var(--light);
font-size: 0.9rem;
transition: all 0.3s ease;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 8px rgba(0, 243, 255, 0.5);
}
/* 通知样式 */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 8px;
color: var(--light);
box-shadow: var(--glow);
z-index: 1100;
transform: translateX(150%);
transition: transform 0.3s ease;
}
.notification.show {
transform: translateX(0);
}
.notification.success {
border-left: 4px solid var(--success);
}
.notification.error {
border-left: 4px solid var(--danger);
}
/* 页面内容 */
.page-content {
display: none;
}
.page-content.active {
display: block;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 响应式设计 */
@media (max-width: 1024px) {
.sidebar {
width: 70px;
}
.sidebar .logo h1,
.sidebar .nav-link span {
display: none;
}
.sidebar .nav-link {
justify-content: center;
padding: 15px;
}
.sidebar .nav-link i {
margin-right: 0;
font-size: 1.2rem;
}
.main-content {
margin-left: 70px;
width: calc(100% - 70px);
}
.tasks-section {
grid-template-columns: 1fr;
}
.task-board {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.stats-cards {
grid-template-columns: 1fr;
}
.projects-grid {
grid-template-columns: 1fr;
}
.header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.user-info {
width: 100%;
justify-content: space-between;
}
.search-box input {
width: 200px;
}
.data-actions {
flex-direction: column;
}
.data-stats {
grid-template-columns: 1fr 1fr;
}
.gantt-task-name {
width: 150px;
}
.gantt-timeline-item {
flex: 0 0 40px;
}
}
/* 本地存储信息样式 */
.local-storage-info {
background: rgba(0, 243, 255, 0.1);
border: 1px solid var(--primary);
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
.local-storage-info h4 {
color: var(--primary);
margin-bottom: 10px;
}
.local-storage-info p {
color: var(--light);
margin-bottom: 5px;
font-size: 0.9rem;
}
.local-storage-info .path {
font-family: monospace;
background: rgba(10, 10, 26, 0.5);
padding: 5px 10px;
border-radius: 4px;
display: inline-block;
margin-top: 5px;
}
5. renderer.js (渲染进程逻辑)
javascript
// 数据模型
const appData = {
projects: [],
tasks: [],
teamMembers: [],
calendarEvents: [],
files: [],
currentUser: '管理员'
};
// 初始化应用
async function initApp() {
// 设置事件监听器
setupEventListeners();
// 获取存储路径信息
const paths = await window.electronAPI.getStoragePaths();
document.getElementById('storagePath').textContent = paths.dataPath;
document.getElementById('filesPath').textContent = paths.filesPath;
document.getElementById('storagePath2').textContent = paths.dataPath;
document.getElementById('filesPath2').textContent = paths.filesPath;
// 初始化数据
await initData();
// 设置数据管理功能
setupDataManagement();
}
// 初始化数据
async function initData() {
// 尝试从本地文件加载数据
const result = await window.electronAPI.loadAppData();
if (result.success && result.data) {
// 使用从文件加载的数据
const loadedData = result.data;
appData.projects = loadedData.projects || [];
appData.tasks = loadedData.tasks || [];
appData.teamMembers = loadedData.teamMembers || [];
appData.calendarEvents = loadedData.calendarEvents || [];
appData.files = loadedData.files || [];
console.log('数据已从本地文件加载');
} else {
// 使用默认数据
appData.projects = [
{
id: 1,
name: "网站重构项目",
department: "技术部",
manager: "黄晓威",
progress: 65,
startDate: "2025-10-01",
deadline: "2025-12-15",
status: "progress",
team: ["黄", "胡", "唐", "+2"],
completedTasks: 12,
totalTasks: 18,
description: "重构公司官网,提升用户体验和性能",
changeLog: [
{ date: "2025-10-01", user: "黄晓威", action: "创建项目" },
{ date: "2025-10-15", user: "黄晓威", action: "更新项目进度至30%" },
{ date: "2025-11-01", user: "黄晓威", action: "更新项目进度至65%" },
{ date: "2025-11-05", user: "胡镇", action: "添加新功能需求" }
]
},
{
id: 2,
name: "移动应用开发",
department: "产品部",
manager: "胡镇",
progress: 80,
startDate: "2025-09-15",
deadline: "2025-11-30",
status: "review",
team: ["胡", "余", "+3"],
completedTasks: 24,
totalTasks: 30,
description: "开发新一代移动应用,支持iOS和Android平台",
changeLog: [
{ date: "2025-09-15", user: "胡镇", action: "创建项目" },
{ date: "2025-10-10", user: "胡镇", action: "更新项目进度至60%" },
{ date: "2025-10-25", user: "胡镇", action: "更新项目进度至80%" }
]
},
{
id: 3,
name: "市场推广活动",
department: "市场部",
manager: "唐左琪",
progress: 20,
startDate: "2025-11-01",
deadline: "2026-01-20",
status: "planning",
team: ["唐", "文", "+1"],
completedTasks: 5,
totalTasks: 25,
description: "策划并执行新产品市场推广活动",
changeLog: [
{ date: "2025-11-01", user: "唐左琪", action: "创建项目" },
{ date: "2025-11-05", user: "唐左琪", action: "确定市场策略" }
]
}
];
appData.tasks = [
{
id: 1,
name: "完成用户登录模块开发",
projectId: 1,
startDate: "2025-11-08",
deadline: "2025-11-15",
priority: "high",
status: "pending",
description: "实现用户登录、注册和密码找回功能"
},
{
id: 2,
name: "设计应用主界面原型",
projectId: 2,
startDate: "2025-11-05",
deadline: "2025-11-10",
priority: "medium",
status: "completed",
description: "设计应用主要界面的UI原型和交互流程"
},
{
id: 3,
name: "制定市场推广策略",
projectId: 3,
startDate: "2025-11-05",
deadline: "2025-11-20",
priority: "low",
status: "pending",
description: "分析目标市场并制定详细的推广策略"
},
{
id: 4,
name: "编写项目进度报告",
projectId: 1,
startDate: "2025-11-01",
deadline: "2025-11-12",
priority: "medium",
status: "progress",
description: "汇总项目进展并编写进度报告"
},
{
id: 5,
name: "API接口设计",
projectId: 2,
startDate: "2025-11-10",
deadline: "2025-11-18",
priority: "medium",
status: "progress",
description: "设计后端API接口和数据传输格式"
},
{
id: 6,
name: "用户需求收集",
projectId: 1,
startDate: "2025-11-03",
deadline: "2025-11-07",
priority: "low",
status: "completed",
description: "收集并整理用户对新功能的需求"
}
];
appData.teamMembers = [
{ id: 1, name: "黄晓威", role: "技术主管", avatar: "黄", skills: ["项目管理", "团队领导"] },
{ id: 2, name: "胡镇", role: "项目经理", avatar: "胡", skills: ["项目管理", "需求分析", "任务分解"] },
{ id: 3, name: "唐左琪", role: "项目经理", avatar: "唐", skills: ["项目管理", "需求分析", "任务分解"] },
{ id: 4, name: "余传意", role: "技术支持", avatar: "余", skills: ["技术专家", "需求收集", "团队合作"] }
];
appData.calendarEvents = [
{ id: 1, date: "2025-11-10", title: "项目评审会议", description: "评审移动应用开发项目进度" },
{ id: 2, date: "2025-11-15", title: "用户登录模块交付", description: "网站重构项目的用户登录模块交付" }
];
appData.files = [
{
id: 1,
name: "项目需求文档.docx",
projectId: 1,
uploadDate: "2025-10-05",
description: "网站重构项目的详细需求文档",
size: "2.5MB",
type: "docx",
uniqueName: "项目需求文档_1696521600000.docx"
},
{
id: 2,
name: "UI设计稿.pdf",
projectId: 2,
uploadDate: "2025-09-25",
description: "移动应用UI设计稿",
size: "5.2MB",
type: "pdf",
uniqueName: "UI设计稿_1695600000000.pdf"
},
{
id: 3,
name: "市场调研报告.xlsx",
projectId: 3,
uploadDate: "2025-11-03",
description: "新产品市场调研数据和分析报告",
size: "1.8MB",
type: "xlsx",
uniqueName: "市场调研报告_1698969600000.xlsx"
}
];
// 保存默认数据到本地文件
await saveData();
}
// 初始化日历日期
window.currentCalendarDate = new Date();
updateStats();
renderDashboard();
renderProjectsPage();
renderTasksPage();
renderTeamPage();
renderCalendar();
renderProgressPage();
renderReportsPage();
}
// 保存数据到本地文件
async function saveData() {
const result = await window.electronAPI.saveAppData(appData);
if (!result.success) {
console.error('保存数据失败:', result.error);
showNotification('保存数据失败: ' + result.error, 'error');
}
}
// 更新统计信息
function updateStats() {
// 进行中项目
const activeProjects = appData.projects.filter(p => p.status === 'progress').length;
document.getElementById('activeProjectsCount').textContent = activeProjects;
// 已完成任务
const completedTasks = appData.tasks.filter(t => t.status === 'completed').length;
document.getElementById('completedTasksCount').textContent = completedTasks;
// 逾期项目
const today = new Date().toISOString().split('T')[0];
const overdueProjects = appData.projects.filter(p => p.deadline < today && p.status !== 'completed').length;
document.getElementById('overdueProjectsCount').textContent = overdueProjects;
// 团队效率
const totalTasks = appData.tasks.length;
const completed = appData.tasks.filter(t => t.status === 'completed').length;
const efficiency = totalTasks > 0 ? Math.round((completed / totalTasks) * 100) : 0;
document.getElementById('teamEfficiency').textContent = efficiency + '%';
}
// 设置数据管理功能
function setupDataManagement() {
// 数据管理按钮事件
document.getElementById('exportDataBtn').addEventListener('click', exportData);
document.getElementById('copyDataBtn').addEventListener('click', copyDataToClipboard);
document.getElementById('importDataBtn').addEventListener('click', importData);
document.getElementById('resetDataBtn').addEventListener('click', resetToSampleData);
// 文件上传区域事件
const fileUploadArea = document.getElementById('fileUploadArea');
const fileInput = document.getElementById('fileInput');
fileUploadArea.addEventListener('click', () => {
fileInput.click();
});
fileUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
fileUploadArea.style.borderColor = 'var(--primary)';
fileUploadArea.style.background = 'rgba(0, 243, 255, 0.1)';
});
fileUploadArea.addEventListener('dragleave', () => {
fileUploadArea.style.borderColor = 'var(--card-border)';
fileUploadArea.style.background = '';
});
fileUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
fileUploadArea.style.borderColor = 'var(--card-border)';
fileUploadArea.style.background = '';
if (e.dataTransfer.files.length > 0) {
fileInput.files = e.dataTransfer.files;
handleFileSelect();
}
});
fileInput.addEventListener('change', handleFileSelect);
// 数据管理页面按钮事件
document.getElementById('exportDataBtn2').addEventListener('click', exportData);
document.getElementById('copyDataBtn2').addEventListener('click', copyDataToClipboard);
document.getElementById('importDataBtn2').addEventListener('click', importData);
document.getElementById('resetDataBtn2').addEventListener('click', resetToSampleData);
// 文件上传区域事件 - 页面版本
const fileUploadArea2 = document.getElementById('fileUploadArea2');
const fileInput2 = document.getElementById('fileInput2');
fileUploadArea2.addEventListener('click', () => {
fileInput2.click();
});
fileUploadArea2.addEventListener('dragover', (e) => {
e.preventDefault();
fileUploadArea2.style.borderColor = 'var(--primary)';
fileUploadArea2.style.background = 'rgba(0, 243, 255, 0.1)';
});
fileUploadArea2.addEventListener('dragleave', () => {
fileUploadArea2.style.borderColor = 'var(--card-border)';
fileUploadArea2.style.background = '';
});
fileUploadArea2.addEventListener('drop', (e) => {
e.preventDefault();
fileUploadArea2.style.borderColor = 'var(--card-border)';
fileUploadArea2.style.background = '';
if (e.dataTransfer.files.length > 0) {
fileInput2.files = e.dataTransfer.files;
handleFileSelect2();
}
});
fileInput2.addEventListener('change', handleFileSelect2);
// 更新数据统计
updateDataStats();
}
// 处理文件选择
function handleFileSelect() {
const fileInput = document.getElementById('fileInput');
const importDataBtn = document.getElementById('importDataBtn');
if (fileInput.files.length > 0) {
const file = fileInput.files[0];
if (file.type === 'application/json' || file.name.endsWith('.json')) {
importDataBtn.disabled = false;
showNotification('文件已选择,点击"导入数据"按钮导入', 'success');
} else {
importDataBtn.disabled = true;
showNotification('请选择JSON格式的文件', 'error');
}
} else {
importDataBtn.disabled = true;
}
}
// 处理文件选择 - 页面版本
function handleFileSelect2() {
const fileInput2 = document.getElementById('fileInput2');
const importDataBtn2 = document.getElementById('importDataBtn2');
if (fileInput2.files.length > 0) {
const file = fileInput2.files[0];
if (file.type === 'application/json' || file.name.endsWith('.json')) {
importDataBtn2.disabled = false;
showNotification('文件已选择,点击"导入数据"按钮导入', 'success');
} else {
importDataBtn2.disabled = true;
showNotification('请选择JSON格式的文件', 'error');
}
} else {
importDataBtn2.disabled = true;
}
}
// 导出数据
async function exportData() {
const dataToExport = {
projects: appData.projects,
tasks: appData.tasks,
teamMembers: appData.teamMembers,
calendarEvents: appData.calendarEvents,
files: appData.files,
exportDate: new Date().toISOString(),
version: '1.0'
};
const result = await window.electronAPI.exportData(dataToExport);
if (result.success) {
showNotification(`数据已导出到: ${result.path}`, 'success');
} else {
showNotification('导出数据失败: ' + result.error, 'error');
}
}
// 复制数据到剪贴板
function copyDataToClipboard() {
const dataToExport = {
projects: appData.projects,
tasks: appData.tasks,
teamMembers: appData.teamMembers,
calendarEvents: appData.calendarEvents,
files: appData.files,
exportDate: new Date().toISOString(),
version: '1.0'
};
const dataStr = JSON.stringify(dataToExport, null, 2);
navigator.clipboard.writeText(dataStr).then(() => {
showNotification('数据已复制到剪贴板!', 'success');
}).catch(err => {
console.error('复制失败: ', err);
showNotification('复制失败,请手动复制', 'error');
});
}
// 导入数据
async function importData() {
const result = await window.electronAPI.importData();
if (result.success) {
// 确认导入
if (confirm('导入数据将覆盖当前所有数据,确定要继续吗?')) {
// 更新应用数据
appData.projects = result.data.projects || [];
appData.tasks = result.data.tasks || [];
appData.teamMembers = result.data.teamMembers || [];
appData.calendarEvents = result.data.calendarEvents || [];
appData.files = result.data.files || [];
// 保存到本地存储
await saveData();
// 更新界面
updateStats();
renderDashboard();
renderProjectsPage();
renderTasksPage();
renderTeamPage();
renderCalendar();
renderProgressPage();
renderReportsPage();
updateDataStats();
// 重置文件输入
document.getElementById('fileInput').value = '';
document.getElementById('fileInput2').value = '';
document.getElementById('importDataBtn').disabled = true;
document.getElementById('importDataBtn2').disabled = true;
showNotification('数据导入成功!', 'success');
}
} else {
if (result.error !== '用户取消操作') {
showNotification('导入数据失败: ' + result.error, 'error');
}
}
}
// 重置为示例数据
async function resetToSampleData() {
if (confirm('这将重置所有数据为示例数据,当前数据将丢失。确定要继续吗?')) {
// 重新初始化数据
await initData();
// 更新数据统计
updateDataStats();
showNotification('已重置为示例数据', 'success');
}
}
// 更新数据统计
function updateDataStats() {
document.getElementById('projectsCount').textContent = appData.projects.length;
document.getElementById('tasksCount').textContent = appData.tasks.length;
document.getElementById('teamMembersCount').textContent = appData.teamMembers.length;
document.getElementById('eventsCount').textContent = appData.calendarEvents.length;
document.getElementById('projectsCount2').textContent = appData.projects.length;
document.getElementById('tasksCount2').textContent = appData.tasks.length;
document.getElementById('teamMembersCount2').textContent = appData.teamMembers.length;
document.getElementById('eventsCount2').textContent = appData.calendarEvents.length;
}
// 渲染仪表盘
function renderDashboard() {
renderDashboardProjects();
renderDashboardTasks();
renderDashboardTeam();
}
// 渲染仪表盘项目
function renderDashboardProjects() {
const container = document.getElementById('dashboardProjects');
container.innerHTML = '';
// 只显示前6个项目
const projectsToShow = appData.projects.slice(0, 6);
projectsToShow.forEach(project => {
const card = createProjectCard(project);
container.appendChild(card);
});
}
// 渲染仪表盘任务 - 显示前后3天的任务
function renderDashboardTasks() {
const container = document.getElementById('dashboardTasks');
container.innerHTML = '';
// 计算前后3天的日期范围
const today = new Date();
const startDate = new Date(today);
startDate.setDate(today.getDate() - 3);
const endDate = new Date(today);
endDate.setDate(today.getDate() + 3);
// 格式化日期为 YYYY-MM-DD
const formatDate = (date) => {
return date.toISOString().split('T')[0];
};
const startDateStr = formatDate(startDate);
const endDateStr = formatDate(endDate);
// 筛选在前后3天范围内的任务
const tasksToShow = appData.tasks.filter(task => {
return task.startDate >= startDateStr && task.startDate <= endDateStr;
});
// 按开始日期排序
tasksToShow.sort((a, b) => new Date(a.startDate) - new Date(b.startDate));
// 只显示前5个任务
const displayedTasks = tasksToShow.slice(0, 5);
if (displayedTasks.length === 0) {
container.innerHTML = '<p style="text-align: center; color: var(--gray); padding: 20px;">前后3天内没有任务</p>';
return;
}
displayedTasks.forEach(task => {
const taskElement = createTaskItem(task);
container.appendChild(taskElement);
});
}
// 渲染仪表盘团队
function renderDashboardTeam() {
const container = document.getElementById('dashboardTeam');
container.innerHTML = '';
appData.teamMembers.forEach(member => {
const card = createTeamMemberCard(member);
container.appendChild(card);
});
}
// 创建项目卡片
function createProjectCard(project) {
const card = document.createElement('div');
card.className = 'project-card';
card.dataset.id = project.id;
const statusClass = {
'planning': 'status-planning',
'progress': 'status-progress',
'review': 'status-review',
'completed': 'status-completed'
}[project.status] || 'status-planning';
const statusText = {
'planning': '规划中',
'progress': '进行中',
'review': '评审中',
'completed': '已完成'
}[project.status] || '规划中';
// 计算项目进度(基于任务状态)
const projectProgress = calculateProjectProgress(project.id);
// 计算实际完成任务数量和总任务数量
const projectTasks = appData.tasks.filter(task => task.projectId === project.id);
const totalTasks = projectTasks.length;
const completedTasks = projectTasks.filter(task => task.status === 'completed').length;
// 检查项目是否延期或即将延期
const today = new Date();
const deadline = new Date(project.deadline);
const daysUntilDeadline = Math.ceil((deadline - today) / (1000 * 60 * 60 * 24));
let projectNameClass = '';
if (project.status !== 'completed') {
if (daysUntilDeadline < 0) {
projectNameClass = 'project-overdue';
} else if (daysUntilDeadline <= 15) {
projectNameClass = 'project-soon-due';
}
}
card.innerHTML = `
<div class="project-header">
<div class="project-title">
<h4 class="${projectNameClass}">${project.name}</h4>
<p>${project.department} • 负责人: ${project.manager}</p>
</div>
<div class="project-actions">
<button class="action-btn export-project-btn" title="导出项目"><i class="fas fa-download"></i></button>
<button class="action-btn view-tasks-btn" title="查看任务"><i class="fas fa-tasks"></i></button>
<button class="action-btn edit-project-btn" title="编辑项目"><i class="fas fa-edit"></i></button>
<button class="action-btn delete-btn" title="删除项目"><i class="fas fa-trash"></i></button>
<div class="project-status ${statusClass}">${statusText}</div>
</div>
</div>
<div class="project-progress">
<div class="progress-bar">
<div class="progress" style="width: ${projectProgress}%"></div>
</div>
<div class="progress-info">
<span>进度: ${projectProgress}%</span>
<span>开始: ${project.startDate}</span>
<span>截止: ${project.deadline}</span>
</div>
</div>
<div class="project-meta">
<div class="project-team">
${project.team.map(member => `<div class="team-member">${member}</div>`).join('')}
</div>
<div class="task-count">${completedTasks}/${totalTasks} 任务完成</div>
</div>
${project.changeLog && project.changeLog.length > 0 ? `
<div class="change-log">
<h4>变更记录</h4>
${project.changeLog.map(log => ` <div class="change-log-item"> <span class="date">${log.date}</span> - <span class="user">${log.user}</span>: ${log.action} </div> `).join('')}
</div>
` : ''}
`;
// 添加导出项目事件
const exportProjectBtn = card.querySelector('.export-project-btn');
exportProjectBtn.addEventListener('click', (e) => {
e.stopPropagation();
exportSingleProject(project.id);
});
// 添加查看任务事件
const viewTasksBtn = card.querySelector('.view-tasks-btn');
viewTasksBtn.addEventListener('click', (e) => {
e.stopPropagation();
viewProjectTasks(project.id);
});
// 添加编辑项目事件
const editProjectBtn = card.querySelector('.edit-project-btn');
editProjectBtn.addEventListener('click', (e) => {
e.stopPropagation();
openEditProjectModal(project);
});
// 添加删除事件
const deleteBtn = card.querySelector('.delete-btn');
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`确定要删除项目 "${project.name}" 吗?这将删除所有关联的任务。`)) {
deleteProject(project.id);
}
});
card.addEventListener('click', () => {
alert('您点击了项目: ' + project.name);
});
return card;
}
// 创建任务项
function createTaskItem(task) {
const taskElement = document.createElement('div');
taskElement.className = 'task-item';
taskElement.dataset.id = task.id;
const project = appData.projects.find(p => p.id === task.projectId);
const projectName = project ? project.name : '未知项目';
const priorityClass = {
'high': 'priority-high',
'medium': 'priority-medium',
'low': 'priority-low'
}[task.priority] || 'priority-medium';
const priorityText = {
'high': '高',
'medium': '中',
'low': '低'
}[task.priority] || '中';
// 检查任务是否延期或即将到期
const today = new Date();
const deadline = new Date(task.deadline);
const daysUntilDeadline = Math.ceil((deadline - today) / (1000 * 60 * 60 * 24));
let taskNameClass = '';
if (task.status !== 'completed') {
if (daysUntilDeadline < 0) {
taskNameClass = 'task-overdue';
} else if (daysUntilDeadline <= 2) {
taskNameClass = 'task-soon-due';
}
}
taskElement.innerHTML = `
<div class="task-info">
<h4 class="${taskNameClass}">${task.name}</h4>
<p>${projectName}</p>
<div class="task-dates">开始: ${task.startDate} • 截止: ${task.deadline}</div>
</div>
<div class="task-priority ${priorityClass}">${priorityText}</div>
<div class="task-actions">
<button class="action-btn edit-task-btn" title="编辑任务"><i class="fas fa-edit"></i></button>
<button class="action-btn delete-btn" title="删除任务"><i class="fas fa-trash"></i></button>
</div>
`;
// 添加编辑任务事件
const editTaskBtn = taskElement.querySelector('.edit-task-btn');
editTaskBtn.addEventListener('click', (e) => {
e.stopPropagation();
openEditTaskModal(task);
});
// 添加删除事件
const deleteBtn = taskElement.querySelector('.delete-btn');
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`确定要删除任务 "${task.name}" 吗?`)) {
deleteTask(task.id);
}
});
return taskElement;
}
// 创建团队成员卡片
function createTeamMemberCard(member) {
const card = document.createElement('div');
card.className = 'team-member-card';
card.dataset.id = member.id;
card.innerHTML = `
<div class="member-avatar">${member.avatar}</div>
<div class="member-info">
<h4>${member.name}</h4>
<p>${member.role}</p>
${member.skills && member.skills.length > 0 ?
`<p class="member-skills">技能: ${member.skills.join(', ')}</p>` : ''}
</div>
<div class="member-actions">
<button class="action-btn edit-member-btn" title="编辑成员"><i class="fas fa-edit"></i></button>
<button class="action-btn delete-btn" title="删除成员"><i class="fas fa-trash"></i></button>
</div>
<div class="member-role">${member.role.includes('主管') || member.role.includes('经理') ? '管理员' : '成员'}</div>
`;
// 添加编辑成员事件
const editMemberBtn = card.querySelector('.edit-member-btn');
editMemberBtn.addEventListener('click', (e) => {
e.stopPropagation();
openEditTeamMemberModal(member);
});
// 添加删除事件
const deleteBtn = card.querySelector('.delete-btn');
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`确定要删除团队成员 "${member.name}" 吗?`)) {
deleteTeamMember(member.id);
}
});
return card;
}
// 渲染项目页面
function renderProjectsPage() {
const container = document.getElementById('allProjects');
container.innerHTML = '';
appData.projects.forEach(project => {
const card = createProjectCard(project);
container.appendChild(card);
});
}
// 渲染任务页面
function renderTasksPage() {
const container = document.getElementById('taskBoard');
container.innerHTML = '';
// 定义任务状态
const statuses = [
{ id: 'pending', name: '待处理', color: 'var(--warning)' },
{ id: 'progress', name: '进行中', color: 'var(--primary)' },
{ id: 'review', name: '评审中', color: 'var(--secondary)' },
{ id: 'completed', name: '已完成', color: 'var(--success)' }
];
// 应用过滤器
let filteredTasks = appData.tasks;
// 应用状态过滤器
if (window.currentTaskFilter && window.currentTaskFilter !== 'all') {
filteredTasks = filteredTasks.filter(task => task.status === window.currentTaskFilter);
}
// 应用项目过滤器
if (window.currentProjectFilter && window.currentProjectFilter !== 'all') {
filteredTasks = filteredTasks.filter(task => task.projectId == window.currentProjectFilter);
}
// 创建任务列
statuses.forEach(status => {
const column = document.createElement('div');
column.className = 'task-column';
const tasksInColumn = filteredTasks.filter(task => task.status === status.id);
column.innerHTML = `
<div class="column-header">
<h3>${status.name}</h3>
<div class="task-count">${tasksInColumn.length}</div>
</div>
<div class="column-tasks" data-status="${status.id}">
${tasksInColumn.map(task => {
const project = appData.projects.find(p => p.id === task.projectId);
const projectName = project ? project.name : '未知项目';
// 检查任务是否延期或即将到期
const today = new Date();
const deadline = new Date(task.deadline);
const daysUntilDeadline = Math.ceil((deadline - today) / (1000 * 60 * 60 * 24));
let taskNameClass = '';
if (task.status !== 'completed') {
if (daysUntilDeadline < 0) {
taskNameClass = 'task-overdue';
} else if (daysUntilDeadline <= 2) {
taskNameClass = 'task-soon-due';
}
}
return `
<div class="task-item" data-id="${task.id}">
<div class="task-info">
<h4 class="${taskNameClass}">${task.name}</h4>
<p>${projectName} • 开始: ${task.startDate} • 截止: ${task.deadline}</p>
</div>
<div class="task-priority priority-${task.priority}">
${task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低'}
</div>
<div class="task-actions">
<button class="action-btn edit-task-btn" title="编辑任务"><i class="fas fa-edit"></i></button>
<button class="action-btn delete-btn" title="删除任务"><i class="fas fa-trash"></i></button>
</div>
</div>
`;
}).join('')}
</div>
`;
// 为任务项添加事件
column.querySelectorAll('.edit-task-btn').forEach((btn, index) => {
btn.addEventListener('click', () => {
const task = tasksInColumn[index];
openEditTaskModal(task);
});
});
column.querySelectorAll('.delete-btn').forEach((btn, index) => {
btn.addEventListener('click', () => {
const task = tasksInColumn[index];
if (confirm(`确定要删除任务 "${task.name}" 吗?`)) {
deleteTask(task.id);
}
});
});
container.appendChild(column);
});
}
// 渲染团队页面
function renderTeamPage() {
const container = document.getElementById('teamMembers');
container.innerHTML = '';
appData.teamMembers.forEach(member => {
const card = createTeamMemberCard(member);
container.appendChild(card);
});
}
// 渲染日历
function renderCalendar() {
const calendarGrid = document.getElementById('calendarGrid');
const calendarMonth = document.getElementById('calendarMonth');
// 设置月份标题
const monthNames = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"];
calendarMonth.textContent = `${window.currentCalendarDate.getFullYear()}年 ${monthNames[window.currentCalendarDate.getMonth()]}`;
// 清空日历
calendarGrid.innerHTML = '';
// 添加星期标题
const weekdays = ["日", "一", "二", "三", "四", "五", "六"];
weekdays.forEach(day => {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day-header';
dayElement.textContent = day;
calendarGrid.appendChild(dayElement);
});
// 获取当月第一天和最后一天
const firstDay = new Date(window.currentCalendarDate.getFullYear(), window.currentCalendarDate.getMonth(), 1);
const lastDay = new Date(window.currentCalendarDate.getFullYear(), window.currentCalendarDate.getMonth() + 1, 0);
// 获取第一天是星期几 (0 = 星期日, 6 = 星期六)
const firstDayIndex = firstDay.getDay();
// 填充空白
for (let i = 0; i < firstDayIndex; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'calendar-day';
calendarGrid.appendChild(emptyDay);
}
// 填充日期
const today = new Date();
for (let day = 1; day <= lastDay.getDate(); day++) {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day selectable';
// 检查是否是今天
if (window.currentCalendarDate.getFullYear() === today.getFullYear() &&
window.currentCalendarDate.getMonth() === today.getMonth() &&
day === today.getDate()) {
dayElement.classList.add('today');
}
const dateStr = `${window.currentCalendarDate.getFullYear()}-${(window.currentCalendarDate.getMonth()+1).toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
// 查找当天的日历事件
const dayEvents = appData.calendarEvents.filter(event => event.date === dateStr);
dayElement.innerHTML = `<div>${day}</div>`;
// 添加事件
dayEvents.forEach(event => {
const eventElement = document.createElement('div');
eventElement.className = 'calendar-event';
eventElement.textContent = event.title;
dayElement.appendChild(eventElement);
});
// 添加编辑按钮
const editBtn = document.createElement('button');
editBtn.className = 'calendar-event-edit';
editBtn.innerHTML = '<i class="fas fa-edit"></i>';
editBtn.addEventListener('click', (e) => {
e.stopPropagation();
openCalendarEventModal(dateStr);
});
dayElement.appendChild(editBtn);
// 添加点击事件
dayElement.addEventListener('click', () => {
openCalendarEventModal(dateStr);
});
calendarGrid.appendChild(dayElement);
}
}
// 渲染进度管理页面
function renderProgressPage() {
const projectSelect = document.getElementById('projectSelect');
projectSelect.innerHTML = '<option value="">请选择项目</option>';
appData.projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.name;
projectSelect.appendChild(option);
});
// 默认显示第一个项目的横道图
if (appData.projects.length > 0) {
renderGanttChart(appData.projects[0].id);
}
}
// 渲染横道图 - 优化后的版本
function renderGanttChart(projectId) {
const container = document.getElementById('ganttChart');
const timelineContainer = document.getElementById('ganttTimeline');
container.innerHTML = '';
timelineContainer.innerHTML = '';
const project = appData.projects.find(p => p.id == projectId);
if (!project) {
container.innerHTML = '<p style="color: var(--gray); text-align: center; padding: 20px;">请选择项目</p>';
return;
}
// 计算项目总体进度
const projectProgress = calculateProjectProgress(projectId);
// 获取项目开始和结束日期
const startDate = new Date(project.startDate);
const endDate = new Date(project.deadline);
// 计算项目总天数
const totalDays = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
// 计算当前日期
const today = new Date();
today.setHours(0, 0, 0, 0);
// 计算当前日期在项目时间轴上的位置
const daysFromStart = Math.ceil((today - startDate) / (1000 * 60 * 60 * 24));
// 设置横道图容器的宽度 - 根据总天数动态计算
const dayWidth = 60; // 每个日期单元的宽度(像素)
const totalWidth = totalDays * dayWidth;
timelineContainer.style.minWidth = totalWidth + 'px';
// 生成时间线 - 每7天一个主要标记
for (let i = 0; i <= totalDays; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
const timelineItem = document.createElement('div');
timelineItem.className = 'gantt-timeline-item';
// 每7天显示一个主要标记
if (i % 7 === 0) {
timelineItem.classList.add('major');
timelineItem.textContent = `${date.getMonth()+1}/${date.getDate()}`;
} else {
// 其他天显示小点
timelineItem.innerHTML = '<div style="width: 2px; height: 2px; background: var(--gray); border-radius: 50%; margin: 0 auto;"></div>';
}
timelineContainer.appendChild(timelineItem);
}
// 创建项目总体进度条
const projectProgressElement = document.createElement('div');
projectProgressElement.className = 'gantt-task';
// 计算项目进度条的位置和宽度
const projectLeft = 0;
const projectWidth = totalWidth; // 项目占满整个时间轴
projectProgressElement.innerHTML = `
<div class="gantt-task-name">${project.name} (总体进度)</div>
<div class="gantt-task-bar-container" style="position: relative; min-width: ${totalWidth}px;">
<div class="gantt-task-bar" style="position: absolute; left: ${projectLeft}px; width: ${projectWidth}px;">
<div class="gantt-progress" style="width: ${projectProgress}%"></div>
</div>
<div class="gantt-date-start" style="left: ${projectLeft}px;">${formatDate(startDate)}</div>
<div class="gantt-date-end" style="left: ${projectLeft + projectWidth}px;">${formatDate(endDate)}</div>
</div>
<div style="margin-left: 10px; font-size: 0.8rem; min-width: 40px;">
${projectProgress}%
</div>
`;
container.appendChild(projectProgressElement);
// 显示项目下的任务
const projectTasks = appData.tasks.filter(task => task.projectId == projectId);
if (projectTasks.length === 0) {
container.innerHTML += '<p style="color: var(--gray); text-align: center; padding: 20px;">该项目暂无任务</p>';
return;
}
projectTasks.forEach(task => {
// 根据任务状态计算进度
const taskProgress = calculateTaskProgress(task.status);
// 计算任务在时间线上的位置和宽度
const taskStartDate = new Date(task.startDate);
const taskEndDate = new Date(task.deadline);
// 计算任务相对于项目开始日期的偏移量(天数)
const taskStartOffset = Math.ceil((taskStartDate - startDate) / (1000 * 60 * 60 * 24));
const taskDuration = Math.ceil((taskEndDate - taskStartDate) / (1000 * 60 * 60 * 24));
// 计算任务在时间轴上的位置和宽度(像素)
const taskLeft = Math.max(0, taskStartOffset * dayWidth);
const taskWidth = Math.max(20, taskDuration * dayWidth); // 最小宽度20px
const taskElement = document.createElement('div');
taskElement.className = 'gantt-task';
taskElement.innerHTML = `
<div class="gantt-task-name">${task.name}</div>
<div class="gantt-task-bar-container" style="position: relative; min-width: ${totalWidth}px;">
<div class="gantt-task-bar" style="position: absolute; left: ${taskLeft}px; width: ${taskWidth}px;">
<div class="gantt-progress" style="width: ${taskProgress}%"></div>
</div>
<div class="gantt-date-start" style="left: ${taskLeft}px;">${formatDate(taskStartDate)}</div>
<div class="gantt-date-end" style="left: ${taskLeft + taskWidth}px;">${formatDate(taskEndDate)}</div>
</div>
<div style="margin-left: 10px; font-size: 0.8rem; min-width: 40px;">
${taskProgress}%
</div>
`;
container.appendChild(taskElement);
});
// 添加当前日期指示线
if (daysFromStart >= 0 && daysFromStart <= totalDays) {
const currentDateLine = document.createElement('div');
currentDateLine.className = 'gantt-current-date';
currentDateLine.style.left = `${daysFromStart * dayWidth}px`;
container.appendChild(currentDateLine);
}
// 添加滚动控制按钮
addGanttScrollControls();
// 初始滚动到当前日期位置
setTimeout(() => {
scrollToCurrentDate();
}, 100);
}
// 格式化日期为 MM/DD 格式
function formatDate(date) {
return `${date.getMonth()+1}/${date.getDate()}`;
}
// 添加横道图滚动控制
function addGanttScrollControls() {
const ganttContainer = document.querySelector('.gantt-scroll-container');
const existingControls = document.querySelector('.gantt-scroll-controls');
if (existingControls) {
existingControls.remove();
}
const scrollControls = document.createElement('div');
scrollControls.className = 'gantt-scroll-controls';
scrollControls.innerHTML = `
<button class="gantt-scroll-btn" id="scrollLeftBtn"><i class="fas fa-chevron-left"></i> 向左滚动</button>
<button class="gantt-scroll-btn" id="scrollTodayBtn">滚动到今天</button>
<button class="gantt-scroll-btn" id="scrollRightBtn">向右滚动 <i class="fas fa-chevron-right"></i></button>
`;
ganttContainer.parentNode.insertBefore(scrollControls, ganttContainer.nextSibling);
// 添加滚动事件监听
document.getElementById('scrollLeftBtn').addEventListener('click', () => {
ganttContainer.scrollBy({ left: -200, behavior: 'smooth' });
});
document.getElementById('scrollRightBtn').addEventListener('click', () => {
ganttContainer.scrollBy({ left: 200, behavior: 'smooth' });
});
document.getElementById('scrollTodayBtn').addEventListener('click', scrollToCurrentDate);
}
// 滚动到当前日期
function scrollToCurrentDate() {
const ganttContainer = document.querySelector('.gantt-scroll-container');
const currentDateLine = document.querySelector('.gantt-current-date');
if (currentDateLine && ganttContainer) {
const currentDatePos = parseInt(currentDateLine.style.left);
// 滚动到当前日期位置,使其位于容器中央
ganttContainer.scrollTo({
left: currentDatePos - ganttContainer.clientWidth / 2,
behavior: 'smooth'
});
}
}
// 渲染报告页面
function renderReportsPage() {
const fileProjectFilter = document.getElementById('fileProjectFilter');
fileProjectFilter.innerHTML = '<option value="all">所有项目</option>';
appData.projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.name;
fileProjectFilter.appendChild(option);
});
renderFileList();
}
// 渲染文件列表
function renderFileList() {
const fileList = document.getElementById('fileList');
fileList.innerHTML = '';
const projectFilter = document.getElementById('fileProjectFilter').value;
let filesToShow = appData.files;
if (projectFilter !== 'all') {
filesToShow = appData.files.filter(file => file.projectId == projectFilter);
}
if (filesToShow.length === 0) {
fileList.innerHTML = '<p style="text-align: center; color: var(--gray); padding: 20px;">暂无文件</p>';
return;
}
filesToShow.forEach(file => {
const project = appData.projects.find(p => p.id === file.projectId);
const projectName = project ? project.name : '未知项目';
const fileIcon = getFileIcon(file.type);
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-info">
<div class="file-icon">
<i class="${fileIcon}"></i>
</div>
<div class="file-details">
<h4>${file.name}</h4>
<p>关联项目: ${projectName} • 上传日期: ${file.uploadDate} • 大小: ${file.size}</p>
${file.description ? `<p>描述: ${file.description}</p>` : ''}
</div>
</div>
<div class="file-actions">
<button class="action-btn download-file-btn" title="下载文件"><i class="fas fa-download"></i></button>
<button class="action-btn delete-file-btn" title="删除文件"><i class="fas fa-trash"></i></button>
</div>
`;
// 添加下载文件事件
const downloadBtn = fileItem.querySelector('.download-file-btn');
downloadBtn.addEventListener('click', async () => {
const result = await window.electronAPI.downloadFile(file);
if (result.success) {
showNotification(`文件 "${file.name}" 下载成功!`, 'success');
} else {
showNotification(`下载文件失败: ${result.error}`, 'error');
}
});
// 添加删除文件事件
const deleteBtn = fileItem.querySelector('.delete-file-btn');
deleteBtn.addEventListener('click', () => {
if (confirm(`确定要删除文件 "${file.name}" 吗?`)) {
deleteFile(file.id);
}
});
fileList.appendChild(fileItem);
});
}
// 获取文件图标
function getFileIcon(fileType) {
const iconMap = {
'docx': 'fas fa-file-word',
'pdf': 'fas fa-file-pdf',
'xlsx': 'fas fa-file-excel',
'ppt': 'fas fa-file-powerpoint',
'zip': 'fas fa-file-archive',
'jpg': 'fas fa-file-image',
'png': 'fas fa-file-image',
'txt': 'fas fa-file-alt',
'default': 'fas fa-file'
};
return iconMap[fileType] || iconMap.default;
}
// 删除文件
function deleteFile(fileId) {
appData.files = appData.files.filter(f => f.id !== fileId);
saveData();
renderReportsPage();
showNotification('文件已删除!', 'success');
}
// 计算任务进度
function calculateTaskProgress(status) {
switch(status) {
case 'pending': return 0;
case 'progress': return 40;
case 'review': return 80;
case 'completed': return 100;
default: return 0;
}
}
// 计算项目总体进度
function calculateProjectProgress(projectId) {
const projectTasks = appData.tasks.filter(task => task.projectId == projectId);
if (projectTasks.length === 0) {
return 0;
}
const totalProgress = projectTasks.reduce((sum, task) => {
return sum + calculateTaskProgress(task.status);
}, 0);
return Math.round(totalProgress / projectTasks.length);
}
// 打开编辑项目模态框
function openEditProjectModal(project) {
document.getElementById('projectId').value = project.id;
document.getElementById('projectName').value = project.name;
document.getElementById('projectDepartment').value = project.department;
document.getElementById('projectManager').value = project.manager;
document.getElementById('projectStartDate').value = project.startDate;
document.getElementById('projectDeadline').value = project.deadline;
document.getElementById('projectStatus').value = project.status;
document.getElementById('projectDescription').value = project.description || '';
document.getElementById('projectModalTitle').textContent = '编辑项目';
// 显示模态框
document.getElementById('projectModal').classList.add('active');
}
// 打开编辑任务模态框
function openEditTaskModal(task) {
document.getElementById('taskId').value = task.id;
document.getElementById('taskName').value = task.name;
document.getElementById('taskStartDate').value = task.startDate;
document.getElementById('taskDeadline').value = task.deadline;
document.getElementById('taskPriority').value = task.priority;
document.getElementById('taskStatus').value = task.status;
document.getElementById('taskDescription').value = task.description || '';
// 填充项目选择
const projectSelect = document.getElementById('taskProject');
projectSelect.innerHTML = '<option value="">请选择项目</option>';
appData.projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.name;
if (project.id === task.projectId) {
option.selected = true;
}
projectSelect.appendChild(option);
});
document.getElementById('taskModalTitle').textContent = '编辑任务';
// 显示模态框
document.getElementById('taskModal').classList.add('active');
}
// 打开编辑团队成员模态框
function openEditTeamMemberModal(member) {
document.getElementById('teamMemberId').value = member.id;
document.getElementById('teamMemberName').value = member.name;
document.getElementById('teamMemberRole').value = member.role;
document.getElementById('teamMemberSkills').value = member.skills ? member.skills.join(', ') : '';
document.getElementById('teamMemberModalTitle').textContent = '编辑团队成员';
// 显示模态框
document.getElementById('teamMemberModal').classList.add('active');
}
// 打开日历事件编辑模态框
function openCalendarEventModal(date) {
document.getElementById('calendarEventDate').value = date;
// 查找当天的事件
const existingEvent = appData.calendarEvents.find(event => event.date === date);
if (existingEvent) {
document.getElementById('calendarEventId').value = existingEvent.id;
document.getElementById('calendarEventTitle').value = existingEvent.title;
document.getElementById('calendarEventDescription').value = existingEvent.description || '';
document.getElementById('calendarEventModalTitle').textContent = '编辑事件';
} else {
document.getElementById('calendarEventId').value = '';
document.getElementById('calendarEventTitle').value = '';
document.getElementById('calendarEventDescription').value = '';
document.getElementById('calendarEventModalTitle').textContent = '添加事件';
}
// 显示模态框
document.getElementById('calendarEventModal').classList.add('active');
}
// 打开文件上传模态框
function openFileUploadModal() {
const projectSelect = document.getElementById('uploadFileProject');
projectSelect.innerHTML = '<option value="">请选择项目</option>';
appData.projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.name;
projectSelect.appendChild(option);
});
// 重置表单
document.getElementById('uploadFileDescription').value = '';
document.getElementById('uploadFileInput').value = '';
document.getElementById('confirmFileUpload').disabled = true;
// 显示模态框
document.getElementById('fileUploadModal').classList.add('active');
}
// 设置事件监听器
function setupEventListeners() {
// 页面导航
setupPageNavigation();
// 模态框功能
setupModals();
// 日历功能
setupCalendar();
// 文件上传功能
setupFileUpload();
// 搜索功能
setupSearch();
// 项目表单提交
document.getElementById('projectForm').addEventListener('submit', handleProjectForm);
// 任务表单提交
document.getElementById('taskForm').addEventListener('submit', handleTaskForm);
// 团队成员表单提交
document.getElementById('teamMemberForm').addEventListener('submit', handleTeamMemberForm);
// 日历事件表单提交
document.getElementById('calendarEventForm').addEventListener('submit', handleCalendarEventForm);
// 任务过滤器
setupTaskFilters();
// 仪表盘编辑按钮
setupDashboardEditButtons();
// 项目选择变化事件
document.getElementById('projectSelect').addEventListener('change', function() {
renderGanttChart(this.value);
});
// 文件项目筛选器变化事件
document.getElementById('fileProjectFilter').addEventListener('change', function() {
renderFileList();
});
// 导入项目按钮
document.getElementById('importProjectBtn').addEventListener('click', function() {
document.getElementById('fileInput2').click();
});
// 单个项目导入处理
document.getElementById('fileInput2').addEventListener('change', handleSingleProjectImport);
}
// 设置页面导航
function setupPageNavigation() {
const navLinks = document.querySelectorAll('.nav-link');
const pageContents = document.querySelectorAll('.page-content');
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetPage = this.getAttribute('data-page');
navLinks.forEach(l => l.classList.remove('active'));
this.classList.add('active');
pageContents.forEach(content => {
content.classList.remove('active');
});
document.getElementById(targetPage).classList.add('active');
// 如果是进度管理页面,渲染横道图
if (targetPage === 'progress') {
renderProgressPage();
}
// 如果是数据管理页面,更新数据统计
if (targetPage === 'dataManagement') {
updateDataStats();
}
// 如果是报告页面,渲染文件列表
if (targetPage === 'reports') {
renderReportsPage();
}
// 如果是任务页面,清除项目过滤器,显示所有任务
if (targetPage === 'tasks') {
window.currentProjectFilter = 'all';
window.currentTaskFilter = 'all';
// 设置所有任务过滤器为激活状态
const filterBtns = document.querySelectorAll('.filter-btn');
filterBtns.forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.filter === 'all') {
btn.classList.add('active');
}
});
// 更新项目筛选器
const projectFilter = document.getElementById('taskProjectFilter');
projectFilter.value = 'all';
renderTasksPage();
}
});
});
}
// 设置模态框
function setupModals() {
// 关闭模态框
const closeButtons = document.querySelectorAll('.close-modal, .btn-secondary');
closeButtons.forEach(button => {
button.addEventListener('click', closeAllModals);
});
// 点击模态框外部关闭
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeAllModals();
}
});
});
}
// 设置日历功能
function setupCalendar() {
document.getElementById('prevMonth').addEventListener('click', () => {
window.currentCalendarDate.setMonth(window.currentCalendarDate.getMonth() - 1);
renderCalendar();
});
document.getElementById('nextMonth').addEventListener('click', () => {
window.currentCalendarDate.setMonth(window.currentCalendarDate.getMonth() + 1);
renderCalendar();
});
}
// 设置文件上传功能
function setupFileUpload() {
// 文件上传按钮
document.getElementById('uploadFileBtn').addEventListener('click', openFileUploadModal);
// 文件上传区域事件
const uploadFileArea = document.getElementById('uploadFileArea');
const uploadFileInput = document.getElementById('uploadFileInput');
const confirmFileUpload = document.getElementById('confirmFileUpload');
uploadFileArea.addEventListener('click', () => {
uploadFileInput.click();
});
uploadFileArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadFileArea.style.borderColor = 'var(--primary)';
uploadFileArea.style.background = 'rgba(0, 243, 255, 0.1)';
});
uploadFileArea.addEventListener('dragleave', () => {
uploadFileArea.style.borderColor = 'var(--card-border)';
uploadFileArea.style.background = '';
});
uploadFileArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadFileArea.style.borderColor = 'var(--card-border)';
uploadFileArea.style.background = '';
if (e.dataTransfer.files.length > 0) {
uploadFileInput.files = e.dataTransfer.files;
handleUploadFileSelect();
}
});
uploadFileInput.addEventListener('change', handleUploadFileSelect);
// 确认文件上传
confirmFileUpload.addEventListener('click', handleFileUpload);
// 取消文件上传
document.getElementById('cancelFileUpload').addEventListener('click', () => {
closeAllModals();
});
}
// 处理上传文件选择
function handleUploadFileSelect() {
const uploadFileInput = document.getElementById('uploadFileInput');
const confirmFileUpload = document.getElementById('confirmFileUpload');
if (uploadFileInput.files.length > 0) {
confirmFileUpload.disabled = false;
} else {
confirmFileUpload.disabled = true;
}
}
// 处理文件上传
async function handleFileUpload() {
const uploadFileProject = document.getElementById('uploadFileProject');
const uploadFileDescription = document.getElementById('uploadFileDescription');
const uploadFileInput = document.getElementById('uploadFileInput');
if (!uploadFileProject.value) {
showNotification('请选择关联项目', 'error');
return;
}
if (uploadFileInput.files.length === 0) {
showNotification('请选择要上传的文件', 'error');
return;
}
const file = uploadFileInput.files[0];
// 读取文件为Base64
const reader = new FileReader();
reader.onload = async function(e) {
const fileInfo = {
fileName: file.name,
fileData: e.target.result,
projectId: uploadFileProject.value,
description: uploadFileDescription.value
};
const result = await window.electronAPI.saveFile(fileInfo);
if (result.success) {
// 添加到应用数据
appData.files.push(result.fileInfo);
// 保存数据
await saveData();
// 更新界面
renderReportsPage();
closeAllModals();
showNotification('文件上传成功!', 'success');
} else {
showNotification('文件上传失败: ' + result.error, 'error');
}
};
reader.onerror = function() {
showNotification('文件读取失败', 'error');
};
reader.readAsDataURL(file);
}
// 设置搜索功能
function setupSearch() {
// 全局搜索
document.getElementById('globalSearch').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
// 搜索项目
const projectCards = document.querySelectorAll('.project-card');
projectCards.forEach(card => {
const projectName = card.querySelector('h4').textContent.toLowerCase();
const projectManager = card.querySelector('p').textContent.toLowerCase();
if (projectName.includes(searchTerm) || projectManager.includes(searchTerm)) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
// 搜索任务
const taskItems = document.querySelectorAll('.task-item');
taskItems.forEach(item => {
const taskName = item.querySelector('h4').textContent.toLowerCase();
const taskProject = item.querySelector('p').textContent.toLowerCase();
if (taskName.includes(searchTerm) || taskProject.includes(searchTerm)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
// 搜索团队成员
const teamCards = document.querySelectorAll('.team-member-card');
teamCards.forEach(card => {
const memberName = card.querySelector('h4').textContent.toLowerCase();
const memberRole = card.querySelector('p').textContent.toLowerCase();
if (memberName.includes(searchTerm) || memberRole.includes(searchTerm)) {
card.style.display = 'flex';
} else {
card.style.display = 'none';
}
});
});
// 项目页面搜索
document.getElementById('projectSearch').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const projectCards = document.querySelectorAll('#allProjects .project-card');
projectCards.forEach(card => {
const projectName = card.querySelector('h4').textContent.toLowerCase();
const projectManager = card.querySelector('p').textContent.toLowerCase();
if (projectName.includes(searchTerm) || projectManager.includes(searchTerm)) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
});
// 任务页面搜索
document.getElementById('taskSearch').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const taskItems = document.querySelectorAll('.task-item');
taskItems.forEach(item => {
const taskName = item.querySelector('h4').textContent.toLowerCase();
const taskProject = item.querySelector('p').textContent.toLowerCase();
if (taskName.includes(searchTerm) || taskProject.includes(searchTerm)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
});
// 团队页面搜索
document.getElementById('teamSearch').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const teamCards = document.querySelectorAll('#teamMembers .team-member-card');
teamCards.forEach(card => {
const memberName = card.querySelector('h4').textContent.toLowerCase();
const memberRole = card.querySelector('p').textContent.toLowerCase();
if (memberName.includes(searchTerm) || memberRole.includes(searchTerm)) {
card.style.display = 'flex';
} else {
card.style.display = 'none';
}
});
});
// 日历页面搜索
document.getElementById('calendarSearch').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const calendarDays = document.querySelectorAll('.calendar-day');
calendarDays.forEach(day => {
const dayNumber = day.querySelector('div')?.textContent.toLowerCase();
if (dayNumber && dayNumber.includes(searchTerm)) {
day.style.backgroundColor = 'rgba(0, 243, 255, 0.2)';
} else {
day.style.backgroundColor = '';
}
});
});
// 进度管理页面搜索
document.getElementById('progressSearch').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const projectSelect = document.getElementById('projectSelect');
const options = projectSelect.querySelectorAll('option');
options.forEach(option => {
const projectName = option.textContent.toLowerCase();
if (projectName.includes(searchTerm)) {
option.style.display = 'block';
} else {
option.style.display = 'none';
}
});
});
}
// 设置任务过滤器
function setupTaskFilters() {
const filterBtns = document.querySelectorAll('.filter-btn');
const projectFilter = document.getElementById('taskProjectFilter');
// 填充项目筛选器
projectFilter.innerHTML = '<option value="all">所有项目</option>';
appData.projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.name;
projectFilter.appendChild(option);
});
filterBtns.forEach(btn => {
btn.addEventListener('click', function() {
filterBtns.forEach(b => b.classList.remove('active'));
this.classList.add('active');
window.currentTaskFilter = this.dataset.filter;
renderTasksPage();
});
});
projectFilter.addEventListener('change', function() {
window.currentProjectFilter = this.value;
renderTasksPage();
});
}
// 设置仪表盘编辑按钮
function setupDashboardEditButtons() {
// 添加项目按钮
document.getElementById('addProjectBtn').addEventListener('click', () => {
document.getElementById('projectId').value = '';
document.getElementById('projectName').value = '';
document.getElementById('projectDepartment').value = '';
document.getElementById('projectManager').value = '';
document.getElementById('projectStartDate').value = '';
document.getElementById('projectDeadline').value = '';
document.getElementById('projectStatus').value = 'planning';
document.getElementById('projectDescription').value = '';
document.getElementById('projectModalTitle').textContent = '新建项目';
document.getElementById('projectModal').classList.add('active');
});
document.getElementById('addProjectBtn2').addEventListener('click', () => {
document.getElementById('projectId').value = '';
document.getElementById('projectName').value = '';
document.getElementById('projectDepartment').value = '';
document.getElementById('projectManager').value = '';
document.getElementById('projectStartDate').value = '';
document.getElementById('projectDeadline').value = '';
document.getElementById('projectStatus').value = 'planning';
document.getElementById('projectDescription').value = '';
document.getElementById('projectModalTitle').textContent = '新建项目';
document.getElementById('projectModal').classList.add('active');
});
// 添加任务按钮
document.getElementById('addTaskBtn').addEventListener('click', () => {
document.getElementById('taskId').value = '';
document.getElementById('taskName').value = '';
document.getElementById('taskStartDate').value = '';
document.getElementById('taskDeadline').value = '';
document.getElementById('taskPriority').value = 'medium';
document.getElementById('taskStatus').value = 'pending';
document.getElementById('taskDescription').value = '';
// 填充项目选择
const projectSelect = document.getElementById('taskProject');
projectSelect.innerHTML = '<option value="">请选择项目</option>';
appData.projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.name;
projectSelect.appendChild(option);
});
document.getElementById('taskModalTitle').textContent = '新建任务';
document.getElementById('taskModal').classList.add('active');
});
// 添加团队成员按钮
document.getElementById('addTeamMemberBtn').addEventListener('click', () => {
document.getElementById('teamMemberId').value = '';
document.getElementById('teamMemberName').value = '';
document.getElementById('teamMemberRole').value = '';
document.getElementById('teamMemberSkills').value = '';
document.getElementById('teamMemberModalTitle').textContent = '添加团队成员';
document.getElementById('teamMemberModal').classList.add('active');
});
document.getElementById('addTeamMemberBtn2').addEventListener('click', () => {
document.getElementById('teamMemberId').value = '';
document.getElementById('teamMemberName').value = '';
document.getElementById('teamMemberRole').value = '';
document.getElementById('teamMemberSkills').value = '';
document.getElementById('teamMemberModalTitle').textContent = '添加团队成员';
document.getElementById('teamMemberModal').classList.add('active');
});
}
// 关闭所有模态框
function closeAllModals() {
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
modal.classList.remove('active');
});
}
// 处理项目表单提交
async function handleProjectForm(e) {
e.preventDefault();
const projectId = document.getElementById('projectId').value;
const projectName = document.getElementById('projectName').value;
const projectDepartment = document.getElementById('projectDepartment').value;
const projectManager = document.getElementById('projectManager').value;
const projectStartDate = document.getElementById('projectStartDate').value;
const projectDeadline = document.getElementById('projectDeadline').value;
const projectStatus = document.getElementById('projectStatus').value;
const projectDescription = document.getElementById('projectDescription').value;
let project;
let isNew = false;
if (projectId) {
// 编辑现有项目
project = appData.projects.find(p => p.id == projectId);
// 记录变更
const changeLogItem = {
date: new Date().toISOString().split('T')[0],
user: appData.currentUser,
action: `更新项目信息: ${projectName}`
};
if (!project.changeLog) {
project.changeLog = [];
}
project.changeLog.push(changeLogItem);
// 更新项目信息
project.name = projectName;
project.department = projectDepartment;
project.manager = projectManager;
project.startDate = projectStartDate;
project.deadline = projectDeadline;
project.status = projectStatus;
project.description = projectDescription;
} else {
// 创建新项目
project = {
id: appData.projects.length > 0 ? Math.max(...appData.projects.map(p => p.id)) + 1 : 1,
name: projectName,
department: projectDepartment,
manager: projectManager,
progress: 0,
startDate: projectStartDate,
deadline: projectDeadline,
status: projectStatus,
team: [projectManager.charAt(0)],
completedTasks: 0,
totalTasks: 0,
description: projectDescription,
changeLog: [
{
date: new Date().toISOString().split('T')[0],
user: appData.currentUser,
action: "创建项目"
}
]
};
appData.projects.push(project);
isNew = true;
}
await saveData();
updateStats();
renderDashboard();
renderProjectsPage();
renderReportsPage();
closeAllModals();
document.getElementById('projectForm').reset();
showNotification(`项目${isNew ? '创建' : '更新'}成功!`, 'success');
}
// 处理任务表单提交
async function handleTaskForm(e) {
e.preventDefault();
const taskId = document.getElementById('taskId').value;
const taskName = document.getElementById('taskName').value;
const taskProject = document.getElementById('taskProject').value;
const taskStartDate = document.getElementById('taskStartDate').value;
const taskDeadline = document.getElementById('taskDeadline').value;
const taskPriority = document.getElementById('taskPriority').value;
const taskStatus = document.getElementById('taskStatus').value;
const taskDescription = document.getElementById('taskDescription').value;
let task;
let isNew = false;
if (taskId) {
// 编辑现有任务
task = appData.tasks.find(t => t.id == taskId);
// 更新任务信息
task.name = taskName;
task.projectId = parseInt(taskProject);
task.startDate = taskStartDate;
task.deadline = taskDeadline;
task.priority = taskPriority;
task.status = taskStatus;
task.description = taskDescription;
} else {
// 创建新任务
task = {
id: appData.tasks.length > 0 ? Math.max(...appData.tasks.map(t => t.id)) + 1 : 1,
name: taskName,
projectId: parseInt(taskProject),
startDate: taskStartDate,
deadline: taskDeadline,
priority: taskPriority,
status: taskStatus,
description: taskDescription
};
appData.tasks.push(task);
isNew = true;
}
await saveData();
updateStats();
renderDashboard();
renderTasksPage();
renderProgressPage();
closeAllModals();
document.getElementById('taskForm').reset();
showNotification(`任务${isNew ? '创建' : '更新'}成功!`, 'success');
}
// 处理团队成员表单提交
async function handleTeamMemberForm(e) {
e.preventDefault();
const teamMemberId = document.getElementById('teamMemberId').value;
const teamMemberName = document.getElementById('teamMemberName').value;
const teamMemberRole = document.getElementById('teamMemberRole').value;
const teamMemberSkills = document.getElementById('teamMemberSkills').value;
let member;
let isNew = false;
if (teamMemberId) {
// 编辑现有成员
member = appData.teamMembers.find(m => m.id == teamMemberId);
// 更新成员信息
member.name = teamMemberName;
member.role = teamMemberRole;
member.skills = teamMemberSkills ? teamMemberSkills.split(',').map(s => s.trim()) : [];
} else {
// 创建新成员
member = {
id: appData.teamMembers.length > 0 ? Math.max(...appData.teamMembers.map(m => m.id)) + 1 : 1,
name: teamMemberName,
role: teamMemberRole,
avatar: teamMemberName.charAt(0),
skills: teamMemberSkills ? teamMemberSkills.split(',').map(s => s.trim()) : []
};
appData.teamMembers.push(member);
isNew = true;
}
await saveData();
renderDashboard();
renderTeamPage();
closeAllModals();
document.getElementById('teamMemberForm').reset();
showNotification(`团队成员${isNew ? '添加' : '更新'}成功!`, 'success');
}
// 处理日历事件表单提交
async function handleCalendarEventForm(e) {
e.preventDefault();
const eventId = document.getElementById('calendarEventId').value;
const eventDate = document.getElementById('calendarEventDate').value;
const eventTitle = document.getElementById('calendarEventTitle').value;
const eventDescription = document.getElementById('calendarEventDescription').value;
let event;
let isNew = false;
if (eventId) {
// 编辑现有事件
event = appData.calendarEvents.find(e => e.id == eventId);
// 更新事件信息
event.title = eventTitle;
event.description = eventDescription;
} else {
// 创建新事件
event = {
id: appData.calendarEvents.length > 0 ? Math.max(...appData.calendarEvents.map(e => e.id)) + 1 : 1,
date: eventDate,
title: eventTitle,
description: eventDescription
};
appData.calendarEvents.push(event);
isNew = true;
}
await saveData();
renderCalendar();
closeAllModals();
document.getElementById('calendarEventForm').reset();
showNotification(`日历事件${isNew ? '添加' : '更新'}成功!`, 'success');
}
// 删除项目
async function deleteProject(projectId) {
// 删除项目
appData.projects = appData.projects.filter(p => p.id !== projectId);
// 删除关联的任务
appData.tasks = appData.tasks.filter(t => t.projectId !== projectId);
// 删除关联的文件
appData.files = appData.files.filter(f => f.projectId !== projectId);
await saveData();
updateStats();
renderDashboard();
renderProjectsPage();
renderTasksPage();
renderReportsPage();
renderProgressPage();
showNotification('项目已删除!', 'success');
}
// 删除任务
async function deleteTask(taskId) {
appData.tasks = appData.tasks.filter(t => t.id !== taskId);
await saveData();
updateStats();
renderDashboard();
renderTasksPage();
renderProgressPage();
showNotification('任务已删除!', 'success');
}
// 删除团队成员
async function deleteTeamMember(memberId) {
appData.teamMembers = appData.teamMembers.filter(m => m.id !== memberId);
await saveData();
renderDashboard();
renderTeamPage();
showNotification('团队成员已删除!', 'success');
}
// 导出单个项目
async function exportSingleProject(projectId) {
const project = appData.projects.find(p => p.id === projectId);
if (!project) return;
// 获取项目相关的任务
const projectTasks = appData.tasks.filter(t => t.projectId === projectId);
// 获取项目相关的文件
const projectFiles = appData.files.filter(f => f.projectId === projectId);
// 创建导出数据
const exportData = {
project: project,
tasks: projectTasks,
files: projectFiles,
exportDate: new Date().toISOString(),
version: '1.0'
};
const result = await window.electronAPI.exportData(exportData);
if (result.success) {
showNotification(`项目 "${project.name}" 导出成功!`, 'success');
} else {
showNotification(`导出项目失败: ${result.error}`, 'error');
}
}
// 查看项目任务
function viewProjectTasks(projectId) {
// 切换到任务页面
const taskNavLink = document.querySelector('[data-page="tasks"]');
const taskPage = document.getElementById('tasks');
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
document.querySelectorAll('.page-content').forEach(c => c.classList.remove('active'));
taskNavLink.classList.add('active');
taskPage.classList.add('active');
// 设置项目筛选器
const projectFilter = document.getElementById('taskProjectFilter');
projectFilter.value = projectId;
window.currentProjectFilter = projectId;
// 设置所有任务过滤器
const filterBtns = document.querySelectorAll('.filter-btn');
filterBtns.forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.filter === 'all') {
btn.classList.add('active');
}
});
window.currentTaskFilter = 'all';
// 渲染任务页面
renderTasksPage();
}
// 处理单个项目导入
async function handleSingleProjectImport(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async function(e) {
try {
const importedData = JSON.parse(e.target.result);
// 验证数据格式
if (!importedData.project) {
showNotification('项目数据格式不正确,无法导入', 'error');
return;
}
// 确认导入
if (confirm(`确定要导入项目 "${importedData.project.name}" 吗?`)) {
// 检查项目是否已存在
const existingProject = appData.projects.find(p => p.id === importedData.project.id);
if (existingProject) {
// 更新现有项目
Object.assign(existingProject, importedData.project);
// 更新或添加任务
if (importedData.tasks) {
importedData.tasks.forEach(task => {
const existingTask = appData.tasks.find(t => t.id === task.id);
if (existingTask) {
Object.assign(existingTask, task);
} else {
appData.tasks.push(task);
}
});
}
// 更新或添加文件
if (importedData.files) {
importedData.files.forEach(file => {
const existingFile = appData.files.find(f => f.id === file.id);
if (existingFile) {
Object.assign(existingFile, file);
} else {
appData.files.push(file);
}
});
}
} else {
// 添加新项目
appData.projects.push(importedData.project);
// 添加任务
if (importedData.tasks) {
appData.tasks.push(...importedData.tasks);
}
// 添加文件
if (importedData.files) {
appData.files.push(...importedData.files);
}
}
// 保存到本地存储
await saveData();
// 更新界面
updateStats();
renderDashboard();
renderProjectsPage();
renderTasksPage();
renderReportsPage();
renderProgressPage();
// 重置文件输入
document.getElementById('fileInput2').value = '';
showNotification('项目导入成功!', 'success');
}
} catch (error) {
console.error('导入失败:', error);
showNotification('文件解析失败,请检查文件格式', 'error');
}
};
reader.onerror = function() {
showNotification('文件读取失败', 'error');
};
reader.readAsText(file);
}
// 显示通知
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// 初始化应用
document.addEventListener('DOMContentLoaded', initApp);
6. index.html (主HTML文件)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目管理信息系统 - 桌面版</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="sci-fi-bg"></div>
<!-- 通知 -->
<div class="notification" id="notification"></div>
<!-- 项目编辑模态框 -->
<div id="projectModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="projectModalTitle">编辑项目</h3>
<button class="close-modal">×</button>
</div>
<form id="projectForm">
<input type="hidden" id="projectId">
<div class="modal-body">
<div class="form-group">
<label for="projectName">项目名称</label>
<input type="text" id="projectName" class="form-control" required>
</div>
<div class="form-group">
<label for="projectDepartment">部门</label>
<input type="text" id="projectDepartment" class="form-control" required>
</div>
<div class="form-group">
<label for="projectManager">负责人</label>
<input type="text" id="projectManager" class="form-control" required>
</div>
<div class="form-group">
<label for="projectStartDate">开始日期</label>
<input type="date" id="projectStartDate" class="form-control" required>
</div>
<div class="form-group">
<label for="projectDeadline">截止日期</label>
<input type="date" id="projectDeadline" class="form-control" required>
</div>
<div class="form-group">
<label for="projectStatus">状态</label>
<select id="projectStatus" class="form-control" required>
<option value="planning">规划中</option>
<option value="progress">进行中</option>
<option value="review">评审中</option>
<option value="completed">已完成</option>
</select>
</div>
<div class="form-group">
<label for="projectDescription">项目描述</label>
<textarea id="projectDescription" class="form-control" rows="3"></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancelProject">取消</button>
<button type="submit" class="btn">保存项目</button>
</div>
</div>
</form>
</div>
</div>
<!-- 任务编辑模态框 -->
<div id="taskModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="taskModalTitle">编辑任务</h3>
<button class="close-modal">×</button>
</div>
<form id="taskForm">
<input type="hidden" id="taskId">
<div class="modal-body">
<div class="form-group">
<label for="taskName">任务名称</label>
<input type="text" id="taskName" class="form-control" required>
</div>
<div class="form-group">
<label for="taskProject">关联项目</label>
<select id="taskProject" class="form-control" required>
<option value="">请选择项目</option>
</select>
</div>
<div class="form-group">
<label for="taskStartDate">开始日期</label>
<input type="date" id="taskStartDate" class="form-control" required>
</div>
<div class="form-group">
<label for="taskDeadline">截止日期</label>
<input type="date" id="taskDeadline" class="form-control" required>
</div>
<div class="form-group">
<label for="taskPriority">优先级</label>
<select id="taskPriority" class="form-control" required>
<option value="high">高</option>
<option value="medium">中</option>
<option value="low">低</option>
</select>
</div>
<div class="form-group">
<label for="taskStatus">状态</label>
<select id="taskStatus" class="form-control" required>
<option value="pending">待处理</option>
<option value="progress">进行中</option>
<option value="review">评审中</option>
<option value="completed">已完成</option>
</select>
</div>
<div class="form-group">
<label for="taskDescription">任务描述</label>
<textarea id="taskDescription" class="form-control" rows="3"></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancelTask">取消</button>
<button type="submit" class="btn">保存任务</button>
</div>
</div>
</form>
</div>
</div>
<!-- 团队成员编辑模态框 -->
<div id="teamMemberModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="teamMemberModalTitle">编辑团队成员</h3>
<button class="close-modal">×</button>
</div>
<form id="teamMemberForm">
<input type="hidden" id="teamMemberId">
<div class="modal-body">
<div class="form-group">
<label for="teamMemberName">成员姓名</label>
<input type="text" id="teamMemberName" class="form-control" required>
</div>
<div class="form-group">
<label for="teamMemberRole">角色</label>
<input type="text" id="teamMemberRole" class="form-control" required>
</div>
<div class="form-group">
<label for="teamMemberSkills">技能</label>
<input type="text" id="teamMemberSkills" class="form-control" placeholder="用逗号分隔技能">
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancelTeamMember">取消</button>
<button type="submit" class="btn">保存成员</button>
</div>
</div>
</form>
</div>
</div>
<!-- 日历事件编辑模态框 -->
<div id="calendarEventModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="calendarEventModalTitle">添加/编辑事件</h3>
<button class="close-modal">×</button>
</div>
<form id="calendarEventForm">
<input type="hidden" id="calendarEventId">
<input type="hidden" id="calendarEventDate">
<div class="modal-body">
<div class="form-group">
<label for="calendarEventTitle">事件标题</label>
<input type="text" id="calendarEventTitle" class="form-control" required>
</div>
<div class="form-group">
<label for="calendarEventDescription">事件描述</label>
<textarea id="calendarEventDescription" class="form-control" rows="3"></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancelCalendarEvent">取消</button>
<button type="submit" class="btn">保存事件</button>
</div>
</div>
</form>
</div>
</div>
<!-- 数据管理模态框 -->
<div id="dataManagementModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">数据管理</h3>
<button class="close-modal">×</button>
</div>
<div class="modal-body">
<div class="data-management-section">
<h4>数据导出</h4>
<p>将当前系统中的项目、任务、团队成员和日历事件数据导出为JSON文件。</p>
<div class="data-actions">
<button class="btn" id="exportDataBtn">
<i class="fas fa-download"></i> 导出数据
</button>
<button class="btn btn-secondary" id="copyDataBtn">
<i class="fas fa-copy"></i> 复制到剪贴板
</button>
</div>
<div class="data-info">
<h4>数据导入</h4>
<p>从JSON文件导入数据到系统中。注意:导入数据将覆盖当前所有数据,请谨慎操作!</p>
<div class="file-upload-area" id="fileUploadArea">
<i class="fas fa-cloud-upload-alt"></i>
<p>点击选择文件或拖拽文件到此处</p>
<p class="file-hint">支持JSON格式文件</p>
<input type="file" id="fileInput" accept=".json" style="display: none">
</div>
<div class="data-actions">
<button class="btn btn-secondary" id="importDataBtn" disabled>
<i class="fas fa-upload"></i> 导入数据
</button>
<button class="btn btn-secondary" id="resetDataBtn">
<i class="fas fa-trash"></i> 重置为示例数据
</button>
</div>
</div>
<div class="data-info">
<h4>当前数据统计</h4>
<div class="data-stats">
<div class="data-stat">
<div class="value" id="projectsCount">0</div>
<div class="label">项目</div>
</div>
<div class="data-stat">
<div class="value" id="tasksCount">0</div>
<div class="label">任务</div>
</div>
<div class="data-stat">
<div class="value" id="teamMembersCount">0</div>
<div class="label">团队成员</div>
</div>
<div class="data-stat">
<div class="value" id="eventsCount">0</div>
<div class="label">日历事件</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 文件上传模态框 -->
<div id="fileUploadModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">上传文件</h3>
<button class="close-modal">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="uploadFileProject">关联项目</label>
<select id="uploadFileProject" class="form-control" required>
<option value="">请选择项目</option>
</select>
</div>
<div class="form-group">
<label for="uploadFileDescription">文件描述</label>
<input type="text" id="uploadFileDescription" class="form-control" placeholder="请输入文件描述">
</div>
<div class="file-upload-area" id="uploadFileArea">
<i class="fas fa-cloud-upload-alt"></i>
<p>点击选择文件或拖拽文件到此处</p>
<p class="file-hint">支持所有格式文件</p>
<input type="file" id="uploadFileInput" style="display: none">
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="cancelFileUpload">取消</button>
<button type="button" class="btn" id="confirmFileUpload" disabled>上传文件</button>
</div>
</div>
</div>
</div>
<!-- 主应用容器 -->
<div class="container" id="appContainer">
<!-- 侧边栏 -->
<div class="sidebar">
<div class="logo">
<h1><i class="fas fa-rocket"></i> 项目管理系统</h1>
</div>
<ul class="nav-links">
<li><a href="#" class="nav-link active" data-page="dashboard"><i class="fas fa-home"></i> <span>仪表盘</span></a></li>
<li><a href="#" class="nav-link" data-page="projects"><i class="fas fa-project-diagram"></i> <span>项目</span></a></li>
<li><a href="#" class="nav-link" data-page="tasks"><i class="fas fa-tasks"></i> <span>任务</span></a></li>
<li><a href="#" class="nav-link" data-page="team"><i class="fas fa-users"></i> <span>团队</span></a></li>
<li><a href="#" class="nav-link" data-page="calendar"><i class="fas fa-calendar-alt"></i> <span>日历</span></a></li>
<li><a href="#" class="nav-link" data-page="reports"><i class="fas fa-chart-bar"></i> <span>报告</span></a></li>
<li><a href="#" class="nav-link" data-page="progress"><i class="fas fa-chart-line"></i> <span>进度管理</span></a></li>
<li><a href="#" class="nav-link" data-page="dataManagement"><i class="fas fa-database"></i> <span>数据管理</span></a></li>
</ul>
</div>
<!-- 主内容区 -->
<div class="main-content">
<!-- 仪表盘页面 -->
<div id="dashboard" class="page-content active">
<div class="header">
<div class="page-title">
<h2>项目仪表盘</h2>
<p>欢迎回来,查看您的项目进展和团队动态</p>
</div>
<div class="user-info">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="globalSearch" placeholder="搜索项目、任务或成员...">
</div>
<div class="user-avatar floating" id="userAvatar">PM</div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-cards">
<div class="stat-card">
<h3>进行中项目</h3>
<div class="value" id="activeProjectsCount">0</div>
</div>
<div class="stat-card">
<h3>已完成任务</h3>
<div class="value" id="completedTasksCount">0</div>
</div>
<div class="stat-card">
<h3>逾期项目</h3>
<div class="value" id="overdueProjectsCount">0</div>
</div>
<div class="stat-card">
<h3>团队效率</h3>
<div class="value" id="teamEfficiency">0%</div>
</div>
</div>
<!-- 项目列表 -->
<div class="projects-section">
<div class="section-header">
<h3>我的项目</h3>
<div>
<button class="btn" id="addProjectBtn"><i class="fas fa-plus"></i> 新建项目</button>
</div>
</div>
<div class="projects-grid" id="dashboardProjects">
<!-- 项目卡片将动态生成 -->
</div>
</div>
<!-- 任务和团队 -->
<div class="tasks-section">
<div class="task-list">
<div class="section-header">
<h3>近期任务</h3>
<div>
<button class="btn" id="addTaskBtn"><i class="fas fa-plus"></i> 新建任务</button>
</div>
</div>
<div class="task-items" id="dashboardTasks">
<!-- 任务将动态生成 -->
</div>
</div>
<div class="team-section">
<div class="section-header">
<h3>团队成员</h3>
<div>
<button class="btn" id="addTeamMemberBtn"><i class="fas fa-user-plus"></i> 添加成员</button>
</div>
</div>
<div class="team-members" id="dashboardTeam">
<!-- 团队成员将动态生成 -->
</div>
</div>
</div>
</div>
<!-- 项目页面 -->
<div id="projects" class="page-content">
<div class="header">
<div class="page-title">
<h2>项目管理</h2>
<p>查看和管理所有项目</p>
</div>
<div class="user-info">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="projectSearch" placeholder="搜索项目...">
</div>
<div class="user-avatar floating" id="userAvatar2">PM</div>
</div>
</div>
<div class="projects-section">
<div class="section-header">
<h3>所有项目</h3>
<div>
<button class="btn" id="addProjectBtn2"><i class="fas fa-plus"></i> 新建项目</button>
<button class="btn btn-secondary" id="importProjectBtn"><i class="fas fa-file-import"></i> 导入项目</button>
</div>
</div>
<div class="projects-grid" id="allProjects">
<!-- 项目卡片将动态生成 -->
</div>
</div>
</div>
<!-- 任务页面 -->
<div id="tasks" class="page-content">
<div class="header">
<div class="page-title">
<h2>任务管理</h2>
<p>查看和管理所有任务</p>
</div>
<div class="user-info">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="taskSearch" placeholder="搜索任务...">
</div>
<div class="user-avatar floating" id="userAvatar3">PM</div>
</div>
</div>
<!-- 任务过滤器 -->
<div class="task-filter">
<button class="filter-btn active" data-filter="all">所有任务</button>
<button class="filter-btn" data-filter="pending">待处理</button>
<button class="filter-btn" data-filter="progress">进行中</button>
<button class="filter-btn" data-filter="completed">已完成</button>
<select class="form-control" id="taskProjectFilter" style="width: auto;">
<option value="all">所有项目</option>
</select>
</div>
<div class="task-board" id="taskBoard">
<!-- 任务列将动态生成 -->
</div>
</div>
<!-- 团队页面 -->
<div id="team" class="page-content">
<div class="header">
<div class="page-title">
<h2>团队管理</h2>
<p>管理团队成员和权限</p>
</div>
<div class="user-info">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="teamSearch" placeholder="搜索成员...">
</div>
<div class="user-avatar floating" id="userAvatar4">PM</div>
</div>
</div>
<div class="section-header">
<h3>团队成员</h3>
<button class="btn" id="addTeamMemberBtn2"><i class="fas fa-user-plus"></i> 添加成员</button>
</div>
<div class="team-members" id="teamMembers">
<!-- 团队成员将动态生成 -->
</div>
</div>
<!-- 日历页面 -->
<div id="calendar" class="page-content">
<div class="header">
<div class="page-title">
<h2>项目日历</h2>
<p>查看项目时间表和重要事件</p>
</div>
<div class="user-info">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="calendarSearch" placeholder="搜索事件...">
</div>
<div class="user-avatar floating" id="userAvatar5">PM</div>
</div>
</div>
<div class="calendar-container">
<div class="calendar-header">
<h3 id="calendarMonth">2025年 十一月</h3>
<div class="calendar-nav">
<button class="btn btn-secondary" id="prevMonth"><i class="fas fa-chevron-left"></i></button>
<button class="btn btn-secondary" id="nextMonth"><i class="fas fa-chevron-right"></i></button>
</div>
</div>
<div class="calendar-grid" id="calendarGrid">
<!-- 日历将动态生成 -->
</div>
</div>
</div>
<!-- 报告页面 -->
<div id="reports" class="page-content">
<div class="header">
<div class="page-title">
<h2>项目报告</h2>
<p>查看项目统计和分析</p>
</div>
<div class="user-avatar floating" id="userAvatar6">PM</div>
</div>
<div class="section-header">
<h3>文件管理</h3>
<div>
<button class="btn" id="uploadFileBtn"><i class="fas fa-upload"></i> 上传文件</button>
<select id="fileProjectFilter" class="form-control" style="display: inline-block; width: auto; margin-left: 15px;">
<option value="all">所有项目</option>
</select>
</div>
</div>
<div class="file-list" id="fileList">
<!-- 文件列表将动态生成 -->
</div>
</div>
<!-- 项目进度管理页面 -->
<div id="progress" class="page-content">
<div class="header">
<div class="page-title">
<h2>项目进度管理</h2>
<p>横道图展示项目任务进度</p>
</div>
<div class="user-info">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="progressSearch" placeholder="搜索项目...">
</div>
<div class="user-avatar floating" id="userAvatar8">PM</div>
</div>
</div>
<div class="section-header">
<h3>项目选择</h3>
</div>
<select id="projectSelect" class="form-control">
<option value="">请选择项目</option>
</select>
<div class="gantt-container" id="ganttContainer">
<div class="gantt-header">
<h3>项目横道图</h3>
</div>
<div class="gantt-scroll-container">
<div class="gantt-timeline" id="ganttTimeline">
<!-- 时间线将动态生成 -->
</div>
<div class="gantt-chart" id="ganttChart">
<!-- 横道图将动态生成 -->
</div>
</div>
</div>
</div>
<!-- 数据管理页面 -->
<div id="dataManagement" class="page-content">
<div class="header">
<div class="page-title">
<h2>数据管理</h2>
<p>导入、导出和备份系统数据</p>
</div>
<div class="user-avatar floating" id="userAvatar7">PM</div>
</div>
<div class="data-management-section">
<div class="local-storage-info">
<h4>本地数据存储</h4>
<p><strong>数据存储路径:</strong></p>
<p class="path" id="storagePath2">文档/ProjectManagementSystem/</p>
<p><strong>文件存储路径:</strong></p>
<p class="path" id="filesPath2">文档/ProjectManagementSystem/uploaded_files/</p>
</div>
<h3>数据导出</h3>
<p>将当前系统中的项目、任务、团队成员和日历事件数据导出为JSON文件。</p>
<div class="data-actions">
<button class="btn" id="exportDataBtn2">
<i class="fas fa-download"></i> 导出数据到本地文件
</button>
<button class="btn btn-secondary" id="copyDataBtn2">
<i class="fas fa-copy"></i> 复制到剪贴板
</button>
</div>
<div class="data-info">
<h3>数据导入</h3>
<p>从JSON文件导入数据到系统中。注意:导入数据将覆盖当前所有数据,请谨慎操作!</p>
<div class="file-upload-area" id="fileUploadArea2">
<i class="fas fa-cloud-upload-alt"></i>
<p>点击选择文件或拖拽文件到此处</p>
<p class="file-hint">支持JSON格式文件</p>
<input type="file" id="fileInput2" accept=".json" style="display: none">
</div>
<div class="data-actions">
<button class="btn btn-secondary" id="importDataBtn2" disabled>
<i class="fas fa-upload"></i> 从本地文件导入
</button>
<button class="btn btn-secondary" id="resetDataBtn2">
<i class="fas fa-trash"></i> 重置为示例数据
</button>
</div>
</div>
<div class="data-info">
<h3>当前数据统计</h3>
<div class="data-stats">
<div class="data-stat">
<div class="value" id="projectsCount2">0</div>
<div class="label">项目</div>
</div>
<div class="data-stat">
<div class="value" id="tasksCount2">0</div>
<div class="label">任务</div>
</div>
<div class="data-stat">
<div class="value" id="teamMembersCount2">0</div>
<div class="label">团队成员</div>
</div>
<div class="data-stat">
<div class="value" id="eventsCount2">0</div>
<div class="label">日历事件</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>
使用说明
安装和运行
1.将上述文件保存到项目文件夹中
2.在项目文件夹中打开命令行,运行以下命令安装依赖:
text
npm install
3.运行应用:
text
npm start
构建分发版本
text
npm run build
这将在 dist 文件夹中生成应用程序的安装包
功能特点
-
本地数据存储:所有数据存储在用户文档目录下的 ProjectManagementSystem 文件夹中
-
文件上传:上传的文件保存在 ProjectManagementSystem/uploaded_files 目录中
-
文件下载:从本地文件系统下载文件到用户选择的位置
-
数据导入导出:支持完整的数据导入导出功能
-
保持原有UI和功能:完全保留了原有HTML应用的所有功能和界面设计
存储路径
- 数据文件:文档/ProjectManagementSystem/data.json
- 上传文件:文档/ProjectManagementSystem/uploaded_files/