前言
我是阿江,我又来了,首先说一下真不是标题党,而是前段时间去看vue2源码的时候,发现在源码目录里面有一个叫examples的文件夹,尤大用vue写的小demo,咱怎么着也得进去学习一下。
然后就发现了本文诞生的灵感demo: 基于firebase实现的共享邮箱的demo,当下笔者就坐不住了。
这如果用来实现一个简易的实时聊天功能,多是一件美逝啊!所以就有了下文。
这里是笔者实现并部署的一个简单的聊天室
话不多说欸哈哈哈哈,鸡汤来咯!
开始
firebase的作用
首先在实现之前,先需要了解一下 firebase 是干嘛用的。firebase是由谷歌支持开发,一款云数据库服务。而firebase和常规云数据库不同的是,firebase 可以共享实时数据库。
接下来读者没有google账号的可以先去注册一个 google账号
当注册后,可以直接打开firebase官网
创建firebase项目
这里我们直接继续下一步。
这个我们只是一个demo 所以就不配置Google Analytics,有需要的笔者可以配置一下,大体就是统计分析流量等。
接下来就是创建一个 Realtime Database实时数据库。
这里我们选择一个最近的新加坡服务器
由于方便测试我们这里以测试模式开始
创建好的默认数据
这里需要注意的是firebase存储的并不是类似数据库的表型结构数据,而是
json格式数据,以便保证传输实时性得到保证!而我们的聊天数据也就可以创建一个数组对象来存储。
创建脚手架项目
到这一步我就可以正式开始项目搭建了,这里笔者直接使用自己的脚手架下载项目模板搭建。感兴趣可以了解一下上一篇文章:【开源心路历程】创建一个属于你自己的脚手架
当然也可以使用
vue-cli脚手架生成 ,不过多赘述。
create-cli
当前项目的目录层级如下:
|- page //项目页面
|- |— index //index 页面文件
|- |— |— static //资源文件
|- |— |— |- css
|- |— |— |- img
|- |— |— |- js
|- index.html //页面 html
下载依赖
npm i firebase --save
之后在js文件夹下创建一个firebase.config.js文件,用于配置firebase初始化配置对象等。
firebase.config.js 配置文件
回到firebase官网中
点击创建Web应用
这里分别代表引入初始化函数和生成firebase配置对象
将这段代码复制到firebase.config.js文件中
这里需要注意的是:引入firebase的方式,笔者没有按照firebase上面默认生成的配置,而是选择引入全部
firebase即为import firebase from 'firebase';这种方式。
引入firebase代码
这里引入我们使用V9的版本
import {initializeApp} from 'firebase/app';
import {getDatabase,ref,onValue,push} from 'firebase/database'
const firebaseConfig = {
apiKey: "AIzaSyCL59rWYn4qI40MOa55PFud-6dn-KtkiYc",
authDomain: "chatdemo-aa281.firebaseapp.com",
databaseURL: "https://chatdemo-aa281-default-rtdb.asia-southeast1.firebasedatabase.app",
projectId: "chatdemo-aa281",
storageBucket: "chatdemo-aa281.appspot.com",
messagingSenderId: "771306835282",
appId: "1:771306835282:web:de5dda7d19acc99a3bfe8b"
};
| 参数 | 说明 |
|---|---|
getDatabase | 获取数据库引用 |
ref("xxx/xxx") | 返回对查询位置的引用 |
onValue | 侦听特定位置的数据更改 |
push | 如果您提供了一个值 push () ,则该值将写入生成的位置。如果没有传递值,则不会向数据库写入任何内容,并且子内容仍然为空(但可以在其他地方使用 Reference)。 |
初始化firebase
initializeApp(firebaseConfig);
创建websocket连接实时数据库
const database=getDatabase();
const chatsRef = ref(database,'chatList'); //返回聊天信息位置的引用
编写提交函数
/***
* 提交函数,用于判断符合规则的信息提交
* @param nickName 昵称
* @param chatContent 聊天信息
* @param chatDate 发送日期
*/
const chatSubmit = (nickName,chatContent,chatDate) => {
if (nickName.length == 0) {
alert("昵称不能为空!");
return ;
}else if(chatContent.trim() == ""){
alert("内容不能为空!");
return ;
}else if(chatContent.length > 200 ){
alert("内容超过最大字数!");
return ;
} else{
pushChatData(nickName,chatContent,chatDate)
}
}
/***
* 添加聊天信息到firebase库里
* @param nickName 昵称
* @param chatContent 聊天信息
* @param chatDate 发送日期
*/
const pushChatData =(nickName,chatContent, chatDate) =>{
push(chatsRef,{
chatContent,
chatDate,
nickName,
})
}
导出
export {chatsRef , chatSubmit ,onValue }
chatsRef 代表聊天ref连接,在外面通过onValue获取聊天信息列表数据时需要用到。
index.js 入口文件
由于笔者使用的脚手架中有index.js文件所以不用创建,读者只需在同级创建一个index.js文件,引入代码
import {chatsRef,chatSubmit,onValue} from './firebase.config' // 引入firebase 的配置
引入Vue
下载依赖
npm install vue --save
引入Vue
这里需要注意是,我们不能默认
import Vue from 'vue'引入,这样引入vue是默认不包含编译器的版本,无法在页面上正常编译标签!所以我们引入完整版本的import Vue from 'vue/dist/vue'具体解释可以参照Vue文档:对不同构建版本的解释
在index.js中引入Vue
import Vue from 'vue/dist/vue'
创建Vue实例
el:"#index", //定义实例id
data:{
state: {
chatsList: [],//聊天信息数据
prpoverVisible: false, // 气泡控制
spinning: true, //加载控制
},
userState: {
nickName: "", //昵称
chatContent: "",//信息内容
nickCheckStatus: 0,//0 为未填写, 1 非法 2 合格 3 校验成功
}
},
加入逻辑函数,这里主要包含,
整体概要逻辑
初始化如果用户之前已经输入过昵称就会记录在localStorage中,并且会取出来验证昵称的合理性(这里的合理性,并没有做名称去重验证,只是简单验证昵称长度和特殊字符等)。如果合理则开始聊天。
并且保证信息发生改变时,整体显示信息的列表框要始终在最底部位置。以及日期转换和前后空格合并等。
methods:{
/***
* 初始化
* 获取本地存储nickname 如果没有就需要强行设置
*/
init(){
let nickName=localStorage.getItem('nickName');
if (nickName == null) return ;
if (this.checkNickName(nickName)) {
this.userState.nickName = nickName;
this.userState.nickCheckStatus = 3;
}
},
/***
* 验证昵称是否通过
* @param nickName 昵称
* @returns {string|*|void|undefined} 验证通过返回名字,没通过返回空字符串,用于直接判断。
*/
checkNickName(nickName){
let checkRule=new RegExp(/^[\u4e00-\u9fa5_a-zA-Z0-9-]{2,8}$/);
let checkValue=nickName.replace(/\s/g, "");
if (!checkValue.length) {
this.userState.nickCheckStatus=0;
return '';
}else if (checkRule.test(checkValue)) {
this.userState.nickCheckStatus=2;
return checkValue;
}else{
this.userState.nickCheckStatus=1;
//这里非法,是为了防止,有人直接通过浏览器 application 修改昵称用
//做的非法清空
window.localStorage.removeItem('nickName');
return '';
}
},
/**
* 添加昵称
*/
addNickName(){
if (this.checkNickName(this.userState.nickName)) {
localStorage.setItem('nickName',this.userState.nickName);
this.init();
}
},
/***
* 清空输入框和去掉空格
*/
clearChatContent(){
this.userState.chatContent="";
this.userState.chatContent=this.userState.chatContent.replace(/\s/g, "");
},
/**
* 转换年月日
* @param date
* @param fmt
* @returns {*|void|string}
*/
dateShow(date,fmt){
let o = {
"M+" : date.getMonth()+1, //月份
"d+" : date.getDate(), //日
"h+" : date.getHours(), //小时
"m+" : date.getMinutes(), //分
"s+" : date.getSeconds(), //秒
"q+" : Math.floor((date.getMonth()+3)/3), //季度
};
if(/(y+)/.test(fmt)) {
fmt=fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length));
}
for(var k in o) {
if(new RegExp("("+ k +")").test(fmt)){
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
}
}
return fmt;
},
/**
* 添加聊天
* @param e
*/
addChats(e){
event.cancelBubble=true; //禁止换行
event.preventDefault();
event.stopPropagation();
this.userState.chatContent = this.userState.chatContent.trim();
chatSubmit(this.userState.nickName,this.userState.chatContent,this.dateShow(new Date(),"yyyy/MM/dd hh:mm:ss"));
this.clearChatContent();
},
/**
* 变成底部
* @returns {Promise<void>}
*/
async scrollToEnd(){
await this.$nextTick();
this.$refs.chatMedia.scrollTop = this.$refs.chatMedia.scrollHeight;
}
},
created初始化 建聊天信息的更新的监听和初始化函数
created(){
onValue(chatsRef,(snapshot)=>{
this.state.chatsList = snapshot.val();
if(this.state.spinning) this.state.spinning=false;
this.scrollToEnd();
})
this.init();
}
index.html 页面
分别创建两个区域 :聊天信息展示区域、输入区域
<div id="index">
<div class="norem-index">
<div class="chat">
<h3 class="chat-title"> LiveChat 聊天室</h3>
<!-- 聊天信息展示区域 -->
<!-- 输入区域 -->
</div>
</div>
</div>
聊天信息展示区域
<div class="chat-media" ref="chatMedia">
<div class="media" v-for="item in state.chatsList">
<div class="media-body">
<p class="media-heading">
{{ dateShow(new Date(item.chatDate),"yyyy/MM/dd hh:mm:ss") }}
<span class="media-name">{{item.nickName}}</span></p>
<p class="media-content">
{{item.chatContent}}
</p>
</div>
</div>
</div>
输入区域
<div class="enter">
<!-- 这里是负责初次进来填写的正常来说不填昵称不让输入的-->
<div class="init-enter" v-if="userState.nickCheckStatus !== 3">
<input class="nick-input" v-model="userState.nickName" @change="checkNickName(userState.nickName)" placeholder="初次见面,请设定您的昵称~"/>
<button :disabled="userState.nickCheckStatus !== 2" @click="addNickName">开始聊天!</button>
</div>
<div class="enter-main" v-if="userState.nickCheckStatus === 3">
<textarea
class="textarea-enter"
v-model="userState.chatContent"
placeholder="你有什么想说的吗? Enter发送"
:rows="5"
@keydown.enter.stop="addChats"
/>
</div>
</div>
这里只是做演示,样式不在这里贴了,下面笔者会贴上github地址用于查看源码。
这里的流程就是:用户需要进来的时候,先输入昵称,验证昵称是否合理,然后再进行聊天。自此我们其实就已经可以查看页面了,已经实现了基本的聊天功能。
拓展
上面只是事先了简单的聊天,但是聊天怎么能只有文字呢,下面会加入一些emoji做为表情库,来为我们的聊天增加易玩度。
firebase添加emojiList
首先在firebase中Realtime Database加入专门存放emoji表情的字段:emojiList
这里需要注意的是,一定要存储成数组,而不是字符串,否则会导致
emoji乱码
firebase.config.js 加入 emoji
获取emoji的引入位置
const emojiRef = ref(database,'emojiList'); //返回对查询位置的引用
导出
加入emoji位置的导出 emojiRef
export {chatsRef ,emojiRef , chatSubmit ,onValue }
index.js 加入 emoji 逻辑
Vue 实例的 data对象 中 state 加入
state: {
emojiList: [],//emoji表情集
prpoverVisible: false, // 气泡控制
},
methods函数中加入
点击对应的emoji添加到输入框中,并关闭表情框。
/**
* emoji表情点击添加
* @param item
*/
emojiAddChat(item){
this.userState.chatContent+=item;
this.state.prpoverVisible=false;
},
created生命周期加入侦听emoji的数据更改。以保证我们只需要更新数据库中的emojiList用户表情框也会实时变化
onValue(emojiRef,(snapshot)=>{
this.state.emojiList = snapshot.val();
})
index.html 加入 emoji 展示区域
找到我们之前添加的输入区域
<div class="enter-main">
添加如下代码
主要就是遍历获取到的emojiList,以及在输入框上层加入emoji选择icon。
<div class="menu">
<div>
<div class="menu-tip" v-show="state.prpoverVisible">
<span class="menu-emoji" @click="emojiAddChat(item)" v-for="item in state.emojiList">
{{item}}
</span>
</div>
<img class="emoji" @click="()=>{state.prpoverVisible=!state.prpoverVisible}" src="https://www.emojiall.com/favicon.ico"/>
</div>
</div>
至此emoji也已加入成功。
结语
这样我们就实现了一个简单的实时聊天,可以根据上述api的去实现更多有意思的功能。如登陆、在线人数、在线列表、讨论高级特工等。
最后这里留下笔者的 成果地址,其中包含V8版本demo 和V9版本demo
希望小伙伴们能点点star支持一下笔者。之后会给大家带来更好的文章!
一起努力一起进步,我是阿江!