qq.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  1. <template>
  2. <div style="width: 100%;height: 100%">
  3. <el-tabs class="im-tabs" type="card" v-model="appKey" @tab-click="qwUserChange">
  4. <el-tab-pane
  5. v-for="item in qwUserList"
  6. :key="item.id"
  7. :name="item.appKey">
  8. <span slot="label">
  9. <el-badge :value="getUnreadCount(item.appKey)"
  10. :max="99"
  11. v-if="getUnreadCount(item.appKey) > 0">
  12. {{ item.qwUserName }}
  13. </el-badge>
  14. <template v-else>
  15. {{ item.qwUserName }}
  16. </template>
  17. </span>
  18. </el-tab-pane>
  19. </el-tabs>
  20. <i class="el-icon-close" @click="handleClose" style="font-size: 22px;position: absolute;top: 8px;right: 8px;cursor: pointer;"/>
  21. <div class="imui-center qq-lemon-imui" v-show="showQW">
  22. <lemon-imui class="lemon-slot"
  23. :width="isMaximized ? '100vw' : windowWidth"
  24. :height="isMaximized ? '90vh' : windowHeight"
  25. :user="userData"
  26. ref="IMUI"
  27. :contact-contextmenu="contactContextmenu"
  28. :theme="theme"
  29. :hide-menu="hideMenu"
  30. :hide-menu-avatar="hideMenuAvatar"
  31. :hide-message-name="hideMessageName"
  32. :hide-message-time="hideMessageTime"
  33. :messageTimeFormat="messageTimeFormat"
  34. @change-menu="handleChangeMenu"
  35. @pull-messages="handlePullMessages"
  36. @change-contact="handleChangeContact"
  37. @change-conversation="handleChangeConversation"
  38. @message-click="handleMessageClick"
  39. @menu-avatar-click="handleMenuAvatarClick"
  40. @pick-image="handleImageClick"
  41. @send="handleSend">
  42. <template #sidebar-message-top>
  43. <div style="padding: 8px; display: flex; align-items: center;">
  44. <el-input
  45. v-model="searchKeyword"
  46. prefix-icon="el-icon-search"
  47. placeholder="搜索"
  48. size="small"
  49. clearable
  50. @input="handleSearch"
  51. style="flex: 1;height: 32px;"
  52. />
  53. <el-popover
  54. placement="bottom-end"
  55. width="200"
  56. v-model="filterPopoverVisible"
  57. trigger="click"
  58. >
  59. <!-- 这里放你的过滤选项内容 -->
  60. <div>
  61. <el-checkbox-group v-model="selectedFilters">
  62. <el-checkbox label="1">去掉重粉</el-checkbox>
  63. <el-checkbox label="2">去掉小黑屋</el-checkbox>
  64. </el-checkbox-group>
  65. <div style="margin-top: 10px; text-align: right;">
  66. <el-button size="mini" type="primary" @click="applyFilter">确定</el-button>
  67. </div>
  68. </div>
  69. <el-button
  70. slot="reference"
  71. icon="el-icon-setting"
  72. style="padding: 8px;height: 32px;"
  73. size="small"
  74. :type="selectedFilters.length > 0 ? 'primary' : 'default'"
  75. ></el-button>
  76. </el-popover>
  77. </div>
  78. </template>
  79. <template #message-title="contact">
  80. <!-- <div @click="openDrawer('right')" style="position: absolute;right: 14px;top: 10px;">-->
  81. <!-- <i class="el-icon-more" style="cursor: pointer"/>-->
  82. <!-- </div>-->
  83. <!-- <div>-->
  84. <!-- <el-button-->
  85. <!-- type="primary"-->
  86. <!-- size="small"-->
  87. <!-- style="position: absolute;right: 14px;top: 10px;z-index: 1000"-->
  88. <!-- @click="showDetail(contact.extId)"-->
  89. <!-- >详情</el-button>-->
  90. <!-- </div>-->
  91. </template>
  92. <template #message-extend="contact">
  93. <div v-if="contact.extId" style="width: 100%;height: 100%;overflow: auto">
  94. <userDetail :ext-id="contact.extId" :extend="true" ref="userDetail" />
  95. </div>
  96. </template>
  97. </lemon-imui>
  98. </div>
  99. <el-dialog :visible.sync="dialogVisible" append-to-body width="35%">
  100. <img style="width:100%;height:auto" :src="dialogImageUrl" alt="" />
  101. </el-dialog>
  102. <el-dialog :visible.sync="dialogVideoVisible"
  103. :close-on-click-modal="false"
  104. :destroy-on-close="true"
  105. :width="aplayer.pWidth"
  106. :height="aplayer.height"
  107. @close="dialogVideoVisible=false"
  108. ref="player">
  109. <videoPlayer :width="aplayer.width" :height="aplayer.height" :videoWidth="aplayer.videoWidth" :videoHeight="aplayer.videoHeight" :vid="aplayer.vid" :autoplay="true"
  110. :source="dialogVideoUrl" :cover="dialogVideoCover" ref="player"></videoPlayer>
  111. </el-dialog>
  112. <!-- 用户详情 -->
  113. <el-drawer
  114. append-to-body
  115. :with-header="false"
  116. size="35%"
  117. :destroy-on-close="true"
  118. :title="detail.title" :visible.sync="detail.open">
  119. <userDetail ref="userDetail" />
  120. </el-drawer>
  121. </div>
  122. </template>
  123. <script>
  124. import {getConversations,getMessageList,getConversation,sendMsg,getQwUserList} from '@/api/qw/im';
  125. import Conversations from "@/components/LemonUI/database/conversations";
  126. import EmojiData from "@/components/LemonUI/database/emoji";
  127. import '@/components/LemonUI/index.css';
  128. import VideoPlayer from '@/components/VideoPlayer/VueAliplayer.vue'
  129. import UserDetail from "@/views/qw/qwChat/userDetail/index.vue";
  130. import {ImSocket} from "@/utils/ImSocket";
  131. import {getToken} from '@/utils/auth'
  132. import {uploadOss} from "@/api/common";
  133. import {mapState} from "vuex";
  134. import {generateUUID} from "@/components/LemonUI/utils";
  135. import {getTime} from "@/utils";
  136. import notificationMp3 from '@/assets/voice/new-notification.mp3'
  137. export default {
  138. name: "qqChat",
  139. components: {
  140. VideoPlayer,
  141. UserDetail
  142. },
  143. props: {
  144. showQw: Boolean,
  145. isMaximized: Boolean
  146. },
  147. data() {
  148. return {
  149. contactName:'',
  150. appKey:null,
  151. qwUserId:null,
  152. qwUserList:[],
  153. theme: "default",
  154. IMUI:null,
  155. contactContextmenu: [],
  156. hideMenuAvatar: false,
  157. hideMenu: false,
  158. hideMessageName: false,
  159. hideMessageTime: true,
  160. qwUser:{},
  161. showQW:false,
  162. userData: {},
  163. contactData:null,
  164. conversationData:Conversations,
  165. dialogImageUrl: '',
  166. dialogVisible: false,
  167. dialogVideoVisible:false,
  168. dialogVideoUrl:'',
  169. dialogVideoCover:'',
  170. imageArr:[],
  171. pickUploadImgData:null,
  172. aplayer: {
  173. vid: "bf9b7e4a36d84aea8cee769765fbc28b",
  174. pWidth:"1040px",
  175. width:"1000px",
  176. height:"900px",
  177. videoWidth:"1000px",
  178. videoHeight:"900px"
  179. },
  180. player: null,
  181. roomMembers:[],
  182. roomAdmins:[],
  183. roomInfo:null,
  184. windowWidth: document.documentElement.clientWidth * 0.8, //实时屏幕宽度
  185. windowHeight: document.documentElement.clientHeight * 0.85, //实时屏幕高度
  186. queryParams: {
  187. pageNum: 1,
  188. pageSize: 10,
  189. conversationId: null,
  190. userId:null
  191. },
  192. detail: {
  193. title: '',
  194. open: false
  195. },
  196. imSocket: null,
  197. imUrl: process.env.VUE_APP_IM_WS_URL,
  198. searchKeyword: '',
  199. filterPopoverVisible: false,
  200. selectedFilters: [],
  201. // 存储每个企微账号的会话记录和状态
  202. qwUserSessions: {},
  203. // 存储每个企微账号的分页信息
  204. qwUserPages: {},
  205. // 公司ID,用于WebSocket连接
  206. companyId: null,
  207. // 存储每个企微账号的未读消息数
  208. qwUserUnreadCount: [],
  209. };
  210. },
  211. created(){
  212. // 初始化企微账号会话页码对象
  213. this.qwUserPages = {};
  214. //获取企微列表
  215. getQwUserList().then(response => {
  216. this.qwUserList = response.data;
  217. if (this.qwUserList.length>0){
  218. this.qwUser = this.qwUserList[0];
  219. this.appKey = this.qwUser.appKey;
  220. this.companyId = this.qwUser.companyId; // 保存公司ID用于WebSocket连接
  221. this.setQwUserInfo();
  222. this.getConversation();
  223. // 初始化单个WebSocket连接
  224. this.initImSocket();
  225. // 初始化未读消息数
  226. this.initUnreadCount();
  227. }else {
  228. this.qwUser={};
  229. }
  230. });
  231. },
  232. computed: {
  233. ...mapState('qwIm', ['shareCourse']),
  234. // 计算所有企微账号的未读消息总数
  235. totalUnreadCount() {
  236. return this.qwUserUnreadCount.reduce((sum, item) => sum + item.unreadCount, 0);
  237. },
  238. },
  239. watch: {
  240. showQw(nv, ov) {
  241. if (nv) {
  242. this.clearUnreadCount(this.appKey)
  243. this.$nextTick(() => {
  244. this.$refs.IMUI.messageViewToBottom();
  245. });
  246. }
  247. },
  248. shareCourse(nv) {
  249. if (nv) {
  250. // 发送小程序卡片消息
  251. const IMUI = this.$refs.IMUI;
  252. const contact = IMUI.currentContact;
  253. if (!contact) {
  254. return;
  255. }
  256. // 创建消息对象
  257. let message = {
  258. type: 'miniprogram',
  259. content: nv,
  260. toContactId: contact.conversationId,
  261. };
  262. this.detail.open = false
  263. this.$store.dispatch('qwIm/shareCourse', null)
  264. // 使用handleSend方法发送消息
  265. this.handleSend(
  266. message,
  267. (replaceMessage = { status: "succeed" }) => {
  268. IMUI.updateMessage(Object.assign(message, replaceMessage));
  269. },
  270. null
  271. );
  272. }
  273. },
  274. // 监听未读消息数变化
  275. qwUserUnreadCount: {
  276. handler() {
  277. this.$store.dispatch('qwIm/totalUnreadCount', this.totalUnreadCount)
  278. },
  279. deep: true // 深度监听对象内部变化
  280. }
  281. },
  282. mounted() {
  283. this.showQW=true;
  284. const IMUI = this.$refs.IMUI;
  285. const conversationData = Conversations.filter(item => item.conversationId <= 1);
  286. conversationData.sort((a1, a2) => {
  287. return a2.lastSendTime - a1.lastSendTime;
  288. });
  289. IMUI.initMenus([
  290. {
  291. name: "messages",
  292. }
  293. ]);
  294. IMUI.initEmoji(EmojiData);
  295. IMUI.initEditorTools([
  296. { name: "emoji" },
  297. { name: "uploadImage" }
  298. ])
  299. },
  300. beforeDestroy() {
  301. // 关闭WebSocket连接
  302. if (this.imSocket) {
  303. this.imSocket.close();
  304. this.imSocket = null;
  305. }
  306. this.clearSessionCache()
  307. },
  308. methods: {
  309. getUnreadCount(appKey) {
  310. const index = this.qwUserUnreadCount.findIndex(entry => entry.appKey === appKey);
  311. return index !== -1 ? this.qwUserUnreadCount[index].unreadCount : 0;
  312. },
  313. // 清除会话缓存
  314. clearSessionCache() {
  315. this.qwUserSessions = {};
  316. this.qwUserPages = {};
  317. },
  318. // 初始化WebSocket连接
  319. initImSocket() {
  320. // 如果已经有连接,先关闭
  321. if (this.imSocket) {
  322. this.imSocket.close();
  323. }
  324. // 创建新的WebSocket连接,使用公司ID
  325. this.imSocket = new ImSocket(`${this.imUrl}/${this.companyId}?token=${getToken()}`);
  326. // 处理接收到的消息
  327. this.imSocket.onMessage(data => {
  328. const { IMUI } = this.$refs;
  329. try {
  330. let message = JSON.parse(data);
  331. this.appendRemoteMessage(message);
  332. // 播放声音
  333. const audio = new Audio(notificationMp3);
  334. audio.play().catch(err => {
  335. console.warn("播放声音失败", err);
  336. });
  337. // 确定消息所属的企微账号
  338. const messageAppKey = message.appKey;
  339. // 检查消息是否属于当前企微账号
  340. if (messageAppKey === this.appKey) {
  341. if (!this.showQw) {
  342. this.incrementUnreadCount(messageAppKey);
  343. }
  344. let conversation = IMUI.findConversation(message.toContactId);
  345. if (conversation) {
  346. conversation.lastSendTime = message.sendTime;
  347. conversation.lastContent = IMUI.lastContentRender(message);
  348. IMUI.topPopConversations(conversation);
  349. // 更新缓存中的会话记录
  350. this.updateSessionConversation(conversation);
  351. } else {
  352. // 如果找不到会话,可能需要刷新会话列表
  353. this.refreshCurrentSession();
  354. }
  355. } else if (messageAppKey) {
  356. // 如果不是当前账号的消息,增加对应账号的未读消息数
  357. this.incrementUnreadCount(messageAppKey);
  358. if (this.qwUserSessions[messageAppKey]) {
  359. const index = this.qwUserSessions[messageAppKey].findIndex(
  360. item => item.conversationId === message.toContactId
  361. );
  362. if (index !== -1) {
  363. let conversation = this.qwUserSessions[messageAppKey][index]
  364. conversation.lastSendTime = message.sendTime;
  365. conversation.lastContent = IMUI.lastContentRender(message);
  366. conversation.unread = conversation.unread + 1;
  367. this.qwUserSessions[messageAppKey][index] = {...conversation};
  368. }
  369. }
  370. }
  371. } catch (error) {
  372. console.error("处理WebSocket消息时出错:", error);
  373. }
  374. });
  375. },
  376. // 初始化未读消息数
  377. initUnreadCount() {
  378. // 为每个企微账号初始化未读消息数为0
  379. this.qwUserUnreadCount = []; // 先清空
  380. this.qwUserList.forEach(user => {
  381. this.qwUserUnreadCount.push({
  382. appKey: user.appKey,
  383. qwUserId: user.qwUserId,
  384. unreadCount: 0
  385. });
  386. });
  387. },
  388. // 增加指定企微账号的未读消息数
  389. incrementUnreadCount(appKey) {
  390. if (!appKey) {
  391. console.error("incrementUnreadCount: appKey不能为空");
  392. return;
  393. }
  394. // 查找对应的企微账号记录
  395. const index = this.qwUserUnreadCount.findIndex(item => item.appKey === appKey);
  396. if (index !== -1) {
  397. // 如果找到,则增加未读消息数
  398. const newCount = this.qwUserUnreadCount[index].unreadCount + 1;
  399. this.$set(this.qwUserUnreadCount[index], 'unreadCount', newCount);
  400. } else {
  401. // 如果没找到,则添加新记录
  402. const user = this.qwUserList.find(u => u.appKey === appKey);
  403. if (user) {
  404. this.qwUserUnreadCount.push({
  405. appKey: appKey,
  406. qwUserId: user.qwUserId,
  407. unreadCount: 1
  408. });
  409. }
  410. }
  411. },
  412. // 清除指定企微账号的未读消息数
  413. clearUnreadCount(appKey) {
  414. // 查找对应的企微账号记录
  415. const index = this.qwUserUnreadCount.findIndex(item => item.appKey === appKey);
  416. if (index !== -1) {
  417. // 如果找到,则清除未读消息数
  418. this.$set(this.qwUserUnreadCount[index], 'unreadCount', 0);
  419. }
  420. },
  421. // 切换企微账号
  422. qwUserChange(tab, event){
  423. this.appKey = tab.name;
  424. let index= this.qwUserList.findIndex(item => item.appKey === tab.name);
  425. this.qwUser=this.qwUserList[index];
  426. // 清除当前账号的未读消息数
  427. this.clearUnreadCount(this.appKey);
  428. // 不需要重新创建WebSocket连接,只需更新当前账号信息
  429. this.setQwUserInfo();
  430. this.getConversation(); //获取会话信息
  431. },
  432. setQwUserInfo(){
  433. this.userData.id=this.qwUser.id;
  434. this.userData.displayName=this.qwUser.qwUserName;
  435. this.userData.avatar=this.qwUser.avatar;
  436. },
  437. getConversation(){
  438. const IMUI = this.$refs.IMUI;
  439. // 检查是否已有该企微账号的会话记录缓存
  440. if (this.qwUserSessions[this.appKey]) {
  441. // 如果有,直接使用缓存的会话记录
  442. this.conversationData = this.qwUserSessions[this.appKey];
  443. IMUI.initConversations(this.conversationData);
  444. // 恢复上次选中的会话
  445. const lastSelectedConversation = this.qwUserSessions[`${this.appKey}_selected`];
  446. if (lastSelectedConversation) {
  447. IMUI.changeContact(lastSelectedConversation);
  448. } else {
  449. // 如果没有上次选中的会话,选择第一个会话
  450. const fstConversation = this.conversationData[0];
  451. if(fstConversation) {
  452. IMUI.changeContact(fstConversation.conversationId);
  453. }
  454. }
  455. } else {
  456. // 如果没有缓存,则从服务器获取会话记录
  457. getConversations(this.qwUser.id).then(response => {
  458. this.conversationData = response.data;
  459. // 缓存会话记录
  460. this.qwUserSessions[this.appKey] = response.data;
  461. IMUI.initConversations(response.data);
  462. const fstConversation = this.conversationData[0];
  463. if(fstConversation) {
  464. IMUI.changeContact(fstConversation.conversationId);
  465. }
  466. });
  467. }
  468. },
  469. messageTimeFormat(time) {
  470. return this.friendlyDate(time);
  471. },
  472. openDrawer(position) {
  473. const IMUI = this.$refs.IMUI;
  474. const params = {
  475. width: '30%',
  476. position,
  477. render: contact => {
  478. return (
  479. <div style="padding:15px">
  480. <h5>{contact.displayName}</h5>
  481. </div>
  482. );
  483. },
  484. };
  485. IMUI.changeDrawer(params);
  486. },
  487. handlePullMessages(contact, next,instance) {
  488. const { IMUI } = this.$refs;
  489. let isEnd = false;
  490. getMessageList(this.queryParams).then(response => {
  491. if(response.code==200){
  492. isEnd=response.data.isLastPage;
  493. next(response.data.list, isEnd);
  494. if(!isEnd){
  495. this.qwUserPages[contact.conversationId]++;
  496. this.queryParams.pageNum=this.qwUserPages[contact.conversationId];
  497. this.queryParams.userId = this.qwUser.id
  498. }
  499. }
  500. });
  501. },
  502. handleChangeConversation(conversation, instance) {
  503. // 保存当前选中的会话ID
  504. this.qwUserSessions[`${this.appKey}_selected`] = conversation.conversationId;
  505. // 检查是否已有该会话的分页信息
  506. if (!this.qwUserPages[conversation.conversationId]){
  507. this.qwUserPages[conversation.conversationId] =1;
  508. }
  509. this.queryParams.pageNum=this.qwUserPages[conversation.conversationId];
  510. this.queryParams.conversationId=conversation.conversationId;
  511. this.queryParams.userId = this.qwUser.id
  512. if(conversation.unread>0){
  513. conversation.unread=0;
  514. instance.updateContact(conversation);
  515. // 更新缓存中的会话记录
  516. this.updateSessionConversation(conversation);
  517. }
  518. },
  519. // 更新缓存中的会话记录
  520. updateSessionConversation(conversation) {
  521. if (this.qwUserSessions[this.appKey]) {
  522. const index = this.qwUserSessions[this.appKey].findIndex(
  523. item => item.conversationId === conversation.conversationId
  524. );
  525. if (index !== -1) {
  526. this.qwUserSessions[this.appKey][index] = {...conversation};
  527. }
  528. }
  529. },
  530. handleChangeContact(contact, instance) {
  531. console.log("ChangeContact:", contact, instance)
  532. },
  533. //收到消息后添加消息显示
  534. appendMessageAction(msgData){
  535. if(msgData.type=="text" || msgData.type=="image" || msgData.type=="voice"){ //文本 text image video voice
  536. const message = {
  537. id: msgData.id,
  538. status: msgData.status,
  539. type: msgData.type,
  540. sendTime: msgData.sendTime,
  541. content: msgData.content,
  542. params1: "1",
  543. params2: "2",
  544. toContactId: msgData.toContactId,
  545. fromUser: msgData.fromUser,
  546. };
  547. this.appendRemoteMessage(message);
  548. }
  549. else if(msgData.type=="file"){
  550. const message = {
  551. id: msgData.id,
  552. status: msgData.status,
  553. type: msgData.type,
  554. sendTime: msgData.sendTime,
  555. content: msgData.content,
  556. toContactId: msgData.toContactId,
  557. fromUser: msgData.fromUser,
  558. fileName:msgData.fileName,
  559. fileSize:msgData.fileSize
  560. };
  561. this.appendRemoteMessage(message);
  562. }
  563. else{
  564. const message = {
  565. id: msgData.id,
  566. status: msgData.status,
  567. type: msgData.type,
  568. sendTime: msgData.sendTime,
  569. content: msgData.content,
  570. toContactId: msgData.toContactId,
  571. fromUser: msgData.fromUser,
  572. };
  573. this.appendRemoteMessage(message);
  574. }
  575. },
  576. //发送消息
  577. handleSend(message, next, file) {
  578. const IMUI = this.$refs.IMUI;
  579. let params = {};
  580. if(message.type === "text"){ //text image voice video
  581. params = {"sessionId":message.toContactId,"appKey":this.qwUser.appKey,"content":message.content, "msgType": 1};
  582. sendMsg(params).then(response => {
  583. const {code, data} = response
  584. if(code === 200){
  585. this.appendRemoteMessage(data)
  586. let conversation = IMUI.findConversation(message.toContactId);
  587. conversation.lastSendTime = message.sendTime;
  588. conversation.lastContent = IMUI.lastContentRender(message)
  589. IMUI.topPopConversations(conversation);
  590. // 更新缓存中的会话记录
  591. this.updateSessionConversation(conversation);
  592. next();
  593. } else {
  594. next({status:'failed'})
  595. }
  596. });
  597. }
  598. // image
  599. else if(message.type === "image"){
  600. const formData = new FormData();
  601. formData.append("file", file);
  602. uploadOss(formData).then(response => {
  603. const {code, url} = response
  604. if (code === 200) {
  605. params = {"sessionId":message.toContactId,"appKey":this.qwUser.appKey,"content":url, "msgType": 2};
  606. sendMsg(params).then(response => {
  607. const {code} = response
  608. if(code === 200){
  609. let conversation = IMUI.findConversation(message.toContactId);
  610. conversation.lastSendTime = message.sendTime;
  611. conversation.lastContent = IMUI.lastContentRender(message)
  612. IMUI.topPopConversations(conversation);
  613. // 更新缓存中的会话记录
  614. this.updateSessionConversation(conversation);
  615. next();
  616. } else {
  617. next({status:'failed'})
  618. }
  619. });
  620. }
  621. })
  622. }
  623. // 小程序
  624. else if(message.type === "miniprogram"){
  625. // 小程序消息参数
  626. params = {
  627. "sessionId": message.toContactId,
  628. "appKey": this.qwUser.appKey,
  629. "content": message.content,
  630. "msgType": 5 // 小程序消息类型
  631. };
  632. sendMsg(params).then(response => {
  633. const {code, data} = response
  634. if(code === 200){
  635. this.appendRemoteMessage(data)
  636. let conversation = IMUI.findConversation(message.toContactId);
  637. conversation.lastSendTime = message.sendTime;
  638. conversation.lastContent = IMUI.lastContentRender(message)
  639. IMUI.topPopConversations(conversation);
  640. // 更新缓存中的会话记录
  641. this.updateSessionConversation(conversation);
  642. }
  643. });
  644. }
  645. // file
  646. else if(message.type === "file"){
  647. console.log("Event:file-click", message, next, file)
  648. }
  649. },
  650. handleMenuAvatarClick() {
  651. console.log("Event:menu-avatar-click");
  652. },
  653. //聊天工具栏点击图片
  654. handleImageClick() {
  655. // this.$refs.material.openMaterial(this.qwUser.deviceId);
  656. console.log("Event:handleImageClick");
  657. },
  658. //选择图片框确定按钮回调
  659. handlePickImageDone(data){
  660. console.log("handlePickImageDone:"+JSON.stringify(data));
  661. this.pickUploadImgData=data;
  662. const IMUI = this.$refs.IMUI;
  663. IMUI._handleRemoteImage(data.url);
  664. },
  665. tooglePlayVideo(data){
  666. this.dialogVideoVisible=true;
  667. this.dialogVideoUrl=data.content;
  668. this.dialogVideoCover=data.url;
  669. const player = this.$refs.player.instance
  670. player && player.play()
  671. },
  672. handleMessageClick(e, key, message, instance) {
  673. if (key === 'avatar') {
  674. this.qwUser.id !== message.fromUser.id && this.showDetail(message.extId)
  675. return
  676. }
  677. if(message.type=="image"){
  678. var url=!!message.url?message.url:message.content;
  679. this.handlePicturePreview(url);
  680. }
  681. else if(message.type=="video"){
  682. this.tooglePlayVideo(message);
  683. }
  684. else if(message.type=="file"){
  685. }
  686. if (key == "status") {
  687. instance.updateMessage({
  688. id: message.id,
  689. status: "going",
  690. content: "正在重新发送消息...",
  691. });
  692. setTimeout(() => {
  693. instance.updateMessage({
  694. id: message.id,
  695. status: "succeed",
  696. content: "发送成功",
  697. });
  698. }, 2000);
  699. }
  700. },
  701. changeMenuAvatarVisible() {
  702. this.hideMenuAvatar = !this.hideMenuAvatar;
  703. },
  704. changeMenuVisible() {
  705. this.hideMenu = !this.hideMenu;
  706. },
  707. changeMessageNameVisible() {
  708. this.hideMessageName = !this.hideMessageName;
  709. },
  710. changeMessageTimeVisible() {
  711. this.hideMessageTime = !this.hideMessageTime;
  712. },
  713. removeMessage() {
  714. const { IMUI } = this.$refs;
  715. const messages = IMUI.getCurrentMessages();
  716. const id = messages[messages.length - 1].id;
  717. if (messages.length > 0) {
  718. IMUI.removeMessage(id);
  719. }
  720. },
  721. updateMessage() {
  722. const { IMUI } = this.$refs;
  723. const messages = IMUI.getCurrentMessages();
  724. const message = messages[messages.length - 1];
  725. if (messages.length > 0) {
  726. const update = {
  727. id: message.id,
  728. status: "succeed",
  729. type: "file",
  730. fileName: "被修改成文件了.txt",
  731. fileSize: "4200000",
  732. };
  733. if (message.type == "event") {
  734. update.fromUser = this.user;
  735. }
  736. IMUI.updateMessage(update);
  737. IMUI.messageViewToBottom();
  738. }
  739. },
  740. appendCustomMessage() {
  741. const { IMUI } = this.$refs;
  742. const message = {
  743. id: generateUUID(),
  744. status: "succeed",
  745. type: "voice",
  746. sendTime: getTime(),
  747. content: "语音消息",
  748. params1: "1",
  749. params2: "2",
  750. toContactId: "contact-1",
  751. fromUser: this.user,
  752. };
  753. IMUI.appendMessage(message, true);
  754. },
  755. appendMessage() {
  756. const { IMUI } = this.$refs;
  757. const contact = IMUI.currentContact;
  758. const message = generateMessage("contact-3");
  759. message.fromUser = {
  760. ...message.fromUser,
  761. ...this.user,
  762. };
  763. IMUI.appendMessage(message, true);
  764. console.log("🚀 ~ file: App.vue ~ line 1508 ~ appendMessage ~ message", message)
  765. },
  766. appendRemoteMessage(message) { //从服务端返回的消息
  767. const { IMUI } = this.$refs;
  768. if (!IMUI) {
  769. console.error("IMUI引用不存在,无法添加消息");
  770. return;
  771. }
  772. try {
  773. // 确保消息对象包含必要的字段
  774. if (!message.id) {
  775. message.id = generateUUID(); // 使用UUID生成唯一ID
  776. }
  777. if (!message.status) {
  778. message.status = "succeed";
  779. }
  780. if (!message.sendTime) {
  781. message.sendTime = new Date().getTime();
  782. }
  783. // 根据消息类型处理
  784. switch (message.type) {
  785. case "text":
  786. // 文本消息不需要特殊处理
  787. break;
  788. case "image":
  789. // 确保图片消息有content或url字段
  790. if (!message.content && message.url) {
  791. message.content = message.url;
  792. }
  793. break;
  794. case "voice":
  795. // 确保语音消息有content字段
  796. if (!message.content) {
  797. message.content = "语音消息";
  798. }
  799. // 添加语音时长
  800. if (message.duration) {
  801. message.params1 = message.duration.toString();
  802. }
  803. break;
  804. case "file":
  805. // 确保文件消息有fileName和fileSize字段
  806. if (!message.fileName) {
  807. message.fileName = "未命名文件";
  808. }
  809. if (!message.fileSize && message.fileSize !== 0) {
  810. message.fileSize = 0;
  811. }
  812. break;
  813. case "miniprogram":
  814. // 小程序消息处理
  815. if (!message.content) {
  816. message.content = "小程序卡片";
  817. }
  818. break;
  819. default:
  820. console.warn("未知消息类型:", message.type);
  821. break;
  822. }
  823. // 添加消息到UI
  824. IMUI.appendMessage(message, true);
  825. // 更新缓存中的会话记录
  826. if (message.toContactId && IMUI.conversations) {
  827. const conversation = IMUI.findConversation(message.toContactId);
  828. if (conversation) {
  829. this.updateSessionConversation(conversation);
  830. }
  831. }
  832. } catch (error) {
  833. console.error("添加远程消息时出错:", error);
  834. }
  835. },
  836. handleChangeMenu() {
  837. console.log("Event:change-menu");
  838. },
  839. handlePicturePreview(url) {
  840. this.dialogImageUrl = url;
  841. this.dialogVisible = true;
  842. },
  843. // 详情
  844. showDetail(extId) {
  845. this.detail.open = true
  846. setTimeout(() => {
  847. this.$refs.userDetail.getDetail(extId);
  848. }, 1);
  849. },
  850. // 搜索
  851. handleSearch() {
  852. const keyword = this.searchKeyword.trim().toLowerCase();
  853. if (!keyword) {
  854. // 为空时显示全部
  855. this.$refs.IMUI.initConversations(this.conversationData);
  856. return;
  857. }
  858. // 过滤会话列表
  859. const filtered = this.conversationData.filter(item => {
  860. // 搜索最后一条消息内容和联系人名
  861. return (
  862. (item.lastContent && item.lastContent.toLowerCase().includes(keyword)) ||
  863. (item.displayName && item.displayName.toLowerCase().includes(keyword))
  864. );
  865. });
  866. this.$refs.IMUI.initConversations(filtered);
  867. },
  868. applyFilter() {
  869. this.filterPopoverVisible = false;
  870. // 这里可以根据 selectedFilters 做过滤
  871. this.handleSearch();
  872. },
  873. // 手动刷新当前企微账号的会话列表
  874. refreshCurrentSession() {
  875. // 删除当前企微账号的会话缓存
  876. if (this.appKey) {
  877. delete this.qwUserSessions[this.appKey];
  878. delete this.qwUserSessions[`${this.appKey}_selected`];
  879. // 重新获取会话列表
  880. this.getConversation();
  881. this.$message({
  882. message: '会话列表已刷新',
  883. type: 'success'
  884. });
  885. }
  886. },
  887. handleClose() {
  888. this.$emit('close')
  889. }
  890. },
  891. };
  892. </script>
  893. <style lang="scss" scoped>
  894. .imui-center{
  895. align-items: center;
  896. justify-content: center;
  897. }
  898. .lemon-drawer{
  899. border:1px solid #ddd;
  900. border-left:0;
  901. }
  902. .more {
  903. font-size: 12px;
  904. line-height: 24px;
  905. height: 24px;
  906. position: absolute;
  907. top: 14px;
  908. right: 14px;
  909. cursor: pointer;
  910. -webkit-user-select: none;
  911. -moz-user-select: none;
  912. -ms-user-select: none;
  913. user-select: none;
  914. color: #f1f1f1;
  915. display: inline-block;
  916. border-radius: 4px;
  917. background: #111;
  918. padding: 0 8px;
  919. }
  920. /deep/.el-dialog__headerbtn{
  921. top:10px;
  922. }
  923. /deep/.el-dialog:not(.is-fullscreen) {
  924. margin-top: 0 !important;
  925. }
  926. ::v-deep .im-tabs {
  927. width: 97%;
  928. .el-tabs__header {
  929. margin: 0 !important;
  930. .el-tabs__item {
  931. border-bottom: 0 !important;
  932. }
  933. }
  934. /* 未读消息徽标样式 */
  935. .el-badge__content {
  936. border: none;
  937. font-size: 12px;
  938. font-weight: bold;
  939. background-color: #F56C6C;
  940. transform: translateY(-2px);
  941. right: unset
  942. }
  943. }
  944. </style>
  945. <style lang="stylus">
  946. .lemon-container__title{
  947. padding-bottom:10px;
  948. }
  949. .slot-group{
  950. width:200px;
  951. }
  952. .slot-group-title{
  953. padding:0 0 10px 0;
  954. }
  955. .slot-group-notice{
  956. padding: 10px 10px;
  957. }
  958. .slot-search{
  959. width:calc(100% - 20px) ;
  960. margin:5px 10px;
  961. padding:3px 0px;
  962. }
  963. .slot-group-member{
  964. display: flex;
  965. padding: 5px 0;
  966. flex:1;
  967. font-size: 14px;
  968. align-items: center;
  969. }
  970. .avatar{
  971. width: 30px;
  972. height: 30px;
  973. line-height: 30px;
  974. margin-right:5px;
  975. }
  976. .img{
  977. vertical-align: middle;
  978. border-style: none;
  979. width: 100%;
  980. height: 100%;
  981. line-height: 30px;
  982. border-radius: 50%;
  983. }
  984. </style>