首页 › tutorials › Claude Code + Tauri桌面应用全实战

Claude Code + Tauri v2 桌面应用开发:从零到交付全实战

学习目标:掌握使用 Claude Code 全流程驱动 Tauri v2 桌面应用开发,涵盖项目初始化、IPC 架构、前后端联调、打包分发。学完可独立交付跨平台桌面应用。

适用人群:有基本前端/命令行经验的开发者,对 Rust 不要求深入了解,但需愿意阅读 Rust 代码。


一、为什么选择 Tauri + Claude Code

1.1 Tauri vs Electron:真实数据对比

2026 年,Tauri v2 已进入生产就绪阶段。社区数据对比:

维度 Electron Tauri v2
安装包体积 80–200 MB 3–15 MB
内存占用 200–500 MB 30–80 MB
启动速度 2–5 秒 0.3–1 秒
后端语言 Node.js Rust
渲染引擎 内置 Chromium 系统 WebView
移动端支持 无(需 Capacitor) 原生 iOS/Android
安全模型 完整 Node.js 权限 最小权限 + 能力系统

Tauri 的核心优势:不打包 Chromium,使用操作系统原生 WebView。这意味着 macOS 用 WebKit、Windows 用 WebView2、Linux 用 WebKitGTK——安装包从 100MB+ 压缩到 5MB 级别。

1.2 Claude Code 驱动 Tauri 开发的独特价值

Claude Code 对 Tauri 开发有三个关键加速点:

  1. Rust 后端代码生成:Claude Code 能直接编写 Rust 命令(#[tauri::command]),处理序列化/反序列化,减少语言切换成本
  2. IPC 桥接自动生成:Claude Code 能同时维护 Rust 后端和 TypeScript/前端调用层,保持两端类型一致
  3. 跨文件重构:修改一个 Rust 结构体时,自动更新所有引用该类型的前端代码

实际案例:开发者 Steve Bolch 使用 Claude Code 作为"整个开发团队",独立完成了 Tauri 2.0 + SvelteKit 5 的桌面应用交付,涵盖 UI、Rust 后端、打包发布全流程。另一位开发者 Caio Ricciuti 用两天时间构建了 Glyphic——一个管理 Claude Code 会话的桌面 GUI 工具。


二、环境准备

2.1 系统要求

# macOS
xcode-select --install
brew install rustup
rustup-init

# Linux (Ubuntu/Debian)
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Windows
# 安装 Visual Studio Build Tools + WebView2
# https://visualstudio.microsoft.com/visual-cpp-build-tools/
winget install Microsoft.WebView2Runtime

2.2 核心工具安装

# Rust 工具链(Tauri 要求 1.77.2+)
rustup default stable
rustc --version  # 确认 >= 1.77.2

# Node.js(前端构建)
node --version   # 建议 >= 20.x

# Claude Code
npm install -g @anthropic-ai/claude-code
claude --version

# Tauri CLI
cargo install tauri-cli

2.3 验证环境

# 创建一个最小化测试项目
cargo tauri init --template vanilla
cargo tauri dev
# 应弹出空白桌面窗口,环境OK

三、项目初始化:CLAUDE.md 配置先行

在开始编码前,先在项目根目录创建 CLAUDE.md,这是让 Claude Code 高效理解 Tauri 项目的关键。

3.1 Tauri 项目专用 CLAUDE.md

# Project: Clipboard Manager Desktop App

## Tech Stack
- Tauri v2 (Rust backend)
- Svelte 5 + SvelteKit (frontend)
- Tailwind CSS 4 (styling)
- SQLite via rusqlite (local storage)

## Project Structure
- `src/` - SvelteKit frontend code
- `src-tauri/` - Rust backend code
- `src-tauri/src/main.rs` - Rust entry point
- `src-tauri/src/commands/` - Tauri commands (IPC handlers)
- `src-tauri/tauri.conf.json` - Tauri configuration
- `src-tauri/capabilities/` - Security capability files

## Architecture
- Frontend communicates with Rust backend ONLY via Tauri invoke()
- All file system, database, OS operations happen in Rust
- Frontend never directly accesses Node.js APIs or filesystem

## Key Rules
- Rust commands must use #[tauri::command] macro
- All command parameters must implement Serialize + Deserialize
- Use Result<T, String> for error handling in commands
- Frontend calls: import { invoke } from '@tauri-apps/api/core'
- Events use tauri::AppHandle::emit() from Rust, listen() from frontend

## Build & Run
- `cargo tauri dev` - development mode
- `cargo tauri build` - production build
- `npm run dev` - frontend-only dev (no Rust)

3.2 为什么这个 CLAUDE.md 如此重要

Tauri 项目横跨两种语言(Rust + TypeScript/Svelte),Claude Code 需要明确知道:

没有这个文件,Claude Code 可能将 Rust 命令写在 main.rs 里导致文件膨胀,或在前端直接使用 Node.js API(Tauri 环境中没有 Node.js)。


四、创建项目:SvelteKit + Tauri v2

4.1 用 Claude Code 初始化

在终端中启动 Claude Code,直接对话式创建项目:

mkdir clipboard-manager && cd clipboard-manager
claude

在 Claude Code 中输入:

创建一个 Tauri v2 + SvelteKit 项目。
- 使用 npm create tauri-app@latest -- --template sveltekit
- 项目名 clipboard-manager
- 前端用 SvelteKit + Tailwind CSS
- 创建 CLAUDE.md(使用上面准备的内容)
- 创建 src-tauri/src/commands/ 目录

Claude Code 会执行以下操作序列:

# Step 1: 脚手架
npm create tauri-app@latest -- clipboard-manager --template sveltekit

# Step 2: 安装依赖
cd clipboard-manager
npm install
npm install -D @tailwindcss/vite tailwindcss

# Step 3: 配置 Tailwind
# 在 vite.config.js 添加 Tailwind 插件

# Step 4: Tauri 依赖
cd src-tauri
cargo add serde --features derive
cargo add rusqlite --features bundled

4.2 项目结构验证

clipboard-manager/
├── CLAUDE.md                    # 项目规则(核心!)
├── package.json
├── src/                         # SvelteKit 前端
│   ├── routes/
│   │   └── +page.svelte        # 主页面
│   ├── lib/
│   │   └── api.ts              # Tauri invoke 封装
│   └── app.html
├── src-tauri/                   # Rust 后端
│   ├── Cargo.toml
│   ├── tauri.conf.json
│   ├── capabilities/
│   │   └── default.json        # 安全能力配置
│   └── src/
│       ├── main.rs              # 入口 + 插件注册
│       ├── lib.rs               # 命令注册
│       └── commands/
│           ├── mod.rs
│           └── clipboard.rs     # 剪贴板操作命令
└── static/

五、核心架构:IPC 桥接实战

Tauri v2 的 IPC 架构是整个开发的关键。理解这张图,就理解了 Tauri 的数据流:

graph LR
    subgraph Frontend["前端 (WebView)"]
        UI["Svelte 组件"]
        API["api.ts 封装层"]
    end

    subgraph IPC["IPC 桥接"]
        Invoke["invoke()"]
        Event["事件系统"]
        Channel["Channel 流"]
    end

    subgraph Backend["Rust 后端"]
        Cmd["#[tauri::command]"]
        State["StateManager"]
        DB["SQLite / 文件系统"]
    end

    UI --> API --> Invoke --> Cmd
    Cmd --> State --> DB
    DB --> State --> Event --> UI

5.1 定义 Rust 命令

src-tauri/src/commands/clipboard.rs:

use serde::{Deserialize, Serialize};
use tauri::State;
use std::sync::Mutex;

#[derive(Serialize, Deserialize, Clone)]
pub struct ClipItem {
    pub id: i64,
    pub content: String,
    pub content_type: String,  // "text" | "image" | "file"
    pub created_at: String,
    pub pinned: bool,
}

pub struct AppState {
    pub db: Mutex<rusqlite::Connection>,
}

#[tauri::command]
pub fn get_clips(
    state: State<'_, AppState>,
    limit: Option<u32>,
) -> Result<Vec<ClipItem>, String> {
    let limit = limit.unwrap_or(50);
    let conn = state.db.lock().map_err(|e| e.to_string())?;

    let mut stmt = conn
        .prepare("SELECT id, content, content_type, created_at, pinned FROM clips ORDER BY created_at DESC LIMIT ?1")
        .map_err(|e| e.to_string())?;

    let items = stmt
        .query_map([limit], |row| {
            Ok(ClipItem {
                id: row.get(0)?,
                content: row.get(1)?,
                content_type: row.get(2)?,
                created_at: row.get(3)?,
                pinned: row.get(4)?,
            })
        })
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;

    Ok(items)
}

#[tauri::command]
pub fn save_clip(
    state: State<'_, AppState>,
    content: String,
    content_type: String,
) -> Result<ClipItem, String> {
    let conn = state.db.lock().map_err(|e| e.to_string())?;

    conn.execute(
        "INSERT INTO clips (content, content_type) VALUES (?1, ?2)",
        [&content, &content_type],
    ).map_err(|e| e.to_string())?;

    let id = conn.last_insert_rowid();

    Ok(ClipItem {
        id,
        content,
        content_type,
        created_at: chrono::Utc::now().to_rfc3339(),
        pinned: false,
    })
}

#[tauri::command]
pub fn delete_clip(state: State<'_, AppState>, id: i64) -> Result<(), String> {
    let conn = state.db.lock().map_err(|e| e.to_string())?;
    conn.execute("DELETE FROM clips WHERE id = ?1", [id])
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
pub fn toggle_pin(state: State<'_, AppState>, id: i64) -> Result<bool, String> {
    let conn = state.db.lock().map_err(|e| e.to_string())?;
    conn.execute(
        "UPDATE clips SET pinned = NOT pinned WHERE id = ?1",
        [id],
    ).map_err(|e| e.to_string())?;

    let pinned: bool = conn
        .query_row("SELECT pinned FROM clips WHERE id = ?1", [id], |row| row.get(0))
        .map_err(|e| e.to_string())?;

    Ok(pinned)
}

5.2 注册命令

src-tauri/src/lib.rs:

mod commands;
use commands::clipboard::{AppState, get_clips, save_clip, delete_clip, toggle_pin};

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let db = rusqlite::Connection::open_in_memory()
        .expect("failed to open database");

    db.execute_batch(
        "CREATE TABLE IF NOT EXISTS clips (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            content TEXT NOT NULL,
            content_type TEXT NOT NULL DEFAULT 'text',
            created_at TEXT DEFAULT (datetime('now')),
            pinned INTEGER DEFAULT 0
        )"
    ).expect("failed to create table");

    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .manage(AppState {
            db: std::sync::Mutex::new(db),
        })
        .invoke_handler(tauri::generate_handler![
            get_clips,
            save_clip,
            delete_clip,
            toggle_pin,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

5.3 前端 API 封装层

src/lib/api.ts:

import { invoke } from '@tauri-apps/api/core';

export interface ClipItem {
  id: number;
  content: string;
  content_type: string;
  created_at: string;
  pinned: boolean;
}

export async function getClips(limit?: number): Promise<ClipItem[]> {
  return invoke<ClipItem[]>('get_clips', { limit });
}

export async function saveClip(content: string, contentType: string): Promise<ClipItem> {
  return invoke<ClipItem>('save_clip', { content, contentType });
}

export async function deleteClip(id: number): Promise<void> {
  return invoke('delete_clip', { id });
}

export async function togglePin(id: number): Promise<boolean> {
  return invoke<boolean>('toggle_pin', { id });
}

5.4 前端 UI 组件

src/routes/+page.svelte:

<script lang="ts">
  import { onMount } from 'svelte';
  import { getClips, saveClip, deleteClip, togglePin, type ClipItem } from '$lib/api';

  let clips: ClipItem[] = $state([]);
  let inputText: string = $state('');
  let loading: boolean = $state(false);

  onMount(async () => {
    await refreshClips();
  });

  async function refreshClips() {
    loading = true;
    try {
      clips = await getClips(100);
    } catch (e) {
      console.error('Failed to load clips:', e);
    } finally {
      loading = false;
    }
  }

  async function handleSave() {
    if (!inputText.trim()) return;
    await saveClip(inputText, 'text');
    inputText = '';
    await refreshClips();
  }

  async function handleDelete(id: number) {
    await deleteClip(id);
    await refreshClips();
  }

  async function handlePin(id: number) {
    await togglePin(id);
    await refreshClips();
  }
</script>

<main class="min-h-screen bg-gray-900 text-white p-6">
  <h1 class="text-2xl font-bold mb-6">📋 Clipboard Manager</h1>

  <div class="flex gap-2 mb-6">
    <input
      bind:value={inputText}
      onkeydown={(e) => e.key === 'Enter' && handleSave()}
      placeholder="粘贴或输入内容..."
      class="flex-1 px-4 py-2 rounded bg-gray-800 border border-gray-700"
    />
    <button onclick={handleSave} class="px-6 py-2 bg-blue-600 rounded hover:bg-blue-500">
      保存
    </button>
  </div>

  {#if loading}
    <p class="text-gray-400">加载中...</p>
  {:else if clips.length === 0}
    <p class="text-gray-400">暂无剪贴板记录</p>
  {:else}
    <div class="space-y-2">
      {#each clips as clip (clip.id)}
        <div class="flex items-center gap-3 p-3 rounded bg-gray-800">
          <span class="flex-1 text-sm truncate">{clip.content}</span>
          <span class="text-xs text-gray-500">{clip.created_at}</span>
          <button
            onclick={() => handlePin(clip.id)}
            class="px-2 py-1 text-xs rounded {clip.pinned ? 'bg-yellow-600' : 'bg-gray-700'}"
          >
            {clip.pinned ? '📌 已置顶' : '置顶'}
          </button>
          <button
            onclick={() => handleDelete(clip.id)}
            class="px-2 py-1 text-xs bg-red-700 rounded hover:bg-red-600"
          >
            删除
          </button>
        </div>
      {/each}
    </div>
  {/if}
</main>

5.5 关键:invoke() 的命名转换规则

Tauri 的 invoke() 有一个容易出错的命名映射:

Rust 函数名 invoke() 调用名
get_clips 'get_clips'
save_clip 'save_clip'
togglePin 'toggle_pin'(camelCase → snake_case)

Claude Code 特别注意:如果 Rust 命令用 snake_case 命名(如 get_clips),前端 invoke 直接用同名。如果 Rust 用 camelCase(如 togglePin),Tauri 会自动转为 toggle_pin。在 CLAUDE.md 中统一约定使用 snake_case 可避免此问题。


六、安全能力配置

Tauri v2 的安全模型基于"能力"(Capabilities),而非 Electron 的全权限模型。

6.1 能力配置文件

src-tauri/capabilities/default.json:

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Default permissions for the app",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "core:window:default",
    "core:window:allow-close",
    "core:window:allow-set-title",
    "opener:default",
    {
      "identifier": "fs:default",
      "allow": [
        { "path": "$APPDATA/**" }
      ]
    }
  ]
}

6.2 安全原则

graph TD
    A["用户操作"] --> B{"需要 OS 权限?"}
    B -->|"否"| C["前端直接处理"]
    B -->|"是"| D["通过 invoke 调用 Rust"]
    D --> E{"能力配置允许?"}
    E -->|"否"| F["❌ 拒绝,返回权限错误"]
    E -->|"是"| G["✅ 执行 Rust 命令"]
    G --> H["返回结果到前端"]

    style F fill:#ef4444
    style G fill:#22c55e

与 Electron 对比:Electron 的 renderer 进程(如果不启用 contextIsolation)可以直访问 Node.js API,包括文件系统、网络、子进程。Tauri 前端完全无法直接访问系统资源——所有操作必须经过能力系统审核。


七、用 Claude Code 加速开发的实战技巧

7.1 分层 Prompt 策略

与其让 Claude Code 一次性生成整个应用,不如按 Tauri 的架构分层驱动:

第一轮:定义数据模型和命令签名

在 src-tauri/src/commands/clipboard.rs 中定义以下命令:
- get_clips(limit: Option<u32>) -> Vec<ClipItem>
- save_clip(content: String, content_type: String) -> ClipItem
- delete_clip(id: i64) -> ()
- search_clips(query: String) -> Vec<ClipItem>

ClipItem 结构体包含:id, content, content_type, created_at, pinned
使用 rusqlite,数据库路径用 tauri::api::path::app_data_dir

第二轮:生成前端 API 层

根据 src-tauri/src/commands/clipboard.rs 中的 Rust 命令,
生成对应的 TypeScript 封装到 src/lib/api.ts。
使用 invoke() 调用,导出类型安全的函数。

第三轮:生成 UI 组件

基于 src/lib/api.ts 中的函数,
创建 src/routes/+page.svelte:
- 顶部输入框 + 保存按钮
- 剪贴板列表,每项有:内容预览、时间、置顶按钮、删除按钮
- 置顶项排在最前
- 使用 Tailwind CSS,深色主题

这种分层方式的优势:每一步的输出都是下一步的输入,Claude Code 的上下文利用率最高。

7.2 使用 CLAUDE.md 锁定架构约定

## Tauri Command Rules
- 命令文件统一放在 src-tauri/src/commands/ 目录
- 每个领域一个文件:clipboard.rs, settings.rs, export.rs
- 错误处理统一用 Result<T, String>
- 数据库操作使用 State<AppState> 注入
- 不在 main.rs 中写业务逻辑

## Frontend Rules
- 所有 Tauri 调用封装在 src/lib/api.ts
- UI 组件不直接 import invoke
- 使用 TypeScript 类型对应 Rust 结构体
- CSS 只用 Tailwind 工具类

7.3 调试 IPC 问题的 Prompt 模板

当遇到 invoke 调用失败时:

调试以下 Tauri IPC 调用错误:
- 前端调用:invoke('get_clips', { limit: 50 })
- 错误信息:[粘贴完整错误]
- Rust 命令签名:[粘贴函数签名]
- 检查:参数名是否匹配 snake_case/camelCase 转换规则

常见错误排查表:

错误信息 原因 解决方案
Command getClips not found 命令未注册 检查 lib.rs 的 invoke_handler
Missing field limit 参数名不匹配 Rust 用 limit,前端也必须用 { limit: 50 }
Permission denied 能力配置缺失 在 capabilities/default.json 添加权限
Serialization error 类型不匹配 检查 Rust Serialize 和 TS 类型是否一致

八、事件系统:Rust → 前端的实时推送

除了请求-响应模式的 invoke(),Tauri 还支持事件驱动的实时通信。

8.1 Rust 端发射事件

use tauri::{AppHandle, Emitter};

#[tauri::command]
pub fn watch_clipboard(app: AppHandle) -> Result<(), String> {
    let handle = app.clone();
    std::thread::spawn(move || {
        // 模拟剪贴板监听
        loop {
            let content = read_clipboard_content();
            if let Some(text) = content {
                let _ = handle.emit("clipboard-changed", text);
            }
            std::thread::sleep(std::time::Duration::from_secs(1));
        }
    });
    Ok(())
}

8.2 前端监听事件

import { listen } from '@tauri-apps/api/event';

onMount(async () => {
  const unlisten = await listen<string>('clipboard-changed', (event) => {
    console.log('新剪贴板内容:', event.payload);
    refreshClips(); // 自动刷新列表
  });

  return () => unlisten();
});

8.3 双向通信架构

sequenceDiagram
    participant F as 前端 (Svelte)
    participant T as Tauri Core
    participant R as Rust 后端

    F->>T: invoke("watch_clipboard")
    T->>R: 路由到 watch_clipboard 命令
    R->>R: 启动后台线程监听剪贴板

    loop 每秒检测
        R->>T: emit("clipboard-changed", payload)
        T->>F: 事件回调触发
        F->>T: invoke("get_clips")
        T->>R: 查询数据库
        R-->>F: 返回最新列表
    end

九、打包与分发

9.1 构建生产版本

# 构建所有平台的安装包
cargo tauri build

# 输出位置
# macOS: src-tauri/target/release/bundle/dmg/
# Windows: src-tauri/target/release/bundle/msi/
# Linux: src-tauri/target/release/bundle/deb/ 或 appimage/

9.2 配置应用元数据

src-tauri/tauri.conf.json 关键字段:

{
  "productName": "Clipboard Manager",
  "version": "1.0.0",
  "identifier": "com.example.clipboard-manager",
  "build": {
    "beforeBuildCommand": "npm run build",
    "beforeDevCommand": "npm run dev",
    "devUrl": "http://localhost:5173",
    "frontendDist": "../build"
  },
  "app": {
    "windows": [
      {
        "title": "Clipboard Manager",
        "width": 800,
        "height": 600,
        "resizable": true,
        "fullscreen": false
      }
    ],
    "security": {
      "csp": "default-src 'self'; style-src 'unsafe-inline'"
    }
  },
  "bundle": {
    "active": true,
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ],
    "category": "Utility"
  }
}

9.3 代码签名(生产必需)

# macOS
export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)"
cargo tauri build --target universal-apple-darwin

# Windows
# 需要购买代码签名证书
cargo tauri build

十、完整实战案例:Markdown 笔记应用

以下是一个比剪贴板管理器更完整的案例——支持 Markdown 编辑、实时预览、本地持久化的笔记应用。

10.1 项目结构

markdown-notes/
├── CLAUDE.md
├── src/
│   ├── routes/
│   │   ├── +page.svelte          # 笔记列表
│   │   └── edit/[id]/+page.svelte # 编辑页面
│   └── lib/
│       ├── api.ts                # IPC 封装
│       └── markdown.ts           # Markdown 解析
├── src-tauri/
│   └── src/commands/
│       ├── mod.rs
│       ├── notes.rs              # 笔记 CRUD
│       └── export.rs             # 导出 PDF/HTML

10.2 用 Claude Code 一键生成

在项目根目录启动 Claude Code 后:

创建一个 Tauri v2 + SvelteKit 的 Markdown 笔记应用。

要求:
1. Rust 后端使用 SQLite 存储,包含 notes 表(id, title, content, created_at, updated_at, tags)
2. 实现命令:list_notes, get_note, create_note, update_note, delete_note, search_notes, export_html
3. 前端:
   - 左侧笔记列表(支持搜索、标签过滤)
   - 右侧 Markdown 编辑器(实时预览)
   - 顶部工具栏(新建、删除、导出)
4. 使用 marked.js 解析 Markdown
5. 深色主题,响应式布局
6. 所有文件操作通过 Rust 后端

请按照 CLAUDE.md 中的架构规则,分层实现:
先 Rust 命令,再 API 封装,最后 UI 组件。

Claude Code 会自动按 CLAUDE.md 约定的架构分层输出代码。

10.3 预期成果

文件 行数 功能
commands/notes.rs ~120 7 个 CRUD + 搜索命令
commands/export.rs ~40 HTML 导出
api.ts ~50 TypeScript 封装
+page.svelte ~80 笔记列表
edit/[id]/+page.svelte ~150 编辑器 + 预览

整体代码量约 440 行,Claude Code 单次对话即可完成。


十一、移动端扩展

Tauri v2 的重大升级是支持 iOS/Android。同一套 Rust 后端 + 前端代码,可编译到移动端。

# 添加移动端目标
cargo tauri android init
cargo tauri ios init

# 运行 Android
cargo tauri android dev

# 运行 iOS
cargo tauri ios dev

注意事项: - 移动端使用原生 WebView(Android WebView / iOS WKWebView) - 文件系统路径需要使用 Tauri 的 app_data_dir() 而非硬编码 - 某些 Rust crate 可能不支持移动目标编译 - UI 需要适配触摸交互和移动端布局


十二、常见问题与排错

Q1: cargo tauri dev 编译报错 WebKit 相关

Linux 用户:确保安装了 libwebkit2gtk-4.1-dev(注意是 4.1 版本,不是 4.0):

sudo apt install libwebkit2gtk-4.1-dev

Q2: 前端热更新不生效

检查 tauri.conf.json 中的 devUrl 是否匹配 SvelteKit 开发服务器端口(默认 5173)。

Q3: Rust 编译时间过长

# Cargo.toml - 开发时使用优化
[profile.dev]
opt-level = 1

[profile.dev.package."*"]
opt-level = 3

Q4: SQLite 数据持久化

开发模式下使用内存数据库(Connection::open_in_memory),生产环境需改为文件路径:

let db_path = app.path().app_data_dir()?.join("data.db");
let db = rusqlite::Connection::open(&db_path)?;

Q5: invoke 参数传递 undefined

TypeScript 的可选参数(limit?: number)在传递给 Rust 时,如果值为 undefined,Tauri 会将其视为缺失参数。确保 Rust 端参数类型为 Option<T>。


十三、开发工作流总结

graph TD
    A["1. 项目初始化<br/>cargo tauri init --template sveltekit"] --> B["2. 编写 CLAUDE.md<br/>定义架构规则"]
    B --> C["3. Claude Code 分层驱动<br/>Rust命令 → API层 → UI"]
    C --> D["4. cargo tauri dev<br/>实时联调"]
    D --> E{"功能完整?"}
    E -->|"否"| C
    E -->|"是"| F["5. cargo tauri build<br/>打包安装包"]
    F --> G["6. 代码签名 + 分发"]

    style A fill:#3b82f6,color:#fff
    style B fill:#8b5cf6,color:#fff
    style C fill:#f59e0b,color:#fff
    style D fill:#22c55e,color:#fff
    style F fill:#ef4444,color:#fff
    style G fill:#ec4899,color:#fff

延伸阅读

训练营适用性评估

维度 评估
适合训练营类型 桌面应用开发训练营、Rust 入门训练营、跨平台开发工坊
预计教学时长 4–6 小时(含动手实操)
学员基础要求 基本前端知识 + 命令行操作能力
硬件要求 任意操作系统,4GB+ 内存
核心产出 每人交付一个可安装的桌面应用

参考来源

  1. I built and shipped a full desktop app entirely with Claude Code — 开发者使用 Claude Code 独立完成 Tauri 2.0 + SvelteKit 桌面应用交付的完整经验分享
  2. Tauri Practical Guide with Claude Code | ClaudeCodeLab — Claude Code 驱动 Tauri 开发的实战指南
  3. Build a Desktop App with Tauri v2 in 2026 — Tauri v2 桌面应用分步教程
  4. Claude Code with Tauri: Build Rust-Backed Desktop Apps — CLAUDE.md 配置与 IPC 架构最佳实践
  5. Tauri v2 IPC Architecture — Tauri 官方进程间通信文档

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入