123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731 |
- <template>
- <div>
- <div
- :class="{
- 'tui-chat': true,
- 'tui-chat-h5': isMobile,
- }"
- @click="onMessageListBackgroundClick"
- >
- <!-- <JoinGroupCard /> -->
- <div class="tui-chat-main">
- <MessageGroupApplication
- v-if="isGroup"
- :key="props.groupID"
- :groupID="props.groupID"
- />
- <scroll-view
- id="messageScrollList"
- class="tui-message-list"
- scroll-y="true"
- :scroll-top="scrollTop"
- :scroll-into-view="`tui-${historyFirstMessageID}`"
- @scroll="handelScrollListScroll"
- >
- <p
- v-if="!isCompleted"
- class="message-more"
- @click="getHistoryMessageList"
- >
- {{ TUITranslateService.t("TUIChat.查看更多") }}
- </p>
- <li
- v-for="(item, index) in messageList"
- :id="`tui-${item.ID}`"
- :key="item.vueForRenderKey"
- :class="'message-li ' + item.flow"
- >
- <MessageTimestamp
- :currTime="item.time"
- :prevTime="index > 0 ? messageList[index - 1].time : 0"
- />
- <div class="message-item" @click="toggleID = ''">
- <MessageTip
- v-if="
- item.type === TYPES.MSG_GRP_TIP ||
- isCreateGroupCustomMessage(item)
- "
- :content="item.getMessageContent()"
- />
- <div
- v-else-if="!item.isRevoked && !isPluginMessage(item)"
- :id="`msg-bubble-${item.ID}`"
- class="message-bubble-container"
- @longpress="handleToggleMessageItem($event, item, index, true)"
- @touchstart="
- handleH5LongPress($event, item, index, 'touchstart')
- "
- @touchend="handleH5LongPress($event, item, index, 'touchend')"
- @mouseover="handleH5LongPress($event, item, index, 'touchend')"
- >
- <MessageBubble
- :messageItem="item"
- :content="item.getMessageContent()"
- :blinkMessageIDList="blinkMessageIDList"
- @resendMessage="resendMessage(item)"
- @blinkMessage="blinkMessage"
- @scrollTo="scrollTo"
- @setReadReceiptPanelVisible="setReadReceiptPanelVisible"
- >
- <MessageText
- v-if="item.type === TYPES.MSG_TEXT"
- :content="item.getMessageContent()"
- />
- <ProgressMessage
- v-if="item.type === TYPES.MSG_IMAGE"
- :content="item.getMessageContent()"
- :messageItem="item"
- >
- <MessageImage
- :content="item.getMessageContent()"
- :messageItem="item"
- @previewImage="handleImagePreview(index)"
- />
- </ProgressMessage>
- <ProgressMessage
- v-if="item.type === TYPES.MSG_VIDEO"
- :content="item.getMessageContent()"
- :messageItem="item"
- >
- <MessageVideo
- :content="item.getMessageContent()"
- :messageItem="item"
- />
- </ProgressMessage>
- <MessageAudio
- v-if="item.type === TYPES.MSG_AUDIO"
- :content="item.getMessageContent()"
- :messageItem="item"
- :broadcastNewAudioSrc="broadcastNewAudioSrc"
- @getGlobalAudioContext="getGlobalAudioContext"
- />
- <MessageFile
- v-if="item.type === TYPES.MSG_FILE"
- :content="item.getMessageContent()"
- />
- <MessageFace
- v-if="item.type === TYPES.MSG_FACE"
- :content="item.getMessageContent()"
- :isPC="isPC"
- />
- <MessageLocation
- v-if="item.type === TYPES.MSG_LOCATION"
- :content="item.getMessageContent()"
- />
- <MessageCustom
- v-if="item.type === TYPES.MSG_CUSTOM"
- :content="item.getMessageContent()"
- :messageItem="item"
- />
- </MessageBubble>
- </div>
- <MessagePlugin
- v-else-if="!item.isRevoked && isPluginMessage(item)"
- :message="item"
- @resendMessage="resendMessage"
- @handleToggleMessageItem="handleToggleMessageItem"
- @handleH5LongPress="handleH5LongPress"
- />
- <MessageRevoked
- v-else
- :isEdit="item.type === TYPES.MSG_TEXT"
- :messageItem="item"
- @messageEdit="handleEdit(item)"
- />
- <MessageTool
- v-if="item.ID === toggleID"
- :class="[
- 'message-tool',
- item.flow === 'out' ? 'message-tool-out' : 'message-tool-in',
- ]"
- :messageItem="item"
- />
- </div>
- </li>
- </scroll-view>
- <!-- 滚动按钮 -->
- <ScrollButton
- ref="scrollButtonInstanceRef"
- @scrollToLatestMessage="scrollToLatestMessage"
- />
- <Dialog
- v-if="reSendDialogShow"
- :show="reSendDialogShow"
- :isH5="!isPC"
- :center="true"
- :isHeaderShow="isPC"
- @submit="resendMessageConfirm()"
- @update:show="(e) => (reSendDialogShow = e)"
- >
- <p class="delDialog-title">
- {{ TUITranslateService.t("TUIChat.确认重发该消息?") }}
- </p>
- </Dialog>
- <!-- 已读回执用户列表面板 -->
- <ReadReceiptPanel
- v-if="isShowReadUserStatusPanel"
- :message="Object.assign({}, readStatusMessage)"
- @setReadReceiptPanelVisible="setReadReceiptPanelVisible"
- />
- </div>
- </div>
- <InterViewApplication
- :mapAddress="mapAddress"
- ref="InterViewApplicationRef"
- v-if="showDialog"
- :showDialog="showDialog"
- @close="ShowDialogClose"
- :infoData="infoData"
- ></InterViewApplication>
- </div>
- </template>
- <script lang="ts" setup>
- import InterViewApplication from "./interview-application.vue";
- import ToolbarItemContainer from "../../TUIChat/message-input-toolbar/evaluate/index.vue";
- import {
- ref,
- nextTick,
- onMounted,
- onUnmounted,
- getCurrentInstance,
- watch,
- onUpdated,
- } from "../../../adapter-vue";
- import TUIChatEngine, {
- IMessageModel,
- TUIStore,
- StoreName,
- TUITranslateService,
- TUIChatService,
- } from "@tencentcloud/chat-uikit-engine";
- import throttle from "lodash/throttle";
- import {
- setInstanceMapping,
- getBoundingClientRect,
- getScrollInfo,
- } from "@tencentcloud/universal-api";
- // import { JoinGroupCard } from '@tencentcloud/call-uikit-wechat';
- import Link from "./link";
- import MessageGroupApplication from "./message-group-application/index.vue";
- import MessageText from "./message-elements/message-text.vue";
- import ProgressMessage from "../../common/ProgressMessage/index.vue";
- import MessageImage from "./message-elements/message-image.vue";
- import MessageAudio from "./message-elements/message-audio.vue";
- import MessageFile from "./message-elements/message-file.vue";
- import MessageFace from "./message-elements/message-face.vue";
- import MessageCustom from "./message-elements/message-custom.vue";
- import MessageTip from "./message-elements/message-tip.vue";
- import MessageBubble from "./message-elements/message-bubble.vue";
- import MessageLocation from "./message-elements/message-location.vue";
- import MessageTimestamp from "./message-elements/message-timestamp.vue";
- import MessageVideo from "./message-elements/message-video.vue";
- import MessageTool from "./message-tool/index.vue";
- import MessageRevoked from "./message-tool/message-revoked.vue";
- import MessagePlugin from "../../../plugins/plugin-components/message-plugin.vue";
- import ReadReceiptPanel from "./read-receipt-panel/index.vue";
- import ScrollButton from "./scroll-button/index.vue";
- import { isPluginMessage } from "../../../plugins/plugin-components/index";
- import Dialog from "../../common/Dialog/index.vue";
- import { Toast, TOAST_TYPE } from "../../common/Toast/index";
- import { isCreateGroupCustomMessage } from "../utils/utils";
- import { isEnabledMessageReadReceiptGlobal } from "../utils/utils";
- import { isPC, isH5, isMobile } from "../../../utils/env";
- import { IAudioContext } from "../../../interface";
- import config from "@/request/config";
- import value from "../../../../uni_modules/uview-ui/components/u-text/value";
- interface IEmits {
- (e: "closeInputToolBar"): void;
- (e: "handleEditor", message: IMessageModel, type: string): void;
- }
- const emits = defineEmits<IEmits>();
- const props = defineProps({
- groupID: {
- type: String,
- default: "",
- },
- isGroup: {
- type: Boolean,
- default: false,
- },
- infoData: {
- type: Object,
- },
- address: {
- type: Object,
- },
- });
- const mapAddress = ref();
- watch(props, (nweProps) => {
- console.log("ccc", nweProps);
- mapAddress.value = nweProps.address;
- });
- const showDialog = ref(false);
- const chatFunctionList = ref();
- let observer: any = null;
- let groupType: string | undefined;
- const sentReceiptMessageID = new Set<string>();
- const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance();
- const messageList = ref<IMessageModel[]>();
- const isCompleted = ref(false);
- const currentConversationID = ref("");
- const toggleID = ref("");
- const scrollTop = ref(5000); // 首次是 15 条消息,最大消息高度为300
- const TYPES = ref(TUIChatEngine.TYPES);
- const isLoadingMessage = ref(false);
- const isLongpressing = ref(false);
- const blinkMessageIDList = ref<string[]>([]);
- const messageTarget = ref<IMessageModel>();
- const scrollButtonInstanceRef = ref<InstanceType<typeof ScrollButton>>();
- const historyFirstMessageID = ref<string>("");
- let selfAddValue = 0;
- // audio control
- const broadcastNewAudioSrc = ref<string>("");
- // 阅读回执状态message
- const readStatusMessage = ref<IMessageModel>();
- const isShowReadUserStatusPanel = ref<boolean>(false);
- // 消息重发 Dialog
- const reSendDialogShow = ref(false);
- const resendMessageData = ref();
- // 消息滑动到底部,建议搭配 nextTick 使用
- const scrollToBottom = () => {
- // 文本消息高度:60, 最大消息高度 280
- scrollTop.value += 300;
- // 解决 uniapp 打包到 app 首次进入滑动到底部,300 可设置
- const timer = setTimeout(() => {
- scrollTop.value += 1;
- clearTimeout(timer);
- }, 300);
- };
- // 监听回调函数
- const onCurrentConversationIDUpdated = (conversationID: string) => {
- currentConversationID.value = conversationID;
- // 开启已读回执的状态 群聊缓存群类型
- if (isEnabledMessageReadReceiptGlobal()) {
- const { groupProfile } =
- TUIStore.getConversationModel(conversationID) || {};
- groupType = groupProfile?.type;
- }
- };
- const ShowDialogClose = () => {
- showDialog.value = false;
- };
- const loginType = ref(uni.getStorageSync("loginType"));
- onMounted(() => {
- // 消息列表监听
- TUIStore.watch(StoreName.CHAT, {
- messageList: onMessageListUpdated,
- messageSource: onMessageSourceUpdated,
- isCompleted: onChatCompletedUpdated,
- });
- TUIStore.watch(StoreName.CONV, {
- currentConversationID: onCurrentConversationIDUpdated,
- });
- setInstanceMapping("messageList", thisInstance);
- uni.$on("scroll-to-bottom", scrollToLatestMessage);
- console.log(messageList.value);
- });
- // 取消监听
- onUnmounted(() => {
- TUIStore.unwatch(StoreName.CHAT, {
- messageList: onMessageListUpdated,
- isCompleted: onChatCompletedUpdated,
- });
- TUIStore.unwatch(StoreName.CONV, {
- currentConversationID: onCurrentConversationIDUpdated,
- });
- observer?.disconnect();
- observer = null;
- uni.$off("scroll-to-bottom");
- });
- const handelScrollListScroll = throttle(
- function (e: Event) {
- scrollButtonInstanceRef.value?.judgeScrollOverOneScreen(e);
- },
- 500,
- { leading: true }
- );
- // // 跳转详情
- // const toDetail = (publishType, houseId, houseStatus, demandId) => {
- // if (demandId) {
- // return false;
- // }
- // if (houseStatus == -1) {
- // uni.$u.toast("该房源已删除");
- // } else {
- // let path = ref("");
- // if (publishType == 2) {
- // path = "/pages/user/house/new-house-detail";
- // } else if (publishType == 3) {
- // path = "/pages/user/house/used-house-detail";
- // } else {
- // path = "/pages/user/house/renting-house-detail";
- // }
- // uni.navigateTo({
- // url: path + "?id=" + houseId,
- // });
- // }
- // };
- function getGlobalAudioContext(
- audioMap: Map<string, IAudioContext>,
- options?: { newAudioSrc: string }
- ) {
- if (options?.newAudioSrc) {
- broadcastNewAudioSrc.value = options.newAudioSrc;
- }
- }
- async function onMessageListUpdated(list: IMessageModel[]) {
- observer?.disconnect();
- messageList.value = list
- .filter((message) => !message.isDeleted)
- .map((message) => {
- message.vueForRenderKey = `${message.ID}`;
- return message;
- });
- const newLastMessage = messageList.value?.[messageList.value?.length - 1];
- if (messageTarget.value) {
- // scroll to target message
- scrollAndBlinkMessage(messageTarget.value);
- } else if (
- !isLoadingMessage.value &&
- !(
- scrollButtonInstanceRef.value?.isScrollButtonVisible &&
- newLastMessage?.flow === "in"
- )
- ) {
- // scroll to bottom
- nextTick(() => {
- scrollToBottom();
- });
- }
- if (isEnabledMessageReadReceiptGlobal()) {
- nextTick(() => bindIntersectionObserver());
- }
- // 如果未聊过天,自己发送一条当前房源的消息
- console.log(messageList.value);
- }
- // 滚动到最新消息
- async function scrollToLatestMessage() {
- try {
- const { scrollHeight } = await getScrollInfo(
- "#messageScrollList",
- "messageList"
- );
- if (scrollHeight) {
- scrollTop.value === scrollHeight
- ? (scrollTop.value = scrollHeight + 1)
- : (scrollTop.value = scrollHeight);
- } else {
- scrollToBottom();
- }
- } catch (error) {
- scrollToBottom();
- }
- }
- async function onMessageSourceUpdated(message: IMessageModel) {
- messageTarget.value = message;
- scrollAndBlinkMessage(messageTarget.value);
- }
- function scrollAndBlinkMessage(message: IMessageModel) {
- if (
- messageList.value?.some(
- (messageListItem) => messageListItem?.ID === message?.ID
- )
- ) {
- nextTick(async () => {
- await scrollToTargetMessage(message);
- await blinkMessage(message?.ID);
- messageTarget.value = undefined;
- });
- }
- }
- function onChatCompletedUpdated(flag: boolean) {
- isCompleted.value = flag;
- }
- // 获取历史消息
- const getHistoryMessageList = () => {
- isLoadingMessage.value = true;
- const currentFirstMessageID = messageList.value?.[0]?.ID || "";
- TUIChatService.getMessageList().then(() => {
- nextTick(() => {
- historyFirstMessageID.value = currentFirstMessageID;
- const timer = setTimeout(() => {
- historyFirstMessageID.value = "";
- isLoadingMessage.value = false;
- clearTimeout(timer);
- }, 500);
- });
- });
- };
- // 消息操作
- const handleToggleMessageItem = (
- e: any,
- message: IMessageModel,
- index: number,
- isLongpress = false
- ) => {
- if (isLongpress) {
- isLongpressing.value = true;
- }
- toggleID.value = message.ID;
- };
- // h5 long press
- let timer: number;
- const handleH5LongPress = (
- e: any,
- message: IMessageModel,
- index: number,
- type: string
- ) => {
- if (!isH5) return;
- function longPressHandler() {
- clearTimeout(timer);
- handleToggleMessageItem(e, message, index, true);
- }
- function touchStartHandler() {
- timer = setTimeout(longPressHandler, 500);
- }
- function touchEndHandler() {
- clearTimeout(timer);
- }
- switch (type) {
- case "touchstart":
- touchStartHandler();
- break;
- case "touchend":
- touchEndHandler();
- setTimeout(() => {
- isLongpressing.value = false;
- }, 200);
- break;
- }
- };
- // 消息撤回后,编辑消息
- const handleEdit = (message: IMessageModel) => {
- emits("handleEditor", message, "reedit");
- };
- // 重发消息
- const resendMessage = (message: IMessageModel) => {
- reSendDialogShow.value = true;
- resendMessageData.value = message;
- };
- // 图片预览
- // 开启图片预览
- const handleImagePreview = (index: number) => {
- if (!messageList.value) {
- return;
- }
- const imageMessageIndex: number[] = [];
- const imageMessageList: IMessageModel[] = messageList.value.filter(
- (item, index) => {
- if (
- !item.isRevoked &&
- !item.hasRiskContent &&
- item.type === TYPES.value.MSG_IMAGE
- ) {
- imageMessageIndex.push(index);
- return true;
- }
- return false;
- }
- );
- uni.previewImage({
- current: imageMessageIndex.indexOf(index),
- urls: imageMessageList.map(
- (message) => message.payload.imageInfoArray?.[2].url
- ),
- // #ifdef APP-PLUS
- indicator: "number",
- // #endif
- });
- };
- const resendMessageConfirm = () => {
- reSendDialogShow.value = !reSendDialogShow.value;
- const messageModel = resendMessageData.value;
- messageModel.resendMessage();
- };
- function blinkMessage(messageID: string): Promise<void> {
- return new Promise((resolve) => {
- const index = blinkMessageIDList.value.indexOf(messageID);
- if (index < 0) {
- blinkMessageIDList.value.push(messageID);
- const timer = setTimeout(() => {
- blinkMessageIDList.value.splice(
- blinkMessageIDList.value.indexOf(messageID),
- 1
- );
- clearTimeout(timer);
- resolve();
- }, 3000);
- }
- });
- }
- function scrollTo(scrollHeight: number) {
- scrollTop.value = scrollHeight;
- }
- async function bindIntersectionObserver() {
- if (!messageList.value || messageList.value.length === 0) {
- return;
- }
- if (
- groupType === TYPES.value.GRP_AVCHATROOM ||
- groupType === TYPES.value.GRP_COMMUNITY
- ) {
- // 直播群以及社群不进行消息的已读回执监听
- return;
- }
- observer?.disconnect();
- observer = uni
- .createIntersectionObserver(thisInstance, {
- threshold: [0.7],
- observeAll: true,
- // uni 下会把 safetip 也算进去 需要负 margin 来排除
- })
- .relativeTo("#messageScrollList", { top: -70 });
- observer?.observe(".message-li.in .message-bubble-container", (res: any) => {
- if (sentReceiptMessageID.has(res.id)) {
- return;
- }
- const matchingMessage = messageList.value.find((message: IMessageModel) => {
- return res.id.indexOf(message.ID) > -1;
- });
- if (
- matchingMessage &&
- matchingMessage.needReadReceipt &&
- matchingMessage.flow === "in" &&
- !matchingMessage.readReceiptInfo?.isPeerRead
- ) {
- TUIChatService.sendMessageReadReceipt([matchingMessage]);
- sentReceiptMessageID.add(res.id);
- }
- });
- }
- function setReadReceiptPanelVisible(visible: boolean, message?: IMessageModel) {
- if (!visible) {
- readStatusMessage.value = undefined;
- } else {
- readStatusMessage.value = message;
- }
- isShowReadUserStatusPanel.value = visible;
- }
- async function scrollToTargetMessage(message: IMessageModel) {
- const targetMessageID = message.ID;
- const isTargetMessageInScreen =
- messageList.value &&
- messageList.value.some((msg) => msg.ID === targetMessageID);
- if (targetMessageID && isTargetMessageInScreen) {
- const timer = setTimeout(async () => {
- try {
- const scrollViewRect = await getBoundingClientRect(
- "#messageScrollList",
- "messageList"
- );
- const originalMessageRect = await getBoundingClientRect(
- "#tui-" + targetMessageID,
- "messageList"
- );
- const { scrollTop } = await getScrollInfo(
- "#messageScrollList",
- "messageList"
- );
- const finalScrollTop =
- originalMessageRect.top +
- scrollTop -
- scrollViewRect.top -
- (selfAddValue++ % 2);
- scrollTo(finalScrollTop);
- clearTimeout(timer);
- } catch (error) {
- // todo
- }
- }, 500);
- } else {
- Toast({
- message: TUITranslateService.t("TUIChat.无法定位到原消息"),
- type: TOAST_TYPE.WARNING,
- });
- }
- }
- function onMessageListBackgroundClick() {
- emits("closeInputToolBar");
- }
- </script>
- <style lang="scss" scoped src="./style/index.scss"></style>
- <style lang="scss" scoped>
- .function-box {
- padding: 20px 0px 12px;
- > div {
- flex: 1;
- text-align: center;
- }
- img {
- width: 20px;
- height: 20px;
- margin-bottom: 12px;
- }
- }
- .job-info {
- padding: 8px 25px;
- background: #f1fffd;
- .demand-box {
- width: 100%;
- }
- .company-name {
- margin-top: 5px;
- }
- .right-icon {
- width: 20upx;
- height: 20upx;
- margin-left: 4upx;
- }
- i {
- font-style: normal;
- }
- }
- </style>
|