乐趣区

关于vite:electron12vite2vadmin基于vue3xelectron中后台桌面管理系统Electron后台模板

零碎简介

vite2-electron-admin 一套轻量级的中后盾零碎解决方案。它整合了 vite.js + electron12 开发实现。应用了最新的前端技术栈,蕴含了 i18n 国际化语言,动静权限路由,提供了多功能表格 / 表单 业务性能。

技术框架

  • vue3 技术:vite.js+vue3.0+vuex4+vue-router@4
  • 跨端框架:electron^12.0.4
  • 打包工具:vue-cli-plugin-electron-builder
  • UI 组件库:element-plus (饿了么 vue3 组件库)
  • 表格拖拽:sortablejs
  • 图表组件:echarts^5.1.1
  • 国际化:vue-i18n^9.1.6
  • 模仿数据:mockjs

我的项目构造如下

特色

  1. 反对 PC 及平板自适应布局
  2. 最新前端技术 Vite2、Vue3、Electron12、Element Plus、Vue-i18n、Echarts5.x
  3. 反对组件式 + 指令式两种权限认证形式
  4. 反对中英文 / 繁体国际化计划
  5. 反对表格拖拽排序、全屏、树形表格等性能
  6. 反对多主题换肤切换

electron-vite-admin 还反对新开多窗口,以如下换肤窗口为例,后面曾经有过相干分享文章,这里就不具体介绍。

import {createWin} from '@/windows/actions'

// 换肤窗口
const handleOpenTheme = () => {
    createWin({
        title: '主题换肤',
        route: '/skin',
        width: 750,
        height: 480,
    })
}

electron+vite 仿 mac 格调导航栏

如上图:为了我的项目 UI 一致性,顶部导航栏采纳了自定义组件实现,格调有些相似 mac 导航。

<template>
    <WinBar zIndex="1000">
        <template #wbtn>
            <MsgMenu />
            <Lang />
            <a class="wbtn" title="换肤" @click="handleSkinWin"><i class="iconfont icon-huanfu"></i></a>
            <Setting />
            <a class="wbtn" title="刷新" @click="handleRefresh"><i class="iconfont el-icon-refresh"></i></a>
            <a class="wbtn" :class="{'on': isAlwaysOnTop}" :title="isAlwaysOnTop ?' 勾销置顶 ':' 置顶 '"@click="handleAlwaysTop"><i class="iconfont icon-ding"></i></a>
            <Avatar @logout="handleLogout" />
        </template>
    </WinBar>
</template>

至于具体的实现形式,这里不作过多介绍了。之前有过一些相干分享文章。

入口页 main.js

/**
 * 渲染过程主入口
 * @author XiaoYan
 */
import {createApp} from 'vue'
import App from './App.vue'
import Router from './router'
import Store from './store'

// 引入公共配置
import gPlugins from './plugins'
import {winCfg, loadWin} from './windows/actions'

loadWin().then(config => {
    winCfg.window = config
    createApp(App).use(Router).use(Store).use(gPlugins).mount('#app')
})

引入公共组件 plugin.js

/**
 * 公共组件 / 插件配置文件
 * @author XiaoYan
 */

// 引入公共款式
import "@/assets/fonts/iconfont.css"
import "@/assets/css/common.scss"

// 引入 elementPlus 组件库
import ELPlus from "element-plus"

// 引入国际化配置
import VueI18n, {elPlusLang, getLang} from './i18n'

// 引入 vue3 自定义组件
import V3Layer from '@/components/v3layer'
import V3Scroll from '@/components/v3scroll'

// 引入公共组件模板
import WinBar from '@/components/winbar'
import WinBtn from '@/components/winbar/winbtn.vue'
import MacBtn from '@/components/winbar/macbtn.vue'
import Icon from '@/components/Icon'

import Utils from '@/utils'
import ElUtil from './elUtil'

const gPlugins = (app) => {
    app.use(ELPlus, {
        size: 'small',
        locale: elPlusLang[getLang()]
    })
    app.use(VueI18n)

    app.use(V3Layer)
    app.use(V3Scroll)

    // 注册公共组件
    app.component('WinBar', WinBar)
    app.component('WinBtn', WinBtn)
    app.component('MacBtn', MacBtn)
    app.component('Icon', Icon)

    // 注入全局依赖
    app.provide('utils', Utils)
    app.provide('elUtil', ElUtil)
}

国际化配置 vue-i18n

新建一个 i18n.js 用来引入国际化语言配置。

/**
 * 国际化配置 VueI18n util
 * @author XiaoYan  Q:282310962
 */

import {createI18n} from "vue-i18n"
import Storage from "@/utils/storage"

// 默认值
export const langKey = 'lang'
export const langVal = 'zh-CN'

/* elementPlus 国际化配置 */
import enUS from "element-plus/lib/locale/lang/en"
import zhCN from "element-plus/lib/locale/lang/zh-cn"
import zhTW from "element-plus/lib/locale/lang/zh-tw"
export const elPlusLang = {
    'en-US': enUS,
    'zh-CN': zhCN,
    'zh-TW': zhTW,
}

/* 初始化多语言 */
export const $messages = importAllLang()
export const $lang = getLang()
const i18n = createI18n({
    legacy: false,
    locale: $lang,
    messages: $messages
})

/* 获取语言 */
export function getLang() {const lang = Storage.get(langKey)
    return lang || langVal
}

/**
 * 长久化存储
 * @param lang 语言类型 zh-CN / zh-TW / en-US
 */
export function setLang(lang, reload = false) {if(getLang() !== lang) {Storage.set(langKey, lang || '')
        // 设置全局语言
        // i18n.global.locale.value = lang

        // 重载页面
        if(reload) {window.location.reload()
        }
    }
}

/**
 * 自动化导入本地 locale 目录下语言配置
 */
export function importAllLang() {const langModule = {}
    try {const localeCtx = require.context('@/locale', true, /([a-z]{2})-?([A-Z]{2})?\.js$/)
        localeCtx.keys().map(path => {const pathCtx = localeCtx(path)
            if(pathCtx.default) {const pathName = path.replace(/(.*\/)*([^.]+).*/ig, '$2')
                if(langModule[pathName]) {langModule[pathName] = {...langModule[pathName], ...pathCtx.default
                    }
                }else {langModule[pathName] = pathCtx.default
                }
            }
        })
    } catch (error) {console.log(error)
    }
    return langModule
}

我的项目模板布局

我的项目布局分为 Auth 和 Main 两个次要模块。

  • auth 模块模板
<template>
    <div class="vadmin__wrapper">
        <router-view class="vadmin__layouts-auth"></router-view>
    </div>
</template>

<script>
import {useRoute} from "vue-router"
import useTitle from '@/hooks/useTitle'

export default {components: {},
    setup() {const route = useRoute()

        // 设置题目
        useTitle(route)
    }
}
</script>
  • main 模块模板
<template>
    <div class="vadmin__wrapper" :style="{'--themeSkin': store.state.skin}">
        <div v-if="!route.meta.isNewin" class="vadmin__layouts-main flexbox flex-col">
            <!-- 顶部导航 -->
            <div class="layout__topbar">
                <TopNav />
            </div>
            
            <div class="layout__workpanel flex1 flexbox">
                <!-- 侧边栏 -->
                <div v-show="rootRouteEnable" class="panel__leftlayer">
                    <SideMenu :routes="mainRoutes" :rootRoute="rootRoute" />
                </div>

                <!-- 两头栏 -->
                <div class="panel__middlelayer" :class="{'collapsed': collapsed}">
                    <RouteMenu 
                        :routes="getAllRoutes" 
                        :rootRoute="rootRoute" 
                        :defaultActive="defaultActive" 
                        :rootRouteEnable="rootRouteEnable" 
                    />
                </div>

                <!-- // 左边栏 -->
                <div class="panel__rightlayer flex1 flexbox flex-col">
                    <!-- 面包屑 -->
                    <BreadCrumb />
                    
                    <v3-scroll autohide>
                        <div class="lay__container">
                            <permission :roles="route.meta.roles">
                                <template #tooltips>
                                    <Forbidden />
                                </template>
                                <router-view></router-view>
                            </permission>
                        </div>
                    </v3-scroll>
                </div>
            </div>
        </div>
        <router-view v-else class="vadmin__layouts-main flexbox flex-col"></router-view>
    </div>
</template>

图表 Hook

为了简化图表调用,封装一个图表 hooks。应用到了 element-resize-detector 来监测 dom 尺寸变动。

import {onMounted, onBeforeUnmount, ref} from "vue"
import * as echarts from "echarts"
import elementResizeDetectorMaker from "element-resize-detector"
import utils from "@/utils"

export default function useChart(refs, options) {
    let chartInst
    let chartRef = ref(null)
    let erd = elementResizeDetectorMaker()

    const handleResize = utils.debounce(() => {chartInst.resize()
    }, 100)

    onMounted(() => {if(refs.value) {chartInst = echarts.init(refs.value)
            chartInst.setOption(options)
            chartRef.value = chartInst
        }
        erd.listenTo(refs.value, handleResize)
    })

    onBeforeUnmount(() => {chartInst.dispose()
        erd.removeListener(refs.value, handleResize)
    })

    return chartRef
}

这样每次调用的时候,只需传入图表 dom 元素和图表数据即可疾速生成一个图表。

Okey,使用 electron+vite.js 跨端开发后盾管理系统临时分享到这里。

最初附上 Electron+Vue3 桌面端仿都有短视频 + 直播
https://segmentfault.com/a/1190000039725671

退出移动版