introduced
基于tauri+vue3+pinia2+veplus跨平台、桌面端通用后台管理系统应用模板TauriAdmin。



Tauri-admin uses cross-end technology Tauri Rust to integrate vite4.x to build a computer-side background system program exe template.

Support multi-open window management, internationalization and multi-language, route authentication verification/route caching, and common business function modules.

technology stack
- Editor: VScode
- Technical framework: tauri+vite4+vue3+pinia2+vue-router
- UI component library: ve-plus (based on vue3 PC UI component library)
- Style processing: sass^1.63.6
- Chart component: echarts^5.4.2
- Internationalization plan: vue-i18n^9.2.2
- Editor component: wangeditor^4.7.15
- Local cache: pinia-plugin-persistedstate^3.1.0

The project has three commonly used layout template styles built in, you can switch them as you like, or you can customize your favorite template.
Project building catalog

The entire project is based on tauri-cli scaffolding to build the vue3 project. If you are interested in creating project templates and opening more windows, you can go and check out the previous shared articles.
https://www.cnblogs.com/xiaoyan2017/p/16812092.html




Tauri vue3 packages multi-open windows
Create a new index.js file in the multiwins directory for tauri multi-window packaging management.

// 创建窗口配置
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, // 隐藏窗口
};
Create a new window instance using the WebviewWindow method provided by tauri. Since label is the unique identifier of the window, when you need to switch to the main window, you need to set the label to include the main keyword. Later, we will search whether there is a main keyword to determine whether it is the main window.
/**
* @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();
});
// ...
}
}





Main template layout
Three commonly used template layout styles are built into the project. They are traditional column style, menu style, and horizontal menu style.


<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>




routing configuration
The tauri project uses vue-router for route jump management.

/**
* 路由配置 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;
Status management pinia
Currently, vue3 projects recommend using pinia for status management, but of course vuex can still be used.

/**
* 状态管理配置 Pinia
* @author YXY
*/
import { createPinia } from "pinia";
// 引入pinia本地持久化存储
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;
Local persistent storage is implemented using the pinia plug-in pinia-plugin-persisted state.





vue-i18n International Solution
Tauri-admin supports three languages: Chinese and English/Traditional.

/**
* 国际化配置 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 Configuration
{
"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, the above are some shares of tauri+vue3+pinia's client backend management system template examples.
