zoukankan      html  css  js  c++  java
  • 基于vue3+electron11实现QQ登录切换|自定义导航栏|托盘|打包

    上一篇有给大家分享过使用vue3和electron快速搭建项目、创建多窗口/父子modal窗口的一些方法。今天继续给大家分享一些vue3.x+electron11项目开发中的一些知识点/踩坑记录,希望对你有帮助~~

    1、vue3+electron实现QQ登录界面

    <template>
        <div>
            <div class="ntMain__cont flex1 flexbox flex-col">
                <div class="nt__lgregWrapper flex1 flexbox flex-col vui__drag">
                    <NavBar title=" " transparent fixed>
                        <template #wbtn>
                            <a class="wbtn" title="设置"><i class="iconfont icon-shezhi1"></i></a>
                        </template>
                    </NavBar>
    
                    <template v-if="!isShowQrcode">
                        <div class="nt__lgregBox flex1">
                            <div class="banner">
                                <h2 class="tit">Vue3-Electron</h2>
                                <img class="bg" src="@assets/img/skin/bg-banner.png" />
                            </div>
                            <div class="slogan">
                                <div class="avatar"><img src="@assets/logo-white.png" /></div>
                            </div>
                            <div class="forms">
                                <form @submit.prevent="handleSubmit">
                                    <ul class="clearfix">
                                        <li class="flexbox flex-alignc"><input class="iptxt flex1" type="text" v-model="formObj.tel" placeholder="请输入手机号" autocomplete="off" maxLength="11" /><em class="borLine"></em></li>
                                        <li class="flexbox flex-alignc"><input class="iptxt flex1" type="password" v-model="formObj.pwd" placeholder="请输入密码" autocomplete="off" /><em class="borLine"></em></li>
                                    </ul>
                                    
                                    <div class="btns"><button class="vui__btn vui__btn-primary btn__submit" type="submit">登录</button></div>
                                    <div class="lgregLink align-c clearfix">
                                        <router-link class="navigator" to="#">忘记密码</router-link>
                                        <router-link class="navigator" to="/register">注册账号</router-link>
                                    </div>
                                </form>
                            </div>
                        </div>
                        <div class="nt__lgregFoot vui__nodrag" @click="handleShowQR">
                            <i class="iconfont icon-saoma"></i>
                        </div>
                    </template>
                    <template v-else>
                        <div class="nt__lgregBox flex1">
                            <div class="banner">
                                <h2 class="tit">Vue3-Electron</h2>
                                <img class="bg" src="@assets/img/skin/bg-banner.png" />
                            </div>
                            <div class="slogan">
                                <div class="qrcode"><img src="@assets/img/placeholder/wx-qrcode.jpg" /></div>
                            </div>
                            <div class="forms">
                                <div style="text-align:center;margin:20px 0 25px;">使用手机扫一扫快速登录。</div>
                                <div class="btns"><button class="vui__btn vui__btn-primary btn__submit" @click="handleShowQR">返回</button></div>
                            </div>
                        </div>
                    </template>
                </div>
            </div>
        </div>
    </template>
    
    <script>
    import { ref, reactive, inject } from 'vue'
    import { useStore } from 'vuex'
    import { createWin } from '@module/actions'
    
    export default {
        components: {},
        setup() {
            const store = useStore()
    
            const v3layer = inject('v3layer')
    
            const utils = inject('utils')
    
            const isShowQrcode = ref(false)
    
            const formObj = reactive({})
    
            const handleShowQR = () => {
                isShowQrcode.value = !isShowQrcode.value
            }
    
            const VTips = (content) => {
                v3layer({content: content, time: 2})
            }
    
            const handleSubmit = () => {
                if(!formObj.tel){
                    VTips('手机号不能为空!')
                }else if(!utils.checkTel(formObj.tel)){
                    VTips('手机号格式不正确!')
                }else if(!formObj.pwd){
                    VTips('密码不能为空!')
                }else{
                    store.commit('SET_ISLOGIN', true);
                    store.commit('SET_TOKEN', utils.setToken());
                    store.commit('SET_USER', formObj.tel);
                    
                    // ...
                }
            }
    
            return {
                isShowQrcode,
                handleShowQR,
                formObj,
                handleSubmit
            }
        }
    }
    </script>

    全局路由钩子判断登录状态,没有登录就跳转到上面的登录页面。

    // 全局钩子拦截登录状态
    router.beforeEach((to, from, next) => {
        const hasLogined = store.state.isLogin
    
        // 判断当前路由地址是否需要登录权限
        if(to.meta.requireAuth) {
            if(hasLogined) {
                next()
            }else {
                // 跳转登录页面
                loginWin()
            }
        }else {
            next()
        }
    })
    /**
     * @desc 登录窗口
     */
    export function loginWin() {
        createWin({
            isMainWin: true,
            title: '登录',
            route: '/login',
             430,
            height: 330,
            resize: false,
            alwaysOnTop: true,
        })
    }

    注意1:自定义可拖拽区域,必须是兄弟节点或是父子节点,如果是通过position:absolute/fixed定位,则定位的那个区域设置 -webkit-app-region: no-drag 会无效/无法点击。这时需要把定位的元素放在可拖拽节点的子节点里面。

    如上图:如果把NavBar组件放在箭头位置,设置/最小化/关闭按钮会无法点击。

    <script>
    import { getCurrentInstance } from 'vue'
    
    export default {
        setup() {
            const { ctx } = getCurrentInstance()
    
            ctx.$store.commit(...)
            ctx.$router.push(...)
        }
    }
    </script>

    注意2:最好不要使用上面的getCurrentInstance来获取上下文操作,打包的时候会报错+报错+报错

    2、vue3+electron实现无边框窗体自定义导航条

    创建窗体的时候,设置 frame: false 窗口会无顶部栏。这时就需要自定义拖拽区域和最小/大化及关闭按钮了。

    新建navbar.vuewinbar.vue两个模板,分别是导航栏/右上角按钮组。

    • navbar模板
    <template>
        <div class="nt__navbar" :class="{'fixed': fixed, 'transparent fixed': transparent}">
            <div class="nt__navbar-wrap flexbox flex-alignc vui__drag" :style="{'background': bgcolor, 'color': color, 'z-index': zIndex}">
                <!-- 标题 -->
                <div class="nt__navbar-title" :class="{'center': center}">
                    <template v-if="$slots.title"><slot name="title" /></template>
                    <template v-else>{{title || winCfg.window.title}}</template>
                </div>
            </div>
            <WinBar :minimizable="minimizable" :maximizable="maximizable" :closable="closable">
                <slot name="wbtn" />
            </WinBar>
        </div>
    </template>
    
    <script>
    import { winCfg } from '@module/actions'
    
    export default {
        props: {
            // 标题
            title: { type: String, default: '' },
            // 标题颜色
            color: { type: String, default: '#fff' },
            // 背景颜色
            bgcolor: String,
            // 标题是否居中
            center: { type: [Boolean, String], default: false },
            // 是否固定
            fixed: { type: [Boolean, String], default: false },
            // 背景透明
            transparent: { type: [Boolean, String], default: false },
            // 设置层级
            zIndex: { type: [Number, String], default: '2021' },
    
            /**
             * WinBar组件参数
             */
            // 窗口是否可以最小化
            minimizable: { type: [Boolean, String], default: true },
            // 窗口是否可以最大化
            maximizable: { type: [Boolean, String], default: true },
            // 窗口是否可以关闭
            closable: { type: [Boolean, String], default: true },
        },
        setup() {
            return {
                winCfg,
            }
        }
    }
    </script>

    支持自定义标题/居中、颜色/背景色、是否固定、透明背景等功能。

    <NavBar bgcolor="#00d2ff" minimizable="false">
        <template #title><i class="iconfont icon-about"></i> 关于</template>
    </NavBar>

    <NavBar bgcolor="#15ff95" color="#f00" center>
        <template #title><i class="iconfont icon-huanfu"></i> 个性装扮</template>
        <template #wbtn>
            <a class="wbtn" title="我的装扮"><i class="iconfont icon-tabbar3"></i></a>
        </template>
    </NavBar>

    <NavBar :bgcolor="headerBg" transparent>
        <template #title><i class="iconfont icon-pyq"></i> 朋友圈</template>
        <template #wbtn>
            <a class="wbtn" title="更换封面"><i class="iconfont icon-dianzan"></i></a>
            <a class="wbtn" title="发布" @click="isShowPublish=true"><i class="iconfont icon-paizhao"></i></a>
        </template>
    </NavBar>
    • winbar.vue模板
    <template>
        <div class="vui__winbtn flexbox flex-alignc">
            <div class="vui__winbtn-groups" :style="{'color': color}">
                <slot />
                <a v-if="JSON.parse(minimizable)" class="wbtn" title="最小化" @click="handleWinMin"><i class="iconfont icon-min"></i></a>
                <a v-if="JSON.parse(maximizable)&&winCfg.window.resize" class="wbtn" :title="hasMaximized ? '向下还原' : '最大化'" @click="handleWinMax2Min"><i class="iconfont" :class="hasMaximized ? 'icon-restore' : 'icon-max'"></i></a>
                <a v-if="JSON.parse(closable)" class="wbtn close" title="关闭" @click="handleWinClose"><i class="iconfont icon-quit"></i></a>
            </div>
        </div>
    </template>
    
    <script>
    import { remote } from 'electron'
    import { onMounted, reactive, inject, toRefs } from 'vue'
    import { useStore } from 'vuex'
    import { winCfg, setWin } from '@module/actions'
    
    export default {
        props: {
            color: { type: String, default: '#fff' },
            // 窗口是否可以最小化
            minimizable: { type: [Boolean, String], default: true },
            // 窗口是否可以最大化
            maximizable: { type: [Boolean, String], default: true },
            // 窗口是否可以关闭
            closable: { type: [Boolean, String], default: true },
        },
        setup() {
            let win = remote.getCurrentWindow()
    
            const store = useStore()
    
            const v3layer = inject('v3layer')
    
            const data = reactive({
                hasMaximized: false
            })
    
            onMounted(() => {
                if(win.isMaximized()) {
                    data.hasMaximized = true
                }
                win.on('maximize', () => {
                    data.hasMaximized = true
                })
                win.on('unmaximize', () => {
                    data.hasMaximized = false
                })
            })
    
            // 最小化
            const handleWinMin = () => {
                setWin('min', winCfg.window.id)
            }
            // 最大化/还原
            const handleWinMax2Min = () => {
                setWin('max2min', winCfg.window.id)
            }
            // 关闭
            const handleWinClose = () => {
                setWin('close', winCfg.window.id)
            }
    
            return {
                ...toRefs(data),
                winCfg,
    
                handleWinMin,
                handleWinMax2Min,
                handleWinClose
            }
        }
    }
    </script>

    支持自定义颜色、是否可以最大/小化及关闭等功能。

    <!-- //网址链接模板 -->
    <template>
        <div>
            <NavBar></NavBar>
            
            <!-- 内容区 -->
            <div class="ntMain__cont flex1 flexbox flex-col">
                <div class="vChat__lkview">
                    <iframe scrolling="auto" allowtransparency="true" frameborder="0" :src="data.url"></iframe>
                </div>
            </div>
        </div>
    </template>

    <WinBar color="#ff0">
        <a class="wbtn" title="关于" @click="handleAboutWin"><i class="iconfont icon-about"></i></a>
        <a class="wbtn" title="个性装扮" @click="handleSkinWin"><i class="iconfont icon-huanfu"></i></a>
        <a class="wbtn" title="朋友圈" @click="handleFZoneWin"><i class="iconfont icon-pyq2"></i><em class="vui__badge-dot"></em></a>
        <a class="wbtn" title="设置" @click="isShowSettingLayer=true"><i class="iconfont icon-peizhi"></i></a>
        <a class="wbtn" title="界面管理器" @click="handleUIManager"><i class="iconfont icon-tianjia"></i></a>
        <a class="wbtn" :class="{'on': isAlwaysOnTop}" :title="isAlwaysOnTop ? '取消置顶' : '置顶'" @click="handleAlwaysTop"><i class="iconfont icon-ding"></i></a>
    </WinBar>

    实现起来非常简单,只是需要注意一些细节即可。另外设置-webkit-app-region: drag之后,下面的子元素记得要设置-webkit-app-region: no-drag,否则无法响应点击事件。

    另外拖拽区域会出现如下系统右键菜单,为了让拖拽区更加逼真,需要屏蔽系统菜单。

    win.hookWindowMessage(278, function(e){
        win.setEnabled(false)
        setTimeout(() => {
            win.setEnabled(true)
        }, 150)
        
        return true
    })

    3、vue3+electron模仿QQ托盘图标/托盘闪烁

    在项目根目录下放置两个大小一致的tray.ico文件,其中一个透明即可。

    let tray = null
    let flashTimer = null
    let trayIco1 = path.join(__dirname, '../static/tray.ico')
    let trayIco2 = path.join(__dirname, '../static/tray-empty.ico')
    
    // 创建系统托盘图标
    createTray() {
        const trayMenu = Menu.buildFromTemplate([
            {
                label: '我在线上', icon: path.join(__dirname, '../static/icon-online.png'),
                click: () => {...}
            },
            {
                label: '忙碌', icon: path.join(__dirname, '../static/icon-busy.png'),
                click: () => {...}
            },
            {
                label: '隐身', icon: path.join(__dirname, '../static/icon-invisible.png'),
                click: () => {...}
            },
            {
                label: '离开', icon: path.join(__dirname, '../static/icon-offline.png'),
                click: () => {...}
            },
            {type: 'separator'},
            {
                label: '关闭所有声音', click: () => {...},
            },
            {
                label: '关闭头像闪动', click: () => {
                    this.flashTray(false)
                }
            },
            {type: 'separator'},
            {
                label: '打开主窗口', click: () => {
                    // ...
                }
            },
            {
                label: '退出', click: () => {
                    // ...
                }
            },
        ])
        this.tray = new Tray(this.trayIco1)
        this.tray.setContextMenu(trayMenu)
        this.tray.setToolTip(app.name)
        this.tray.on('double-click', () => {
            // ...
        })
    }
    
    // 托盘图标闪烁
    flashTray(flash) {
        let hasIco = false
    
        if(flash) {
            if(this.flashTimer) return
            this.flashTimer = setInterval(() => {
                this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
                hasIco = !hasIco
            }, 500)
        }else {
            if(this.flashTimer) {
                clearInterval(this.flashTimer)
                this.flashTimer = null
            }
            this.tray.setImage(this.trayIco1)
        }
    }
    
    // 销毁托盘图标
    destoryTray() {
        this.flashTray(false)
        this.tray.destroy()
        this.tray = null
    }

    调用  flashTray(true)  和  flashTray(false)  分别开启/关闭托盘图标闪烁。

    在一开始调试的时候托盘图标一直不显示,最后发现是路径错了。通过 console.log(__dirname) 指向了默认的 dist_electron 打包目录。

    console.log('----------开始创建托盘')
    console.log(__dirname)
    console.log(path.join(__dirname, '../static/tray.ico'))

    4、vue3+electron打包遇到的一些坑

    项目使用的是electron-builder打包器来打包的,下面是常用的一些electron-builder打包配置。这里选择的是在vue.config.js里面配置。

    /**
     * 项目配置文件
     */
    
    const path = require('path')
    
    module.exports = {
        ...
    
        // webpack配置
        chainWebpack: config => {
            ...
        },
    
        pluginOptions: {
            electronBuilder: {
                nodeIntegration: true,
                // 项目打包参数配置
                builderOptions: {
                    "productName": "electron-qchat", //项目名称 打包生成exe的前缀名
                    "appId": "com.example.electronqchat", //包名
                    "compression": "maximum", //store|normal|maximum 打包压缩情况(store速度较快)
                    "artifactName": "${productName}-${version}-${platform}-${arch}.${ext}",
                    // "directories": {
                    //     "output": "build", //输出文件夹(默认dist_electron)
                    // },
                    "asar": false, //asar打包
                    // 拷贝静态资源目录到指定位置
                    "extraResources": [
                        {
                            "from": "./static",
                            "to": "static"
                        },
                    ],
                    "nsis": {
                        "oneClick": false, //一键安装
                        "allowToChangeInstallationDirectory": true, //允许修改安装目录
                        "perMachine": true, //是否开启安装时权限设置(此电脑或当前用户)
                        "artifactName": "${productName}-${version}-${platform}-${arch}-setup.${ext}",
                        "deleteAppDataOnUninstall": true, //卸载时删除数据
                        "createDesktopShortcut": true, //创建桌面图标
                        "createStartMenuShortcut": true, //创建开始菜单图标
                        "shortcutName": "ElectronQChat", //桌面快捷键图标名称
                    },
                    "win": {
                        "icon": "./static/shortcut.ico", //图标路径
                    }
                }
            }
        }
    }

    1、大家如果在.vue页面中使用 ipcRenderer 或 remote 模块,出现如下报错:

     Uncaught TypeError: fs.existsSync is not a function 

    出现这个问题的原因:

    • 因为渲染进程属于浏览器端,没有集成Node的环境,像fs这样的Node包是不可以使用的。
    • 没有Node环境,所以require关键词也是不可以使用的。

    配置如上参数即可快速解决问题。

    2、项目路径不能设置中文,否则打包会出错,一定要养成英文命名习惯。

    3、最好不要使用getCurrentInstance函数来获取上下文,操作store或router,打包会出错。

    4、创建的vue3项目默认会是 createWebHistory 模式,打包会出现白屏情况;改为 createWebHashHistory Hash模式。

    5、打包静态资源缺失或截图dll无效。需在打包配置里设置 extraResources 参数。

    "extraResources": [
        {
            "from": "./static",
            "to": "static"
        },
    ],

    from参数表示资源目录位置,to表示打包时移动资源目录到指定位置。如下图所示:

    项目中的static目录用来存放一些ico图标或截图dll文件,即配置中的from目录名称。

    打包后,会在resources目录下生成static文件夹,即配置中的to定义的目录名称。

    好了,以上就是vue3+electron11开发跨平台项目的一些分享及踩坑记录,希望能帮助到大家哈!

    最后贴上一个vue3.0+vant3开发移动端h5聊天实例

    https://www.cnblogs.com/xiaoyan2017/p/14250798.html

    本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)
  • 相关阅读:
    PHP中Foreach在引用时的陷阱 j神
    Yii中Session和cookie的用法 j神
    如何解决web大流量、高并发的问题 j神
    PHP无限级分类的递归算法 j神
    关于breadcrumbs j神
    MySQL索引分析和优化 j神
    Yii笔记 j神
    YII中的URL Management j神
    forward与redirect的区别 j神
    网络编程学习笔记
  • 原文地址:https://www.cnblogs.com/xiaoyan2017/p/14449570.html
Copyright © 2011-2022 走看看