基於vite2.x+electron12桌面端後臺管理系統Vite2ElectronVAdmin。
繼上一次分享vite2整合electron搭建後臺框架,這次帶來的是最新開發的跨桌面中後臺許可權管理系統。使用最新的前端技術棧,內建 i18n 國際化解決方案,動態許可權路由,許可權驗證,整合了典型的表格/表單等業務模組功能。
一、技術棧
- 編碼器: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建立多視窗模式感興趣的話,可以去看看下面這篇文章。
https://www.cnblogs.com/xiaoyan2017/p/14403820.html
◆ electron實現無邊框Mac導航欄效果
如上圖:頂部導航欄預設是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>
對於自定義導航條的實現方式,由於之前有過相關分享文章,這裡就不詳細介紹了。
https://www.cnblogs.com/xiaoyan2017/p/14449570.html
◆ 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
如上圖:新建locale目錄用來處理相應模組語言配置。
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 })
◆ 動態化圖表Hooks
專案中圖表是使用最新的Echarts元件。為了避免每次都使用echarts.init呼叫圖表介面。於是就封裝了呼叫圖表hook函式。
一開始是使用監聽window.resize來自適應圖表尺寸,這裡有一個bug,只有視窗大小改變才會觸發,而DOM改變則不會觸發了,於是改用 element-resize-detector 來監聽,完美解決問題。
/** * 動態化圖表Hook * @author XiaoYan */ 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 } // window.addEventListener('resize', handleResize) erd.listenTo(refs.value, handleResize) }) onBeforeUnmount(() => { chartInst.dispose() // window.removeEventListener('resize', handleResize) erd.removeListener(refs.value, handleResize) }) return chartRef }
◆ 路由許可權管理
專案中的許可權尺寸元件式和指令式兩種模式。
<!-- //許可權驗證模板 --> <template> <slot v-if="isPermission"/> <slot v-else name="tooltips"> <el-alert title="對不起,您沒有許可權操作此頁面!" type="error" show-icon></el-alert> </slot> </template> <script> import { computed } from "vue"; import { useStore } from "vuex"; import { getPermissionRoute } from "@/utils/routes"; export default { props: { roles: { type: [String, Array] } }, components: {}, setup(props) { const store = useStore(); // 判斷是否有許可權 const isPermission = computed(() => getPermissionRoute(JSON.stringify(store.state.roles), props.roles) ); return { isPermission }; } }; </script>
import store from "@/store" import { getPermissionRoute } from "@/utils/routes" const Permission = (el, binding) => { const { value } = binding if(value) { const userRoles = JSON.stringify(store.state.roles) if(!getPermissionRoute(userRoles, value)) { el.parentNode && el.parentNode.removeChild(el) } }else { console.error(`Set Roles! Like v-permission="['admin', 'dev']" or v-permission="'test'"`) } } export default Permission
元件呼叫
<Permission roles="test"> <template #tooltips> <h2 style="color:red;">此模組只有test角色才能操作!</h2> </template> <el-button type="primary" icon="el-icon-search">查詢</el-button> </Permission> <el-divider /> <Permission roles="dev"> <template #tooltips> <h2 style="color:red;">你無權操作Dev模組!</h2> </template> <el-button type="primary" icon="el-icon-edit">編輯</el-button> <el-button type="warning" icon="el-icon-delete">刪除</el-button> </Permission>
指令呼叫
<el-button v-permission="'test'" type="primary" icon="el-icon-search">查詢</el-button> <el-button v-permission="'dev'" type="success" icon="el-icon-plus">新增</el-button> <el-button v-permission="['test', 'dev']" type="warning" icon="el-icon-edit">編輯</el-button> <el-button v-permission="['admin']" type="danger" icon="el-icon-delete">刪除</el-button>
◆ 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}" } }
最後還需注意
1、專案路徑命名不能包含中文,否則打包會報錯!
2、儘量不要使用 getCurrentInstance 函式來使用router或store,打包也會報錯!
3、打包後執行出現白屏情況,可配置 history: createWebHashHistory()
4、提示fs.existsSync錯誤,設定nodeIntegration: true開啟Node支援;
好了,基於vite2+electron開發後臺管理系統就分享到這裡,希望對大家有些幫助~
最後附上一個electron+vue3+vant短視訊專案
https://www.cnblogs.com/xiaoyan2017/p/14585223.html