跳至主要內容

Tauri + Vue 3:前端框架整合實戰

10 分鐘閱讀 2,400 字

前言:為什麼選 Vue 3?

在上一篇文章中,我們認識了 Tauri 2.0 的基本概念和專案建立方式。現在是時候把前端框架帶進來了。

Vue 3 搭配 Tauri 是一個相當理想的組合。Vue 的 Composition API 讓程式碼組織更靈活,搭配 TypeScript 能提供完整的型別安全。加上 Vue 生態系的 Router 和 Pinia,你幾乎可以用開發 SPA 的方式來開發桌面應用——只不過這次你的「瀏覽器」是一個原生視窗。

建立 Tauri + Vue 3 專案

最快的方式是用 create-tauri-app 直接指定 Vue 模板:

npm create tauri-app@latest my-vue-tauri -- --template vue-ts
cd my-vue-tauri
npm install

這會建立一個已經整合好 Vite + Vue 3 + TypeScript 的 Tauri 專案。接著安裝我們需要的套件:

# Vue Router 和 Pinia 狀態管理
npm install vue-router pinia

# Tauri API(用於呼叫系統功能)
npm install @tauri-apps/api @tauri-apps/plugin-notification @tauri-apps/plugin-clipboard-manager

整合 Vue Router

桌面應用通常也有多個「頁面」或「視圖」。Vue Router 在這裡完美適用,只是我們用 createWebHashHistory 而不是 createWebHistory,因為 Tauri 的 WebView 不是真正的 HTTP 伺服器。

建立 src/router/index.ts

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import SettingsView from '../views/SettingsView.vue'

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/settings',
      name: 'settings',
      component: SettingsView
    }
  ]
})

export default router

然後在 src/main.ts 中註冊:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

Pinia 狀態管理

對於桌面應用來說,狀態管理特別重要。使用者的偏好設定、應用程式狀態等都需要集中管理。Pinia 是 Vue 3 官方推薦的狀態管理方案,語法簡潔、TypeScript 支援極佳。

建立一個簡單的應用狀態 store,src/stores/app.ts

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useAppStore = defineStore('app', () => {
  const title = ref('My Tauri App')
  const isMaximized = ref(false)
  const theme = ref<'light' | 'dark'>('light')

  function toggleTheme() {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
  }

  return { title, isMaximized, theme, toggleTheme }
})

使用 Tauri API

Tauri 2.0 的前端 API 透過 @tauri-apps/api 和各種插件提供。要使用這些功能,你需要先在 Tauri 的 capabilities 設定中授權。

設定權限

src-tauri/capabilities/default.json 中加入需要的權限:

{
  "identifier": "default",
  "description": "Default capabilities",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "core:window:allow-minimize",
    "core:window:allow-maximize",
    "core:window:allow-unmaximize",
    "core:window:allow-close",
    "core:window:allow-start-dragging",
    "core:window:allow-is-maximized",
    "notification:default",
    "notification:allow-notify",
    "clipboard-manager:allow-write-text",
    "clipboard-manager:allow-read-text"
  ]
}

同時,在 src-tauri/Cargo.toml 中加入插件依賴:

[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-notification = "2"
tauri-plugin-clipboard-manager = "2"

並在 src-tauri/src/lib.rs 中註冊插件:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_notification::init())
        .plugin(tauri_plugin_clipboard_manager::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

視窗控制

桌面應用最基本的需求就是控制視窗。以下是在 Vue 元件中操作視窗的範例:

<script setup lang="ts">
import { getCurrentWindow } from '@tauri-apps/api/window'
import { useAppStore } from '../stores/app'

const appWindow = getCurrentWindow()
const store = useAppStore()

async function minimizeWindow() {
  await appWindow.minimize()
}

async function toggleMaximize() {
  if (await appWindow.isMaximized()) {
    await appWindow.unmaximize()
    store.isMaximized = false
  } else {
    await appWindow.maximize()
    store.isMaximized = true
  }
}

async function closeWindow() {
  await appWindow.close()
}
</script>

系統通知

使用 Tauri 的通知插件可以發送系統層級的通知:

import {
  isPermissionGranted,
  requestPermission,
  sendNotification
} from '@tauri-apps/plugin-notification'

async function notify(title: string, body: string) {
  let hasPermission = await isPermissionGranted()
  if (!hasPermission) {
    const permission = await requestPermission()
    hasPermission = permission === 'granted'
  }
  if (hasPermission) {
    sendNotification({ title, body })
  }
}

// 使用方式
await notify('任務完成', '你的檔案已經成功匯出!')

剪貼簿操作

import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager'

// 複製文字到剪貼簿
async function copyToClipboard(text: string) {
  await writeText(text)
}

// 讀取剪貼簿內容
async function pasteFromClipboard(): Promise<string> {
  return await readText()
}

自訂標題列

原生視窗的標題列雖然功能完整,但長得就是「系統預設款」。如果你想要一個品牌感更強的應用,自訂標題列是必經之路。

首先,在 tauri.conf.json 中關閉原生標題列裝飾:

{
  "app": {
    "windows": [
      {
        "title": "My Vue Tauri App",
        "width": 1024,
        "height": 768,
        "decorations": false
      }
    ]
  }
}

接著建立自訂標題列元件 src/components/TitleBar.vue

<script setup lang="ts">
import { getCurrentWindow } from '@tauri-apps/api/window'
import { useAppStore } from '../stores/app'

const appWindow = getCurrentWindow()
const store = useAppStore()

async function handleMinimize() {
  await appWindow.minimize()
}

async function handleToggleMaximize() {
  const maximized = await appWindow.isMaximized()
  if (maximized) {
    await appWindow.unmaximize()
  } else {
    await appWindow.maximize()
  }
  store.isMaximized = !maximized
}

async function handleClose() {
  await appWindow.close()
}
</script>

<template>
  <div
    class="titlebar"
    data-tauri-drag-region
  >
    <div class="titlebar-title">{{ store.title }}</div>
    <div class="titlebar-buttons">
      <button @click="handleMinimize" class="btn-minimize">
        &#x2212;
      </button>
      <button @click="handleToggleMaximize" class="btn-maximize">
        {{ store.isMaximized ? '&#x29C9;' : '&#x25A1;' }}
      </button>
      <button @click="handleClose" class="btn-close">
        &#x2715;
      </button>
    </div>
  </div>
</template>

<style scoped>
.titlebar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 36px;
  padding: 0 12px;
  background: #1a1a2e;
  color: #e0e0e0;
  user-select: none;
}

.titlebar-title {
  font-size: 13px;
  font-weight: 500;
}

.titlebar-buttons {
  display: flex;
  gap: 4px;
}

.titlebar-buttons button {
  width: 32px;
  height: 28px;
  border: none;
  background: transparent;
  color: #e0e0e0;
  font-size: 14px;
  cursor: pointer;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.titlebar-buttons button:hover {
  background: rgba(255, 255, 255, 0.1);
}

.btn-close:hover {
  background: #e81123 !important;
  color: white;
}
</style>

注意 data-tauri-drag-region 這個屬性——它告訴 Tauri 這個區域可以用來拖曳移動視窗,就像原生標題列一樣。

然後在 App.vue 中使用:

<script setup lang="ts">
import TitleBar from './components/TitleBar.vue'
</script>

<template>
  <TitleBar />
  <main class="content">
    <router-view />
  </main>
</template>

<style>
body {
  margin: 0;
  overflow: hidden;
}

.content {
  height: calc(100vh - 36px);
  overflow-y: auto;
}
</style>

小結

到這裡,你已經建立了一個相當完整的 Tauri + Vue 3 桌面應用骨架:

  • Vue Router 管理多頁面導航
  • Pinia 集中管理應用狀態
  • Tauri API 控制視窗行為
  • 系統通知剪貼簿等原生功能
  • 自訂標題列提供品牌化的使用者體驗

這個架構已經足以應對大多數桌面應用的需求。接下來你可以加入更多功能,例如檔案系統操作、資料庫整合(搭配 SQLite)、或是自動更新機制。

Tauri 的哲學是「只給你需要的」,這和 Vue 的漸進式理念不謀而合。從一個簡單的工具開始,隨著需求成長逐步擴展——這才是工程的藝術。

分享這篇文章