index.vue 21 KB

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