消息
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div
|
||||
ref="emojiPickerDialog"
|
||||
:class="{
|
||||
'emoji-picker': true,
|
||||
'emoji-picker-h5': !isPC
|
||||
}"
|
||||
>
|
||||
<ul
|
||||
ref="emojiPickerListRef"
|
||||
:class="['emoji-picker-list', !isPC && 'emoji-picker-h5-list']"
|
||||
>
|
||||
<li
|
||||
v-for="(childrenItem, childrenIndex) in currentEmojiList"
|
||||
:key="childrenIndex"
|
||||
class="emoji-picker-list-item"
|
||||
@click="select(childrenItem, childrenIndex)"
|
||||
>
|
||||
<img
|
||||
v-if="currentTabItem.type === EMOJI_TYPE.BASIC"
|
||||
class="emoji"
|
||||
:src="currentTabItem.url + BASIC_EMOJI_URL_MAPPING[childrenItem]"
|
||||
>
|
||||
<img
|
||||
v-else-if="currentTabItem.type === EMOJI_TYPE.BIG"
|
||||
class="emoji-big"
|
||||
:src="currentTabItem.url + childrenItem + '@2x.png'"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
class="emoji-custom emoji-big"
|
||||
:src="currentTabItem.url + childrenItem"
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="emoji-picker-tab">
|
||||
<li
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="emoji-picker-tab-item"
|
||||
@click="toggleEmojiTab(index)"
|
||||
>
|
||||
<Icon
|
||||
v-if="item.type === EMOJI_TYPE.BASIC"
|
||||
class="icon"
|
||||
:file="faceIcon"
|
||||
/>
|
||||
<img
|
||||
v-else-if="item.type === EMOJI_TYPE.BIG"
|
||||
class="icon-big"
|
||||
:src="item.url + item.list[0] + '@2x.png'"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
class="icon-custom icon-big"
|
||||
:src="item.url + item.list[0]"
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
v-if="isUniFrameWork"
|
||||
class="send-btn"
|
||||
@click="sendMessage"
|
||||
>
|
||||
发送
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from '../../../../adapter-vue';
|
||||
import {
|
||||
TUIChatService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
SendMessageParams,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import Icon from '../../../common/Icon.vue';
|
||||
import faceIconLight from '../../../../assets/icon/face-light.svg';
|
||||
import faceIconDark from '../../../../assets/icon/face-dark.svg';
|
||||
import { EMOJI_TYPE } from '.././../../../constant';
|
||||
import { isPC, isUniFrameWork } from '../../../../utils/env';
|
||||
import { IEmojiGroupList, IEmojiGroup } from '../../../../interface';
|
||||
import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
|
||||
import { EMOJI_GROUP_LIST, BASIC_EMOJI_URL_MAPPING, convertKeyToEmojiName } from '../../emoji-config';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
const faceIcon = TUIChatConfig.getTheme() === 'dark' ? faceIconDark : faceIconLight;
|
||||
const emits = defineEmits(['insertEmoji', 'onClose', 'sendMessage']);
|
||||
const currentTabIndex = ref<number>(0);
|
||||
const currentConversation = ref();
|
||||
const emojiPickerDialog = ref();
|
||||
const emojiPickerListRef = ref();
|
||||
const featureConfig = TUIChatConfig.getFeatureConfig();
|
||||
const list = ref<IEmojiGroupList>(initEmojiList());
|
||||
const currentTabItem = ref<IEmojiGroup>(list?.value[0]);
|
||||
const currentEmojiList = ref<string[]>(list?.value[0]?.list);
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
const toggleEmojiTab = (index: number) => {
|
||||
currentTabIndex.value = index;
|
||||
currentTabItem.value = list?.value[index];
|
||||
currentEmojiList.value = list?.value[index]?.list;
|
||||
// web & h5 side scroll to top
|
||||
if (!isUniFrameWork) {
|
||||
emojiPickerListRef?.value && (emojiPickerListRef.value.scrollTop = 0);
|
||||
}
|
||||
};
|
||||
|
||||
const select = (item: any, index: number) => {
|
||||
const options: any = {
|
||||
emoji: { key: item, name: convertKeyToEmojiName(item) },
|
||||
type: currentTabItem?.value?.type,
|
||||
};
|
||||
switch (currentTabItem?.value?.type) {
|
||||
case EMOJI_TYPE.BASIC:
|
||||
options.url = currentTabItem?.value?.url + BASIC_EMOJI_URL_MAPPING[item];
|
||||
if (isUniFrameWork) {
|
||||
uni.$emit('insert-emoji', options);
|
||||
} else {
|
||||
emits('insertEmoji', options);
|
||||
}
|
||||
break;
|
||||
case EMOJI_TYPE.BIG:
|
||||
sendFaceMessage(index, currentTabItem.value);
|
||||
break;
|
||||
case EMOJI_TYPE.CUSTOM:
|
||||
sendFaceMessage(index, currentTabItem.value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
isPC && emits('onClose');
|
||||
};
|
||||
|
||||
const sendFaceMessage = (index: number, listItem: IEmojiGroup) => {
|
||||
const options = {
|
||||
to:
|
||||
currentConversation?.value?.groupProfile?.groupID
|
||||
|| currentConversation?.value?.userProfile?.userID,
|
||||
conversationType: currentConversation?.value?.type,
|
||||
payload: {
|
||||
index: listItem.emojiGroupID,
|
||||
data: listItem.list[index],
|
||||
},
|
||||
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
||||
} as SendMessageParams;
|
||||
TUIChatService.sendFaceMessage(options);
|
||||
};
|
||||
|
||||
function sendMessage() {
|
||||
uni.$emit('send-message-in-emoji-picker');
|
||||
}
|
||||
|
||||
function onCurrentConversationUpdate(conversation: IConversationModel) {
|
||||
currentConversation.value = conversation;
|
||||
}
|
||||
|
||||
function initEmojiList() {
|
||||
return EMOJI_GROUP_LIST.filter((item) => {
|
||||
if (item.type === EMOJI_TYPE.BASIC) {
|
||||
return featureConfig.InputEmoji;
|
||||
}
|
||||
if (item.type === EMOJI_TYPE.BIG) {
|
||||
return featureConfig.InputStickers;
|
||||
}
|
||||
if (item.type === EMOJI_TYPE.CUSTOM) {
|
||||
return featureConfig.InputStickers;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
@@ -0,0 +1,2 @@
|
||||
import EmojiPicker from './index.vue';
|
||||
export default EmojiPicker;
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<ToolbarItemContainer
|
||||
ref="container"
|
||||
:iconFile="faceIcon"
|
||||
title="表情"
|
||||
@onDialogShow="onDialogShow"
|
||||
@onDialogClose="onDialogClose"
|
||||
>
|
||||
<EmojiPickerDialog
|
||||
@insertEmoji="insertEmoji"
|
||||
@sendMessage="sendMessage"
|
||||
@onClose="onClose"
|
||||
/>
|
||||
</ToolbarItemContainer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { ref } from '../../../../adapter-vue';
|
||||
import faceIconLight from '../../../../assets/icon/face-light.svg';
|
||||
import faceIconDark from '../../../../assets/icon/face-dark.svg';
|
||||
import EmojiPickerDialog from './emoji-picker-dialog.vue';
|
||||
import ToolbarItemContainer from '../toolbar-item-container/index.vue';
|
||||
import { isH5 } from '../../../../utils/env';
|
||||
import { ToolbarDisplayType } from '../../../../interface';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
interface IEmits {
|
||||
(e: 'sendMessage'): void;
|
||||
(e: 'toggleComponent'): void;
|
||||
(e: 'insertEmoji', emoji: any): void;
|
||||
(e: 'dialogShowInH5', dialogRef: HTMLElement): void;
|
||||
(e: 'dialogCloseInH5', dialogRef: HTMLElement): void;
|
||||
(e: 'changeToolbarDisplayType', type: ToolbarDisplayType): void;
|
||||
}
|
||||
|
||||
const faceIcon = TUIChatConfig.getTheme() === 'dark' ? faceIconDark : faceIconLight;
|
||||
const emits = defineEmits<IEmits>();
|
||||
const currentConversation = ref();
|
||||
const container = ref<InstanceType<typeof ToolbarItemContainer>>();
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: (conversation: IConversationModel) => {
|
||||
currentConversation.value = conversation;
|
||||
},
|
||||
});
|
||||
|
||||
const onDialogShow = (dialogRef: any) => {
|
||||
if (!isH5) {
|
||||
return;
|
||||
}
|
||||
emits('changeToolbarDisplayType', 'emojiPicker');
|
||||
emits('dialogShowInH5', dialogRef.value);
|
||||
};
|
||||
|
||||
const onDialogClose = (dialogRef: any) => {
|
||||
if (!isH5) {
|
||||
return;
|
||||
}
|
||||
emits('changeToolbarDisplayType', 'none');
|
||||
emits('dialogCloseInH5', dialogRef.value);
|
||||
};
|
||||
|
||||
const insertEmoji = (emojiObj) => {
|
||||
emits('insertEmoji', emojiObj);
|
||||
};
|
||||
const sendMessage = () => {
|
||||
emits('sendMessage');
|
||||
};
|
||||
const onClose = () => {
|
||||
container.value?.toggleDialogDisplay(false);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
closeEmojiPicker: onClose,
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
@@ -0,0 +1,25 @@
|
||||
.emoji-picker-h5 {
|
||||
width: 100%;
|
||||
|
||||
&-list {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-list::after {
|
||||
content: "";
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
background-color: #55C06A;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "../../../../../assets/styles/common";
|
||||
@import "./web";
|
||||
@import "./h5";
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
.emoji-picker {
|
||||
width: 405px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
margin: 2px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-item {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
|
||||
.emoji {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.emoji-big {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-item {
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
margin: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&-big {
|
||||
margin: 2px 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import Evaluate from './index.vue';
|
||||
export default Evaluate;
|
||||
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<ToolbarItemContainer
|
||||
ref="container"
|
||||
:iconFile="evaluateIcon"
|
||||
title="评价"
|
||||
:needBottomPopup="true"
|
||||
:iconWidth="isUniFrameWork ? '26px' : '20px'"
|
||||
:iconHeight="isUniFrameWork ? '26px' : '20px'"
|
||||
@onDialogShow="onDialogShow"
|
||||
@onDialogClose="onDialogClose"
|
||||
>
|
||||
<div :class="['evaluate', !isPC && 'evaluate-h5']">
|
||||
<div :class="['evaluate-header', !isPC && 'evaluate-h5-header']">
|
||||
<div
|
||||
:class="[
|
||||
'evaluate-header-content',
|
||||
!isPC && 'evaluate-h5-header-content',
|
||||
]"
|
||||
>
|
||||
{{ TUITranslateService.t("Evaluate.请对本次服务进行评价") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!isPC"
|
||||
:class="[
|
||||
'evaluate-header-close',
|
||||
!isPC && 'evaluate-h5-header-close',
|
||||
]"
|
||||
@click.stop="closeDialog"
|
||||
>
|
||||
{{ TUITranslateService.t("关闭") }}
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['evaluate-content', !isPC && 'evaluate-h5-content']">
|
||||
<ul
|
||||
:class="[
|
||||
'evaluate-content-list',
|
||||
!isPC && 'evaluate-h5-content-list',
|
||||
]"
|
||||
>
|
||||
<li
|
||||
v-for="(item, index) in starList"
|
||||
:key="index"
|
||||
:class="[
|
||||
'evaluate-content-list-item',
|
||||
!isPC && 'evaluate-h5-content-list-item',
|
||||
]"
|
||||
@click.stop="selectStar(index)"
|
||||
>
|
||||
<Icon
|
||||
v-if="index <= currentStarIndex"
|
||||
:file="starLightIcon"
|
||||
:width="isPC ? '20px' : '30px'"
|
||||
:height="isPC ? '20px' : '30px'"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
:file="starIcon"
|
||||
:width="isPC ? '20px' : '30px'"
|
||||
:height="isPC ? '20px' : '30px'"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<textarea
|
||||
v-model="comment"
|
||||
:class="[
|
||||
'evaluate-content-text',
|
||||
!isPC && 'evaluate-h5-content-text',
|
||||
]"
|
||||
/>
|
||||
<div
|
||||
:class="[
|
||||
'evaluate-content-button',
|
||||
!isPC && 'evaluate-h5-content-button',
|
||||
]"
|
||||
>
|
||||
<button
|
||||
:class="['btn', isEvaluateValid ? 'btn-valid' : 'btn-invalid']"
|
||||
@click="submitEvaluate"
|
||||
>
|
||||
{{ TUITranslateService.t("Evaluate.提交评价") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['evaluate-adv', !isPC && 'evaluate-h5-adv']">
|
||||
{{ TUITranslateService.t("Evaluate.服务评价工具") }}
|
||||
{{ "(" + TUITranslateService.t("Evaluate.使用") }}
|
||||
<a @click="openLink(Link.customMessage)">
|
||||
{{ TUITranslateService.t(`Evaluate.${Link.customMessage.label}`) }}
|
||||
</a>
|
||||
{{ TUITranslateService.t("Evaluate.搭建") + ")" }}
|
||||
</div>
|
||||
</div>
|
||||
</ToolbarItemContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TUIChatEngine, {
|
||||
TUITranslateService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
TUIChatService,
|
||||
SendMessageParams,
|
||||
SendMessageOptions,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { ref, computed } from '../../../../adapter-vue';
|
||||
import ToolbarItemContainer from '../toolbar-item-container/index.vue';
|
||||
import evaluateIconLight from '../../../../assets/icon/evalute-light.svg';
|
||||
import evaluateIconDark from '../../../../assets/icon/evalute-dark.svg';
|
||||
import Link from '../../../../utils/documentLink';
|
||||
import Icon from '../../../common/Icon.vue';
|
||||
import starIcon from '../../../../assets/icon/star.png';
|
||||
import starLightIcon from '../../../../assets/icon/star-light.png';
|
||||
import { CHAT_MSG_CUSTOM_TYPE } from '../../../../constant';
|
||||
import { isPC, isH5, isUniFrameWork } from '../../../../utils/env';
|
||||
import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
|
||||
import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
const evaluateIcon = TUIChatConfig.getTheme() === 'dark' ? evaluateIconDark : evaluateIconLight;
|
||||
const props = defineProps({
|
||||
starTotal: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['onDialogPopupShowOrHide']);
|
||||
|
||||
const container = ref();
|
||||
|
||||
const starList = ref<number>(props.starTotal);
|
||||
const currentStarIndex = ref<number>(-1);
|
||||
const comment = ref('');
|
||||
const currentConversation = ref<IConversationModel>();
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: (conversation: IConversationModel) => {
|
||||
currentConversation.value = conversation;
|
||||
},
|
||||
});
|
||||
|
||||
const isEvaluateValid = computed(() => comment.value.length || currentStarIndex.value >= 0);
|
||||
|
||||
const onDialogShow = () => {
|
||||
emits('onDialogPopupShowOrHide', true);
|
||||
};
|
||||
|
||||
const onDialogClose = () => {
|
||||
resetEvaluate();
|
||||
emits('onDialogPopupShowOrHide', false);
|
||||
};
|
||||
|
||||
const openLink = () => {
|
||||
if (isPC || isH5) {
|
||||
window.open(Link?.customMessage?.url);
|
||||
}
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
container?.value?.toggleDialogDisplay(false);
|
||||
};
|
||||
|
||||
const resetEvaluate = () => {
|
||||
currentStarIndex.value = -1;
|
||||
comment.value = '';
|
||||
};
|
||||
|
||||
const selectStar = (starIndex?: any) => {
|
||||
if (currentStarIndex.value === starIndex) {
|
||||
currentStarIndex.value = currentStarIndex.value - 1;
|
||||
} else {
|
||||
currentStarIndex.value = starIndex;
|
||||
}
|
||||
};
|
||||
|
||||
const submitEvaluate = () => {
|
||||
// The evaluate message must have at least one star or comment to be submitted.
|
||||
if (currentStarIndex.value < 0 && !comment.value.length) {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
data: JSON.stringify({
|
||||
businessID: CHAT_MSG_CUSTOM_TYPE.EVALUATE,
|
||||
version: 1,
|
||||
score: currentStarIndex.value + 1,
|
||||
comment: comment.value,
|
||||
}),
|
||||
description: '对本次的服务评价',
|
||||
extension: '对本次的服务评价',
|
||||
};
|
||||
const options = {
|
||||
to:
|
||||
currentConversation?.value?.groupProfile?.groupID
|
||||
|| currentConversation?.value?.userProfile?.userID,
|
||||
conversationType: currentConversation?.value?.type,
|
||||
payload,
|
||||
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
||||
};
|
||||
const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
|
||||
conversation: currentConversation.value,
|
||||
payload: options.payload,
|
||||
messageType: TUIChatEngine.TYPES.MSG_CUSTOM,
|
||||
};
|
||||
const sendMessageOptions: SendMessageOptions = {
|
||||
offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
|
||||
};
|
||||
TUIChatService.sendCustomMessage(options as SendMessageParams, sendMessageOptions);
|
||||
// close dialog after submit evaluate
|
||||
container?.value?.toggleDialogDisplay(false);
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss" src="./style/index.scss"></style>
|
||||
@@ -0,0 +1,57 @@
|
||||
.evaluate {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
&-header {
|
||||
&-content {
|
||||
font-weight: 500;
|
||||
color: #1c1c1c;
|
||||
}
|
||||
}
|
||||
|
||||
&-adv {
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
|
||||
a {
|
||||
color: #006eff;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
&-text {
|
||||
background: #f8f8f8;
|
||||
border: 1px solid #ececec;
|
||||
}
|
||||
|
||||
&-list {
|
||||
&-item {
|
||||
font-weight: 400;
|
||||
color: #50545c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-H5 {
|
||||
&-main {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
.evaluate-main-content {
|
||||
background: #fff;
|
||||
|
||||
p {
|
||||
a {
|
||||
color: #3370ff;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
font-family: PingFangSC-Regular;
|
||||
font-weight: 400;
|
||||
color: #3370ff;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
.evaluate-h5 {
|
||||
position: static;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
border-radius: 0;
|
||||
background: #fff;
|
||||
padding: 23px !important;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&-content {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&-close {
|
||||
font-size: 18px;
|
||||
line-height: 27px;
|
||||
font-weight: 400;
|
||||
color: #3370ff;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
order: 1;
|
||||
|
||||
&-list {
|
||||
&-item {
|
||||
width: 40px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
cursor: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 14px 0;
|
||||
font-size: 18px;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-adv {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "./color";
|
||||
@import "./web";
|
||||
@import "./h5";
|
||||
@import "../../../../../assets/styles/common";
|
||||
@@ -0,0 +1,93 @@
|
||||
.evaluate {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
width: 315px;
|
||||
top: -255px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 8px;
|
||||
background: url("https://web.sdk.qcloud.com/im/assets/images/login-background.png") no-repeat;
|
||||
background-color: #fff;
|
||||
background-size: cover;
|
||||
background-position-x: 128px;
|
||||
background-position-y: 77px;
|
||||
user-select: none;
|
||||
|
||||
&-header {
|
||||
&-content {
|
||||
font-style: normal;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
|
||||
&-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
&-item {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
padding: 4px 0;
|
||||
font-size: 12px;
|
||||
padding-right: 15px;
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-text {
|
||||
box-sizing: border-box;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
margin: 12px 0;
|
||||
padding: 12px;
|
||||
border-radius: 2px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
&-button {
|
||||
.btn {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
padding: 2px 46px;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-valid {
|
||||
background-color: #3370ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-invalid{
|
||||
background-color: rgb(160, 207, 255);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-adv {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import ImageUpload from './index.vue';
|
||||
export default ImageUpload;
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<ToolbarItemContainer
|
||||
:iconFile="fileIcon"
|
||||
title="文件"
|
||||
:iconWidth="isUniFrameWork ? '32px' : '20px'"
|
||||
:iconHeight="isUniFrameWork ? '25px' : '18px'"
|
||||
:needDialog="false"
|
||||
@onIconClick="onIconClick"
|
||||
>
|
||||
<div :class="['file-upload', !isPC && 'file-upload-h5']">
|
||||
<input
|
||||
ref="inputRef"
|
||||
title="文件"
|
||||
type="file"
|
||||
data-type="file"
|
||||
accept="*"
|
||||
@change="sendFileMessage"
|
||||
>
|
||||
</div>
|
||||
</ToolbarItemContainer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TUIChatEngine, {
|
||||
TUIChatService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
SendMessageParams,
|
||||
SendMessageOptions,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { ref } from '../../../../adapter-vue';
|
||||
import ToolbarItemContainer from '../toolbar-item-container/index.vue';
|
||||
import fileIconLight from '../../../../assets/icon/file-light.svg';
|
||||
import fileIconDark from '../../../../assets/icon/file-dark.svg';
|
||||
import { isPC, isUniFrameWork } from '../../../../utils/env';
|
||||
import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
|
||||
import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
const fileIcon = TUIChatConfig.getTheme() === 'dark' ? fileIconDark : fileIconLight;
|
||||
const inputRef = ref();
|
||||
const currentConversation = ref<IConversationModel>();
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: (conversation: IConversationModel) => {
|
||||
currentConversation.value = conversation;
|
||||
},
|
||||
});
|
||||
|
||||
const onIconClick = () => {
|
||||
if (isUniFrameWork) {
|
||||
return;
|
||||
} else {
|
||||
inputRef?.value?.click && inputRef?.value?.click();
|
||||
}
|
||||
};
|
||||
|
||||
const sendFileMessage = (e: any) => {
|
||||
if (e?.target?.files?.length <= 0) {
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
to:
|
||||
currentConversation?.value?.groupProfile?.groupID
|
||||
|| currentConversation?.value?.userProfile?.userID,
|
||||
conversationType: currentConversation?.value?.type,
|
||||
payload: {
|
||||
file: e?.target,
|
||||
},
|
||||
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
||||
} as SendMessageParams;
|
||||
const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
|
||||
conversation: currentConversation.value,
|
||||
payload: options.payload,
|
||||
messageType: TUIChatEngine.TYPES.MSG_FILE,
|
||||
};
|
||||
const sendMessageOptions: SendMessageOptions = {
|
||||
offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
|
||||
};
|
||||
TUIChatService.sendFileMessage(options, sendMessageOptions);
|
||||
e.target.value = '';
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../assets/styles/common";
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
import ImageUpload from './index.vue';
|
||||
export default ImageUpload;
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<ToolbarItemContainer
|
||||
:iconFile="imageToolbarForShow.icon"
|
||||
:title="imageToolbarForShow.title"
|
||||
:iconWidth="isUniFrameWork ? '32px' : '20px'"
|
||||
:iconHeight="isUniFrameWork ? '25px' : '18px'"
|
||||
:needDialog="false"
|
||||
@onIconClick="onIconClick"
|
||||
>
|
||||
<div
|
||||
v-if="!isUniFrameWork"
|
||||
:class="['image-upload', !isPC && 'image-upload-h5']"
|
||||
>
|
||||
<input
|
||||
ref="inputRef"
|
||||
title="图片"
|
||||
type="file"
|
||||
data-type="image"
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/bmp,image/webp"
|
||||
@change="sendImageInWeb"
|
||||
>
|
||||
</div>
|
||||
</ToolbarItemContainer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TUIChatEngine, {
|
||||
TUIChatService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
SendMessageParams,
|
||||
SendMessageOptions,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { ref, computed } from '../../../../adapter-vue';
|
||||
import { isPC, isWeChat, isUniFrameWork } from '../../../../utils/env';
|
||||
import ToolbarItemContainer from '../toolbar-item-container/index.vue';
|
||||
import imageIconLight from '../../../../assets/icon/image-light.svg';
|
||||
import imageIconDark from '../../../../assets/icon/image-dark.svg';
|
||||
import imageUniIcon from '../../../../assets/icon/image-uni.png';
|
||||
import cameraUniIcon from '../../../../assets/icon/camera-uni.png';
|
||||
import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
|
||||
import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
const props = defineProps({
|
||||
// Image source: only valid for uni-app version, web version only supports selecting images from the album.
|
||||
// album: Select from album
|
||||
// camera: Take a photo using the camera
|
||||
imageSourceType: {
|
||||
type: String,
|
||||
default: 'album',
|
||||
},
|
||||
});
|
||||
|
||||
const inputRef = ref();
|
||||
const currentConversation = ref<IConversationModel>();
|
||||
const theme = TUIChatConfig.getTheme();
|
||||
const IMAGE_TOOLBAR_SHOW_MAP = {
|
||||
web_album: {
|
||||
icon: theme === 'dark' ? imageIconDark : imageIconLight,
|
||||
title: '图片',
|
||||
},
|
||||
uni_album: {
|
||||
icon: imageUniIcon,
|
||||
title: '图片',
|
||||
},
|
||||
uni_camera: {
|
||||
icon: cameraUniIcon,
|
||||
title: '拍照',
|
||||
},
|
||||
};
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: (conversation: IConversationModel) => {
|
||||
currentConversation.value = conversation;
|
||||
},
|
||||
});
|
||||
|
||||
const imageToolbarForShow = computed((): { icon: string; title: string } => {
|
||||
if (isUniFrameWork) {
|
||||
return props.imageSourceType === 'camera'
|
||||
? IMAGE_TOOLBAR_SHOW_MAP['uni_camera']
|
||||
: IMAGE_TOOLBAR_SHOW_MAP['uni_album'];
|
||||
} else {
|
||||
return IMAGE_TOOLBAR_SHOW_MAP['web_album'];
|
||||
}
|
||||
});
|
||||
|
||||
const onIconClick = () => {
|
||||
// uni-app send image
|
||||
if (isUniFrameWork) {
|
||||
if (isWeChat && TUIGlobal?.chooseMedia) {
|
||||
TUIGlobal?.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sizeType: ['original', 'compressed'],
|
||||
sourceType: [props.imageSourceType], // Use camera or select from album.
|
||||
success: function (res: any) {
|
||||
sendImageMessage(res);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// uni-app H5/App send image
|
||||
TUIGlobal?.chooseImage({
|
||||
count: 1,
|
||||
sourceType: [props.imageSourceType], // Use camera or select from album.
|
||||
success: function (res) {
|
||||
sendImageMessage(res);
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (inputRef.value?.click) {
|
||||
inputRef.value.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sendImageInWeb = (e: any) => {
|
||||
if (e?.target?.files?.length <= 0) {
|
||||
return;
|
||||
}
|
||||
sendImageMessage(e?.target);
|
||||
e.target.value = '';
|
||||
};
|
||||
|
||||
const sendImageMessage = (files: any) => {
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
to:
|
||||
currentConversation?.value?.groupProfile?.groupID
|
||||
|| currentConversation?.value?.userProfile?.userID,
|
||||
conversationType: currentConversation?.value?.type,
|
||||
payload: {
|
||||
file: files,
|
||||
},
|
||||
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
||||
} as SendMessageParams;
|
||||
const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
|
||||
conversation: currentConversation.value,
|
||||
payload: options.payload,
|
||||
messageType: TUIChatEngine.TYPES.MSG_IMAGE,
|
||||
};
|
||||
const sendMessageOptions: SendMessageOptions = {
|
||||
offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
|
||||
};
|
||||
TUIChatService.sendImageMessage(options, sendMessageOptions);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../assets/styles/common";
|
||||
</style>
|
||||
2
TUIKit/components/TUIChat/message-input-toolbar/index.ts
Normal file
2
TUIKit/components/TUIChat/message-input-toolbar/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import MessageInputToolbar from './index.vue';
|
||||
export default MessageInputToolbar;
|
||||
316
TUIKit/components/TUIChat/message-input-toolbar/index.vue
Normal file
316
TUIKit/components/TUIChat/message-input-toolbar/index.vue
Normal file
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'message-input-toolbar',
|
||||
'message-input-toolbar-h5',
|
||||
'message-input-toolbar-uni',
|
||||
]"
|
||||
>
|
||||
<div v-if="props.displayType === 'emojiPicker'">
|
||||
<EmojiPickerDialog />
|
||||
</div>
|
||||
<div v-else>
|
||||
<swiper
|
||||
:class="['message-input-toolbar-swiper']"
|
||||
:indicator-dots="isSwiperIndicatorDotsEnable"
|
||||
:autoplay="false"
|
||||
:circular="false"
|
||||
>
|
||||
<swiper-item
|
||||
:class="[
|
||||
'message-input-toolbar-list',
|
||||
'message-input-toolbar-h5-list',
|
||||
'message-input-toolbar-uni-list',
|
||||
]"
|
||||
>
|
||||
<ImageUpload
|
||||
v-if="featureConfig.InputImage"
|
||||
imageSourceType="camera"
|
||||
/>
|
||||
<ImageUpload
|
||||
v-if="featureConfig.InputImage"
|
||||
imageSourceType="album"
|
||||
/>
|
||||
<VideoUpload
|
||||
v-if="featureConfig.InputVideo"
|
||||
videoSourceType="album"
|
||||
/>
|
||||
<VideoUpload
|
||||
v-if="featureConfig.InputVideo"
|
||||
videoSourceType="camera"
|
||||
/>
|
||||
<template v-if="currentExtensionList.length > 0">
|
||||
<div
|
||||
v-for="(extension, index) in currentExtensionList.slice(0, slicePos)"
|
||||
:key="index"
|
||||
>
|
||||
<ToolbarItemContainer
|
||||
v-if="extension"
|
||||
:iconFile="genExtensionIcon(extension)"
|
||||
:title="genExtensionText(extension)"
|
||||
iconWidth="25px"
|
||||
iconHeight="25px"
|
||||
:needDialog="false"
|
||||
@onIconClick="onExtensionClick(extension)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="neededCountFirstPage === 1">
|
||||
<Evaluate
|
||||
v-if="featureConfig.InputEvaluation"
|
||||
@onDialogPopupShowOrHide="handleSwiperDotShow"
|
||||
/>
|
||||
<Words
|
||||
v-else-if="featureConfig.InputQuickReplies"
|
||||
@onDialogPopupShowOrHide="handleSwiperDotShow"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="neededCountFirstPage > 1">
|
||||
<Evaluate
|
||||
v-if="featureConfig.InputEvaluation"
|
||||
@onDialogPopupShowOrHide="handleSwiperDotShow"
|
||||
/>
|
||||
<Words
|
||||
v-if="featureConfig.InputQuickReplies"
|
||||
@onDialogPopupShowOrHide="handleSwiperDotShow"
|
||||
/>
|
||||
</template>
|
||||
</swiper-item>
|
||||
<swiper-item
|
||||
v-if="neededCountFirstPage <= 1"
|
||||
:class="[
|
||||
'message-input-toolbar-list',
|
||||
'message-input-toolbar-h5-list',
|
||||
'message-input-toolbar-uni-list',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-for="(extension, index) in currentExtensionList.slice(slicePos)"
|
||||
:key="index"
|
||||
>
|
||||
<ToolbarItemContainer
|
||||
v-if="extension"
|
||||
:iconFile="genExtensionIcon(extension)"
|
||||
:title="genExtensionText(extension)"
|
||||
iconWidth="25px"
|
||||
iconHeight="25px"
|
||||
:needDialog="false"
|
||||
@onIconClick="onExtensionClick(extension)"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="neededCountFirstPage === 1">
|
||||
<Words
|
||||
v-if="featureConfig.InputQuickReplies"
|
||||
@onDialogPopupShowOrHide="handleSwiperDotShow"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Evaluate
|
||||
v-if="featureConfig.InputEvaluation"
|
||||
@onDialogPopupShowOrHide="handleSwiperDotShow"
|
||||
/>
|
||||
<Words
|
||||
v-if="featureConfig.InputQuickReplies"
|
||||
@onDialogPopupShowOrHide="handleSwiperDotShow"
|
||||
/>
|
||||
</template>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</div>
|
||||
<UserSelector
|
||||
ref="userSelectorRef"
|
||||
:type="selectorShowType"
|
||||
:currentConversation="currentConversation"
|
||||
:isGroup="isGroup"
|
||||
@submit="onUserSelectorSubmit"
|
||||
@cancel="onUserSelectorCancel"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onUnmounted, onMounted } from '../../../adapter-vue';
|
||||
import TUIChatEngine, {
|
||||
IConversationModel,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUIReportService,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import TUICore, { ExtensionInfo, TUIConstants } from '@tencentcloud/tui-core';
|
||||
import ImageUpload from './image-upload/index.vue';
|
||||
import VideoUpload from './video-upload/index.vue';
|
||||
import Evaluate from './evaluate/index.vue';
|
||||
import Words from './words/index.vue';
|
||||
import ToolbarItemContainer from './toolbar-item-container/index.vue';
|
||||
import EmojiPickerDialog from './emoji-picker/emoji-picker-dialog.vue';
|
||||
import UserSelector from './user-selector/index.vue';
|
||||
import TUIChatConfig from '../config';
|
||||
import { enableSampleTaskStatus } from '../../../utils/enableSampleTaskStatus';
|
||||
import { ToolbarDisplayType } from '../../../interface';
|
||||
import OfflinePushInfoManager, { PUSH_SCENE } from '../offlinePushInfoManager/index';
|
||||
|
||||
interface IProps {
|
||||
displayType: ToolbarDisplayType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
});
|
||||
|
||||
const currentConversation = ref<IConversationModel>();
|
||||
const isGroup = ref<boolean>(false);
|
||||
const selectorShowType = ref<string>('');
|
||||
const userSelectorRef = ref();
|
||||
const currentUserSelectorExtension = ref<ExtensionInfo | null>();
|
||||
const currentExtensionList = ref<ExtensionInfo[]>([]);
|
||||
const isSwiperIndicatorDotsEnable = ref<boolean>(false);
|
||||
const featureConfig = TUIChatConfig.getFeatureConfig();
|
||||
const neededCountFirstPage = ref<number>(8);
|
||||
const slicePos = ref<number>(0);
|
||||
|
||||
const computeToolbarPaging = () => {
|
||||
if (featureConfig.InputImage && featureConfig.InputVideo) {
|
||||
neededCountFirstPage.value -= 4;
|
||||
} else if (featureConfig.InputImage || featureConfig.InputVideo) {
|
||||
neededCountFirstPage.value -= 2;
|
||||
}
|
||||
|
||||
slicePos.value = neededCountFirstPage.value;
|
||||
neededCountFirstPage.value -= currentExtensionList.value.length;
|
||||
|
||||
if (neededCountFirstPage.value === 1) {
|
||||
isSwiperIndicatorDotsEnable.value = (featureConfig.InputEvaluation && featureConfig.InputQuickReplies);
|
||||
} else if (neededCountFirstPage.value < 1) {
|
||||
isSwiperIndicatorDotsEnable.value = featureConfig.InputEvaluation || featureConfig.InputQuickReplies;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
activeConversation: onActiveConversationUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CUSTOM, {
|
||||
activeConversation: onActiveConversationUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
const onActiveConversationUpdate = (conversationID: string) => {
|
||||
if (!conversationID) {
|
||||
return;
|
||||
}
|
||||
if (conversationID !== currentConversation.value?.conversationID) {
|
||||
getExtensionList();
|
||||
computeToolbarPaging();
|
||||
currentConversation.value = TUIStore.getData(StoreName.CONV, 'currentConversation');
|
||||
isGroup.value = conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP);
|
||||
}
|
||||
};
|
||||
|
||||
const getExtensionList = () => {
|
||||
const chatType = TUIChatConfig.getChatType();
|
||||
const params: Record<string, boolean | string> = { chatType };
|
||||
// Backward compatibility: When callkit does not have chatType judgment, use filterVoice and filterVideo to filter
|
||||
if (chatType === TUIConstants.TUIChat.TYPE.CUSTOMER_SERVICE) {
|
||||
params.filterVoice = true;
|
||||
params.filterVideo = true;
|
||||
enableSampleTaskStatus('customerService');
|
||||
}
|
||||
// uni-app build ios app has null in last index need to filter
|
||||
currentExtensionList.value = [
|
||||
...TUICore.getExtensionList(TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID, params),
|
||||
].filter((extension: ExtensionInfo) => {
|
||||
if (extension?.data?.name === 'search') {
|
||||
return featureConfig.MessageSearch;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
reportExtension(currentExtensionList.value);
|
||||
};
|
||||
|
||||
function reportExtension(extensionList:ExtensionInfo[]){
|
||||
extensionList.forEach((extension: ExtensionInfo)=>{
|
||||
const _name = extension?.data?.name;
|
||||
if(_name === 'voiceCall'){
|
||||
TUIReportService.reportFeature(203, 'voice-call');
|
||||
} else if (_name === 'videoCall') {
|
||||
TUIReportService.reportFeature(203, 'video-call');
|
||||
} else if(_name === 'quickRoom'){
|
||||
TUIReportService.reportFeature(204);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// handle extensions onclick
|
||||
const onExtensionClick = (extension: ExtensionInfo) => {
|
||||
// uniapp vue2 build wx lose listener proto
|
||||
const extensionModel = currentExtensionList.value.find(
|
||||
targetExtension => targetExtension?.data?.name === extension?.data?.name,
|
||||
);
|
||||
switch (extensionModel?.data?.name) {
|
||||
case 'voiceCall':
|
||||
onCallExtensionClicked(extensionModel, 1);
|
||||
break;
|
||||
case 'videoCall':
|
||||
onCallExtensionClicked(extensionModel, 2);
|
||||
break;
|
||||
case 'search':
|
||||
extensionModel?.listener?.onClicked?.();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onCallExtensionClicked = (extension: ExtensionInfo, callType: number) => {
|
||||
selectorShowType.value = extension?.data?.name;
|
||||
if (currentConversation?.value?.type === TUIChatEngine.TYPES.CONV_C2C) {
|
||||
extension?.listener?.onClicked?.({
|
||||
userIDList: [currentConversation?.value?.conversationID?.slice(3)],
|
||||
type: callType,
|
||||
callParams: {
|
||||
offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(PUSH_SCENE.CALL),
|
||||
},
|
||||
});
|
||||
} else if (isGroup.value) {
|
||||
currentUserSelectorExtension.value = extension;
|
||||
userSelectorRef?.value?.toggleShow && userSelectorRef.value.toggleShow(true);
|
||||
}
|
||||
};
|
||||
|
||||
const genExtensionIcon = (extension: any) => {
|
||||
return extension?.icon;
|
||||
};
|
||||
const genExtensionText = (extension: any) => {
|
||||
return extension?.text;
|
||||
};
|
||||
|
||||
const onUserSelectorSubmit = (selectedInfo: any) => {
|
||||
currentUserSelectorExtension.value?.listener?.onClicked?.({
|
||||
...selectedInfo,
|
||||
callParams: {
|
||||
offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(PUSH_SCENE.CALL),
|
||||
},
|
||||
});
|
||||
currentUserSelectorExtension.value = null;
|
||||
};
|
||||
|
||||
const onUserSelectorCancel = () => {
|
||||
currentUserSelectorExtension.value = null;
|
||||
};
|
||||
|
||||
const handleSwiperDotShow = (showStatus: boolean) => {
|
||||
isSwiperIndicatorDotsEnable.value = (neededCountFirstPage.value <= 1 && !showStatus);
|
||||
};
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import '../../../assets/styles/common';
|
||||
@import './style/uni';
|
||||
</style>
|
||||
111
TUIKit/components/TUIChat/message-input-toolbar/style/uni.scss
Normal file
111
TUIKit/components/TUIChat/message-input-toolbar/style/uni.scss
Normal file
@@ -0,0 +1,111 @@
|
||||
/* stylelint-disable */
|
||||
.message-input-toolbar {
|
||||
border-top: 1px solid #f4f5f9;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
&-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.extension-list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
|
||||
&-item {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 12px 10px 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-input-toolbar-h5 {
|
||||
padding: 5px 10px;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.message-input-toolbar-uni {
|
||||
background-color: #ebf0f6;
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
|
||||
&-list {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 25%);
|
||||
grid-template-rows: repeat(2, 100px);
|
||||
}
|
||||
}
|
||||
|
||||
// uniapp swiper style
|
||||
wx-swiper .wx-swiper-wrapper,
|
||||
wx-swiper .wx-swiper-slides,
|
||||
wx-swiper .wx-swiper-slide-frame,
|
||||
.message-input-toolbar-list {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.message-input-toolbar {
|
||||
|
||||
.bottom-popup,
|
||||
.bottom-popup-h5,
|
||||
.bottom-popup-uni {
|
||||
position: sticky !important;
|
||||
}
|
||||
}
|
||||
|
||||
.message-input-toolbar-swiper {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
|
||||
::v-deep .uni-swiper-wrapper,
|
||||
wx-swiper .wx-swiper-wrapper {
|
||||
overflow: visible !important;
|
||||
|
||||
.uni-swiper-slides,
|
||||
.wx-swiper-slides,
|
||||
wx-swiper .wx-swiper-slides {
|
||||
overflow: visible !important;
|
||||
|
||||
.uni-swiper-slide-frame,
|
||||
.wx-swiper-slide-frame,
|
||||
wx-swiper .wx-swiper-slide-frame {
|
||||
overflow: visible !important;
|
||||
|
||||
.message-input-toolbar-list {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.toolbar-item-container-uni {
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
.toolbar-item-container-dialog {
|
||||
position: absolute !important;
|
||||
background: transparent;
|
||||
left: -10px;
|
||||
bottom: -5px;
|
||||
|
||||
.bottom-popup-uni {
|
||||
position: sticky !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div
|
||||
ref="toolbarItemRef"
|
||||
:class="[
|
||||
'toolbar-item-container',
|
||||
!isPC && 'toolbar-item-container-h5',
|
||||
isUniFrameWork && 'toolbar-item-container-uni',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'toolbar-item-container-icon',
|
||||
isUniFrameWork && 'toolbar-item-container-uni-icon',
|
||||
]"
|
||||
@click="toggleToolbarItem"
|
||||
>
|
||||
<Icon
|
||||
:file="props.iconFile"
|
||||
class="icon"
|
||||
:width="props.iconWidth"
|
||||
:height="props.iconHeight"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="isUniFrameWork"
|
||||
:class="['toolbar-item-container-uni-title']"
|
||||
>
|
||||
{{ props.title }}
|
||||
</div>
|
||||
<div
|
||||
v-show="showDialog"
|
||||
ref="dialogRef"
|
||||
:class="[
|
||||
'toolbar-item-container-dialog',
|
||||
isDark && 'toolbar-item-container-dialog-dark',
|
||||
!isPC && 'toolbar-item-container-h5-dialog',
|
||||
isUniFrameWork && 'toolbar-item-container-uni-dialog',
|
||||
]"
|
||||
>
|
||||
<BottomPopup
|
||||
v-if="props.needBottomPopup && !isPC"
|
||||
class="toolbar-bottom-popup"
|
||||
:show="showDialog"
|
||||
@touchmove.stop.prevent
|
||||
@onClose="onPopupClose"
|
||||
>
|
||||
<slot />
|
||||
</BottomPopup>
|
||||
<slot v-else />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from '../../../../adapter-vue';
|
||||
import { outsideClick } from '@tencentcloud/universal-api';
|
||||
import Icon from '../../../common/Icon.vue';
|
||||
import BottomPopup from '../../../common/BottomPopup/index.vue';
|
||||
import { isPC, isUniFrameWork } from '../../../../utils/env';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
const props = defineProps({
|
||||
iconFile: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
needDialog: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
iconWidth: {
|
||||
type: String,
|
||||
default: '20px',
|
||||
},
|
||||
iconHeight: {
|
||||
type: String,
|
||||
default: '20px',
|
||||
},
|
||||
// Whether to display the bottom popup dialog on mobile devices
|
||||
// Invalid on PC
|
||||
needBottomPopup: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['onIconClick', 'onDialogClose', 'onDialogShow']);
|
||||
|
||||
const isDark = ref(TUIChatConfig.getTheme() === 'dark');
|
||||
const showDialog = ref(false);
|
||||
const toolbarItemRef = ref();
|
||||
const dialogRef = ref();
|
||||
|
||||
const toggleToolbarItem = () => {
|
||||
emits('onIconClick', dialogRef);
|
||||
if (isPC) {
|
||||
outsideClick.listen({
|
||||
domRefs: toolbarItemRef.value,
|
||||
handler: closeToolbarItem,
|
||||
});
|
||||
}
|
||||
if (!props.needDialog) {
|
||||
return;
|
||||
}
|
||||
toggleDialogDisplay(!showDialog.value);
|
||||
};
|
||||
|
||||
const closeToolbarItem = () => {
|
||||
showDialog.value = false;
|
||||
emits('onDialogClose', dialogRef);
|
||||
};
|
||||
|
||||
const toggleDialogDisplay = (showStatus: boolean) => {
|
||||
if (showDialog.value === showStatus) {
|
||||
return;
|
||||
}
|
||||
showDialog.value = showStatus;
|
||||
switch (showStatus) {
|
||||
case true:
|
||||
emits('onDialogShow', dialogRef);
|
||||
break;
|
||||
case false:
|
||||
emits('onDialogClose', dialogRef);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopupClose = () => {
|
||||
showDialog.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
toggleDialogDisplay,
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
@@ -0,0 +1,6 @@
|
||||
.toolbar-item-container {
|
||||
&-dialog {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.toolbar-item-container-h5 {
|
||||
&-dialog {
|
||||
position: static !important;
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@import "../../../../../assets/styles/common";
|
||||
@import "./color";
|
||||
@import "./web";
|
||||
@import "./h5";
|
||||
@import "./uni";
|
||||
@@ -0,0 +1,36 @@
|
||||
.toolbar-item-container-uni {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: static;
|
||||
|
||||
&-icon {
|
||||
background: #fff;
|
||||
border-radius: 15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
color: #8F959D;
|
||||
}
|
||||
|
||||
&-dialog{
|
||||
position: absolute !important;
|
||||
background: transparent;
|
||||
left: -10px;
|
||||
bottom: -5px;
|
||||
|
||||
.toolbar-bottom-popup{
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.toolbar-item-container {
|
||||
position: relative;
|
||||
|
||||
&-icon {
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
&-dialog {
|
||||
z-index: 5;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 4px -3px rgba(32, 77, 141, 0.03), 0 6px 10px 1px rgba(32, 77, 141, 0.06), 0 3px 14px 2px rgba(32, 77, 141, 0.05);
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
bottom: 35px;
|
||||
}
|
||||
|
||||
&-dialog-dark {
|
||||
background: #22262E;
|
||||
box-shadow: 0 8px 40px 0 rgba(23, 25, 31, 0.6), 0 4px 12px 0 rgba(23, 25, 31, 0.8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import UserSelector from './index.vue';
|
||||
export default UserSelector;
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:show="show"
|
||||
:isH5="!isPC"
|
||||
:isHeaderShow="false"
|
||||
:isFooterShow="false"
|
||||
:background="false"
|
||||
@update:show="toggleShow"
|
||||
>
|
||||
<Transfer
|
||||
:isSearch="true"
|
||||
:title="title"
|
||||
:list="searchMemberList"
|
||||
:isH5="!isPC"
|
||||
:isRadio="false"
|
||||
@search="search"
|
||||
@submit="submit"
|
||||
@cancel="cancel"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
TUIGroupService,
|
||||
TUIUserService,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { ref, computed, watch } from '../../../../adapter-vue';
|
||||
import Dialog from '../../../common/Dialog/index.vue';
|
||||
import Transfer from '../../../common/Transfer/index.vue';
|
||||
import { isPC } from '../../../../utils/env';
|
||||
|
||||
const props = defineProps({
|
||||
// type: voiceCall/groupCall/...
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currentConversation: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
isGroup: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['submit', 'cancel']);
|
||||
const show = ref<boolean>(false);
|
||||
const groupID = ref<string>('');
|
||||
const memberList = ref<any[]>([]);
|
||||
const searchMemberList = ref<any[]>([]);
|
||||
const selfUserID = ref<string>('');
|
||||
const titleMap: any = {
|
||||
voiceCall: '发起群语音',
|
||||
videoCall: '发起群视频',
|
||||
};
|
||||
const title = computed(() => {
|
||||
return titleMap[props.type] ? titleMap[props.type] : '';
|
||||
});
|
||||
|
||||
TUIUserService.getUserProfile().then((res: any) => {
|
||||
if (res?.data?.userID) {
|
||||
selfUserID.value = res.data.userID;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [props?.currentConversation?.conversationID, show.value],
|
||||
(newVal: any, oldVal: any) => {
|
||||
if (newVal && newVal !== oldVal) {
|
||||
if (props.isGroup && show.value) {
|
||||
groupID.value = props.currentConversation.groupProfile.groupID;
|
||||
TUIGroupService.getGroupMemberList({
|
||||
groupID: groupID.value,
|
||||
}).then((res: any) => {
|
||||
memberList.value = res?.data?.memberList?.filter(
|
||||
(user: any) => user?.userID !== selfUserID.value,
|
||||
);
|
||||
searchMemberList.value = memberList.value;
|
||||
});
|
||||
} else {
|
||||
groupID.value = '';
|
||||
memberList.value = [];
|
||||
searchMemberList.value = memberList.value;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const search = (searchInfo: string) => {
|
||||
const results = memberList.value?.filter(
|
||||
(member: any) => member?.userID === searchInfo,
|
||||
);
|
||||
searchMemberList.value = results?.length ? results : memberList.value;
|
||||
};
|
||||
|
||||
const submit = (selectedMemberList: string[]) => {
|
||||
const userIDList: string[] = [];
|
||||
selectedMemberList?.forEach((user: any) => {
|
||||
user?.userID && userIDList.push(user.userID);
|
||||
});
|
||||
if (props.type === 'voiceCall') {
|
||||
emits('submit', { userIDList, groupID: groupID.value, type: 1 });
|
||||
} else if (props.type === 'videoCall') {
|
||||
emits('submit', { userIDList, groupID: groupID.value, type: 2 });
|
||||
}
|
||||
searchMemberList.value = memberList.value;
|
||||
toggleShow(false);
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
searchMemberList.value = memberList.value;
|
||||
emits('cancel');
|
||||
toggleShow(false);
|
||||
};
|
||||
|
||||
const toggleShow = (showStatus: boolean) => {
|
||||
show.value = showStatus;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
toggleShow,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,2 @@
|
||||
import ImageUpload from './index.vue';
|
||||
export default ImageUpload;
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<ToolbarItemContainer
|
||||
:iconFile="handleIcon()"
|
||||
:title="handleTitle()"
|
||||
:needDialog="false"
|
||||
:iconWidth="isUniFrameWork ? '32px' : '20px'"
|
||||
:iconHeight="isUniFrameWork
|
||||
? props.videoSourceType === 'album'
|
||||
? '20px'
|
||||
: '25px'
|
||||
: '18px'
|
||||
"
|
||||
@onIconClick="onIconClick"
|
||||
>
|
||||
<div :class="['video-upload', !isPC && 'video-upload-h5']">
|
||||
<input
|
||||
ref="inputRef"
|
||||
title="视频"
|
||||
type="file"
|
||||
data-type="video"
|
||||
accept="video/*"
|
||||
@change="sendVideoInWeb"
|
||||
>
|
||||
</div>
|
||||
</ToolbarItemContainer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TUIChatEngine, {
|
||||
TUIChatService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
SendMessageParams,
|
||||
SendMessageOptions,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { ref } from '../../../../adapter-vue';
|
||||
import { isPC, isWeChat, isUniFrameWork } from '../../../../utils/env';
|
||||
import ToolbarItemContainer from '../toolbar-item-container/index.vue';
|
||||
import videoIconLight from '../../../../assets/icon/video-light.svg';
|
||||
import videoIconDark from '../../../../assets/icon/video-dark.svg';
|
||||
import videoUniIcon from '../../../../assets/icon/video-uni.png';
|
||||
import cameraUniIcon from '../../../../assets/icon/camera-uni.png';
|
||||
import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
|
||||
import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
const props = defineProps({
|
||||
// Video source, only valid for uni-app version, web version only supports selecting videos from files
|
||||
// album: Select from files
|
||||
// camera: Take a video using the camera
|
||||
videoSourceType: {
|
||||
type: String,
|
||||
default: 'album',
|
||||
},
|
||||
});
|
||||
|
||||
const inputRef = ref();
|
||||
const currentConversation = ref<IConversationModel>();
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: (conversation: IConversationModel) => {
|
||||
currentConversation.value = conversation;
|
||||
},
|
||||
});
|
||||
|
||||
const handleIcon = (): string => {
|
||||
if (isUniFrameWork) {
|
||||
switch (props.videoSourceType) {
|
||||
case 'album':
|
||||
return videoUniIcon;
|
||||
case 'camera':
|
||||
return cameraUniIcon;
|
||||
default:
|
||||
return videoUniIcon;
|
||||
}
|
||||
} else {
|
||||
const videoIcon = TUIChatConfig.getTheme() === 'dark' ? videoIconDark : videoIconLight;
|
||||
return videoIcon;
|
||||
}
|
||||
};
|
||||
|
||||
const handleTitle = (): string => {
|
||||
if (isUniFrameWork && props.videoSourceType === 'camera') {
|
||||
return '录制';
|
||||
} else {
|
||||
return '视频';
|
||||
}
|
||||
};
|
||||
|
||||
const onIconClick = () => {
|
||||
// uni-app send video
|
||||
if (isUniFrameWork) {
|
||||
if (isWeChat && TUIGlobal?.chooseMedia) {
|
||||
TUIGlobal?.chooseMedia({
|
||||
mediaType: ['video'],
|
||||
count: 1,
|
||||
sourceType: [props.videoSourceType],
|
||||
maxDuration: 60,
|
||||
success: function (res: any) {
|
||||
sendVideoMessage(res);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
TUIGlobal?.chooseVideo({
|
||||
count: 1,
|
||||
sourceType: [props.videoSourceType],
|
||||
compressed: false,
|
||||
success: function (res: any) {
|
||||
sendVideoMessage(res);
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
inputRef?.value?.click && inputRef?.value?.click();
|
||||
}
|
||||
};
|
||||
|
||||
const sendVideoInWeb = (e: any) => {
|
||||
if (e?.target?.files?.length <= 0) {
|
||||
return;
|
||||
}
|
||||
sendVideoMessage(e?.target);
|
||||
e.target.value = '';
|
||||
};
|
||||
|
||||
const sendVideoMessage = (file: any) => {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
to:
|
||||
currentConversation?.value?.groupProfile?.groupID
|
||||
|| currentConversation?.value?.userProfile?.userID,
|
||||
conversationType: currentConversation?.value?.type,
|
||||
payload: {
|
||||
file,
|
||||
},
|
||||
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
||||
} as SendMessageParams;
|
||||
const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
|
||||
conversation: currentConversation.value,
|
||||
payload: options.payload,
|
||||
messageType: TUIChatEngine.TYPES.MSG_VIDEO,
|
||||
};
|
||||
const sendMessageOptions: SendMessageOptions = {
|
||||
offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
|
||||
};
|
||||
TUIChatService.sendVideoMessage(options, sendMessageOptions);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../assets/styles/common";
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
import Words from "./index.vue";
|
||||
export default Words;
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<ToolbarItemContainer
|
||||
ref="container"
|
||||
:iconFile="wordsIcon"
|
||||
title="常用语"
|
||||
:needBottomPopup="true"
|
||||
:iconWidth="isUniFrameWork ? '26px' : '20px'"
|
||||
:iconHeight="isUniFrameWork ? '26px' : '20px'"
|
||||
@onDialogShow="onDialogShow"
|
||||
@onDialogClose="onDialogClose"
|
||||
>
|
||||
<div :class="['words', !isPC && 'words-h5']">
|
||||
<div :class="['words-header', !isPC && 'words-h5-header']">
|
||||
<span :class="['words-header-title', !isPC && 'words-h5-header-title']">
|
||||
{{ TUITranslateService.t("Words.常用语-快捷回复工具") }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!isPC"
|
||||
:class="['words-header-close', !isPC && 'words-h5-header-close']"
|
||||
@click="closeDialog"
|
||||
>
|
||||
关闭
|
||||
</span>
|
||||
</div>
|
||||
<ul :class="['words-list', !isPC && 'words-h5-list']">
|
||||
<li
|
||||
v-for="(item, index) in wordsList"
|
||||
:key="index"
|
||||
:class="['words-list-item', !isPC && 'words-h5-list-item']"
|
||||
@click="selectWord(item)"
|
||||
>
|
||||
{{ TUITranslateService.t(`Words.${item.value}`) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ToolbarItemContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
TUITranslateService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
SendMessageParams,
|
||||
TUIChatService,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { ref } from '../../../../adapter-vue';
|
||||
import ToolbarItemContainer from '../toolbar-item-container/index.vue';
|
||||
import wordsIconLight from '../../../../assets/icon/words-light.svg';
|
||||
import wordsIconDark from '../../../../assets/icon/words-dark.svg';
|
||||
import { wordsList } from '../../utils/wordsList';
|
||||
import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
|
||||
import { isPC, isUniFrameWork } from '../../../../utils/env';
|
||||
import TUIChatConfig from '../../config';
|
||||
|
||||
const wordsIcon = TUIChatConfig.getTheme() === 'dark' ? wordsIconDark : wordsIconLight;
|
||||
const emits = defineEmits(['onDialogPopupShowOrHide']);
|
||||
const currentConversation = ref<IConversationModel>();
|
||||
const container = ref();
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: (conversation: IConversationModel) => {
|
||||
currentConversation.value = conversation;
|
||||
},
|
||||
});
|
||||
|
||||
const selectWord = (item: any) => {
|
||||
const options = {
|
||||
to:
|
||||
currentConversation?.value?.groupProfile?.groupID
|
||||
|| currentConversation?.value?.userProfile?.userID,
|
||||
conversationType: currentConversation?.value?.type,
|
||||
payload: {
|
||||
text: TUITranslateService.t(`Words.${item.value}`),
|
||||
},
|
||||
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
||||
} as SendMessageParams;
|
||||
TUIChatService.sendTextMessage(options);
|
||||
// close dialog after submit evaluate
|
||||
container?.value?.toggleDialogDisplay(false);
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
container?.value?.toggleDialogDisplay(false);
|
||||
};
|
||||
|
||||
const onDialogShow = () => {
|
||||
emits('onDialogPopupShowOrHide', true);
|
||||
};
|
||||
|
||||
const onDialogClose = () => {
|
||||
emits('onDialogPopupShowOrHide', false);
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss" src="./style/index.scss"></style>
|
||||
@@ -0,0 +1,8 @@
|
||||
.words {
|
||||
background-color: #ffffff;
|
||||
&-header {
|
||||
&-close {
|
||||
color: #3370ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
.words-h5 {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
max-height: 80vh;
|
||||
height: fit-content;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&-header {
|
||||
&-title {
|
||||
font-size: 18px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
&-list {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
&-item {
|
||||
cursor: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-moz-tap-highlight-color: transparent;
|
||||
padding: 12px 0;
|
||||
font-size: 16px;
|
||||
color: #50545c;
|
||||
line-height: 18px;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@import url("../../../../../assets/styles/common.scss");
|
||||
@import "./color.scss";
|
||||
@import "./web.scss";
|
||||
@import "./h5.scss";
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
.words {
|
||||
z-index: 5;
|
||||
width: 315px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 19.13rem;
|
||||
height: 12.44rem;
|
||||
overflow-y: auto;
|
||||
&-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
&-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
&-item {
|
||||
cursor: pointer;
|
||||
padding: 4px 0;
|
||||
font-size: 14px;
|
||||
color: #50545c;
|
||||
line-height: 18px;
|
||||
}
|
||||
&-item:hover {
|
||||
color: #006eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user