self-researchelectron31+vite5High-value back-office management solution for desktopsElectronViteAdmin。
vite-electron31-adminThe original is based onelectron31+vite5+vue3 setup+pinia2+element-plus+echartsBuild a client-side lightweight backend management system.Built-in 4 generic layout templates, support for i18n internationalization, dynamic routing permissions, integrated tables, forms, charts, lists, editorsand other business scenarios.
technology stack
- Editor: VScode
- Framework technology: vite^5.3.4+vue^3.4.31+vue-router^4.4.0
- Cross-end framework: electron^31.3.0
- UI component library: element-plus^2.7.8
- Status management: pinia^2.2.0
- Internationalization programme: vue-i18n@9
- Chart component: echarts^5.5.1
- markdown editor: md-editor-v3^4.18.0
- Simulated data: mockjs^1.1.0
- Packaging tool: electron-builder^24.13.3
- Electron+vite bridge plugin: vite-plugin-electron^0.28.7
Project Catalog Structure
vite-electron-admin desktop backend system useelectron31+vite5 Build a project template usingvue3 setup Grammar Development.
Functional characteristics
- Latest Frontend Technology Stack, Vue3, Electron31, ElementPlus, Vue-I18n, Echarts
- Support Chinese/English/Traditional internationalization solutions
- Supports dynamic permission routing, multiple tab cache routing
- Packaging Multi-Window Manager
- Built-in 4 universal layout templates, free to switch styles
- Integration of common forms, tables, lists, charts, editors, error handling and other modules
- High-value UI interface, lightweight modularity, high customizability
The Electron31-Vue3Admin generic backend system has now been posted to my original portfolio.
/item/detail/1106734011
Element Plus Component Library
The electron-viteadmin backend system uses the vue3 component library introduced by the hungry front-end team.
Electron main thread configuration
/** * electron main thread configuration * @author andy */ import { app, BrowserWindow } from 'electron' import { WindowManager } from '../src/windows/' // Ignore Security Warning Alerts Electron Security Warning (Insecure Content-Security-Policy) ['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true const createWindow = () => { let win = new WindowManager() ({isMajor: true}) // System Tray Management () // Listening to the ipcMain event () } ().then(() => { createWindow() ('activate', () => { if(().length === 0) createWindow() }) }) ('window-all-closed', () => { if( !== 'darwin') () })
entry file
import { createApp } from 'vue' import './' import App from './' import { launchApp } from '@/windows/actions' // Introducing Routing and Stateful Configuration import Router from './router' import Pinia from './pinia' // Introducing Plugin Configuration import Plugins from './plugins' launchApp().then(config => { if(config) { ('Window parameters:', config) ('window id:', config?.id) // Global Storage Window Configuration = config } // Initializing an app application instance createApp(App) .use(Router) .use(Pinia) .use(Plugins) .mount('#app') })
Electron wraps multi-window management | custom system navigation bar
As shown above: the login window switches to the main window.
<script setup> import { ref, markRaw } from 'vue' import { ElMessageBox } from 'element-plus' import { QuestionFilled, SwitchButton } from '@element-plus/icons-vue' import { isTrue } from '@/utils' import { authState } from '@/pinia/modules/auth' import { winSet } from '@/windows/actions' const authstate = authState() const props = defineProps({ color: String, // Whether the window can be minimized minimizable: {type: [Boolean, String], default: true}, // Whether the window can be maximized or not maximizable: {type: [Boolean, String], default: true}, // Whether the window can be closed or not closable: {type: [Boolean, String], default: true}, // level zIndex: {type: [Number, String], default: 2024}, }) const hasMaximized = ref(false) // Initially listens to whether the window is maximized or not ('win-isMaximized').then(res => { = res }) // Listen to the maximization of the window in real time ('win-maximized', (e, data) => { = data }) // minimize (computing) const handleWinMin = () => { // winSet('minimize', ) ('win-min') } // Maximize/Restore const handleWinToggle = () => { // winSet('max2min', ) ('win-toggle').then(res => { = res }) } // cloture const handleWinClose = () => { if() { ('Does it minimize to the system tray without exiting the application?', '', { type: 'warning', icon: markRaw(QuestionFilled), confirmButtonText: 'Exit the application', cancelButtonText: 'Minimize to tray', customStyle: {'borderRadius': '8px'}, roundButton: true, distinguishCancelAndClose: true, }).then(() => { () winSet('close') }).catch((action) => { if(action === 'cancel') { setTimeout(() => { winSet('hide', ) }, 250) } }) }else { winSet('close', ) } } </script> <template> <div class="ev__winbtns vu__drag" :style="{'z-index': zIndex}"> <div class="ev__winbtns-actions vu__undrag" :style="{'color': color}"> <a v-if="isTrue(minimizable)" class="wbtn min" title= "Minimize" @click="handleWinMin"><i class="wicon iconfont elec-icon-min"></i></a> <a v-if="isTrue(maximizable)" class="wbtn toggle" :title= "hasMaximized ? 'Restore Down' : 'Maximized'" @click="handleWinToggle"> <i class="wicon iconfont" :class="hasMaximized ? 'elec-icon-restore' : 'elec-icon-max'"></i> </a> <a v-if="isTrue(closable)" class="wbtn close" title= "Close" @click="handleWinClose"><i class="wicon iconfont elec-icon-quit"></i></a> </div> </div> </template>
Electron-Vue3Admin Layout Template
As above: built-in4A variety of commonly used generic layout templates. Templates can also be customized as needed.
/** * Generic layout template * @author Andy Q:282310962 */ <script setup> import { appState } from '@/pinia/modules/app' // Introduction of layout templates import Classic from './template/classic/' import Columns from './template/columns/' import Vertical from './template/vertical/' import Horizontal from './template/horizontal/' const appstate = appState() const LayoutMap = { 'classic': Classic, 'columns': Columns, 'vertical': Vertical, 'horizontal': Horizontal } </script> <template> <div class="vuadmin__container" :style="{'--themeSkin': }"> <component :is="LayoutMap[]" /> </div> </template>
electron+vue3-i18n internationalization configuration
The electron-viteadmin system usesvue-i18n Internationalized solutions that supportChinese/English/TraditionalLanguage.
/** * Internationalized configuration * @author YXY */ import { createI18n } from 'vue-i18n' import { appState } from '@/pinia/modules/app' // Introducing Language Configuration import enUS from './en-US' import zhCN from './zh-CN' import zhTW from './zh-TW' // default language export const langVal = 'zh-CN' export default async (app) => { const appstate = appState() const lang = || langVal (lang) const i18n = createI18n({ legacy: false, locale: lang, messages: { 'en': enUS, 'zh-CN': zhCN, 'zh-TW': zhTW } }) (i18n) }
Vue3 Dynamized Charts Echarts
vue3 encapsulates chart hooks for multiple chart conforming calls. Use element-resize-detector component to dynamically listen for window DOM size changes to update the chart.
/** * :: Dynamic Chart Hook */ import { onMounted, onBeforeUnmount, ref } from 'vue' import * as echarts from 'echarts' import elementResizeDetectorMaker from 'element-resize-detector' export function useEcharts(el, options) { let chartEl let chartRef = ref(null) let erd = elementResizeDetectorMaker() const resizeHandle = () => { chartEl && () } onMounted(() => { if(el?.value) { chartEl = () (options) = chartEl } (, resizeHandle) }) onBeforeUnmount(() => { () (, resizeHandle) }) return chartRef }
import { useEcharts } from '@/hooks/useEcharts' const userActionChartRef = ref(null) useEcharts(userActionChartRef, { // ... })
Electron+vue3 custom routing menu
<Menus :rootRouteEnable="false" /> <Menus rootRouteEnable :dark="true" /> <Menus mode="horizontal" :dark="true" />
<script setup> import { ref, computed } from 'vue' import { isObject, isArray, isImg } from '@/utils' import { appState } from '@/pinia/modules/app' import { useRoutes } from '@/hooks/useRoutes' const props = defineProps({ // Menu mode (vertical|horizontal) mode: { type: String, default: 'vertical' }, // Whether to enable the first level routing menu rootRouteEnable: { type: Boolean, default: true }, // Dark Mode or not dark: { type: Boolean } }) import Submenu from './' // Introducing the Primary Routing Table import routes from '@/router/modules/' const appstate = appState() const { route, getActiveRoute, getCurrentRootRoute, getTreeRoutes } = useRoutes() const activeRoute = computed(() => getActiveRoute(route)) const rootRoute = computed(() => getCurrentRootRoute(route)) const treeRoutes = computed(() => getTreeRoutes(routes)) const filterRoutes = computed(() => { if() { return } // Filtering first-level routing menus return (item => === && )?.children }) </script> <template> <div class="vu__menubar" :class="{'is-dark': dark, 'is-collapsed': mode == 'vertical' && }"> <el-menu class="vu__menus" :default-active="activeRoute" :mode="mode" :collapse=""> <Submenu v-for="route in filterRoutes" :key="" :item="route" :rootRoute="rootRoute" :rootRouteEnable="rootRouteEnable" /> </el-menu> </div> </template>
electron+vue3 multi tabview
element-plus component library el-dropdown component right-click control to show only one dropdown menu at a time.
<template> <div class="vu__tabview"> <el-tabs v-model="activeTab" class="vu__tabview-tabs" @tab-change="changeTabs" @tab-remove="removeTab" > <el-tab-pane v-for="(item, index) in tabList" :key="index" :name="" :closable="!item?.meta?.isAffix" > <template #label> <el-dropdown ref="dropdownRef" trigger="contextmenu" :id="" @visible-change="handleDropdownChange($event, )" @command="handleDropdownCommand($event, item)"> <span class="vu__tabview-tabs__label"> <span>{{$t(item?.meta?.title)}}</span> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="refresh" :icon="Refresh">{{$t('tabview__contextmenu-refresh')}}</el-dropdown-item> <el-dropdown-item command="close" :icon="Close" :disabled="">{{$t('tabview__contextmenu-close')}}</el-dropdown-item> <el-dropdown-item command="closeOther" :icon="Switch">{{$t('tabview__contextmenu-closeother')}}</el-dropdown-item> <el-dropdown-item command="closeLeft" :icon="DArrowLeft">{{$t('tabview__contextmenu-closeleft')}}</el-dropdown-item> <el-dropdown-item command="closeRight" :icon="DArrowRight">{{$t('tabview__contextmenu-closeright')}}</el-dropdown-item> <el-dropdown-item command="closeAll" :icon="CircleCloseFilled">{{$t('tabview__contextmenu-closeall')}}</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </template> </el-tab-pane> </el-tabs> </div> </template>
<script setup> import { onMounted, ref, computed, watch, nextTick } from 'vue' import { useRouter, useRoute } from 'vue-router' import { useI18n } from 'vue-i18n' import { Refresh, Close, Switch, DArrowLeft, DArrowRight, CircleCloseFilled } from '@element-plus/icons-vue' import { isObject, isImg } from '@/utils' import { useLink } from '@/hooks/useLink' import { appState } from '@/pinia/modules/app' const router = useRouter() const route = useRoute() const { jump } = useLink() const { locale } = useI18n() let { config: { keepAlive, tabRoutes, cacheRoutes }, updateConfig } = appState() const dropdownRef = ref() const activeTab = ref() const tabList = ref(tabRoutes) // New tabs const addTab = () => { const index = (item => item?.path === ) if(index == -1) { ({ path: route?.path, name: route?.name, meta: { ...route?.meta, } }) } updateConfig('tabRoutes', ) updateCacheRoutes() } // Delete Tab const removeTab = (path) => { const index = (item => item?.path === path) if(index > -1) { (index, 1) updateTabs() } } // Remove the left tab const removeLeftTab = (path) => { const index = (item => item?.path === path) if(index > -1) { = ((item, i) => item?.meta?.isAffix || i >= index) updateTabs() } } // Remove the right tab const removeRightTab = (path) => { const index = (item => item?.path === path) if(index > -1) { = ((item, i) => item?.meta?.isAffix || i <= index) updateTabs() } } // Delete other tabs const removeOtherTab = (path) => { = (item => item?.meta?.isAffix || item?.path === path) updateTabs() } // Delete all const removeAllTab = (path) => { = (item => item?.meta?.isAffix) updateTabs() } // Update tab const updateTabs = (tabs) => { updateConfig('tabRoutes', tabs) updateCacheRoutes() const nextTab = tabs[ + 1] || tabs[ - 1] if(!nextTab) return jump(nextTab?.path) } // Updating the keep-alive cache const updateCacheRoutes = () => { let caches = (item => keepAlive || item?.meta?.isKeepAlive).map(item => ) // ('cacheViews cache routing >>:', caches) updateConfig('cacheRoutes', caches) } // Clearing the keep-alive cache const clearCacheRoutes = () => { updateConfig('cacheRoutes', []) } // Click on the tabs const changeTabs = (path) => { jump(path) } // Right-click menu update const handleDropdownChange = (visible, name) => { // Controls the display of one context menu at a time if(!visible) return (item => { if( === name) return () }) } // context menu command (computing) const handleDropdownCommand = (cmd, item) => { const path = item?.path switch(cmd) { case 'refresh': (0) break case 'close': removeTab(path) break case 'closeLeft': removeLeftTab(path) break case 'closeRight': removeRightTab(path) break case 'closeOther': removeOtherTab(path) break case 'closeAll': removeAllTab() break } } watch(() => , () => { = addTab() }, { immediate: true }) </script>
In summary, electron31+vue3+element-plus practical desktop lightweight backend system to share some knowledge, I hope to help you!
Finally, two examples of the latest original projects are attached
/xiaoyan2017/p/18290962
/xiaoyan2017/p/18323930