zoukankan      html  css  js  c++  java
  • Electron-Vue3-Vadmin后台系统|vite2+electron桌面端权限管理系统

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

    二、主要特性

    1. 前端技术栈Vite2、Vue3、Electron12、Element Plus、Vue-i18n、Echarts5.x、Sortable、Mockjs。
    2. 权限认证支持组件式+指令式两种方式。
    3. 支持中文/英文/繁体国际化解决方案。
    4. 支持表格拖拽排序、缩放、树形表格等功能。
    5. 支持加载动态权限菜单,多方式轻松权限控制。
    6. 高效率开发,整个框架已经搭建完毕,只需新增相应模块即可。

    三、项目结构图

    整个项目使用最新vue3语法编码,采用标准的分层目录结构形式,数据均是使用Mock.js进行模拟。

    ◆ electron支持多开新窗口

    项目支持打开多个窗口,如主题换肤、关于等窗口。只需通过如下的方式调用即可。

    import { winCfg, createWin } from '@/windows/actions'
    
    // 换肤窗口
    const handleOpenTheme = () => {
        createWin({
            title: '个性装扮',
            route: '/skin',
             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

    本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)
  • 相关阅读:
    什么是马甲APP?怎么用马甲APP导流
    OC与JS交互前言-b
    UIWebView1-b
    Mac双系统切换
    iOS之手势滑动返回功能
    Duplicate Symbol链接错的原因总结和解决方法-b
    #ifndef#define#endif的用法-b
    iOS Copy 和 MutableCopy的区别 深浅拷贝的区别-供参考
    解决CocoaPods在OS X 10.11出现问题-b
    django中cookies和session
  • 原文地址:https://www.cnblogs.com/xiaoyan2017/p/14776441.html
Copyright © 2011-2022 走看看