基于VUE&Firebase的在线聊天室APP

前言

这一期我们准备做一个基于VUE3.0 Composition API与FireBase的在线聊天室

用户登陆到我们的在线聊天室进行聊天

前期准备

命令行中利用vue-cli创建vue3.0项目

1
2
vue create test 
//test是项目名

在这里插入图片描述

选择手动添加Manually select features

在这里插入图片描述

选择上述勾选项

接下来,选择3.x(preview)因为要用到VUE的composition API

选择with node-sass,如下图所示:

在这里插入图片描述

然后一直enter即可,直到出现package.json,如图所示:

选择In package.json

在这里插入图片描述

出现preset选项,选择否 not save

然后经过漫长的等待…

如果一切顺利,那么会出现下图所示:

在这里插入图片描述

1
2
3
4
5
Successfully created project test.
Get started with the following commands:

$ cd test
$ npm run serve

cd 切换到这个新项目的工作目录。

这个时候我们要安装Firebase数据库用于聊天室中所有消息的存储

  • 注意,Firebase由Google创建并持续维护,安装前请科学上网 ✈ ️
1
npm i firebase

目前为止,我们的前期工作就已经做好了!

然后利用VS Code或者IDEA等编译器导入这个VUE项目即可

配置项目的数据库Firebase

首先,我们需要修改一个配置信息

打开package.json 在”rules”中添加:

1
"no-unused-vars": "off"

因为在做一些初始配置,所以演示的时候,可能会因为定义了未被使用的变量而报错。

在这里插入图片描述

Src 目录下创建db.js firebase配置数据库信息

在这里插入图片描述

进入db.js

输入以下备用代码:

1
2
3
4
5
6
7
8
9
10
11
import firebase from "firebase/app";
import "firebase/database";

const config = {
//API KEYS

}

const db = firebase.initializeApp(config)

export default db;

这里注意到,我们的config中还没有配置我们firebase的接口信息(包括你这个应用的KEY、用户KEYS、项目ID等等)

因此,接下来我们要去firebase的官网配置:

  1. 点击右上角的转到控制台

  2. 点击添加项目并输入项目名称

  3. 取消勾选“为此项目启动 Google Analytics分析”

  4. 等待正在创建项目…

在这里插入图片描述

在这里插入图片描述

  1. 点击对应的项目名,顺利的话,会进入以下画面:

在这里插入图片描述

  1. 添加网页端应用,并获取到应用接口信息

在这里插入图片描述

  1. 输入我们的应用别名,注册应用后,可以获得Firebase SDK (下图所示)

在这里插入图片描述

由于我们已经在项目中 预先安装了firebase 那么就不用最上面的JS引入语句了

JS文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: "AIzaSyAgKkSD7cherkQylfbmrr_cCb6awLk9ynk",
authDomain: "test-dc7e4.firebaseapp.com",
projectId: "test-dc7e4",
storageBucket: "test-dc7e4.appspot.com",
messagingSenderId: "594878054873",
appId: "1:594878054873:web:9d533554407419fe7259c8"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
</script>

我们需要将firebaseConfig中的内容粘贴到,我们的db.js中去:

我的配置信息如下图所示:

在这里插入图片描述

  1. 配置实时数据库 RealTime Database

这一步,是为了查看聊天室中的实时存储的消息。

在这里插入图片描述

导航栏左侧的Realtime Database实时数据库 一步步创建即可

  • 记得数据库位置需要选择美国 US 🇺🇸

那么,到目前为止,数据库配置信息完成了。

进入项目

页面的样式添加

进入初始话的文件,我们将没用的初始文件都删掉:component以及assets中的内容

进入APP.vue中,写好初始页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>

</template>

<script>
export default{
setup(){

return{

}
}
}
</script>

<style lang="scss">

</style>

我们可以发现,与VUE2.0不同的是,我们使用setup来配置初始化信息,并且在初始化之后,return这些信息,让前端页面任何地方可以引用这些数据(比如用于v-model)。

接下来,将以下样式贴到style中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
* {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0;
padding: 0;
box-sizing: border-box;
}
.view {
display: flex;
justify-content: center;
min-height: 100vh;
background-color: #ea526f;

&.login {
align-items: center;
.login-form {
display: block;
width: 100%;
padding: 15px;

.form-inner {
display: block;
background-color: #FFF;
padding: 50px 15px;
border-radius: 16px;
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.2);
h1 {
color: #AAA;
font-size: 28px;
margin-bottom: 30px;
}
label {
display: block;
margin-bottom: 5px;
color: #AAA;
font-size: 16px;
transition: 0.4s;
}
input[type="text"] {
appearance: none;
border: none;
outline: none;
background: none;
display: block;
width: 100%;
padding: 10px 15px;
border-radius: 8px;
margin-bottom: 15px;

color: #333;
font-size: 18px;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
background-color: #F3F3F3;
transition: 0.4s;
&::placeholder {
color: #888;
transition: 0.4s;
}
}
input[type="submit"] {
appearance: none;
border: none;
outline: none;
background: none;
display: block;
width: 100%;
padding: 10px 15px;
background-color: #ea526f;
border-radius: 8px;
color: #FFF;
font-size: 18px;
font-weight: 700;
}
&:focus-within {
label {
color: #ea526f;
}
input[type="text"] {
background-color: #FFF;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
&::placeholder {
color: #666;
}
}
}
}
}
}
&.chat {
flex-direction: column;
header {
position: relative;
display: block;
width: 100%;
padding: 50px 30px 10px;
.logout {
position: absolute;
top: 15px;
right: 15px;
appearance: none;
border: none;
outline: none;
background: none;

color: #FFF;
font-size: 18px;
margin-bottom: 10px;
text-align: right;
}
h1 {
color: #FFF;
}
}
.chat-box {
border-radius: 24px 24px 0px 0px;
background-color: #FFF;
box-shadow: 0px 0px 12px rgba(100, 100, 100, 0.2);
flex: 1 1 100%;
padding: 30px;
.message {
display: flex;
margin-bottom: 15px;

.message-inner {
.username {
color: #888;
font-size: 16px;
margin-bottom: 5px;
padding-left: 15px;
padding-right: 15px;
}
.content {
display: inline-block;
padding: 10px 20px;
background-color: #F3F3F3;
border-radius: 999px;
color: #333;
font-size: 18px;
line-height: 1.2em;
text-align: left;
}
}
&.current-user {
margin-top: 30px;
justify-content: flex-end;
text-align: right;
.message-inner {
max-width: 75%;
.content {
color: #FFF;
font-weight: 600;
background-color: #ea526f;
}
}
}
}
}
footer {
position: sticky;
bottom: 0px;
background-color: #FFF;
padding: 30px;
box-shadow: 0px 0px 12px rgba(100, 100, 100, 0.2);
form {
display: flex;
input[type="text"] {
flex: 1 1 100%;
appearance: none;
border: none;
outline: none;
background: none;
display: block;
width: 100%;
padding: 10px 15px;
border-radius: 8px 0px 0px 8px;

color: #333;
font-size: 18px;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
background-color: #F3F3F3;
transition: 0.4s;
&::placeholder {
color: #888;
transition: 0.4s;
}
}

input[type="submit"] {
appearance: none;
border: none;
outline: none;
background: none;
display: block;
padding: 10px 15px;
border-radius: 0px 8px 8px 0px;
background-color: #ea526f;
color: #FFF;
font-size: 18px;
font-weight: 700;
}
}
}
}
}

登陆模块

  • 定义默认提交进入的方法 @submit.prevent=”Login” 绑定事件

  • 定义登陆时,输入框中输入的信息所绑定的变量(导入vue的ref) const inputUsername = ref(“”);

  • 登录时,将输入框中的值对应的信息保存数据库中,否则下次登陆系统后,以前的信息(包括用户名与消息)都不会保存起来(导入vue的reactive响应变动)

  1. JS中的setup中配置相应的变量与方法(记得返回这些数据让页面显示可以使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { reactive, onMounted, ref } from 'vue';
import db from './db';

export default{
setup () {
//定义输入框输入绑定的变量 以及 保存用户信息的state变量
const inputUsername = ref(""); //初始化为空 相当于vue2.0的 inputUsername: ''
const state = reactive({ //用户发的信息 后面会用到
username: "",
messages: []
});

//定义表单提交绑定的方法
const Login = () => {
//与vue2.0不同的是 我们不再用this.val
//检测到用户输入信息 就存储用户登陆信息
if(inputUsername.value != "" || inputUsername.value != null){
state.username = inputUsername.value;
inputUsername.value = "";
}
}

return{
inputUsername,
Login,
state
}
}
}
  1. 页面显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="view login" v-if="state.username === '' || state.username === null">
<form class="login-form" @submit.prevent="Login">
<div class="form-inner">
<h1>Login to FireChat</h1>
<label for="username">Username</label>
<input
type="text"
v-model="inputUsername"
placeholder="Please enter your username..." />
<input
type="submit"
value="Login" />
</div>
</form>
</div>

<div class="view chat" v-else>
<h1> 这是聊天室显示区域 </h1>
</div>

</template>

用户界面效果图,如下图所示:

在这里插入图片描述

可以看到,我们使用了v-if进行判断

如果存储在state中的用户名信息不为空(即表示用户填入了信息想进入聊天室)

则显示这是聊天室显示区域 否则会显示登陆区域让用户进行登陆(由于是在线聊天室,不设置密码)

  1. 从聊天室中注销出来

刚才我们模拟进入了聊天室,现在我们添加注销功能

1
2
3
4
5
6
<div class="view chat" v-else>
<header>
<button class="logout" @click="Logout">Logout</button>
<h1>Welcome, {{ state.username }}</h1>
</header>
</div>

前端页面写好了,补充JS中的Logout方法,还是写在setup中

1
2
3
4
5
6
7
8
9
10
const Logout = () => {
state.username = "";
}

//别忘了return 让前端页面调用
return{
...Login,
state,
Logout
}

前面提到了,当state中的username为空或者为null时,会显示登陆区域。

  1. 为聊天室添加(发送消息,查看消息功能)

这是最后一个步骤了,也是需要用到firebase(特别是我们刚才配置的实时数据库)的一步

首先在刚才注销区域<header>标签 的下方,加入代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<section class="chat-box">
<div
v-for="message in state.messages"
:key="message.key"
:class="(message.username == state.username ? 'message current-user' : 'message')">
<div class="message-inner">
<div class="username">{{ message.username }}</div>
<div class="content">{{ message.content }}</div>
</div>
</div>
</section>

<footer>
<form @submit.prevent="SendMessage">
<input
type="text"
v-model="inputMessage"
placeholder="Write a message..." />
<input
type="submit"
value="Send" />
</form>
</footer>

可以看到,我们用到了SendMessage方法用来发送数据

  • 用到了state中的messages并且将当前用户的前端样式区别于其他用户

  • 并且从messages中取出所有消息 利用v-for在前端中显示

  • 输入的消息绑定v-model inputMessage

那么,下面就可以定义消息变量以及相应发送消息的函数!

还是写在setup中 并注意return给前端使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//const inputUsername = ref("");
const inputMessage = ref("");

const SendMessage = () => {
const messagesRef = db.database().ref("messages"); //firebase数据库
if(inputMessage.value === "" || inputMessage.value === value){ //用户不能发送空消息
return;
}

const message = { //准备将发送的消息存储至数据库中
username: stae.username,
content: inputMessage.value
}
messageRef.push(message); //相当于MySQL中的insert firebase是基于JSON的 故用push
inputMessage.value = "";
}

那么,到目前位置,我们写入数据库的操作就写完了。

在这里插入图片描述

  • 但是现在是无法实时看到,任何用户发送的消息的,为什么?

  • 因为,我们虽然存储到了firebase数据库中去,但是我们每次v-for取出的数据,是从state中取出的

因此,下一步我们需要,在进入聊天室时,将数据库中的数据赋给state

  1. firebase检索数据(写在onMounted钩子函数中)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
onMounted(() => {
const messagesRef = db.database().ref("messages"); //选择数据库
messagesRef.on('value', snapshot => { //on 用于firebase数据库检索数据
const data = snapshot.val();
let messages = [];
Object.keys(data).forEach(key => {
messages.push({
id: key,
username: data[key].username,
content: data[key].content
});
});
state.messages = messages;
});
});

其中,data是我们从数据库中查询到的所有数据。

如果我们试着打印以下内容,你会发现:

在这里插入图片描述

因为,Firebase中存储的信息都是JSON对象,那么,我们就要用相应的方法来遍历每个对象,并将所有对象存储在messages中

最后将messages的内容,赋给我们的state 好让我们的v-for循环遍历数据可以生效!

并且值得注意的是,每次有新的内容添加至数据库时(即每次发送消息时)

  • 这个messagesRef.on(‘value’, …) 都会被调用一次,我们可以看成是事件监听(每当有新的数据时,会被调用!)

文末总结

P.S. 该文所有的代码以及教程均来自YT博主(非自己原创)只不过自己以文字思路的形式写成笔记而已

  1. 点我查看源码地址

  2. 点我查看视频教学

如果哪里有写的不对的地方,多多包涵,请于文章下方的评论区告知,谢谢!