qq.vue 18 KB

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