index.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  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. console.log('resMsgData',resMsgData);
  165. resMsgData.rows.map((i) => (i.isForward = false));
  166. chatRecords.data = resMsgData.rows.reverse();
  167. const PageNum = Math.ceil(chatRecords.data.length / 10) + 1;
  168. if (PageNum * 10 > resMsgData.total && !noMes.value) {
  169. loading.value = false;
  170. noMes.value = false;
  171. }
  172. const nowtime = parseTime(new Date().getTime(), "{y}-{m}-{d}");
  173. chatRecords.data.map((i) => {
  174. if (nowtime == i.createTime.substr(0, 10))
  175. i.createTime = i.createTime.substring(11);
  176. });
  177. total.value = resMsgData.total;
  178. boottmScroll();
  179. setTimeout(() => {
  180. noReadList = localStorage.getItem('noreadlist');
  181. if(noReadList==null || noReadList=='') return;
  182. noReadList = JSON.parse(noReadList);
  183. if(noReadList[sendCont.data.toId]!=null) delete noReadList[sendCont.data.toId]
  184. let i=0
  185. for(let k in noReadList){
  186. i++
  187. }
  188. if(i==0) window.localStorage.removeItem('noreadlist');
  189. else window.localStorage.setItem('noreadlist',JSON.stringify(noReadList));
  190. }, 500);
  191. };
  192. //点击左侧新建聊天
  193. const open = ref(false);
  194. const userTreeData = reactive({ data: [] });
  195. const clickNewPerson = async () => {
  196. const res = await userTree();
  197. userTreeData.data = res;
  198. toRaw(userTreeData.data);
  199. open.value = true;
  200. };
  201. // 点击文件预览
  202. const toPreviewFile = async (index,file)=>{
  203. copyFileType.value = file.fileType;
  204. loadingPreview.value = true;
  205. const filePreview = canPreviewFile(file.fileType);
  206. if (filePreview) {
  207. loadingPreview.value = false;
  208. const itemData = {
  209. name: file.fileName,
  210. path: `${window.location.origin}/fileEdit?clickRowId=${file.docId}&canEdit=0&canCopy=0&history=0&fileId=0`
  211. }
  212. sessionStorage.setItem('newTab',JSON.stringify(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. // console.log("===============有新消息了:",newMessages)
  409. if(router.currentRoute.value.path!='/index' || 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. console.log('firstNewMessageTop',firstNewMessageTop);
  491. mainContainer.scrollTop = firstNewMessageTop;
  492. chatRecords.data;
  493. //===========
  494. // 将加载的上一页聊天记录插入到 chatRecords.data 的前面
  495. loading.value = false;
  496. // }, 50);
  497. };
  498. const transferFiles = (forwardVal, msgIds, indexs) => {
  499. chatRecords.data.map((i, index) => {
  500. if (i.msgId == msgIds && i.msgType == "1" && indexs == index) {
  501. i.isForward = true;
  502. } else {
  503. i.isForward = false;
  504. }
  505. });
  506. };
  507. // 点击转存
  508. const forwardTreeData = reactive({ data: {} });
  509. const spaceId = ref("");
  510. const docId = ref("");
  511. const forwardClick = async (indexs, docIds) => {
  512. docId.value = docIds ? docIds.toString() : "";
  513. const resDir = await dirTree(3);
  514. forwardTreeData.data = resDir;
  515. toRaw(forwardTreeData.data);
  516. //获取最上层树的id
  517. const topSpaceid = await spaceInfo(3);
  518. spaceId.value = topSpaceid.data.spaceId.toString();
  519. openForwardFile.value = true;
  520. };
  521. const forwardChangeMsg = async (val) => {};
  522. //点击下载
  523. const downClick = async (file) => {
  524. // location.href = `${import.meta.env.VITE_APP_BASE_API}/api/download/${fileId}`;
  525. downLoadfile(file)
  526. };
  527. //搜索的点击事件
  528. const SearchChat = () => {
  529. if (searchText.value) {
  530. searchData.value.map((user) => {
  531. let charUserName = user?.toId == userIds.uid ? user.fromName : user.toName;
  532. if(charUserName==null) charUserName = user.nickName
  533. if(charUserName.indexOf(searchText.value.replace(/ /gi,''))>-1) user['display'] = 1
  534. else user['display'] = 0
  535. });
  536. } else {
  537. searchData.value.map((user) => {
  538. user['display'] = 1
  539. });
  540. }
  541. };
  542. //转聊天记录的/n为实际样式
  543. const formatText = (text) => {
  544. const formattedText = text.replace(/\n/g, "<br>");
  545. return formattedText;
  546. };
  547. // 文件下载
  548. const downLoadfile = (file)=>{
  549. myfile.fileDown(file.docId).then(res=>{
  550. var reader = new FileReader();
  551. reader.onloadend = function(event){
  552. //event 就是你要的返回内容
  553. //因为返回的报错格式是字符串,手动转换成对象,转换成功表示请求失败
  554. //转换失败就意味着你拿到的result是文件流,那么直接手动下载就好
  555. try{
  556. let data = JSON.parse(event.target.result)
  557. }catch(err){
  558. const link = document.createElement('a'); // 创建a标签
  559. let blob = new Blob([res]);
  560. link.style.display = 'none';
  561. link.href = URL.createObjectURL(blob); // 创建下载的链接
  562. link.setAttribute('download',file.fileName); // 给下载后的文件命名
  563. document.body.appendChild(link);
  564. link.click(); // 点击下载
  565. document.body.removeChild(link); // 完成移除元素
  566. window.URL.revokeObjectURL(link.href); // 释放blob对象
  567. }
  568. };
  569. reader.readAsText(res);
  570. })
  571. };
  572. const headError=(ind,item)=>{
  573. // console.log("头像加载失败:",ind)
  574. // item.fromAvatar=item.toAvatar=item.avatar=null;
  575. };
  576. // 滚动翻页========
  577. onMounted(() => {
  578. // height.value = document.documentElement.clientHeight - document.getElementsByClassName("tab_box")[0].offsetHeight-8-document.getElementsByClassName("nav")[0].offsetHeight -26 + "px";
  579. height2.value = height.value.replace('px','') - 48 + 'px';
  580. getMsgList();
  581. websoctStore.connect();
  582. setInterval(handleNewMessage, 1000); // 每秒钟检查是否有新消息
  583. });
  584. </script>
  585. <template>
  586. <div class="main" :style="'height:' + height">
  587. <!-- 左侧用户列表 -->
  588. <div class="left-main">
  589. <div class="left-top search">
  590. <el-input
  591. v-model="searchText"
  592. maxlength="32"
  593. class="w-50 m-2"
  594. size="small"
  595. clearable
  596. placeholder="搜索聊天"
  597. suffix-icon="Search"
  598. @change="SearchChat"
  599. />
  600. <!-- 添加聊天人员 -->
  601. <el-icon
  602. size="24"
  603. color="#505870"
  604. class="shouzhi"
  605. @click="clickNewPerson"
  606. style="margin-right: 8px"
  607. ><Plus
  608. /></el-icon>
  609. <!-- 新建聊天弹框 -->
  610. <Addperson
  611. :open="open"
  612. @close="open = false"
  613. :userTreeData="userTreeData.data"
  614. @changeMsg="changeMsg"
  615. ></Addperson>
  616. </div>
  617. <!-- 列表 -->
  618. <div :style="'overflow-y: auto;height: '+height2">
  619. <div
  620. :class="
  621. (item.display!=null && item.display==0)
  622. ? ''
  623. :(
  624. clickPersonIndex == index
  625. ? 'activ-left-container left-container shouzhi'
  626. : 'left-container shouzhi'
  627. )
  628. "
  629. v-for="(item, index) in searchData"
  630. :key="index"
  631. @click="clickPerson(index, item)"
  632. >
  633. <template v-if="item.display==null || item.display==1">
  634. <img :src="cebian" class="cebian" v-if="clickPersonIndex == index" />
  635. <button
  636. class="del-chat"
  637. v-if="clickPersonIndex == index && item.fromId!=-1"
  638. @click="delClick(item.userId!=null? item.userId : (item.toId == userIds.uid?item.fromId:item.toId))"
  639. >
  640. 删除会话
  641. </button>
  642. <div>
  643. <img
  644. :src="wangzhi + item.fromAvatar"
  645. class="head-sculpture"
  646. :onerror="headError(index,item)"
  647. v-if="item.toId == userIds.uid && item.fromAvatar"
  648. />
  649. <img :src="wangzhi + item.avatar" :onerror="headError(index,item)" alt="" class="head-sculpture" v-else-if="item.avatar"/>
  650. <img
  651. :src="wangzhi + item.toAvatar"
  652. class="head-sculpture"
  653. :onerror="headError(index,item)"
  654. v-else-if="item.toId != userIds.uid && item.toAvatar"
  655. />
  656. <span
  657. style="
  658. background-color: #7a89ba;
  659. display: inline-block;
  660. color: #fff;
  661. font-weight: 600;
  662. text-align: center;
  663. line-height: 40px;
  664. "
  665. class="head-sculpture"
  666. v-else
  667. >{{ item.nickName!=null? item.nickName.slice(0, 1):item.toId == userIds.uid? item.fromName.slice(0, 1): item.toName.slice(0, 1) }}</span>
  668. </div>
  669. <div class="spill">
  670. <span class="person-name">{{
  671. item.nickName
  672. ? item.nickName
  673. : item.toId == userIds.uid
  674. ? item.fromName
  675. : item.toName
  676. }}</span
  677. ><span class="person-cont spill">
  678. {{ item.file?.fileName ? item.file?.fileName : item.content }}</span
  679. >
  680. </div>
  681. <span
  682. class="yuandian"
  683. v-if="item.showCircel && $route.path == '/index' && clickPersonId!==item.fromId"
  684. ></span>
  685. </template>
  686. </div>
  687. </div>
  688. </div>
  689. <!-- 右侧聊天 -->
  690. <div class="right-main">
  691. <div
  692. class="common-layout"
  693. style="display: flex; flex-direction: column; height: 100vh"
  694. >
  695. <el-container>
  696. <el-header height="64px" class="right-header flex-buju">{{
  697. headerName
  698. }}</el-header>
  699. <!-- 聊天 -->
  700. <el-main
  701. class="right-container"
  702. @wheel="handleScroll"
  703. ref="mainContainer"
  704. >
  705. <div v-loading="loading"></div>
  706. <!-- <div v-if="noMes" style="color:#9fa1a5 ;">无更多聊天记录</div> -->
  707. <div
  708. class="message-container"
  709. v-for="(record, index) in chatRecords.data"
  710. :class="{
  711. 'message-left': useUserStore().uid == record.toId,
  712. 'message-right': useUserStore().uid !== record.toId,
  713. }"
  714. :key="index"
  715. >
  716. <div>
  717. <div
  718. v-if="useUserStore().uid !== record.toId"
  719. style="display: flex"
  720. >
  721. <div
  722. :class="
  723. record.msgType == '1'
  724. ? 'file-msg right-back'
  725. : 'time-text right-back'
  726. "
  727. >
  728. <img
  729. :src="word"
  730. v-if="record.msgType == '1'"
  731. class="head-sculpture"
  732. />
  733. <div
  734. :class="
  735. record.msgType == '2' ? 'clip-path' : 'clip-path-right'
  736. "
  737. >
  738. <div
  739. style="
  740. color: #c1cce3;
  741. font-size: 12px;
  742. margin-bottom: 4px;
  743. "
  744. >
  745. {{ record.createTime.slice(0, -3) }}
  746. </div>
  747. <span v-if="record.msgType == '1'">{{
  748. record.file?.fileName
  749. }}</span>
  750. <!-- <span v-else-if="record.msgType == '2'">{{
  751. record.content
  752. }}</span> -->
  753. <span
  754. v-else-if="record.msgType == '2'"
  755. v-html="formatText(record.content)"
  756. ></span>
  757. </div>
  758. </div>
  759. <div>
  760. <!-- 右侧聊天头像 -->
  761. <img
  762. :src="wangzhi + record.fromAvatar"
  763. class="head-sculpture"
  764. :onerror="headError(index,record)"
  765. v-if="
  766. useUserStore().uid != record.toId && record.fromAvatar
  767. "
  768. />
  769. <img
  770. :src="wangzhi + record.toAvatar"
  771. class="head-sculpture"
  772. :onerror="headError(index,record)"
  773. v-else-if="
  774. useUserStore().uid == record.toId && record.toAvatar
  775. "
  776. />
  777. <span
  778. style="
  779. background-color: #7a89ba;
  780. display: inline-block;
  781. color: #fff;
  782. font-weight: 600;
  783. text-align: center;
  784. line-height: 40px;
  785. "
  786. class="head-sculpture"
  787. v-else
  788. >{{ record.fromName!=null ? record.fromName.slice(0, 1) :'系' }}</span
  789. >
  790. </div>
  791. </div>
  792. <!-- 头像 -->
  793. <!-- <img :src="chat" class="head-sculpture" /> -->
  794. </div>
  795. <div style="display: flex; align-items: center">
  796. <div
  797. v-if="useUserStore().uid == record.toId"
  798. style="display: flex"
  799. >
  800. <div>
  801. <!-- 左侧侧聊天头像 -->
  802. <img
  803. :src="wangzhi + record.fromAvatar"
  804. class="head-sculpture"
  805. :onerror="headError(index,record)"
  806. v-if="
  807. useUserStore().uid == record.toId && record.fromAvatar
  808. "
  809. />
  810. <img
  811. :src="wangzhi + record.toAvatar"
  812. class="head-sculpture"
  813. :onerror="headError(index,record)"
  814. v-else-if="
  815. useUserStore().uid != record.toId && record.toAvatar
  816. "
  817. />
  818. <span
  819. style="
  820. background-color: #7a89ba;
  821. display: inline-block;
  822. color: #fff;
  823. font-weight: 600;
  824. text-align: center;
  825. line-height: 40px;
  826. "
  827. class="head-sculpture"
  828. v-else
  829. >{{ record.fromName!=null? record.fromName.slice(0, 1):'系' }}</span
  830. >
  831. </div>
  832. <div
  833. :class="
  834. record.msgType == '1' ? 'file-msg left-back' : 'left-back'
  835. "
  836. @click="transferFiles(record.msgType, record.msgId, index)"
  837. >
  838. <img
  839. v-if="record.msgType == '1'"
  840. :src="setImg(record.file.fileType)"
  841. class="head-sculpture"
  842. />
  843. <div
  844. :class="
  845. record.msgType == '2' ? 'clip-path' : 'clip-path-left'
  846. "
  847. >
  848. <div
  849. style="
  850. color: #7a89ba;
  851. font-size: 12px;
  852. margin-bottom: 4px;
  853. "
  854. >
  855. {{ record.createTime.slice(0, -3) }}
  856. </div>
  857. <span v-if="record.msgType == '1'">{{
  858. record.file?.fileName
  859. }}</span>
  860. <span
  861. v-else-if="record.msgType == '2'"
  862. v-html="formatText(record.content)"
  863. ></span>
  864. </div>
  865. </div>
  866. </div>
  867. <img
  868. :src="preFile"
  869. class="zhuanfa downf shouzhi"
  870. alt="预览"
  871. v-if="record.isForward"
  872. @click="toPreviewFile(index, record.file)"
  873. />
  874. <img
  875. :src="forwardFile"
  876. class="zhuanfa forwd shouzhi"
  877. alt="转存"
  878. v-if="record.isForward && record.file.unloading"
  879. @click="forwardClick(index, record.file?.docId)"
  880. />
  881. <img
  882. :src="downFile"
  883. class="zhuanfa downf shouzhi"
  884. alt="下载"
  885. v-if="record.isForward && record.file.unloading"
  886. @click="downClick(record.file)"
  887. />
  888. </div>
  889. </div>
  890. </el-main>
  891. <!-- 底部 -->
  892. <el-footer height="112px" class="right-footer" v-if="sendCont.data.toId>0">
  893. <!-- 发送文件 -->
  894. <img
  895. :src="sendFile"
  896. class="send-info-file shouzhi"
  897. @click="sendFileClick"
  898. />
  899. <FileTreeChoice
  900. :openFile="openFile"
  901. @close="openFile = false"
  902. :fileUserTreeData="fileUserTreeData.data"
  903. @fileChangeMsg="fileChangeMsg"
  904. ></FileTreeChoice>
  905. <el-input
  906. v-model="messageText"
  907. class="w-50 m-2"
  908. type="textarea"
  909. :autosize="{ minRows: 3, maxRows: 5 }"
  910. clearable
  911. ref="inputRef"
  912. size="small"
  913. placeholder="请输入聊天内容"
  914. maxlength="800"
  915. @keyup.ctrl.enter="msgSendClick($event)"
  916. @keyup.shift.enter="msgSendClick($event)"
  917. @keydown.enter="msgSendClick2($event)"
  918. />
  919. <!-- 发送按钮 -->
  920. <img :src="send" class="send-info" @click="msgSendClick2($event,true)" />
  921. </el-footer>
  922. </el-container>
  923. </div>
  924. </div>
  925. </div>
  926. <!-- 发送消息按钮选择的文件 -->
  927. <forwordTree
  928. :openForwardFile="openForwardFile"
  929. :docId="docId"
  930. :spaceId="spaceId"
  931. @close="openForwardFile = false"
  932. :forwardTreeData="forwardTreeData.data"
  933. @forwardChangeMsg="forwardChangeMsg"
  934. ></forwordTree>
  935. <ImgPreview
  936. :previewData="previewData"
  937. :copyFileType="copyFileType"
  938. :showPreview="showPreview"
  939. @closeImgPreview="closeImgPreview"
  940. ></ImgPreview>
  941. <div
  942. v-loading.fullscreen="loadingPreview"
  943. v-if="loadingPreview"
  944. class="lodingBox"
  945. ></div>
  946. </template>
  947. <style lang="scss" scoped>
  948. @import "@/assets/styles/my-common.scss";
  949. .left-container {
  950. position: relative;
  951. }
  952. .yuandian {
  953. width: 8px;
  954. height: 8px;
  955. position: absolute;
  956. right: 8px;
  957. top: 6px;
  958. background: #fa5151;
  959. border-radius: 4px;
  960. }
  961. :deep(.el-textarea__inner) {
  962. resize: none;
  963. }
  964. </style>