Home.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <template>
  2. <el-container class="home-page">
  3. <el-aside width="80px" class="navi-bar">
  4. <div class="user-head-image">
  5. <head-image :name="$store.state.userStore.userInfo.nickName"
  6. :url="$store.state.userStore.userInfo.headImageThumb" :size="50"
  7. @click.native="showSettingDialog = true">
  8. </head-image>
  9. </div>
  10. <el-menu background-color="#19082f" style="margin-top: 25px;">
  11. <el-menu-item title="聊天">
  12. <router-link class="link" v-bind:to="'/home/chat'">
  13. <span class="icon iconfont icon-chat"></span>
  14. <div v-show="unreadCount > 0" class="unread-text">{{ unreadCount }}</div>
  15. </router-link>
  16. </el-menu-item>
  17. <el-menu-item title="好友">
  18. <router-link class="link" v-bind:to="'/home/friend'">
  19. <span class="icon iconfont icon-friend"></span>
  20. </router-link>
  21. </el-menu-item>
  22. <el-menu-item title="群聊">
  23. <router-link class="link" v-bind:to="'/home/group'">
  24. <span class="icon iconfont icon-group"></span>
  25. </router-link>
  26. </el-menu-item>
  27. <el-menu-item title="设置" @click="showSetting()">
  28. <span class="icon iconfont icon-setting"></span>
  29. </el-menu-item>
  30. </el-menu>
  31. <div class="exit-box" @click="onExit()" title="退出">
  32. <span class="icon iconfont icon-exit"></span>
  33. </div>
  34. </el-aside>
  35. <el-main class="content-box">
  36. <router-view></router-view>
  37. </el-main>
  38. <setting :visible="showSettingDialog" @close="closeSetting()"></setting>
  39. <user-info v-show="uiStore.userInfo.show" :pos="uiStore.userInfo.pos" :user="uiStore.userInfo.user"
  40. @close="$store.commit('closeUserInfoBox')"></user-info>
  41. <full-image :visible="uiStore.fullImage.show" :url="uiStore.fullImage.url"
  42. @close="$store.commit('closeFullImageBox')"></full-image>
  43. <rtc-private-video ref="rtcPrivateVideo"></rtc-private-video>
  44. <rtc-private-acceptor ref="rtcPrivateAcceptor"></rtc-private-acceptor>
  45. </el-container>
  46. </template>
  47. <script>
  48. import HeadImage from '../components/common/HeadImage.vue';
  49. import Setting from '../components/setting/Setting.vue';
  50. import UserInfo from '../components/common/UserInfo.vue';
  51. import FullImage from '../components/common/FullImage.vue';
  52. import RtcPrivateVideo from '../components/rtc/RtcPrivateVideo.vue';
  53. import RtcPrivateAcceptor from '../components/rtc/RtcPrivateAcceptor.vue';
  54. export default {
  55. components: {
  56. HeadImage,
  57. Setting,
  58. UserInfo,
  59. FullImage,
  60. RtcPrivateVideo,
  61. RtcPrivateAcceptor
  62. },
  63. data() {
  64. return {
  65. showSettingDialog: false,
  66. lastPlayAudioTime: new Date().getTime() - 1000
  67. }
  68. },
  69. methods: {
  70. init() {
  71. this.$store.dispatch("load").then(() => {
  72. // ws初始化
  73. this.$wsApi.connect(process.env.VUE_APP_WS_URL, sessionStorage.getItem("accessToken"));
  74. this.$wsApi.onConnect(() => {
  75. // 加载离线消息
  76. this.pullPrivateOfflineMessage(this.$store.state.chatStore.privateMsgMaxId);
  77. this.pullGroupOfflineMessage(this.$store.state.chatStore.groupMsgMaxId);
  78. });
  79. this.$wsApi.onMessage((cmd, msgInfo) => {
  80. if (cmd == 2) {
  81. // 关闭ws
  82. this.$wsApi.close(3000)
  83. // 异地登录,强制下线
  84. this.$alert("您已在其他地方登陆,将被强制下线", "强制下线通知", {
  85. confirmButtonText: '确定',
  86. callback: action => {
  87. location.href = "/";
  88. }
  89. });
  90. } else if (cmd == 3) {
  91. // 插入私聊消息
  92. this.handlePrivateMessage(msgInfo);
  93. } else if (cmd == 4) {
  94. // 插入群聊消息
  95. this.handleGroupMessage(msgInfo);
  96. }
  97. });
  98. this.$wsApi.onClose((e) => {
  99. console.log(e);
  100. if (e.code != 3000) {
  101. // 断线重连
  102. this.$message.error("连接断开,正在尝试重新连接...");
  103. this.$wsApi.reconnect(process.env.VUE_APP_WS_URL, sessionStorage.getItem(
  104. "accessToken"));
  105. }
  106. });
  107. }).catch((e) => {
  108. console.log("初始化失败", e);
  109. })
  110. },
  111. pullPrivateOfflineMessage(minId) {
  112. this.$http({
  113. url: "/message/private/pullOfflineMessage?minId=" + minId,
  114. method: 'get'
  115. });
  116. },
  117. pullGroupOfflineMessage(minId) {
  118. this.$http({
  119. url: "/message/group/pullOfflineMessage?minId=" + minId,
  120. method: 'get'
  121. });
  122. },
  123. handlePrivateMessage(msg) {
  124. // 消息加载标志
  125. if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
  126. this.$store.commit("loadingPrivateMsg", JSON.parse(msg.content))
  127. return;
  128. }
  129. // 消息已读处理,清空已读数量
  130. if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
  131. this.$store.commit("resetUnreadCount", {
  132. type: 'PRIVATE',
  133. targetId: msg.recvId
  134. })
  135. return;
  136. }
  137. // 消息回执处理,改消息状态为已读
  138. if (msg.type == this.$enums.MESSAGE_TYPE.RECEIPT) {
  139. this.$store.commit("readedMessage", {
  140. friendId: msg.sendId
  141. })
  142. return;
  143. }
  144. // 标记这条消息是不是自己发的
  145. msg.selfSend = msg.sendId == this.$store.state.userStore.userInfo.id;
  146. // 好友id
  147. let friendId = msg.selfSend ? msg.recvId : msg.sendId;
  148. this.loadFriendInfo(friendId).then((friend) => {
  149. this.insertPrivateMessage(friend, msg);
  150. })
  151. },
  152. insertPrivateMessage(friend, msg) {
  153. // webrtc 信令
  154. if (msg.type >= this.$enums.MESSAGE_TYPE.RTC_CALL_VOICE &&
  155. msg.type <= this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) {
  156. let rtcInfo = this.$store.state.userStore.rtcInfo;
  157. // 呼叫
  158. if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CALL_VOICE ||
  159. msg.type == this.$enums.MESSAGE_TYPE.RTC_CALL_VIDEO ||
  160. rtcInfo.state == this.$enums.RTC_STATE.FREE ||
  161. rtcInfo.state == this.$enums.RTC_STATE.WAIT_ACCEPT) {
  162. this.$refs.rtcPrivateAcceptor.onRTCMessage(msg,friend)
  163. } else {
  164. this.$refs.rtcPrivateVideo.onRTCMessage(msg)
  165. }
  166. return;
  167. }
  168. let chatInfo = {
  169. type: 'PRIVATE',
  170. targetId: friend.id,
  171. showName: friend.nickName,
  172. headImage: friend.headImage
  173. };
  174. // 打开会话
  175. this.$store.commit("openChat", chatInfo);
  176. // 插入消息
  177. this.$store.commit("insertMessage", msg);
  178. // 播放提示音
  179. if (!msg.selfSend && msg.status != this.$enums.MESSAGE_STATUS.READED) {
  180. this.playAudioTip();
  181. }
  182. },
  183. handleGroupMessage(msg) {
  184. // 消息加载标志
  185. if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
  186. this.$store.commit("loadingGroupMsg", JSON.parse(msg.content))
  187. return;
  188. }
  189. // 消息已读处理
  190. if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
  191. // 我已读对方的消息,清空已读数量
  192. let chatInfo = {
  193. type: 'GROUP',
  194. targetId: msg.groupId
  195. }
  196. this.$store.commit("resetUnreadCount", chatInfo)
  197. return;
  198. }
  199. // 消息回执处理
  200. if (msg.type == this.$enums.MESSAGE_TYPE.RECEIPT) {
  201. // 更新消息已读人数
  202. let msgInfo = {
  203. id: msg.id,
  204. groupId: msg.groupId,
  205. readedCount: msg.readedCount,
  206. receiptOk: msg.receiptOk
  207. };
  208. this.$store.commit("updateMessage", msgInfo)
  209. return;
  210. }
  211. // 标记这条消息是不是自己发的
  212. msg.selfSend = msg.sendId == this.$store.state.userStore.userInfo.id;
  213. this.loadGroupInfo(msg.groupId).then((group) => {
  214. // 插入群聊消息
  215. this.insertGroupMessage(group, msg);
  216. })
  217. },
  218. insertGroupMessage(group, msg) {
  219. let chatInfo = {
  220. type: 'GROUP',
  221. targetId: group.id,
  222. showName: group.remark,
  223. headImage: group.headImageThumb
  224. };
  225. // 打开会话
  226. this.$store.commit("openChat", chatInfo);
  227. // 插入消息
  228. this.$store.commit("insertMessage", msg);
  229. // 播放提示音
  230. if (!msg.selfSend && msg.status != this.$enums.MESSAGE_STATUS.READED) {
  231. this.playAudioTip();
  232. }
  233. },
  234. onExit() {
  235. this.$wsApi.close(3000);
  236. sessionStorage.removeItem("accessToken");
  237. location.href = "/";
  238. },
  239. playAudioTip() {
  240. if (new Date().getTime() - this.lastPlayAudioTime > 1000) {
  241. this.lastPlayAudioTime = new Date().getTime();
  242. let audio = new Audio();
  243. let url = require(`@/assets/audio/tip.wav`);
  244. audio.src = url;
  245. audio.play();
  246. }
  247. },
  248. showSetting() {
  249. this.showSettingDialog = true;
  250. },
  251. closeSetting() {
  252. this.showSettingDialog = false;
  253. },
  254. loadFriendInfo(id) {
  255. return new Promise((resolve, reject) => {
  256. let friend = this.$store.state.friendStore.friends.find((f) => f.id == id);
  257. if (friend) {
  258. resolve(friend);
  259. } else {
  260. this.$http({
  261. url: `/friend/find/${id}`,
  262. method: 'get'
  263. }).then((friend) => {
  264. this.$store.commit("addFriend", friend);
  265. resolve(friend)
  266. })
  267. }
  268. });
  269. },
  270. loadGroupInfo(id) {
  271. return new Promise((resolve, reject) => {
  272. let group = this.$store.state.groupStore.groups.find((g) => g.id == id);
  273. if (group) {
  274. resolve(group);
  275. } else {
  276. this.$http({
  277. url: `/group/find/${id}`,
  278. method: 'get'
  279. }).then((group) => {
  280. resolve(group)
  281. this.$store.commit("addGroup", group);
  282. })
  283. }
  284. });
  285. }
  286. },
  287. computed: {
  288. uiStore() {
  289. return this.$store.state.uiStore;
  290. },
  291. unreadCount() {
  292. let unreadCount = 0;
  293. let chats = this.$store.state.chatStore.chats;
  294. chats.forEach((chat) => {
  295. unreadCount += chat.unreadCount
  296. });
  297. return unreadCount;
  298. }
  299. },
  300. watch: {
  301. unreadCount: {
  302. handler(newCount, oldCount) {
  303. let tip = newCount > 0 ? `${newCount}条未读` : "";
  304. this.$elm.setTitleTip(tip);
  305. },
  306. immediate: true
  307. }
  308. },
  309. mounted() {
  310. this.init();
  311. },
  312. unmounted() {
  313. this.$wsApi.close();
  314. }
  315. }
  316. </script>
  317. <style scoped lang="scss">
  318. .navi-bar {
  319. background: #19082f;
  320. padding: 15px;
  321. padding-top: 50px;
  322. .el-menu {
  323. border: none;
  324. flex: 1;
  325. .el-menu-item {
  326. margin: 20px 0;
  327. background-color: #19082f !important;
  328. padding: 0 !important;
  329. text-align: center;
  330. .link {
  331. text-decoration: none;
  332. &.router-link-active .icon {
  333. color: #ba785a;
  334. }
  335. }
  336. .icon {
  337. font-size: 26px !important;
  338. color: #ddd;
  339. }
  340. .unread-text {
  341. position: absolute;
  342. line-height: 20px;
  343. background-color: #f56c6c;
  344. left: 36px;
  345. top: 7px;
  346. color: white;
  347. border-radius: 30px;
  348. padding: 0 5px;
  349. font-size: 10px;
  350. text-align: center;
  351. white-space: nowrap;
  352. border: 1px solid #f1e5e5;
  353. }
  354. }
  355. }
  356. .exit-box {
  357. position: absolute;
  358. width: 60px;
  359. bottom: 40px;
  360. color: #ccc;
  361. text-align: center;
  362. cursor: pointer;
  363. .icon {
  364. font-size: 28px;
  365. }
  366. &:hover {
  367. color: white;
  368. }
  369. }
  370. }
  371. .content-box {
  372. padding: 0;
  373. background-color: #f8f8f8;
  374. color: black;
  375. text-align: center;
  376. }
  377. </style>