一、專案概況
基于Electron+vue+electron-vue+vuex+Nodejs+vueVideoPlayer+electron-builder等技術仿制微信電腦端界面聊天室實體,實作訊息發送/動態表情,圖片/視頻預覽,拖拽圖片/粘貼截圖發送,朋友圈/紅包/換膚等功能,
二、效果圖

三、技術堆疊
- 框架技術:electron + electron-vue + vue
- 狀態管理:Vuex
- 地址路由:Vue-router
- 字體圖示:阿里iconfont字體圖示庫
- 彈窗插件:wcPop
- 打包工具:electron-builder
- 環境配置:Node.js + Chromium
- 圖片預覽:vue-photo-preview
- 視頻組件:vue-video-player
















如何配置開發環境及使用electron-vue,這里不作多介紹,可查閱官網及搜資料
https://simulatedgreg.gitbooks.io/electron-vue/content/cn/
https://github.com/SimulatedGREG/electron-vue
注意:由于electron-vue作者長時間未更新,里面electron版本v2.0.4太舊,如遇問題,可升級到最新版本
◆ Electron主行程index.js
通過BrowserWindow創建和控制瀏覽器視窗,官網有詳細介紹,這里略過...
https://electronjs.org/docs/api/browser-window
... let mainWin let tray let forceQuit = false let logined = false /** * 創建主視窗============================= */ function createMainWin() { mainWin = new BrowserWindow({ // 背景顏色 // backgroundColor: '#ebebeb', width: Common.WIN_SIZE_MAIN.width, height: Common.WIN_SIZE_MAIN.height, title: Common.WIN_TITLE, useContentSize: true, autoHideMenuBar: true, // 無邊框視窗 frame: false, resizable: true, // 視窗創建的時候是否顯示. 默認值為true show: false, webPreferences: { // devTools: false, webSecurity: false } }) mainWin.setMenu(null) mainWin.loadURL(Common.WIN_LOAD_URL()) mainWin.once('ready-to-show', () => { mainWin.show() mainWin.focus() }) // 點擊關閉最小到托盤判斷 mainWin.on('close', (e) => { if(logined && !forceQuit) { e.preventDefault() mainWin.hide() }else { mainWin = null app.quit() } }) initialIPC()
apptray.createTray() } app.on('ready', createMainWin) app.on('activate', () => { if(mainWin === null) { createMainWin() } }) ...

如上圖:創建托盤圖示及閃爍效果
/** * 托盤圖示事件 */ let flashTrayTimer = null let trayIco1 = `${__static}/icon.ico` let trayIco2 = `${__static}/empty.ico` let apptray = { // 創建托盤圖示 createTray() { tray = new Tray(trayIco1) const menu = Menu.buildFromTemplate([ { label: '打開主界面', icon: `${__static}/tray-ico1.png`, click: () => { if(mainWin.isMinimized()) mainWin.restore() mainWin.show() mainWin.focus() this.flashTray(false) } }, { label: '關于', }, { label: '退出', click: () => { if(process.platform !== 'darwin') { mainWin.show() // 清空登錄資訊 mainWin.webContents.send('clearLoggedInfo') forceQuit = true mainWin = null app.quit() } } }, ]) tray.setContextMenu(menu) tray.setToolTip('electron-vchat v1.0.0') // 托盤點擊事件 tray.on('click', () => { if(mainWin.isMinimized()) mainWin.restore() mainWin.show() mainWin.focus() this.flashTray(false) }) }, // 托盤圖示閃爍 flashTray(flash) { let hasIco = false if(flash) { if(flashTrayTimer) return flashTrayTimer = setInterval(() => { tray.setImage(hasIco ? trayIco1 : trayIco2) hasIco = !hasIco }, 500) }else { if(flashTrayTimer) { clearInterval(flashTrayTimer) flashTrayTimer = null } tray.setImage(trayIco1) } }, // 銷毀托盤圖示 destroyTray() { this.flashTray(false) tray.destroy() tray = null } }
◆ 渲染行程主頁面main.js及app.vue
/** * @Desc 主入口main.js * @about Q:282310962 wx:xy190310 */ import Vue from 'vue' import axios from 'axios' import App from './App' import router from './router' import store from './store' // 引入組件配置 import $components from './components' Vue.use($components) if (!process.env.IS_WEB) Vue.use(require('vue-electron')) Vue.http = Vue.prototype.$http = axios /* eslint-disable no-new */ new Vue({ components: { App }, router, store, template: '<App/>' }).$mount('#app')
<template> <div id="app"> <div class="elv-container" :style="$store.state.winSkin && {'background-image': 'url('+$store.state.winSkin+')'}"> <div class="elv-wrapper flexbox"> <!-- //側邊欄 --> <side-bar v-if="!$route.meta.hideSideBar" /> <!-- //主布局 --> <div class="elv-mainbx flex1 flexbox flex-col"> <!-- ...頂部按鈕 --> <win-bar /> <keep-alive> <router-view></router-view> </keep-alive> </div> </div> </div> </div> </template>
至于狀態管理及路由配置基本和vue里面使用一樣,這里也略過...
◆ electron自定義最大/小化、關閉按鈕、無邊框視窗拖動
配置BrowserWindow里面frame:false就會是無邊框視窗,這時就可以自定義最大/小,關閉按鈕,那么問題來了,無邊框視窗如何進行拖動尼?
- 1、通過mousedown、mousemove等事件處理
- 2、設定需要拖動區css屬性 -webkit-app-region
.elv__drag{-webkit-app-region: drag; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none;} .elv__nodrag{-webkit-app-region: no-drag;}

注意:默認設定-webkit-app-region: drag后,下面的元素不能點擊操作,可通過設定需點擊元素no-drag即可,
import { app, remote, ipcRenderer } from 'electron'
import { mapState, mapMutations } from 'vuex'
let currentWin = remote.getCurrentWindow()
export default {
props: {
title: String,
},
data () {
return {// 是否置頂
isAlwaysOnTop: false,
// 視窗是否可以最小化
isMinimizable: true,
// 視窗是否可以最大化
isMaximizable: true,
}
},
computed: {
...mapState(['isWinMaxed'])
},
mounted() {if(!currentWin.isMinimizable()) {
this.isMinimizable = false
}
if(!currentWin.isMaximizable()) {
this.isMaximizable = false
}
if(this.isWinMaxed && currentWin.isMaximizable()) {
currentWin.maximize()
}
// 監聽是否最大化
currentWin.on('maximize', () => {
this.SET_WINMAXIMIZE(true)
})
currentWin.on('unmaximize', () => {
this.SET_WINMAXIMIZE(false)
})
},
methods: {
...mapMutations(['SET_WINMAXIMIZE']),
// 置頂視窗
handleFixTop() {
this.isAlwaysOnTop = !this.isAlwaysOnTop
currentWin.setAlwaysOnTop(this.isAlwaysOnTop)
},
// 最小化
handleMin() {
currentWin.minimize()
},
// 最大化
handleMax() {
if(!currentWin.isMaximizable()) return
if(currentWin.isMaximized()) {
currentWin.unmaximize()
this.SET_WINMAXIMIZE(false)
}else {
currentWin.maximize()
this.SET_WINMAXIMIZE(true)
}
},
// 關閉
handleQuit() {
currentWin.close()
}
}
}
◆ 聊天編輯器游標處插入表情、div可編輯contenteditable="true"雙向系結
如何實作electron vue中向編輯框contenteditable游標處插入動態表情,類似QQ、微信聊天編輯器??
- 1、使用input、textarea文本框實作
通過給input或textarea文本框插入[奮斗]、(:17 等表情符標簽,展示資訊的時候決議標簽就行

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row"> <div class="col col-sm-12"> <button class="btn btn-success" data-emoj="[笑臉]">[笑臉]</button> <button class="btn btn-success" data-emoj="[奮斗]">[奮斗]</button> <button class="btn btn-success" data-emoj="[:17]">[:17]</button> </div> <div class="col col-sm-12"> <textarea class="form-control" id="content" rows="10"></textarea> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script> (function ($) { $.fn.extend({ insertEmojAtCaret: function (myValue) { var $t = $(this)[0]; if (document.selection) { this.focus(); sel = document.selection.createRange(); sel.text = myValue; this.focus(); } else if ($t.selectionStart || $t.selectionStart == '0') { var startPos = $t.selectionStart; var endPos = $t.selectionEnd; var scrollTop = $t.scrollTop; $t.value = $t.value.substring(0, startPos) + myValue + $t.value.substring(endPos, $t.value.length); this.focus(); $t.selectionStart = startPos + myValue.length; $t.selectionEnd = startPos + myValue.length; $t.scrollTop = scrollTop; } else { this.value += myValue; this.focus(); } } }); })(jQuery); $("button").on("click", function() { $("#content").insertEmojAtCaret($(this).attr("data-emoj")); }); </script> </body> </html>
- 2、運用h5中div可編輯contenteditable="true"實作
在vue頁面設定contenteditable="true" 實作富文本文本框效果,由于div不能系結v-model,只能使用vue提供的自定義組件v-model功能,
https://cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model

思路:新建一個chatInputEdit.vue組件,通過監聽資料變化回傳父組件資料
1):呼叫chatInputEdit.vue組件,并給其系結v-model
<template> ... <chatInputEdit ref="chatInput" v-model="editorText" /> </template> ... export default { data () { return { editorText: '', ... } }, ... }
2):v-model中傳入的值在子組件prop中獲取并監聽value變化
export default { props: { value: { type: String, default: '' } }, data () { return { editorText: this.value, ... } }, watch: { value() { ... } }, }
3):監聽獲取到的值賦值給子組件中的v-html引數,就打通雙向系結鏈路了
/** * contenteditable游標處插入內容 */ insertHtmlAtCaret(html) { let sel, range; if(!this.$refs.editor.childNodes.length) { this.$refs.editor.focus() } if (window.getSelection) { // IE9 and non-IE sel = window.getSelection(); if (sel.getRangeAt && sel.rangeCount) { range = sel.getRangeAt(0); range.deleteContents(); let el = document.createElement("div"); el.appendChild(html) var frag = document.createDocumentFragment(), node, lastNode; while ((node = el.firstChild)) { lastNode = frag.appendChild(node); } range.insertNode(frag); if (lastNode) { range = range.cloneRange(); range.setStartAfter(lastNode); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); } } } else if (document.selection && document.selection.type != "Control") { // IE < 9 document.selection.createRange().pasteHTML(html); } }
◆ electron+vue實作微信截圖功能

Node中通過的execFile方法執行exe檔案,exe呼叫同級目錄下的微信截圖dll,調出截圖工具
handleCaptureScreen() { return new Promise((resolve) => { const { execFile } = require('child_process') var screenWin = execFile('./static/PrintScr.exe') screenWin.on('exit', function(code) { let pngs = require('electron').clipboard.readImage().toPNG() let imgData = new Buffer.from(pngs, 'base64') let imgs = 'data:image/png;base64,' + btoa(new Uint8Array(imgData).reduce((data, byte) => data + String.fromCharCode(byte), '')) resolve(imgs) }) }) },
Okay,以上就是基于electron+vue開發仿微信客戶端聊天實體分享,希望能有些幫助!!????
最后附上兩個最近實體專案
uniapp+vue實作抖音短視頻|陌陌app直播:https://www.cnblogs.com/xiaoyan2017/p/11835641.html
taro+react多端 (h5/小程式/App) 聊天實體:https://www.cnblogs.com/xiaoyan2017/p/12039544.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/18250.html
標籤:HTML5
上一篇:顯示滑鼠滑動軌跡
下一篇:vue 中的路由為什么 采用 hash 路由模式,而不是href超鏈接模式(Hypertext,Reference)?
