百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

electron聊天室|vue+electron-vue仿微信客户端|electron桌面聊天

yuyutoo 2024-10-13 00:35 4 浏览 0 评论

一、项目概况

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

本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)

相关推荐

Python操作Word文档神器:python-docx库从入门到精通

Python操作Word文档神器:python-docx库从入门到精通动动小手,点击关注...

Python 函数调用从入门到精通:超详细定义解析与实战指南 附案例

一、函数基础:定义与调用的核心逻辑定义:函数是将重复或相关的代码块封装成可复用的单元,通过函数名和参数实现特定功能。它是Python模块化编程的基础,能提高代码复用性和可读性。定义语法:...

等这么长时间Python背记手册终于来了,入门到精通(视频400集)

本文毫无套路!真诚分享!前言:无论是学习任何一门语言,基础知识一定要扎实,基础功非常的重要,找一个有丰富编程经验的老师或者师兄带着你会少走很多弯路,你的进步速度也会快很多,无论我们学习的目的是什么,...

图解Python编程:从入门到精通系列教程(附全套速查表)

引言本系列教程展开讲解Python编程语言,Python是一门开源免费、通用型的脚本编程语言,它上手简单,功能强大,它也是互联网最热门的编程语言之一。Python生态丰富,库(模块)极其丰富,这使...

Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够

本书是Python经典实例解析,采用基于实例的方法编写,每个实例都会解决具体的问题和难题。主要内容有:数字、字符串和元组,语句与语法,函数定义,列表、集、字典,用户输入和输出等内置数据结构,类和对象,...

Python函数全解析:从入门到精通,一文搞定!

1.为什么要用函数?函数的作用:封装代码,提高复用性,减少重复,提高可读性。...

Python中的单例模式:从入门到精通

Python中的单例模式:从入门到精通引言单例模式是一种常用的软件设计模式,它保证了一个类只有一个实例,并提供一个全局访问点。这种模式通常用于那些需要频繁创建和销毁的对象,比如日志对象、线程池、缓存等...

【Python王者归来】手把手教你,Python从入门到精通!

用800个程序实例、5万行代码手把手教你,Python从入门到精通!...

Python从零基础入门到精通:一个月就够了

如果想从零基础到入门,能够全职学习(自学),那么一个月足够了。...

Python 从入门到精通:一个月就够了

要知道,一个月是一段很长的时间。如果每天坚持用6-7小时来做一件事,你会有意想不到的收获。作为初学者,第一个月的月目标应该是这样的:熟悉基本概念(变量,条件,列表,循环,函数)练习超过30个编...

Python零基础到精通,这8个入门技巧让你少走弯路,7天速通编程!

Python学习就像玩积木,从最基础的块开始,一步步搭建出复杂的作品。我记得刚开始学Python时也是一头雾水,走了不少弯路。现在回头看,其实掌握几个核心概念,就能快速入门这门编程语言。来聊聊怎么用最...

神仙级python入门教程(非常详细),从0到精通,从看这篇开始!

python入门虽然简单,很多新手依然卡在基础安装阶段,大部分教程对一些基础内容都是一带而过,好多新手朋友,对一些基础知识常常一知半解,需要在网上查询很久。...

Python类从入门到精通,一篇就够!

一、Python类是什么?大家在生活中应该都见过汽车吧,每一辆真实存在、能在路上跑的汽车,都可以看作是一个“对象”。那这些汽车是怎么生产出来的呢?其实,在生产之前,汽车公司都会先设计一个详细的蓝图...

学习Python从入门到精通:30天足够了,这才是python基础的天花板

当年2w买的全套python教程用不着了,现在送给有缘人,不要钱,一个月教你从入门到精通1、本套视频共487集,本套视频共分4季...

30天Python 入门到精通(3天学会python)

以下是一个为期30天的Python入门到精通学习课程,专为零基础新手设计。课程从基础语法开始,逐步深入到面向对象编程、数据处理,最后实现运行简单的大语言模型(如基于HuggingFace...

取消回复欢迎 发表评论: