首先,我们需要初始化项目并安装必要的依赖:
bash
npm init -y
npm install electron electron-builder --save-dev
接下来,让我们创建项目的基本文件结构:
- 首先创建
main.js
文件,这是 Electron 主进程的入口文件:
javascript:main.js
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const fs = require('fs');
const https = require('https');
const http = require('http');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
});
mainWindow.loadFile('index.html');
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
// 文件下载功能
ipcMain.handle('download-file', async (event, { url, fileName }) => {
try {
const result = await dialog.showSaveDialog(mainWindow, {
defaultPath: fileName,
});
if (result.canceled) {
return { success: false, message: '用户取消了下载' };
}
const filePath = result.filePath;
return new Promise((resolve) => {
try {
// Determine which protocol module to use based on the URL
const protocol = url.startsWith('https:') ? https : url.startsWith('http:') ? http : null;
if (!protocol) {
resolve({ success: false, message: '不支持的协议,仅支持 HTTP 和 HTTPS' });
return;
}
protocol.get(url, (response) => {
const file = fs.createWriteStream(filePath);
response.pipe(file);
file.on('finish', () => {
file.close();
resolve({ success: true, filePath });
});
file.on('error', (err) => {
fs.unlink(filePath, () => {});
resolve({ success: false, message: err.message });
});
}).on('error', (err) => {
resolve({ success: false, message: err.message });
});
} catch (error) {
resolve({ success: false, message: error.message });
}
});
} catch (error) {
return { success: false, message: error.message };
}
});
// 文件上传功能
ipcMain.handle('upload-file', async (event) => {
try {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: '所有文件', extensions: ['*'] }
]
});
if (result.canceled || result.filePaths.length === 0) {
return { success: false, message: '用户取消了上传' };
}
const filePath = result.filePaths[0];
const fileName = path.basename(filePath);
const fileContent = fs.readFileSync(filePath);
// 这里是上传示例,实际应用中应该替换为实际的上传逻辑
// 例如使用 axios 或 fetch 发送到服务器
// 模拟上传成功
return {
success: true,
fileName,
filePath,
fileSize: fileContent.length
};
} catch (error) {
return { success: false, message: error.message };
}
});
- 创建
preload.js
文件,用于在主进程和渲染进程之间建立安全的通信通道:
javascript:preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
downloadFile: (params) => ipcRenderer.invoke('download-file', params),
uploadFile: () => ipcRenderer.invoke('upload-file')
});
- 创建
index.html
文件,这是应用的主界面:
html:index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Electron 文件上传下载示例</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 40px;
text-align: center;
}
.container {
max-width: 600px;
margin: 0 auto;
}
h1 {
color: #333;
}
.form-group {
margin-bottom: 20px;
text-align: left;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input, button {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background-color: #45a049;
}
.upload-btn {
background-color: #2196F3;
}
.upload-btn:hover {
background-color: #0b7dda;
}
#status {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
background-color: #f5f5f5;
min-height: 20px;
}
.success {
color: green;
}
.error {
color: red;
}
</style>
</head>
<body>
<div class="container">
<h1>Electron 文件上传下载示例</h1>
<div class="form-group">
<label for="download-url">下载文件 URL:</label>
<input type="text" id="download-url" placeholder="请输入文件的下载链接">
<button id="download-btn">下载文件</button>
</div>
<div class="form-group">
<label for="file-name">保存的文件名:</label>
<input type="text" id="file-name" placeholder="请输入要保存的文件名">
</div>
<div class="form-group">
<button id="upload-btn" class="upload-btn">上传文件</button>
</div>
<div id="status"></div>
</div>
<script>
document.getElementById('download-btn').addEventListener('click', async () => {
const url = document.getElementById('download-url').value;
const fileName = document.getElementById('file-name').value || 'downloaded-file';
const statusElement = document.getElementById('status');
if (!url) {
statusElement.textContent = '请输入文件的下载链接';
statusElement.className = 'error';
return;
}
statusElement.textContent = '正在下载...';
statusElement.className = '';
try {
const result = await window.electronAPI.downloadFile({ url, fileName });
if (result.success) {
statusElement.textContent = `文件下载成功: ${result.filePath}`;
statusElement.className = 'success';
} else {
statusElement.textContent = `下载失败: ${result.message}`;
statusElement.className = 'error';
}
} catch (error) {
statusElement.textContent = `发生错误: ${error.message}`;
statusElement.className = 'error';
}
});
document.getElementById('upload-btn').addEventListener('click', async () => {
const statusElement = document.getElementById('status');
statusElement.textContent = '请选择要上传的文件...';
statusElement.className = '';
try {
const result = await window.electronAPI.uploadFile();
if (result.success) {
statusElement.textContent =
`文件上传成功: ${result.fileName} (${(result.fileSize / 1024).toFixed(2)} KB)`;
statusElement.className = 'success';
// 在实际应用中,这里可以添加更多的上传后处理逻辑
} else {
statusElement.textContent = `上传失败: ${result.message}`;
statusElement.className = 'error';
}
} catch (error) {
statusElement.textContent = `发生错误: ${error.message}`;
statusElement.className = 'error';
}
});
</script>
</body>
</html>
- 更新
package.json
文件,添加启动脚本和应用信息:
json:package.json
{
"name": "electron-file-transfer",
"version": "1.0.0",
"description": "Electron 文件上传下载示例应用",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder"
},
"keywords": ["electron", "file", "upload", "download"],
"author": "Your Name",
"license": "MIT",
"devDependencies": {
"electron": "^25.0.0",
"electron-builder": "^24.6.3"
},
"build": {
"appId": "com.example.electronfiletransfer",
"productName": "Electron文件传输",
"win": {
"target": "nsis",
"icon": "build/icon.ico"
},
"mac": {
"target": "dmg",
"icon": "build/icon.icns"
},
"linux": {
"target": "AppImage"
}
}
}
现在,项目的基本文件已经创建完成。让我们创建一个 .gitignore
文件(如果需要的话):
:.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
项目创建完成后,您可以通过以下命令运行应用:
bash
npm start
如果需要打包应用,可以使用:
bash
npm run build
这个 Electron 应用实现了以下功能:
-
文件下载:用户可以输入文件的 URL 和保存的文件名,点击下载按钮后,应用会通过 Electron 的对话框让用户选择保存位置,然后下载文件。
-
文件上传:用户点击上传按钮后,应用会弹出文件选择对话框,用户选择文件后,应用会读取文件内容并模拟上传过程。在实际应用中,您需要将模拟上传的部分替换为实际的服务器上传逻辑。
这个项目使用了 Electron 的现代安全实践,如 contextIsolation 和 preload.js,确保了应用的安全性。