123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- <template>
- <div
- v-if="!isAllActionItemInvalid && !messageItem.hasRiskContent"
- ref="messageToolDom"
- :class="['dialog-item', !isPC ? 'dialog-item-h5' : 'dialog-item-web']"
- >
- <slot
- v-if="featureConfig.EmojiReaction"
- name="TUIEmojiPlugin"
- />
- <div
- class="dialog-item-list"
- :class="!isPC ? 'dialog-item-list-h5' : 'dialog-item-list-web'"
- >
- <template v-for="(item, index) in actionItems">
- <div
- v-if="item.renderCondition()"
- :key="item.key"
- class="list-item"
- @click="getFunction(index)"
- @mousedown="beforeCopy(item.key)"
- >
- <Icon
- :file="item.iconUrl"
- :size="'15px'"
- />
- <span class="list-item-text">{{ item.text }}</span>
- </div>
- </template>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- import TUIChatEngine, {
- TUIStore,
- StoreName,
- TUITranslateService,
- IMessageModel,
- } from '@tencentcloud/chat-uikit-engine';
- import { TUIGlobal } from '@tencentcloud/universal-api';
- import { ref, watchEffect, computed, onMounted, onUnmounted } from '../../../../adapter-vue';
- import Icon from '../../../common/Icon.vue';
- import { Toast, TOAST_TYPE } from '../../../common/Toast/index';
- import delIcon from '../../../../assets/icon/msg-del.svg';
- import copyIcon from '../../../../assets/icon/msg-copy.svg';
- import quoteIcon from '../../../../assets/icon/msg-quote.svg';
- import revokeIcon from '../../../../assets/icon/msg-revoke.svg';
- import forwardIcon from '../../../../assets/icon/msg-forward.svg';
- import translateIcon from '../../../../assets/icon/translate.svg';
- import multipleSelectIcon from '../../../../assets/icon/multiple-select.svg';
- import convertText from '../../../../assets/icon/convertText_zh.svg';
- import { enableSampleTaskStatus } from '../../../../utils/enableSampleTaskStatus';
- import { transformTextWithKeysToEmojiNames } from '../../emoji-config';
- import { isH5, isPC, isUniFrameWork } from '../../../../utils/env';
- import { ITranslateInfo, IConvertInfo } from '../../../../interface';
- import TUIChatConfig from '../../config';
- // uni-app conditional compilation will not run the following code
- // #ifndef APP || APP-PLUS || MP || H5
- import CopyManager from '../../utils/copy';
- // #endif
- interface IProps {
- messageItem: IMessageModel;
- isMultipleSelectMode: boolean;
- }
- interface IEmits {
- (key: 'toggleMultipleSelectMode'): void;
- }
- const emits = defineEmits<IEmits>();
- const props = withDefaults(defineProps<IProps>(), {
- isMultipleSelectMode: false,
- messageItem: () => ({}) as IMessageModel,
- });
- const featureConfig = TUIChatConfig.getFeatureConfig();
- const TYPES = TUIChatEngine.TYPES;
- const actionItems = ref([
- {
- key: 'open',
- text: TUITranslateService.t('TUIChat.打开'),
- iconUrl: copyIcon,
- renderCondition() {
- if (!featureConfig.DownloadFile || !message.value) return false;
- return isPC && (message.value?.type === TYPES.MSG_FILE
- || message.value.type === TYPES.MSG_VIDEO
- || message.value.type === TYPES.MSG_IMAGE);
- },
- clickEvent: openMessage,
- },
- {
- key: 'copy',
- text: TUITranslateService.t('TUIChat.复制'),
- iconUrl: copyIcon,
- renderCondition() {
- if (!featureConfig.CopyMessage || !message.value) return false;
- return message.value.type === TYPES.MSG_TEXT;
- },
- clickEvent: copyMessage,
- },
- {
- key: 'revoke',
- text: TUITranslateService.t('TUIChat.撤回'),
- iconUrl: revokeIcon,
- renderCondition() {
- if (!featureConfig.RevokeMessage || !message.value) return false;
- return message.value.flow === 'out' && message.value.status === 'success';
- },
- clickEvent: revokeMessage,
- },
- {
- key: 'delete',
- text: TUITranslateService.t('TUIChat.删除'),
- iconUrl: delIcon,
- renderCondition() {
- if (!featureConfig.DeleteMessage || !message.value) return false;
- return message.value.status === 'success';
- },
- clickEvent: deleteMessage,
- },
- {
- key: 'forward',
- text: TUITranslateService.t('TUIChat.转发'),
- iconUrl: forwardIcon,
- renderCondition() {
- if (!featureConfig.ForwardMessage || !message.value) return false;
- return message.value.status === 'success';
- },
- clickEvent: forwardSingleMessage,
- },
- {
- key: 'quote',
- text: TUITranslateService.t('TUIChat.引用'),
- iconUrl: quoteIcon,
- renderCondition() {
- if (!featureConfig.QuoteMessage || !message.value) return false;
- const _message = TUIStore.getMessageModel(message.value.ID);
- return message.value.status === 'success' && !_message.getSignalingInfo();
- },
- clickEvent: quoteMessage,
- },
- {
- key: 'translate',
- text: TUITranslateService.t('TUIChat.翻译'),
- visible: false,
- iconUrl: translateIcon,
- renderCondition() {
- if (!featureConfig.TranslateMessage || !message.value) return false;
- return message.value.status === 'success' && message.value.type === TYPES.MSG_TEXT;
- },
- clickEvent: translateMessage,
- },
- {
- key: 'convert',
- text: TUITranslateService.t('TUIChat.转文字'),
- visible: false,
- iconUrl: convertText,
- renderCondition() {
- if (!featureConfig.VoiceToText || !message.value) return false;
- return message.value.status === 'success' && message.value.type === TYPES.MSG_AUDIO;
- },
- clickEvent: convertVoiceToText,
- },
- {
- key: 'multi-select',
- text: TUITranslateService.t('TUIChat.多选'),
- iconUrl: multipleSelectIcon,
- renderCondition() {
- if (!featureConfig.MultiSelection || !message.value) return false;
- return message.value.status === 'success';
- },
- clickEvent: multipleSelectMessage,
- },
- ]);
- const message = ref<IMessageModel>();
- const messageToolDom = ref<HTMLElement>();
- onMounted(() => {
- TUIStore.watch(StoreName.CHAT, {
- translateTextInfo: onMessageTranslationInfoUpdated,
- voiceToTextInfo: onMessageConvertInfoUpdated,
- });
- });
- onUnmounted(() => {
- TUIStore.unwatch(StoreName.CHAT, {
- translateTextInfo: onMessageTranslationInfoUpdated,
- voiceToTextInfo: onMessageConvertInfoUpdated,
- });
- });
- watchEffect(() => {
- message.value = TUIStore.getMessageModel(props.messageItem.ID);
- });
- const isAllActionItemInvalid = computed(() => {
- for (let i = 0; i < actionItems.value.length; ++i) {
- if (actionItems.value[i].renderCondition()) {
- return false;
- }
- }
- return true;
- });
- function getFunction(index: number) {
- // Compatible with Vue2 and WeChat Mini Program syntax, dynamic binding is not allowed.
- actionItems.value[index].clickEvent();
- }
- function openMessage() {
- let url = '';
- switch (message.value?.type) {
- case TUIChatEngine.TYPES.MSG_FILE:
- url = message.value.payload.fileUrl;
- break;
- case TUIChatEngine.TYPES.MSG_VIDEO:
- url = message.value.payload.remoteVideoUrl;
- break;
- case TUIChatEngine.TYPES.MSG_IMAGE:
- url = message.value.payload.imageInfoArray[0].url;
- break;
- }
- window?.open(url, '_blank');
- }
- function revokeMessage() {
- if (!message.value) return;
- const messageModel = TUIStore.getMessageModel(message.value.ID);
- messageModel
- .revokeMessage()
- .then(() => {
- enableSampleTaskStatus('revokeMessage');
- })
- .catch((error: any) => {
- if (error.code === 20016) {
- const message = TUITranslateService.t('TUIChat.已过撤回时限');
- Toast({
- message,
- type: TOAST_TYPE.ERROR,
- });
- }
- });
- }
- function deleteMessage() {
- if (!message.value) return;
- const messageModel = TUIStore.getMessageModel(message.value.ID);
- messageModel.deleteMessage();
- }
- async function copyMessage() {
- if (isUniFrameWork) {
- TUIGlobal?.setClipboardData({
- data: transformTextWithKeysToEmojiNames(message.value?.payload?.text),
- });
- } else {
- // uni-app conditional compilation will not run the following code
- // #ifndef APP || APP-PLUS || MP || H5
- CopyManager.copySelection(message.value?.payload?.text);
- // #endif
- }
- }
- function beforeCopy(key: string) {
- // only pc support copy selection or copy full message text
- // uni-app and h5 only support copy full message text
- if (key !== 'copy' || isH5) {
- return;
- }
- // uni-app conditional compilation will not run the following code
- // #ifndef APP || APP-PLUS || MP || H5
- CopyManager.saveCurrentSelection();
- // #endif
- }
- function forwardSingleMessage() {
- if (!message.value) return;
- TUIStore.update(StoreName.CUSTOM, 'singleForwardMessageID', message.value.ID);
- }
- function quoteMessage() {
- if (!message.value) return;
- message.value.quoteMessage();
- }
- function translateMessage() {
- const enable = TUIStore.getData(StoreName.APP, 'enabledTranslationPlugin');
- if (!enable) {
- Toast({
- message: TUITranslateService.t('TUIChat.请开通翻译功能'),
- type: TOAST_TYPE.WARNING,
- });
- return;
- }
- if (!message.value) return;
- const index = actionItems.value.findIndex(item => item.key === 'translate');
- TUIStore.update(StoreName.CHAT, 'translateTextInfo', {
- conversationID: message.value.conversationID,
- messageID: message.value.ID,
- visible: !actionItems.value[index].visible,
- });
- }
- function convertVoiceToText() {
- const enable = TUIStore.getData(StoreName.APP, 'enabledVoiceToText');
- if (!enable) {
- Toast({
- message: TUITranslateService.t('TUIChat.请开通语音转文字功能'),
- });
- return;
- }
- if (!message.value) return;
- const index = actionItems.value.findIndex(item => item.key === 'convert');
- TUIStore.update(StoreName.CHAT, 'voiceToTextInfo', {
- conversationID: message.value.conversationID,
- messageID: message.value.ID,
- visible: !actionItems.value[index].visible,
- });
- }
- function multipleSelectMessage() {
- emits('toggleMultipleSelectMode');
- }
- function onMessageTranslationInfoUpdated(info: Map<string, ITranslateInfo[]>) {
- if (info === undefined) return;
- const translationInfoList = info.get(props.messageItem.conversationID) || [];
- const idx = actionItems.value.findIndex(item => item.key === 'translate');
- for (let i = 0; i < translationInfoList.length; ++i) {
- const { messageID, visible } = translationInfoList[i];
- if (messageID === props.messageItem.ID) {
- actionItems.value[idx].text = TUITranslateService.t(visible ? 'TUIChat.隐藏' : 'TUIChat.翻译');
- actionItems.value[idx].visible = !!visible;
- return;
- }
- }
- actionItems.value[idx].text = TUITranslateService.t('TUIChat.翻译');
- }
- function onMessageConvertInfoUpdated(info: Map<string, IConvertInfo[]>) {
- if (info === undefined) return;
- const convertInfoList = info.get(props.messageItem.conversationID) || [];
- const idx = actionItems.value.findIndex(item => item.key === 'convert');
- for (let i = 0; i < convertInfoList.length; ++i) {
- const { messageID, visible } = convertInfoList[i];
- if (messageID === props.messageItem.ID) {
- actionItems.value[idx].text = TUITranslateService.t(visible ? 'TUIChat.隐藏' : 'TUIChat.转文字');
- actionItems.value[idx].visible = !!visible;
- return;
- }
- }
- actionItems.value[idx].text = TUITranslateService.t('TUIChat.转文字');
- }
- defineExpose({
- messageToolDom,
- });
- </script>
- <style lang="scss" scoped>
- @import "../../../../assets/styles/common";
- .dialog-item-web {
- background: #fff;
- border-radius: 8px;
- border: 1px solid #e0e0e0;
- padding: 12px 0;
- .dialog-item-list {
- display: flex;
- align-items: baseline;
- white-space: nowrap;
- flex-wrap: wrap;
- max-width: 280px;
- .list-item {
- padding: 4px 12px;
- display: flex;
- flex-direction: row;
- align-items: center;
- .list-item-text {
- padding-left: 4px;
- font-size: 12px;
- line-height: 17px;
- }
- }
- }
- }
- .dialog-item-h5 {
- @extend .dialog-item-web;
- padding: 0;
- .dialog-item-list {
- margin: 10px;
- white-space: nowrap;
- flex-wrap: wrap;
- max-width: 280px;
- .list-item {
- padding: 0 8px;
- display: flex;
- flex-direction: column;
- align-items: center;
- color: #4f4f4f;
- .list-item-text {
- padding-left: 0;
- }
- }
- }
- }
- </style>
|