tauriAdmin-vue3 一款基于tauri+rust+vue3+pinia等技术搭建的电脑端中后台管理系统模板项目。

null

null

tauri-admin 内置了三种常用的模板布局样式。

null

技术栈

  • 开发工具:Vscode
  • 框架技术:tauri+vite4+vue3+pinia2+vue-router
  • UI组件库:ve-plus (基于vue3轻量级组件库)
  • 样式处理:sass^1.63.6
  • 图表组件:echarts^5.4.2
  • 国际化方案:vue-i18n^9.2.2
  • 编辑器组件:wangeditor^4.7.15
  • 缓存插件:pinia-plugin-persistedstate^3.1.0

null

tauri-admin-vue3 使用了最新跨端技术Tauri Rust 及webview2整合Vite4构建工具实现vue3电脑端后台管理解决方案。

null

目录结构

null

null

null

null

null

null

null

null

null

null

null

null

tauri 封装多窗口

null

/**
 * @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
    })
}

布局模板

null

<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" :style="{'--themeSkin': store.config.skin}">
        <component :is="LayoutConfig[config.layout]" />
    </div>
</template>

路由配置

null

/**
 * 路由配置
 * @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

null

/**
 * 状态管理Pinia
 * @author YXY
 */

import { createPinia } from 'pinia'
// 引入pinia本地持久化存储
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

export default pinia

OK,以上就是tauri+vue3开发跨端后台管理系统实例的一些分享。