
一、前言
在开始写代码之前,我已经将官网上的核心概念大致扫了一遍,tauri.app/zh-cn/conce... web 端相差甚远,不过我好像摸到了一些门路,以下都是我个人见解,可能有所偏差,欢迎指正。Tauri 开发的应用程序分为两个部分,前端和后端,其实本质上也是 C/S 架构。前端就和之前 web 开发一样,你可以选择自己擅长的 UI 框架,而后端则是基于 Rust 来做的,可以完成业务逻辑操作,系统调用等等。我的脑海里目前结构是这样的,对应代码的目录,前端就是 src 下的代码,后端就是 src-tauri。

看到这其实就比较熟悉了,把 Rust 换成我们熟悉的 Java,又回到了 web 开发的模式了。当然如果你愿意深入学习 Rust,完全用 Rust 来构建服务端也是可以的。
所以最后实际上我的项目就变成了这样。

这就很熟悉了,Tauri 似乎只是简单的套壳了。后来仔细想想好像也不是,相比于 Web 端多了一些访问系统原生 Api 的能力。
✅ Vue3:前端开发体验好
✅ Java 后端:成熟稳定,开发效率高
✅ Tauri:提供桌面应用外壳,未来可扩展系统功能
二、技术栈选择
前端(Tauri):
- Vue3 + TypeScript
- UI 组件库:Ant Design Vue
- HTTP 客户端:axios
- 状态管理:Pinia
后端(Java):
-
Spring Boot + Sa-Token
-
数据库:PostgreSQL + MyBatis-Plus
-
API 文档:Swagger
-
部署:Docker + 云服务器
截止目前 2025.11.26 我会使用当前所有最新稳定版本进行开发。我建议是我当前的文章作为辅助,以官方文档为主,我会在关键位置给出文档链接。
三、完成前后端数据交互
3.1. 目录机构规划
首先和常规 Vue 项目一样,规划好我们的目录结构,大致如下
bash
src
├─ api # API 接口
├─ assets # 静态资源
├─ components # 通用组件
├─ layouts # 布局组件
├─ router # 路由配置
├─ stores # 状态管理(Pinia)
├─ utils # 工具函数
├─ views # 页面组件
├─ App.vue
├─ main.ts
└─ vite-env.d.ts
3.2. 安装整合常用依赖
安装所有依赖
bash
# Vue 生态
pnpm add vue-router@4 pinia pinia-plugin-persistedstate
# UI 组件库 (Ant Design Vue)
pnpm add ant-design-vue@4.x @ant-design/icons-vue
# 样式预处理
pnpm add install less
# HTTP 客户端
pnpm add install axios
一键安装所有
bash
pnpm add vue-router@4 pinia pinia-plugin-persistedstate ant-design-vue@4.x @ant-design/icons-vue less axios
3.2.1. 整合 Ant Design Vue
这里我希望能够实现自动按需导入,官方文档这里写的不够清楚,www.antdv.com/docs/vue/ge...
unplugin-vue-components和unplugin-auto-import这两款插件来完成。
bash
pnpm install -D unplugin-vue-components unplugin-auto-import
在插件的 GitHub 仓库中可以看到是支持的,所以无需担心。
在 vite.config.js 中配置按需引入:
typescript
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [AntDesignVueResolver()],
}),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: 'less', // 使用 less
}),
],
}),
],
})
这里要注意, 必须用
less
随便找一个组件,测试一下

3.2.2. 整合 Vue Router
文档:router.vuejs.org/zh/installa...
创建路由实例 router/index.js
typescript
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', component: HomeView },
{ path: '/about', component: AboutView },
]
})
export default router
为了方便测试,我定义了两个页面组件。
main.ts 中注册路由
typescript
import { createApp } from "vue";
import App from "./App.vue";
import router from './router'
createApp(App).use(router).mount("#app");
在 app.vue 中测试
vue
<template>
<h1>Hello App!</h1>
<p><strong>Current route path:</strong> {{ $route.fullPath }}</p>
<nav>
<RouterLink to="/">Go to Home</RouterLink>
<RouterLink to="/about">Go to About</RouterLink>
</nav>
<main>
<RouterView />
</main>
</template>

3.2.3. 整合 pinia & pinia-plugin-persistedstate
官方文档:pinia.vuejs.org/zh/introduc...
按模块化定义 store,以下为目录结构
stores
├─ modules
│ └─ user.ts
└─ index.ts
- 创建
src/stores/index.ts
typescript
import { createPinia } from 'pinia'
const store = createPinia()
export default store
main.ts里引入
typescript
//main.ts
...
// 引入 pinia
import store from './stores'
const app = createApp(App)
app.use(store)
...
- modules: 按模块定义对应的 store, user.ts 表示用户相关数据存储的仓库
typescript
// user.ts
import { defineStore } from 'pinia'
// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useUserInfoStore = defineStore('userInfo', {
// 其他配置...
})
在引入 pinia 的时候做一些调整,不直接在 main.ts 里引入,这样做的好处在于,之后对于 pinia 集成一些插件,或者做一些其他配置,可以独立出来,不用全部堆积在 main.ts 中
- 使用
pinia-plugin-persistedstate持久化
官方文档:praz.codeberg.page/pinia-plugi...
在
web端的时候可以用LocalStorage、SessionStorage等,但是桌面端我就不确定了,我去搜了一下,据说是支持LocalStorage的,但是有局限性,推荐了一个Tauri Store,这个等到具体使用的时候再去研究,今天任务就是先整合上。
将插件添加到 pinia 实例上
typescript
// ./stores/index.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(piniaPluginPersistedstate)
export default store
只需要在定义的 store 里开启配置即可完成持久化操作
typescript
export const useUserInfoStore = defineStore('userInfo', {
// 其他配置...
persist: true
})
默认使用的就是
LocalStorage,这里我暂时不测试了,等到用的时候替换为Tauri Store再研究。
3.2.4. 整合 Axios
定义请求实例,这里我直接从之前项目中拷贝过来的,基本上都差不多。
创建 utils/request.ts
typescript
import { message } from 'ant-design-vue'
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios'
type RequestConfig<T = unknown> = AxiosRequestConfig & {
// 可以在这里扩展自定义配置
mock?: boolean // 是否使用mock数据
mockData?: T // mock数据
noErrorToast?: boolean // 是否不显示错误提示
}
class Request {
private instance: AxiosInstance // axios实例
constructor() {
this.instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 180000,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
})
this.setupInterceptors()
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config) => config,
(error) => Promise.reject(error),
)
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
// 业务状态码处理
if (response.data.code !== 200) {
const config = response.config as RequestConfig
if (!config.noErrorToast) {
message.error(response.data.message || '请求失败')
}
return Promise.reject(response.data)
}
return response.data.data
},
(error) => {
// 处理mock数据
if (error.isMock) {
return Promise.resolve(error.data)
}
const config = error.config as RequestConfig | undefined
// 只有配置了不跳过错误处理时才显示错误提示
if (!config?.noErrorToast) {
let msg = '请求错误'
if (error.response) {
switch (error.response.status) {
case 400:
msg = '请求参数错误'
break
case 401:
msg = '未授权,请登录'
break
case 403:
msg = '拒绝访问'
break
case 404:
msg = '请求资源不存在'
break
case 500:
msg = '服务器异常,请稍后再试'
break
default:
msg = error.response.data?.message || `服务器错误: ${error.response.status}`
}
} else if (error.request) {
msg = '请求超时或网络异常'
} else {
msg = error.message || '未知错误'
}
message.error(msg)
}
return Promise.reject(error)
},
)
}
// 封装核心请求方法
public request<T = unknown>(config: RequestConfig<T>): Promise<T> {
// 模拟数据直接返回
if (config.mock) {
return Promise.resolve(config.mockData as T)
}
return this.instance(config)
}
public get<T = unknown>(url: string, config?: RequestConfig<T>): Promise<T> {
return this.request({ ...config, method: 'GET', url })
}
public post<T = unknown>(url: string, data?: unknown, config?: RequestConfig<T>): Promise<T> {
return this.request({ ...config, method: 'POST', url, data })
}
public put<T = unknown>(url: string, data?: unknown, config?: RequestConfig<T>): Promise<T> {
return this.request({ ...config, method: 'PUT', url, data })
}
public delete<T = unknown>(url: string, data?: unknown, config?: RequestConfig<T>): Promise<T> {
return this.request({ ...config, method: 'DELETE', url, data })
}
}
const http = new Request()
export default http
定义个接口测试一下
创建 api/user.ts
typescript
import http from '../utils/request'
export const userApi = {
// 获取用户列表
getUserInfo: () =>
http.post('/user/info'),
}
在 app.vue 中测试
vue
<a-button type="primary" @click="getUserInfo">获取用户信息</a-button>
typescript
<script setup lang="ts">
import { userApi } from './api/user'
const getUserInfo = () => {
userApi.getUserInfo()
}
</script>
在应用窗口点击按钮,按 F12,看到请求出去了,即表示成功了。

四、总结
到头来发现又回到了熟悉的领域,如果你不想做成桌面端,直接使用 vue 脚手架搭建项目,所有整合步骤几乎一模一样。今天就先这样,现在有个问题,先做前端用 mock 数据做接口,完成基础功能后,再开发服务端,还是直接同时进行。如果有人看到这里,你希望采用哪种方式呢?欢迎留言。
千里之行,始于足下。你的"个人公司"从这第一个2小时开始。欢迎在评论区分享你的进展或遇到的卡点,我会逐一查看,尽可能的帮助解决。我们下一篇文章见!