index.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. <template>
  2. <div>
  3. <div
  4. :class="{
  5. 'tui-chat': true,
  6. 'tui-chat-h5': isMobile,
  7. }"
  8. @click="onMessageListBackgroundClick"
  9. >
  10. <!-- <JoinGroupCard /> -->
  11. <div class="tui-chat-main">
  12. <div class="tui-chat-safe-tips">
  13. <div class="qzy-space-between-center function-box">
  14. <!-- 用户端 -->
  15. <div @tap="sendMsgHandle('电话沟通')">
  16. <!-- 电话沟通按钮状态根据是否聊过天判断,接受按钮根据是否发送面试邀请判断 -->
  17. <template v-if="messageList && messageList.length > 0">
  18. <img
  19. src="https://qianzhiy-applet.oss-rg-china-mainland.aliyuncs.com/h5/images/house/icon/chat-icon1-active.png"
  20. />
  21. <div class="item-title">电话沟通</div>
  22. </template>
  23. <template v-else>
  24. <img
  25. src="https://qianzhiy-applet.oss-rg-china-mainland.aliyuncs.com/h5/images/house/icon/chat-icon1.png"
  26. />
  27. <div class="item-title item-item-disabled">电话沟通</div>
  28. </template>
  29. </div>
  30. <!-- 对方为用户显示预约看房按钮 -->
  31. <div
  32. @tap="sendMsgHandle('预约看房')"
  33. v-if="infoData.agentId === '0' || loginType == 1"
  34. >
  35. <!-- 电话沟通按钮状态根据是否聊过天判断,接受按钮根据是否发送面试邀请判断 -->
  36. <template v-if="messageList && messageList.length > 0">
  37. <img
  38. src="https://qianzhiy-applet.oss-rg-china-mainland.aliyuncs.com/h5/images/house/icon/chat-icon5-active.png"
  39. />
  40. <div class="item-title">预约看房</div>
  41. </template>
  42. <template v-else>
  43. <img
  44. src="https://qianzhiy-applet.oss-rg-china-mainland.aliyuncs.com/h5/images/house/icon/chat-icon5.png"
  45. />
  46. <div class="item-title item-item-disabled">预约看房</div>
  47. </template>
  48. </div>
  49. <!-- <div @tap="sendMsgHandle('接受预约')">
  50. <template v-if="props.infoData.status == 0">
  51. <img src="https://qianzhiy-applet.oss-rg-china-mainland.aliyuncs.com/h5/images/house/icon/chat-icon4-active.png" />
  52. <div class="item-title">接受预约</div>
  53. </template>
  54. <template v-else>
  55. <img src="https://qianzhiy-applet.oss-rg-china-mainland.aliyuncs.com/h5/images/house/icon/chat-icon4.png" />
  56. <div class="item-title item-item-disabled">接受预约</div>
  57. </template>
  58. </div> -->
  59. </div>
  60. <div
  61. class="job-info"
  62. @tap="
  63. toDetail(
  64. infoData.publishType,
  65. infoData.houseId,
  66. infoData.houseStatus,
  67. infoData.demandId
  68. )
  69. "
  70. >
  71. <div class="qzy-space-between-center">
  72. <template v-if="infoData.demandId == ''">
  73. <div class="text-16">
  74. <span class="color-333 text-bold">{{
  75. infoData.communityName
  76. }}</span>
  77. <div class="text-500 text-14 color-999 company-name">
  78. {{ infoData.priceEnd
  79. }}{{ infoData.unit == 0 ? "元/月" : "万" }}
  80. </div>
  81. </div>
  82. <div class="text-500">
  83. <i class="color-999">信息费:</i>
  84. <span class="color-red">{{ infoData.messageFee }}元</span>
  85. <image
  86. src="https://qianzhiy-applet.oss-rg-china-mainland.aliyuncs.com/h5/images/house/user/arrow-right.png"
  87. class="right-icon"
  88. />
  89. </div>
  90. </template>
  91. <template v-if="infoData.demandId != ''">
  92. <div class="demand-box">
  93. <div class="color-333 text-bold ellipsis-nowrap">
  94. {{ infoData.demandAddr }}
  95. </div>
  96. <div class="qzy-space-between-center">
  97. <div class="text-500 text-14 color-999 company-name">
  98. {{ infoData.demandType == 0 ? "(求租)" : "(买房)"
  99. }}{{ infoData.demandPrice
  100. }}{{ infoData.demandType == 0 ? "元/月" : "万" }}
  101. </div>
  102. <div class="text-500">
  103. <i class="color-999">信息费:</i>
  104. <span class="color-red">{{ infoData.messageFee }}元</span>
  105. </div>
  106. </div>
  107. </div>
  108. </template>
  109. </div>
  110. </div>
  111. </div>
  112. <MessageGroupApplication
  113. v-if="isGroup"
  114. :key="props.groupID"
  115. :groupID="props.groupID"
  116. />
  117. <scroll-view
  118. id="messageScrollList"
  119. class="tui-message-list"
  120. scroll-y="true"
  121. :scroll-top="scrollTop"
  122. :scroll-into-view="`tui-${historyFirstMessageID}`"
  123. @scroll="handelScrollListScroll"
  124. >
  125. <p
  126. v-if="!isCompleted"
  127. class="message-more"
  128. @click="getHistoryMessageList"
  129. >
  130. {{ TUITranslateService.t("TUIChat.查看更多") }}
  131. </p>
  132. <!-- {{messageList}} -->
  133. <li
  134. v-for="(item, index) in messageList"
  135. :id="`tui-${item.ID}`"
  136. :key="item.vueForRenderKey"
  137. :class="'message-li ' + item.flow"
  138. >
  139. <MessageTimestamp
  140. :currTime="item.time"
  141. :prevTime="index > 0 ? messageList[index - 1].time : 0"
  142. />
  143. <div class="message-item" @click="toggleID = ''">
  144. <MessageTip
  145. v-if="
  146. item.type === TYPES.MSG_GRP_TIP ||
  147. isCreateGroupCustomMessage(item)
  148. "
  149. :content="item.getMessageContent()"
  150. />
  151. <div
  152. v-else-if="!item.isRevoked && !isPluginMessage(item)"
  153. :id="`msg-bubble-${item.ID}`"
  154. class="message-bubble-container"
  155. @longpress="handleToggleMessageItem($event, item, index, true)"
  156. @touchstart="
  157. handleH5LongPress($event, item, index, 'touchstart')
  158. "
  159. @touchend="handleH5LongPress($event, item, index, 'touchend')"
  160. @mouseover="handleH5LongPress($event, item, index, 'touchend')"
  161. >
  162. <MessageBubble
  163. :messageItem="item"
  164. :content="item.getMessageContent()"
  165. :blinkMessageIDList="blinkMessageIDList"
  166. @resendMessage="resendMessage(item)"
  167. @blinkMessage="blinkMessage"
  168. @scrollTo="scrollTo"
  169. @setReadReceiptPanelVisible="setReadReceiptPanelVisible"
  170. >
  171. <MessageText
  172. v-if="item.type === TYPES.MSG_TEXT"
  173. :content="item.getMessageContent()"
  174. />
  175. <ProgressMessage
  176. v-if="item.type === TYPES.MSG_IMAGE"
  177. :content="item.getMessageContent()"
  178. :messageItem="item"
  179. >
  180. <MessageImage
  181. :content="item.getMessageContent()"
  182. :messageItem="item"
  183. @previewImage="handleImagePreview(index)"
  184. />
  185. </ProgressMessage>
  186. <ProgressMessage
  187. v-if="item.type === TYPES.MSG_VIDEO"
  188. :content="item.getMessageContent()"
  189. :messageItem="item"
  190. >
  191. <MessageVideo
  192. :content="item.getMessageContent()"
  193. :messageItem="item"
  194. />
  195. </ProgressMessage>
  196. <MessageAudio
  197. v-if="item.type === TYPES.MSG_AUDIO"
  198. :content="item.getMessageContent()"
  199. :messageItem="item"
  200. :broadcastNewAudioSrc="broadcastNewAudioSrc"
  201. @getGlobalAudioContext="getGlobalAudioContext"
  202. />
  203. <MessageFile
  204. v-if="item.type === TYPES.MSG_FILE"
  205. :content="item.getMessageContent()"
  206. />
  207. <MessageFace
  208. v-if="item.type === TYPES.MSG_FACE"
  209. :content="item.getMessageContent()"
  210. :isPC="isPC"
  211. />
  212. <MessageLocation
  213. v-if="item.type === TYPES.MSG_LOCATION"
  214. :content="item.getMessageContent()"
  215. />
  216. <MessageCustom
  217. v-if="item.type === TYPES.MSG_CUSTOM"
  218. :content="item.getMessageContent()"
  219. :messageItem="item"
  220. />
  221. </MessageBubble>
  222. </div>
  223. <MessagePlugin
  224. v-else-if="!item.isRevoked && isPluginMessage(item)"
  225. :message="item"
  226. @resendMessage="resendMessage"
  227. @handleToggleMessageItem="handleToggleMessageItem"
  228. @handleH5LongPress="handleH5LongPress"
  229. />
  230. <MessageRevoked
  231. v-else
  232. :isEdit="item.type === TYPES.MSG_TEXT"
  233. :messageItem="item"
  234. @messageEdit="handleEdit(item)"
  235. />
  236. <MessageTool
  237. v-if="item.ID === toggleID"
  238. :class="[
  239. 'message-tool',
  240. item.flow === 'out' ? 'message-tool-out' : 'message-tool-in',
  241. ]"
  242. :messageItem="item"
  243. />
  244. </div>
  245. </li>
  246. </scroll-view>
  247. <!-- 滚动按钮 -->
  248. <ScrollButton
  249. ref="scrollButtonInstanceRef"
  250. @scrollToLatestMessage="scrollToLatestMessage"
  251. />
  252. <Dialog
  253. v-if="reSendDialogShow"
  254. :show="reSendDialogShow"
  255. :isH5="!isPC"
  256. :center="true"
  257. :isHeaderShow="isPC"
  258. @submit="resendMessageConfirm()"
  259. @update:show="(e) => (reSendDialogShow = e)"
  260. >
  261. <p class="delDialog-title">
  262. {{ TUITranslateService.t("TUIChat.确认重发该消息?") }}
  263. </p>
  264. </Dialog>
  265. <!-- 已读回执用户列表面板 -->
  266. <ReadReceiptPanel
  267. v-if="isShowReadUserStatusPanel"
  268. :message="Object.assign({}, readStatusMessage)"
  269. @setReadReceiptPanelVisible="setReadReceiptPanelVisible"
  270. />
  271. </div>
  272. </div>
  273. <InterViewApplication
  274. :mapAddress="mapAddress"
  275. ref="InterViewApplicationRef"
  276. v-if="showDialog"
  277. :showDialog="showDialog"
  278. @close="ShowDialogClose"
  279. :infoData="infoData"
  280. ></InterViewApplication>
  281. </div>
  282. </template>
  283. <script lang="ts" setup>
  284. import InterViewApplication from "./interview-application.vue";
  285. import ToolbarItemContainer from "../../TUIChat/message-input-toolbar/evaluate/index.vue";
  286. import {
  287. ref,
  288. nextTick,
  289. onMounted,
  290. onUnmounted,
  291. getCurrentInstance,
  292. watch,
  293. onUpdated,
  294. } from "../../../adapter-vue";
  295. import TUIChatEngine, {
  296. IMessageModel,
  297. TUIStore,
  298. StoreName,
  299. TUITranslateService,
  300. TUIChatService,
  301. } from "@tencentcloud/chat-uikit-engine";
  302. import throttle from "lodash/throttle";
  303. import {
  304. setInstanceMapping,
  305. getBoundingClientRect,
  306. getScrollInfo,
  307. } from "@tencentcloud/universal-api";
  308. // import { JoinGroupCard } from '@tencentcloud/call-uikit-wechat';
  309. import Link from "./link";
  310. import MessageGroupApplication from "./message-group-application/index.vue";
  311. import MessageText from "./message-elements/message-text.vue";
  312. import ProgressMessage from "../../common/ProgressMessage/index.vue";
  313. import MessageImage from "./message-elements/message-image.vue";
  314. import MessageAudio from "./message-elements/message-audio.vue";
  315. import MessageFile from "./message-elements/message-file.vue";
  316. import MessageFace from "./message-elements/message-face.vue";
  317. import MessageCustom from "./message-elements/message-custom.vue";
  318. import MessageTip from "./message-elements/message-tip.vue";
  319. import MessageBubble from "./message-elements/message-bubble.vue";
  320. import MessageLocation from "./message-elements/message-location.vue";
  321. import MessageTimestamp from "./message-elements/message-timestamp.vue";
  322. import MessageVideo from "./message-elements/message-video.vue";
  323. import MessageTool from "./message-tool/index.vue";
  324. import MessageRevoked from "./message-tool/message-revoked.vue";
  325. import MessagePlugin from "../../../plugins/plugin-components/message-plugin.vue";
  326. import ReadReceiptPanel from "./read-receipt-panel/index.vue";
  327. import ScrollButton from "./scroll-button/index.vue";
  328. import { isPluginMessage } from "../../../plugins/plugin-components/index";
  329. import Dialog from "../../common/Dialog/index.vue";
  330. import { Toast, TOAST_TYPE } from "../../common/Toast/index";
  331. import { isCreateGroupCustomMessage } from "../utils/utils";
  332. import { isEnabledMessageReadReceiptGlobal } from "../utils/utils";
  333. import { isPC, isH5, isMobile } from "../../../utils/env";
  334. import { IAudioContext } from "../../../interface";
  335. import config from "@/request/config";
  336. import value from "../../../../uni_modules/uview-ui/components/u-text/value";
  337. interface IEmits {
  338. (e: "closeInputToolBar"): void;
  339. (e: "handleEditor", message: IMessageModel, type: string): void;
  340. }
  341. const emits = defineEmits<IEmits>();
  342. const props = defineProps({
  343. groupID: {
  344. type: String,
  345. default: "",
  346. },
  347. isGroup: {
  348. type: Boolean,
  349. default: false,
  350. },
  351. infoData: {
  352. type: Object,
  353. },
  354. address: {
  355. type: Object,
  356. },
  357. });
  358. const mapAddress = ref();
  359. watch(props, (nweProps) => {
  360. console.log("ccc", nweProps);
  361. mapAddress.value = nweProps.address;
  362. });
  363. const showDialog = ref(false);
  364. const chatFunctionList = ref();
  365. let observer: any = null;
  366. let groupType: string | undefined;
  367. const sentReceiptMessageID = new Set<string>();
  368. const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance();
  369. const messageList = ref<IMessageModel[]>();
  370. const isCompleted = ref(false);
  371. const currentConversationID = ref("");
  372. const toggleID = ref("");
  373. const scrollTop = ref(5000); // 首次是 15 条消息,最大消息高度为300
  374. const TYPES = ref(TUIChatEngine.TYPES);
  375. const isLoadingMessage = ref(false);
  376. const isLongpressing = ref(false);
  377. const blinkMessageIDList = ref<string[]>([]);
  378. const messageTarget = ref<IMessageModel>();
  379. const scrollButtonInstanceRef = ref<InstanceType<typeof ScrollButton>>();
  380. const historyFirstMessageID = ref<string>("");
  381. let selfAddValue = 0;
  382. // audio control
  383. const broadcastNewAudioSrc = ref<string>("");
  384. // 阅读回执状态message
  385. const readStatusMessage = ref<IMessageModel>();
  386. const isShowReadUserStatusPanel = ref<boolean>(false);
  387. // 消息重发 Dialog
  388. const reSendDialogShow = ref(false);
  389. const resendMessageData = ref();
  390. // 消息滑动到底部,建议搭配 nextTick 使用
  391. const scrollToBottom = () => {
  392. // 文本消息高度:60, 最大消息高度 280
  393. scrollTop.value += 300;
  394. // 解决 uniapp 打包到 app 首次进入滑动到底部,300 可设置
  395. const timer = setTimeout(() => {
  396. scrollTop.value += 1;
  397. clearTimeout(timer);
  398. }, 300);
  399. };
  400. // 监听回调函数
  401. const onCurrentConversationIDUpdated = (conversationID: string) => {
  402. currentConversationID.value = conversationID;
  403. // 开启已读回执的状态 群聊缓存群类型
  404. if (isEnabledMessageReadReceiptGlobal()) {
  405. const { groupProfile } =
  406. TUIStore.getConversationModel(conversationID) || {};
  407. groupType = groupProfile?.type;
  408. }
  409. };
  410. const ShowDialogClose = () => {
  411. showDialog.value = false;
  412. };
  413. const loginType = ref(uni.getStorageSync("loginType"));
  414. onMounted(() => {
  415. // 消息列表监听
  416. TUIStore.watch(StoreName.CHAT, {
  417. messageList: onMessageListUpdated,
  418. messageSource: onMessageSourceUpdated,
  419. isCompleted: onChatCompletedUpdated,
  420. });
  421. TUIStore.watch(StoreName.CONV, {
  422. currentConversationID: onCurrentConversationIDUpdated,
  423. });
  424. setInstanceMapping("messageList", thisInstance);
  425. uni.$on("scroll-to-bottom", scrollToLatestMessage);
  426. });
  427. // 取消监听
  428. onUnmounted(() => {
  429. TUIStore.unwatch(StoreName.CHAT, {
  430. messageList: onMessageListUpdated,
  431. isCompleted: onChatCompletedUpdated,
  432. });
  433. TUIStore.unwatch(StoreName.CONV, {
  434. currentConversationID: onCurrentConversationIDUpdated,
  435. });
  436. observer?.disconnect();
  437. observer = null;
  438. uni.$off("scroll-to-bottom");
  439. });
  440. const handelScrollListScroll = throttle(
  441. function (e: Event) {
  442. scrollButtonInstanceRef.value?.judgeScrollOverOneScreen(e);
  443. },
  444. 500,
  445. { leading: true }
  446. );
  447. // 各种消息处理
  448. const sendMsgHandle = (title) => {
  449. if (
  450. messageList.value.length == 0 &&
  451. (title == "电话沟通" || title == "预约看房")
  452. ) {
  453. uni.$u.toast("与对方沟通后才可" + title);
  454. return false;
  455. }
  456. if (title == "电话沟通") {
  457. uni.makePhoneCall({
  458. phoneNumber: props.infoData.shopUserPhone,
  459. });
  460. } else if (title == "预约看房") {
  461. webUni.webView.navigateTo({
  462. url:
  463. "/packageHouse/user/house-manage/subscribe-house?inviteeId=" + props.infoData.shopUserId,
  464. });
  465. }
  466. };
  467. // 跳转详情
  468. const toDetail = (publishType, houseId, houseStatus, demandId) => {
  469. if (demandId) {
  470. return false;
  471. }
  472. if (houseStatus == -1) {
  473. uni.$u.toast("该房源已删除");
  474. } else {
  475. let path = ref("");
  476. if (publishType == 2) {
  477. path = "/packageHouse/home/house/new-house-detail";
  478. } else if (publishType == 3) {
  479. path = "/packageHouse/home/house/used-house-detail";
  480. } else {
  481. path = "/packageHouse/home/house/renting-house-detail";
  482. }
  483. webUni.webView.navigateTo({
  484. url: path + "?id=" + houseId,
  485. });
  486. }
  487. };
  488. function getGlobalAudioContext(
  489. audioMap: Map<string, IAudioContext>,
  490. options?: { newAudioSrc: string }
  491. ) {
  492. if (options?.newAudioSrc) {
  493. broadcastNewAudioSrc.value = options.newAudioSrc;
  494. }
  495. }
  496. async function onMessageListUpdated(list: IMessageModel[]) {
  497. observer?.disconnect();
  498. console.log(1111123123213);
  499. messageList.value = list
  500. .filter((message) => !message.isDeleted)
  501. .map((message) => {
  502. message.vueForRenderKey = `${message.ID}`;
  503. return message;
  504. });
  505. const newLastMessage = messageList.value?.[messageList.value?.length - 1];
  506. if (messageTarget.value) {
  507. // scroll to target message
  508. scrollAndBlinkMessage(messageTarget.value);
  509. } else if (
  510. !isLoadingMessage.value &&
  511. !(
  512. scrollButtonInstanceRef.value?.isScrollButtonVisible &&
  513. newLastMessage?.flow === "in"
  514. )
  515. ) {
  516. // scroll to bottom
  517. nextTick(() => {
  518. scrollToBottom();
  519. });
  520. }
  521. if (isEnabledMessageReadReceiptGlobal()) {
  522. nextTick(() => bindIntersectionObserver());
  523. }
  524. }
  525. // 滚动到最新消息
  526. async function scrollToLatestMessage() {
  527. try {
  528. const { scrollHeight } = await getScrollInfo(
  529. "#messageScrollList",
  530. "messageList"
  531. );
  532. if (scrollHeight) {
  533. scrollTop.value === scrollHeight
  534. ? (scrollTop.value = scrollHeight + 1)
  535. : (scrollTop.value = scrollHeight);
  536. } else {
  537. scrollToBottom();
  538. }
  539. } catch (error) {
  540. scrollToBottom();
  541. }
  542. }
  543. async function onMessageSourceUpdated(message: IMessageModel) {
  544. messageTarget.value = message;
  545. scrollAndBlinkMessage(messageTarget.value);
  546. }
  547. function scrollAndBlinkMessage(message: IMessageModel) {
  548. if (
  549. messageList.value?.some(
  550. (messageListItem) => messageListItem?.ID === message?.ID
  551. )
  552. ) {
  553. nextTick(async () => {
  554. await scrollToTargetMessage(message);
  555. await blinkMessage(message?.ID);
  556. messageTarget.value = undefined;
  557. });
  558. }
  559. }
  560. function onChatCompletedUpdated(flag: boolean) {
  561. isCompleted.value = flag;
  562. }
  563. // 获取历史消息
  564. const getHistoryMessageList = () => {
  565. isLoadingMessage.value = true;
  566. const currentFirstMessageID = messageList.value?.[0]?.ID || "";
  567. TUIChatService.getMessageList().then(() => {
  568. nextTick(() => {
  569. historyFirstMessageID.value = currentFirstMessageID;
  570. const timer = setTimeout(() => {
  571. historyFirstMessageID.value = "";
  572. isLoadingMessage.value = false;
  573. clearTimeout(timer);
  574. }, 500);
  575. });
  576. });
  577. };
  578. // 消息操作
  579. const handleToggleMessageItem = (
  580. e: any,
  581. message: IMessageModel,
  582. index: number,
  583. isLongpress = false
  584. ) => {
  585. if (isLongpress) {
  586. isLongpressing.value = true;
  587. }
  588. toggleID.value = message.ID;
  589. };
  590. // h5 long press
  591. let timer: number;
  592. const handleH5LongPress = (
  593. e: any,
  594. message: IMessageModel,
  595. index: number,
  596. type: string
  597. ) => {
  598. if (!isH5) return;
  599. function longPressHandler() {
  600. clearTimeout(timer);
  601. handleToggleMessageItem(e, message, index, true);
  602. }
  603. function touchStartHandler() {
  604. timer = setTimeout(longPressHandler, 500);
  605. }
  606. function touchEndHandler() {
  607. clearTimeout(timer);
  608. }
  609. switch (type) {
  610. case "touchstart":
  611. touchStartHandler();
  612. break;
  613. case "touchend":
  614. touchEndHandler();
  615. setTimeout(() => {
  616. isLongpressing.value = false;
  617. }, 200);
  618. break;
  619. }
  620. };
  621. // 消息撤回后,编辑消息
  622. const handleEdit = (message: IMessageModel) => {
  623. emits("handleEditor", message, "reedit");
  624. };
  625. // 重发消息
  626. const resendMessage = (message: IMessageModel) => {
  627. reSendDialogShow.value = true;
  628. resendMessageData.value = message;
  629. };
  630. // 图片预览
  631. // 开启图片预览
  632. const handleImagePreview = (index: number) => {
  633. if (!messageList.value) {
  634. return;
  635. }
  636. const imageMessageIndex: number[] = [];
  637. const imageMessageList: IMessageModel[] = messageList.value.filter(
  638. (item, index) => {
  639. if (
  640. !item.isRevoked &&
  641. !item.hasRiskContent &&
  642. item.type === TYPES.value.MSG_IMAGE
  643. ) {
  644. imageMessageIndex.push(index);
  645. return true;
  646. }
  647. return false;
  648. }
  649. );
  650. uni.previewImage({
  651. current: imageMessageIndex.indexOf(index),
  652. urls: imageMessageList.map(
  653. (message) => message.payload.imageInfoArray?.[2].url
  654. ),
  655. // #ifdef APP-PLUS
  656. indicator: "number",
  657. // #endif
  658. });
  659. };
  660. const resendMessageConfirm = () => {
  661. reSendDialogShow.value = !reSendDialogShow.value;
  662. const messageModel = resendMessageData.value;
  663. messageModel.resendMessage();
  664. };
  665. function blinkMessage(messageID: string): Promise<void> {
  666. return new Promise((resolve) => {
  667. const index = blinkMessageIDList.value.indexOf(messageID);
  668. if (index < 0) {
  669. blinkMessageIDList.value.push(messageID);
  670. const timer = setTimeout(() => {
  671. blinkMessageIDList.value.splice(
  672. blinkMessageIDList.value.indexOf(messageID),
  673. 1
  674. );
  675. clearTimeout(timer);
  676. resolve();
  677. }, 3000);
  678. }
  679. });
  680. }
  681. function scrollTo(scrollHeight: number) {
  682. scrollTop.value = scrollHeight;
  683. }
  684. async function bindIntersectionObserver() {
  685. if (!messageList.value || messageList.value.length === 0) {
  686. return;
  687. }
  688. if (
  689. groupType === TYPES.value.GRP_AVCHATROOM ||
  690. groupType === TYPES.value.GRP_COMMUNITY
  691. ) {
  692. // 直播群以及社群不进行消息的已读回执监听
  693. return;
  694. }
  695. observer?.disconnect();
  696. observer = uni
  697. .createIntersectionObserver(thisInstance, {
  698. threshold: [0.7],
  699. observeAll: true,
  700. // uni 下会把 safetip 也算进去 需要负 margin 来排除
  701. })
  702. .relativeTo("#messageScrollList", { top: -70 });
  703. observer?.observe(".message-li.in .message-bubble-container", (res: any) => {
  704. if (sentReceiptMessageID.has(res.id)) {
  705. return;
  706. }
  707. const matchingMessage = messageList.value.find((message: IMessageModel) => {
  708. return res.id.indexOf(message.ID) > -1;
  709. });
  710. if (
  711. matchingMessage &&
  712. matchingMessage.needReadReceipt &&
  713. matchingMessage.flow === "in" &&
  714. !matchingMessage.readReceiptInfo?.isPeerRead
  715. ) {
  716. TUIChatService.sendMessageReadReceipt([matchingMessage]);
  717. sentReceiptMessageID.add(res.id);
  718. }
  719. });
  720. }
  721. function setReadReceiptPanelVisible(visible: boolean, message?: IMessageModel) {
  722. if (!visible) {
  723. readStatusMessage.value = undefined;
  724. } else {
  725. readStatusMessage.value = message;
  726. }
  727. isShowReadUserStatusPanel.value = visible;
  728. }
  729. async function scrollToTargetMessage(message: IMessageModel) {
  730. const targetMessageID = message.ID;
  731. const isTargetMessageInScreen =
  732. messageList.value &&
  733. messageList.value.some((msg) => msg.ID === targetMessageID);
  734. if (targetMessageID && isTargetMessageInScreen) {
  735. const timer = setTimeout(async () => {
  736. try {
  737. const scrollViewRect = await getBoundingClientRect(
  738. "#messageScrollList",
  739. "messageList"
  740. );
  741. const originalMessageRect = await getBoundingClientRect(
  742. "#tui-" + targetMessageID,
  743. "messageList"
  744. );
  745. const { scrollTop } = await getScrollInfo(
  746. "#messageScrollList",
  747. "messageList"
  748. );
  749. const finalScrollTop =
  750. originalMessageRect.top +
  751. scrollTop -
  752. scrollViewRect.top -
  753. (selfAddValue++ % 2);
  754. scrollTo(finalScrollTop);
  755. clearTimeout(timer);
  756. } catch (error) {
  757. // todo
  758. }
  759. }, 500);
  760. } else {
  761. Toast({
  762. message: TUITranslateService.t("TUIChat.无法定位到原消息"),
  763. type: TOAST_TYPE.WARNING,
  764. });
  765. }
  766. }
  767. function onMessageListBackgroundClick() {
  768. emits("closeInputToolBar");
  769. }
  770. </script>
  771. <style lang="scss" scoped src="./style/index.scss"></style>
  772. <style lang="scss" scoped>
  773. .function-box {
  774. padding: 20px 0px 12px;
  775. > div {
  776. flex: 1;
  777. text-align: center;
  778. }
  779. img {
  780. width: 20px;
  781. height: 20px;
  782. margin-bottom: 12px;
  783. }
  784. }
  785. .job-info {
  786. padding: 8px 25px;
  787. background: #f1fffd;
  788. .demand-box {
  789. width: 100%;
  790. }
  791. .company-name {
  792. margin-top: 5px;
  793. }
  794. .right-icon {
  795. width: 20upx;
  796. height: 20upx;
  797. margin-left: 4upx;
  798. }
  799. i {
  800. font-style: normal;
  801. }
  802. }
  803. </style>