项目介绍
tauri-vue3-admin 基于 Tauri Rust Webview
跨端技术整合 vite4+vue3+pinia2+vue-router
等技术搭建跨端管理系统应用程序模板EXE。
框架技术
- 编码工具:Vscode
- 框架技术:tauri+vite^4.2.1+vue^3.2.45+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-admin 支持多窗口切换管理、vue-i18n多语言包、动态路由权限、常用业务功能模块、3种布局模板及动态路由缓存
等功能。
项目结构
tauri vue3多窗口
新建一个multiwins目录处理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 // 隐藏窗口
}
/**
* @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()
})
// ...
}
}
actions.js渲染进程/主进程通讯
/**
* 处理渲染器进程到主进程的异步通信
*/
import { WebviewWindow } from '@tauri-apps/api/window'
import { emit } from '@tauri-apps/api/event'
/**
* @desc 创建新窗口
* @param args {object} {label: 'new', url: '/new', width: 500, height: 300, ...}
*/
export async function createWin(args) {
console.log(args)
await emit('win-create', args)
}
/**
* @desc 获取窗口
* @param args {string} 'main'|'main_login' ...
*/
export async function getWin(label) {
return await WebviewWindow.getByLabel(label)
}
/**
* @desc 设置窗口
* @param type {string} 'show'|'hide'|'close'|'min'|'max'|'max2min'|'exit'|'relaunch'
* @param id {number}
*/
export async function setWin(type) {
await emit('win-' + type)
}
/**
* @desc 主|渲染进程数据传递
* @param args {object} {type: 'MSG_TYPE_XXX', value: 123}
*/
export async function setWinData(args) {
await emit('win-setdata', args)
}
/**
* @desc 屏蔽系统右键菜单
*/
export function disableWindowMenu() {
document.addEventListener('contextmenu', e => e.preventDefault())
}
/**
* @desc 登录窗口
*/
export async function loginWin() {
await createWin({
label: 'main_login',
title: '登录',
url: '/login',
width: 520,
height: 420,
resizable: false,
alwaysOnTop: true
})
}
/**
* @desc 主窗口
*/
export async function mainWin() {
await createWin({
label: 'main',
title: 'TAURI-ADMIN',
url: '/',
width: 1000,
height: 640,
minWidth: 750,
minHeight: 500
})
}
布局模板
tauri-admin内置了三种常见的布局模板。
路由配置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: `当前登录角色 ${store.roles} 没有操作权限,请联系管理员授权后再操作。