您现在的位置是:网站首页> 学习资源
编写跨平台客户端应用
- 学习资源
- 2025-09-07
- 1143人已阅读
编写跨平台客户端应用
nodejs基本操作
使用n模块进行管理
首先需要先安装n模块
npm install -g n
然后执行以下命令,即可将npm升级至最新版本
n latest npm
然后执行以下命令,即可将node.js升级至最新版本
n latest
当用正常安装出错时如:
npm install @types/lodash
解决办法
方式一、临时使用其他仓库进行下载安装
npm --registry https://registry.npm.taobao.org/ install @types/lodash
方式二、切换仓库进行下载安装
如何更换npm仓库
你可以使用以下命令来切换npm仓库:
npm config set registry <仓库地址>
方式三、通过cnpm来安装
安装cnpm
npm intsall -g cnpm -- registry=https://registry.npm.taobao.org/
查看版本
cnpm -v
npm仓库有哪些
使用下面指令可以查看镜像源:
npm config get registry
官方仓库 - https://registry.npmjs.org/
官方镜像仓库 - https://skimdb.npmjs.com/registry/
淘宝镜像仓库(旧域名) - https://registry.npm.taobao.org/
淘宝镜像仓库(新域名) - https://registry.npmmirror.com/
腾讯仓库 - https://mirrors.cloud.tencent.com/npm/
cnpm仓库 - https://r.cnpmjs.org/
yarn仓库 - https://registry.yarnpkg.com/
Electron 是一种基于 Node.js 和 Chromium 的框架,可以用 HTML、CSS 和 JavaScript 创建跨平台的桌面应用。由于它基于 Chromium,因此可以实现优秀的跨平台兼容性,并且有许多可用的插件和库可供使用。
一、环境准备
在安装 Electron 前,需要先配置基础开发环境:
安装 Node.js
Electron 依赖 Node.js 运行环境,建议安装 LTS 版本:
下载地址:Node.js 官网
验证安装:安装完成后,在终端运行以下命令检查版本
node -v # 应输出 v14.x 或更高版本
npm -v # 应输出 6.x 或更高版本
二、安装 Electron
有两种常用方式安装 Electron:
1. 全局安装(不推荐)
npm install -g electron
全局安装可能导致不同项目间的版本冲突,一般不推荐。
2. 项目内局部安装(推荐)
步骤 1:创建项目文件夹并初始化
mkdir my-electron-app
cd my-electron-app
npm init -y
这会创建一个默认的 package.json 文件。
步骤 2:安装 Electron 到项目依赖
npm install electron --save-dev
步骤 3:验证安装
npx electron --version
如果成功,会输出当前安装的 Electron 版本号。
三.打包应用
要将应用打包为可执行文件,我们使用 electron-packager:
安装打包工具:
npm install electron-packager --save-dev
执行打包命令(根据目标平台选择):
# Windows
npm run package
# macOS
electron-packager . MyApp --platform=darwin --arch=x64
# Linux
electron-packager . MyApp --platform=linux --arch=x64
打包完成后,会在项目目录下生成对应平台的可执行文件。
典型例子:
这个 Electron 示例包含了以下关键部分:
package.json - 项目配置文件,定义了应用名称、版本和脚本命令等
main.js - 主进程代码,负责:
创建和管理浏览器窗口
定义应用菜单和快捷键
处理文件对话框(打开 / 保存)
与渲染进程通信
preload.js - 预加载脚本,安全地暴露主进程 API 给渲染进程,保持上下文隔离
index.html - 渲染进程界面,包含:
基本的 HTML 结构和样式
可编辑内容区域
与主进程通信的 JavaScript 代码
要运行这个应用,你需要先安装依赖:
npm install
然后启动应用:
npm start
package.json
{
"name": "electron-example-app",
"version": "1.0.0",
"description": "A typical Electron application",
"main": "main.js",
"scripts": {
"start": "electron .",
"package": "electron-packager . --platform=win32 --arch=x64"
},
"devDependencies": {
"electron": "^28.0.0",
"electron-packager": "^17.1.2"
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron Example App</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
#content {
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
min-height: 200px;
}
button {
background-color: #0078d7;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #005a9e;
}
#status {
margin-top: 10px;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>Electron Example Application</h1>
<div id="content" contenteditable="true">
这是一个可编辑区域。尝试输入一些文本,然后使用菜单栏中的文件选项保存。
</div>
<button id="sendBtn">发送消息到主进程</button>
<div id="status"></div>
</div>
<script>
// 获取DOM元素
const sendBtn = document.getElementById('sendBtn');
const statusEl = document.getElementById('status');
const contentEl = document.getElementById('content');
// 发送消息到主进程
sendBtn.addEventListener('click', () => {
window.electronAPI.sendMessage('Hello from renderer process!');
statusEl.textContent = '消息已发送到主进程';
});
// 接收来自主进程的消息
window.electronAPI.onMessage((message) => {
statusEl.textContent = `收到主进程的消息: ${message}`;
});
// 处理文件打开
window.electronAPI.onFileOpened(async (filePath) => {
try {
// 使用fetch API读取文件(渲染进程中不能直接使用fs模块)
const response = await fetch(`file://${filePath}`);
const content = await response.text();
contentEl.textContent = content;
statusEl.textContent = `已打开文件: ${filePath}`;
} catch (err) {
statusEl.textContent = `打开文件失败: ${err.message}`;
}
});
// 处理文件保存
window.electronAPI.onFileSave((filePath) => {
const content = contentEl.textContent;
// 发送文件内容到主进程进行保存
window.electronAPI.saveFileContent(filePath, content);
statusEl.textContent = `已保存文件: ${filePath}`;
});
// 监听主进程保存文件内容的请求
window.electronAPI.on('save-file-complete', (success) => {
if (success) {
statusEl.textContent = '文件保存成功';
} else {
statusEl.textContent = '文件保存失败';
}
});
</script>
</body>
</html>
main.js
const { app, BrowserWindow, Menu, dialog, ipcMain } = require('electron');
const path = require('path');
// 保持对window对象的全局引用,如果不这样做,当JavaScript对象被垃圾回收时,window将自动关闭
let mainWindow;
function createWindow() {
// 创建浏览器窗口
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false, // 禁用节点集成
contextIsolation: true // 启用上下文隔离
},
title: 'Electron Example App'
});
// 加载index.html文件
mainWindow.loadFile('index.html');
// 打开开发者工具
// mainWindow.webContents.openDevTools();
// 窗口关闭时触发
mainWindow.on('closed', () => {
// 取消引用window对象,如果你的应用支持多窗口,通常会把多个window对象存放在一个数组里
// 与此同时,你应该删除相应的元素
mainWindow = null;
});
// 创建应用菜单
createMenu();
}
// 创建菜单
function createMenu() {
const template = [
{
label: '文件',
submenu: [
{
label: '打开',
accelerator: 'CmdOrCtrl+O',
click() { openFile(); }
},
{
label: '保存',
accelerator: 'CmdOrCtrl+S',
click() { saveFile(); }
},
{ type: 'separator' },
{
label: '退出',
accelerator: 'CmdOrCtrl+Q',
click() { app.quit(); }
}
]
},
{
label: '编辑',
submenu: [
{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: '重做', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' },
{ label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' },
{ label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' },
{ label: '全选', accelerator: 'CmdOrCtrl+A', role: 'selectAll' }
]
},
{
label: '视图',
submenu: [
{
label: '刷新',
accelerator: 'CmdOrCtrl+R',
click() { mainWindow.reload(); }
},
{
label: '切换开发者工具',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click() { mainWindow.webContents.openDevTools(); }
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
// 打开文件对话框
function openFile() {
const options = {
title: '打开文件',
filters: [
{ name: '文本文件', extensions: ['txt'] },
{ name: '所有文件', extensions: ['*'] }
]
};
dialog.showOpenDialog(mainWindow, options).then(result => {
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0];
// 可以在这里读取文件内容并发送到渲染进程
mainWindow.webContents.send('file-opened', filePath);
}
});
}
// 保存文件对话框
function saveFile() {
const options = {
title: '保存文件',
defaultPath: path.join(app.getPath('documents'), 'untitled.txt'),
filters: [
{ name: '文本文件', extensions: ['txt'] },
{ name: '所有文件', extensions: ['*'] }
]
};
dialog.showSaveDialog(mainWindow, options).then(result => {
if (!result.canceled) {
const filePath = result.filePath;
// 通知渲染进程保存文件
mainWindow.webContents.send('file-save', filePath);
}
});
}
// 当Electron完成初始化并准备创建浏览器窗口时调用此方法
app.on('ready', createWindow);
// 当所有窗口关闭时退出
app.on('window-all-closed', () => {
// 在macOS上,除非用户用Cmd + Q明确退出,否则大部分应用及其菜单栏会保持激活
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// 在macOS上,当点击dock图标并且没有其他窗口打开时,通常在应用程序中重新创建一个窗口
if (mainWindow === null) {
createWindow();
}
});
// 监听来自渲染进程的消息
ipcMain.on('message-from-renderer', (event, arg) => {
console.log('Received from renderer:', arg);
event.reply('message-from-main', 'Hello from main process!');
});
preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 在window对象上暴露安全的API
contextBridge.exposeInMainWorld('electronAPI', {
// 从渲染进程发送消息到主进程
sendMessage: (message) => ipcRenderer.send('message-from-renderer', message),
// 从主进程接收消息
onMessage: (callback) => ipcRenderer.on('message-from-main', (event, message) => callback(message)),
// 文件操作相关
onFileOpened: (callback) => ipcRenderer.on('file-opened', (event, path) => callback(path)),
onFileSave: (callback) => ipcRenderer.on('file-save', (event, path) => callback(path)),
// 通知主进程文件内容已准备好保存
saveFileContent: (path, content) => ipcRenderer.send('save-file-content', path, content)
});
Puppeteer 和 Electron 共用同一个Chrome 或 Chromium浏览器二进制文件。
将 Puppeteer 的可执行路径设置为 Electron 的可执行路径来实现这一点
以下是一个示例代码,展示了如何在 Puppeteer 中使用 Electron 的浏览器二进制文件:
const puppeteer = require('puppeteer-core');
(async () => {
// 设置 Electron 的可执行路径
const executablePath = '/path/to/electron';
// 启动 Puppeteer,并将可执行路径设置为 Electron 的可执行路径
const browser = await puppeteer.launch({
executablePath,
});
// 进行其他操作...
await browser.close();
})();
在上述代码中,你需要将 executablePath 变量设置为 Electron 的可执行路径。然后,在启动 Puppeteer 时,将 executablePath 设置为 launch 方法的配置选项中。这样,Puppeteer 将使用指定的 Electron 可执行文件来启动浏览器。
请注意,Puppeteer 需要与 Electron 版本兼容。因此,你需要确保 Puppeteer 和 Electron
的版本匹配。你可以通过在 package.json 文件中指定正确的版本号来确保兼容性。例如,如果你的 Electron 版本是
12.0.0,你可以在 package.json 文件中设置 “puppeteer”: “^12.0.0”,以确保 Puppeteer 使用与 Electron 版本兼容的 Chromium 版本。
相关技术文章
使用Electron+Puppeteer实现万媒易发,一键发布原创文章到各大主流博客平台
NW.js 也是一个基于 Node.js 的框架,但与 Electron 不同的是,它使用了 Node-Webkit,它允许使用 JavaScript、HTML 和 CSS 编写桌面应用程序,并提供与操作系统的集成
Godot游戏引擎
Unity3D和GoDot
Rust的Tauri
使用 Cargo 创建 Tauri 应用的步骤
安装必要的依赖
首先需要安装 Rust 和 Tauri CLI:
# 安装Rust
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
# 安装Tauri CLI
cargo install tauri-cli
创建新的 Tauri 项目
cargo tauri init
执行该命令后,会出现交互式配置,你可以根据需要设置应用名称、窗口标题等信息。
运行应用
cargo tauri dev
示例代码
下面是一个简单的 Tauri 应用示例,包含基本的窗口控制和 JavaScript 与 Rust 的交互:
Tauri应用示例代码
V1
创建时间:09:21
项目结构说明
Tauri 项目的基本结构如下:
plaintext
src-tauri/
├── Cargo.toml # Rust项目配置
├── src/
│ └── main.rs # Rust主程序
├── tauri.conf.json # Tauri配置
└── frontend/ # 前端资源
└── index.html # 主页面
Tauri 默认会加载前端资源目录中的index.html文件
默认情况下,前端资源目录是src-tauri/frontend,你可以在tauri.conf.json中修改这个路径。
tauri.conf.json
{
"build": {
"distDir": "../frontend", // 前端资源目录相对路径
"devPath": "../frontend" // 开发环境下的资源路径
},
"package": {
"productName": "tauri-app",
"version": "0.1.0"
},
"tauri": {
"windows": [
{
"title": "Tauri App",
"width": 800,
"height": 600
}
],
"allowlist": {
"all": false
}
}
}
Cargo.toml:
[package]
name = "tauri_example"
version = "0.1.0"
description = "A Tauri example application"
authors = ["Your Name <you@example.com>"]
license = "MIT"
repository = ""
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.5", features = [] }
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.5", features = ["api-all"] }
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs, it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = ["custom-protocol"]
# this feature is used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]
main.rs:
// src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::{Manager, Window};
// 自定义命令:关闭窗口
#[tauri::command]
fn close_window(window: Window) {
window.close().unwrap();
}
// 自定义命令:计算两个数的和
#[tauri::command]
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 自定义命令:获取系统信息
#[tauri::command]
fn get_system_info() -> String {
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
format!("操作系统: {}, 架构: {}", os, arch)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
close_window,
add,
get_system_info
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
index.html
<!-- src-tauri/frontend/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tauri示例应用</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.container {
text-align: center;
background-color: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
button {
padding: 0.5rem 1rem;
margin: 0.5rem;
border: none;
border-radius: 5px;
background-color: #4285f4;
color: white;
cursor: pointer;
font-size: 1rem;
}
button:hover {
background-color: #3367d6;
}
.result {
margin: 1rem 0;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 5px;
min-width: 200px;
}
</style>
</head>
<body>
<div class="container">
<h1>Hello Tauri!</h1>
<div class="operations">
<h3>加法计算器</h3>
<input type="number" id="num1" value="5"> +
<input type="number" id="num2" value="3">
<button onclick="calculate()">计算</button>
</div>
<div class="result" id="result">结果将显示在这里</div>
<div class="system-info">
<button onclick="showSystemInfo()">显示系统信息</button>
</div>
<button onclick="closeApp()">关闭应用</button>
</div>
<script>
// 导入Tauri的API
const { invoke } = window.__TAURI__.tauri;
const { getCurrentWindow } = window.__TAURI__.window;
// 计算两个数的和
async function calculate() {
const num1 = parseInt(document.getElementById('num1').value);
const num2 = parseInt(document.getElementById('num2').value);
try {
const result = await invoke('add', { a: num1, b: num2 });
document.getElementById('result').textContent = `计算结果: ${num1} + ${num2} = ${result}`;
} catch (e) {
document.getElementById('result').textContent = `错误: ${e}`;
}
}
// 显示系统信息
async function showSystemInfo() {
try {
const info = await invoke('get_system_info');
document.getElementById('result').textContent = info;
} catch (e) {
document.getElementById('result').textContent = `错误: ${e}`;
}
}
// 关闭应用
async function closeApp() {
try {
const window = getCurrentWindow();
await invoke('close_window', { window });
} catch (e) {
console.error(e);
}
}
// 初始化时计算一次
calculate();
</script>
</body>
</html>
上一篇:RPA自动化脚本开发相关
下一篇:学习资源目录结构