index.vue 26 KB

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