ChatBox.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. <template>
  2. <div class="chat-box" @click="closeRefBox()" @mousemove="readedMessage()">
  3. <el-container>
  4. <el-header height="50px">
  5. <span>{{ title }}</span>
  6. <span title="群聊信息" v-show="this.chat.type == 'GROUP'" class="btn-side el-icon-more"
  7. @click="showSide = !showSide"></span>
  8. </el-header>
  9. <el-main style="padding: 0;">
  10. <el-container>
  11. <el-container class="content-box">
  12. <el-main class="im-chat-main" id="chatScrollBox" @scroll="onScroll">
  13. <div class="im-chat-box">
  14. <ul>
  15. <li v-for="(msgInfo, idx) in chat.messages" :key="idx">
  16. <chat-message-item v-if="idx >= showMinIdx" @call="onCall(msgInfo.type)"
  17. :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)"
  18. :showName="showName(msgInfo)" :msgInfo="msgInfo"
  19. :groupMembers="groupMembers" @delete="deleteMessage"
  20. @recall="recallMessage">
  21. </chat-message-item>
  22. </li>
  23. </ul>
  24. </div>
  25. </el-main>
  26. <el-footer height="220px" class="im-chat-footer">
  27. <div class="chat-tool-bar">
  28. <div title="表情" class="icon iconfont icon-emoji" ref="emotion"
  29. @click.stop="showEmotionBox()">
  30. </div>
  31. <div title="发送图片">
  32. <file-upload :action="'/image/upload'" :maxSize="5 * 1024 * 1024"
  33. :fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp', 'image/gif']"
  34. @before="onImageBefore" @success="onImageSuccess" @fail="onImageFail">
  35. <i class="el-icon-picture-outline"></i>
  36. </file-upload>
  37. </div>
  38. <div title="发送文件">
  39. <file-upload ref="fileUpload" :action="'/file/upload'" :maxSize="10 * 1024 * 1024"
  40. @before="onFileBefore" @success="onFileSuccess" @fail="onFileFail">
  41. <i class="el-icon-wallet"></i>
  42. </file-upload>
  43. </div>
  44. <div title="回执消息" v-show="chat.type == 'GROUP' && memberSize <= 500"
  45. class="icon iconfont icon-receipt" :class="isReceipt ? 'chat-tool-active' : ''"
  46. @click="onSwitchReceipt">
  47. </div>
  48. <div title="发送语音" class="el-icon-microphone" @click="showRecordBox()">
  49. </div>
  50. <div title="语音通话" v-show="chat.type == 'PRIVATE'" class="el-icon-phone-outline"
  51. @click="showPrivateVideo('voice')">
  52. </div>
  53. <div title="语音通话" v-show="chat.type == 'GROUP'" class="el-icon-phone-outline"
  54. @click="onGroupVideo()">
  55. </div>
  56. <div title="视频通话" v-show="chat.type == 'PRIVATE'" class="el-icon-video-camera"
  57. @click="showPrivateVideo('video')">
  58. </div>
  59. <div title="聊天记录" class="el-icon-chat-dot-round" @click="showHistoryBox()"></div>
  60. </div>
  61. <div class="send-content-area">
  62. <ChatInput :ownerId="group.ownerId" ref="chatInputEditor" :group-members="groupMembers"
  63. @submit="sendMessage" />
  64. <div class="send-btn-area">
  65. <el-button type="primary" icon="el-icon-s-promotion"
  66. @click="notifySend()">发送</el-button>
  67. </div>
  68. </div>
  69. </el-footer>
  70. </el-container>
  71. <el-aside class="chat-group-side-box" width="320px" v-if="showSide">
  72. <chat-group-side :group="group" :groupMembers="groupMembers" @reload="loadGroup(group.id)">
  73. </chat-group-side>
  74. </el-aside>
  75. </el-container>
  76. </el-main>
  77. <emotion ref="emoBox" @emotion="onEmotion"></Emotion>
  78. <chat-record :visible="showRecord" @close="closeRecordBox" @send="onSendRecord"></chat-record>
  79. <group-member-selector ref="rtcSel" :group="group" @complete="onInviteOk"></group-member-selector>
  80. <rtc-group-join ref="rtcJoin" :groupId="group.id"></rtc-group-join>
  81. <chat-history :visible="showHistory" :chat="chat" :friend="friend" :group="group"
  82. :groupMembers="groupMembers" @close="closeHistoryBox"></chat-history>
  83. </el-container>
  84. </div>
  85. </template>
  86. <script>
  87. import ChatGroupSide from "./ChatGroupSide.vue";
  88. import ChatMessageItem from "./ChatMessageItem.vue";
  89. import FileUpload from "../common/FileUpload.vue";
  90. import Emotion from "../common/Emotion.vue";
  91. import ChatRecord from "./ChatRecord.vue";
  92. import ChatHistory from "./ChatHistory.vue";
  93. import ChatAtBox from "./ChatAtBox.vue"
  94. import GroupMemberSelector from "../group/GroupMemberSelector.vue"
  95. import RtcGroupJoin from "../rtc/RtcGroupJoin.vue"
  96. import ChatInput from "./ChatInput";
  97. export default {
  98. name: "chatPrivate",
  99. components: {
  100. ChatInput,
  101. ChatMessageItem,
  102. FileUpload,
  103. ChatGroupSide,
  104. Emotion,
  105. ChatRecord,
  106. ChatHistory,
  107. ChatAtBox,
  108. GroupMemberSelector,
  109. RtcGroupJoin
  110. },
  111. props: {
  112. chat: {
  113. type: Object
  114. }
  115. },
  116. data() {
  117. return {
  118. userInfo: {},
  119. group: {},
  120. groupMembers: [],
  121. sendImageUrl: "",
  122. sendImageFile: "",
  123. placeholder: "",
  124. isReceipt: true,
  125. showRecord: false, // 是否显示语音录制弹窗
  126. showSide: false, // 是否显示群聊信息栏
  127. showHistory: false, // 是否显示历史聊天记录
  128. lockMessage: false, // 是否锁定发送,
  129. showMinIdx: 0, // 下标低于showMinIdx的消息不显示,否则页面会很卡置
  130. reqQueue: [],
  131. isSending: false
  132. }
  133. },
  134. methods: {
  135. moveChatToTop() {
  136. let chatIdx = this.$store.getters.findChatIdx(this.chat);
  137. this.$store.commit("moveTop", chatIdx);
  138. },
  139. closeRefBox() {
  140. this.$refs.emoBox.close();
  141. // this.$refs.atBox.close();
  142. },
  143. onCall(type) {
  144. if (type == this.$enums.MESSAGE_TYPE.ACT_RT_VOICE) {
  145. this.showPrivateVideo('voice');
  146. } else if (type == this.$enums.MESSAGE_TYPE.ACT_RT_VIDEO) {
  147. this.showPrivateVideo('video');
  148. }
  149. },
  150. onSwitchReceipt() {
  151. this.isReceipt = !this.isReceipt;
  152. this.refreshPlaceHolder();
  153. },
  154. onImageSuccess(data, file) {
  155. let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
  156. msgInfo.content = JSON.stringify(data);
  157. msgInfo.receipt = this.isReceipt;
  158. this.sendMessageRequest(msgInfo).then((m) => {
  159. msgInfo.loadStatus = 'ok';
  160. msgInfo.id = m.id;
  161. this.isReceipt = false;
  162. this.$store.commit("insertMessage", [msgInfo, file.chat]);
  163. })
  164. },
  165. onImageFail(e, file) {
  166. let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
  167. msgInfo.loadStatus = 'fail';
  168. this.$store.commit("insertMessage", [msgInfo, file.chat]);
  169. },
  170. onImageBefore(file) {
  171. // 被封禁提示
  172. if (this.isBanned) {
  173. this.showBannedTip();
  174. return;
  175. }
  176. let url = URL.createObjectURL(file);
  177. let data = {
  178. originUrl: url,
  179. thumbUrl: url
  180. }
  181. let msgInfo = {
  182. id: 0,
  183. tmpId: this.generateId(),
  184. fileId: file.uid,
  185. sendId: this.mine.id,
  186. content: JSON.stringify(data),
  187. sendTime: new Date().getTime(),
  188. selfSend: true,
  189. type: 1,
  190. readedCount: 0,
  191. loadStatus: "loading",
  192. status: this.$enums.MESSAGE_STATUS.UNSEND
  193. }
  194. // 填充对方id
  195. this.fillTargetId(msgInfo, this.chat.targetId);
  196. // 插入消息
  197. this.$store.commit("insertMessage", [msgInfo, this.chat]);
  198. // 会话置顶
  199. this.moveChatToTop();
  200. // 滚动到底部
  201. this.scrollToBottom();
  202. // 借助file对象保存
  203. file.msgInfo = msgInfo;
  204. file.chat = this.chat;
  205. },
  206. onFileSuccess(url, file) {
  207. let data = {
  208. name: file.name,
  209. size: file.size,
  210. url: url
  211. }
  212. let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
  213. msgInfo.content = JSON.stringify(data);
  214. msgInfo.receipt = this.isReceipt
  215. this.sendMessageRequest(msgInfo).then((m) => {
  216. msgInfo.loadStatus = 'ok';
  217. msgInfo.id = m.id;
  218. this.isReceipt = false;
  219. this.refreshPlaceHolder();
  220. this.$store.commit("insertMessage", [msgInfo, file.chat]);
  221. })
  222. },
  223. onFileFail(e, file) {
  224. let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
  225. msgInfo.loadStatus = 'fail';
  226. this.$store.commit("insertMessage", [msgInfo, file.chat]);
  227. },
  228. onFileBefore(file) {
  229. // 被封禁提示
  230. if (this.isBanned) {
  231. this.showBannedTip();
  232. return;
  233. }
  234. let url = URL.createObjectURL(file);
  235. let data = {
  236. name: file.name,
  237. size: file.size,
  238. url: url
  239. }
  240. let msgInfo = {
  241. id: 0,
  242. tmpId: this.generateId(),
  243. sendId: this.mine.id,
  244. content: JSON.stringify(data),
  245. sendTime: new Date().getTime(),
  246. selfSend: true,
  247. type: 2,
  248. loadStatus: "loading",
  249. readedCount: 0,
  250. status: this.$enums.MESSAGE_STATUS.UNSEND
  251. }
  252. // 填充对方id
  253. this.fillTargetId(msgInfo, this.chat.targetId);
  254. // 插入消息
  255. this.$store.commit("insertMessage", [msgInfo, this.chat]);
  256. // 会话置顶
  257. this.moveChatToTop();
  258. // 滚动到底部
  259. this.scrollToBottom();
  260. // 借助file对象透传
  261. file.msgInfo = msgInfo;
  262. file.chat = this.chat;
  263. },
  264. onCloseSide() {
  265. this.showSide = false;
  266. },
  267. onScrollToTop() {
  268. // 多展示10条信息
  269. this.showMinIdx = this.showMinIdx > 10 ? this.showMinIdx - 10 : 0;
  270. },
  271. onScroll(e) {
  272. let scrollElement = e.target
  273. let scrollTop = scrollElement.scrollTop
  274. if (scrollTop < 30) { // 在顶部,不滚动的情况
  275. // 多展示20条信息
  276. this.showMinIdx = this.showMinIdx > 20 ? this.showMinIdx - 20 : 0;
  277. }
  278. },
  279. showEmotionBox() {
  280. let width = this.$refs.emotion.offsetWidth;
  281. let left = this.$elm.fixLeft(this.$refs.emotion);
  282. let top = this.$elm.fixTop(this.$refs.emotion);
  283. this.$refs.emoBox.open({
  284. x: left + width / 2,
  285. y: top
  286. })
  287. },
  288. onEmotion(emoText) {
  289. this.$refs.chatInputEditor.insertEmoji(emoText);
  290. },
  291. showRecordBox() {
  292. this.showRecord = true;
  293. },
  294. closeRecordBox() {
  295. this.showRecord = false;
  296. },
  297. showPrivateVideo(mode) {
  298. // 检查是否被封禁
  299. if (this.isBanned) {
  300. this.showBannedTip();
  301. return;
  302. }
  303. let rtcInfo = {
  304. mode: mode,
  305. isHost: true,
  306. friend: this.friend,
  307. }
  308. // 通过home.vue打开单人视频窗口
  309. this.$eventBus.$emit("openPrivateVideo", rtcInfo);
  310. },
  311. onGroupVideo() {
  312. // 检查是否被封禁
  313. if (this.isBanned) {
  314. this.showBannedTip();
  315. return;
  316. }
  317. // 邀请成员发起通话
  318. let ids = [this.mine.id];
  319. let maxChannel = this.$store.state.configStore.webrtc.maxChannel;
  320. this.$refs.rtcSel.open(maxChannel, ids, ids,[]);
  321. },
  322. onInviteOk(members) {
  323. if (members.length < 2) {
  324. return;
  325. }
  326. let userInfos = [];
  327. members.forEach(m => {
  328. userInfos.push({
  329. id: m.userId,
  330. nickName: m.showNickName,
  331. headImage: m.headImage,
  332. isCamera: false,
  333. isMicroPhone: true
  334. })
  335. })
  336. let rtcInfo = {
  337. isHost: true,
  338. groupId: this.group.id,
  339. inviterId: this.mine.id,
  340. userInfos: userInfos
  341. }
  342. // 通过home.vue打开多人视频窗口
  343. this.$eventBus.$emit("openGroupVideo", rtcInfo);
  344. },
  345. showHistoryBox() {
  346. this.showHistory = true;
  347. },
  348. closeHistoryBox() {
  349. this.showHistory = false;
  350. },
  351. onSendRecord(data) {
  352. // 检查是否被封禁
  353. if (this.isBanned) {
  354. this.showBannedTip();
  355. return;
  356. }
  357. let msgInfo = {
  358. content: JSON.stringify(data),
  359. type: 3,
  360. receipt: this.isReceipt
  361. }
  362. // 填充对方id
  363. this.fillTargetId(msgInfo, this.chat.targetId);
  364. this.sendMessageRequest(msgInfo).then((m) => {
  365. m.selfSend = true;
  366. this.$store.commit("insertMessage", [m, this.chat]);
  367. // 会话置顶
  368. this.moveChatToTop();
  369. // 保持输入框焦点
  370. this.$refs.chatInputEditor.focus();
  371. // 滚动到底部
  372. this.scrollToBottom();
  373. // 关闭录音窗口
  374. this.showRecord = false;
  375. this.isReceipt = false;
  376. this.refreshPlaceHolder();
  377. })
  378. },
  379. fillTargetId(msgInfo, targetId) {
  380. if (this.chat.type == "GROUP") {
  381. msgInfo.groupId = targetId;
  382. } else {
  383. msgInfo.recvId = targetId;
  384. }
  385. },
  386. notifySend() {
  387. this.$refs.chatInputEditor.submit();
  388. },
  389. async sendMessage(fullList) {
  390. this.resetEditor();
  391. this.readedMessage();
  392. // 检查是否被封禁
  393. if (this.isBanned) {
  394. this.showBannedTip();
  395. return;
  396. }
  397. let sendText = this.isReceipt ? "【回执消息】" : "";
  398. let promiseList = [];
  399. for (let i = 0; i < fullList.length; i++) {
  400. let msg = fullList[i];
  401. switch (msg.type) {
  402. case "text":
  403. await this.sendTextMessage(sendText + msg.content, msg.atUserIds);
  404. break;
  405. case "image":
  406. await this.sendImageMessage(msg.content.file);
  407. break;
  408. case "file":
  409. await this.sendFileMessage(msg.content.file);
  410. break;
  411. }
  412. }
  413. },
  414. sendImageMessage(file) {
  415. return new Promise((resolve, reject) => {
  416. this.onImageBefore(file);
  417. let formData = new FormData()
  418. formData.append('file', file)
  419. this.$http.post("/image/upload", formData, {
  420. headers: {
  421. 'Content-Type': 'multipart/form-data'
  422. }
  423. }).then((data) => {
  424. this.onImageSuccess(data, file);
  425. resolve();
  426. }).catch((res) => {
  427. this.onImageFail(res, file);
  428. reject();
  429. })
  430. this.$nextTick(() => this.$refs.chatInputEditor.focus());
  431. this.scrollToBottom();
  432. });
  433. },
  434. sendTextMessage(sendText, atUserIds) {
  435. return new Promise((resolve, reject) => {
  436. if (!sendText.trim()) {
  437. reject();
  438. }
  439. let msgInfo = {
  440. content: sendText,
  441. type: 0
  442. }
  443. // 填充对方id
  444. this.fillTargetId(msgInfo, this.chat.targetId);
  445. // 被@人员列表
  446. if (this.chat.type == "GROUP") {
  447. msgInfo.atUserIds = atUserIds;
  448. msgInfo.receipt = this.isReceipt;
  449. }
  450. this.lockMessage = true;
  451. this.sendMessageRequest(msgInfo).then((m) => {
  452. m.selfSend = true;
  453. this.$store.commit("insertMessage", [m, this.chat]);
  454. // 会话置顶
  455. this.moveChatToTop();
  456. }).finally(() => {
  457. // 解除锁定
  458. this.scrollToBottom();
  459. this.isReceipt = false;
  460. resolve();
  461. });
  462. });
  463. },
  464. sendFileMessage(file) {
  465. return new Promise((resolve, reject) => {
  466. let check = this.$refs.fileUpload.beforeUpload(file);
  467. if (check) {
  468. this.$refs.fileUpload.onFileUpload({ file });
  469. }
  470. })
  471. },
  472. deleteMessage(msgInfo) {
  473. this.$confirm('确认删除消息?', '删除消息', {
  474. confirmButtonText: '确定',
  475. cancelButtonText: '取消',
  476. type: 'warning'
  477. }).then(() => {
  478. this.$store.commit("deleteMessage", [msgInfo, this.chat]);
  479. });
  480. },
  481. recallMessage(msgInfo) {
  482. this.$confirm('确认撤回消息?', '撤回消息', {
  483. confirmButtonText: '确定',
  484. cancelButtonText: '取消',
  485. type: 'warning'
  486. }).then(() => {
  487. let url = `/message/${this.chat.type.toLowerCase()}/recall/${msgInfo.id}`
  488. this.$http({
  489. url: url,
  490. method: 'delete'
  491. }).then((m) => {
  492. this.$message.success("消息已撤回");
  493. m.selfSend = true;
  494. this.$store.commit("recallMessage", [m, this.chat]);
  495. })
  496. });
  497. },
  498. readedMessage() {
  499. if (this.chat.unreadCount == 0) {
  500. return;
  501. }
  502. this.$store.commit("resetUnreadCount", this.chat)
  503. if (this.chat.type == "GROUP") {
  504. var url = `/message/group/readed?groupId=${this.chat.targetId}`
  505. } else {
  506. url = `/message/private/readed?friendId=${this.chat.targetId}`
  507. }
  508. this.$http({
  509. url: url,
  510. method: 'put'
  511. }).then(() => {})
  512. },
  513. loadReaded(fId) {
  514. this.$http({
  515. url: `/message/private/maxReadedId?friendId=${fId}`,
  516. method: 'get'
  517. }).then((id) => {
  518. this.$store.commit("readedMessage", {
  519. friendId: fId,
  520. maxId: id
  521. });
  522. });
  523. },
  524. loadGroup(groupId) {
  525. this.$http({
  526. url: `/group/find/${groupId}`,
  527. method: 'get'
  528. }).then((group) => {
  529. this.group = group;
  530. this.$store.commit("updateChatFromGroup", group);
  531. this.$store.commit("updateGroup", group);
  532. });
  533. this.$http({
  534. url: `/group/members/${groupId}`,
  535. method: 'get'
  536. }).then((groupMembers) => {
  537. this.groupMembers = groupMembers;
  538. });
  539. },
  540. updateFriendInfo() {
  541. if (this.isFriend) {
  542. // store的数据不能直接修改,深拷贝一份store的数据
  543. let friend = JSON.parse(JSON.stringify(this.friend));
  544. friend.headImage = this.userInfo.headImageThumb;
  545. friend.nickName = this.userInfo.nickName;
  546. friend.showNickName = friend.remarkNickName ? friend.remarkNickName : friend.nickName;
  547. this.$store.commit("updateChatFromFriend", friend);
  548. this.$store.commit("updateFriend", friend);
  549. } else {
  550. this.$store.commit("updateChatFromUser", this.userInfo);
  551. }
  552. },
  553. loadFriend(friendId) {
  554. // 获取好友信息
  555. this.$http({
  556. url: `/user/find/${friendId}`,
  557. method: 'GET'
  558. }).then((userInfo) => {
  559. this.userInfo = userInfo;
  560. this.updateFriendInfo();
  561. })
  562. },
  563. showName(msgInfo) {
  564. if (!msgInfo) {
  565. return ""
  566. }
  567. if (this.isGroup) {
  568. let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
  569. return member ? member.showNickName : msgInfo.sendNickName || "";
  570. } else {
  571. return msgInfo.selfSend ? this.mine.nickName : this.chat.showName
  572. }
  573. },
  574. headImage(msgInfo) {
  575. if (this.chat.type == 'GROUP') {
  576. let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
  577. return member ? member.headImage : "";
  578. } else {
  579. return msgInfo.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage
  580. }
  581. },
  582. resetEditor() {
  583. this.$nextTick(() => {
  584. this.$refs.chatInputEditor.clear();
  585. this.$refs.chatInputEditor.focus();
  586. });
  587. },
  588. scrollToBottom() {
  589. this.$nextTick(() => {
  590. let div = document.getElementById("chatScrollBox");
  591. div.scrollTop = div.scrollHeight;
  592. });
  593. },
  594. refreshPlaceHolder() {
  595. if (this.isReceipt) {
  596. this.placeholder = "【回执消息】"
  597. } else if (this.$refs.editBox && this.$refs.editBox.innerHTML) {
  598. this.placeholder = ""
  599. } else {
  600. this.placeholder = "聊点什么吧~";
  601. }
  602. },
  603. sendMessageRequest(msgInfo) {
  604. return new Promise((resolve, reject) => {
  605. // 请求入队列,防止请求"后发先至",导致消息错序
  606. this.reqQueue.push({ msgInfo, resolve, reject });
  607. this.processReqQueue();
  608. })
  609. },
  610. processReqQueue() {
  611. if (this.reqQueue.length && !this.isSending) {
  612. this.isSending = true;
  613. const reqData = this.reqQueue.shift();
  614. this.$http({
  615. url: this.messageAction,
  616. method: 'post',
  617. data: reqData.msgInfo
  618. }).then((res) => {
  619. reqData.resolve(res)
  620. }).catch((e) => {
  621. reqData.reject(e)
  622. }).finally(() => {
  623. this.isSending = false;
  624. // 发送下一条请求
  625. this.processReqQueue();
  626. })
  627. }
  628. },
  629. showBannedTip() {
  630. let msgInfo = {
  631. tmpId: this.generateId(),
  632. sendId: this.mine.id,
  633. sendTime: new Date().getTime(),
  634. type: this.$enums.MESSAGE_TYPE.TIP_TEXT
  635. }
  636. if (this.chat.type == "PRIVATE") {
  637. msgInfo.recvId = this.mine.id
  638. msgInfo.content = "该用户已被管理员封禁,原因:" + this.userInfo.reason
  639. } else {
  640. msgInfo.groupId = this.group.id;
  641. msgInfo.content = "本群聊已被管理员封禁,原因:" + this.group.reason
  642. }
  643. this.$store.commit("insertMessage", [msgInfo, this.chat]);
  644. },
  645. generateId() {
  646. // 生成临时id
  647. return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000));
  648. }
  649. },
  650. computed: {
  651. mine() {
  652. return this.$store.state.userStore.userInfo;
  653. },
  654. isFriend() {
  655. return this.$store.getters.isFriend(this.userInfo.id);
  656. },
  657. friend() {
  658. return this.$store.getters.findFriend(this.userInfo.id)
  659. },
  660. title() {
  661. let title = this.chat.showName;
  662. if (this.chat.type == "GROUP") {
  663. let size = this.groupMembers.filter(m => !m.quit).length;
  664. title += `(${size})`;
  665. }
  666. return title;
  667. },
  668. messageAction() {
  669. return `/message/${this.chat.type.toLowerCase()}/send`;
  670. },
  671. unreadCount() {
  672. return this.chat.unreadCount;
  673. },
  674. messageSize() {
  675. if (!this.chat || !this.chat.messages) {
  676. return 0;
  677. }
  678. return this.chat.messages.length;
  679. },
  680. isBanned() {
  681. return (this.chat.type == "PRIVATE" && this.userInfo.isBanned) ||
  682. (this.chat.type == "GROUP" && this.group.isBanned)
  683. },
  684. memberSize() {
  685. return this.groupMembers.filter(m => !m.quit).length;
  686. }
  687. },
  688. watch: {
  689. chat: {
  690. handler(newChat, oldChat) {
  691. if (newChat.targetId > 0 && (!oldChat || newChat.type != oldChat.type ||
  692. newChat.targetId != oldChat.targetId)) {
  693. if (this.chat.type == "GROUP") {
  694. this.loadGroup(this.chat.targetId);
  695. } else {
  696. this.loadFriend(this.chat.targetId);
  697. // 加载已读状态
  698. this.loadReaded(this.chat.targetId)
  699. }
  700. // 滚到底部
  701. this.scrollToBottom();
  702. this.showSide = false;
  703. // 消息已读
  704. this.readedMessage()
  705. // 初始状态只显示30条消息
  706. let size = this.chat.messages.length;
  707. this.showMinIdx = size > 30 ? size - 30 : 0;
  708. // 重置输入框
  709. this.resetEditor();
  710. // 复位回执消息
  711. this.isReceipt = false;
  712. // 更新placeholder
  713. this.refreshPlaceHolder();
  714. }
  715. },
  716. immediate: true
  717. },
  718. messageSize: {
  719. handler(newSize, oldSize) {
  720. if (newSize > oldSize) {
  721. // 拉至底部
  722. this.scrollToBottom();
  723. }
  724. }
  725. }
  726. },
  727. mounted() {
  728. let div = document.getElementById("chatScrollBox");
  729. div.addEventListener('scroll', this.onScroll)
  730. }
  731. }
  732. </script>
  733. <style lang="scss">
  734. .chat-box {
  735. position: relative;
  736. width: 100%;
  737. background: #fff;
  738. .el-header {
  739. display: flex;
  740. justify-content: space-between;
  741. padding: 0 12px;
  742. line-height: 50px;
  743. font-size: var(--im-font-size-larger);
  744. border-bottom: var(--im-border);
  745. .btn-side {
  746. position: absolute;
  747. right: 20px;
  748. line-height: 50px;
  749. font-size: 20px;
  750. cursor: pointer;
  751. color: var(--im-text-color-light);
  752. }
  753. }
  754. .im-chat-main {
  755. padding: 0;
  756. background-color: #fff;
  757. .im-chat-box {
  758. >ul {
  759. padding: 0 20px;
  760. li {
  761. list-style-type: none;
  762. }
  763. }
  764. }
  765. }
  766. .im-chat-footer {
  767. display: flex;
  768. flex-direction: column;
  769. padding: 0;
  770. .chat-tool-bar {
  771. display: flex;
  772. position: relative;
  773. width: 100%;
  774. height: 36px;
  775. text-align: left;
  776. box-sizing: border-box;
  777. border-top: var(--im-border);
  778. padding: 4px 2px 2px 8px;
  779. >div {
  780. font-size: 22px;
  781. cursor: pointer;
  782. line-height: 30px;
  783. width: 30px;
  784. height: 30px;
  785. text-align: center;
  786. border-radius: 2px;
  787. margin-right: 8px;
  788. color: #999;
  789. transition: 0.3s;
  790. &.chat-tool-active {
  791. font-weight: 600;
  792. color: var(--im-color-primary);
  793. background-color: #ddd;
  794. }
  795. }
  796. >div:hover {
  797. color: #333;
  798. }
  799. }
  800. .send-content-area {
  801. position: relative;
  802. display: flex;
  803. flex-direction: column;
  804. height: 100%;
  805. background-color: white !important;
  806. .send-btn-area {
  807. padding: 10px;
  808. position: absolute;
  809. bottom: 4px;
  810. right: 6px;
  811. }
  812. }
  813. }
  814. .chat-group-side-box {
  815. border-left: var(--im-border);
  816. //animation: rtl-drawer-in .3s 1ms;
  817. }
  818. }
  819. </style>