使用Pusher和Vue.js构建实时聊天应用

     如今,实时通信的应用程序越来越流畅,用户体验也变得越来越流行。

     在本教程中,我们将使用由Chater提供的服务ChatKit提供支持的Vue.js构建实时聊天应用程序。 ChatKit服务将为我们提供在任何设备上构建聊天应用程序所需的完整后端,使我们专注于构建通过ChatKit客户端软件包连接到ChatKit服务的前端用户界面。 

    想从头开始学习Vue.js吗? 借助SitePoint Premium,可以获取有关基础知识,项目,技巧和工具及更多内容的Vue图书的完整集合。 立即加入,每月只需$ 9。 

先决条件 :这是中级到高级教程。 您需要先熟悉以下概念:

Vue.js基础
Vuex基础
采用CSS框架

  您还需要在计算机上安装Node。 您可以通过从官方网站下载二进制文件或使用版本管理器来执行此操作。 这可能是最简单的方法,因为它允许您在同一台计算机上管理多个版本的Node。

最后,您需要使用以下命令在全球范围内安装Vue CLI:

npm install -g @vue/cli

在撰写本文时,Node 10.14.1和Vue CLI 3.2.1是最新版本。

关于该项目
我们将构建一个类似于Slack或Discord的基本聊天应用程序。 该应用程序将执行以下操作:

有多个频道和房间
列出会议室成员并检测状态
检测其他用户何时开始输入
如前所述,我们只是在构建前端。 ChatKit服务具有一个后端界面,该界面使我们可以管理用户,权限和房间。

您可以在GitHub上找到该项目的完整代码。https://github.com/sitepoint-editors/vue-chatkit

设置一个ChatKit实例
让我们创建一个ChatKit实例,如果您熟悉Discord,它类似于服务器实例。

前往Pusher网站上的ChatKit页面,然后单击“注册”按钮。 系统会提示您输入电子邮件地址和密码,并提供使用GitHub或Google登录的选项。

选择最适合您的选项,然后在下一个屏幕上填写一些详细信息,例如姓名,帐户类型,用户角色等。

点击完成入门,您将进入主Pusher仪表板。 在这里,您应该单击ChatKit产品。

单击创建按钮创建一个新的ChatKit实例。 我要打电话给我的VueChatTut。

 

 

本教程将使用免费计划。 它最多支持1,000个唯一用户,足以满足我们的需求。 转到控制台选项卡。 您需要创建一个新用户才能上手。 继续并单击创建用户按钮。 

 

我将其命名为“ john”(用户标识符)和“ John Wick”(显示名称),但是您可以根据需要命名。 下一部分很简单:创建两个或更多用户。 例如:

盐伊芙琳盐
亨特(Ethan Hunt)
创建三个或更多房间并分配用户。 例如:

将军(约翰,盐,狩猎)
武器(约翰,盐)
战斗(约翰·亨特)
以下是控制台界面的快照。

 

接下来,您可以转到“房间”选项卡,并使用选定的用户为每个房间创建一条消息。 这是出于测试目的。 然后转到“凭据”选项卡,并记下“实例定位器”。 我们需要激活用于生成我们的HTTP端点的测试令牌提供程序,并且还要注意这一点。

 

我们的ChatKit后端现已准备就绪。 让我们开始构建我们的Vue.js前端。

搭建Vue.js项目
打开您的终端并按照以下步骤创建项目:

vue create vue-chatkit

选择手动选择功能并回答以下问题。

 

请确保您已选择Babel,Vuex和Vue路由器作为其他功能。 接下来,如下创建以下文件夹和文件:

 

确保按照演示创建所有文件夹和文件。 删除上图中没有出现的所有不必要的文件。

对于那些在家中使用控制台的人,以下是执行所有操作的命令:

mkdir src/assets/css
mkdir src/store

touch src/assets/css/{loading.css,loading-btn.css}
touch src/components/{ChatNavBar.vue,LoginForm.vue,MessageForm.vue,MessageList.vue,RoomList.vue,UserList.vue}
touch src/store/{actions.js,index.js,mutations.js}
touch src/views/{ChatDashboard.vue,Login.vue}
touch src/chatkit.js

rm src/components/HelloWorld.vue
rm src/views/{About.vue,Home.vue}
rm src/store.js

 

完成后,src文件夹的内容应如下所示:

.
├── App.vue
├── assets
│   ├── css
│   │   ├── loading-btn.css
│   │   └── loading.css
│   └── logo.png
├── chatkit.js
├── components
│   ├── ChatNavBar.vue
│   ├── LoginForm.vue
│   ├── MessageForm.vue
│   ├── MessageList.vue
│   ├── RoomList.vue
│   └── UserList.vue
├── main.js
├── router.js
├── store
│   ├── actions.js
│   ├── index.js
│   └── mutations.js
└── views
    ├── ChatDashboard.vue
    └── Login.vue

 

对于loading-btn.css和loading.css文件,可以在loading.io网站上找到它们。 这些文件在npm存储库中不可用,因此您需要手动下载它们并将其放置在项目中。 确保确保阅读文档以了解它们是什么以及如何使用可自定义的加载程序。

接下来,我们将安装以下依赖项:

@ pusher / chatkit-client,ChatKit服务的实时客户端界面
bootstrap-vue,一个CSS框架
时刻,日期和时间格式化实用程序
vue-chat-scroll,添加新内容后会自动滚动到底部
vuex-persist,将Vuex状态保存在浏览器的本地存储中

npm i @pusher/chatkit-client bootstrap-vue moment vue-chat-scroll vuex-persist

 

请检查链接,以了解有关每个软件包的功能以及如何配置的更多信息。

现在,让我们配置Vue.js项目。 打开src / main.js并更新代码,如下所示:

import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import VueChatScroll from 'vue-chat-scroll'

import App from './App.vue'
import router from './router'
import store from './store/index'

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import './assets/css/loading.css'
import './assets/css/loading-btn.css'

Vue.config.productionTip = false
Vue.use(BootstrapVue)
Vue.use(VueChatScroll)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

 如下更新src / router.js:

import Vue from 'vue'
import Router from 'vue-router'
import Login from './views/Login.vue'
import ChatDashboard from './views/ChatDashboard.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'login',
      component: Login
    },
    {
      path: '/chat',
      name: 'chat',
      component: ChatDashboard,
    }
  ]
})

 更新src / store / index.js:

import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
import mutations from './mutations'
import actions from './actions'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

const vuexLocal = new VuexPersistence({
  storage: window.localStorage
})

export default new Vuex.Store({
  state: {
  },
  mutations,
  actions,
  getters: {
  },
  plugins: [vuexLocal.plugin],
  strict: debug
})

vuex-persist软件包可确保在页面重新加载或刷新之间保存我们的Vuex状态。

我们的项目现在应该能够正确编译。 但是,暂时不要运行它,因为我们需要构建用户界面。

构建UI界面
首先,按照以下步骤更新src / App.vue:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

 接下来,我们需要定义UI组件正常工作所需的Vuex存储状态。 我们将转到src / store / index.js中的Vuex商店来完成此操作。 只需更新state和getters部分,如下所示:

state: {
  loading: false,
  sending: false,
  error: null,
  user: [],
  reconnect: false,
  activeRoom: null,
  rooms: [],
  users: [],
  messages: [],
  userTyping: null
},
getters: {
  hasError: state => state.error ? true : false
},

 

这些都是聊天应用程序所需的所有状态变量。 UI使用加载状态来确定是否应运行CSS加载器。 错误状态用于存储刚刚发生的错误的信息。 当我们跨过状态变量时,我们将讨论其余的状态变量。

接下来打开src / view / Login.vue并更新如下:

<template>
  <div class="login">
    <b-jumbotron  header="Vue.js Chat"
                  lead="Powered by Chatkit SDK and Bootstrap-Vue"
                  bg-variant="info"
                  text-variant="white">
      <p>For more information visit website</p>
      <b-btn target="_blank" href="https://pusher.com/chatkit">More Info</b-btn>
    </b-jumbotron>
    <b-container>
      <b-row>
        <b-col lg="4" md="3"></b-col>
        <b-col lg="4" md="6">
          <LoginForm />
        </b-col>
        <b-col lg="4" md="3"></b-col>
      </b-row>
    </b-container>
  </div>
</template>

<script>
import LoginForm from '@/components/LoginForm.vue'

export default {
  name: 'login',
  components: {
    LoginForm
  }
}
</script>

接下来,为src / components / LoginForm.vue插入代码,如下所示:

<template>
  <div class="login-form">
    <h5 class="text-center">Chat Login</h5>
    <hr>
    <b-form @submit.prevent="onSubmit">
       <b-alert variant="danger" :show="hasError">{{ error }} </b-alert>

      <b-form-group id="userInputGroup"
                    label="User Name"
                    label-for="userInput">
        <b-form-input id="userInput"
                      type="text"
                      placeholder="Enter user name"
                      v-model="userId"
                      autocomplete="off"
                      :disabled="loading"
                      required>
        </b-form-input>
      </b-form-group>

      <b-button type="submit"
                variant="primary"
                class="ld-ext-right"
                v-bind:class="{ running: loading }"
                :disabled="isValid">
                Login <div class="ld ld-ring ld-spin"></div>
      </b-button>
    </b-form>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  name: 'login-form',
  data() {
    return {
      userId: '',
    }
  },
  computed: {
    isValid: function() {
      const result = this.userId.length < 3;
      return result ? result : this.loading
    },
    ...mapState([
      'loading',
      'error'
    ]),
    ...mapGetters([
      'hasError'
    ])
  }
}
</script>

如前所述,这是高级教程。 如果您在此处无法理解任何代码,请转至先决条件或项目依赖项以获取信息。

现在,我们可以通过npm run serve启动Vue开发服务器,以确保我们的应用程序运行时没有任何编译问题。

 

 

您可以通过输入用户名来确认验证工作正常。 输入三个字符后,您应该看到“登录”按钮被激活。 登录按钮暂时无法使用,因为我们尚未对该部分进行编码。 我们待会再调查。 现在,让我们继续构建我们的聊天用户界面。

转到src / view / ChatDashboard.vue并按如下所示插入代码:

<template>
  <div class="chat-dashboard">
    <ChatNavBar />
    <b-container fluid class="ld-over" v-bind:class="{ running: loading }">
      <div class="ld ld-ring ld-spin"></div>
      <b-row>
        <b-col cols="2">
          <RoomList />
        </b-col>

        <b-col cols="8">
          <b-row>
            <b-col id="chat-content">
              <MessageList />
            </b-col>
          </b-row>
          <b-row>
            <b-col>
              <MessageForm />
            </b-col>
          </b-row>
        </b-col>

        <b-col cols="2">
          <UserList />
        </b-col>
      </b-row>
    </b-container>
  </div>
</template>

<script>
import ChatNavBar from '@/components/ChatNavBar.vue'
import RoomList from '@/components/RoomList.vue'
import MessageList from '@/components/MessageList.vue'
import MessageForm from '@/components/MessageForm.vue'
import UserList from '@/components/UserList.vue'
import { mapState } from 'vuex';

export default {
  name: 'Chat',
  components: {
    ChatNavBar,
    RoomList,
    UserList,
    MessageList,
    MessageForm
  },
  computed: {
    ...mapState([
      'loading'
    ])
  }
}
</script>

 

ChatDashboard将充当以下子组件的布局父对象:


报表广告
ChatNavBar,基本的导航栏
RoomList,列出已登录用户有权访问的房间,它也是房间选择器
UserList,列出选定房间的成员
MessageList,显示在选定房间中发布的消息
MessageForm,用于将消息发送到所选房间的表单
让我们在每个组件中添加一些样板代码,以确保所有内容都能显示出来。

插入src / components / ChatNavBar.vue的样板代码,如下所示:

<template>
  <b-navbar id="chat-navbar" toggleable="md" type="dark" variant="info">
    <b-navbar-brand href="#">
      Vue Chat
    </b-navbar-brand>
    <b-navbar-nav class="ml-auto">
      <b-nav-text>{{ user.name }} | </b-nav-text>
      <b-nav-item href="#" active>Logout</b-nav-item>
    </b-navbar-nav>
  </b-navbar>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'ChatNavBar',
  computed: {
    ...mapState([
      'user',
    ])
  },
}
</script>

<style>
  #chat-navbar {
    margin-bottom: 15px;
  }
</style>

插入src / components / RoomList.vue的样板代码,如下所示:

<template>
  <div class="room-list">
    <h4>Channels</h4>
    <hr>
    <b-list-group v-if="activeRoom">
      <b-list-group-item v-for="room in rooms"
                        :key="room.name"
                        :active="activeRoom.id === room.id"
                        href="#"
                        @click="onChange(room)">
        # {{ room.name }}
      </b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'RoomList',
  computed: {
    ...mapState([
      'rooms',
      'activeRoom'
    ]),
  }
}
</script>

插入src / components / UserList.vue的样板代码,如下所示:

<template>
  <div class="user-list">
    <h4>Members</h4>
    <hr>
    <b-list-group>
      <b-list-group-item v-for="user in users" :key="user.username">
        {{ user.name }}
        <b-badge v-if="user.presence"
        :variant="statusColor(user.presence)"
        pill>
        {{ user.presence }}</b-badge>
      </b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'user-list',
  computed: {
    ...mapState([
      'loading',
      'users'
    ])
  },
  methods: {
    statusColor(status) {
      return status === 'online' ? 'success' : 'warning'
    }
  }
}
</script>

插入src / components / MessageList.vue的样板代码,如下所示:

<template>
  <div class="message-list">
    <h4>Messages</h4>
    <hr>
    <div id="chat-messages" class="message-group" v-chat-scroll="{smooth: true}">
      <div class="message" v-for="(message, index) in messages" :key="index">
        <div class="clearfix">
          <h4 class="message-title">{{ message.name }}</h4>
          <small class="text-muted float-right">@{{ message.username }}</small>
        </div>
        <p class="message-text">
          {{ message.text }}
        </p>
        <div class="clearfix">
          <small class="text-muted float-right">{{ message.date }}</small>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'message-list',
  computed: {
    ...mapState([
      'messages',
    ])
  }
}
</script>

<style>
.message-list {
  margin-bottom: 15px;
  padding-right: 15px;
}
.message-group {
  height: 65vh !important;
  overflow-y: scroll;
}
.message {
  border: 1px solid lightblue;
  border-radius: 4px;
  padding: 10px;
  margin-bottom: 15px;
}
.message-title {
  font-size: 1rem;
  display:inline;
}
.message-text {
  color: gray;
  margin-bottom: 0;
}
.user-typing {
  height: 1rem;
}
</style>

插入src / components / MessageForm.vue的样板代码,如下所示:

<template>
  <div class="message-form ld-over">
    <small class="text-muted">@{{ user.username }}</small>
    <b-form @submit.prevent="onSubmit" class="ld-over" v-bind:class="{ running: sending }">
      <div class="ld ld-ring ld-spin"></div>
      <b-alert variant="danger" :show="hasError">{{ error }} </b-alert>
      <b-form-group>
        <b-form-input id="message-input"
                      type="text"
                      v-model="message"
                      placeholder="Enter Message"
                      autocomplete="off"
                      required>
        </b-form-input>
      </b-form-group>
      <div class="clearfix">
        <b-button type="submit" variant="primary" class="float-right">
          Send
        </b-button>
      </div>
    </b-form>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  name: 'message-form',
  data() {
    return {
      message: ''
    }
  },
  computed: {
    ...mapState([
      'user',
      'sending',
      'error',
      'activeRoom'
    ]),
    ...mapGetters([
      'hasError'
    ])
  }
}
</script>

仔细阅读代码,以确保一切对您而言都是个谜。 导航到http:// localhost:8080 / chat以检查是否一切都在运行。 检查终端和浏览器控制台,以确保此时没有错误。 您现在应该具有以下视图。

 

很空吧? 让我们转到src / store / index.js并在状态中插入一些模拟数据:

state: {
  loading: false,
  sending: false,
  error: 'Relax! This is just a drill error message',
  user: {
    username: 'Jack',
    name: 'Jack Sparrow'
  },
  reconnect: false,
  activeRoom: {
    id: '124'
  },
  rooms: [
    {
      id: '123',
      name: 'Ships'
    },
    {
      id: '124',
      name: 'Treasure'
    }
  ],
  users: [
    {
      username: 'Jack',
      name: 'Jack Sparrow',
      presence: 'online'
    },
    {
      username: 'Barbossa',
      name: 'Hector Barbossa',
      presence: 'offline'
    }
  ],
  messages: [
    {
      username: 'Jack',
      date: '11/12/1644',
      text: 'Not all treasure is silver and gold mate'
    },
    {
      username: 'Jack',
      date: '12/12/1644',
      text: 'If you were waiting for the opportune moment, that was it'
    },
    {
      username: 'Hector',
      date: '12/12/1644',
      text: 'You know Jack, I thought I had you figured out'
    }
  ],
  userTyping: null
},

保存文件后,您的视图应与下图匹配。

 

这个简单的测试可确保所有组件和状态都很好地捆绑在一起。 现在,您可以将状态代码恢复为原始形式:

state: {
  loading: false,
  sending: false,
  error: null,
  user: null,
  reconnect: false,
  activeRoom: null,
  rooms: [],
  users: [],
  messages: [],
  userTyping: null
}

让我们从登录表单开始实施具体功能。

无密码认证
在本教程中,我们将采用无密码的非安全身份验证系统。 适当的安全身份验证系统不在本教程的讨论范围之内。 首先,我们需要开始构建自己的界面,该界面将通过@ pusher / chatkit-client软件包与ChatKit服务进行交互。

返回到ChatKit仪表板并复制实例和测试令牌参数。 将它们保存在项目根目录下的.env.local文件中,如下所示:

VUE_APP_INSTANCE_LOCATOR=
VUE_APP_TOKEN_URL=
VUE_APP_MESSAGE_LIMIT=10

 

我还添加了一个MESSAGE_LIMIT参数。 该值仅限制了我们的聊天应用程序可获取的消息数。 确保在“凭据”选项卡中填写其他参数。

接下来,转到src / chatkit.js开始构建我们的聊天应用程序基础:

import { ChatManager, TokenProvider } from '@pusher/chatkit-client'

const INSTANCE_LOCATOR = process.env.VUE_APP_INSTANCE_LOCATOR;
const TOKEN_URL = process.env.VUE_APP_TOKEN_URL;
const MESSAGE_LIMIT = Number(process.env.VUE_APP_MESSAGE_LIMIT) || 10;

let currentUser = null;
let activeRoom = null;

async function connectUser(userId) {
  const chatManager = new ChatManager({
    instanceLocator: INSTANCE_LOCATOR,
    tokenProvider: new TokenProvider({ url: TOKEN_URL }),
    userId
  });
  currentUser = await chatManager.connect();
  return currentUser;
}

export default {
  connectUser
}

 

请注意,我们正在将MESSAGE_LIMIT常量强制转换为数字,因为默认情况下,process.env对象将其所有属性强制为字符串类型。

为src / store / mutations插入以下代码:

export default {
  setError(state, error) {
    state.error = error;
  },
  setLoading(state, loading) {
    state.loading = loading;
  },
  setUser(state, user) {
    state.user = user;
  },
  setReconnect(state, reconnect) {
    state.reconnect = reconnect;
  },
  setActiveRoom(state, roomId) {
    state.activeRoom = roomId;
  },
  setRooms(state, rooms) {
    state.rooms = rooms
  },
  setUsers(state, users) {
    state.users = users
  },
 clearChatRoom(state) {
    state.users = [];
    state.messages = [];
  },
  setMessages(state, messages) {
    state.messages = messages
  },
  addMessage(state, message) {
    state.messages.push(message)
  },
  setSending(state, status) {
    state.sending = status
  },
  setUserTyping(state, userId) {
    state.userTyping = userId
  },
  reset(state) {
    state.error = null;
    state.users = [];
    state.messages = [];
    state.rooms = [];
    state.user = null
  }
}

 

 突变的代码非常简单-只是一堆设置器。 您将很快在后面的部分中了解每个突变功能的作用。 接下来,使用以下代码更新src / store / actions.js:

import chatkit from '../chatkit';

// Helper function for displaying error messages
function handleError(commit, error) {
  const message = error.message || error.info.error_description;
  commit('setError', message);
}

export default {
  async login({ commit, state }, userId) {
    try {
      commit('setError', '');
      commit('setLoading', true);
      // Connect user to ChatKit service
      const currentUser = await chatkit.connectUser(userId);
      commit('setUser', {
        username: currentUser.id,
        name: currentUser.name
      });
      commit('setReconnect', false);

      // Test state.user
      console.log(state.user);
    } catch (error) {
      handleError(commit, error)
    } finally {
      commit('setLoading', false);
    }
  }
}

 

接下来,如下更新src / components / LoginForm.vue:

import { mapState, mapGetters, mapActions } from 'vuex'

//...
export default {
  //...
  methods: {
    ...mapActions([
      'login'
    ]),
    async onSubmit() {
      const result = await this.login(this.userId);
      if(result) {
        this.$router.push('chat');
      }
    }
  }
}

您必须重新启动Vue.js服务器才能加载env.local数据。 如果看到有关未使用变量的任何错误,请暂时将其忽略。 完成此操作后,导航至http:// localhost:8080 /并测试登录功能:

 

在上面的示例中,我使用了错误的用户名,只是为了确保错误处理功能正常运行。

 

 

 在此屏幕截图中,我使用了正确的用户名。 我还打开了浏览器控制台标签,以确保已填充用户对象。 更妙的是,如果您在Chrome或Firefox中安装了Vue.js开发工具,则应该能够看到更多详细信息。

 

如果目前一切正常,请继续执行下一步。

订阅房间
现在我们已经成功验证了登录功能是否正常,我们需要将用户重定向到ChatDashboard视图。 代码this。$ router.push('chat'); 为我们做到这一点。 但是,我们的操作登录名需要返回一个布尔值,以确定何时可以导航到ChatDashboard视图。 我们还需要使用来自ChatKit服务的实际数据填充RoomList和UserList组件。

如下更新src / chatkit.js:

//...
import moment from 'moment'
import store from './store/index'

//...
function setMembers() {
  const members = activeRoom.users.map(user => ({
    username: user.id,
    name: user.name,
    presence: user.presence.state
  }));
  store.commit('setUsers', members);
}

async function subscribeToRoom(roomId) {
  store.commit('clearChatRoom');
  activeRoom = await currentUser.subscribeToRoom({
    roomId,
    messageLimit: MESSAGE_LIMIT,
    hooks: {
      onMessage: message => {
        store.commit('addMessage', {
          name: message.sender.name,
          username: message.senderId,
          text: message.text,
          date: moment(message.createdAt).format('h:mm:ss a D-MM-YYYY')
        });
      },
      onPresenceChanged: () => {
        setMembers();
      },
      onUserStartedTyping: user => {
        store.commit('setUserTyping', user.id)
      },
      onUserStoppedTyping: () => {
        store.commit('setUserTyping', null)
      }
    }
  });
  setMembers();
  return activeRoom;
}

export default {
  connectUser,
  subscribeToRoom
}

 

如果您查看钩子部分,那么ChatKit服务将使用事件处理程序与客户端应用程序进行通信。 您可以在此处找到完整的文档。 我将快速总结每种钩子方法的目的:

onMessage接收消息
当用户登录或注销时,onPresenceChanged会收到一个事件
onUserStartedTyping接收到用户正在键入的事件
onUserStoppedTyping收到用户停止输入的事件
为了使onUserStartedTyping正常工作,我们需要在用户键入时从MessageForm发出键入事件。 我们将在下一部分中对此进行研究。

使用以下代码更新src / store / actions.js中的登录功能:

//...
try {
  //... (place right after the `setUser` commit statement)
  // Save list of user's rooms in store
  const rooms = currentUser.rooms.map(room => ({
    id: room.id,
    name: room.name
  }))
  commit('setRooms', rooms);

  // Subscribe user to a room
  const activeRoom = state.activeRoom || rooms[0]; // pick last used room, or the first one
  commit('setActiveRoom', {
    id: activeRoom.id,
    name: activeRoom.name
  });
  await chatkit.subscribeToRoom(activeRoom.id);

  return true;
} catch (error) {
  //...
}

保存代码后,返回登录屏幕并输入正确的用户名。 您应该进入以下屏幕。

 

真好!几乎所有组件都可以正常工作,因为我们已将它们正确连接到Vuex商店。尝试通过ChatKit的信息中心控制台界面发送消息。创建一条消息并将其发布到常规室。您应该看到新消息自动在MessageList组件中弹出。很快,我们将实现从Vue.js应用发送消息的逻辑。

如果您遇到问题
如果遇到问题,请尝试以下操作:

重新启动Vue.js服务器
清除浏览器缓存
进行硬重置/刷新(如果“控制台”标签处于打开状态并且按住“重新加载”按钮五秒钟,则在Chrome中可用)
使用浏览器控制台清除localStorage
如果到目前为止一切正常,请继续执行下一部分,在该部分中实现更改房间的逻辑。

更衣室
这部分非常简单,因为我们已经奠定了基础。首先,我们将创建一个操作,允许用户更改房间。转到src / store / actions.js并在登录操作处理程序之后添加此函数:

async changeRoom({ commit }, roomId) {
  try {
    const { id, name } = await chatkit.subscribeToRoom(roomId);
    commit('setActiveRoom', { id, name });
  } catch (error) {
    handleError(commit, error)
  }
},

接下来,转到src / components / RoomList.vue并按如下所示更新脚本部分:

import { mapState, mapActions } from 'vuex'
//...
export default {
  //...
  methods: {
    ...mapActions([
      'changeRoom'
    ]),
    onChange(room) {
      this.changeRoom(room.id)
    }
  }
}

您还记得吗,我们已经在b-list-group-item元素中定义了@ click =“ onChange(room)”。 让我们通过单击RoomList组件中的项目来测试这项新功能。

 

您的用户界面应随房间的每次点击而更新。 MessageList和UserList组件应显示所选房间的正确信息。在下一节中,我们将一次实现多种功能。

页面刷新后重新连接用户
您可能已经注意到,对store / index.js进行某些更改或刷新页面时,会出现以下错误:无法读取null的属性'subscribeToRoom'。发生这种情况是因为您的应用程序状态被重置。幸运的是,vuex-persist软件包通过将页面保存在浏览器的本地存储中来维护页面重新加载之间的Vuex状态。

不幸的是,将我们的应用程序连接到ChatKit服务器的引用被重置为null。要解决此问题,我们需要执行重新连接操作。我们还需要一种方法来告诉我们的应用程序刚刚发生了页面重新加载,并且我们的应用程序需要重新连接才能继续正常运行。我们将在src / components / ChatNavbar.vue中实现此代码。如下更新脚本部分:

<script>
import { mapState, mapActions, mapMutations } from 'vuex'

export default {
  name: 'ChatNavBar',
  computed: {
    ...mapState([
      'user',
       'reconnect'
    ])
  },
  methods: {
    ...mapActions([
      'logout',
      'login'
    ]),
    ...mapMutations([
      'setReconnect'
    ]),
    onLogout() {
      this.$router.push({ path: '/' });
      this.logout();
    },
    unload() {
      if(this.user.username) { // User hasn't logged out
        this.setReconnect(true);
      }
    }
  },
  mounted() {
    window.addEventListener('beforeunload', this.unload);
    if(this.reconnect) {
      this.login(this.user.username);
    }
  }
}
</script>

让我分解事件的顺序,以便您了解重新连接到ChatKit服务背后的逻辑:

卸下。页面刷新发生时,将调用此方法。它首先检查状态user.username已设置。如果已注册,则意味着用户尚未注销。状态重新连接设置为true。
已安装。每当ChatNavbar.vue刚完成渲染时,都会调用此方法。它首先为事件监听器分配一个处理程序,该事件监听器在页面卸载之前被调用。它还会检查state.reconnect是否已设置为true。如果是这样,则执行登录过程,从而将我们的聊天应用程序重新连接回我们的ChatKit服务。
我还添加了注销功能,稍后我们将进行研究。

进行这些更改后,请尝试刷新页面。您会看到页面自动更新,因为它会在后台执行重新连接过程。当您切换房间时,它应该可以正常工作。

发送消息,检测用户键入并注销
让我们从添加以下代码开始在src / chatkit.js中实现这些功能:

//...
async function sendMessage(text) {
  const messageId = await currentUser.sendMessage({
    text,
    roomId: activeRoom.id
  });
  return messageId;
}

export function isTyping(roomId) {
  currentUser.isTypingIn({ roomId });
}

function disconnectUser() {
  currentUser.disconnect();
}

export default {
  connectUser,
  subscribeToRoom,
  sendMessage,
  disconnectUser
}

 

 

虽然sendMessage和断开用户功能将捆绑在ChatKit的模块导出中,但isTyping函数将单独导出。 这是为了允许MessageForm直接发送键入事件,而无需涉及Vuex存储。

对于sendMessage和disconnectUser,我们需要更新商店,以处理错误处理和加载状态通知之类的事情。 转到src / store / actions.js并在changeRoom函数之后插入以下代码:

async sendMessage({ commit }, message) {
  try {
    commit('setError', '');
    commit('setSending', true);
    const messageId = await chatkit.sendMessage(message);
    return messageId;
  } catch (error) {
    handleError(commit, error)
  } finally {
    commit('setSending', false);
  }
},
async logout({ commit }) {
  commit('reset');
  chatkit.disconnectUser();
  window.localStorage.clear();
}

对于注销功能,我们调用commit('reset')将我们的商店重置为其原始状态。 这是一项基本的安全功能,可以从浏览器缓存中删除用户信息和消息。

首先,通过添加@input指令来更新src / components / MessageForm.vue中的表单输入以发出键入事件:

<b-form-input id="message-input"
              type="text"
              v-model="message"
              @input="isTyping"
              placeholder="Enter Message"
              autocomplete="off"
              required>
</b-form-input>

 

现在,让我们更新src / components / MessageForm.vue的脚本部分,以处理消息发送和发出键入事件。 更新如下:

<script>
import { mapActions, mapState, mapGetters } from 'vuex'
import { isTyping } from '../chatkit.js'

export default {
  name: 'message-form',
  data() {
    return {
      message: ''
    }
  },
  computed: {
    ...mapState([
      'user',
      'sending',
      'error',
      'activeRoom'
    ]),
    ...mapGetters([
      'hasError'
    ])
  },
  methods: {
    ...mapActions([
      'sendMessage',
    ]),
    async onSubmit() {
      const result = await this.sendMessage(this.message);
      if(result) {
        this.message = '';
      }
    },
     async isTyping() {
      await isTyping(this.activeRoom.id);
    }
  }
}
</script>

 

 并在src / MessageList.vue中:

import { mapState } from 'vuex'

export default {
  name: 'message-list',
  computed: {
    ...mapState([
      'messages',
      'userTyping'
    ])
  }
}

现在,发送消息功能应该可以使用了。 为了显示另一个用户正在输入的通知,我们需要添加一个元素来显示此信息。 在消息组div之后的src / components / MessageList.vue的模板部分中添加以下代码段:

<div class="user-typing">
  <small class="text-muted" v-if="userTyping">@{{ userTyping }} is typing....</small>
</div>

 要测试此功能,只需使用其他浏览器以其他用户身份登录并开始输入即可。 您应该会在另一用户的聊天窗口中看到一条通知。

 

让我们通过实现最后一个功能注销来结束本教程。 我们的Vuex商店已经具有必要的代码来处理注销过程。 我们只需要更新src / components / ChatNavBar.vue。 只需将注销按钮与我们之前指定的函数处理程序onLogout链接即可:

 <b-nav-item href="#" @click="onLogout" active>Logout</b-nav-item>

 而已。 现在,您可以注销并以其他用户身份再次登录。

 

摘要
现在,我们到了教程的结尾。 ChatKit API使我们能够在短时间内快速构建聊天应用程序。 如果我们要从头开始构建类似的应用程序,则可能要花几个星期,因为我们还必须充实后端。 此解决方案的优点在于,我们不必处理托管,管理数据库和其他基础结构问题。 我们可以简单地将前端代码构建和部署到Web,Android和IOS平台上的客户端设备。

请仔细阅读文档,因为本教程中没有大量后端功能。 如果有时间,您可以轻松构建一个功能强大的聊天应用程序,该应用程序可以与Slack和Discord等流行的聊天产品匹敌。

转自https://www.sitepoint.com/javascript-tooling-evolution-modern-developers-guide/

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值