瀏覽代碼

测试store封装

wangwei 4 年之前
父節點
當前提交
d95e3930f9

+ 21 - 0
package-lock.json

@@ -5940,6 +5940,11 @@
       "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
       "dev": true
     },
+    "immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+    },
     "import-from": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz",
@@ -7195,6 +7200,14 @@
         "type-check": "~0.3.2"
       }
     },
+    "lie": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+      "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+      "requires": {
+        "immediate": "~3.0.5"
+      }
+    },
     "load-json-file": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -7247,6 +7260,14 @@
         }
       }
     },
+    "localforage": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz",
+      "integrity": "sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==",
+      "requires": {
+        "lie": "3.1.1"
+      }
+    },
     "locate-path": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "axios": "^0.21.1",
     "element-ui": "^2.15.3",
     "js-cookie": "^3.0.0",
+    "localforage": "^1.9.0",
     "socket.io-client": "^4.1.3",
     "socketio-file-upload": "^0.7.3",
     "v-contextmenu": "^3.0.0",

+ 27 - 0
src/common/index.js

@@ -0,0 +1,27 @@
+/*
+ * @file: 默认群组,需结合后台 (用户新建后默认进入该群组)
+ */
+
+// 默认群组Id
+let bg = '';
+if (process.env.VUE_APP_CDN !== 'true' && process.env.NODE_ENV === 'production') {
+  // 默认背景图片
+  bg = 'http://11.176.37.20:8090/img/secret.jpg';
+} else {
+  bg = 'https://pic.downk.cc/item/5fc744ea394ac5237897a81d.jpg';
+}
+
+export const DEFAULT_GROUP = 'group';
+
+export const DEFAULT_BACKGROUND = bg;
+// 默认机器人Id
+export const DEFAULT_ROBOT = 'robot';
+
+// 图片/附件请求路径
+export const IMAGE_SAVE_PATH = '/static/image/';
+export const FILE_SAVE_PATH = '/static/file/';
+
+// MIME类型
+export const MIME_TYPE = ['xls', 'xlsx', 'doc', 'docx', 'exe', 'pdf', 'ppt', 'txt', 'zip', 'img', 'rar'];
+// 图片类型
+export const IMAGE_TYPE = ['png', 'jpg', 'jpeg', 'gif'];

+ 29 - 0
src/common/theme.js

@@ -0,0 +1,29 @@
+/*
+ * @file: 背景图片
+ */
+export default [
+  {
+    url: 'https://pic.downk.cc/item/5fc7432a394ac52378970d9e.jpg',
+    name: '壁纸一',
+  },
+  {
+    url: 'https://pic.downk.cc/item/5fc7438c394ac5237897328d.jpg',
+    name: '壁纸二',
+  },
+  {
+    url: 'https://pic.downk.cc/item/5fc744ca394ac52378979ca1.jpg',
+    name: '壁纸三',
+  },
+  {
+    url: 'https://pic.downk.cc/item/5fc744d6394ac5237897a1f4.jpg',
+    name: '壁纸四',
+  },
+  {
+    url: 'https://pic.downk.cc/item/5fc744e1394ac5237897a560.jpg',
+    name: '壁纸五',
+  },
+  {
+    url: 'https://pic.downk.cc/item/5fc744ea394ac5237897a81d.jpg',
+    name: '壁纸六',
+  },
+];

+ 19 - 49
src/components/ChatFrame.vue

@@ -26,24 +26,22 @@
               <div class="group-record-item">
                 <!-- 链接 -->
                 <a
-                  class="message-content-text"
+                  class="message-content-text receive-record-item"
                   v-if="_isUrl(item.msg)"
                   :href="item.msg"
                   target="_blank"
-                  style="margin-left: 10px; margin-top: 15px; background: #fff"
                 >
                   {{ item.msg }}
                 </a>
                 <!-- 文本 -->
                 <div
-                  class="message-content-text"
+                  class="message-content-text receive-record-item"
                   v-if="item.type === 'text'"
                   v-html="_parseHtml(item.msg)"
-                  style="margin-left: 10px; margin-top: 15px; background: #fff"
                 ></div>
                 <!-- 图片 -->
                 <div
-                  class="message-content-image"
+                  class="message-content-image receive-record-item"
                   v-if="item.type === 'image'"
                   :style="getImageStyle(item.msg)"
                   style="margin-left: 10px; margin-top: 15px;"
@@ -54,7 +52,7 @@
                 </div>
                 <!-- 视频格式文件 -->
                 <div
-                  class="message-content-image"
+                  class="message-content-image receive-record-item"
                   v-if="item.type === 'video'"
                   style="margin-left: 10px; margin-top: 15px;"
                 >
@@ -154,7 +152,7 @@
                 </div>
                 <!-- 链接 -->
                 <a
-                  class="message-content-text"
+                  class="message-content-text receive-record-item"
                   v-if="_isUrl(item.msg)"
                   :href="item.msg"
                   target="_blank"
@@ -163,13 +161,13 @@
                 </a>
                 <!-- 文本 -->
                 <div
-                  class="message-content-text"
+                  class="message-content-text receive-record-item"
                   v-if="item.type === 'text'"
                   v-html="_parseHtml(item.msg)"
                 ></div>
                 <!-- 图片 -->
                 <div
-                  class="message-content-image"
+                  class="message-content-image receive-record-item"
                   v-if="item.type === 'image'"
                   :style="getImageStyle(item.msg)"
                 >
@@ -191,7 +189,7 @@
               </div> -->
                 <!-- 视频格式文件 -->
                 <div
-                  class="message-content-image"
+                  class="message-content-image receive-record-item"
                   v-if="item.type === 'video'"
                 >
                   <video
@@ -421,14 +419,13 @@ export default {
      */
     _parseHtml(text) {
       console.log(`text----fffff`, text)
-      const regex2 = /!\[(.+?)\]!/g;
+      const regex2 = /!!\[(.+?)\]!!/g;
       const codeList = text.match(regex2)
-      console.log('[]',text.match(regex2))
-      let code = null
+      console.log('text.match(regex2)',text.match(regex2))
       let html = text
       if (codeList) {
         codeList.map((item) => {
-        code = item.match(/\[(.+?)\]/g)[0] 
+        const code = item.match(/\[(.+?)\]/g)[0] 
         html = html.replace(item, "<img style='width: 20px; height: 20px;vertical-align: sub;' src='" +
           this.getIconPic(code) +
           "' unicode = '" +
@@ -523,7 +520,6 @@ export default {
 
     // 添加表情, 到输入框
     addEmoji(code) {
-      console.log(`code111111`, code);
       let inputNode = document.getElementById("charInput");
       let html =
         "<img style='width: 20px; height: 20px;vertical-align: sub;' src='" +
@@ -587,30 +583,8 @@ export default {
       let inputValue = document.getElementById('charInput').innerHTML
 
       inputValue = inputValue.replace(/<img.*?(?:>|\/>)/gi, (val) => {
-        let unicode = '!' + val.match(/unicode=[\'\"]?([^\'\"]*)[\'\"]?/i)[1] + '!'
-        // let icon = this.emojiIcon
-        // let iPic = ''
-
-        // 遍历查找Unicode表情
-        // for (const key in icon) {
-        //   if (icon.hasOwnProperty(key)) {
-        //     const iType = icon[key]
-        //     let flag = false
-
-        //     for (let index = 0; index < iType.length; index++) {
-        //       const element = iType[index]
-
-        //       if (element.unicode == unicode) {
-        //         iPic = element.emoji
-        //         flag = true
-        //         break
-        //       }
-        //     }
-
-        //     if (flag) {break}
-        //   }
-        // }
-
+        let unicode = '!!' + val.match(/unicode=[\'\"]?([^\'\"]*)[\'\"]?/i)[1] + '!!'
+        
         return unicode
       })
 
@@ -894,10 +868,7 @@ export default {
   position: relative;
   justify-content: flex-end;
 }
-.receive-record {
-  width: 60%;
-  justify-content: flex-start;
-}
+
 .send-record > span {
   display: inline-block;
   text-align: right;
@@ -926,9 +897,10 @@ export default {
   max-width: 225px;
   max-height: 225px;
 }
-.receive-record > span {
-  text-align: left;
-  background: rgb(199, 206, 200);
+.receive-record-item {
+  margin-left: 10px; 
+  margin-top: 15px; 
+  background: #fff
 }
 .input-box {
   display: flex;
@@ -948,8 +920,7 @@ export default {
 .chatframe_input_con {
     display: inline-block;
     width: 92%;
-    min-height: 172px;
-    max-height: 172px;
+    height: 95px;
     resize: none;
     padding: 5px 15px;
     line-height: 1.5;
@@ -960,7 +931,6 @@ export default {
     outline: none;
     overflow-y: scroll;
     margin-bottom: 20px;
-    margin-top: 5px;
     
     // 超出自动换行 -- 火狐
     word-wrap: break-word;

+ 3 - 0
src/components/Login.vue

@@ -50,6 +50,9 @@ export default {
       // console.log(`init===`, this.$socket.id);
     },
     login() {
+      console.log(`process`, process)
+      const apiurl = process.env.VUE_APP_API_URL
+      console.log(`apiurl`, apiurl)
       if (this.$socket.id) {
         if (this.nickname !== "") {
           const userInfo = {

+ 1 - 1
src/main.js

@@ -4,7 +4,7 @@ import Vue from 'vue'
 import App from './App'
 import router from './router'
 import Viewer from 'v-viewer'; // 图片预览插件
-import store from './store/index'
+import store from './store'
 import VueSocketIO from 'vue-socket.io'
 import SocketIO from 'socket.io-client'
 import ElementUI from 'element-ui';

+ 0 - 29
src/store/axios.js

@@ -1,29 +0,0 @@
-// // store.jsimport Vue from 'Vue'
-// import Vuex from 'vuex'
-
-// // 引入 axios
-// import axios from 'axios'
-
-// const https = {
-//     namespaced: true,
-//     state: {
-//         test01: {
-//           name: 'Wise Wrong'
-//         },
-//         test02: {
-//           tell: '12312345678'
-//         }
-//     },
-//     mutations: {},
-//     actions: {
-//         // 封装一个 ajax 方法
-//         saveForm (context) {
-//             axios({
-//             method: 'post',
-//             url: '/user',
-//             data: context.state.test02
-//             })
-//         }
-//     }
-//   }
-//   export default https

+ 15 - 13
src/store/index.js

@@ -1,16 +1,18 @@
-import Vue from 'vue'
-import Vuex from 'vuex'
+import Vue from 'vue';
+import Vuex from 'vuex';
+// app
+import app from './modules/app';
+// chat
+import chat from './modules/chat';
 
-Vue.use(Vuex)
+Vue.use(Vuex);
+
+const modules = {
+  app,
+  chat,
+};
 
 export default new Vuex.Store({
-  state: {
-    pathName: ''
-  },
-  mutations: {
-    // 保存当前菜单栏的路径
-    savePath (state, pathName) {
-      state.pathName = pathName
-    }
-  }
-})
+  modules,
+});
+

+ 0 - 0
src/store/modules/app/actions.js


+ 0 - 0
src/store/modules/app/getters.js


+ 0 - 0
src/store/modules/app/index.js


+ 7 - 0
src/store/modules/app/mutation-types.js

@@ -0,0 +1,7 @@
+export const SET_USER = 'set_user';
+export const CLEAR_USER = 'clear_user';
+export const SET_TOKEN = 'set_token';
+export const SET_MOBILE = 'set_mobile';
+export const SET_BACKGROUND = 'set_background';
+export const SET_ACTIVETABNAME = 'set_activeTabName';
+export const SET_LOADING = 'set_loading';

+ 0 - 0
src/store/modules/app/mutations.js


+ 19 - 0
src/store/modules/app/state.js

@@ -0,0 +1,19 @@
+import cookie from 'js-cookie';
+
+const appState = {
+  user: {
+    userId: '',
+    username: '',
+    password: '',
+    avatar: '',
+    createTime: 0,
+  },
+  token: cookie.get('token'),
+  mobile: false,
+  background: '',
+  activeTabName: 'message',
+  apiUrl: process.env.VUE_APP_API_URL, // 后台api地址
+  loading: false, // 全局Loading状态
+};
+
+export default appState;

+ 343 - 0
src/store/modules/chat/actions.js

@@ -0,0 +1,343 @@
+import io from 'socket.io-client';
+import Vue from 'vue';
+import { DEFAULT_GROUP } from '@/common/index';
+import localforage from 'localforage';
+import { SET_LOADING, CLEAR_USER } from '../app/mutation-types';
+import {
+  SET_SOCKET,
+  SET_DROPPED,
+  ADD_GROUP_MESSAGE,
+  ADD_FRIEND_MESSAGE,
+  SET_GROUP_GATHER,
+  SET_FRIEND_GATHER,
+  SET_USER_GATHER,
+  SET_ACTIVE_ROOM,
+  DEL_GROUP,
+  DEL_GROUP_MEMBER,
+  DEL_FRIEND,
+  ADD_UNREAD_GATHER,
+  REVOKE_MESSAGE,
+  USER_ONLINE,
+  USER_OFFLINE,
+  ADD_GROUP_MEMBER,
+  UPDATE_USER_INFO,
+} from './mutation-types';
+
+const actions = {
+  // 初始化socket连接和监听socket事件
+  async connectSocket({ commit, state, dispatch, rootState }) {
+    const { user, token } = rootState.app;
+    const socket = io.connect(`ws://${process.env.VUE_APP_API_URL.split('http://')[1]}`, {
+      reconnection: true,
+      query: {
+        token,
+        userId: user.userId,
+      },
+    });
+    // token校验,失败则要求重新登录
+    socket.on('unauthorized', (msg) => {
+      Vue.prototype.$message.error(msg);
+      // 清空token,socket
+      commit(`app/${CLEAR_USER}`, {}, { root: true });
+      setTimeout(() => {
+        window.location.reload();
+      }, 1000);
+    });
+
+    socket.on('connect', async () => {
+      console.log('连接成功');
+      // 获取聊天室所需所有信息
+      socket.emit('chatData', token);
+
+      // 先保存好socket对象
+      commit(SET_SOCKET, socket);
+    });
+    // 用户上线
+    socket.on('userOnline', (data) => {
+      console.log('userOnline', data);
+      commit(USER_ONLINE, data.data);
+    });
+
+    // 用户下线
+    socket.on('userOffline', (data) => {
+      console.log('userOffline', data);
+      commit(USER_OFFLINE, data.data);
+    });
+
+    // 新建群组
+    socket.on('addGroup', (res) => {
+      console.log('on addGroup', res);
+      if (res.code) {
+        return Vue.prototype.$message.error(res.msg);
+      }
+      Vue.prototype.$message.success(res.msg);
+      commit(SET_GROUP_GATHER, res.data);
+      commit(`app/${SET_LOADING}`, false, { root: true });
+    });
+
+    // 加入群组
+    socket.on('joinGroup', async (res) => {
+      if (res.code) {
+        return Vue.prototype.$message.error(res.msg);
+      }
+      console.log('on joinGroup', res);
+      const { invited, group, userId } = res.data;
+
+      // 此处区分是搜索群加入群聊还是被邀请加入群聊
+      if (invited) {
+        // 被邀请的用户Id
+        const { friendIds } = res.data;
+        // 当前用户被邀请加入群,则加入群
+        if (friendIds.includes(user.userId) && !state.groupGather[group.groupId]) {
+          // commit(SET_GROUP_GATHER, group);
+          // 获取群里面所有用户的用户信息
+          socket.emit('chatData', token);
+        } else if (userId === user.userId) {
+          // 邀请发起者
+          commit(ADD_GROUP_MEMBER, {
+            groupId: group.groupId,
+            members: Object.values(state.friendGather).filter((friend) => friendIds.includes(friend.userId)),
+          });
+          const groupGather2 = state.groupGather;
+          // ?? 待优化
+          commit(SET_ACTIVE_ROOM, groupGather2[group.groupId]);
+          return Vue.prototype.$message.info(res.msg);
+        }
+      } else {
+        const newUser = res.data.user;
+        newUser.online = 1;
+        // 新用户加入群
+        if (newUser.userId !== rootState.app.user.userId) {
+          commit(ADD_GROUP_MEMBER, {
+            groupId: group.groupId,
+            members: [newUser],
+          });
+          return Vue.prototype.$message.info(`${newUser.username}加入群${group.groupName}`);
+        }
+        // 是用户自己 则加入到某个群
+        if (!state.groupGather[group.groupId]) {
+          commit(SET_GROUP_GATHER, group);
+          // 获取群里面所有用户的用户信息
+          socket.emit('chatData', token);
+        }
+        Vue.prototype.$message.info(`成功加入群${group.groupName}`);
+        commit(SET_ACTIVE_ROOM, state.groupGather[group.groupId]);
+        commit(`app/${SET_LOADING}`, false, { root: true });
+      }
+    });
+    //
+    socket.on('joinGroupSocket', (res) => {
+      console.log('on joinGroupSocket', res);
+      if (res.code) {
+        return Vue.prototype.$message.error(res.msg);
+      }
+      const newUser = res.data.user;
+      newUser.online = 1;
+      const { group } = res.data;
+      const groupObj = state.groupGather[group.groupId];
+      // 新用户注册后默认进入到DEFAULT_GROUP,此处需要判断一下是否在群内,不在群内的话需要加入本群中
+      // 否则在线的用户无法收到新成员进群的变更
+      if (!groupObj.members.find((member) => member.userId === newUser.userId)) {
+        newUser.isManager = 0;
+        groupObj.members.push(newUser);
+        Vue.prototype.$message.info(res.msg);
+      }
+      commit(SET_USER_GATHER, newUser);
+    });
+
+    socket.on('groupMessage', (res) => {
+      console.log('on groupMessage', res);
+      if (!res.code) {
+        commit(ADD_GROUP_MESSAGE, res.data);
+        const { activeRoom } = state;
+        if (activeRoom && activeRoom.groupId !== res.data.groupId) {
+          commit(ADD_UNREAD_GATHER, res.data.groupId);
+        }
+      } else {
+        Vue.prototype.$message.error(res.msg);
+      }
+    });
+
+    socket.on('addFriend', (res) => {
+      console.log('on addFriend', res);
+      if (!res.code) {
+        commit(SET_FRIEND_GATHER, res.data);
+        commit(SET_USER_GATHER, res.data);
+        // 取消loading
+        Vue.prototype.$message.info(res.msg);
+        socket.emit('joinFriendSocket', {
+          userId: user.userId,
+          friendId: res.data.userId,
+        });
+      } else {
+        Vue.prototype.$message.error(res.msg);
+      }
+      commit(`app/${SET_LOADING}`, false, { root: true });
+    });
+
+    socket.on('joinFriendSocket', (res) => {
+      console.log('on joinFriendSocket', res);
+      // 添加好友之后默认进入好友聊天房间,初始化时不默认选中该好友房间
+      if (!state.activeRoom) {
+        commit(SET_ACTIVE_ROOM, state.friendGather[res.data.friendId]);
+      }
+      if (!res.code) {
+        console.log('成功加入私聊房间');
+      }
+    });
+
+    socket.on('friendMessage', async (res) => {
+      console.log('on friendMessage', res);
+      if (!res.code) {
+        if (res.data.friendId === user.userId || res.data.userId === user.userId) {
+          console.log('ADD_FRIEND_MESSAGE', res.data);
+          commit(ADD_FRIEND_MESSAGE, res.data);
+          // 新增私聊信息需要检测本地是否已删除聊天,如已删除需要恢复
+          let deletedChat = (await localforage.getItem(`${user.userId}-deletedChatId`));
+          if (deletedChat) {
+            if (res.data.friendId === user.userId) {
+              deletedChat = deletedChat.filter((id) => id !== res.data.userId);
+            } else {
+              deletedChat = deletedChat.filter((id) => id !== res.data.friendId);
+            }
+            await localforage.setItem(`${user.userId}-deletedChatId`, deletedChat);
+          }
+          const { activeRoom } = state;
+          if (activeRoom && activeRoom.userId !== res.data.userId && activeRoom.userId !== res.data.friendId) {
+            commit(ADD_UNREAD_GATHER, res.data.userId);
+          }
+        }
+      } else {
+        Vue.prototype.$message.error(res.msg);
+      }
+    });
+
+    socket.on('chatData', (res) => {
+      if (res.code) {
+        return Vue.prototype.$message.error(res.msg);
+      }
+      console.log(res);
+      dispatch('handleChatData', res.data);
+      commit(SET_DROPPED, false);
+    });
+
+    // 退出群组
+    socket.on('exitGroup', (res) => {
+      if (!res.code) {
+        // 如果是当前用户退群,则删除群聊
+        if (res.data.userId === user.userId) {
+          commit(DEL_GROUP, res.data);
+          commit(SET_ACTIVE_ROOM, state.groupGather[DEFAULT_GROUP]);
+          Vue.prototype.$message.success(res.msg);
+        } else {
+          console.log(`--用户--${res.data.userId}`, '--退出群--', res.data.groupId);
+          // 广播给其他用户,从群成员中删除该成员
+          commit(DEL_GROUP_MEMBER, res.data);
+        }
+      } else if (res.data.userId === user.userId) {
+        Vue.prototype.$message.error(res.msg);
+      }
+    });
+
+    // 更新群信息
+    socket.on('updateGroupInfo', (res) => {
+      if (!res.code) {
+        const group = state.groupGather[res.data.groupId];
+        if (group) {
+          group.groupName = res.data.groupName;
+          group.notice = res.data.notice;
+          if (state.activeRoom.groupId) {
+            state.activeRoom.groupName = res.data.groupName;
+            state.activeRoom.notice = res.data.notice;
+          }
+          if (res.data.userId === user.userId) {
+            Vue.prototype.$message.success(res.msg);
+          }
+        }
+      }
+    });
+
+    // 更新好友信息
+    socket.on('updateUserInfo', (res) => {
+      if (!res.code) {
+        commit(UPDATE_USER_INFO, res.data);
+      }
+    });
+    // 删除好友
+    socket.on('exitFriend', (res) => {
+      if (!res.code) {
+        commit(DEL_FRIEND, res.data);
+        commit(SET_ACTIVE_ROOM, state.groupGather[DEFAULT_GROUP]);
+        Vue.prototype.$message.success(res.msg);
+      } else {
+        Vue.prototype.$message.error(res.msg);
+      }
+    });
+
+    // 消息撤回
+    socket.on('revokeMessage', (res) => {
+      if (!res.code) {
+        commit(REVOKE_MESSAGE, res.data);
+      } else {
+        Vue.prototype.$message.error(res.msg);
+      }
+    });
+  },
+
+  // 根据chatData返回的好友列表群组列表
+  // 建立各自socket连接
+  // 并保存至各自Gather
+  async handleChatData({ commit, dispatch, state, rootState }, payload) {
+    const { user } = rootState.app;
+    const { socket } = state;
+    const { groupGather } = state;
+    const groupArr = payload.groupData;
+    const friendArr = payload.friendData;
+    const userArr = payload.userData;
+    if (groupArr.length) {
+      // eslint-disable-next-line no-restricted-syntax
+      for (const group of groupArr) {
+        socket.emit('joinGroupSocket', {
+          groupId: group.groupId,
+          userId: user.userId,
+        });
+        commit(SET_GROUP_GATHER, group);
+      }
+    }
+    if (friendArr.length) {
+      // eslint-disable-next-line no-restricted-syntax
+      for (const friend of friendArr) {
+        socket.emit('joinFriendSocket', {
+          userId: user.userId,
+          friendId: friend.userId,
+        });
+        commit(SET_FRIEND_GATHER, friend);
+      }
+    }
+    if (userArr.length) {
+      // eslint-disable-next-line no-restricted-syntax
+      for (const user_ of userArr) {
+        commit(SET_USER_GATHER, user_);
+      }
+    }
+
+    /**
+     * 由于groupgather和userGather都更新了, 但是activeGather依旧是老对象,
+     * 这里需要根据老的activeGather找到最新的gather对象,这样才能使得vue的watch监听新gather
+     */
+
+    const { activeRoom } = state;
+    console.log('init');
+    console.log(activeRoom);
+    const groupGather2 = state.groupGather;
+    const friendGather2 = state.friendGather;
+    if (!activeRoom) {
+      console.log(DEFAULT_GROUP);
+      // 更新完数据没有默认activeRoom设置群为DEFAULT_GROUP
+      return commit(SET_ACTIVE_ROOM, groupGather[DEFAULT_GROUP]);
+    }
+    commit(SET_ACTIVE_ROOM, groupGather2[activeRoom.groupId] || friendGather2[activeRoom.userId]);
+  },
+};
+
+export default actions;

+ 25 - 0
src/store/modules/chat/getters.js

@@ -0,0 +1,25 @@
+const getters = {
+  socket(state) {
+    return state.socket;
+  },
+  dropped(state) {
+    return state.dropped;
+  },
+  activeRoom(state) {
+    return state.activeRoom;
+  },
+  groupGather(state) {
+    return state.groupGather;
+  },
+  friendGather(state) {
+    return state.friendGather;
+  },
+  userGather(state) {
+    return state.userGather;
+  },
+  unReadGather(state) {
+    return state.unReadGather;
+  },
+};
+
+export default getters;

+ 14 - 0
src/store/modules/chat/index.js

@@ -0,0 +1,14 @@
+import actions from './actions';
+import mutations from './mutations';
+import getters from './getters';
+import state from './state';
+
+const chat = {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+  getters,
+};
+
+export default chat;

+ 21 - 0
src/store/modules/chat/mutation-types.js

@@ -0,0 +1,21 @@
+export const SET_SOCKET = 'set_socket';
+export const SET_DROPPED = 'set_dropped';
+export const SET_ACTIVE_GROUP_USER = 'set_active_group_user';
+export const SET_ACTIVE_ROOM = 'set_active_room';
+export const SET_USER_GATHER = 'set_user_gather';
+export const SET_FRIEND_GATHER = 'set_friend_gather';
+export const SET_GROUP_GATHER = 'set_group_gather';
+export const ADD_GROUP_MESSAGE = 'add_group_message';
+export const SET_GROUP_MESSAGES = 'set_group_messages';
+export const ADD_FRIEND_MESSAGE = 'add_friend_message';
+export const SET_FRIEND_MESSAGES = 'set_friend_messages';
+export const DEL_GROUP = 'del_group';
+export const DEL_GROUP_MEMBER = 'del_group_member';
+export const DEL_FRIEND = 'del_friend';
+export const ADD_UNREAD_GATHER = 'set_unread_gather';
+export const LOSE_UNREAD_GATHER = 'lose_unread_gather';
+export const REVOKE_MESSAGE = 'revoke_message';
+export const USER_ONLINE = 'user_online';
+export const USER_OFFLINE = 'user_offline';
+export const ADD_GROUP_MEMBER = 'add_group_member';
+export const UPDATE_USER_INFO = 'update_user_info';

+ 229 - 0
src/store/modules/chat/mutations.js

@@ -0,0 +1,229 @@
+import Vue from 'vue';
+import { MutationTree } from 'vuex';
+import {
+  SET_SOCKET,
+  SET_DROPPED,
+  ADD_GROUP_MEMBER,
+  ADD_GROUP_MESSAGE,
+  SET_GROUP_MESSAGES,
+  ADD_FRIEND_MESSAGE,
+  SET_FRIEND_MESSAGES,
+  SET_ACTIVE_ROOM,
+  SET_GROUP_GATHER,
+  SET_FRIEND_GATHER,
+  SET_USER_GATHER,
+  DEL_GROUP,
+  DEL_GROUP_MEMBER,
+  DEL_FRIEND,
+  ADD_UNREAD_GATHER,
+  LOSE_UNREAD_GATHER,
+  REVOKE_MESSAGE,
+  USER_ONLINE,
+  USER_OFFLINE,
+  UPDATE_USER_INFO,
+} from './mutation-types';
+
+const mutations = {
+  // 保存socket
+  [SET_SOCKET](state, payload) {
+    state.socket = payload;
+  },
+
+  // 设置用户是否处于掉线重连状态
+  [SET_DROPPED](state, payload) {
+    state.dropped = payload;
+  },
+
+  /**
+   * 用户上线
+   * @param state
+   * @param payload userId
+   */
+  [USER_ONLINE](state, userId) {
+    // 更新好友列表用户状态
+    if (state.friendGather[userId]) {
+      console.log(`${userId}----上线`);
+      Vue.set(state.friendGather[userId], 'online', 1);
+      console.log(state.friendGather);
+    }
+    // 更新所有群组中该成员在线状态
+    (Object.values(state.groupGather)).forEach((group) => {
+      const member = group.members.find((m) => m.userId === userId);
+      if (member) {
+        member.online = 1;
+      }
+    });
+  },
+
+  // 用户下线
+  [USER_OFFLINE](state, userId) {
+    if (state.friendGather[userId]) {
+      Vue.set(state.friendGather[userId], 'online', 0);
+    }
+    // 更新所有群组中该成员在线状态
+    (Object.values(state.groupGather)).forEach((group) => {
+      const member = group.members.find((m) => m.userId === userId);
+      if (member) {
+        member.online = 0;
+      }
+    });
+  },
+  // 新增群成员
+  [ADD_GROUP_MEMBER](state, payload) {
+    const members = payload.members.map((member) => ({
+      ...member,
+      isManager: 0,
+    }));
+    if (state.groupGather[payload.groupId].members && members) {
+      state.groupGather[payload.groupId].members = state.groupGather[payload.groupId].members.concat(members);
+    } else {
+      // vuex对象数组中对象改变不更新问题
+      Vue.set(state.groupGather[payload.groupId], 'members', members);
+    }
+  },
+  // 新增一条群消息
+  [ADD_GROUP_MESSAGE](state, payload) {
+    if (state.groupGather[payload.groupId].messages) {
+      state.groupGather[payload.groupId].messages.push(payload);
+    } else {
+      // vuex对象数组中对象改变不更新问题
+      Vue.set(state.groupGather[payload.groupId], 'messages', [payload]);
+    }
+  },
+
+  // 设置群消息
+  [SET_GROUP_MESSAGES](state, payload) {
+    if (payload && payload.length) {
+      Vue.set(state.groupGather[payload[0].groupId], 'messages', payload);
+    }
+  },
+
+  // 新增一条私聊消息
+  [ADD_FRIEND_MESSAGE](state, payload) {
+    // @ts-ignore
+    const { userId } = this.getters['app/user'];
+    if (payload.friendId === userId) {
+      if (state.friendGather[payload.userId].messages) {
+        state.friendGather[payload.userId].messages.push(payload);
+      } else {
+        Vue.set(state.friendGather[payload.userId], 'messages', [payload]);
+      }
+    } else if (state.friendGather[payload.friendId].messages) {
+      state.friendGather[payload.friendId].messages.push(payload);
+    } else {
+      Vue.set(state.friendGather[payload.friendId], 'messages', [payload]);
+    }
+  },
+
+  // 设置私聊记录
+  [SET_FRIEND_MESSAGES](state, payload) {
+    // @ts-ignore
+    const { userId } = this.getters['app/user'];
+    if (payload && payload.length) {
+      if (payload[0].friendId === userId) {
+        Vue.set(state.friendGather[payload[0].userId], 'messages', payload);
+      } else {
+        Vue.set(state.friendGather[payload[0].friendId], 'messages', payload);
+      }
+    }
+  },
+
+  // 设置当前聊天对象(群或好友)
+  [SET_ACTIVE_ROOM](state, payload) {
+    state.activeRoom = payload;
+  },
+
+  // 设置所有的群的群详细信息(头像,群名字等)
+  [SET_GROUP_GATHER](state, payload) {
+    Vue.set(state.groupGather, payload.groupId, payload);
+  },
+
+  // 设置所有的用户的用户详细信息(头像,昵称等)
+  [SET_USER_GATHER](state, payload) {
+    Vue.set(state.userGather, payload.userId, payload);
+  },
+
+  // 设置所有的好友的用户详细信息(头像,昵称等)
+  [SET_FRIEND_GATHER](state, payload) {
+    Vue.set(state.friendGather, payload.userId, payload);
+  },
+
+  // 设置所有的用户的用户详细信息(头像,昵称等)
+  [UPDATE_USER_INFO](state, user) {
+    const { userId, username, avatar } = user;
+    const { userGather, friendGather } = state;
+    if (userGather[userId]) {
+      userGather[userId].username = username;
+      userGather[userId].avatar = avatar;
+    }
+    if (friendGather[userId]) {
+      friendGather[userId].username = username;
+      friendGather[userId].avatar = avatar;
+    }
+  },
+
+  // 退群
+  [DEL_GROUP](state, payload) {
+    Vue.delete(state.groupGather, payload.groupId);
+  },
+
+  // 删除群成员
+  [DEL_GROUP_MEMBER](state, payload) {
+    const group = state.groupGather[payload.groupId];
+    if (group) {
+      group.members = group.members.filter((member) => member.userId !== payload.userId);
+    }
+  },
+
+  // 删好友
+  [DEL_FRIEND](state, payload) {
+    Vue.delete(state.friendGather, payload.friendId);
+  },
+
+  // 给某个聊天组添加未读消息
+  [ADD_UNREAD_GATHER](state, payload) {
+    document.title = '【有未读消息】TylooChat聊天室';
+    if (!state.unReadGather[payload]) {
+      Vue.set(state.unReadGather, payload, 1);
+    } else {
+      ++state.unReadGather[payload];
+    }
+  },
+
+  // 给某个聊天组清空未读消息
+  [LOSE_UNREAD_GATHER](state, payload) {
+    document.title = 'TylooChat聊天室';
+    Vue.set(state.unReadGather, payload, 0);
+  },
+
+  // 消息撤回
+  // [REVOKE_MESSAGE](state, payload: FriendMessage & GroupMessage & { username }) {
+  [REVOKE_MESSAGE](state, payload) {
+    // @ts-ignore
+    const { userId } = this.getters['app/user'];
+    // 撤回的为群消息
+    if (payload.groupId) {
+      const { messages } = state.groupGather[payload.groupId];
+      // 将该消息设置为isRevoke,并设置撤回人姓名
+      if (messages) {
+        const msg = messages.find((message) => message._id === payload._id);
+        if (msg) {
+          Vue.set(msg, 'isRevoke', true);
+          Vue.set(msg, 'revokeUserName', payload.username);
+        }
+      }
+    } else {
+      const { messages } = state.friendGather[payload.friendId === userId ? payload.userId : payload.friendId];
+      // 将该消息设置为isRevoke,并设置撤回人姓名
+      if (messages) {
+        const msg = messages.find((message) => message._id === payload._id);
+        if (msg) {
+          Vue.set(msg, 'isRevoke', true);
+          Vue.set(msg, 'revokeUserName', payload.username);
+        }
+      }
+    }
+  },
+};
+
+export default mutations;

+ 11 - 0
src/store/modules/chat/state.js

@@ -0,0 +1,11 @@
+const chatState = {
+  socket: null, // ws实例
+  dropped: false, // 是否断开连接
+  activeRoom: null, // 当前访问房间
+  groupGather: {}, // 群组列表
+  userGather: {}, // 设置群在线用户列表
+  friendGather: {}, // 好友列表
+  unReadGather: {}, // 所有会话未读消息集合
+};
+
+export default chatState;

+ 0 - 17
src/store/user.js

@@ -1,17 +0,0 @@
-const user = {
-  namespaced: true,
-  state: {},
-  mutations: {
-  // todo
-    AddInfo (state, data) {
-    // todo with state
-    }
-  },
-  actions: {
-    addInfo ({ commit }, data) {
-      commit('AddInfo', data)
-    }
-    // todo
-  }
-}
-export default user