Win11 下使用 Rust + Tauri 打包桌面 EXE 操作手册
1. 适用场景
本文档适用于将一个本地 HTML 页面改造成 Windows 11 桌面应用,并最终打包成 .exe 文件。
适合以下场景:
1. 企业内部工具封装为桌面程序
2. 本地 Web 页面打包成 EXE
3. 需要调用本机或局域网后端接口
4. 需要绕过浏览器 CORS 限制
5. 不想使用 Electron,希望安装包更轻量
推荐技术栈:
Tauri v2
Rust
HTML / CSS / JavaScript
pnpm
Windows 11
2. 最终项目结构
推荐将前端静态资源和 Tauri 工程隔离:
algorithm-engine-console/
├─ package.json
├─ web/
│ ├─ index.html
│ └─ src/
│ ├─ main.js
│ └─ styles.css
└─ src-tauri/
├─ Cargo.toml
├─ Cargo.lock
├─ tauri.conf.json
├─ build.rs
├─ icons/
│ └─ icon.ico
└─ src/
└─ main.rs
注意:
web/ 目录必须保持干净,只放前端静态资源。
不要让 frontendDist 指向项目根目录,否则会把 node_modules、src-tauri、target 等目录一起打包,导致构建失败。
3. 环境准备
3.1 安装 Node.js
建议安装:
Node.js 22 LTS
pnpm 最新版
检查版本:
node -v
npm -v
pnpm -v
如果没有安装 pnpm:
npm install -g pnpm
3.2 安装 Rust
推荐使用 winget 安装:
winget install --id Rustlang.Rustup
安装完成后重新打开 PowerShell。
检查:
rustc --version
cargo --version
rustup --version
3.3 安装 Visual Studio C++ Build Tools
Rust 在 Windows 下需要 MSVC 工具链。
安装时勾选:
Desktop development with C++
必须包含:
MSVC
Windows SDK
CMake
3.4 安装 WebView2
Windows 11 通常已内置 WebView2。
如果没有,需要安装:
Microsoft Edge WebView2 Evergreen Runtime
4. Cargo 国内镜像配置
如果 Cargo 下载依赖很慢,或者出现类似错误:
repository 'https://rsproxy.cn/crates.io-index/' not found
可以配置 sparse 镜像。
打开配置文件:
notepad $env:USERPROFILE\.cargo\config.toml
推荐内容:
[source.crates-io]
replace-with = "rsproxy-sparse"
[source.rsproxy-sparse]
registry = "sparse+https://rsproxy.cn/index/"
[net]
git-fetch-with-cli = true
清理旧缓存:
Remove-Item -Recurse -Force $env:USERPROFILE\.cargo\registry\index -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $env:USERPROFILE\.cargo\registry\cache -ErrorAction SilentlyContinue
5. 创建项目
cd D:\my-work\frontend-work
mkdir algorithm-engine-console
cd algorithm-engine-console
pnpm init
安装依赖:
pnpm add @tauri-apps/api@2.11.1
pnpm add -D @tauri-apps/cli@2.11.1
创建目录:
mkdir web
mkdir web\src
mkdir src-tauri
mkdir src-tauri\src
mkdir src-tauri\icons
6. package.json 配置
{
"name": "algorithm-engine-console",
"version": "1.0.0",
"private": true,
"scripts": {
"tauri": "tauri",
"dev": "tauri dev",
"build": "tauri build"
},
"devDependencies": {
"@tauri-apps/cli": "2.11.1"
},
"dependencies": {
"@tauri-apps/api": "2.11.1"
}
}
7. Rust 配置
文件路径:
src-tauri/Cargo.toml
推荐配置:
[package]
name = "AlgorithmEngine"
version = "1.0.0"
description = "Algorithm Engine Console"
authors = ["your-name"]
edition = "2024"
[build-dependencies]
tauri-build = { version = "2.5.1", features = [] }
[dependencies]
tauri = { version = "2.11.1", features = [] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["blocking", "json"] }
说明:
name 会影响最终生成的 exe 名称。
例如:
name = "AlgorithmEngine"
最终生成:
AlgorithmEngine.exe
注意:
Cargo package name 不建议使用中文或空格。
8. Tauri 配置
文件路径:
src-tauri/tauri.conf.json
推荐配置:
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Algorithm Engine Console",
"version": "1.0.0",
"identifier": "com.local.algorithm.engine.console",
"build": {
"frontendDist": "../web",
"beforeDevCommand": "",
"beforeBuildCommand": ""
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "Algorithm Engine Console",
"width": 1016,
"height": 818,
"minWidth": 920,
"minHeight": 760,
"resizable": true,
"fullscreen": false,
"center": true,
"devtools": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": false,
"icon": [
"icons/icon.ico"
]
}
}
关键说明:
frontendDist = "../web"
表示只打包 web 目录。
withGlobalTauri = true
允许普通 HTML/JS 使用 window.__TAURI__ 调用 Rust 命令。
bundle.active = false
表示只生成 exe,不生成安装包,避免 NSIS 下载超时。
9. build.rs
文件路径:
src-tauri/build.rs
内容:
fn main() {
tauri_build::build()
}
10. Rust 主程序
文件路径:
src-tauri/src/main.rs
示例代码:
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use serde::Serialize;
use std::time::Duration;
#[derive(Serialize)]
struct ApiResponse {
status: u16,
body: String,
}
#[tauri::command]
fn request_backend_api(url: String, token: String) -> Result<ApiResponse, String> {
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(60))
.build()
.map_err(|e| format!("创建 HTTP Client 失败: {}", e))?;
let response = client
.post(url)
.header("Authorization", token)
.header("Accept", "*/*")
.header("Content-Type", "application/json")
.body("{}")
.send()
.map_err(|e| format!("请求后端接口失败: {}", e))?;
let status = response.status().as_u16();
let body = response
.text()
.map_err(|e| format!("读取响应内容失败: {}", e))?;
Ok(ApiResponse { status, body })
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![request_backend_api])
.run(tauri::generate_context!())
.expect("启动桌面应用失败");
}
重要说明:
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
作用:
打包 release exe 后,双击不会弹出黑色控制台窗口。
如果不加这行,双击 exe 可能会同时弹出 CMD 黑窗口。
11. 前端页面
文件路径:
web/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>算法引擎控制台</title>
<link rel="stylesheet" href="src/styles.css" />
</head>
<body>
<div class="glow glow-blue"></div>
<div class="glow glow-purple"></div>
<main class="panel">
<div class="top-line"></div>
<header class="header">
<div>
<div class="badge">Direct Mode</div>
<h1>算法引擎控制台</h1>
</div>
<div class="status-box">
<span id="statusDot" class="dot success"></span>
<span id="statusText">READY</span>
</div>
</header>
<section class="config-card">
<div class="config-head">
<div>
<label class="config-label" for="serverHost">后端服务地址</label>
</div>
<span class="config-badge">HOST</span>
</div>
<div class="config-row">
<div class="input-wrap">
<span class="input-prefix">URL</span>
<input
id="serverHost"
class="server-input"
type="text"
value="http://localhost:8080"
placeholder="http://localhost:8080"
/>
</div>
<button id="saveHostBtn" class="secondary-btn">
保存
</button>
</div>
<p class="config-tip">
示例:http://localhost:8080、http://127.0.0.1:8080、http://192.168.1.10:8080
</p>
</section>
<section class="card">
<div class="request-info">
<p class="card-title">接口请求</p>
<p id="requestUrlText" class="card-url">
POST http://localhost:8080/your-api-path
</p>
</div>
<button id="refreshBtn" class="refresh-btn">
<span id="btnIcon">⟳</span>
<span>执行请求</span>
</button>
</section>
<section>
<div class="response-head">
<span>Response</span>
<span id="responseTime">-- ms</span>
</div>
<pre id="jsonOutput">点击按钮直接请求后端接口...</pre>
</section>
</main>
<script src="src/main.js"></script>
</body>
</html>
注意:
以下 ID 会被 JS 使用,不要随意修改:
serverHost
saveHostBtn
requestUrlText
refreshBtn
btnIcon
statusDot
statusText
responseTime
jsonOutput
12. 前端逻辑
文件路径:
web/src/main.js
示例代码:
const API_PATH = "/your-api-path";
const STORAGE_KEY_SERVER_HOST = "desktop_app_server_host";
const DEFAULT_SERVER_HOST = "http://localhost:8080";
// 不要在代码中写死真实 token,生产环境建议改为输入框、配置文件或登录接口获取
const TOKEN = "YOUR_TOKEN_HERE";
function getInvoke() {
if (!window.__TAURI__ || !window.__TAURI__.core || !window.__TAURI__.core.invoke) {
throw new Error("Tauri API 未注入,请检查 tauri.conf.json 是否配置 withGlobalTauri: true");
}
return window.__TAURI__.core.invoke;
}
function normalizeHost(host) {
let value = (host || "").trim();
if (!value) {
return DEFAULT_SERVER_HOST;
}
value = value.replace(/\/+$/, "");
if (!/^https?:\/\//i.test(value)) {
value = `http://${value}`;
}
return value;
}
function getServerHost() {
const input = document.getElementById("serverHost");
const inputValue = input ? input.value : "";
return normalizeHost(inputValue);
}
function buildTargetUrl() {
return `${getServerHost()}${API_PATH}`;
}
function refreshRequestUrlText() {
const requestUrlText = document.getElementById("requestUrlText");
if (requestUrlText) {
requestUrlText.innerText = `POST ${buildTargetUrl()}`;
}
}
function saveServerHost() {
const input = document.getElementById("serverHost");
const host = normalizeHost(input.value);
input.value = host;
localStorage.setItem(STORAGE_KEY_SERVER_HOST, host);
refreshRequestUrlText();
setOutput("", `后端服务地址已保存:\n${host}\n\n点击按钮开始请求。`);
setStatus("success", "READY");
}
function loadServerHost() {
const input = document.getElementById("serverHost");
const savedHost = localStorage.getItem(STORAGE_KEY_SERVER_HOST);
input.value = normalizeHost(savedHost || DEFAULT_SERVER_HOST);
refreshRequestUrlText();
}
function setStatus(type, text) {
const dot = document.getElementById("statusDot");
const statusText = document.getElementById("statusText");
dot.className = `dot ${type}`;
statusText.innerText = text;
}
function setOutput(type, text) {
const output = document.getElementById("jsonOutput");
output.className = type ? `text-${type}` : "";
output.innerText = text;
}
function formatResponse(text) {
try {
return JSON.stringify(JSON.parse(text), null, 4);
} catch {
return text;
}
}
async function triggerRefresh() {
const btn = document.getElementById("refreshBtn");
const btnIcon = document.getElementById("btnIcon");
const responseTime = document.getElementById("responseTime");
const targetUrl = buildTargetUrl();
refreshRequestUrlText();
btn.disabled = true;
btnIcon.classList.add("spin");
setStatus("warning", "REQUESTING...");
const startTime = performance.now();
try {
const invoke = getInvoke();
const result = await invoke("request_backend_api", {
url: targetUrl,
token: TOKEN
});
const endTime = performance.now();
responseTime.innerText = `${Math.round(endTime - startTime)} ms`;
const pretty = formatResponse(result.body);
if (result.status >= 200 && result.status < 300) {
setOutput("success", pretty);
setStatus("success", "SUCCESS");
} else {
setOutput("warning", `HTTP ${result.status}\n${pretty}`);
setStatus(
"warning",
result.status === 401 || result.status === 403 ? "AUTH FAILED" : "ERROR"
);
}
} catch (error) {
const endTime = performance.now();
responseTime.innerText = `${Math.round(endTime - startTime)} ms`;
setOutput(
"error",
`[请求失败] ${error}\n\n当前请求地址:\n${targetUrl}\n\n请求方式:POST\n\n排查顺序:\n1. 后端服务地址是否正确。\n2. 后端服务是否已启动。\n3. 接口路径是否正确。\n4. Rust 注册命令名是否和 JS invoke 命令名一致。\n5. Token 是否有效。\n6. 防火墙是否拦截本机请求。`
);
setStatus("error", "FAILED");
} finally {
btn.disabled = false;
btnIcon.classList.remove("spin");
}
}
window.addEventListener("DOMContentLoaded", () => {
const refreshBtn = document.getElementById("refreshBtn");
const saveHostBtn = document.getElementById("saveHostBtn");
const serverHostInput = document.getElementById("serverHost");
loadServerHost();
refreshBtn.addEventListener("click", triggerRefresh);
saveHostBtn.addEventListener("click", saveServerHost);
serverHostInput.addEventListener("input", refreshRequestUrlText);
serverHostInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
saveServerHost();
}
});
});
安全建议:
不要把真实 token、账号、密码、密钥写死在 main.js 中。
建议方案:
1. 页面增加 token 输入框
2. 使用登录接口动态获取 token
3. 使用本地配置文件读取
4. 使用 Tauri secure storage 或后端代理鉴权
13. 样式文件
文件路径:
web/src/styles.css
基础样式示例:
* {
box-sizing: border-box;
}
body {
margin: 0;
width: 100vw;
height: 100vh;
min-height: 100vh;
background: #0b0f19;
color: #f8fafc;
font-family: "Microsoft YaHei", system-ui, sans-serif;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
@keyframes pulse-glow {
0%, 100% {
opacity: 0.6;
filter: blur(120px);
}
50% {
opacity: 1;
filter: blur(140px);
}
}
.glow {
position: absolute;
width: 380px;
height: 380px;
border-radius: 999px;
animation: pulse-glow 4s infinite;
}
.glow-blue {
top: 20%;
left: 20%;
background: rgba(37, 99, 235, 0.16);
}
.glow-purple {
right: 20%;
bottom: 20%;
background: rgba(147, 51, 234, 0.16);
animation-delay: 2s;
}
.panel {
position: relative;
z-index: 2;
width: 760px;
max-width: 760px;
max-height: calc(100vh - 56px);
padding: 30px 32px 32px;
border-radius: 28px;
background: rgba(15, 23, 42, 0.76);
border: 1px solid rgba(51, 65, 85, 0.9);
box-shadow: 0 28px 80px rgba(0, 0, 0, 0.45);
backdrop-filter: blur(20px);
display: flex;
flex-direction: column;
}
.top-line {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #3b82f6, #a855f7, transparent);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.badge {
display: inline-block;
padding: 5px 12px;
border-radius: 999px;
color: #c084fc;
background: rgba(168, 85, 247, 0.1);
border: 1px solid rgba(168, 85, 247, 0.2);
font-size: 12px;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
}
h1 {
margin: 10px 0 0;
font-size: 28px;
line-height: 1.2;
background: linear-gradient(90deg, #fff, #cbd5e1, #94a3b8);
-webkit-background-clip: text;
color: transparent;
}
.status-box {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border-radius: 14px;
background: rgba(2, 6, 23, 0.8);
border: 1px solid rgba(51, 65, 85, 0.9);
font-size: 12px;
color: #94a3b8;
font-family: Consolas, monospace;
}
.dot {
width: 8px;
height: 8px;
border-radius: 999px;
}
.success {
background: #10b981;
box-shadow: 0 0 10px #10b981;
}
.warning {
background: #f59e0b;
box-shadow: 0 0 10px #f59e0b;
}
.error {
background: #f43f5e;
box-shadow: 0 0 10px #f43f5e;
}
.config-card,
.card {
border-radius: 22px;
background: linear-gradient(180deg, rgba(15, 23, 42, 0.48), rgba(2, 6, 23, 0.34));
border: 1px solid rgba(51, 65, 85, 0.72);
box-shadow:
inset 0 1px 0 rgba(148, 163, 184, 0.06),
0 18px 40px rgba(0, 0, 0, 0.18);
}
.config-card {
margin-bottom: 18px;
padding: 20px 24px 18px;
}
.config-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
}
.config-label {
color: #e2e8f0;
font-size: 15px;
font-weight: 700;
}
.config-badge {
padding: 5px 10px;
border-radius: 999px;
color: #93c5fd;
background: rgba(59, 130, 246, 0.08);
border: 1px solid rgba(59, 130, 246, 0.16);
font-family: Consolas, monospace;
font-size: 11px;
font-weight: 700;
}
.config-row {
display: flex;
align-items: center;
gap: 14px;
}
.input-wrap {
flex: 1;
height: 50px;
display: flex;
align-items: center;
border-radius: 15px;
background: rgba(2, 6, 23, 0.62);
border: 1px solid rgba(51, 65, 85, 0.95);
}
.input-prefix {
margin-left: 15px;
padding: 4px 8px;
border-radius: 8px;
color: #818cf8;
background: rgba(99, 102, 241, 0.1);
font-family: Consolas, monospace;
font-size: 11px;
font-weight: 700;
}
.server-input {
flex: 1;
height: 100%;
padding: 0 15px 0 12px;
border: 0;
outline: none;
color: #e2e8f0;
background: transparent;
font-family: Consolas, "Microsoft YaHei", monospace;
font-size: 14px;
}
.config-tip {
margin: 12px 0 0;
color: #94a3b8;
font-size: 12px;
}
.card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
margin-bottom: 22px;
padding: 22px 24px;
}
.card-title {
margin: 0;
color: #cbd5e1;
font-size: 15px;
font-weight: 600;
}
.card-url {
margin: 8px 0 0;
color: #64748b;
font-size: 12px;
font-family: Consolas, monospace;
line-height: 1.45;
word-break: break-all;
}
.request-info {
flex: 1;
min-width: 0;
}
button {
border: 0;
cursor: pointer;
color: white;
border-radius: 14px;
font-size: 14px;
font-weight: 600;
background: linear-gradient(90deg, #2563eb, #9333ea);
box-shadow: 0 8px 28px rgba(99, 102, 241, 0.25);
}
button:hover {
background: linear-gradient(90deg, #3b82f6, #a855f7);
}
button:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.secondary-btn {
flex: 0 0 78px;
height: 50px;
padding: 0 18px;
white-space: nowrap;
}
.refresh-btn {
min-width: 176px;
height: 66px;
padding: 0 26px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
white-space: nowrap;
}
.spin {
display: inline-block;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.response-head {
display: flex;
justify-content: space-between;
padding: 0 4px 8px;
color: #94a3b8;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.6px;
text-transform: uppercase;
}
#responseTime {
color: #475569;
font-family: Consolas, monospace;
}
pre {
min-height: 260px;
max-height: 260px;
overflow: auto;
margin: 0;
padding: 20px;
border-radius: 20px;
background: rgba(2, 6, 23, 0.82);
border: 1px solid rgba(51, 65, 85, 0.8);
color: #60a5fa;
white-space: pre-wrap;
word-break: break-word;
font-family: Consolas, monospace;
font-size: 14px;
line-height: 1.6;
}
.text-success {
color: #34d399;
}
.text-warning {
color: #fbbf24;
}
.text-error {
color: #fb7185;
}
14. 图标生成
如果缺少图标,打包可能报:
icons/icon.ico not found
可以先准备一个 PNG 图片,然后执行:
pnpm tauri icon .\app-icon.png
生成目录:
src-tauri/icons/
其中必须包含:
src-tauri/icons/icon.ico
15. 开发运行
cd D:\my-work\frontend-work\algorithm-engine-console
pnpm install
pnpm tauri dev
16. 打包 EXE
pnpm tauri build
如果 bundle.active=false,最终 exe 路径一般为:
src-tauri/target/release/AlgorithmEngine.exe
17. 常见错误与解决方案
17.1 Cargo 找不到 lib
错误:
can't find library `xxx_lib`, rename file to `src/lib.rs` or specify lib.path
原因:
Cargo.toml 声明了 [lib],但项目只有 main.rs。
解决:
删除 Cargo.toml 中的 [lib] 配置。
17.2 frontendDist 指向项目根目录
错误:
The configured frontendDist includes the ["src-tauri\\target", "node_modules", "src-tauri"] folders.
原因:
"frontendDist": "../"
解决:
"frontendDist": "../web"
17.3 Tauri 版本不一致
错误:
tauri (v2.0.0) : @tauri-apps/api (v2.11.1)
解决:
统一 Rust tauri 和 NPM @tauri-apps/api / cli 版本。
推荐:
"@tauri-apps/api": "2.11.1",
"@tauri-apps/cli": "2.11.1"
tauri = { version = "2.11.1", features = [] }
tauri-build = { version = "2.5.1", features = [] }
清理后重装:
Remove-Item -Recurse -Force node_modules -ErrorAction SilentlyContinue
Remove-Item -Force pnpm-lock.yaml -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force src-tauri\target -ErrorAction SilentlyContinue
Remove-Item -Force src-tauri\Cargo.lock -ErrorAction SilentlyContinue
pnpm install
pnpm tauri build
17.4 icons/icon.ico not found
错误:
icons/icon.ico not found
解决:
pnpm tauri icon .\app-icon.png
或者手动放置:
src-tauri/icons/icon.ico
17.5 双击 exe 弹出黑窗口
解决:
在 main.rs 顶部添加:
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
17.6 Command not found
错误:
Command request_xxx not found
原因:
JS invoke 的命令名和 Rust 注册的命令名不一致。
解决:
JS:
invoke("request_backend_api", ...)
Rust:
tauri::generate_handler![request_backend_api]
两边必须完全一致。
17.7 按钮点击没反应
常见原因:
import { invoke } from "@tauri-apps/api/core";
纯 HTML 页面不经过 Vite/Webpack 打包时,不能直接这样导入。
解决:
const invoke = window.__TAURI__.core.invoke;
并确保 tauri.conf.json 中有:
"withGlobalTauri": true
17.8 请求失败 error sending request
说明:
页面按钮和 Rust 命令已经打通,但 Rust 请求后端失败。
排查:
1. 后端服务是否启动
2. 后端地址是否正确
3. 接口路径是否正确
4. 请求方式是否匹配 GET / POST
5. Token 是否有效
6. 后端是否需要 body、文件或 multipart
7. 本机防火墙是否拦截
17.9 NSIS 下载超时
错误:
failed to bundle project `timeout: global`
原因:
Tauri 打安装包时需要下载 NSIS,网络超时。
解决:
先关闭安装包,只生成 exe:
"bundle": {
"active": false,
"icon": [
"icons/icon.ico"
]
}
18. 推荐发布方式
内部工具推荐先交付单文件 exe:
src-tauri/target/release/AlgorithmEngine.exe
优点:
1. 不依赖安装器
2. 不需要 NSIS
3. 构建更稳定
4. 复制到其他 Windows 电脑即可运行
后续如果需要正式安装包,再开启:
"bundle": {
"active": true,
"targets": ["nsis"],
"icon": [
"icons/icon.ico"
]
}
19. 安全注意事项
不要在文档、代码、截图、Git 仓库中保留以下内容:
真实 JWT
账号密码
接口密钥
客户地址
内网敏感 IP
生产环境接口地址
数据库连接串
推荐替换为:
YOUR_TOKEN_HERE
YOUR_API_PATH
http://localhost:8080
http://your-server-host:port
如必须使用 token,建议改成:
1. 页面输入 token
2. 登录接口动态获取 token
3. 使用本地配置文件
4. 使用后端代理鉴权
20. 完整操作命令汇总
cd D:\my-work\frontend-work
mkdir algorithm-engine-console
cd algorithm-engine-console
pnpm init
pnpm add @tauri-apps/api@2.11.1
pnpm add -D @tauri-apps/cli@2.11.1
mkdir web
mkdir web\src
mkdir src-tauri
mkdir src-tauri\src
mkdir src-tauri\icons
pnpm install
pnpm tauri dev
打包:
pnpm tauri build
运行:
.\src-tauri\target\release\AlgorithmEngine.exe
结论
使用 Tauri + Rust 可以非常轻量地将 HTML 页面封装为 Windows 桌面 EXE。
推荐最终方案:
前端:HTML + CSS + JavaScript
桌面框架:Tauri v2
后端请求:Rust reqwest 代理调用
配置保存:localStorage
窗口模式:固定默认大窗口,页面无滚动条
打包方式:优先生成单文件 exe
敏感信息:全部使用占位符,不写入代码仓库
这套方案适合企业内部桌面工具、本地运维工具、算法引擎控制台、局域网接口调用工具等场景。