@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 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
- <Icon :file="DialogCloseIcon" width="20px" height="20px" @click="closeFeedbackModal"/>
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
- item.isSelected = !item.isSelected;
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
- :isInAudioMode="isInAudioMode"
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
- isInAudioMode:{
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.isInAudioMode);
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.isInAudioMode],
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: props.bottomQuickOrder,
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: 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?.content?.title || '',
77
+ title: props.payload.content.title || '',
74
78
  // all branch list
75
- list: props.payload?.content?.items || [],
79
+ list: welcomeItemList,
76
80
  // current branch list
77
- showList: (props.payload?.content?.items || []).slice(0, 3),
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
- <ToolbarButtonHumanService v-if="item.presetId === TOOLBAR_BUTTON_TYPE.HUMAN_SERVICE && shouldRender(item) && !isInHumanService" :title="item.title" :icon="item.icon"/>
5
- <ToolbarButtonServiceRating v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.SERVICE_RATING && shouldRender(item) && isInHumanService" :title="item.title" :icon="item.icon"/>
6
- <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"/>
7
- <div v-else-if="shouldRender(item) && !item.presetId" :key="index"
8
- :class="['toolbar-button', isH5 ? 'toolbar-button-h5' : '']" @click="onClick(item, index)">
9
- <Icon v-if="item.icon" class="toolbar-button-icon" :file="item.icon" width="18px" height="18px"/>
10
- <div class="toolbar-button-text">
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: auto; /* 允许横向滚动 */
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: 5px;
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>
@@ -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>
@@ -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
 
@@ -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/component/TUIKit/assets/avatar_21.png',
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/component/TUIKit/assets/avatar_21.png',
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
@@ -165,8 +165,6 @@ export const INPUT_TOOLBAR_TYPE = {
165
165
  AUDIO: 'audio',
166
166
  };
167
167
 
168
- export const USER_DEFAULT_AVATAR = 'https://web.sdk.qcloud.com/im/desk/assets/user_default_avatar.png';
169
-
170
168
  export enum ReadState {
171
169
  Read,
172
170
  Unread,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencentcloud/ai-desk-customer-vue",
3
- "version": "1.6.3",
3
+ "version": "1.6.6",
4
4
  "description": "Vue2/Vue3 UIKit for AI Desk",
5
5
  "main": "index",
6
6
  "keywords": [
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, updateCustomStore, isNonEmptyObject } from "./utils/utils";
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 = { avatar: USER_DEFAULT_AVATAR };
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
+ };