index.vue 30 KB

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