qq.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  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. :label="item.qwUserId"
  8. :name="item.appKey"/>
  9. </el-tabs>
  10. <div class="imui-center qq-lemon-imui" v-show="showQW">
  11. <lemon-imui class="lemon-slot"
  12. :width="isMaximized ? '100vw' : windowWidth"
  13. :height="isMaximized ? '90vh' : windowHeight"
  14. :user="userData"
  15. ref="IMUI"
  16. :contact-contextmenu="contactContextmenu"
  17. :theme="theme"
  18. :hide-menu="hideMenu"
  19. :hide-menu-avatar="hideMenuAvatar"
  20. :hide-message-name="hideMessageName"
  21. :hide-message-time="hideMessageTime"
  22. :messageTimeFormat="messageTimeFormat"
  23. @change-menu="handleChangeMenu"
  24. @pull-messages="handlePullMessages"
  25. @change-contact="handleChangeContact"
  26. @change-conversation="handleChangeConversation"
  27. @message-click="handleMessageClick"
  28. @menu-avatar-click="handleMenuAvatarClick"
  29. @pick-image="handleImageClick"
  30. @send="handleSend">
  31. <template #sidebar-message-top>
  32. <div style="padding: 8px;">
  33. <el-input
  34. v-model="searchKeyword"
  35. prefix-icon="el-icon-search"
  36. placeholder="搜索"
  37. size="small"
  38. clearable
  39. @input="handleSearch"
  40. style="width: 100%;"
  41. />
  42. </div>
  43. </template>
  44. <template #message-title>
  45. <!-- <div @click="openDrawer('right')" style="position: absolute;right: 14px;top: 10px;">-->
  46. <!-- <i class="el-icon-more" style="cursor: pointer"/>-->
  47. <!-- </div>-->
  48. </template>
  49. </lemon-imui>
  50. </div>
  51. <el-dialog :visible.sync="dialogVisible" append-to-body width="35%">
  52. <img style="width:100%;height:auto" :src="dialogImageUrl" alt="" />
  53. </el-dialog>
  54. <el-dialog :visible.sync="dialogVideoVisible"
  55. :close-on-click-modal="false"
  56. :destroy-on-close="true"
  57. :width="aplayer.pWidth"
  58. :height="aplayer.height"
  59. @close="dialogVideoVisible=false"
  60. ref="player">
  61. <videoPlayer :width="aplayer.width" :height="aplayer.height" :videoWidth="aplayer.videoWidth" :videoHeight="aplayer.videoHeight" :vid="aplayer.vid" :autoplay="true"
  62. :source="dialogVideoUrl" :cover="dialogVideoCover" ref="player"></videoPlayer>
  63. </el-dialog>
  64. <!-- 用户详情 -->
  65. <el-drawer
  66. append-to-body
  67. :with-header="false"
  68. size="35%"
  69. :title="detail.title" :visible.sync="detail.open">
  70. <userDetail ref="userDetail" />
  71. </el-drawer>
  72. </div>
  73. </template>
  74. <script>
  75. import {getConversations,getMessageList,getConversation,sendMsg,getQwUserList} from '@/api/qw/im';
  76. import Conversations from "@/components/LemonUI/database/conversations";
  77. import EmojiData from "@/components/LemonUI/database/emoji";
  78. import '@/components/LemonUI/index.css';
  79. import VideoPlayer from '@/components/VideoPlayer/VueAliplayer.vue'
  80. import UserDetail from "@/views/qw/qwChat/userDetail/index.vue";
  81. import {ImSocket} from "@/utils/ImSocket";
  82. import {getToken} from '@/utils/auth'
  83. import {uploadOss} from "@/api/common";
  84. let pages = {};
  85. export default {
  86. name: "qqChat",
  87. components: {
  88. VideoPlayer,
  89. UserDetail
  90. },
  91. props: {
  92. showQw: Boolean,
  93. isMaximized: Boolean
  94. },
  95. data() {
  96. return {
  97. contactName:'',
  98. appKey:null,
  99. qwUserId:null,
  100. qwUserList:[],
  101. theme: "default",
  102. IMUI:null,
  103. contactContextmenu: [],
  104. hideMenuAvatar: false,
  105. hideMenu: false,
  106. hideMessageName: false,
  107. hideMessageTime: true,
  108. qwUser:{},
  109. showQW:false,
  110. userData: {},
  111. contactData:null,
  112. conversationData:Conversations,
  113. dialogImageUrl: '',
  114. dialogVisible: false,
  115. dialogVideoVisible:false,
  116. dialogVideoUrl:'',
  117. dialogVideoCover:'',
  118. imageArr:[],
  119. pickUploadImgData:null,
  120. aplayer: {
  121. vid: "bf9b7e4a36d84aea8cee769765fbc28b",
  122. pWidth:"1040px",
  123. width:"1000px",
  124. height:"900px",
  125. videoWidth:"1000px",
  126. videoHeight:"900px"
  127. },
  128. player: null,
  129. roomMembers:[],
  130. roomAdmins:[],
  131. roomInfo:null,
  132. windowWidth: document.documentElement.clientWidth*0.6, //实时屏幕宽度
  133. windowHeight: document.documentElement.clientHeight*0.7, //实时屏幕高度
  134. queryParams: {
  135. pageNum: 1,
  136. pageSize: 10,
  137. conversationId: null,
  138. userId:null
  139. },
  140. detail: {
  141. title: '',
  142. open: false
  143. },
  144. imSocket: null,
  145. imUrl: process.env.VUE_APP_IM_WS_URL,
  146. searchKeyword: '',
  147. };
  148. },
  149. created(){
  150. //获取企微列表
  151. getQwUserList().then(response => {
  152. this.qwUserList = response.data;
  153. if (this.qwUserList.length>0){
  154. this.qwUser = this.qwUserList[0];
  155. this.appKey = this.qwUser.appKey;
  156. this.setQwUserInfo();
  157. this.getConversation();
  158. this.initImSocket(this.qwUser.companyId)
  159. }else {
  160. this.qwUser={};
  161. }
  162. });
  163. },
  164. watch: {
  165. showQw(nv, ov) {
  166. if (nv) {
  167. this.$nextTick(() => {
  168. this.$refs.IMUI.messageViewToBottom();
  169. });
  170. }
  171. }
  172. },
  173. mounted() {
  174. this.showQW=true;
  175. const IMUI = this.$refs.IMUI;
  176. const conversationData = Conversations.filter(item => item.conversationId <= 1);
  177. conversationData.sort((a1, a2) => {
  178. return a2.lastSendTime - a1.lastSendTime;
  179. });
  180. IMUI.initMenus([
  181. {
  182. name: "messages",
  183. }
  184. ]);
  185. IMUI.initEmoji(EmojiData);
  186. IMUI.initEditorTools([
  187. { name: "emoji" },
  188. { name: "uploadImage" }
  189. ])
  190. },
  191. beforeDestroy() {
  192. if (this.imSocket) {
  193. this.imSocket.close();
  194. }
  195. },
  196. methods: {
  197. initImSocket(companyId) {
  198. this.imSocket = new ImSocket(`${this.imUrl}/${companyId}?token=${getToken()}`);
  199. this.imSocket.onMessage(data => {
  200. const { IMUI } = this.$refs;
  201. let message = JSON.parse(data);
  202. this.appendRemoteMessage(message)
  203. let conversation = IMUI.findConversation(message.toContactId);
  204. if (conversation.msgId) {
  205. conversation.lastSendTime = message.sendTime;
  206. conversation.lastContent = message.content;
  207. IMUI.topPopConversations(conversation);
  208. }
  209. })
  210. },
  211. // 切换企微账号
  212. qwUserChange(tab, event){
  213. this.appKey = tab.name;
  214. let index= this.qwUserList.findIndex(item => item.appKey === tab.name);
  215. this.qwUser=this.qwUserList[index];
  216. this.getConversation(); //获取会话信息
  217. this.setQwUserInfo();
  218. },
  219. setQwUserInfo(){
  220. this.userData.id=this.qwUser.id;
  221. this.userData.displayName=this.qwUser.qwUserName;
  222. this.userData.avatar="https://cos.his.cdwjyyh.com/fs/20241231/22a765a96da247d1b83ea94fef438a41.png";
  223. },
  224. getConversation(){
  225. const IMUI = this.$refs.IMUI;
  226. getConversations(this.qwUser.id).then(response => {
  227. this.conversationData = response.data;
  228. IMUI.initConversations(response.data);
  229. const fstConversation = this.conversationData[0];
  230. if(fstConversation) {
  231. IMUI.changeContact(fstConversation.conversationId);
  232. }
  233. });
  234. },
  235. messageTimeFormat(time) {
  236. return this.friendlyDate(time);
  237. },
  238. openDrawer(position) {
  239. const IMUI = this.$refs.IMUI;
  240. const params = {
  241. width: '30%',
  242. position,
  243. render: contact => {
  244. return (
  245. <div style="padding:15px">
  246. <h5>{contact.displayName}</h5>
  247. </div>
  248. );
  249. },
  250. };
  251. IMUI.changeDrawer(params);
  252. },
  253. handlePullMessages(contact, next,instance) {
  254. const { IMUI } = this.$refs;
  255. let isEnd = false;
  256. getMessageList(this.queryParams).then(response => {
  257. if(response.code==200){
  258. isEnd=response.data.isLastPage;
  259. next(response.data.list, isEnd);
  260. if(!isEnd){
  261. pages[contact.conversationId]++;
  262. this.queryParams.pageNum=pages[contact.conversationId];
  263. this.queryParams.userId = this.qwUser.id
  264. }
  265. }
  266. });
  267. },
  268. handleChangeConversation(conversation, instance) {
  269. if (!pages[conversation.conversationId]){
  270. pages[conversation.conversationId] =1;
  271. }
  272. this.queryParams.pageNum=pages[conversation.conversationId];
  273. this.queryParams.conversationId=conversation.conversationId;
  274. this.queryParams.userId = this.qwUser.id
  275. if(conversation.unread>0){
  276. conversation.unread=0;
  277. instance.updateContact(conversation);
  278. }
  279. },
  280. handleChangeContact(contact, instance) {
  281. console.log("ChangeContact:", contact, instance)
  282. },
  283. //收到消息后添加消息显示
  284. appendMessageAction(msgData){
  285. if(msgData.type=="text" || msgData.type=="image" || msgData.type=="voice"){ //文本 text image video voice
  286. const message = {
  287. id: msgData.id,
  288. status: msgData.status,
  289. type: msgData.type,
  290. sendTime: msgData.sendTime,
  291. content: msgData.content,
  292. params1: "1",
  293. params2: "2",
  294. toContactId: msgData.toContactId,
  295. fromUser: msgData.fromUser,
  296. };
  297. this.appendRemoteMessage(message);
  298. }
  299. else if(msgData.type=="file"){
  300. const message = {
  301. id: msgData.id,
  302. status: msgData.status,
  303. type: msgData.type,
  304. sendTime: msgData.sendTime,
  305. content: msgData.content,
  306. toContactId: msgData.toContactId,
  307. fromUser: msgData.fromUser,
  308. fileName:msgData.fileName,
  309. fileSize:msgData.fileSize
  310. };
  311. this.appendRemoteMessage(message);
  312. }
  313. else{
  314. const message = {
  315. id: msgData.id,
  316. status: msgData.status,
  317. type: msgData.type,
  318. sendTime: msgData.sendTime,
  319. content: msgData.content,
  320. toContactId: msgData.toContactId,
  321. fromUser: msgData.fromUser,
  322. };
  323. this.appendRemoteMessage(message);
  324. }
  325. },
  326. //发送消息
  327. handleSend(message, next, file) {
  328. const IMUI = this.$refs.IMUI;
  329. let params = {};
  330. if(message.type === "text"){ //text image voice video
  331. params = {"sessionId":message.toContactId,"appKey":this.qwUser.appKey,"content":message.content, "msgType": 1};
  332. sendMsg(params).then(response => {
  333. const {code, data} = response
  334. if(code === 200){
  335. this.appendRemoteMessage(data)
  336. let conversation = IMUI.findConversation(message.toContactId);
  337. conversation.lastSendTime = message.sendTime;
  338. conversation.lastContent = message.content;
  339. IMUI.topPopConversations(conversation);
  340. next();
  341. } else {
  342. next({status:'failed'})
  343. }
  344. });
  345. }
  346. // image
  347. else if(message.type === "image"){
  348. console.log("Event:image-click", message, next, file)
  349. const formData = new FormData();
  350. formData.append("file", file);
  351. uploadOss(formData).then(response => {
  352. const {code, url} = response
  353. if (code === 200) {
  354. params = {"sessionId":message.toContactId,"appKey":this.qwUser.appKey,"content":url, "msgType": 2};
  355. sendMsg(params).then(response => {
  356. const {code} = response
  357. if(code === 200){
  358. let conversation = IMUI.findConversation(message.toContactId);
  359. conversation.lastSendTime = message.sendTime;
  360. conversation.lastContent = message.content;
  361. IMUI.topPopConversations(conversation);
  362. next();
  363. } else {
  364. next({status:'failed'})
  365. }
  366. });
  367. }
  368. })
  369. }
  370. // file
  371. else if(message.type === "file"){
  372. console.log("Event:file-click", message, next, file)
  373. }
  374. },
  375. handleMenuAvatarClick() {
  376. console.log("Event:menu-avatar-click");
  377. },
  378. //聊天工具栏点击图片
  379. handleImageClick() {
  380. // this.$refs.material.openMaterial(this.qwUser.deviceId);
  381. console.log("Event:handleImageClick");
  382. },
  383. //选择图片框确定按钮回调
  384. handlePickImageDone(data){
  385. console.log("handlePickImageDone:"+JSON.stringify(data));
  386. this.pickUploadImgData=data;
  387. const IMUI = this.$refs.IMUI;
  388. IMUI._handleRemoteImage(data.url);
  389. },
  390. tooglePlayVideo(data){
  391. this.dialogVideoVisible=true;
  392. this.dialogVideoUrl=data.content;
  393. this.dialogVideoCover=data.url;
  394. const player = this.$refs.player.instance
  395. player && player.play()
  396. },
  397. handleMessageClick(e, key, message, instance) {
  398. if (key === 'avatar') {
  399. this.qwUser.id !== message.fromUser.id && this.showDetail(message.extId)
  400. return
  401. }
  402. if(message.type=="image"){
  403. var url=!!message.url?message.url:message.content;
  404. this.handlePicturePreview(url);
  405. }
  406. else if(message.type=="video"){
  407. this.tooglePlayVideo(message);
  408. }
  409. else if(message.type=="file"){
  410. }
  411. if (key == "status") {
  412. instance.updateMessage({
  413. id: message.id,
  414. status: "going",
  415. content: "正在重新发送消息...",
  416. });
  417. setTimeout(() => {
  418. instance.updateMessage({
  419. id: message.id,
  420. status: "succeed",
  421. content: "发送成功",
  422. });
  423. }, 2000);
  424. }
  425. },
  426. changeMenuAvatarVisible() {
  427. this.hideMenuAvatar = !this.hideMenuAvatar;
  428. },
  429. changeMenuVisible() {
  430. this.hideMenu = !this.hideMenu;
  431. },
  432. changeMessageNameVisible() {
  433. this.hideMessageName = !this.hideMessageName;
  434. },
  435. changeMessageTimeVisible() {
  436. this.hideMessageTime = !this.hideMessageTime;
  437. },
  438. removeMessage() {
  439. const { IMUI } = this.$refs;
  440. const messages = IMUI.getCurrentMessages();
  441. const id = messages[messages.length - 1].id;
  442. if (messages.length > 0) {
  443. IMUI.removeMessage(id);
  444. }
  445. },
  446. updateMessage() {
  447. const { IMUI } = this.$refs;
  448. const messages = IMUI.getCurrentMessages();
  449. const message = messages[messages.length - 1];
  450. if (messages.length > 0) {
  451. const update = {
  452. id: message.id,
  453. status: "succeed",
  454. type: "file",
  455. fileName: "被修改成文件了.txt",
  456. fileSize: "4200000",
  457. };
  458. if (message.type == "event") {
  459. update.fromUser = this.user;
  460. }
  461. IMUI.updateMessage(update);
  462. IMUI.messageViewToBottom();
  463. }
  464. },
  465. appendCustomMessage() {
  466. const { IMUI } = this.$refs;
  467. const message = {
  468. id: generateRandId(),
  469. status: "succeed",
  470. type: "voice",
  471. sendTime: getTime(),
  472. content: "语音消息",
  473. params1: "1",
  474. params2: "2",
  475. toContactId: "contact-1",
  476. fromUser: this.user,
  477. };
  478. IMUI.appendMessage(message, true);
  479. },
  480. appendMessage() {
  481. const { IMUI } = this.$refs;
  482. const contact = IMUI.currentContact;
  483. const message = generateMessage("contact-3");
  484. message.fromUser = {
  485. ...message.fromUser,
  486. ...this.user,
  487. };
  488. IMUI.appendMessage(message, true);
  489. console.log("🚀 ~ file: App.vue ~ line 1508 ~ appendMessage ~ message", message)
  490. },
  491. appendRemoteMessage(message) { //从服务端返回的消息
  492. const { IMUI } = this.$refs;
  493. IMUI.appendMessage(message, true);
  494. },
  495. handleChangeMenu() {
  496. console.log("Event:change-menu");
  497. },
  498. handlePicturePreview(url) {
  499. this.dialogImageUrl = url;
  500. this.dialogVisible = true;
  501. },
  502. // 详情
  503. showDetail(extId) {
  504. this.detail.open = true
  505. setTimeout(() => {
  506. this.$refs.userDetail.getDetail(extId);
  507. }, 1);
  508. },
  509. // 搜索
  510. handleSearch() {
  511. const keyword = this.searchKeyword.trim().toLowerCase();
  512. if (!keyword) {
  513. // 为空时显示全部
  514. this.$refs.IMUI.initConversations(this.conversationData);
  515. return;
  516. }
  517. // 过滤会话列表
  518. const filtered = this.conversationData.filter(item => {
  519. // 搜索最后一条消息内容和联系人名
  520. return (
  521. (item.lastContent && item.lastContent.toLowerCase().includes(keyword)) ||
  522. (item.displayName && item.displayName.toLowerCase().includes(keyword))
  523. );
  524. });
  525. this.$refs.IMUI.initConversations(filtered);
  526. },
  527. },
  528. };
  529. </script>
  530. <style lang="scss" scoped>
  531. .imui-center{
  532. align-items: center;
  533. justify-content: center;
  534. }
  535. .lemon-wrapper{
  536. border:1px solid #ddd;
  537. //height: !important;
  538. }
  539. .lemon-drawer{
  540. border:1px solid #ddd;
  541. border-left:0;
  542. }
  543. .more {
  544. font-size: 12px;
  545. line-height: 24px;
  546. height: 24px;
  547. position: absolute;
  548. top: 14px;
  549. right: 14px;
  550. cursor: pointer;
  551. -webkit-user-select: none;
  552. -moz-user-select: none;
  553. -ms-user-select: none;
  554. user-select: none;
  555. color: #f1f1f1;
  556. display: inline-block;
  557. border-radius: 4px;
  558. background: #111;
  559. padding: 0 8px;
  560. }
  561. /deep/.el-dialog__headerbtn{
  562. top:10px;
  563. }
  564. /deep/.el-dialog:not(.is-fullscreen) {
  565. margin-top: 0 !important;
  566. }
  567. ::v-deep .im-tabs {
  568. .el-tabs__header {
  569. margin: 0 !important;
  570. .el-tabs__item {
  571. border-bottom: 0 !important;
  572. }
  573. }
  574. }
  575. </style>
  576. <style lang="stylus">
  577. .lemon-container__title{
  578. padding-bottom:10px;
  579. }
  580. .slot-group{
  581. width:200px;
  582. }
  583. .slot-group-title{
  584. padding:0 0 10px 0;
  585. }
  586. .slot-group-notice{
  587. padding: 10px 10px;
  588. }
  589. .slot-search{
  590. width:calc(100% - 20px) ;
  591. margin:5px 10px;
  592. padding:3px 0px;
  593. }
  594. .slot-group-member{
  595. display: flex;
  596. padding: 5px 0;
  597. flex:1;
  598. font-size: 14px;
  599. align-items: center;
  600. }
  601. .avatar{
  602. width: 30px;
  603. height: 30px;
  604. line-height: 30px;
  605. margin-right:5px;
  606. }
  607. .img{
  608. vertical-align: middle;
  609. border-style: none;
  610. width: 100%;
  611. height: 100%;
  612. line-height: 30px;
  613. border-radius: 50%;
  614. }
  615. </style>