这次带来的是最新开发的跨桌面中后台权限管理系统。使用最新的前端技术栈,内置 i18n 国际化解决方案,动态权限路由,权限验证,整合了典型的表格/表单等业务模块功能。通过Electron打包成跨平台的应用程序。
技术栈
- 编码器:vscode
- vue3技术:vite2.1.5+vue3.0+vuex4+vue-router@4
- 跨端框架:electron^12.0.4
- 打包工具:vue-cli-plugin-electron-builder
- UI组件库:element-plus^1.0.2 (饿了么vue3组件库)
- 表格拖拽:sortablejs^1.13.0
- 图表组件:echarts^5.1.1
- 国际化方案:vue-i18n^9.1.6
- 数据模拟:mockjs^1.1.0
主要特性
- 前端技术栈Vite2、Vue3、Electron12、Element Plus、Vue-i18n、Echarts5.x、Sortable、Mockjs。
- 权限认证支持组件式+指令式两种方式。
- 支持中文/英文/繁体国际化解决方案。
- 支持表格拖拽排序、缩放、树形表格等功能。
- 支持加载动态权限菜单,多方式轻松权限控制。
- 高效率开发,整个框架已经搭建完毕,只需新增相应模块即可。
项目结构图
整个项目使用最新vue3语法编码,采用标准的分层目录结构形式,数据均是使用Mock.js进行模拟。
electron支持多开新窗口
项目支持打开多个窗口,如主题换肤、关于等窗口。只需通过如下的方式调用即可。
import { winCfg, createWin } from '@/windows/actions'
// 换肤窗口
const handleOpenTheme = () => {
createWin({
title: '个性装扮',
route: '/skin',
width: 750,
height: 480,
modal: true,
parent: winCfg.window.id,
resize: false,
})
}
electron实现无边框Mac导航栏效果
设置 -webkit-app-region: drag
实现导航条可拖拽,标题及按钮 -webkit-app-region:no-drag
可响应点击事件。
<!-- //顶部导航 -->
<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>
Vite2|electron项目布局模板
为了使得项目分层结构更加清晰,布局分为 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>
Vue-Router路由配置
/**
* 路由配置 Router util
* @author XiaoYan
*/
import { createRouter, createWebHashHistory } from "vue-router"
import { ElLoading } from "element-plus"
import { loginWin } from "@/windows/actions"
import store from '@/store'
// 导入公共模板/路由配置
import mainLayout from "@/layouts/main"
import authLayout from "@/layouts/auth"
import mainRoutes from "@/layouts/main/routes.js"
import authRoutes from "@/layouts/auth/routes.js"
const RoutesLs = [
// 主页面模块
{
path: '/',
redirect: '/home/index',
component: mainLayout,
children: mainRoutes,
},
// 验证模块
{
path: '/auth',
redirect: '/auth/login',
component: authLayout,
children: authRoutes,
},
// 错误模块
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/error/404.vue'),
meta: {
title: 'app__global-page-notfound',
}
}
]
const router = createRouter({
history: createWebHashHistory(),
routes: RoutesLs,
})
// 全局钩子拦截验证状态
let loadingIns
router.beforeEach((to, from, next) => {
// 开启加载提示
loadingIns = ElLoading.service({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(19, 209, 122, .1)'
})
// 判断当前路由是否需要验证状态
const isLogined = store.state.isLogin
if(to.meta.auth) {
if(isLogined) {
next()
}else {
loginWin()
loadingIns.close()
}
}else {
next()
}
})
router.afterEach(() => {
// 关闭加载提示
loadingIns.close()
})
Vue-I18n国际化解决方案
项目中路由采用了 vue-i18n 国际化,支持中文|繁体|英文三种语言。
目前vue-i18n插件支持vue3项目了,大家需安装最新版本即可。
npm i vue-i18n@next -D
electron-builder打包配置
{
"productName": "electron-vadmin",
"appId": "cc.xiaoyan.electron-vadmin",
"copyright": "Copyright © 2021-present XiaoYan",
"compression": "maximum",
"asar": false,
"extraResources": [{
"from": "./resource","to": "resource"
}],
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": true,
"deleteAppDataOnUninstall": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "ElectronVAdmin"
},
"win": {
"icon": "./resource/shortcut.ico",
"artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}",
"target": [{
"target": "nsis","arch": ["ia32"]
}]
},
"mac": {
"icon": "./resource/shortcut.icns","artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
},
"linux": {
"icon": "./resource","artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
}
}
最后还需注意
- 项目路径命名不能包含中文,否则打包会报错!
- 尽量不要使用 getCurrentInstance 函数来使用router或store,打包也会报错!
- 打包后运行出现白屏情况,可配置 history: createWebHashHistory()
- 提示fs.existsSync错误,设置nodeIntegration: true开启Node支持;