@tencentcloud/ai-desk-customer-vue 1.6.3 → 1.6.6
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 +19 -0
- package/assets/tool_more.svg +5 -0
- package/components/CustomerServiceChat/feedback-modal/index.vue +20 -8
- package/components/CustomerServiceChat/index-web.vue +7 -0
- package/components/CustomerServiceChat/message-input/index-web.vue +3 -4
- package/components/CustomerServiceChat/message-input/message-input-editor-web.vue +3 -3
- package/components/CustomerServiceChat/message-list/index-web.vue +11 -1
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-desk.vue +9 -1
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-multi-branch/branch-pc.vue +4 -1
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-robot-welcome.vue +9 -4
- 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/package.json +1 -1
- package/server.ts +2 -7
- package/utils/utils.ts +15 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 1.6.6 @2025.12.22
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
- 新增参数 showAllRobotWelcomeItems,支持一页展示欢迎卡片全部内容
|
|
5
|
+
- 工具栏快捷按钮支持设置【结束排队】。
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- 底部快捷订单的自定义数据类型不是 string 导致的未被机器人正确识别的问题。
|
|
9
|
+
- 用户昵称和头像可能被覆盖的问题。
|
|
10
|
+
- 工具栏快捷按钮超出屏幕时折叠超出按钮
|
|
11
|
+
|
|
12
|
+
## 1.6.4 @2025.11.25
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
- 支持区分用户端上行消息的来源(用户输入或用户点击分支选项)
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- vue2 已知问题
|
|
19
|
+
|
|
1
20
|
## 1.6.3 @2025.11.13
|
|
2
21
|
|
|
3
22
|
### 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>
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
{{ TUITranslateService.t("AIDesk.感谢您的反馈,我们会持续优化改进") }}
|
|
9
9
|
</span>
|
|
10
10
|
</div>
|
|
11
|
-
<
|
|
11
|
+
<div @click="closeFeedbackModal">
|
|
12
|
+
<Icon :file="DialogCloseIcon" width="20px" height="20px"/>
|
|
13
|
+
</div>
|
|
12
14
|
</div>
|
|
13
15
|
|
|
14
16
|
<div class="dialog-select-container">
|
|
@@ -86,6 +88,7 @@
|
|
|
86
88
|
</template>
|
|
87
89
|
<script lang="ts">
|
|
88
90
|
import vue from '../../../adapter-vue';
|
|
91
|
+
import { vueVersion } from '../../../adapter-vue-web';
|
|
89
92
|
import Icon from '../../common/Icon.vue';
|
|
90
93
|
import { StoreName, TUIStore, TUITranslateService, TUIChatService } from '@tencentcloud/chat-uikit-engine';
|
|
91
94
|
import GreenCheck from '../../../assets/green_check.svg';
|
|
@@ -178,7 +181,20 @@ export default {
|
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
const dialogButtonClick = (item) => {
|
|
181
|
-
|
|
184
|
+
if (vueVersion === 3) {
|
|
185
|
+
// vue 3
|
|
186
|
+
item.isSelected = !item.isSelected;
|
|
187
|
+
} else {
|
|
188
|
+
// vue 2.6 or vue 2.7
|
|
189
|
+
const index = feedbackButtonList.value.findIndex(i => i.id === item.id);
|
|
190
|
+
if (index === -1) return;
|
|
191
|
+
const newList = feedbackButtonList.value.slice();
|
|
192
|
+
newList[index] = {
|
|
193
|
+
...newList[index],
|
|
194
|
+
isSelected: !newList[index].isSelected
|
|
195
|
+
};
|
|
196
|
+
feedbackButtonList.value = newList;
|
|
197
|
+
}
|
|
182
198
|
};
|
|
183
199
|
|
|
184
200
|
const isSubmitEnabled = computed(() => {
|
|
@@ -212,12 +228,6 @@ export default {
|
|
|
212
228
|
const onDislike = (messageInfo) => {
|
|
213
229
|
_messageInfo = messageInfo;
|
|
214
230
|
};
|
|
215
|
-
|
|
216
|
-
expose({
|
|
217
|
-
onLike,
|
|
218
|
-
onDislike,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
231
|
return {
|
|
222
232
|
isPC,
|
|
223
233
|
GreenCheck,
|
|
@@ -230,6 +240,8 @@ export default {
|
|
|
230
240
|
submit,
|
|
231
241
|
isSubmitEnabled,
|
|
232
242
|
feedbackButtonListFromStore,
|
|
243
|
+
onLike,
|
|
244
|
+
onDislike,
|
|
233
245
|
};
|
|
234
246
|
}
|
|
235
247
|
}
|
|
@@ -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']);
|
|
@@ -204,6 +205,7 @@ const props = withDefaults(defineProps<IProps>(), {
|
|
|
204
205
|
enableURLDetection: 0,
|
|
205
206
|
enableSendingAudio: 0,
|
|
206
207
|
showQueuePage: 0,
|
|
208
|
+
showAllRobotWelcomeItems: 0,
|
|
207
209
|
});
|
|
208
210
|
|
|
209
211
|
const loginCustomerUIKit = () => {
|
|
@@ -320,6 +322,10 @@ const setEnableURLDetection = () => {
|
|
|
320
322
|
state.set('enableURLDetection', props.enableURLDetection);
|
|
321
323
|
}
|
|
322
324
|
|
|
325
|
+
const setShowAllRobotWelcomeItems = () => {
|
|
326
|
+
state.set('showAllRobotWelcomeItems', props.showAllRobotWelcomeItems);
|
|
327
|
+
}
|
|
328
|
+
|
|
323
329
|
try {
|
|
324
330
|
const userContext = TUILogin.getContext();
|
|
325
331
|
if (userContext.userID == '' && props.SDKAppID !==0 && props.userID !=='' && props.userSig !==''){
|
|
@@ -333,6 +339,7 @@ try {
|
|
|
333
339
|
setShowReadStatus();
|
|
334
340
|
setShowTyping();
|
|
335
341
|
setEnableURLDetection();
|
|
342
|
+
setShowAllRobotWelcomeItems();
|
|
336
343
|
getTimeZoneAndCountry();
|
|
337
344
|
if (isNonEmptyObject(props.bottomQuickOrder)) {
|
|
338
345
|
showBottomQuickOrder.value = true;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:enableInput="props.enableInput"
|
|
21
21
|
:enableTyping="props.enableTyping"
|
|
22
22
|
:enableDragUpload="props.enableDragUpload"
|
|
23
|
-
:
|
|
23
|
+
:isInAudioModeFromProps="isInAudioMode"
|
|
24
24
|
:shouldShowToolbar="shouldShowToolbar"
|
|
25
25
|
:shouldShowEmoji="shouldShowEmoji"
|
|
26
26
|
:shouldShowAudio="shouldShowAudio"
|
|
@@ -60,7 +60,7 @@ import MessageInputButton from './message-input-button.vue';
|
|
|
60
60
|
import MessageInputQuote from './message-input-quote/index.vue';
|
|
61
61
|
import { sendMessages, sendTyping } from '../utils/sendMessage';
|
|
62
62
|
import { transformTextWithEmojiNamesToKeys } from '../emoji-config';
|
|
63
|
-
import { isPC,isH5 } from '../../../utils/env';
|
|
63
|
+
import { isPC, isH5 } from '../../../utils/env';
|
|
64
64
|
import Icon from '../../common/Icon.vue';
|
|
65
65
|
import emojiIcon from '../../../assets/emoji.png';
|
|
66
66
|
import toolIcon from '../../../assets/more_tools.png';
|
|
@@ -68,9 +68,8 @@ import sendButtonIcon from '../../../assets/send_button_h5.svg';
|
|
|
68
68
|
import audioIcon from '../../../assets/audio-blue.svg';
|
|
69
69
|
import keyboardIcon from '../../../assets/keyboard-icon.svg';
|
|
70
70
|
import { INPUT_TOOLBAR_TYPE } from '../../../constant';
|
|
71
|
-
import { onUnmounted } from 'vue';
|
|
72
71
|
import Log from '../../../utils/logger';
|
|
73
|
-
const { ref, onMounted, onBeforeUnmount, computed } = vue;
|
|
72
|
+
const { ref, onMounted, onBeforeUnmount, computed, onUnmounted } = vue;
|
|
74
73
|
|
|
75
74
|
const props = defineProps({
|
|
76
75
|
isMuted: {
|
|
@@ -116,7 +116,7 @@ const props = defineProps({
|
|
|
116
116
|
type: Boolean,
|
|
117
117
|
default: true,
|
|
118
118
|
},
|
|
119
|
-
|
|
119
|
+
isInAudioModeFromProps:{
|
|
120
120
|
type: Boolean,
|
|
121
121
|
default: false,
|
|
122
122
|
},
|
|
@@ -146,7 +146,7 @@ let editor: Editor | null = null;
|
|
|
146
146
|
const fileMap = new Map<string, any>();
|
|
147
147
|
const recorder = ref();
|
|
148
148
|
const recordTime = ref(0);
|
|
149
|
-
const isInAudioMode = ref(props.
|
|
149
|
+
const isInAudioMode = ref(props.isInAudioModeFromProps);
|
|
150
150
|
const isRecording = ref(false);
|
|
151
151
|
let startRecordY = 0;
|
|
152
152
|
const recordCancel = ref(false);
|
|
@@ -194,7 +194,7 @@ function focusEditor() {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
watch(
|
|
197
|
-
() => [props.
|
|
197
|
+
() => [props.isInAudioModeFromProps],
|
|
198
198
|
(newValue) => {
|
|
199
199
|
isInAudioMode.value = newValue[0];
|
|
200
200
|
}
|
|
@@ -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
|
},
|
|
@@ -130,9 +130,17 @@ export default {
|
|
|
130
130
|
return props.message && JSONToObject(props.message?.payload?.data);
|
|
131
131
|
});
|
|
132
132
|
const sendTextMessage = async (payload: TextMessagePayload, cloudCustomData?: string) => {
|
|
133
|
+
let cloudCustomDataFinal = cloudCustomData || '';
|
|
134
|
+
if (!cloudCustomDataFinal) {
|
|
135
|
+
cloudCustomDataFinal = JSON.stringify({
|
|
136
|
+
deskExtInfo: {
|
|
137
|
+
isPresetContent : 1
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
133
141
|
await TUIChatService.sendTextMessage({
|
|
134
142
|
payload,
|
|
135
|
-
cloudCustomData:
|
|
143
|
+
cloudCustomData: cloudCustomDataFinal,
|
|
136
144
|
needReadReceipt: isEnabledMessageReadReceiptGlobal()
|
|
137
145
|
});
|
|
138
146
|
emit('messageSent');
|
|
@@ -62,7 +62,10 @@ export default {
|
|
|
62
62
|
taskID: props.payload.taskInfo?.taskID,
|
|
63
63
|
nodeID: props.payload.taskInfo?.nodeID,
|
|
64
64
|
env: props.payload.taskInfo?.env,
|
|
65
|
-
}
|
|
65
|
+
},
|
|
66
|
+
deskExtInfo: {
|
|
67
|
+
isPresetContent: 1
|
|
68
|
+
},
|
|
66
69
|
});
|
|
67
70
|
}
|
|
68
71
|
emit('input-click', branch, cloudCustomData);
|
|
@@ -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
|
};
|
|
@@ -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, endQueuing } 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
|
+
endQueuing(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/package.json
CHANGED
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
|
|
|
@@ -54,7 +53,7 @@ export default class TUICustomerServer {
|
|
|
54
53
|
this.loginFailToasts = [];
|
|
55
54
|
this.isLoggedIn = false;
|
|
56
55
|
this.loggedInUserID = '';
|
|
57
|
-
this.myProfile = {
|
|
56
|
+
this.myProfile = {};
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
static getInstance(): TUICustomerServer {
|
|
@@ -144,14 +143,10 @@ export default class TUICustomerServer {
|
|
|
144
143
|
if (nickName) {
|
|
145
144
|
// chat 个人资料的昵称是 nick
|
|
146
145
|
this.myProfile.nick = nickName;
|
|
147
|
-
} else {
|
|
148
|
-
this.myProfile.nick = '';
|
|
149
146
|
}
|
|
150
147
|
|
|
151
148
|
if (avatar) {
|
|
152
149
|
this.myProfile.avatar = avatar;
|
|
153
|
-
} else {
|
|
154
|
-
this.myProfile.avatar = USER_DEFAULT_AVATAR;
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
if (customerServiceID) {
|
package/utils/utils.ts
CHANGED
|
@@ -376,4 +376,18 @@ export function debounce(func, delay) {
|
|
|
376
376
|
func.apply(context, args);
|
|
377
377
|
}, delay);
|
|
378
378
|
}
|
|
379
|
-
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function endQueuing(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
|
+
};
|