您现在的位置是:网站首页> 学习资源

编写跨平台客户端应用

摘要

编写跨平台客户端应用


1.jpg


Fluter开发

MAUI开发

Avalonia .NET 的跨平台 UI 框架

nodejs基本操作

Electron

NW.JS

Godot游戏引擎

Unity3D和GoDot

Rust的Tauri



nodejs基本操作

点击进入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点击查看官方教程

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实现万媒易发,一键发布原创文章到各大主流博客平台

使用Electron+Puppeteer 开发爬虫



NW.js

NW.js 也是一个基于 Node.js 的框架,但与 Electron 不同的是,它使用了 Node-Webkit,它允许使用 JavaScript、HTML 和 CSS 编写桌面应用程序,并提供与操作系统的集成



Godot游戏引擎

Godot游戏引擎

Godot中文社区

GoDot教程

GDScript教程



Unity3D和GoDot

GoDot官网



Rust的Tauri

Rust UI库

使用 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>
















Top