介紹
基於 tauri+vue3+pinia2+veplus 跨平台、桌面端通用後台管理系統應用模板 TauriAdmin。



tauri-admin 採用跨端技術 Tauri Rust 整合 vite4.x 建置電腦端後台系統程式 exe 模板。

支援多開視窗管理、國際化多語言、路由鑑權驗證/路由快取、常用業務功能模組。

技術棧
- 編輯器:VScode
- 技術框架:tauri+vite4+vue3+pinia2+vue-router
- UI 元件庫:ve-plus (基於 vue3 電腦端 UI 元件庫)
- 樣式處理:sass^1.63.6
- 圖表元件:echarts^5.4.2
- 國際化方案:vue-i18n^9.2.2
- 編輯器元件:wangeditor^4.7.15
- 本機快取:pinia-plugin-persistedstate^3.1.0

專案內建了三種常用的版面配置模板樣式,根據喜歡隨意切換即可,也可以訂製喜歡的模板。
專案建置目錄

整個專案基於 tauri-cli 腳手架建置 vue3 專案,如果大家對建立專案模板及多開視窗感興趣,可以去看看之前的分享文章。
https://www.cnblogs.com/xiaoyan2017/p/16812092.html




tauri vue3 封裝多開視窗
在 multiwins 目錄下新建一個 index.js 檔案,用於 tauri 多視窗封裝管理。

// 建立視窗配置
export const windowConfig = {
label: null, // 視窗唯一label
title: "", // 視窗標題
url: "", // 路由地址url
width: 1000, // 視窗寬度
height: 640, // 視窗高度
minWidth: null, // 視窗最小寬度
minHeight: null, // 視窗最小高度
x: null, // 視窗相對於螢幕左側坐標
y: null, // 視窗相對於螢幕頂端坐標
center: true, // 視窗居中顯示
resizable: true, // 是否支援縮放
maximized: false, // 最大化視窗
decorations: false, // 視窗是否裝飾邊框及導航條
alwaysOnTop: false, // 置頂視窗
fileDropEnabled: false, // 禁止系統拖放
visible: false, // 隱藏視窗
};
透過 tauri 提供的 WebviewWindow 方法建立新視窗實例。由於 label 是視窗唯一標識,當需要切換設定為主視窗時,需要設定 label 包含 main 關鍵詞。後續會檢索是否有 main 關鍵詞判斷是否為主視窗。
/**
* @desc 視窗管理
* @author: YXY Q:282310962
* @time 2023.07
*/
import { WebviewWindow, appWindow, getAll } from "@tauri-apps/api/window";
import { relaunch, exit } from "@tauri-apps/api/process";
import { emit, listen } from "@tauri-apps/api/event";
import { setWin } from "./actions";
// 建立視窗參數配置
export const windowConfig = {
label: null, // 視窗唯一label
title: "", // 視窗標題
url: "", // 路由地址url
width: 1000, // 視窗寬度
height: 640, // 視窗高度
minWidth: null, // 視窗最小寬度
minHeight: null, // 視窗最小高度
x: null, // 視窗相對於螢幕左側坐標
y: null, // 視窗相對於螢幕頂端坐標
center: true, // 視窗居中顯示
resizable: true, // 是否支援縮放
maximized: false, // 最大化視窗
decorations: false, // 視窗是否裝飾邊框及導航條
alwaysOnTop: false, // 置頂視窗
fileDropEnabled: false, // 禁止系統拖放
visible: false, // 隱藏視窗
};
class Windows {
constructor() {
// 主視窗
this.mainWin = null;
}
// 建立新視窗
async createWin(options) {
console.log("-=-=-=-=-=開始建立視窗");
const args = Object.assign({}, windowConfig, options);
// 判斷視窗是否存在
const existWin = getAll().find((w) => w.label == args.label);
if (existWin) {
console.log("視窗已存在>>", existWin);
if (existWin.label.indexOf("main") == -1) {
// 自定義處理...
}
}
// 是否主視窗
if (args.label.indexOf("main") > -1) {
console.log("該視窗是主視窗");
// 自定義處理...
}
// 建立視窗物件
let win = new WebviewWindow(args.label, args);
// 是否最大化
if (args.maximized && args.resizable) {
win.maximize();
}
// 視窗建立完畢/失敗
win.once("tauri://created", async () => {
console.log("window create success!");
await win?.show();
});
win.once("tauri://error", async () => {
console.log("window create error!");
});
}
// 獲取視窗
getWin(label) {
return WebviewWindow.getByLabel(label);
}
// 獲取全部視窗
getAllWin() {
return getAll();
}
// 開啟主進程監聽事件
async listen() {
console.log("——+——+——+——+——+開始監聽視窗");
// 建立新視窗
await listen("win-create", (event) => {
this.createWin(event.payload);
});
// 顯示視窗
await listen("win-show", async (event) => {
if (appWindow.label.indexOf("main") == -1) return;
await appWindow.show();
await appWindow.unminimize();
await appWindow.setFocus();
});
// 隱藏視窗
await listen("win-hide", async (event) => {
if (appWindow.label.indexOf("main") == -1) return;
await appWindow.hide();
});
// 關閉視窗
await listen("win-close", async (event) => {
await appWindow.close();
});
// 退出應用
await listen("win-exit", async (event) => {
setWin("logout");
await exit();
});
// ...
}
}





主模板版面配置
專案中內建了三種常用的模板版面配置樣式。分別為傳統分欄、菜單式、水平菜單式。


<script setup>
import { computed } from 'vue'
import { appStore } from '@/pinia/modules/app'
// 引入版面配置模板
import Columns from './template/columns/index.vue'
import Vertical from './template/vertical/index.vue'
import Transverse from './template/transverse/index.vue'
const store = appStore()
const config = computed(() => store.config)
const LayoutConfig = {
columns: Columns,
vertical: Vertical,
transverse: Transverse
}
</script>
<template>
<div class="veadmin__container">
<component :is="LayoutConfig[config.layout]" />
</div>
</template>




路由配置
tauri 專案中使用 vue-router 進行路由跳轉管理。

/**
* 路由配置 Router
* @author YXY
*/
import { appWindow } from "@tauri-apps/api/window";
import { createRouter, createWebHistory } from "vue-router";
import { appStore } from "@/pinia/modules/app";
import { hasPermission } from "@/hooks/usePermission";
import { loginWin } from "@/multiwins/actions";
// 批量導入modules路由
const modules = import.meta.glob("./modules/*.js", { eager: true });
const patchRoutes = Object.keys(modules)
.map((key) => modules[key].default)
.flat();
/**
* @description 動態路由參數配置
* @param path ==> 菜單路徑
* @param redirect ==> 重定向地址
* @param component ==> 視圖檔案路徑
* 菜單資訊(meta)
* @param meta.icon ==> 菜單圖標
* @param meta.title ==> 菜單標題
* @param meta.activeRoute ==> 路由選中(預設空 route.path)
* @param meta.rootRoute ==> 所屬根路由選中(預設空)
* @param meta.roles ==> 頁面權限 ['admin', 'dev', 'test']
* @param meta.breadcrumb ==> 自定義麵包屑導航 [{meta:{...}, path: '...'}]
* @param meta.isAuth ==> 是否需要驗證
* @param meta.isHidden ==> 是否隱藏頁面
* @param meta.isFull ==> 是否全屏頁面
* @param meta.isKeepAlive ==> 是否快取頁面
* @param meta.isAffix ==> 是否固定標籤(tabs標籤欄不能關閉)
* */
const routes = [
// 首頁
{
path: "/",
redirect: "/home",
},
// 錯誤模組
{
path: "/:pathMatch(.*)*",
component: () => import("@views/error/404.vue"),
meta: {
title: "page__error-notfound",
},
},
...patchRoutes,
];
const router = createRouter({
history: createWebHistory(),
routes,
});
// 全域鉤子攔截
router.beforeEach((to, from, next) => {
// 開啟載入提示
loading({
text: "Loading...",
background: "rgba(70, 255, 170, .1)",
});
const store = appStore();
if (to?.meta?.isAuth && !store.isLogged) {
loginWin();
loading.close();
} else if (!hasPermission(store.roles, to?.meta?.roles)) {
// 路由鑑權
appWindow?.show();
next("/error/forbidden");
loading.close();
Notify({
title: "訪問限制!",
description: `<span style="color: #999;">當前登入角色 ${store.roles} 沒有操作權限,請聯繫管理員授權後再操作。</div>`,
type: "danger",
icon: "ve-icon-unlock",
time: 10,
});
} else {
appWindow?.show();
next();
}
});
router.afterEach(() => {
loading.close();
});
router.onError((error) => {
loading.close();
console.warn("Router Error》》", error.message);
});
export default router;
狀態管理 pinia
目前 vue3 專案推薦使用 pinia 進行狀態管理,當然 vuex 依然可以使用。

/**
* 狀態管理配置 Pinia
* @author YXY
*/
import { createPinia } from "pinia";
// 引入pinia本地持久化儲存
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;
本地持久化儲存則是使用 pinia 插件 pinia-plugin-persistedstate 進行實現功能。





vue-i18n 國際化解決方案
tauri-admin 支援中英文/繁體三種語言配置。

/**
* 國際化配置 VueI18n
* @author YXY
*/
import { createI18n } from "vue-i18n";
import { appStore } from "@/pinia/modules/app";
// 引入語言配置
import enUS from "./en-US";
import zhCN from "./zh-CN";
import zhTW from "./zh-TW";
// 預設語言
export const langVal = "zh-CN";
export default async (app) => {
const store = appStore();
const lang = store.lang || langVal;
const i18n = createI18n({
legacy: false,
locale: lang,
messages: {
en: enUS,
"zh-CN": zhCN,
"zh-TW": zhTW,
},
});
app.use(i18n);
};
tauri.conf.json 配置
{
"build": {
"beforeDevCommand": "yarn dev",
"beforeBuildCommand": "yarn build",
"devPath": "http://localhost:1420",
"distDir": "../dist",
"withGlobalTauri": false
},
"package": {
"productName": "tauri-admin",
"version": "0.0.0"
},
"tauri": {
"allowlist": {
"all": true,
"shell": {
"all": false,
"open": true
}
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.admin",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
},
"security": {
"csp": null
},
"windows": [
{
"fullscreen": false,
"resizable": true,
"title": "tauri-admin",
"width": 1000,
"height": 640,
"center": true,
"decorations": false,
"fileDropEnabled": false,
"visible": false
}
],
"systemTray": {
"iconPath": "icons/icon.ico",
"iconAsTemplate": true,
"menuOnLeftClick": false
}
}
}
OK,以上就是 tauri+vue3+pinia 實現客戶端後台管理系統模板實例的一些分享。
