@tencentcloud/ai-desk-customer-vue 1.6.4 → 1.6.7
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 +20 -0
- package/assets/tool_more.svg +5 -0
- package/components/CustomerServiceChat/customer-queue-page/index.vue +7 -16
- package/components/CustomerServiceChat/index-web.vue +16 -3
- package/components/CustomerServiceChat/message-input/message-input-editor-web.vue +1 -1
- package/components/CustomerServiceChat/message-input-toolbar/rating-tool/index.vue +1 -1
- package/components/CustomerServiceChat/message-list/index-web.vue +12 -2
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-robot-welcome.vue +9 -4
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-stream.vue +3 -0
- package/components/CustomerServiceChat/message-list/message-elements/message-thinking.vue +1 -1
- package/components/CustomerServiceChat/message-list/message-elements/simple-message-list/index.vue +0 -1
- package/components/CustomerServiceChat/message-toolbar-button/index.vue +285 -20
- package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-end-human-service.vue +6 -1
- package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-human-service.vue +5 -0
- package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-service-rating.vue +6 -1
- package/components/common/Avatar/index.vue +2 -2
- package/constant.ts +0 -2
- package/locales/en/aidesk.ts +1 -1
- package/package.json +1 -3
- package/server.ts +14 -9
- package/utils/utils.ts +42 -0
- package/assets/video-play.png +0 -0
- package/components/CustomerServiceChat/message-list/message-elements/video-play.vue +0 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 1.6.7 @2025.12.30
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
- initWithProfile 接口 Options 新增参数 clientCustomData,支持用户登录时传入自定义字段。
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
- 优化关闭排队页面的用户体验。
|
|
8
|
+
- H5 端已知问题。
|
|
9
|
+
|
|
10
|
+
## 1.6.6 @2025.12.22
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
- 新增参数 showAllRobotWelcomeItems,支持一页展示欢迎卡片全部内容
|
|
14
|
+
- 工具栏快捷按钮支持设置【结束排队】。
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- 底部快捷订单的自定义数据类型不是 string 导致的未被机器人正确识别的问题。
|
|
18
|
+
- 用户昵称和头像可能被覆盖的问题。
|
|
19
|
+
- 工具栏快捷按钮超出屏幕时折叠超出按钮
|
|
20
|
+
|
|
1
21
|
## 1.6.4 @2025.11.25
|
|
2
22
|
|
|
3
23
|
### Features
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M3 9C2.44775 9 2 8.55228 2 8C2 7.44772 2.44775 7 3 7C3.55225 7 4 7.44772 4 8C4 8.55228 3.55225 9 3 9Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
3
|
+
<path d="M7 8C7 8.55228 7.44775 9 8 9C8.55225 9 9 8.55228 9 8C9 7.44772 8.55225 7 8 7C7.44775 7 7 7.44772 7 8Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
4
|
+
<path d="M12 8C12 8.55228 12.4478 9 13 9C13.5522 9 14 8.55228 14 8C14 7.44772 13.5522 7 13 7C12.4478 7 12 7.44772 12 8Z" fill="black" style="fill:black;fill-opacity:1;"/>
|
|
5
|
+
</svg>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
17
17
|
<div class="customer-queue-end-button">
|
|
18
|
-
<div :class="['customer-queue-end-button-content', isClicked ? 'button-clicked' : '']" @click="
|
|
18
|
+
<div :class="['customer-queue-end-button-content', isClicked ? 'button-clicked' : '']" @click="leaveQueue">
|
|
19
19
|
{{ TUITranslateService.t("AIDesk.结束排队") }}
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
@@ -24,9 +24,8 @@
|
|
|
24
24
|
<script lang="ts" setup>
|
|
25
25
|
import vue from '../../../adapter-vue';
|
|
26
26
|
const { ref, onMounted, onUnmounted } = vue;
|
|
27
|
-
import { IConversationModel, StoreName,
|
|
28
|
-
import {
|
|
29
|
-
import { getTo, isEnabledMessageReadReceiptGlobal } from '../../../utils/utils';
|
|
27
|
+
import { IConversationModel, StoreName, TUIStore, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
|
|
28
|
+
import { getTo, leaveQueuing } from '../../../utils/utils';
|
|
30
29
|
const currentConversation = ref<IConversationModel>();
|
|
31
30
|
const isClicked = ref(false);
|
|
32
31
|
const props = defineProps({
|
|
@@ -35,6 +34,7 @@ const props = defineProps({
|
|
|
35
34
|
default: 0
|
|
36
35
|
},
|
|
37
36
|
});
|
|
37
|
+
const emits = defineEmits(['closeQueuePage']);
|
|
38
38
|
onMounted(() => {
|
|
39
39
|
TUIStore.watch(StoreName.CONV, {
|
|
40
40
|
currentConversation: onCurrentConversationUpdate,
|
|
@@ -51,22 +51,13 @@ const onCurrentConversationUpdate = (conversation: IConversationModel) => {
|
|
|
51
51
|
currentConversation.value = conversation;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const leaveQueue = () => {
|
|
55
55
|
if (isClicked.value) {
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
isClicked.value = true;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
conversationType: currentConversation.value.type,
|
|
62
|
-
payload: {
|
|
63
|
-
data: JSON.stringify({
|
|
64
|
-
customerServicePlugin: 0,
|
|
65
|
-
src: CUSTOM_MESSAGE_SRC.USER_END_CONVERSATION,
|
|
66
|
-
}),
|
|
67
|
-
},
|
|
68
|
-
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
|
69
|
-
},{ onlineUserOnly:true });
|
|
59
|
+
leaveQueuing(getTo(currentConversation.value));
|
|
60
|
+
emits('closeQueuePage');
|
|
70
61
|
}
|
|
71
62
|
</script>
|
|
72
63
|
<style lang="scss">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="['tui-chat', !isPC && 'tui-chat-h5']" :key="currentLanguage">
|
|
3
3
|
<div
|
|
4
|
-
v-if="currentConversationID && (!props.showQueuePage || queueNumber < 0 || queueNumber === undefined)"
|
|
4
|
+
v-if="currentConversationID && (!props.showQueuePage || queueNumber < 0 || queueNumber === undefined || hasLeftQueue)"
|
|
5
5
|
:class="['tui-chat', !isPC && 'tui-chat-h5']"
|
|
6
6
|
>
|
|
7
7
|
<ChatHeader
|
|
@@ -94,8 +94,8 @@
|
|
|
94
94
|
@close="() => { showFeedbackModal = false }"
|
|
95
95
|
/>
|
|
96
96
|
</div>
|
|
97
|
-
<div v-if="props.showQueuePage && queueNumber >= 0" :class="['tui-chat', !isPC && 'tui-chat-h5']">
|
|
98
|
-
<CustomerQueuePage :queueNumber="queueNumber"/>
|
|
97
|
+
<div v-if="props.showQueuePage && queueNumber >= 0 && !hasLeftQueue" :class="['tui-chat', !isPC && 'tui-chat-h5']">
|
|
98
|
+
<CustomerQueuePage :queueNumber="queueNumber" @closeQueuePage="closeQueuePage"/>
|
|
99
99
|
</div>
|
|
100
100
|
</div>
|
|
101
101
|
</template>
|
|
@@ -157,6 +157,7 @@ interface IProps {
|
|
|
157
157
|
headerConfig?: IHeaderConfig;
|
|
158
158
|
enableSendingAudio?: number;
|
|
159
159
|
showQueuePage?: number;
|
|
160
|
+
showAllRobotWelcomeItems?: number;
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
const emits = defineEmits(['closeChat']);
|
|
@@ -177,6 +178,7 @@ const languageForShowList = ref<Array<string>>([]);
|
|
|
177
178
|
const feedbackModalRef = ref();
|
|
178
179
|
const showFeedbackModal = ref(false);
|
|
179
180
|
const queueNumber = ref(-1);
|
|
181
|
+
const hasLeftQueue = ref(false);
|
|
180
182
|
let timezone = '';
|
|
181
183
|
let countryID = '';
|
|
182
184
|
const props = withDefaults(defineProps<IProps>(), {
|
|
@@ -204,6 +206,7 @@ const props = withDefaults(defineProps<IProps>(), {
|
|
|
204
206
|
enableURLDetection: 0,
|
|
205
207
|
enableSendingAudio: 0,
|
|
206
208
|
showQueuePage: 0,
|
|
209
|
+
showAllRobotWelcomeItems: 0,
|
|
207
210
|
});
|
|
208
211
|
|
|
209
212
|
const loginCustomerUIKit = () => {
|
|
@@ -320,6 +323,10 @@ const setEnableURLDetection = () => {
|
|
|
320
323
|
state.set('enableURLDetection', props.enableURLDetection);
|
|
321
324
|
}
|
|
322
325
|
|
|
326
|
+
const setShowAllRobotWelcomeItems = () => {
|
|
327
|
+
state.set('showAllRobotWelcomeItems', props.showAllRobotWelcomeItems);
|
|
328
|
+
}
|
|
329
|
+
|
|
323
330
|
try {
|
|
324
331
|
const userContext = TUILogin.getContext();
|
|
325
332
|
if (userContext.userID == '' && props.SDKAppID !==0 && props.userID !=='' && props.userSig !==''){
|
|
@@ -333,6 +340,7 @@ try {
|
|
|
333
340
|
setShowReadStatus();
|
|
334
341
|
setShowTyping();
|
|
335
342
|
setEnableURLDetection();
|
|
343
|
+
setShowAllRobotWelcomeItems();
|
|
336
344
|
getTimeZoneAndCountry();
|
|
337
345
|
if (isNonEmptyObject(props.bottomQuickOrder)) {
|
|
338
346
|
showBottomQuickOrder.value = true;
|
|
@@ -521,8 +529,13 @@ function onDislike(messageInfo: Object) {
|
|
|
521
529
|
function onIsQueuingUpdate(data: {conversationID: string, value: number}) {
|
|
522
530
|
if (data && data.conversationID === currentConversationID.value) {
|
|
523
531
|
queueNumber.value = data.value;
|
|
532
|
+
hasLeftQueue.value = false;
|
|
524
533
|
}
|
|
525
534
|
}
|
|
535
|
+
|
|
536
|
+
function closeQueuePage() {
|
|
537
|
+
hasLeftQueue.value = true;
|
|
538
|
+
}
|
|
526
539
|
</script>
|
|
527
540
|
|
|
528
541
|
<style scoped lang="scss" src="./style/index.scss">
|
|
@@ -361,7 +361,7 @@ function handlePasteText(e: ClipboardEvent) {
|
|
|
361
361
|
const text = e.clipboardData?.getData('text/plain') || '';
|
|
362
362
|
// if paste html in pc, paste by tiptap editor default
|
|
363
363
|
// if paste text in pc or mobile, parse text to html to render emoji
|
|
364
|
-
if (!html) {
|
|
364
|
+
if (isH5 || !html) {
|
|
365
365
|
const renderArray = parseTextToRenderArray(text);
|
|
366
366
|
insertEditorContent(renderArray);
|
|
367
367
|
}
|
|
@@ -24,7 +24,7 @@ import ratingToolIcon from '../../../../assets/rating_tool_icon.svg'
|
|
|
24
24
|
import ratingToolIconH5 from '../../../../assets/rating_tool_icon_h5.svg';
|
|
25
25
|
import { getTo } from '../../../../utils/utils';
|
|
26
26
|
import { CUSTOM_MESSAGE_SRC } from '../../../../constant';
|
|
27
|
-
const { ref
|
|
27
|
+
const { ref } = vue;
|
|
28
28
|
|
|
29
29
|
const props = defineProps({
|
|
30
30
|
isH5ToolShow:{
|
|
@@ -208,7 +208,6 @@ import {
|
|
|
208
208
|
getBoundingClientRect,
|
|
209
209
|
getScrollInfo,
|
|
210
210
|
} from '@tencentcloud/universal-api';
|
|
211
|
-
import { throttle } from 'lodash';
|
|
212
211
|
import MessageText from './message-elements/message-text.vue';
|
|
213
212
|
import MessageImage from './message-elements/message-image-web.vue';
|
|
214
213
|
import MessageAudio from './message-elements/message-audio-web.vue';
|
|
@@ -237,6 +236,7 @@ import {
|
|
|
237
236
|
deepCopy,
|
|
238
237
|
isNonEmptyObject,
|
|
239
238
|
updateCustomStore,
|
|
239
|
+
throttle
|
|
240
240
|
} from '../../../utils/utils';
|
|
241
241
|
import { isMessageInvisible, isThinkingMessage, isThinkingMessageOverTime, JSONToObject, isTransferMessageWithoutDesc } from '../../../utils/index';
|
|
242
242
|
import { isCustomerConversation } from '../../../index';
|
|
@@ -820,13 +820,23 @@ function closeBottomQuickOrder() {
|
|
|
820
820
|
}
|
|
821
821
|
|
|
822
822
|
function sendBottomQuickOrder() {
|
|
823
|
+
if (!props.bottomQuickOrder) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const normalizedQuickOrder: QuickOrderModel = {
|
|
827
|
+
...props.bottomQuickOrder,
|
|
828
|
+
customField: (props.bottomQuickOrder.customField || []).map(f => ({
|
|
829
|
+
name: f.name ? String(f.name) : '',
|
|
830
|
+
value: f.value ? String(f.value) : '',
|
|
831
|
+
})),
|
|
832
|
+
}
|
|
823
833
|
TUIChatService.sendCustomMessage({
|
|
824
834
|
to: currentConversationID.value.replace('C2C', ''),
|
|
825
835
|
conversationType: 'C2C',
|
|
826
836
|
payload: {
|
|
827
837
|
data: JSON.stringify({
|
|
828
838
|
src: CUSTOM_MESSAGE_SRC.PRODUCT_CARD,
|
|
829
|
-
content:
|
|
839
|
+
content: normalizedQuickOrder,
|
|
830
840
|
customerServicePlugin: 0,
|
|
831
841
|
}),
|
|
832
842
|
},
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
</div>
|
|
13
13
|
<div
|
|
14
14
|
class="change-wrapper"
|
|
15
|
+
v-if="list.length > 3 && !showAllRobotWelcomeItems"
|
|
15
16
|
@click="changeBranchList()"
|
|
16
17
|
>
|
|
17
18
|
<Icon :src="iconRefresh" />
|
|
@@ -44,6 +45,7 @@ import Icon from './customer-icon.vue';
|
|
|
44
45
|
import iconQuestion from '../../../../../../assets/icon_question.png';
|
|
45
46
|
import iconRefresh from '../../../../../../assets/icon_refresh.png';
|
|
46
47
|
import { customerServicePayloadType } from '../../../../../../interface';
|
|
48
|
+
import state from '../../../../../../utils/state.js';
|
|
47
49
|
const { reactive, toRefs, computed } = vue;
|
|
48
50
|
|
|
49
51
|
interface Props {
|
|
@@ -68,13 +70,15 @@ export default {
|
|
|
68
70
|
},
|
|
69
71
|
emits: ['sendMessage'],
|
|
70
72
|
setup(props: Props, { emit }) {
|
|
73
|
+
const showAllRobotWelcomeItems = state.get('showAllRobotWelcomeItems');
|
|
74
|
+
const welcomeItemList = props.payload.content.items || [];
|
|
71
75
|
const data = reactive({
|
|
72
76
|
// title
|
|
73
|
-
title: props.payload
|
|
77
|
+
title: props.payload.content.title || '',
|
|
74
78
|
// all branch list
|
|
75
|
-
list:
|
|
79
|
+
list: welcomeItemList,
|
|
76
80
|
// current branch list
|
|
77
|
-
showList:
|
|
81
|
+
showList: showAllRobotWelcomeItems ? welcomeItemList : welcomeItemList.slice(0, 3),
|
|
78
82
|
// current page number
|
|
79
83
|
pageNumber: 1,
|
|
80
84
|
});
|
|
@@ -116,7 +120,8 @@ export default {
|
|
|
116
120
|
changeBranchList,
|
|
117
121
|
iconQuestion,
|
|
118
122
|
iconRefresh,
|
|
119
|
-
longestContent
|
|
123
|
+
longestContent,
|
|
124
|
+
showAllRobotWelcomeItems,
|
|
120
125
|
};
|
|
121
126
|
},
|
|
122
127
|
};
|
|
@@ -85,6 +85,9 @@ watch(() => props.payload, (newValue: string, oldValue: string) => {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const _payloadObject = JSONToObject(props.payload);
|
|
88
|
+
if (!_payloadObject.chunks) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
88
91
|
chunks.value = Array.isArray(_payloadObject.chunks) ? _payloadObject.chunks.join('') : _payloadObject.chunks;
|
|
89
92
|
isFinished.value = _payloadObject.isFinished === 1;
|
|
90
93
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import vue from '../../../../adapter-vue';
|
|
12
12
|
import Icon from '../../../common/Icon.vue';
|
|
13
13
|
import loading_message from '../../../../assets/loading_message.svg';
|
|
14
|
-
const { ref,
|
|
14
|
+
const { ref, onMounted, onUnmounted} = vue;
|
|
15
15
|
export default {
|
|
16
16
|
components:{
|
|
17
17
|
Icon
|
package/components/CustomerServiceChat/message-list/message-elements/simple-message-list/index.vue
CHANGED
|
@@ -158,7 +158,6 @@ import TUIChatEngine, {
|
|
|
158
158
|
TUITranslateService,
|
|
159
159
|
} from '@tencentcloud/chat-uikit-engine';
|
|
160
160
|
import addIcon from '../../../../../assets/back.svg';
|
|
161
|
-
import playIcon from '../../../../../assets/video-play.png';
|
|
162
161
|
import Icon from '../../../../common/Icon.vue';
|
|
163
162
|
import MessageContainer from './message-container.vue';
|
|
164
163
|
import MessageRecord from '../message-record/index.vue';
|
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="toolbar-button-container">
|
|
3
|
-
<template v-for="(item, index) in props.toolbarButtonList">
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
<div class="toolbar-button-container" ref="toolbarContainerRef">
|
|
3
|
+
<template v-for="(item, index) in props.toolbarButtonList" :key="index">
|
|
4
|
+
<div :class="['toolbar-item-wrapper', index >= visibleCount ? 'measure-offscreen' : '']" :data-index="index" ref="toolbarWrappersRef">
|
|
5
|
+
<ToolbarButtonHumanService v-if="item.presetId === TOOLBAR_BUTTON_TYPE.HUMAN_SERVICE && shouldRender(item) && !isInHumanService" :title="item.title" :icon="item.icon"/>
|
|
6
|
+
<ToolbarButtonServiceRating v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.SERVICE_RATING && shouldRender(item) && isInHumanService" :title="item.title" :icon="item.icon"/>
|
|
7
|
+
<ToolbarButtonEndHumanService v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.END_HUMAN_SERVICE && shouldRender(item) && ((item.displayFlag === 1 && canEndConversation) || isInHumanService)" :title="item.title" :icon="item.icon"/>
|
|
8
|
+
<div v-else-if="shouldRender(item) && !item.presetId" :key="index"
|
|
9
|
+
:class="['toolbar-button', isH5 ? 'toolbar-button-h5' : '']" @click="onClick(item, index)">
|
|
10
|
+
<Icon v-if="item.icon" class="toolbar-button-icon" :file="item.icon" width="14px" height="14x"/>
|
|
11
|
+
<div class="toolbar-button-text">
|
|
11
12
|
{{ item.title }}
|
|
13
|
+
</div>
|
|
12
14
|
</div>
|
|
13
15
|
</div>
|
|
14
16
|
</template>
|
|
17
|
+
<div v-if="hiddenItems.length > 0" class="toolbar-button overflow-button" ref="toolbarOverflowRef" @click="togglePopover">
|
|
18
|
+
<Icon :file="ToolMoreIcon" width="14px" height="14px"/>
|
|
19
|
+
</div>
|
|
20
|
+
<div :class="['overflow-popover', showPopover ? 'show-popover' : '' ]" ref="toolbarPopoverRef">
|
|
21
|
+
<div v-for="(item, index) in hiddenItems" :key="item.id || ('hidden-' + index)">
|
|
22
|
+
<ToolbarButtonHumanService class="overflow-popover-item" v-if="item.presetId === TOOLBAR_BUTTON_TYPE.HUMAN_SERVICE && shouldRender(item) && !isInHumanService" :title="item.title" :icon="item.icon" @closePopover="closePopover"/>
|
|
23
|
+
<ToolbarButtonServiceRating class="overflow-popover-item" v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.SERVICE_RATING && shouldRender(item) && isInHumanService" :title="item.title" :icon="item.icon" @closePopover="closePopover"/>
|
|
24
|
+
<ToolbarButtonEndHumanService class="overflow-popover-item" v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.END_HUMAN_SERVICE && shouldRender(item) && ((item.displayFlag === 1 && canEndConversation) || isInHumanService)" :title="item.title" :icon="item.icon" @closePopover="closePopover"/>
|
|
25
|
+
<div v-else-if="shouldRender(item)" class="overflow-popover-item" @click="onClick(item, visibleCount + index)">
|
|
26
|
+
<Icon v-if="item.icon" class="toolbar-button-icon" :file="item.icon" width="16px" height="16px"/>
|
|
27
|
+
<span class="toolbar-button-text" style="text-align: left;">{{ item.title }}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="measure-offscreen" ref="measureOverflowRef">···</div>
|
|
15
32
|
</div>
|
|
16
33
|
</template>
|
|
17
34
|
<script lang="ts" setup>
|
|
18
35
|
import vue from '../../../adapter-vue';
|
|
19
|
-
const { ref, onMounted, onUnmounted } = vue;
|
|
36
|
+
const { ref, onMounted, onUnmounted, computed } = vue;
|
|
20
37
|
import {
|
|
21
38
|
TUIChatService,
|
|
22
39
|
TUIStore,
|
|
@@ -27,10 +44,12 @@ import { isH5 } from '../../../utils/env';
|
|
|
27
44
|
import { ToolbarButtonModel } from '../../../interface';
|
|
28
45
|
import Icon from '../../common/Icon.vue';
|
|
29
46
|
import { TOOLBAR_BUTTON_TYPE } from '../../../constant';
|
|
30
|
-
import { isEnabledMessageReadReceiptGlobal, openSafeUrl, getTo, isNonEmptyObject, transferToTaskFlow, transferToHuman, debounce } from '../../../utils/utils';
|
|
47
|
+
import { isEnabledMessageReadReceiptGlobal, openSafeUrl, getTo, isNonEmptyObject, transferToTaskFlow, transferToHuman, debounce, leaveQueuing } from '../../../utils/utils';
|
|
31
48
|
import ToolbarButtonHumanService from './toolbar-button-human-service.vue';
|
|
32
49
|
import ToolbarButtonServiceRating from './toolbar-button-service-rating.vue';
|
|
33
50
|
import ToolbarButtonEndHumanService from './toolbar-button-end-human-service.vue';
|
|
51
|
+
import ToolMoreIcon from '../../../assets/tool_more.svg';
|
|
52
|
+
import Log from '../../../utils/logger';
|
|
34
53
|
interface IProps {
|
|
35
54
|
toolbarButtonList?: ToolbarButtonModel[] | undefined;
|
|
36
55
|
}
|
|
@@ -40,6 +59,21 @@ const props = withDefaults(defineProps<IProps>(), {});
|
|
|
40
59
|
const isInHumanService = ref(false);
|
|
41
60
|
const currentConversation = ref<IConversationModel>();
|
|
42
61
|
const canEndConversation = ref(false);
|
|
62
|
+
const queueNumber = ref(-1);
|
|
63
|
+
const visibleCount = ref(props.toolbarButtonList ? props.toolbarButtonList.length : 0);
|
|
64
|
+
const showPopover = ref(false);
|
|
65
|
+
|
|
66
|
+
const toolbarContainerRef = ref<HTMLElement | null>(null);
|
|
67
|
+
const toolbarWrappersRef = ref<HTMLElement[] | HTMLElement | null>(null);
|
|
68
|
+
const toolbarOverflowRef = ref<HTMLElement | null>(null);
|
|
69
|
+
const toolbarPopoverRef = ref<HTMLElement | null>(null);
|
|
70
|
+
const measureOverflowRef = ref<HTMLElement | null>(null);
|
|
71
|
+
|
|
72
|
+
const buttonWidthList = ref<number[]>([]);
|
|
73
|
+
const overflowButtonWidth = 38;
|
|
74
|
+
|
|
75
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
76
|
+
let mutationObserver: MutationObserver | null = null;
|
|
43
77
|
|
|
44
78
|
onMounted(() => {
|
|
45
79
|
TUIStore.watch(StoreName.CONV, {
|
|
@@ -47,8 +81,32 @@ onMounted(() => {
|
|
|
47
81
|
});
|
|
48
82
|
TUIStore.watch(StoreName.CUSTOM, {
|
|
49
83
|
isInHumanService: onInHumanServiceUpdate,
|
|
50
|
-
canEndConversation: onCanEndConversationUpdate
|
|
84
|
+
canEndConversation: onCanEndConversationUpdate,
|
|
85
|
+
isQueuing: onIsQueuingUpdate,
|
|
51
86
|
});
|
|
87
|
+
getHiddenList();
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
if (typeof (window as any).ResizeObserver !== 'undefined') {
|
|
91
|
+
resizeObserver = new (window as any).ResizeObserver(() => {
|
|
92
|
+
getHiddenList();
|
|
93
|
+
showPopover.value = false;
|
|
94
|
+
});
|
|
95
|
+
if (resizeObserver && toolbarContainerRef.value) {
|
|
96
|
+
resizeObserver.observe(toolbarContainerRef.value);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
window.addEventListener('resize', getHiddenList);
|
|
100
|
+
}
|
|
101
|
+
if (typeof MutationObserver !== 'undefined' && toolbarContainerRef.value) {
|
|
102
|
+
mutationObserver = new MutationObserver(() => getHiddenList());
|
|
103
|
+
mutationObserver.observe(toolbarContainerRef.value, { childList: true, subtree: true });
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
window.addEventListener('resize', getHiddenList);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
document.addEventListener('click', closePopoverOutside);
|
|
52
110
|
});
|
|
53
111
|
|
|
54
112
|
onUnmounted(() => {
|
|
@@ -57,8 +115,27 @@ onUnmounted(() => {
|
|
|
57
115
|
});
|
|
58
116
|
TUIStore.unwatch(StoreName.CUSTOM, {
|
|
59
117
|
isInHumanService: onInHumanServiceUpdate,
|
|
60
|
-
canEndConversation: onCanEndConversationUpdate
|
|
118
|
+
canEndConversation: onCanEndConversationUpdate,
|
|
119
|
+
isQueuing: onIsQueuingUpdate,
|
|
61
120
|
});
|
|
121
|
+
if (resizeObserver) {
|
|
122
|
+
try {
|
|
123
|
+
resizeObserver.disconnect();
|
|
124
|
+
} catch {
|
|
125
|
+
Log.w('ResizeObserver disconnect failed');
|
|
126
|
+
}
|
|
127
|
+
resizeObserver = null;
|
|
128
|
+
}
|
|
129
|
+
if (mutationObserver) {
|
|
130
|
+
try {
|
|
131
|
+
mutationObserver.disconnect();
|
|
132
|
+
} catch {
|
|
133
|
+
Log.w('MutationObserver disconnect failed');
|
|
134
|
+
}
|
|
135
|
+
mutationObserver = null;
|
|
136
|
+
}
|
|
137
|
+
window.removeEventListener('resize', getHiddenList);
|
|
138
|
+
document.removeEventListener('click', closePopoverOutside);
|
|
62
139
|
});
|
|
63
140
|
|
|
64
141
|
const onCurrentConversationUpdate = (conversation: IConversationModel) => {
|
|
@@ -68,15 +145,31 @@ const onCurrentConversationUpdate = (conversation: IConversationModel) => {
|
|
|
68
145
|
const onInHumanServiceUpdate = (data: {conversationID: string, value: boolean}) => {
|
|
69
146
|
if (data && data.conversationID === currentConversation.value.conversationID) {
|
|
70
147
|
isInHumanService.value = data.value;
|
|
148
|
+
if (!mutationObserver) {
|
|
149
|
+
getHiddenList();
|
|
150
|
+
}
|
|
71
151
|
}
|
|
72
152
|
};
|
|
73
153
|
|
|
74
154
|
const onCanEndConversationUpdate = (data: {conversationID: string, value: boolean}) => {
|
|
75
155
|
if (data && data.conversationID === currentConversation.value.conversationID) {
|
|
76
156
|
canEndConversation.value = data.value;
|
|
157
|
+
if (!mutationObserver) {
|
|
158
|
+
getHiddenList();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const onIsQueuingUpdate = (data: {conversationID: string, value: number}) => {
|
|
164
|
+
if (data && data.conversationID === currentConversation.value.conversationID) {
|
|
165
|
+
queueNumber.value = data.value;
|
|
77
166
|
}
|
|
78
167
|
}
|
|
79
168
|
|
|
169
|
+
const closePopover = () => {
|
|
170
|
+
showPopover.value = false;
|
|
171
|
+
}
|
|
172
|
+
|
|
80
173
|
const onClick = debounce((item:ToolbarButtonModel, index: number) => {
|
|
81
174
|
if (!currentConversation.value) {
|
|
82
175
|
return;
|
|
@@ -96,13 +189,23 @@ const onClick = debounce((item:ToolbarButtonModel, index: number) => {
|
|
|
96
189
|
transferToTaskFlow(getTo(currentConversation.value), item.content.taskFlowID, item.content.description);
|
|
97
190
|
} else if (item.type === 4 && isNonEmptyObject(item.content)) {
|
|
98
191
|
transferToHuman(getTo(currentConversation.value), item.content.groupID, item.content.specificMemberList, item.content.description);
|
|
192
|
+
} else if (item.type === 5 && queueNumber.value >= 0) {
|
|
193
|
+
leaveQueuing(getTo(currentConversation.value));
|
|
99
194
|
} else if (props.toolbarButtonList !== undefined && typeof props.toolbarButtonList[index].clickEvent === 'function') {
|
|
100
195
|
props.toolbarButtonList[index].clickEvent();
|
|
101
196
|
}
|
|
197
|
+
showPopover.value = false;
|
|
102
198
|
}, 300);
|
|
103
199
|
|
|
104
200
|
const shouldRender = (item: ToolbarButtonModel) => {
|
|
105
201
|
if (item.isEnabled === 1) {
|
|
202
|
+
if (item.type === 5) {
|
|
203
|
+
// 转人工排队人数<0则不展示
|
|
204
|
+
if (queueNumber.value < 0) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
106
209
|
return true;
|
|
107
210
|
} else if (item.isEnabled === 0) {
|
|
108
211
|
return false;
|
|
@@ -112,6 +215,113 @@ const shouldRender = (item: ToolbarButtonModel) => {
|
|
|
112
215
|
return false;
|
|
113
216
|
}
|
|
114
217
|
|
|
218
|
+
const togglePopover = () => {
|
|
219
|
+
showPopover.value = !showPopover.value;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const hiddenItems = computed(() => {
|
|
223
|
+
if (props.toolbarButtonList) {
|
|
224
|
+
return props.toolbarButtonList.slice(visibleCount.value) || [];
|
|
225
|
+
}
|
|
226
|
+
return [];
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const getVisibleCount = () => {
|
|
230
|
+
const list = props.toolbarButtonList || [];
|
|
231
|
+
const buttonCount = list.length;
|
|
232
|
+
const container = toolbarContainerRef.value;
|
|
233
|
+
if (!container) {
|
|
234
|
+
visibleCount.value = buttonCount;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const requiredRightGap = 11;
|
|
238
|
+
const available = container.clientWidth - requiredRightGap;
|
|
239
|
+
let sum = 0;
|
|
240
|
+
let visible = 0;
|
|
241
|
+
for (let i = 0; i < buttonCount; i++) {
|
|
242
|
+
const w = buttonWidthList.value[i] || 0;
|
|
243
|
+
if (i < buttonCount - 1) {
|
|
244
|
+
if (sum + w + overflowButtonWidth <= available) {
|
|
245
|
+
sum += w;
|
|
246
|
+
visible++;
|
|
247
|
+
} else {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
if (sum + w <= available) {
|
|
252
|
+
sum += w;
|
|
253
|
+
visible++;
|
|
254
|
+
} else {
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (visible === 0 && buttonCount > 0) {
|
|
260
|
+
visibleCount.value = 0;
|
|
261
|
+
} else {
|
|
262
|
+
visibleCount.value = visible;
|
|
263
|
+
}
|
|
264
|
+
updatePopoverPosition();
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const getHiddenList = debounce(() => {
|
|
268
|
+
const container = toolbarContainerRef.value;
|
|
269
|
+
if (!container) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const wrappersRaw = toolbarWrappersRef.value;
|
|
274
|
+
const buttonList = Array.isArray(wrappersRaw) ? wrappersRaw : (wrappersRaw ? [wrappersRaw] : []);
|
|
275
|
+
const list = props.toolbarButtonList || [];
|
|
276
|
+
const newWidths: number[] = [];
|
|
277
|
+
|
|
278
|
+
for (let i = 0; i < list.length; i++) {
|
|
279
|
+
const el = buttonList[i] as HTMLElement | undefined;
|
|
280
|
+
if (el && typeof el.getBoundingClientRect === 'function') {
|
|
281
|
+
const rect = el.getBoundingClientRect();
|
|
282
|
+
const style = window.getComputedStyle(el);
|
|
283
|
+
const ml = parseFloat(style.marginLeft || '0');
|
|
284
|
+
const mr = parseFloat(style.marginRight || '0');
|
|
285
|
+
newWidths[i] = rect.width + ml + mr;
|
|
286
|
+
} else {
|
|
287
|
+
newWidths[i] = 0;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
buttonWidthList.value = newWidths;
|
|
291
|
+
getVisibleCount();
|
|
292
|
+
}, 100);
|
|
293
|
+
|
|
294
|
+
const closePopoverOutside = (e: MouseEvent) => {
|
|
295
|
+
const pop = toolbarPopoverRef.value;
|
|
296
|
+
const btn = toolbarOverflowRef.value;
|
|
297
|
+
if (!pop) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const target = e.target as Node | null;
|
|
301
|
+
if (!target) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (pop.contains(target) || (btn && btn.contains(target))) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
showPopover.value = false;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const updatePopoverPosition = () => {
|
|
311
|
+
const pop = toolbarPopoverRef.value;
|
|
312
|
+
const btn = toolbarOverflowRef.value;
|
|
313
|
+
if (!btn) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const btnRect = btn.getBoundingClientRect();
|
|
317
|
+
if (pop) {
|
|
318
|
+
const left = btnRect.right - pop.offsetWidth;
|
|
319
|
+
const top = btnRect.top - pop.offsetHeight - 6;
|
|
320
|
+
pop.style.left = `${left}px`;
|
|
321
|
+
pop.style.top = `${top}px`;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
115
325
|
</script>
|
|
116
326
|
<style>
|
|
117
327
|
.toolbar-button-container {
|
|
@@ -119,13 +329,8 @@ const shouldRender = (item: ToolbarButtonModel) => {
|
|
|
119
329
|
flex-direction: row !important;
|
|
120
330
|
margin: 5px !important;
|
|
121
331
|
align-items: center ;
|
|
122
|
-
overflow-x:
|
|
123
|
-
scrollbar-width: none; /* Firefox 隐藏滚动条 */
|
|
124
|
-
-ms-overflow-style: none; /* IE/Edge 隐藏滚动条 */
|
|
332
|
+
overflow-x: hidden;
|
|
125
333
|
min-height: 28px;
|
|
126
|
-
&::-webkit-scrollbar {
|
|
127
|
-
display: none; /* Chrome 隐藏滚动条 */
|
|
128
|
-
}
|
|
129
334
|
}
|
|
130
335
|
|
|
131
336
|
.toolbar-button {
|
|
@@ -138,10 +343,11 @@ const shouldRender = (item: ToolbarButtonModel) => {
|
|
|
138
343
|
margin-left: 10px;
|
|
139
344
|
white-space: nowrap;
|
|
140
345
|
user-select: none;
|
|
346
|
+
position: relative;
|
|
141
347
|
}
|
|
142
348
|
|
|
143
349
|
.toolbar-button:first-child {
|
|
144
|
-
margin-left:
|
|
350
|
+
margin-left: 11px;
|
|
145
351
|
}
|
|
146
352
|
|
|
147
353
|
.toolbar-button-h5 {
|
|
@@ -161,4 +367,63 @@ const shouldRender = (item: ToolbarButtonModel) => {
|
|
|
161
367
|
min-width: 25px;
|
|
162
368
|
text-align: center;
|
|
163
369
|
}
|
|
370
|
+
.overflow-button {
|
|
371
|
+
position: relative;
|
|
372
|
+
user-select: none;
|
|
373
|
+
background-color: #fff;
|
|
374
|
+
padding: 5px 14px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.overflow-popover {
|
|
378
|
+
position: fixed;
|
|
379
|
+
z-index: 9999;
|
|
380
|
+
background: #fff;
|
|
381
|
+
border: 1px solid #f9fafc;
|
|
382
|
+
box-shadow: 0 1px 6px 0 rgba(0,0,0,0.06), 0 6px 16px 0 rgba(0,0,0,0.06);
|
|
383
|
+
border-radius: 8px;
|
|
384
|
+
padding: 6px;
|
|
385
|
+
min-width: 120px;
|
|
386
|
+
white-space: nowrap;
|
|
387
|
+
transform-origin: right bottom;
|
|
388
|
+
visibility: hidden;
|
|
389
|
+
max-height: 200px;
|
|
390
|
+
overflow-y: auto;
|
|
391
|
+
max-width: 100px;
|
|
392
|
+
min-width: 25px;
|
|
393
|
+
overflow-x: hidden;
|
|
394
|
+
scrollbar-width: none;
|
|
395
|
+
-ms-overflow-style: none;
|
|
396
|
+
&::-webkit-scrollbar {
|
|
397
|
+
display: none;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
.show-popover {
|
|
401
|
+
display: block;
|
|
402
|
+
visibility: visible;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.overflow-popover-item {
|
|
406
|
+
display: flex;
|
|
407
|
+
align-items: center;
|
|
408
|
+
padding: 8px;
|
|
409
|
+
cursor: pointer;
|
|
410
|
+
white-space: nowrap;
|
|
411
|
+
font-size: 12px;
|
|
412
|
+
border-radius: 4px;
|
|
413
|
+
border: none;
|
|
414
|
+
margin: 0 !important;
|
|
415
|
+
box-shadow: none;
|
|
416
|
+
}
|
|
417
|
+
.overflow-popover-item:hover {
|
|
418
|
+
background: #f9fafc;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.measure-offscreen {
|
|
422
|
+
position: absolute;
|
|
423
|
+
visibility: hidden;
|
|
424
|
+
white-space: nowrap;
|
|
425
|
+
padding: 5px 14px;
|
|
426
|
+
border-radius: 20px;
|
|
427
|
+
border: 1px solid #E7EAEF;
|
|
428
|
+
}
|
|
164
429
|
</style>
|
package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-end-human-service.vue
CHANGED
|
@@ -26,6 +26,8 @@ const props = withDefaults(defineProps<IProps>(), {
|
|
|
26
26
|
icon: ''
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
+
const emits = defineEmits(['closePopover']);
|
|
30
|
+
|
|
29
31
|
onMounted(() => {
|
|
30
32
|
TUIStore.watch(StoreName.CONV, {
|
|
31
33
|
currentConversation: onCurrentConversationUpdate,
|
|
@@ -57,7 +59,10 @@ const onClick = () => {
|
|
|
57
59
|
}),
|
|
58
60
|
},
|
|
59
61
|
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
|
60
|
-
},{ onlineUserOnly:true })
|
|
62
|
+
},{ onlineUserOnly:true })
|
|
63
|
+
.finally(() => {
|
|
64
|
+
emits('closePopover');
|
|
65
|
+
});
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
</script>
|
package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-human-service.vue
CHANGED
|
@@ -24,6 +24,8 @@ const props = withDefaults(defineProps<IProps>(), {
|
|
|
24
24
|
icon: ''
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
+
const emits = defineEmits(['closePopover']);
|
|
28
|
+
|
|
27
29
|
onMounted(() => {
|
|
28
30
|
TUIStore.watch(StoreName.CONV, {
|
|
29
31
|
currentConversation: onCurrentConversationUpdate,
|
|
@@ -51,6 +53,9 @@ const onClick = debounce(() => {
|
|
|
51
53
|
text: TUITranslateService.t('AIDesk.转人工服务')
|
|
52
54
|
},
|
|
53
55
|
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
|
56
|
+
})
|
|
57
|
+
.finally(() => {
|
|
58
|
+
emits('closePopover');
|
|
54
59
|
});
|
|
55
60
|
}, 300);
|
|
56
61
|
|
package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-service-rating.vue
CHANGED
|
@@ -25,6 +25,8 @@ const props = withDefaults(defineProps<IProps>(), {
|
|
|
25
25
|
icon: ''
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
+
const emits = defineEmits(['closePopover']);
|
|
29
|
+
|
|
28
30
|
onMounted(() => {
|
|
29
31
|
TUIStore.watch(StoreName.CONV, {
|
|
30
32
|
currentConversation: onCurrentConversationUpdate,
|
|
@@ -55,7 +57,10 @@ const onClick = debounce(() => {
|
|
|
55
57
|
}),
|
|
56
58
|
},
|
|
57
59
|
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
|
58
|
-
},{ onlineUserOnly: true })
|
|
60
|
+
},{ onlineUserOnly: true })
|
|
61
|
+
.finally(() => {
|
|
62
|
+
emits('closePopover');
|
|
63
|
+
});
|
|
59
64
|
}, 300);
|
|
60
65
|
|
|
61
66
|
</script>
|
|
@@ -41,12 +41,12 @@ interface IEmits {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const defaultAvatarUrl = ref(
|
|
44
|
-
'https://web.sdk.qcloud.com/
|
|
44
|
+
'https://web.sdk.qcloud.com/im/desk/assets/user_default_avatar.png',
|
|
45
45
|
);
|
|
46
46
|
const emits = defineEmits<IEmits>();
|
|
47
47
|
const props = withDefaults(defineProps<IProps>(), {
|
|
48
48
|
// uniapp vue2 does not support constants in defineProps
|
|
49
|
-
url: 'https://web.sdk.qcloud.com/
|
|
49
|
+
url: 'https://web.sdk.qcloud.com/im/desk/assets/user_default_avatar.png',
|
|
50
50
|
size: '36px',
|
|
51
51
|
borderRadius: '50%',
|
|
52
52
|
useSkeletonAnimation: false,
|
package/constant.ts
CHANGED
package/locales/en/aidesk.ts
CHANGED
|
@@ -38,6 +38,6 @@ const AIDesk = {
|
|
|
38
38
|
"排队等待中": "In Queue",
|
|
39
39
|
"排队等待话术": "We apologize for the wait. All agents are currently assisting other customers. You will be connected shortly. Thank you for your patience.",
|
|
40
40
|
"当前前方排队人数": "Queue Position",
|
|
41
|
-
"结束排队": "
|
|
41
|
+
"结束排队": "Leave Queue"
|
|
42
42
|
}
|
|
43
43
|
export default AIDesk;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tencentcloud/ai-desk-customer-vue",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"description": "Vue2/Vue3 UIKit for AI Desk",
|
|
5
5
|
"main": "index",
|
|
6
6
|
"keywords": [
|
|
@@ -31,11 +31,9 @@
|
|
|
31
31
|
"@tiptap/extension-text": "2.0.0-beta.220",
|
|
32
32
|
"@tiptap/pm": "2.0.0-beta.220",
|
|
33
33
|
"@tiptap/suggestion": "2.0.0-beta.220",
|
|
34
|
-
"@types/lodash": "^4.14.202",
|
|
35
34
|
"countries-and-timezones": "^3.8.0",
|
|
36
35
|
"dayjs": "^1.11.10",
|
|
37
36
|
"js-audio-recorder": "^1.0.7",
|
|
38
|
-
"lodash": "^4.17.21",
|
|
39
37
|
"marked": "^6.0.0",
|
|
40
38
|
"mp-html": "^2.5.0",
|
|
41
39
|
"vue-clipboard3": "2.0.0"
|
package/server.ts
CHANGED
|
@@ -17,9 +17,8 @@ import TUIChatEngine, {
|
|
|
17
17
|
import Log from './utils/logger';
|
|
18
18
|
import { version } from './package.json'
|
|
19
19
|
import { Toast, TOAST_TYPE } from "./components/common/Toast/index-web";
|
|
20
|
-
import { switchReadStatus, transferToHuman, transferToTaskFlow, validateUserID,
|
|
20
|
+
import { switchReadStatus, transferToHuman, transferToTaskFlow, validateUserID, isNonEmptyObject } from "./utils/utils";
|
|
21
21
|
import state from "./utils/state";
|
|
22
|
-
import { USER_DEFAULT_AVATAR } from "./constant";
|
|
23
22
|
import { vueVersion } from "./adapter-vue-web";
|
|
24
23
|
import { ITransferToHumanModel, ITransferToTaskFlowModel } from './interface';
|
|
25
24
|
|
|
@@ -30,6 +29,7 @@ interface IInitWithProfile {
|
|
|
30
29
|
nickName?: string,
|
|
31
30
|
avatar?: string,
|
|
32
31
|
customerServiceID?: string,
|
|
32
|
+
clientCustomData?: string,
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
interface IProfile {
|
|
@@ -46,6 +46,7 @@ export default class TUICustomerServer {
|
|
|
46
46
|
private myProfile: IProfile;
|
|
47
47
|
private paramsForActiveAgain: any;
|
|
48
48
|
private loginFailToasts: any[];
|
|
49
|
+
private clientCustomData: string;
|
|
49
50
|
constructor() {
|
|
50
51
|
TUICore.registerService(TUIConstants.TUICustomerServicePlugin.SERVICE.NAME, this);
|
|
51
52
|
TUICore.registerExtension(TUIConstants.TUIContact.EXTENSION.CONTACT_LIST.EXT_ID, this);
|
|
@@ -54,7 +55,8 @@ export default class TUICustomerServer {
|
|
|
54
55
|
this.loginFailToasts = [];
|
|
55
56
|
this.isLoggedIn = false;
|
|
56
57
|
this.loggedInUserID = '';
|
|
57
|
-
this.myProfile = {
|
|
58
|
+
this.myProfile = {};
|
|
59
|
+
this.clientCustomData = '';
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
static getInstance(): TUICustomerServer {
|
|
@@ -139,19 +141,15 @@ export default class TUICustomerServer {
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
public async initWithProfile(options: IInitWithProfile) {
|
|
142
|
-
const { SDKAppID, userID, userSig, nickName, avatar, customerServiceID } = options;
|
|
144
|
+
const { SDKAppID, userID, userSig, nickName, avatar, customerServiceID, clientCustomData } = options;
|
|
143
145
|
Log.l(`TUICustomerServer.initWithProfile version:${version}`);
|
|
144
146
|
if (nickName) {
|
|
145
147
|
// chat 个人资料的昵称是 nick
|
|
146
148
|
this.myProfile.nick = nickName;
|
|
147
|
-
} else {
|
|
148
|
-
this.myProfile.nick = '';
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (avatar) {
|
|
152
152
|
this.myProfile.avatar = avatar;
|
|
153
|
-
} else {
|
|
154
|
-
this.myProfile.avatar = USER_DEFAULT_AVATAR;
|
|
155
153
|
}
|
|
156
154
|
|
|
157
155
|
if (customerServiceID) {
|
|
@@ -160,6 +158,12 @@ export default class TUICustomerServer {
|
|
|
160
158
|
this.customerServiceIDList.push(this.currentCustomerServiceID);
|
|
161
159
|
}
|
|
162
160
|
}
|
|
161
|
+
|
|
162
|
+
if (clientCustomData) {
|
|
163
|
+
this.clientCustomData = clientCustomData;
|
|
164
|
+
} else {
|
|
165
|
+
this.clientCustomData = '';
|
|
166
|
+
}
|
|
163
167
|
return this.init(SDKAppID, userID, userSig);
|
|
164
168
|
}
|
|
165
169
|
|
|
@@ -281,7 +285,7 @@ export default class TUICustomerServer {
|
|
|
281
285
|
|
|
282
286
|
// 激活会话服务流
|
|
283
287
|
private activeServiceFlow(params: any) {
|
|
284
|
-
Log.i(`TUICustomerServer.activeServiceFlow params: language:${params.robotLang} country:${params.country} timezone:${params.timezone}`);
|
|
288
|
+
Log.i(`TUICustomerServer.activeServiceFlow params: language:${params.robotLang} country:${params.country} timezone:${params.timezone} clientCustomData:${this.clientCustomData}`);
|
|
285
289
|
this.paramsForActiveAgain = { ...params };
|
|
286
290
|
TUIChatService.sendCustomMessage({
|
|
287
291
|
to: params.conversationID.slice(3),
|
|
@@ -294,6 +298,7 @@ export default class TUICustomerServer {
|
|
|
294
298
|
language: params.robotLang,
|
|
295
299
|
country: params.country,
|
|
296
300
|
timezone: params.timezone,
|
|
301
|
+
clientCustomData: this.clientCustomData,
|
|
297
302
|
}
|
|
298
303
|
}),
|
|
299
304
|
},
|
package/utils/utils.ts
CHANGED
|
@@ -376,4 +376,46 @@ export function debounce(func, delay) {
|
|
|
376
376
|
func.apply(context, args);
|
|
377
377
|
}, delay);
|
|
378
378
|
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function leaveQueuing(conversationID: string) {
|
|
382
|
+
TUIChatService.sendCustomMessage({
|
|
383
|
+
to: conversationID,
|
|
384
|
+
conversationType: TUIChatEngine.TYPES.CONV_C2C,
|
|
385
|
+
payload: {
|
|
386
|
+
data: JSON.stringify({
|
|
387
|
+
customerServicePlugin: 0,
|
|
388
|
+
src: CUSTOM_MESSAGE_SRC.USER_END_CONVERSATION,
|
|
389
|
+
}),
|
|
390
|
+
},
|
|
391
|
+
needReadReceipt: isEnabledMessageReadReceiptGlobal(),
|
|
392
|
+
},{ onlineUserOnly: true });
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export function throttle(
|
|
396
|
+
func,
|
|
397
|
+
wait = 200,
|
|
398
|
+
options?: { leading?: boolean;}
|
|
399
|
+
) {
|
|
400
|
+
let last = 0;
|
|
401
|
+
let timeout;
|
|
402
|
+
const leading = options?.leading !== undefined ? options.leading : true;
|
|
403
|
+
|
|
404
|
+
return function (this: any, ...args: any[]) {
|
|
405
|
+
const now = Date.now();
|
|
406
|
+
if (!last && !leading) {
|
|
407
|
+
last = now;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const remaining = wait - (now - last);
|
|
411
|
+
|
|
412
|
+
if (remaining <= 0) {
|
|
413
|
+
if (timeout) {
|
|
414
|
+
clearTimeout(timeout);
|
|
415
|
+
timeout = null;
|
|
416
|
+
}
|
|
417
|
+
last = now;
|
|
418
|
+
func.apply(this, args);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
379
421
|
}
|
package/assets/video-play.png
DELETED
|
Binary file
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="dialog-video">
|
|
3
|
-
<video
|
|
4
|
-
v-if="isShow"
|
|
5
|
-
id="videoEle"
|
|
6
|
-
class="video-box"
|
|
7
|
-
:src="videoData"
|
|
8
|
-
controls
|
|
9
|
-
autoplay
|
|
10
|
-
/>
|
|
11
|
-
</div>
|
|
12
|
-
</template>
|
|
13
|
-
|
|
14
|
-
<script lang="ts" setup>
|
|
15
|
-
import vue from '../../TencentCloudCustomer/tui-customer-service-plugin-test/adapter-vue';
|
|
16
|
-
import { TUIGlobal } from '@tencentcloud/universal-api';
|
|
17
|
-
import { onLoad, onReady } from '@dcloudio/uni-app';
|
|
18
|
-
const { ref } = vue;
|
|
19
|
-
|
|
20
|
-
const videoData = ref();
|
|
21
|
-
const isShow = ref(false);
|
|
22
|
-
const videoContext = ref();
|
|
23
|
-
onLoad((option: any) => {
|
|
24
|
-
const decodedUrl = decodeURIComponent(option?.videoUrl);
|
|
25
|
-
videoData.value = decodedUrl;
|
|
26
|
-
isShow.value = true;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
onReady(() => {
|
|
30
|
-
isShow.value = true;
|
|
31
|
-
videoContext.value = TUIGlobal.createVideoContext('videoEle');
|
|
32
|
-
});
|
|
33
|
-
</script>
|
|
34
|
-
<style lang="scss" scoped>
|
|
35
|
-
.dialog-video {
|
|
36
|
-
position: fixed;
|
|
37
|
-
z-index: 999;
|
|
38
|
-
width: 100vw;
|
|
39
|
-
height: 100vh;
|
|
40
|
-
background: rgba(#000, 0.6);
|
|
41
|
-
top: 0;
|
|
42
|
-
left: 0;
|
|
43
|
-
right: 0;
|
|
44
|
-
bottom: 0;
|
|
45
|
-
display: flex;
|
|
46
|
-
justify-content: center;
|
|
47
|
-
align-items: center;
|
|
48
|
-
|
|
49
|
-
.video-box {
|
|
50
|
-
position: absolute;
|
|
51
|
-
width: 100vw;
|
|
52
|
-
height: 100vh;
|
|
53
|
-
top: 0;
|
|
54
|
-
left: 0;
|
|
55
|
-
right: 0;
|
|
56
|
-
bottom: 0;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
</style>
|