index.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  1. <script>
  2. export default {
  3. name: "index",
  4. };
  5. </script>
  6. <script setup>
  7. import { ref, reactive, toRaw, toRefs, nextTick, computed, watchEffect,onMounted,inject } from "vue";
  8. import { useRouter, useRoute } from "vue-router";
  9. import useUserStore from "@/store/modules/user";
  10. import word from "@/assets/images/word2.png";
  11. import chat from "@/assets/images/profile.png";
  12. import cebian from "@/assets/images/cebian.png";
  13. import send from "@/assets/images/send.png";
  14. import downFile from "@/assets/images/down-file.png";
  15. import preFile from "@/assets/images/outline_preview.png";
  16. import forwardFile from "@/assets/images/forward-file.png";
  17. import sendFile from "@/assets/images/send-file.png";
  18. import myfile from '@/api/myfile/myfile'
  19. import { parseTime } from "@/utils/ruoyi";
  20. import store from "@/store";
  21. import {
  22. msgFriend,
  23. userTree,
  24. userInfo,
  25. msgSend,
  26. msgRecord,
  27. delMsg,
  28. dirTree,
  29. fileTree,
  30. spaceInfo,
  31. fileDownload,
  32. } from "@/api/chat/msg";
  33. import Addperson from "@/components/AddPerson/index.vue"; //添加人员的弹框
  34. import FileTreeChoice from "@/components/FileTreeChoice/index.vue"; //选择文件发送的列表
  35. import forwordTree from "@/components/forwordTree/index.vue"; //选择文件发送的列表
  36. //websocket连接====
  37. import useWebsoctStore from "@/store/modules/websocket";
  38. import { ElMessage } from "element-plus";
  39. import { preview } from "@/api/common/common.js";
  40. import { canPreviewFile, rightMenuRole, setIcon } from "@/utils/index.js";
  41. import ImgPreview from '@/components/ImgPreview/ImgPreview.vue'
  42. const router = useRouter(); //注册路由
  43. const websoctStore = useWebsoctStore();
  44. //====
  45. const { proxy } = getCurrentInstance();
  46. const userIds = useUserStore();
  47. // main元素的初始高度,在onMounted时需要重新计算
  48. let height = ref(document.documentElement.clientHeight - 16 + "px;");
  49. let height2= ref('0px'); //会话列表高度
  50. const messageText = ref(""); //发送的内容
  51. let headerName = ref("");
  52. const total = ref(0);
  53. const sendId = ref(""); //选择发送的文件id
  54. const isForward = ref(false);
  55. const openFile = ref(false); //文件目录
  56. const openForwardFile = ref(false); //转发目录
  57. const showCircel = ref(""); //是否 显示圆点
  58. const chatRecords = reactive({ data: [] });
  59. const loading = ref(false);
  60. const addFileTab = inject("addFileTab");
  61. const wangzhi=import.meta.env.VITE_APP_BASE_API
  62. const copyFileType = ref();
  63. const previewData = ref();
  64. const showPreview = ref(false);
  65. const loadingPreview = ref(false);
  66. const sendCont = reactive({
  67. //发送聊天内容数据组装
  68. data: {
  69. content: "",
  70. fileList: [],
  71. toId: 0,
  72. msgType: "",
  73. },
  74. });
  75. const searchText = ref(""); //搜
  76. const searchData = ref([]);
  77. //聊天列表数据模拟
  78. const personList = reactive({ data: [] });
  79. let getchatUserState = false //获取会话人员列表状态.获取动作完成前接收到的消息需要临时缓存
  80. let tmpMsgList = ref([]) //临时消息队列。主要存储会话人员还未获取完成前收到的消息
  81. //获取好友列表
  82. const getMsgList = async () => {
  83. getchatUserState = true
  84. const resFriend = await msgFriend();
  85. personList.data = resFriend.rows;
  86. searchData.value = personList.data;
  87. if(personList.data.length==0){
  88. getchatUserState = false
  89. return
  90. }
  91. //圆点======
  92. sendCont.data.toId =
  93. personList.data[0]?.toId == userIds.uid
  94. ? personList.data[0].fromId
  95. : personList.data[0].toId;
  96. headerName.value =
  97. personList.data[0]?.toId == userIds.uid
  98. ? personList.data[0].fromName
  99. : personList.data[0].toName;
  100. // 默认点击第一个对话人
  101. if (clickPersonId.value ==0 && personList.data.length>0) {
  102. clickPersonId.value = sendCont.data.toId
  103. }
  104. getchatUserState = false
  105. if(tmpMsgList.value.length>0){
  106. //处理临时缓存消息:将发送者标记为消息未读提示
  107. searchData.value.map((user) => {
  108. let charUserid = user?.toId == userIds.uid ? user.fromId : user.toId;
  109. for(let i=0;i<tmpMsgList.value.length;i++){
  110. let tmpFromID = tmpMsgList.value[i].fromId
  111. if (tmpFromID == charUserid && sendCont.data.toId!=tmpFromID) {
  112. user.showCircel = true;
  113. }
  114. }
  115. });
  116. tmpMsgList.value = []
  117. }
  118. if (sendCont.data.toId) {
  119. //调用聊天记录
  120. msgRecordEvent(sendCont.data.toId);
  121. }
  122. };
  123. const noMes = ref(false);
  124. const boottmScroll = () => {
  125. //发送消息后滚动到最底部显示最新消息
  126. const chatContainer = document.querySelector(".right-container");
  127. if(chatContainer==null) return
  128. // 计算滚动的目标位置
  129. const targetScrollTop = chatContainer.scrollHeight;
  130. // 设置滚动位置
  131. nextTick(() => {
  132. chatContainer.scrollTop = targetScrollTop;
  133. });
  134. };
  135. let noReadList = null;
  136. //往前继续读取
  137. let pageNumRead = 1
  138. const readChat =async ()=>{
  139. pageNumRead++
  140. const queryParams = {
  141. pageNum: pageNumRead,
  142. pageSize: 10,
  143. };
  144. const resMsgData = await msgRecord(toIdValue, queryParams);
  145. if(resMsgData.rows[0].isRead === "N"){
  146. readChat()
  147. }
  148. }
  149. //获取用户的聊天记录
  150. const msgRecordEvent = async (toIdValue) => {
  151. if(clickPersonId.value!=toIdValue) return;
  152. const queryParams = {
  153. pageNum: 1,
  154. pageSize: 10,
  155. };
  156. const resMsgData = await msgRecord(toIdValue, queryParams);
  157. if(resMsgData.rows.length<=0){
  158. chatRecords.data = []
  159. return
  160. }
  161. if(resMsgData.rows[0].isRead === "N"){
  162. readChat()
  163. }
  164. resMsgData.rows.map((i) => (i.isForward = false));
  165. chatRecords.data = resMsgData.rows.reverse();
  166. const PageNum = Math.ceil(chatRecords.data.length / 10) + 1;
  167. if (PageNum * 10 > resMsgData.total && !noMes.value) {
  168. loading.value = false;
  169. noMes.value = false;
  170. }
  171. const nowtime = parseTime(new Date().getTime(), "{y}-{m}-{d}");
  172. chatRecords.data.map((i) => {
  173. if (nowtime == i.createTime.substr(0, 10))
  174. i.createTime = i.createTime.substring(11);
  175. });
  176. total.value = resMsgData.total;
  177. boottmScroll();
  178. setTimeout(() => {
  179. noReadList = localStorage.getItem('noreadlist');
  180. if(noReadList==null || noReadList=='') return;
  181. noReadList = JSON.parse(noReadList);
  182. if(noReadList[sendCont.data.toId]!=null) delete noReadList[sendCont.data.toId]
  183. let i=0
  184. for(let k in noReadList){
  185. i++
  186. }
  187. if(i==0) window.localStorage.removeItem('noreadlist');
  188. else window.localStorage.setItem('noreadlist',JSON.stringify(noReadList));
  189. }, 500);
  190. };
  191. //点击左侧新建聊天
  192. const open = ref(false);
  193. const userTreeData = reactive({ data: [] });
  194. const clickNewPerson = async () => {
  195. const res = await userTree();
  196. userTreeData.data = res;
  197. toRaw(userTreeData.data);
  198. open.value = true;
  199. };
  200. // 点击文件预览
  201. const toPreviewFile = async (index,file)=>{
  202. copyFileType.value = file.fileType;
  203. loadingPreview.value = true;
  204. const filePreview = canPreviewFile(file.fileType);
  205. if (filePreview) {
  206. loadingPreview.value = false;
  207. const itemData = {
  208. name: file.fileName,
  209. path: `${window.location.origin}/fileEdit?clickRowId=${file.docId}&canEdit=0&canCopy=0&history=0&fileId=0`
  210. }
  211. // sessionStorage.setItem('newTab',JSON.stringify(itemData))
  212. window.parent.$setOpenTab(itemData)
  213. // addFileTab(file, 0, 0);
  214. } else {
  215. const res = await preview(file.docId);
  216. showPreview.value = true;
  217. previewData.value = URL.createObjectURL(res);
  218. loadingPreview.value = false;
  219. }
  220. }
  221. //关闭图片预览事件
  222. const closeImgPreview = () => {
  223. // console.log('close');
  224. showPreview.value = false;
  225. };
  226. const setImg = (type) => {
  227. return setIcon(type);
  228. };
  229. //点击左侧聊天列表
  230. const clickPersonIndex = ref("");
  231. const clickPersonId = ref(0);
  232. const clickPerson = (index, item) => {
  233. sendCont.data.toId = item.userId
  234. ? item.userId
  235. : item.toId == userIds.uid
  236. ? item.fromId
  237. : item.toId;
  238. headerName.value = item.nickName
  239. ? item.nickName
  240. : item.toId == userIds.uid
  241. ? item.fromName
  242. : item.toName;
  243. noMes.value = false;
  244. clickPersonIndex.value = index;
  245. clickPersonId.value = sendCont.data.toId;
  246. //点击某个人就不显示红点
  247. searchData.value.map((i) => {
  248. if (i.fromId == item.fromId) {
  249. i.showCircel = false;
  250. }
  251. });
  252. const chatContainer = document.querySelector(".right-container");
  253. // 计算滚动的目标位置
  254. const targetScrollTop = chatContainer.scrollHeight;
  255. // 设置滚动位置
  256. chatContainer.scrollTop = targetScrollTop;
  257. msgRecordEvent(sendCont.data.toId);
  258. };
  259. //删除聊天
  260. const delClick = (toId) => {
  261. proxy.$modal
  262. .confirm("删除后,将清空与该人员的所有会话记录,确定吗?")
  263. .then(function () {
  264. return delMsg(toId);
  265. })
  266. .then(() => {
  267. chatRecords.data = []
  268. headerName.value = ''
  269. clickPersonId.value = 0
  270. sendCont.data.toId = 0
  271. getMsgList();
  272. proxy.$modal.msgSuccess("删除成功");
  273. });
  274. };
  275. //树选中的人传过来的选中人的信息,push进入列表中
  276. // val 新的会话人员信息对象
  277. // isnew:false或null:自己添加的 true:其他人员发起的新消息会话
  278. const changeMsg = async (val,isnew) => {
  279. const resInfo = await userInfo(val.id);
  280. resInfo.fromId = val.id
  281. resInfo.content = val.content
  282. resInfo.file = val.file
  283. // resInfo.toAvatar = resInfo.fromAvatar = resInfo.avatar
  284. if (searchData.value.length > 0) {
  285. //判断是否有相同的聊天人
  286. const filerData = searchData.value.filter(
  287. (vPerson) =>
  288. (vPerson.toId && vPerson.toId == resInfo.userId) ||
  289. (vPerson.fromId && vPerson.fromId == resInfo.userId)
  290. );
  291. if (filerData.length > 0) {
  292. // 存在就算了,无需提示
  293. //return ElMessage({ message: "该聊天已存在", type: "error" });
  294. } else {
  295. if(isnew!=null){
  296. resInfo.showCircel = true
  297. }
  298. searchData.value.unshift(resInfo);
  299. chatRecords.data = []
  300. }
  301. } else {
  302. //为空数组的时候无需判断
  303. searchData.value.unshift(resInfo);
  304. }
  305. if(isnew==null){
  306. // 自己添加的会话人员,默认选中进行对话
  307. sendCont.data.toId = resInfo.userId;
  308. headerName.value = resInfo.nickName;
  309. clickPersonId.value = resInfo.userId
  310. clickPerson(0,searchData.value[0]);
  311. }else if(searchData.value.length==1){
  312. sendCont.data.toId = resInfo.userId;
  313. headerName.value = resInfo.nickName;
  314. clickPersonId.value = val.id
  315. clickPerson(0,searchData.value[0]);
  316. }
  317. };
  318. //发送聊天
  319. const inputRef = ref(null);
  320. const msgSendClick = (event) => {
  321. if (event.ctrlKey && event.keyCode == 13) {
  322. //CTRL+enter键换行
  323. const textarea = event.target;
  324. const start = textarea.selectionStart;
  325. const end = textarea.selectionEnd;
  326. const text = messageText.value;
  327. messageText.value = text.slice(0, start) + "\n" + text.slice(end);
  328. nextTick(() => {
  329. textarea.selectionStart = start + 1;
  330. textarea.selectionEnd = start + 1;
  331. });
  332. event.preventDefault();
  333. } else if (event.shiftKey && event.keyCode == 13) {
  334. event.preventDefault();
  335. }
  336. };
  337. const msgSendClick2 = (event,icon) => {
  338. if(sendCont.data.toId==null||sendCont.data.toId==0){
  339. return ElMessage({ message: "请添加或者选择会话人员", type: "error" });
  340. }
  341. if ((!event.shiftKey && event.keyCode == 13 && !event.ctrlKey)||icon) {
  342. event.preventDefault();
  343. noMes.value = false;
  344. if (messageText.value.trim() == "") {
  345. return ElMessage({ message: "不能发送空白消息", type: "error" });
  346. }
  347. const message = {
  348. content: messageText.value,
  349. msgType: "2",
  350. fileList: [],
  351. toId: sendCont.data.toId,
  352. };
  353. websoctStore.sendMessage(message).then((res) => {
  354. if(res.code==200){
  355. //发送成功
  356. messageText.value = "";
  357. msgRecordEvent(sendCont.data.toId)
  358. }
  359. }).catch((err) => {
  360. //发送失败了
  361. })
  362. }
  363. }
  364. //发送文件确认按钮
  365. const fileChangeMsg = async (val) => {
  366. if(sendCont.data.toId==null||sendCont.data.toId==0){
  367. return ElMessage({ message: "请添加或者选择会话人员", type: "error" });
  368. return
  369. }
  370. const message = {
  371. content: val.id,
  372. msgType: "1",
  373. fileList: [],
  374. toId: sendCont.data.toId,
  375. };
  376. sendId.value = val.id;
  377. websoctStore.sendMessage(message).then((res) => {
  378. if(res.code==200){
  379. //发送成功
  380. messageText.value = "";
  381. msgRecordEvent(sendCont.data.toId)
  382. }
  383. }).catch((err) => {
  384. //发送失败了
  385. })
  386. };
  387. //点击发送文件图标
  388. const fileUserTreeData = reactive({ data: {} });
  389. const sendFileClick = async () => {
  390. const resDir = await fileTree(3);
  391. fileUserTreeData.data = resDir;
  392. toRaw(fileUserTreeData.data);
  393. openFile.value = true;
  394. };
  395. //获取用户聊天记录
  396. const handleNewMessage = async () => {
  397. //console.log("======当前状态:",localStorage.getItem("inChat"),' sendCont.data.toId:',sendCont.data.toId)
  398. if('1'==localStorage.getItem("inChat")){
  399. const hasNewMessage = localStorage.getItem('noreadlist');
  400. if(hasNewMessage==null || hasNewMessage=='') return;
  401. msgRecordEvent(sendCont.data.toId); //获取用户的聊天记录
  402. }
  403. };
  404. //接收到的新消息
  405. watchEffect(async() => {
  406. let newMessages = {}
  407. newMessages = websoctStore.messOne
  408. // if(router.currentRoute.value.path!='/index' || newMessages.fromId==null) return;
  409. if( newMessages.fromId==null) return;
  410. //此处判断messOne是否已发生更改,如果还未发生更改则清除该消息
  411. if(websoctStore.messOne.fromId==newMessages.fromId){
  412. noMes.value = false;
  413. websoctStore.messOne={}
  414. }
  415. // console.log("clickPersonId:",clickPersonId.value)
  416. if(getchatUserState){
  417. //会话列表还处理完成,临时缓存消息
  418. tmpMsgList.value.push(newMessages)
  419. // console.log("===============会话列表还处理完成,临时缓存消息:",tmpMsgList.value)
  420. return
  421. }
  422. if(websoctStore.noReadList!=null) {
  423. // 是否正在对话的人员
  424. if(clickPersonId.value!=newMessages.fromId){
  425. let isExist=false // 当前消息发送人员是否已在对话人员列表中
  426. for(let i=0;i<searchData.value.length;i++){
  427. let chatUserID = searchData.value[i].fromId
  428. if(chatUserID==userIds.uid) chatUserID = searchData.value[i].toId
  429. if(chatUserID==newMessages.fromId){
  430. isExist = true
  431. searchData.value[i].showCircel = true
  432. searchData.value[i].content = newMessages.content
  433. searchData.value[i].file = newMessages.file
  434. }
  435. }
  436. if(!isExist){
  437. // 接收到其他人员发来的第一次会话消息,需要将该人员添加到会话人员列表
  438. changeMsg({"id":newMessages.fromId,"content":newMessages.content,"file":newMessages.file},true)
  439. }
  440. return;
  441. }
  442. msgRecordEvent(newMessages.fromId);
  443. // getYuan()
  444. // getMsgList();
  445. }
  446. });
  447. // 滚动翻页========
  448. const mainContainer = ref(null);
  449. const handleScroll = (event) => {
  450. // 在滚动到顶部时,加载上一页的聊天记录
  451. const mainContainer2 = document.querySelector(".right-container");
  452. if (
  453. event.deltaY < 0 &&
  454. mainContainer2.scrollTop <= 1 &&
  455. !noMes.value &&
  456. !loading.value
  457. ) {
  458. loading.value = true;
  459. loadPreviousPage();
  460. }
  461. };
  462. const loadPreviousPage = async () => {
  463. const currentPageNum = Math.ceil(chatRecords.data.length / 10) + 1;
  464. const queryParams = {
  465. pageNum: currentPageNum,
  466. pageSize: 10,
  467. };
  468. const resMsgData = await msgRecord(sendCont.data.toId, queryParams);
  469. const previousPageData = resMsgData.rows.reverse();
  470. chatRecords.data = [...previousPageData, ...chatRecords.data];
  471. const nowtime2 = parseTime(new Date().getTime(), "{y}-{m}-{d}");
  472. chatRecords.data.map((i) => {
  473. if (nowtime2 == i.createTime.substr(0, 10))
  474. i.createTime = i.createTime.substring(11);
  475. });
  476. noMes.value = false;
  477. if (currentPageNum * 10 > resMsgData.total && !noMes.value) {
  478. loading.value = false;
  479. noMes.value = true;
  480. return;
  481. }
  482. //获取数据后滚动到获得新消息的最后一条,而不是第一条=========
  483. await nextTick();
  484. const mainContainer = document.querySelector(".right-container");
  485. const newMessages = document.querySelectorAll(".message-container");
  486. // console.log('mainContainer',mainContainer);
  487. const firstNewMessage = newMessages[previousPageData.length];
  488. // const firstNewMessageTop = mainContainer.offsetHeight + 100;
  489. const firstNewMessageTop = mainContainer.offsetHeight-10;
  490. mainContainer.scrollTop = firstNewMessageTop;
  491. chatRecords.data;
  492. //===========
  493. // 将加载的上一页聊天记录插入到 chatRecords.data 的前面
  494. loading.value = false;
  495. // }, 50);
  496. };
  497. const transferFiles = (forwardVal, msgIds, indexs) => {
  498. chatRecords.data.map((i, index) => {
  499. if (i.msgId == msgIds && i.msgType == "1" && indexs == index) {
  500. i.isForward = true;
  501. } else {
  502. i.isForward = false;
  503. }
  504. });
  505. };
  506. // 点击转存
  507. const forwardTreeData = reactive({ data: {} });
  508. const spaceId = ref("");
  509. const docId = ref("");
  510. const forwardClick = async (indexs, docIds) => {
  511. docId.value = docIds ? docIds.toString() : "";
  512. const resDir = await dirTree(3);
  513. forwardTreeData.data = resDir;
  514. toRaw(forwardTreeData.data);
  515. //获取最上层树的id
  516. const topSpaceid = await spaceInfo(3);
  517. spaceId.value = topSpaceid.data.spaceId.toString();
  518. openForwardFile.value = true;
  519. };
  520. const forwardChangeMsg = async (val) => {};
  521. //点击下载
  522. const downClick = async (file) => {
  523. // location.href = `${import.meta.env.VITE_APP_BASE_API}/api/download/${fileId}`;
  524. downLoadfile(file)
  525. };
  526. //搜索的点击事件
  527. const SearchChat = () => {
  528. if (searchText.value) {
  529. searchData.value.map((user) => {
  530. let charUserName = user?.toId == userIds.uid ? user.fromName : user.toName;
  531. if(charUserName==null) charUserName = user.nickName
  532. if(charUserName.indexOf(searchText.value.replace(/ /gi,''))>-1) user['display'] = 1
  533. else user['display'] = 0
  534. });
  535. } else {
  536. searchData.value.map((user) => {
  537. user['display'] = 1
  538. });
  539. }
  540. };
  541. //转聊天记录的/n为实际样式
  542. const formatText = (text) => {
  543. const formattedText = text.replace(/\n/g, "<br>");
  544. return formattedText;
  545. };
  546. // 文件下载
  547. const downLoadfile = (file)=>{
  548. myfile.fileDown(file.docId).then(res=>{
  549. var reader = new FileReader();
  550. reader.onloadend = function(event){
  551. //event 就是你要的返回内容
  552. //因为返回的报错格式是字符串,手动转换成对象,转换成功表示请求失败
  553. //转换失败就意味着你拿到的result是文件流,那么直接手动下载就好
  554. try{
  555. let data = JSON.parse(event.target.result)
  556. }catch(err){
  557. const link = document.createElement('a'); // 创建a标签
  558. let blob = new Blob([res]);
  559. link.style.display = 'none';
  560. link.href = URL.createObjectURL(blob); // 创建下载的链接
  561. link.setAttribute('download',file.fileName); // 给下载后的文件命名
  562. document.body.appendChild(link);
  563. link.click(); // 点击下载
  564. document.body.removeChild(link); // 完成移除元素
  565. window.URL.revokeObjectURL(link.href); // 释放blob对象
  566. }
  567. };
  568. reader.readAsText(res);
  569. })
  570. };
  571. const headError=(ind,item)=>{
  572. // console.log("头像加载失败:",ind)
  573. // item.fromAvatar=item.toAvatar=item.avatar=null;
  574. };
  575. // 滚动翻页========
  576. onMounted(() => {
  577. // height.value = document.documentElement.clientHeight - document.getElementsByClassName("tab_box")[0].offsetHeight-8-document.getElementsByClassName("nav")[0].offsetHeight -26 + "px";
  578. height2.value = height.value.replace('px','') - 48 + 'px';
  579. getMsgList();
  580. websoctStore.connect();
  581. setInterval(handleNewMessage, 1000); // 每秒钟检查是否有新消息
  582. });
  583. </script>
  584. <template>
  585. <div class="main" :style="'height:' + height">
  586. <!-- 左侧用户列表 -->
  587. <div class="left-main">
  588. <div class="left-top search">
  589. <el-input
  590. v-model="searchText"
  591. maxlength="32"
  592. class="w-50 m-2"
  593. size="small"
  594. clearable
  595. placeholder="搜索聊天"
  596. suffix-icon="Search"
  597. @change="SearchChat"
  598. />
  599. <!-- 添加聊天人员 -->
  600. <el-icon
  601. size="24"
  602. color="#505870"
  603. class="shouzhi"
  604. @click="clickNewPerson"
  605. style="margin-right: 8px"
  606. ><Plus
  607. /></el-icon>
  608. <!-- 新建聊天弹框 -->
  609. <Addperson
  610. :open="open"
  611. @close="open = false"
  612. :userTreeData="userTreeData.data"
  613. @changeMsg="changeMsg"
  614. ></Addperson>
  615. </div>
  616. <!-- 列表 -->
  617. <div :style="'overflow-y: auto;height: '+height2">
  618. <div
  619. :class="
  620. (item.display!=null && item.display==0)
  621. ? ''
  622. :(
  623. clickPersonIndex == index
  624. ? 'activ-left-container left-container shouzhi'
  625. : 'left-container shouzhi'
  626. )
  627. "
  628. v-for="(item, index) in searchData"
  629. :key="index"
  630. @click="clickPerson(index, item)"
  631. >
  632. <template v-if="item.display==null || item.display==1">
  633. <img :src="cebian" class="cebian" v-if="clickPersonIndex == index" />
  634. <button
  635. class="del-chat"
  636. v-if="clickPersonIndex == index && item.fromId!=-1"
  637. @click="delClick(item.userId!=null? item.userId : (item.toId == userIds.uid?item.fromId:item.toId))"
  638. >
  639. 删除会话
  640. </button>
  641. <div>
  642. <img
  643. :src="wangzhi + item.fromAvatar"
  644. class="head-sculpture"
  645. :onerror="headError(index,item)"
  646. v-if="item.toId == userIds.uid && item.fromAvatar"
  647. />
  648. <img :src="wangzhi + item.avatar" :onerror="headError(index,item)" alt="" class="head-sculpture" v-else-if="item.avatar"/>
  649. <img
  650. :src="wangzhi + item.toAvatar"
  651. class="head-sculpture"
  652. :onerror="headError(index,item)"
  653. v-else-if="item.toId != userIds.uid && item.toAvatar"
  654. />
  655. <span
  656. style="
  657. background-color: #7a89ba;
  658. display: inline-block;
  659. color: #fff;
  660. font-weight: 600;
  661. text-align: center;
  662. line-height: 40px;
  663. "
  664. class="head-sculpture"
  665. v-else
  666. >{{ item.nickName!=null? item.nickName.slice(0, 1):item.toId == userIds.uid? item.fromName.slice(0, 1): item.toName.slice(0, 1) }}</span>
  667. </div>
  668. <div class="spill">
  669. <span class="person-name">{{
  670. item.nickName
  671. ? item.nickName
  672. : item.toId == userIds.uid
  673. ? item.fromName
  674. : item.toName
  675. }}</span
  676. ><span class="person-cont spill">
  677. {{ item.file?.fileName ? item.file?.fileName : item.content }}</span
  678. >
  679. </div>
  680. <span
  681. class="yuandian"
  682. v-if="item.showCircel && $route.path == '/index' && clickPersonId!==item.fromId"
  683. ></span>
  684. </template>
  685. </div>
  686. </div>
  687. </div>
  688. <!-- 右侧聊天 -->
  689. <div class="right-main">
  690. <div
  691. class="common-layout"
  692. style="display: flex; flex-direction: column; height: 100vh"
  693. >
  694. <el-container>
  695. <el-header height="64px" class="right-header flex-buju">{{
  696. headerName
  697. }}</el-header>
  698. <!-- 聊天 -->
  699. <el-main
  700. class="right-container"
  701. @wheel="handleScroll"
  702. ref="mainContainer"
  703. >
  704. <div v-loading="loading"></div>
  705. <!-- <div v-if="noMes" style="color:#9fa1a5 ;">无更多聊天记录</div> -->
  706. <div
  707. class="message-container"
  708. v-for="(record, index) in chatRecords.data"
  709. :class="{
  710. 'message-left': useUserStore().uid == record.toId,
  711. 'message-right': useUserStore().uid !== record.toId,
  712. }"
  713. :key="index"
  714. >
  715. <div>
  716. <div
  717. v-if="useUserStore().uid !== record.toId"
  718. style="display: flex"
  719. >
  720. <div
  721. :class="
  722. record.msgType == '1'
  723. ? 'file-msg right-back'
  724. : 'time-text right-back'
  725. "
  726. >
  727. <img
  728. :src="word"
  729. v-if="record.msgType == '1'"
  730. class="head-sculpture"
  731. />
  732. <div
  733. :class="
  734. record.msgType == '2' ? 'clip-path' : 'clip-path-right'
  735. "
  736. >
  737. <div
  738. style="
  739. color: #c1cce3;
  740. font-size: 12px;
  741. margin-bottom: 4px;
  742. "
  743. >
  744. {{ record.createTime.slice(0, -3) }}
  745. </div>
  746. <span v-if="record.msgType == '1'">{{
  747. record.file?.fileName
  748. }}</span>
  749. <!-- <span v-else-if="record.msgType == '2'">{{
  750. record.content
  751. }}</span> -->
  752. <span
  753. v-else-if="record.msgType == '2'"
  754. v-html="formatText(record.content)"
  755. ></span>
  756. </div>
  757. </div>
  758. <div>
  759. <!-- 右侧聊天头像 -->
  760. <img
  761. :src="wangzhi + record.fromAvatar"
  762. class="head-sculpture"
  763. :onerror="headError(index,record)"
  764. v-if="
  765. useUserStore().uid != record.toId && record.fromAvatar
  766. "
  767. />
  768. <img
  769. :src="wangzhi + record.toAvatar"
  770. class="head-sculpture"
  771. :onerror="headError(index,record)"
  772. v-else-if="
  773. useUserStore().uid == record.toId && record.toAvatar
  774. "
  775. />
  776. <span
  777. style="
  778. background-color: #7a89ba;
  779. display: inline-block;
  780. color: #fff;
  781. font-weight: 600;
  782. text-align: center;
  783. line-height: 40px;
  784. "
  785. class="head-sculpture"
  786. v-else
  787. >{{ record.fromName!=null ? record.fromName.slice(0, 1) :'系' }}</span
  788. >
  789. </div>
  790. </div>
  791. <!-- 头像 -->
  792. <!-- <img :src="chat" class="head-sculpture" /> -->
  793. </div>
  794. <div style="display: flex; align-items: center">
  795. <div
  796. v-if="useUserStore().uid == record.toId"
  797. style="display: flex"
  798. >
  799. <div>
  800. <!-- 左侧侧聊天头像 -->
  801. <img
  802. :src="wangzhi + record.fromAvatar"
  803. class="head-sculpture"
  804. :onerror="headError(index,record)"
  805. v-if="
  806. useUserStore().uid == record.toId && record.fromAvatar
  807. "
  808. />
  809. <img
  810. :src="wangzhi + record.toAvatar"
  811. class="head-sculpture"
  812. :onerror="headError(index,record)"
  813. v-else-if="
  814. useUserStore().uid != record.toId && record.toAvatar
  815. "
  816. />
  817. <span
  818. style="
  819. background-color: #7a89ba;
  820. display: inline-block;
  821. color: #fff;
  822. font-weight: 600;
  823. text-align: center;
  824. line-height: 40px;
  825. "
  826. class="head-sculpture"
  827. v-else
  828. >{{ record.fromName!=null? record.fromName.slice(0, 1):'系' }}</span
  829. >
  830. </div>
  831. <div
  832. :class="
  833. record.msgType == '1' ? 'file-msg left-back' : 'left-back'
  834. "
  835. @click="transferFiles(record.msgType, record.msgId, index)"
  836. >
  837. <img
  838. v-if="record.msgType == '1'"
  839. :src="setImg(record.file.fileType)"
  840. class="head-sculpture"
  841. />
  842. <div
  843. :class="
  844. record.msgType == '2' ? 'clip-path' : 'clip-path-left'
  845. "
  846. >
  847. <div
  848. style="
  849. color: #7a89ba;
  850. font-size: 12px;
  851. margin-bottom: 4px;
  852. "
  853. >
  854. {{ record.createTime.slice(0, -3) }}
  855. </div>
  856. <span v-if="record.msgType == '1'">{{
  857. record.file?.fileName
  858. }}</span>
  859. <span
  860. v-else-if="record.msgType == '2'"
  861. v-html="formatText(record.content)"
  862. ></span>
  863. </div>
  864. </div>
  865. </div>
  866. <img
  867. :src="preFile"
  868. class="zhuanfa downf shouzhi"
  869. alt="预览"
  870. v-if="record.isForward"
  871. @click="toPreviewFile(index, record.file)"
  872. />
  873. <img
  874. :src="forwardFile"
  875. class="zhuanfa forwd shouzhi"
  876. alt="转存"
  877. v-if="record.isForward && record.file.unloading"
  878. @click="forwardClick(index, record.file?.docId)"
  879. />
  880. <img
  881. :src="downFile"
  882. class="zhuanfa downf shouzhi"
  883. alt="下载"
  884. v-if="record.isForward && record.file.unloading"
  885. @click="downClick(record.file)"
  886. />
  887. </div>
  888. </div>
  889. </el-main>
  890. <!-- 底部 -->
  891. <el-footer height="112px" class="right-footer" v-if="sendCont.data.toId>0">
  892. <!-- 发送文件 -->
  893. <img
  894. :src="sendFile"
  895. class="send-info-file shouzhi"
  896. @click="sendFileClick"
  897. />
  898. <FileTreeChoice
  899. :openFile="openFile"
  900. @close="openFile = false"
  901. :fileUserTreeData="fileUserTreeData.data"
  902. @fileChangeMsg="fileChangeMsg"
  903. ></FileTreeChoice>
  904. <el-input
  905. v-model="messageText"
  906. class="w-50 m-2"
  907. type="textarea"
  908. :autosize="{ minRows: 3, maxRows: 5 }"
  909. clearable
  910. ref="inputRef"
  911. size="small"
  912. placeholder="请输入聊天内容"
  913. maxlength="800"
  914. @keyup.ctrl.enter="msgSendClick($event)"
  915. @keyup.shift.enter="msgSendClick($event)"
  916. @keydown.enter="msgSendClick2($event)"
  917. />
  918. <!-- 发送按钮 -->
  919. <img :src="send" class="send-info" @click="msgSendClick2($event,true)" />
  920. </el-footer>
  921. </el-container>
  922. </div>
  923. </div>
  924. </div>
  925. <!-- 发送消息按钮选择的文件 -->
  926. <forwordTree
  927. :openForwardFile="openForwardFile"
  928. :docId="docId"
  929. :spaceId="spaceId"
  930. @close="openForwardFile = false"
  931. :forwardTreeData="forwardTreeData.data"
  932. @forwardChangeMsg="forwardChangeMsg"
  933. ></forwordTree>
  934. <ImgPreview
  935. :previewData="previewData"
  936. :copyFileType="copyFileType"
  937. :showPreview="showPreview"
  938. @closeImgPreview="closeImgPreview"
  939. ></ImgPreview>
  940. <div
  941. v-loading.fullscreen="loadingPreview"
  942. v-if="loadingPreview"
  943. class="lodingBox"
  944. ></div>
  945. </template>
  946. <style lang="scss" scoped>
  947. @import "@/assets/styles/my-common.scss";
  948. .left-container {
  949. position: relative;
  950. }
  951. .yuandian {
  952. width: 8px;
  953. height: 8px;
  954. position: absolute;
  955. right: 8px;
  956. top: 6px;
  957. background: #fa5151;
  958. border-radius: 4px;
  959. }
  960. :deep(.el-textarea__inner) {
  961. resize: none;
  962. }
  963. </style>