前言

在上个月初,接到一个需求,要开发一个 聊天通讯 模块 并且 集成到 项目中的多个 入口,实现业务数据的记录追踪.

接到需求后,还挺开心,这是我第一次 搞 通讯 类的需求,之前一直是 B 端 的业务需求,不过现在也是在做这个方向,感觉 B 端 方向 挺有意思,管理着项目的整个项目上游和下游,然后服务于 内部人员 和 外部人员 使用,感觉挺自豪的。

下面就就跟着我来看看 如何 开发一个 聊天通讯 服务吧 ! (主要站在前端的角度来讲如何开发设计 )

技术栈

徽章.png

  • Vue 2.x
  • Websoket
  • Vuex
  • Element
  • vue-at

本项目是 以 Vue 技术栈生态开发的,其实不管用什么语言 , 思路是关键 ! 知道每一步需要干什么, 然后将每一步操作 整合起来 , 最终服务就跑起来了.

当中的每一步需要干什么 就是 编程 中的 function 功能,根据这个功能然后在细化分析需要有到哪些技术点 。在开发的过程中,你不可能对整个链路的所有技术点 熟悉,这就需要遇到啥困难,临时学习就可以了。

开始分析需求

首先,我们要等待 UI 设计师 的设计稿 画出来, 然后根据 UI 设计师的 设计稿分析整体 聊天通讯 的结构,从view 结构 来 划分 应该 大体 包括哪些 component , 每个component 中 又包括哪些小的 component , 这样从 大 到 小 的方向将 设计稿 转化为 程序员视角的 component .

确立了有哪些component , 接下来 就是 确定 每个 小的 component 又有哪些 功能了。 现在 UI 设计师们,一般画完界面后,会通过第三方软件 / 平台 来将效果图 转化成网页,并且可以通过 URL 可以直接访问,当光标放到页面中的某个元素时,可以获取到当前元素的 css style , 不过,我建议不之 copy ,有时和自己写的布局代码会冲突,按需copy .

效果图

真实效果图,我就在这里不放出来了,为了保密性,只把整体结构,列出来,然后带着大家分析结构和功能,如何进行编码设计和组件设计。

功能分析图

根据效果图,在进行组件划分时,我要记住这个原则:高内聚,低耦合 , 组件职责单一性

我们将组件划分为:

  • 联系人组件
  • 聊天组件 —- 包括了 历史记录组件

功能根据 UI 设计师 提供的 URL 网页来看交互效果来定,并和组长 / 产品经理 交流需求,确定需求,以及砍掉不合理需求。

需求确定后,就是梳理组件部分的功能了。

组件构成

在分析组件之前,我们需要先了解一下Vue Component ,使用Vue 的 朋友应该很熟悉了,一个组件的构成由以下组成:

  1. data 组件内部状态

  2. computed 计算属性,监听data 变化来实现对应的业务逻辑需求

  3. watch 监听state 变化

  4. method 组将的功能编写区

  5. props 组件接受父组件 传递来的值,进行约束类型等

  6. lifecycle 组件的生命周期, 可以在组件创建到销毁的过程中执行对应的业务逻辑

联系人组件

这个组件主要是用来在聊天的时候,可以通过分组快速的找到某个人联系它,功能相对简单。

功能:

  1. 查找联系人
  2. 有通知某人操作

功能分析

功能1: 查找联系人

通过现有联系人json 数据来 查找输入的联系人进行匹配。 (简单)

功能2: 通知某人

当用户点击到某个联系人时,将点击的人 放到输入框里 显示 @xxx [ 经过格式化处理 ] , 并将选中的联系人信息加入到发送消息的 json 对象中。

有多种实现方案,当用户点击了某联系人时,将触发事件,携带值传递给父组件[聊天组件的入口 index.vue ] 接收,然后将值传递给 聊天主体组件 ,通过 在 聊天主体组件 中 通过 $refs 进行传递值。

下面只提供示例代码

从联系人列表获取选中联系人

//联系人组件 concat.vue
​
​
getLogname(val){
    this.$emit(\'toParent\',{tag:\'add\',logname:val})
},

聊天框显示选中的联系人

在聊天入口组件 接收 子向父 组件传递 选中联系人数据,然后给 聊天主体 组件绑定 ref , 通过refs 来将联系人数据传递到 聊天主体 组件显示。 [这块 数据传递有多种方法,例如 Vuex]

//聊天组件入口 index.vue   它包括 联系人组件  聊天主体组件  历史记录组件
​
//联系人组件
<Concat @toParent=\'innerHtmlToChat\'/>
​
//聊天主体组件    
<ChatRoom @fullScreen="getFullStatus" @closeWindow="close" ref="chatRoom"/>
​
​
    
 // 接受
 innerHtmlToChat(data){
    this.$refs.chatRoom.$refs.inputConents.innerHTML+=`&nbsp;@&nbsp;${data.logname}`  //拼接到聊天输入框里
},     
​

效果展示

从联系人列表选中人员,发送消息

@人 接收到推送消息

聊天主体组件

这个组件就负责的功能就多了,这块我主要把关键的功能带大家来分析过一遍

关键功能;

  1. @ 好友功能,实现推送通知(在线通知 / 离线-上线通知)
  2. 聊天工具 [ 支持表情 支持大文件上传 ]
  3. 发送消息 [ 这块就可以跟业务挂钩了,发送信息时,并携带一些符合你项目需求的数据]

功能分析

功能1 : @ 实现

vue-at 文档 : https://github.com/von7750/vue-at

它的功能和 微信QQ @ 功能一样,在聊天输入框里,当你 输入 @ 键时, 弹出好友列表,然后从中选择联系人进行聊天。

@ 功能必须包括以下3个关键功能;

  • 可以弹出联系人列表
  • 可以监听输入字符内容进行过滤显示对应数据
  • 删除 @ 联系人
  • …….

一开始, 我是 自己造了个 @ 功能 轮子 搞了搞,后来才发现市场上有相应的轮子,直接用第三方了,挺不错的 vue-at

下面来跟着我,来捋一下思路如何实现这个轮子,此处就不放实现代码了。

先来分析一波:

当在编辑区,输入 @ 时, 弹出框

  1. 我们可以在 mounted 生命周期中监听 按键 code = 50 / 229 (中文/英文) 时,做出处理
  2. 由于我们这块采用的 div 可编辑属性 ,那么就获取到 可编辑属性的光标位置
  3. 然后通过光标位置 动态来改变 弹出框联系人列表的样式 top left , 实现跟着光标的 位置显示联系人列表。
  4. 然后 从列表中选择 联系人进行聊天,并将 联系人列表弹框 隐藏掉。

上面就实现了基本的 选中联系人功能

删除选中的联系人

由于这块是采用的可编辑属性, 我们可以获取选中的人,但无法直接判断是删除的哪个人,这时,只能通过判断 innerHTML 中是否包含某联系人,来进行删除已保存的联系人。

这时,已经基本满足了业务需求实现了。

第三方插件已经的够好了,我们就没必要再造轮子,浪费时间了, 但 实现思路 必须的懂。 下面,我就来演示如何使用 第三方插件vue-at 实现 @ 功能

1. 安装插件

npm i vue-at@2.x

2.组件 内部导入插件组件

import At from "vue-at";

3.注册插件组件

 components: {
        At
 },

4. 页面中使用

At 组件 必须包括 可编辑 输入内容区域, 这样,当输入 @ 时,会弹出联系人列表框。

  • members : 数据源
  • filter-match : 过滤数据
  • deleteMatch : 删除的联系人
  • insert : 获取联系人
<At
    :members="filtercontactListContainer"
    :filter-match="filterMatch"
    :deleteMatch="deleteMatch"
    @insert="getValue"
    >
    <template slot="item" slot-scope="s">
        <div v-text="s.item" style="width:100%"></div>
    </template>
    <div
         class="inputContent"
         contenteditable="true"
         ref="inputConents"
         ></div>
</At>
// 过滤联系人
filterMatch(name, chunk) {
    return name.toLowerCase().indexOf(chunk.toLowerCase()) === 0;
},
// 删除联系人
deleteMatch(name, chunk, suffix) {
    this.contactList = this.contactList.filter(
            item => item.logname != chunk.trim()
        );
  return chunk === name + suffix;
},
// 获取联系人
getValue(val) {
     this.contactList.push({ logname: val });
},

功能2:聊天工具箱

聊天软件除了普通文字聊天,还有一些辅助服务来增加聊天的丰富性,例如: 表情 , 文件上传, 截图上传 …. 功能

我们先来看看 市场 热门聊天软件它们有哪些 聊天工具。

微信聊天工具箱

  • 表情
  • 文件上传
  • 截屏
  • 聊天记录
  • 视频聊天 / 语音聊天

QQ 聊天工具箱

  • 表情
  • GIF 动图
  • 截屏
  • 文件上传
  • 腾讯文档
  • 图片发送
  • ..... 腾讯业务相关功能

介绍了市场上热门聊天的工具箱有哪些工具,回归正题: 我们的聊天工具箱 有哪些功能呢, 其实有哪些功能根据 业务来定,后期工具箱可以不断扩充。 我们的工具箱基本上满足日常聊天需求

  • 表情
  • 文件上传 支持大文件 ( 几个G 都可以)
  • 截屏 Ctrl + Alt + A
  • 历史记录

下面我就来将比较几个重要的功能: 文件上传截屏 , 其它功能都很简单。

文件上传

上传组件我采用的是 Element el-upload 组件,由于我业务 要求上传文件支持大文件, 采用的 分片续传 方式来实现。

分片续传思路

  1. 我们上传也是采用的 websoket 上传,首次发送时,必须发送一些必要的文件基本信息

    • 文件名
    • 文件大小
    • 发送者
    • 一些跟业务相关的字段数据
    • 时间
    • 文件分片大小
    • 文件分片片数
    • 上传进度标识
  2. 首次发送完文件的基本信息后,开始发送分片文件信息,首先将文件分片后,然后依次读取片文件流,发送时携带文件流,等文件分片循环结束后,发送一个结束标识告诉后台发送完毕了 [这块你可以和后端商量设计数据格式]

示例代码演示

<el-upload
           ref="upload"
           class="upload-demo"
           drag
           :auto-upload="false"
           :file-list="fileList"
           :http-request="httpRequest"
           style="width:200px"
           >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text" trigger>
        <em> 将文件拖到此处然后点击上传文件</em>
    </div>
</el-upload>

覆盖掉 Element 默认上传方式,改用自定义上传方式。

开始分片上传

    // 上传文件
    httpRequest(options) {
      let that = this;
​
      //每个文件切片大小
      const bytesPerPiece = 1024 * 2048;
     // 文件必要的信息
      const { name, size } = options.file;
     // 文件分割片数
      const chunkCount = Math.ceil(size / bytesPerPiece);
      
    // 获取到文件后,发送文件的基本信息
      const fileBaseInfo = {
        fileName: name,
        fileSize: size,
        segments: "historymessage",
        loginName: localStorage.getItem("usrname"),
        time: new Date().toLocaleString(),
        chunkSize: bytesPerPiece,
        chunkCount: chunkCount,
        messagetype: "bufferfile",
        process: "begin",
          
          
        ... 一些跟业务挂钩的 字段
​
      };
​
​
      that.$websoketGlobal.ws.send(JSON.stringify(fileBaseInfo));
      
      let start = 0;
​
      // 进行分片
      var blob = options.file.slice(start, start + bytesPerPiece);
      //创建`FileReader`
      var reader = new FileReader();
      //开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象.
      reader.readAsArrayBuffer(blob);
      //读取操作完成时自动触发。
      reader.onload = function(e) {
        // 发送文件流
        that.$websoketGlobal.ws.send(reader.result);
        start += bytesPerPiece;
        if (start < size) {
          var blob = options.file.slice(start, start + bytesPerPiece);
          reader.readAsArrayBuffer(blob);
        } else {
          fileBaseInfo.process = "end";
          // 发送上传文件结束 标识
          that.$websoketGlobal.ws.send(JSON.stringify(fileBaseInfo));
        }
        that.uploadStatus = false;
        that.fileList = [];
      };
    },

效果演示

功能3: 截屏功能

PC 中,这是一个很重要的业务,通过这种技术可以从网上截取下自己感兴趣的文章图片供自己使用观看,可以帮助人们更好的去理解使用知识。

由于我们的输入内容区域采用的 可编辑 区域,此处可以插入任意内容,也可以使用外部 的截图功能,粘贴到输入框区域,这块就没必要的造轮子了

1. 可编辑区域

我们给 div 加上 该属性 contenteditable 就可以控制 div 中可输入哪些内容,外部复制过来内容也可以直接显示,还可以显示其带的css 效果。我们先来看看 contenteditable 有哪些属性吧 !

描述
inherit 默认值继承自父元素
true 或空字符串,表示元素是可编辑的;
false 表示元素不是可编辑的。
plaintext-only 纯文本
caret 符号
events

注意

不允许简写为 <label contenteditable>Example Label</label>

正确的用法是 <label contenteditable="true">Example Label</label>

浏览器支持情况

使用

<div
     class="inputContent"
     contenteditable="true"
     ref="inputConents">
</div>

效果展示

2. 截屏

由于采用的是 可编辑 ,那么就可以随意从外部 copy , 哈哈,有意思的来了,支持 Windows 自带的截屏 + PC 第三方 截屏……

版权声明:本文为HaiJun-Aion原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/HaiJun-Aion/p/15292270.html