基於vue3+electron11實現QQ登陸切換|自定義導航欄|托盤|打包

上一篇有給你們分享過使用vue3和electron快速搭建項目、建立多窗口/父子modal窗口的一些方法。今天繼續給你們分享一些vue3.x+electron11項目開發中的一些知識點/踩坑記錄,但願對你有幫助~~html

一、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>

全局路由鉤子判斷登陸狀態,沒有登陸就跳轉到上面的登陸頁面。vue

// 全局鉤子攔截登陸狀態
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',
        width: 430,
        height: 330,
        resize: false,
        alwaysOnTop: true,
    })
}

注意1:自定義可拖拽區域,必須是兄弟節點或是父子節點,若是是經過position:absolute/fixed定位,則定位的那個區域設置 -webkit-app-region: no-drag 會無效/沒法點擊。這時須要把定位的元素放在可拖拽節點的子節點裏面。node

如上圖:若是把NavBar組件放在箭頭位置,設置/最小化/關閉按鈕會沒法點擊。react

<script>
import { getCurrentInstance } from 'vue'

export default {
    setup() {
        const { ctx } = getCurrentInstance()

        ctx.$store.commit(...)
        ctx.$router.push(...)
    }
}
</script>

注意2:最好不要使用上面的getCurrentInstance來獲取上下文操做,打包的時候會報錯+報錯+報錯webpack

二、vue3+electron實現無邊框窗體自定義導航條

建立窗體的時候,設置 frame: false 窗口會無頂部欄。這時就須要自定義拖拽區域和最小/大化及關閉按鈕了。web

新建navbar.vuewinbar.vue兩個模板,分別是導航欄/右上角按鈕組。vuex

  • 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>

支持自定義顏色、是否能夠最大/小化及關閉等功能。app

<!-- //網址連接模板 -->
<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,不然沒法響應點擊事件。electron

另外拖拽區域會出現以下系統右鍵菜單,爲了讓拖拽區更加逼真,須要屏蔽系統菜單。

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

三、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'))

四、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", //圖標路徑
                }
            }
        }
    }
}

一、你們若是在.vue頁面中使用 ipcRenderer 或 remote 模塊,出現以下報錯:

 Uncaught TypeError: fs.existsSync is not a function 

出現這個問題的緣由:

  • 由於渲染進程屬於瀏覽器端,沒有集成Node的環境,像fs這樣的Node包是不可使用的。
  • 沒有Node環境,因此require關鍵詞也是不可使用的。

配置如上參數便可快速解決問題。

二、項目路徑不能設置中文,不然打包會出錯,必定要養成英文命名習慣。

三、最好不要使用getCurrentInstance函數來獲取上下文,操做store或router,打包會出錯。

四、建立的vue3項目默認會是 createWebHistory 模式,打包會出現白屏狀況;改成 createWebHashHistory Hash模式。

五、打包靜態資源缺失或截圖dll無效。需在打包配置裏設置 extraResources 參數。

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

from參數表示資源目錄位置,to表示打包時移動資源目錄到指定位置。以下圖所示:

項目中的static目錄用來存放一些ico圖標或截圖dll文件,即配置中的from目錄名稱。

打包後,會在resources目錄下生成static文件夾,即配置中的to定義的目錄名稱。

好了,以上就是vue3+electron11開發跨平臺項目的一些分享及踩坑記錄,但願能幫助到你們哈!

最後貼上一個vue3.0+vant3開發移動端h5聊天實例

http://www.javashuo.com/article/p-nsoofcam-ve.html

相關文章
相關標籤/搜索