index.vue 30 KB

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