TauriVue3Admin后台管理程序|tauri+vite+pinia管理系统模板

960 阅读5分钟

基于tauri+vue3跨端开发桌面端后台管理系统应用TauriAdmin。

使用跨端技术tauri+rust搭配vite构建工具开发桌面端后台管理系统模板。内置了多种布局模板,实现了常用的图表/表格/表单/图文编辑器/权限控制等功能

001360截图20230713113578451.png

001360截图20230713113588451.png

001360截图20230713113589451.png

tauri-admin 支持多窗口切换管理、vue-i18n多语言包、动态路由权限、动态路由缓存等功能。

p3.gif

技术栈

  • 编码工具:vscode
  • 框架技术:tauri+vite^4.2.1+vue^3.2.45+pinia+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

p5.gif

项目结构

360截图20230713175628661.png

p2.gif

002360截图20230713113360738.png

003360截图20230713113905683.png

003360截图20230713124420714.png

006360截图20230713115517527.png

006360截图20230713115550099.png

007360截图20230713120136396.png

009360截图20230713120843495.png

011360截图20230713121358628.png

013360截图20230713122419575.png

014360截图20230713122732191.png

015360截图20230713122937528.png

016360截图20230713123030264.png

023360截图20230713123410856.png

tauri+vue3多窗口管理

基于tauri创建多开窗口,登录窗口切换到主窗口。

4e6f7c1903e90e2371e8e808395d7f32_2564621878-64b20a1c55c81.webp

// 创建窗口参数配置
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          // 隐藏窗口
}

7d1cd12e57af61afc80b0ad831b9f10e_bVc8PKH.webp

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

整体布局模板

0d695ae7f87274e62b108c5b6a8efb10_bVc8PKZ.webp

提供了入图分栏、垂直、水平三种常见布局。

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

路由配置

19055773db4073c4f81a7bdef8747ec8_bVc8PK0.webp

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

状态管理

8ef534fe455e8dfda160558c3dafcc7b_bVc8PK2.webp

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

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

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

export default pinia

tauri-admin国际化解决方案

b1ad8a7f64971628f248ceca1e8153e8_1289798-20230713235519555-720981793.png

71c52ab1fe7e8e7e911e58fe87b29bbc_bVc8PK7.webp

使用vue-i18n处理多语言。

/**
 * 国际化配置
 * @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
    }
  }
}

以上就是tauri+vue3开发跨端后台系统模板的一些分享,希望对大家有所帮助哈~~

juejin.cn/post/724934…

juejin.cn/post/723055…

大家也可以关注我的公众号,不定期分享更新一些项目实例。

qrcode_公众号_物料2.png