@tencentcloud/ai-desk-customer-vue 1.5.11 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/assets/arrow_down_icon.svg +1 -0
- package/assets/arrow_down_icon_white.svg +1 -0
- package/assets/audio-blue.svg +4 -0
- package/assets/audio_icon_1.svg +3 -0
- package/assets/audio_icon_2.svg +3 -0
- package/assets/audio_icon_3.svg +3 -0
- package/assets/keyboard-icon.svg +9 -0
- package/components/CustomerServiceChat/chat-header/index-web.vue +1 -5
- package/components/CustomerServiceChat/emoji-config/default-emoji.ts +11 -0
- package/components/CustomerServiceChat/emoji-config/index.ts +14 -1
- package/components/CustomerServiceChat/index-web.vue +10 -2
- package/components/CustomerServiceChat/message-input/index-web.vue +95 -10
- package/components/CustomerServiceChat/message-input/message-input-button.vue +5 -0
- package/components/CustomerServiceChat/message-input/message-input-editor-web.vue +346 -11
- package/components/CustomerServiceChat/message-input-toolbar/emoji-dialog-mobile/emoji-dialog-mobile.vue +26 -46
- package/components/CustomerServiceChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue +8 -38
- package/components/CustomerServiceChat/message-list/bottom-quick-order/index.vue +1 -0
- package/components/CustomerServiceChat/message-list/index-web.vue +13 -0
- package/components/CustomerServiceChat/message-list/message-elements/message-audio-web.vue +50 -78
- package/components/CustomerServiceChat/message-list/message-elements/message-bubble-web.vue +1 -1
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/marked.ts +5 -3
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-robot-welcome.vue +13 -6
- package/components/CustomerServiceChat/message-list/message-elements/message-text.vue +3 -2
- package/components/CustomerServiceChat/message-list/scroll-button/index.vue +17 -9
- package/components/common/Toast/index-web.ts +16 -3
- package/components/common/Toast/index-web.vue +16 -6
- package/constant.ts +1 -0
- package/locales/en/TUIChat.ts +7 -3
- package/locales/en/aidesk.ts +0 -1
- package/locales/fil/TUIChat.ts +7 -3
- package/locales/fil/aidesk.ts +0 -1
- package/locales/id/TUIChat.ts +76 -72
- package/locales/id/aidesk.ts +0 -1
- package/locales/ja/TUIChat.ts +76 -72
- package/locales/ja/aidesk.ts +0 -1
- package/locales/ms/TUIChat.ts +76 -72
- package/locales/ms/aidesk.ts +0 -1
- package/locales/ru/TUIChat.ts +7 -3
- package/locales/ru/aidesk.ts +0 -1
- package/locales/th/TUIChat.ts +76 -72
- package/locales/th/aidesk.ts +0 -1
- package/locales/vi/TUIChat.ts +76 -72
- package/locales/vi/aidesk.ts +0 -1
- package/locales/zh_cn/TUIChat.ts +6 -2
- package/locales/zh_cn/aidesk.ts +0 -1
- package/locales/zh_tw/TUIChat.ts +6 -2
- package/locales/zh_tw/aidesk.ts +0 -1
- package/package.json +2 -1
- package/server.ts +22 -3
- package/assets/double-arrow.svg +0 -1
- package/assets/keyboard_icon.png +0 -0
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
<div
|
|
3
3
|
:class="[
|
|
4
4
|
'message-input-editor-container',
|
|
5
|
-
isPC && !props.
|
|
5
|
+
isPC && !props.shouldShowToolbar && 'message-input-editor-container-no-inputTool',
|
|
6
6
|
isH5 && 'message-input-editor-container-h5',
|
|
7
|
-
isH5 && !props.
|
|
8
|
-
isH5 && !props.
|
|
7
|
+
isH5 && !props.shouldShowEmoji && props.shouldShowToolbar && 'message-input-editor-container-h5-no-emoji-no-tool',
|
|
8
|
+
isH5 && !props.shouldShowEmoji && !props.shouldShowToolbar && 'message-input-editor-container-h5-no-emoji',
|
|
9
|
+
isH5 && props.shouldShowAudio && 'message-input-editor-container-h5-audio',
|
|
10
|
+
isH5 && isInAudioMode && 'message-input-editor-container-h5-audio-mode',
|
|
9
11
|
]"
|
|
10
12
|
>
|
|
11
13
|
<div
|
|
@@ -14,8 +16,35 @@
|
|
|
14
16
|
>
|
|
15
17
|
{{ muteText }}
|
|
16
18
|
</div>
|
|
19
|
+
<div v-if="isInAudioMode"
|
|
20
|
+
class="audio-mode no-copy"
|
|
21
|
+
@touchstart="startRecording"
|
|
22
|
+
@touchend="stopRecording"
|
|
23
|
+
@touchcancel="stopRecording"
|
|
24
|
+
@touchmove="handleTouchMove"
|
|
25
|
+
@contextmenu.prevent
|
|
26
|
+
>
|
|
27
|
+
{{ TUITranslateService.t('TUIChat.按住说话') }}
|
|
28
|
+
<div v-if="isRecording" class="record-container">
|
|
29
|
+
<div class="record-tip">
|
|
30
|
+
{{ recordTip }}
|
|
31
|
+
</div>
|
|
32
|
+
<div class="audio-wave-container">
|
|
33
|
+
<div :class="['audio-wave-bubble', recordCancel ? 'audio-wave-bubble-cancel' : '']">
|
|
34
|
+
<div class="audio-wave">
|
|
35
|
+
<div
|
|
36
|
+
v-for="(bar, index) in audioWaveBars"
|
|
37
|
+
:key="index"
|
|
38
|
+
class="wave-bar"
|
|
39
|
+
:style="{ height: bar.height + 'px' }"
|
|
40
|
+
></div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
17
46
|
<div
|
|
18
|
-
v-if="!isMuted && enableInput"
|
|
47
|
+
v-if="!isMuted && enableInput && !isInAudioMode"
|
|
19
48
|
ref="editorDom"
|
|
20
49
|
class="message-input-editor-area"
|
|
21
50
|
:contenteditable="isH5"
|
|
@@ -30,10 +59,12 @@
|
|
|
30
59
|
</template>
|
|
31
60
|
<script setup lang="ts">
|
|
32
61
|
import vue from '../../../adapter-vue';
|
|
33
|
-
import {
|
|
62
|
+
import TUIChatEngine, {
|
|
34
63
|
TUIStore,
|
|
35
64
|
StoreName,
|
|
36
65
|
IMessageModel,
|
|
66
|
+
TUIChatService,
|
|
67
|
+
TUITranslateService,
|
|
37
68
|
} from '@tencentcloud/chat-uikit-engine';
|
|
38
69
|
import { Editor, JSONContent } from '@tiptap/core';
|
|
39
70
|
import Document from '@tiptap/extension-document';
|
|
@@ -46,6 +77,14 @@ import { ITipTapEditorContent } from '../../../interface';
|
|
|
46
77
|
import { parseTextToRenderArray } from '../emoji-config';
|
|
47
78
|
import { isH5, isPC } from '../../../utils/env';
|
|
48
79
|
import DraftManager from '../utils/conversationDraft';
|
|
80
|
+
import {
|
|
81
|
+
Toast,
|
|
82
|
+
TOAST_TYPE,
|
|
83
|
+
} from '../../common/Toast/index-web';
|
|
84
|
+
import Recorder from "js-audio-recorder";
|
|
85
|
+
import { isEnabledMessageReadReceiptGlobal } from '../../../utils/utils';
|
|
86
|
+
import Log from '../../../utils/logger';
|
|
87
|
+
import state from '../../../utils/state.js';
|
|
49
88
|
const { toRefs, ref, onMounted, watch, onUnmounted } = vue;
|
|
50
89
|
|
|
51
90
|
const props = defineProps({
|
|
@@ -77,14 +116,22 @@ const props = defineProps({
|
|
|
77
116
|
type: Boolean,
|
|
78
117
|
default: true,
|
|
79
118
|
},
|
|
80
|
-
|
|
119
|
+
isInAudioMode:{
|
|
120
|
+
type: Boolean,
|
|
121
|
+
default: false,
|
|
122
|
+
},
|
|
123
|
+
shouldShowEmoji: {
|
|
81
124
|
type: Boolean,
|
|
82
125
|
default: true,
|
|
83
126
|
},
|
|
84
|
-
|
|
127
|
+
shouldShowToolbar: {
|
|
85
128
|
type: Boolean,
|
|
86
129
|
default: true,
|
|
87
|
-
}
|
|
130
|
+
},
|
|
131
|
+
shouldShowAudio: {
|
|
132
|
+
type: Boolean,
|
|
133
|
+
default: false,
|
|
134
|
+
},
|
|
88
135
|
});
|
|
89
136
|
|
|
90
137
|
const emits = defineEmits(['sendMessage', 'onTyping', 'blurToolAndEmojiH5', 'isInputNotEmpty']);
|
|
@@ -97,6 +144,18 @@ const currentQuoteMessage = ref<{ message: IMessageModel; type: string }>();
|
|
|
97
144
|
const editorDom = ref();
|
|
98
145
|
let editor: Editor | null = null;
|
|
99
146
|
const fileMap = new Map<string, any>();
|
|
147
|
+
const recorder = ref();
|
|
148
|
+
const recordTime = ref(0);
|
|
149
|
+
const isInAudioMode = ref(props.isInAudioMode);
|
|
150
|
+
const isRecording = ref(false);
|
|
151
|
+
let startRecordY = 0;
|
|
152
|
+
const recordCancel = ref(false);
|
|
153
|
+
const recordTip = ref(TUITranslateService.t('TUIChat.松开发送'));
|
|
154
|
+
let recordTimer: any = null;
|
|
155
|
+
const audioWaveBars = ref(Array(24).fill({ height: 6 }));
|
|
156
|
+
let audioWaveAnimation = -1;
|
|
157
|
+
let pressStartTime: any = null;
|
|
158
|
+
const hasMicPermission = ref(false);
|
|
100
159
|
|
|
101
160
|
function onCurrentConversationIDUpdated(conversationID: string) {
|
|
102
161
|
if (currentConversationID.value !== conversationID) {
|
|
@@ -134,6 +193,13 @@ function focusEditor() {
|
|
|
134
193
|
}
|
|
135
194
|
}
|
|
136
195
|
|
|
196
|
+
watch(
|
|
197
|
+
() => [props.isInAudioMode],
|
|
198
|
+
(newValue) => {
|
|
199
|
+
isInAudioMode.value = newValue[0];
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
137
203
|
onMounted(() => {
|
|
138
204
|
editor = isPC
|
|
139
205
|
? new Editor({
|
|
@@ -209,6 +275,15 @@ onMounted(() => {
|
|
|
209
275
|
TUIStore.watch(StoreName.CHAT, {
|
|
210
276
|
quoteMessage: onQuoteMessageUpdated,
|
|
211
277
|
});
|
|
278
|
+
|
|
279
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
280
|
+
|
|
281
|
+
recorder.value = new Recorder({
|
|
282
|
+
sampleBits: 16,
|
|
283
|
+
sampleRate: 44100,
|
|
284
|
+
numChannels: 1,
|
|
285
|
+
compiling: false,
|
|
286
|
+
});
|
|
212
287
|
});
|
|
213
288
|
|
|
214
289
|
onUnmounted(() => {
|
|
@@ -222,6 +297,8 @@ onUnmounted(() => {
|
|
|
222
297
|
|
|
223
298
|
// clear map store
|
|
224
299
|
fileMap.clear();
|
|
300
|
+
initRecorder();
|
|
301
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
225
302
|
});
|
|
226
303
|
|
|
227
304
|
function handleEnter(e: any) {
|
|
@@ -250,9 +327,12 @@ function handleH5Blur() {
|
|
|
250
327
|
isH5 && (isEditorBlur.value = true);
|
|
251
328
|
}
|
|
252
329
|
|
|
253
|
-
async function handleH5Focus() {
|
|
330
|
+
async function handleH5Focus(e: any) {
|
|
254
331
|
emits('blurToolAndEmojiH5');
|
|
255
332
|
isH5 && (isEditorBlur.value = false);
|
|
333
|
+
if (e.target) {
|
|
334
|
+
e.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
335
|
+
}
|
|
256
336
|
}
|
|
257
337
|
|
|
258
338
|
function handlePCFileDrop(e: any) {
|
|
@@ -619,7 +699,9 @@ function resetEditor() {
|
|
|
619
699
|
editor?.commands?.clearContent(true);
|
|
620
700
|
isEditorBlur.value = true;
|
|
621
701
|
isEditorEmpty.value = true;
|
|
622
|
-
|
|
702
|
+
if (!isInAudioMode.value) {
|
|
703
|
+
isH5 && (editorDom.value.innerHTML = '');
|
|
704
|
+
}
|
|
623
705
|
}
|
|
624
706
|
|
|
625
707
|
function getEditorHTML(): string {
|
|
@@ -712,7 +794,167 @@ watch(
|
|
|
712
794
|
deep: true,
|
|
713
795
|
},
|
|
714
796
|
);
|
|
797
|
+
async function initRecorder() {
|
|
798
|
+
clearInterval(recordTimer);
|
|
799
|
+
recordTimer = null;
|
|
800
|
+
recordTime.value = 0;
|
|
801
|
+
await recorder.value.stop();
|
|
802
|
+
isRecording.value = false;
|
|
803
|
+
recordTip.value = TUITranslateService.t("TUIChat.松开发送");
|
|
804
|
+
cancelAnimationFrame(audioWaveAnimation);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
watch(
|
|
808
|
+
recordTime,
|
|
809
|
+
(newVal: number) => {
|
|
810
|
+
if(newVal >= 50 && newVal < 60) {
|
|
811
|
+
let tip = '';
|
|
812
|
+
const currentLanguage = state.get('currentLanguage');
|
|
813
|
+
if (currentLanguage === 'zh' || currentLanguage === 'zh_tw' || currentLanguage === 'ja') {
|
|
814
|
+
tip = `${10 - newVal}${TUITranslateService.t('TUIChat.录音结束提醒')}`
|
|
815
|
+
} else {
|
|
816
|
+
tip = `${TUITranslateService.t('TUIChat.录音结束提醒')} ${10 - newVal}s`
|
|
817
|
+
}
|
|
818
|
+
recordTip.value = tip;
|
|
819
|
+
} else if (newVal >= 60) {
|
|
820
|
+
stopRecording();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
const startRecording = async(e) => {
|
|
826
|
+
if (isRecording.value) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
isRecording.value = true;
|
|
830
|
+
pressStartTime = new Date();
|
|
831
|
+
recordCancel.value = false;
|
|
832
|
+
Recorder.getPermission().then(() => {
|
|
833
|
+
hasMicPermission.value = true;
|
|
834
|
+
if (!isRecording.value) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (recordTimer !== null) {
|
|
838
|
+
initRecorder();
|
|
839
|
+
}
|
|
840
|
+
// 提前获取音频流,避免开始录音时启动过长
|
|
841
|
+
recorder.value.start().then(() => {
|
|
842
|
+
if (!isRecording.value) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
updateRecordWave();
|
|
846
|
+
startRecordY = getRecordY(e);
|
|
847
|
+
recordTimer = setInterval(() => {
|
|
848
|
+
recordTime.value = recordTime.value + 1;
|
|
849
|
+
}, 1000);
|
|
850
|
+
}, (error) => {
|
|
851
|
+
Log.e(`Error in recording ${error.name} : ${error.message}`);
|
|
852
|
+
let toastMessage = `${error.name} : ${error.message}`;
|
|
853
|
+
Toast({
|
|
854
|
+
message:toastMessage,
|
|
855
|
+
type: TOAST_TYPE.ERROR,
|
|
856
|
+
duration: 2000,
|
|
857
|
+
});
|
|
858
|
+
initRecorder();
|
|
859
|
+
});
|
|
860
|
+
}, (error) => {
|
|
861
|
+
initRecorder();
|
|
862
|
+
Toast({
|
|
863
|
+
message:TUITranslateService.t("TUIChat.请检查麦克风访问权限"),
|
|
864
|
+
type: TOAST_TYPE.ERROR,
|
|
865
|
+
duration: 2000,
|
|
866
|
+
});
|
|
867
|
+
Log.w(`Error in mic permission ${error.name} : ${error.message}`);
|
|
868
|
+
});
|
|
869
|
+
e.preventDefault();
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
const stopRecording = async () => {
|
|
873
|
+
const duration = recordTime.value;
|
|
874
|
+
initRecorder();
|
|
875
|
+
const pressDuration = Date.now() - pressStartTime;
|
|
876
|
+
if (pressDuration < 1000 && hasMicPermission.value) {
|
|
877
|
+
// 按压时间过短,还没开始录音就结束
|
|
878
|
+
Toast({
|
|
879
|
+
message: TUITranslateService.t("TUIChat.按压时间过短,请按压超过1秒"),
|
|
880
|
+
type: TOAST_TYPE.ERROR,
|
|
881
|
+
duration: 3000,
|
|
882
|
+
});
|
|
883
|
+
} else if (!recordCancel.value && duration) {
|
|
884
|
+
// 已经录音 && 没有取消录音
|
|
885
|
+
if (duration <= 60) {
|
|
886
|
+
let wavBlob = recorder.value.getWAVBlob();
|
|
887
|
+
let tempFilePath = new File([wavBlob], `ai_desk_customer_vue_${Date.now()}.wav`, { type: "wav" });
|
|
888
|
+
(tempFilePath as any).duration = duration * 1000;
|
|
889
|
+
if (tempFilePath && duration <= 60) {
|
|
890
|
+
try {
|
|
891
|
+
TUIChatService.sendAudioMessage({
|
|
892
|
+
to: currentConversationID.value.replace(TUIChatEngine.TYPES.CONV_C2C, ''),
|
|
893
|
+
conversationType: TUIChatEngine.TYPES.CONV_C2C,
|
|
894
|
+
payload: { file: tempFilePath },
|
|
895
|
+
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
|
896
|
+
});
|
|
897
|
+
} catch (error) {
|
|
898
|
+
const message = `${TUITranslateService.t("TUIChat.发送失败")}: ${error}`;
|
|
899
|
+
Toast({
|
|
900
|
+
message,
|
|
901
|
+
type: TOAST_TYPE.ERROR,
|
|
902
|
+
duration: 2000,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
const getRecordY = (e) => {
|
|
911
|
+
return e.type.includes('touch') ? e.touches[0].clientY : e.clientY;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const handleTouchMove = (e) => {
|
|
915
|
+
if (!isRecording.value) return;
|
|
916
|
+
let currentRecordY = getRecordY(e);
|
|
917
|
+
let deltaY = Math.abs(currentRecordY - startRecordY);
|
|
918
|
+
if (recordTime.value <= 50) {
|
|
919
|
+
if (deltaY > 40) {
|
|
920
|
+
recordCancel.value = true;
|
|
921
|
+
recordTip.value = TUITranslateService.t("TUIChat.松手取消");
|
|
922
|
+
} else {
|
|
923
|
+
recordCancel.value = false;
|
|
924
|
+
recordTip.value = TUITranslateService.t("TUIChat.松开发送");
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const updateRecordWave = () => {
|
|
930
|
+
if (!isRecording.value) return;
|
|
931
|
+
const dataArray = recorder.value.getRecordAnalyseData();
|
|
932
|
+
const chunkSize = Math.floor(dataArray.length / 24);
|
|
933
|
+
const newBars = [{height: 0}];
|
|
934
|
+
const SILENCE_THRESHOLD = 129;
|
|
935
|
+
for (let i = 0; i < 24; i++) {
|
|
936
|
+
const chunk = dataArray.slice(i * chunkSize, (i + 1) * chunkSize);
|
|
937
|
+
const avg = chunk.reduce((sum, val) => sum + Math.abs(val), 0) / chunkSize; // 使用绝对值确保正值
|
|
938
|
+
let height;
|
|
939
|
+
if (avg < SILENCE_THRESHOLD) {
|
|
940
|
+
// 静音时固定为5px
|
|
941
|
+
height = 5;
|
|
942
|
+
} else {
|
|
943
|
+
// 动态映射:静音(5px) ~ 最大音量(60px)
|
|
944
|
+
const normalized = Math.min(avg / 255, 1); // 限制到[0,1]范围
|
|
945
|
+
height = 5 + normalized * 55; // 5 + (0~1)*55 => 5~60px
|
|
946
|
+
}
|
|
947
|
+
newBars.push({ height });
|
|
948
|
+
}
|
|
949
|
+
audioWaveBars.value = newBars;
|
|
950
|
+
audioWaveAnimation = requestAnimationFrame(updateRecordWave);
|
|
951
|
+
}
|
|
715
952
|
|
|
953
|
+
const handleVisibilityChange = () => {
|
|
954
|
+
if (document.visibilityState === 'hidden') {
|
|
955
|
+
initRecorder();
|
|
956
|
+
}
|
|
957
|
+
}
|
|
716
958
|
defineExpose({
|
|
717
959
|
getEditorContent,
|
|
718
960
|
addEmoji,
|
|
@@ -721,6 +963,8 @@ defineExpose({
|
|
|
721
963
|
getEditorHTML,
|
|
722
964
|
insertEditorContent,
|
|
723
965
|
blur,
|
|
966
|
+
startRecording,
|
|
967
|
+
stopRecording
|
|
724
968
|
});
|
|
725
969
|
</script>
|
|
726
970
|
|
|
@@ -822,6 +1066,12 @@ defineExpose({
|
|
|
822
1066
|
.message-input-editor-container-h5-no-emoji-no-tool {
|
|
823
1067
|
border-radius: 10px;
|
|
824
1068
|
}
|
|
1069
|
+
.message-input-editor-container-h5-audio {
|
|
1070
|
+
margin: 10px 0 0 5px;
|
|
1071
|
+
}
|
|
1072
|
+
.message-input-editor-container-h5-audio-mode {
|
|
1073
|
+
padding: 0;
|
|
1074
|
+
}
|
|
825
1075
|
</style>
|
|
826
1076
|
<style lang="scss">
|
|
827
1077
|
/* stylelint-disable-next-line selector-class-pattern */
|
|
@@ -915,7 +1165,92 @@ defineExpose({
|
|
|
915
1165
|
pointer-events: none;
|
|
916
1166
|
}
|
|
917
1167
|
}
|
|
918
|
-
|
|
1168
|
+
.audio-mode {
|
|
1169
|
+
text-align: center;
|
|
1170
|
+
font-size: 14px;
|
|
1171
|
+
color: rgb(0,0,0);
|
|
1172
|
+
padding: 8px 0 8px 10px !important;
|
|
1173
|
+
}
|
|
1174
|
+
.no-copy {
|
|
1175
|
+
user-select: none; /* 标准属性 */
|
|
1176
|
+
-webkit-user-select: none; /* Safari/Chrome */
|
|
1177
|
+
-moz-user-select: none; /* Firefox */
|
|
1178
|
+
-ms-user-select: none; /* IE10+ */
|
|
1179
|
+
}
|
|
1180
|
+
.record-container{
|
|
1181
|
+
height: 30vh;
|
|
1182
|
+
width: 100vw;
|
|
1183
|
+
position:fixed;
|
|
1184
|
+
overflow: hidden;
|
|
1185
|
+
right: 0;
|
|
1186
|
+
bottom: 0;
|
|
1187
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 64.35%);
|
|
1188
|
+
display: flex;
|
|
1189
|
+
flex-direction: column;
|
|
1190
|
+
justify-content: flex-end;
|
|
1191
|
+
.audio-wave-container {
|
|
1192
|
+
box-sizing: border-box;
|
|
1193
|
+
width: 100vw;
|
|
1194
|
+
margin-bottom: 10px;
|
|
1195
|
+
display: flex;
|
|
1196
|
+
.audio-wave-bubble {
|
|
1197
|
+
background: #006AF6;
|
|
1198
|
+
margin: 0 10px;
|
|
1199
|
+
height: 34px;
|
|
1200
|
+
border-radius: 10px;
|
|
1201
|
+
width: 100%;
|
|
1202
|
+
display: flex;
|
|
1203
|
+
justify-content: center;
|
|
1204
|
+
align-items: center;
|
|
1205
|
+
}
|
|
1206
|
+
.audio-wave-bubble-cancel {
|
|
1207
|
+
background: #FF002F;
|
|
1208
|
+
}
|
|
1209
|
+
.audio-wave {
|
|
1210
|
+
color: white;
|
|
1211
|
+
display: flex;
|
|
1212
|
+
gap: 4px;
|
|
1213
|
+
.wave-bar {
|
|
1214
|
+
width: 3px;
|
|
1215
|
+
background: white;
|
|
1216
|
+
border-radius: 6px;
|
|
1217
|
+
transition: height 0.1s ease-out;
|
|
1218
|
+
margin-top: auto;
|
|
1219
|
+
margin-bottom: auto;
|
|
1220
|
+
transform-origin: center bottom;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
.icon-container{
|
|
1225
|
+
position: absolute;
|
|
1226
|
+
bottom: -190%;
|
|
1227
|
+
left: -80%;
|
|
1228
|
+
width: 950px;
|
|
1229
|
+
height: 950px;
|
|
1230
|
+
border-radius: 50%;
|
|
1231
|
+
background: linear-gradient(180deg, #E8EDF3 -1.18%, #FBFBFB 4.94%);
|
|
1232
|
+
box-shadow: 0px -7.5px 5px 0px rgba(218, 224, 232, 0.15);
|
|
1233
|
+
}
|
|
1234
|
+
.record-tip{
|
|
1235
|
+
color:#656A72;
|
|
1236
|
+
font-size: 12px;
|
|
1237
|
+
margin-bottom: 12px;
|
|
1238
|
+
}
|
|
1239
|
+
.audio-record-icon{
|
|
1240
|
+
position:absolute;
|
|
1241
|
+
bottom:50px;
|
|
1242
|
+
left: 50%;
|
|
1243
|
+
transform: translateX(-50%);
|
|
1244
|
+
z-index:2;
|
|
1245
|
+
}
|
|
1246
|
+
.audio-close-icon {
|
|
1247
|
+
position:absolute;
|
|
1248
|
+
bottom:160px;
|
|
1249
|
+
left: 50%;
|
|
1250
|
+
transform: translateX(-50%);
|
|
1251
|
+
z-index:2;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
919
1254
|
.message-input-mute {
|
|
920
1255
|
color: #721c24;
|
|
921
1256
|
background-color: #f8d7da;
|
|
@@ -1,60 +1,54 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{{ TUITranslateService.t("发送") }}
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
2
|
+
<div class="emojiDialog">
|
|
3
|
+
<ul ref="emojiPickerListRef" class="emojiDialogList">
|
|
4
|
+
<li
|
|
5
|
+
v-for="(childrenItem, childrenIndex) in currentEmojiList"
|
|
6
|
+
:key="childrenIndex"
|
|
7
|
+
class="emojiDialogList-item"
|
|
8
|
+
@click="select(childrenItem, childrenIndex)"
|
|
9
|
+
>
|
|
10
|
+
<img
|
|
11
|
+
class="emoji"
|
|
12
|
+
:src="BASIC_EMOJI_URL + BASIC_EMOJI_URL_MAPPING[childrenItem]"
|
|
13
|
+
>
|
|
14
|
+
</li>
|
|
15
|
+
</ul>
|
|
16
|
+
<div class="sendButtonContainer">
|
|
17
|
+
<div class="sendButton" @click="sendMessage">
|
|
18
|
+
{{ TUITranslateService.t("发送") }}
|
|
19
|
+
</div>
|
|
23
20
|
</div>
|
|
24
|
-
|
|
21
|
+
</div>
|
|
25
22
|
</template>
|
|
26
23
|
<script lang="ts" setup>
|
|
27
24
|
import vue from '../../../../adapter-vue';
|
|
28
|
-
import { IEmojiGroupList
|
|
25
|
+
import { IEmojiGroupList } from '../../../../interface';
|
|
29
26
|
const { ref, onMounted, onUnmounted } = vue;
|
|
30
27
|
import {
|
|
31
|
-
TUIChatService,
|
|
32
28
|
TUIStore,
|
|
33
29
|
StoreName,
|
|
34
30
|
IConversationModel,
|
|
35
31
|
TUITranslateService,
|
|
36
|
-
SendMessageParams,
|
|
37
32
|
} from '@tencentcloud/chat-uikit-engine';
|
|
38
33
|
import {
|
|
39
34
|
EMOJI_GROUP_LIST,
|
|
40
35
|
BASIC_EMOJI_URL_MAPPING,
|
|
41
36
|
convertKeyToEmojiName,
|
|
37
|
+
BASIC_EMOJI_URL,
|
|
38
|
+
filterEmojisInNonChineseEnv,
|
|
42
39
|
} from '../../emoji-config';
|
|
43
|
-
import { EMOJI_TYPE } from '.././../../../constant';
|
|
44
|
-
import { isPC, } from '../../../../utils/env';
|
|
45
40
|
|
|
46
41
|
const emits = defineEmits(['insertEmoji', 'onClose', 'sendMessage']);
|
|
47
42
|
const list = ref<IEmojiGroupList>(initEmojiList());
|
|
48
43
|
const currentConversation = ref();
|
|
49
44
|
const emojiPickerListRef = ref();
|
|
50
|
-
const currentTabIndex = ref<number>(0);
|
|
51
45
|
const currentEmojiList = ref<string[]>(list?.value[0]?.list);
|
|
52
|
-
const currentTabItem = ref<IEmojiGroup>(list?.value[0]);
|
|
53
46
|
|
|
54
47
|
onMounted(() => {
|
|
55
48
|
TUIStore.watch(StoreName.CONV, {
|
|
56
49
|
currentConversation: onCurrentConversationUpdate,
|
|
57
50
|
});
|
|
51
|
+
currentEmojiList.value = filterEmojisInNonChineseEnv(list.value[0].list);
|
|
58
52
|
});
|
|
59
53
|
|
|
60
54
|
onUnmounted(() => {
|
|
@@ -66,24 +60,10 @@ onUnmounted(() => {
|
|
|
66
60
|
const select = (item: any, index: number) => {
|
|
67
61
|
const options: any = {
|
|
68
62
|
emoji: { key: item, name: convertKeyToEmojiName(item) },
|
|
69
|
-
type:
|
|
63
|
+
type: 'basic',
|
|
64
|
+
url : BASIC_EMOJI_URL + BASIC_EMOJI_URL_MAPPING[item],
|
|
70
65
|
};
|
|
71
|
-
|
|
72
|
-
case EMOJI_TYPE.BASIC:
|
|
73
|
-
options.url = currentTabItem?.value?.url + BASIC_EMOJI_URL_MAPPING[item];
|
|
74
|
-
emits('insertEmoji', options);
|
|
75
|
-
|
|
76
|
-
break;
|
|
77
|
-
case EMOJI_TYPE.BIG:
|
|
78
|
-
// sendFaceMessage(index, currentTabItem.value);
|
|
79
|
-
break;
|
|
80
|
-
case EMOJI_TYPE.CUSTOM:
|
|
81
|
-
// sendFaceMessage(index, currentTabItem.value);
|
|
82
|
-
break;
|
|
83
|
-
default:
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
isPC && emits('onClose');
|
|
66
|
+
emits('insertEmoji', options);
|
|
87
67
|
};
|
|
88
68
|
|
|
89
69
|
function sendMessage(){
|
package/components/CustomerServiceChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue
CHANGED
|
@@ -17,9 +17,8 @@
|
|
|
17
17
|
@click="select(childrenItem, childrenIndex)"
|
|
18
18
|
>
|
|
19
19
|
<img
|
|
20
|
-
v-if="currentTabItem.type === EMOJI_TYPE.BASIC"
|
|
21
20
|
class="emoji"
|
|
22
|
-
:src="
|
|
21
|
+
:src="BASIC_EMOJI_URL + BASIC_EMOJI_URL_MAPPING[childrenItem]"
|
|
23
22
|
>
|
|
24
23
|
<!-- <img-->
|
|
25
24
|
<!-- v-else-if="currentTabItem.type === EMOJI_TYPE.BIG"-->
|
|
@@ -76,9 +75,6 @@ import {
|
|
|
76
75
|
SendMessageParams,
|
|
77
76
|
TUITranslateService
|
|
78
77
|
} from '@tencentcloud/chat-uikit-engine';
|
|
79
|
-
import Icon from '../../../common/Icon.vue';
|
|
80
|
-
import faceIcon from '../../../../assets/face.svg';
|
|
81
|
-
import { EMOJI_TYPE } from '.././../../../constant';
|
|
82
78
|
import { isPC, isH5 } from '../../../../utils/env';
|
|
83
79
|
import { IEmojiGroupList, IEmojiGroup } from '../../../../interface';
|
|
84
80
|
import { isEnabledMessageReadReceiptGlobal } from '../../../../utils/utils';
|
|
@@ -86,22 +82,23 @@ import {
|
|
|
86
82
|
EMOJI_GROUP_LIST,
|
|
87
83
|
BASIC_EMOJI_URL_MAPPING,
|
|
88
84
|
convertKeyToEmojiName,
|
|
85
|
+
BASIC_EMOJI_URL,
|
|
86
|
+
filterEmojisInNonChineseEnv,
|
|
89
87
|
} from '../../emoji-config';
|
|
90
88
|
const { ref, onMounted, onUnmounted } = vue;
|
|
91
89
|
|
|
92
90
|
const emits = defineEmits(['insertEmoji', 'onClose', 'sendMessage']);
|
|
93
|
-
const currentTabIndex = ref<number>(0);
|
|
94
91
|
const currentConversation = ref();
|
|
95
92
|
const emojiPickerDialog = ref();
|
|
96
93
|
const emojiPickerListRef = ref();
|
|
97
|
-
const list = ref<IEmojiGroupList>(
|
|
98
|
-
const currentTabItem = ref<IEmojiGroup>(list?.value[0]);
|
|
94
|
+
const list = ref<IEmojiGroupList>(EMOJI_GROUP_LIST);
|
|
99
95
|
const currentEmojiList = ref<string[]>(list?.value[0]?.list);
|
|
100
96
|
|
|
101
97
|
onMounted(() => {
|
|
102
98
|
TUIStore.watch(StoreName.CONV, {
|
|
103
99
|
currentConversation: onCurrentConversationUpdate,
|
|
104
100
|
});
|
|
101
|
+
currentEmojiList.value = filterEmojisInNonChineseEnv(list.value[0].list);
|
|
105
102
|
});
|
|
106
103
|
|
|
107
104
|
onUnmounted(() => {
|
|
@@ -110,35 +107,13 @@ onUnmounted(() => {
|
|
|
110
107
|
});
|
|
111
108
|
});
|
|
112
109
|
|
|
113
|
-
const toggleEmojiTab = (index: number) => {
|
|
114
|
-
currentTabIndex.value = index;
|
|
115
|
-
currentTabItem.value = list?.value[index];
|
|
116
|
-
currentEmojiList.value = list?.value[index]?.list;
|
|
117
|
-
// web & h5 side scroll to top
|
|
118
|
-
emojiPickerListRef?.value && (emojiPickerListRef.value.scrollTop = 0);
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
|
|
122
110
|
const select = (item: any, index: number) => {
|
|
123
111
|
const options: any = {
|
|
124
112
|
emoji: { key: item, name: convertKeyToEmojiName(item) },
|
|
125
|
-
type:
|
|
113
|
+
type: 'basic',
|
|
126
114
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
options.url = currentTabItem?.value?.url + BASIC_EMOJI_URL_MAPPING[item];
|
|
130
|
-
emits('insertEmoji', options);
|
|
131
|
-
|
|
132
|
-
break;
|
|
133
|
-
case EMOJI_TYPE.BIG:
|
|
134
|
-
sendFaceMessage(index, currentTabItem.value);
|
|
135
|
-
break;
|
|
136
|
-
case EMOJI_TYPE.CUSTOM:
|
|
137
|
-
sendFaceMessage(index, currentTabItem.value);
|
|
138
|
-
break;
|
|
139
|
-
default:
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
115
|
+
options.url = BASIC_EMOJI_URL + BASIC_EMOJI_URL_MAPPING[item];
|
|
116
|
+
emits('insertEmoji', options);
|
|
142
117
|
isPC && emits('onClose');
|
|
143
118
|
};
|
|
144
119
|
|
|
@@ -159,16 +134,11 @@ const sendFaceMessage = (index: number, listItem: IEmojiGroup) => {
|
|
|
159
134
|
|
|
160
135
|
function sendMessage() {
|
|
161
136
|
emits('sendMessage');
|
|
162
|
-
|
|
163
137
|
}
|
|
164
138
|
|
|
165
139
|
function onCurrentConversationUpdate(conversation: IConversationModel) {
|
|
166
140
|
currentConversation.value = conversation;
|
|
167
141
|
}
|
|
168
|
-
|
|
169
|
-
function initEmojiList() {
|
|
170
|
-
return EMOJI_GROUP_LIST;
|
|
171
|
-
}
|
|
172
142
|
</script>
|
|
173
143
|
|
|
174
144
|
<style lang="scss" scoped src="./style/index.scss"></style>
|