index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <script>
  2. export default {
  3. name: "index",
  4. };
  5. </script>
  6. <script setup>
  7. import { ref, reactive, toRaw, toRefs } 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.jpg";
  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 { parseTime } from "@/utils/ruoyi";
  18. import store from "@/store";
  19. import {
  20. msgFriend,
  21. userTree,
  22. userInfo,
  23. msgSend,
  24. msgRecord,
  25. delMsg,
  26. dirTree,
  27. fileTree,
  28. spaceInfo,
  29. fileDownload,
  30. } from "@/api/chat/msg";
  31. import Addperson from "@/components/AddPerson/index.vue"; //添加人员的弹框
  32. import FileTreeChoice from "@/components/FileTreeChoice/index.vue"; //选择文件发送的列表
  33. import forwordTree from "@/components/forwordTree/index.vue"; //选择文件发送的列表
  34. //websocket连接====
  35. import useWebsoctStore from "@/store/modules/websocket";
  36. import { ElMessage } from "element-plus";
  37. const route = useRoute();
  38. const websoctStore = useWebsoctStore();
  39. //====
  40. const { proxy } = getCurrentInstance();
  41. const userIds = useUserStore();
  42. const height = ref(document.documentElement.clientHeight - 74 + "px;");
  43. const messageText = ref(""); //发送的内容
  44. const headerName = ref("");
  45. const total = ref(0);
  46. const sendId = ref(""); //选择发送的文件id
  47. const isForward = ref(false);
  48. const openFile = ref(false); //文件目录
  49. const openForwardFile = ref(false); //转发目录
  50. const showCircel = ref(""); //是否 显示圆点
  51. const chatRecords = reactive({ data: [] });
  52. const loading = ref(false);
  53. const sendCont = reactive({
  54. //发送聊天内容数据组装
  55. data: {
  56. content: "",
  57. fileList: [],
  58. toId: 0,
  59. msgType: "",
  60. },
  61. });
  62. const searchText = ref(""); //搜
  63. const searchData = ref([]);
  64. //聊天列表数据模拟
  65. const personList = reactive({ data: [] });
  66. //获取好友列表
  67. const getMsgList = async () => {
  68. const resFriend = await msgFriend();
  69. personList.data = resFriend.rows;
  70. // .filter(
  71. // (item) => item.toId !== useUserStore().uid
  72. // );
  73. searchData.value = personList.data;
  74. //圆点======
  75. searchData.value.map((i) => {
  76. if(i.fromId==websoctStore.messOne?.fromId){i.showCircel=true}else{
  77. i.showCircel=false
  78. }
  79. });
  80. //圆点======
  81. sendCont.data.toId =
  82. personList.data[0]?.toId == userIds.uid
  83. ? personList.data[0].fromId
  84. : personList.data[0].toId;
  85. headerName.value =
  86. personList.data[0]?.toId == userIds.uid
  87. ? personList.data[0].fromName
  88. : personList.data[0].toName;
  89. if (sendCont.data.toId) {
  90. //调用聊天记录
  91. msgRecordEvent(sendCont.data.toId);
  92. }
  93. };
  94. //获取用户的聊天记录
  95. const msgRecordEvent = async (toIdValue) => {
  96. const queryParams = {
  97. pageNum: 1,
  98. pageSize: 10,
  99. };
  100. const resMsgData = await msgRecord(toIdValue, queryParams);
  101. resMsgData.rows.map((i) => (i.isForward = false));
  102. chatRecords.data = resMsgData.rows.reverse();
  103. const nowtime = parseTime(new Date().getTime(), "{y}-{m}-{d}");
  104. chatRecords.data.map((i) => {
  105. if (nowtime == i.createTime.substr(0, 10))
  106. i.createTime = i.createTime.substring(11);
  107. });
  108. total.value = resMsgData.total;
  109. };
  110. //点击左侧新建聊天
  111. const open = ref(false);
  112. const userTreeData = reactive({ data: [] });
  113. const clickNewPerson = async () => {
  114. const res = await userTree();
  115. userTreeData.data = res;
  116. toRaw(userTreeData.data);
  117. open.value = true;
  118. };
  119. //点击左侧聊天列表
  120. const clickPersonIndex = ref("");
  121. const clickPerson = (index, item) => {
  122. sendCont.data.toId = item.userId
  123. ? item.userId
  124. : item.toId == userIds.uid
  125. ? item.fromId
  126. : item.toId;
  127. headerName.value = item.nickName
  128. ? item.nickName
  129. : item.toId == userIds.uid
  130. ? item.fromName
  131. : item.toName;
  132. noMes.value = false;
  133. clickPersonIndex.value = index;
  134. //点击某个人就不显示红点
  135. searchData.value.map((i) => {
  136. if(i.fromId==websoctStore.messOne?.fromId){i.showCircel=false}
  137. });
  138. msgRecordEvent(sendCont.data.toId);
  139. };
  140. //删除聊天
  141. const delClick = (msgId) => {
  142. proxy.$modal
  143. .confirm("删除后,将清空该聊天的消息记录")
  144. .then(function () {
  145. return delMsg(msgId);
  146. })
  147. .then(() => {
  148. getMsgList();
  149. proxy.$modal.msgSuccess("删除成功");
  150. });
  151. };
  152. //树选中的人传过来的选中人的信息,push进入列表中
  153. const changeMsg = async (val) => {
  154. const resInfo = await userInfo(val.id);
  155. if (searchData.value.length > 0) {
  156. //判断是否有相同的聊天人
  157. const filerData = searchData.value.filter(
  158. (vPerson) =>
  159. (vPerson.toId && vPerson.toId == resInfo.userId) ||
  160. (vPerson.userId && vPerson.userId == resInfo.userId)
  161. );
  162. if (filerData.length > 0) {
  163. return ElMessage({ message: "该聊天已存在", type: "error" });
  164. } else {
  165. searchData.value.unshift(resInfo);
  166. }
  167. } else {
  168. //为空数组的时候无需判断
  169. searchData.value.unshift(resInfo);
  170. }
  171. sendCont.data.toId = resInfo.userId;
  172. msgRecordEvent(sendCont.data.toId);
  173. headerName.value = resInfo.nickName;
  174. };
  175. //发送聊天
  176. const msgSendClick = () => {
  177. noMes.value = false;
  178. if (messageText.value.trim() == "") {
  179. return ElMessage({ message: "不能发送空白消息", type: "error" });
  180. }
  181. const message = {
  182. content: messageText.value,
  183. msgType: "2",
  184. fileList: [],
  185. toId: sendCont.data.toId,
  186. };
  187. websoctStore.sendMessage(message);
  188. //发送消息后滚动到最底部显示最新消息
  189. const chatContainer = document.querySelector(".right-container");
  190. // 计算滚动的目标位置
  191. const targetScrollTop = chatContainer.scrollHeight;
  192. // 设置滚动位置
  193. chatContainer.scrollTop = targetScrollTop;
  194. messageText.value = "";
  195. };
  196. //发送文件确认按钮
  197. const fileChangeMsg = async (val) => {
  198. const message = {
  199. content: val.id,
  200. msgType: "1",
  201. fileList: [],
  202. toId: sendCont.data.toId,
  203. };
  204. sendId.value = val.id;
  205. websoctStore.sendMessage(message);
  206. };
  207. //点击发送文件图标
  208. const fileUserTreeData = reactive({ data: {} });
  209. const sendFileClick = async () => {
  210. const resDir = await fileTree(3);
  211. fileUserTreeData.data = resDir;
  212. toRaw(fileUserTreeData.data);
  213. openFile.value = true;
  214. };
  215. //获取用户聊天记录
  216. const handleNewMessage = async () => {
  217. if (websoctStore.newMessage) {
  218. msgRecordEvent(sendCont.data.toId); //获取用户的聊天记录
  219. getMsgList();
  220. websoctStore.newMessage = false; // 重置新消息标记
  221. }
  222. };
  223. watchEffect(() => {
  224. if (websoctStore.messOne?.fromId) {
  225. msgRecordEvent(websoctStore.messOne.fromId);
  226. getMsgList();
  227. }
  228. });
  229. // 滚动翻页========
  230. const noMes = ref(false);
  231. const handleScroll = (event) => {
  232. // 在滚动到顶部时,加载上一页的聊天记录
  233. const mainContainer = document.querySelector(".right-container");
  234. if (
  235. event.deltaY < 0 &&
  236. mainContainer.scrollTop <= 1 &&
  237. !noMes.value &&
  238. !loading.value
  239. ) {
  240. loading.value = true;
  241. loadPreviousPage();
  242. }
  243. };
  244. const loadPreviousPage = async () => {
  245. const currentPageNum = Math.ceil(chatRecords.data.length / 10) + 1;
  246. const queryParams = {
  247. pageNum: currentPageNum,
  248. pageSize: 10,
  249. };
  250. const resMsgData = await msgRecord(sendCont.data.toId, queryParams);
  251. const previousPageData = resMsgData.rows.reverse();
  252. chatRecords.data = [...previousPageData, ...chatRecords.data];
  253. const nowtime2 = parseTime(new Date().getTime(), "{y}-{m}-{d}");
  254. chatRecords.data.map((i) => {
  255. if (nowtime2 == i.createTime.substr(0, 10))
  256. i.createTime = i.createTime.substring(11);
  257. });
  258. noMes.value = false;
  259. if (currentPageNum * 10 >= resMsgData.total && !noMes.value) {
  260. loading.value = false;
  261. noMes.value = true;
  262. return;
  263. }
  264. // 将加载的上一页聊天记录插入到 chatRecords.data 的前面
  265. loading.value = false;
  266. };
  267. const transferFiles = (forwardVal, msgIds, indexs) => {
  268. chatRecords.data.map((i, index) => {
  269. if (i.msgId == msgIds && i.msgType == "1" && indexs == index) {
  270. i.isForward = true;
  271. } else {
  272. i.isForward = false;
  273. }
  274. });
  275. };
  276. // 点击转存
  277. const forwardTreeData = reactive({ data: {} });
  278. const spaceId = ref("");
  279. const docId = ref("");
  280. const forwardClick = async (indexs, docIds) => {
  281. docId.value = docIds ? docIds.toString() : "";
  282. const resDir = await dirTree(3);
  283. forwardTreeData.data = resDir;
  284. toRaw(forwardTreeData.data);
  285. //获取最上层树的id
  286. const topSpaceid = await spaceInfo(3);
  287. spaceId.value = topSpaceid.data.spaceId.toString();
  288. openForwardFile.value = true;
  289. };
  290. const forwardChangeMsg = async (val) => {};
  291. //点击下载
  292. const downClick = async (fileId) => {
  293. location.href = `${import.meta.env.VITE_APP_BASE_API}/api/download/${fileId}`;
  294. };
  295. //搜索的点击事件
  296. const SearchChat = () => {
  297. if (searchText.value) {
  298. searchData.value = personList.data.filter((i) => {
  299. return i.toName == searchText.value;
  300. });
  301. } else {
  302. getMsgList();
  303. }
  304. };
  305. // 滚动翻页========
  306. onMounted(() => {
  307. getMsgList();
  308. websoctStore.connect();
  309. setInterval(handleNewMessage, 1000); // 每秒钟检查是否有新消息
  310. });
  311. </script>
  312. <template>
  313. <div class="main" :style="'height:' + height">
  314. <!-- 左侧用户列表 -->
  315. <div class="left-main">
  316. <div class="left-top search">
  317. <el-input
  318. v-model="searchText"
  319. maxlength="32"
  320. class="w-50 m-2"
  321. size="small"
  322. clearable
  323. placeholder="搜索聊天"
  324. suffix-icon="Search"
  325. @change="SearchChat"
  326. />
  327. <!-- 添加聊天人员 -->
  328. <el-icon
  329. size="24"
  330. color="#505870"
  331. @click="clickNewPerson"
  332. style="margin-right: 8px"
  333. ><Plus
  334. /></el-icon>
  335. <!-- 新建聊天弹框 -->
  336. <Addperson
  337. :open="open"
  338. @close="open = false"
  339. :userTreeData="userTreeData.data"
  340. @changeMsg="changeMsg"
  341. ></Addperson>
  342. </div>
  343. <!-- 列表 -->
  344. <div
  345. :class="
  346. clickPersonIndex == index
  347. ? 'activ-left-container left-container'
  348. : 'left-container'
  349. "
  350. v-for="(item, index) in searchData"
  351. :key="index"
  352. @click="clickPerson(index, item)"
  353. >
  354. <img :src="cebian" class="cebian" v-if="clickPersonIndex == index" />
  355. <!-- <button
  356. class="del-chat"
  357. v-if="clickPersonIndex == index"
  358. @click="delClick(item.msgId)"
  359. >
  360. 删除聊天
  361. </button> -->
  362. <div>
  363. <img :src="item.avatar ? item.avatar : chat" class="head-sculpture" />
  364. </div>
  365. <div class="spill">
  366. <span class="person-name">{{
  367. item.nickName
  368. ? item.nickName
  369. : item.toId == userIds.uid
  370. ? item.fromName
  371. : item.toName
  372. }}</span
  373. ><span class="person-cont spill">
  374. {{ item.file?.fileName ? item.file?.fileName : item.content }}</span
  375. >
  376. </div>
  377. <span
  378. class="yuandian"
  379. v-if="item.showCircel&& $route.path == '/index'"
  380. ></span>
  381. </div>
  382. </div>
  383. <!-- 右侧聊天 -->
  384. <div class="right-main">
  385. <div
  386. class="common-layout"
  387. style="display: flex; flex-direction: column; height: 100vh"
  388. >
  389. <el-container>
  390. <el-header height="64px" class="right-header flex-buju">{{
  391. headerName
  392. }}</el-header>
  393. <!-- 聊天 -->
  394. <el-main
  395. class="right-container"
  396. @mousewheel="handleScroll"
  397. ref="mainContainer"
  398. >
  399. <div v-loading="loading"></div>
  400. <!-- <div v-if="noMes" style="color:#9fa1a5 ;">无更多聊天记录</div> -->
  401. <div
  402. class="message-container"
  403. v-for="(record, index) in chatRecords.data"
  404. :class="{
  405. 'message-left': useUserStore().uid == record.toId,
  406. 'message-right': useUserStore().uid !== record.toId,
  407. }"
  408. :key="index"
  409. >
  410. <div
  411. v-if="useUserStore().uid !== record.toId"
  412. :class="
  413. record.msgType == '1'
  414. ? 'file-msg right-back'
  415. : 'time-text right-back'
  416. "
  417. >
  418. <img
  419. :src="word"
  420. v-if="record.msgType == '1'"
  421. class="head-sculpture"
  422. />
  423. <div
  424. :class="
  425. record.msgType == '2' ? 'clip-path' : 'clip-path-right'
  426. "
  427. >
  428. <div
  429. style="color: #c1cce3; font-size: 12px; margin-bottom: 4px"
  430. >
  431. {{ record.createTime.slice(0, -3) }}
  432. </div>
  433. <span v-if="record.msgType == '1'">{{
  434. record.file?.fileName
  435. }}</span>
  436. <span v-else-if="record.msgType == '2'">{{
  437. record.content
  438. }}</span>
  439. </div>
  440. </div>
  441. <img :src="chat" class="head-sculpture" />
  442. <div style="display: flex; align-items: center">
  443. <div
  444. v-if="useUserStore().uid == record.toId"
  445. :class="
  446. record.msgType == '1' ? 'file-msg left-back' : 'left-back'
  447. "
  448. @click="transferFiles(record.msgType, record.msgId, index)"
  449. >
  450. <img
  451. v-if="record.msgType == '1'"
  452. :src="word"
  453. class="head-sculpture"
  454. />
  455. <div
  456. :class="
  457. record.msgType == '2' ? 'clip-path' : 'clip-path-left'
  458. "
  459. >
  460. <div
  461. style="
  462. color: #7a89ba;
  463. font-size: 12px;
  464. margin-bottom: 4px;
  465. "
  466. >
  467. {{ record.createTime.slice(0, -3) }}
  468. </div>
  469. <span v-if="record.msgType == '1'">{{
  470. record.file?.fileName
  471. }}</span>
  472. <span v-else-if="record.msgType == '2'">{{
  473. record.content
  474. }}</span>
  475. </div>
  476. </div>
  477. <img
  478. :src="forwardFile"
  479. class="zhuanfa forwd"
  480. alt="转存"
  481. v-if="record.isForward"
  482. @click="forwardClick(index, record.file?.docId)"
  483. />
  484. <img
  485. :src="downFile"
  486. class="zhuanfa downf"
  487. alt="下载"
  488. v-if="record.isForward"
  489. @click="downClick(record.file.fileId)"
  490. />
  491. </div>
  492. </div>
  493. </el-main>
  494. <!-- 底部 -->
  495. <el-footer height="72px" class="right-footer">
  496. <!-- 发送文件 -->
  497. <img
  498. :src="sendFile"
  499. class="send-info-file"
  500. @click="sendFileClick"
  501. />
  502. <FileTreeChoice
  503. :openFile="openFile"
  504. @close="openFile = false"
  505. :fileUserTreeData="fileUserTreeData.data"
  506. @fileChangeMsg="fileChangeMsg"
  507. ></FileTreeChoice>
  508. <el-input
  509. v-model="messageText"
  510. class="w-50 m-2"
  511. clearable
  512. size="small"
  513. placeholder="请输入聊天内容"
  514. maxlength="800"
  515. @keydown.enter="msgSendClick"
  516. />
  517. <!-- 发送按钮 -->
  518. <img :src="send" class="send-info" @click="msgSendClick" />
  519. </el-footer>
  520. </el-container>
  521. </div>
  522. </div>
  523. </div>
  524. <forwordTree
  525. :openForwardFile="openForwardFile"
  526. :docId="docId"
  527. :spaceId="spaceId"
  528. @close="openForwardFile = false"
  529. :forwardTreeData="forwardTreeData.data"
  530. @forwardChangeMsg="forwardChangeMsg"
  531. ></forwordTree>
  532. </template>
  533. <style lang="scss" scoped>
  534. @import "@/assets/styles/my-common.scss";
  535. .left-container {
  536. position: relative;
  537. }
  538. .yuandian {
  539. width: 8px;
  540. height: 8px;
  541. position: absolute;
  542. right: 8px;
  543. top: 6px;
  544. background: #fa5151;
  545. border-radius: 4px;
  546. }
  547. </style>