@tencentcloud/ai-desk-customer-vue 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/components/CustomerServiceChat/index-web.vue +16 -4
  3. package/components/CustomerServiceChat/message-input-toolbar/file-upload/index.vue +14 -1
  4. package/components/CustomerServiceChat/message-input-toolbar/image-upload/index.vue +26 -2
  5. package/components/CustomerServiceChat/message-input-toolbar/video-upload/index.vue +14 -1
  6. package/components/CustomerServiceChat/message-list/bottom-quick-order/index.vue +201 -0
  7. package/components/CustomerServiceChat/message-list/index-web.vue +46 -0
  8. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/marked.ts +6 -1
  9. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-desk.vue +1 -1
  10. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-ivr-form/index.vue +2 -2
  11. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-product-card.vue +140 -87
  12. package/components/CustomerServiceChat/style/h5.scss +1 -0
  13. package/constant.ts +3 -3
  14. package/interface.ts +15 -1
  15. package/locales/en/aidesk.ts +4 -1
  16. package/locales/fil/aidesk.ts +3 -0
  17. package/locales/id/aidesk.ts +3 -0
  18. package/locales/ja/aidesk.ts +3 -0
  19. package/locales/ms/aidesk.ts +3 -0
  20. package/locales/ru/aidesk.ts +3 -0
  21. package/locales/th/aidesk.ts +3 -0
  22. package/locales/vi/aidesk.ts +3 -0
  23. package/locales/zh_cn/aidesk.ts +3 -0
  24. package/locales/zh_tw/aidesk.ts +3 -0
  25. package/package.json +1 -1
  26. package/utils/utils.ts +7 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 1.5.2 @2025.7.2
2
+
3
+ ### Features
4
+ - 新增参数 `bottomQuickOrder`,配置后可展示底部快捷订单。
5
+
6
+ ### Fixed
7
+ - markdown 链接展示问题。
8
+
9
+ ## 1.5.1 @2025.6.23
10
+
11
+ ### Fixed
12
+ - 修复 vue2.6 项目接入时报错。
13
+
1
14
  ## 1.5.0 @2025.6.19
2
15
 
3
16
  ### Features
@@ -17,9 +17,12 @@
17
17
  :class="['tui-chat-message-list', !isPC && 'tui-chat-h5-message-list']"
18
18
  @handleEditor="handleEditor"
19
19
  @closeInputToolBar="() => changeToolbarDisplayType('none')"
20
+ :bottomQuickOrder="props.bottomQuickOrder"
21
+ :showBottomQuickOrder="showBottomQuickOrder"
22
+ @closeBottomQuickOrder="closeBottomQuickOrder"
20
23
  />
21
24
  <MessageToolbarButton
22
- v-if="!(isH5 && Boolean(quoteMessage))"
25
+ v-if="!isH5 || (!Boolean(quoteMessage) && !showBottomQuickOrder)"
23
26
  :toolbarButtonList="props.toolbarButtonList"
24
27
  />
25
28
  <MessageInputToolbar
@@ -95,16 +98,16 @@ import MessageInput from './message-input/index-web.vue';
95
98
  import MessageInputToolbar from './message-input-toolbar/index-web.vue';
96
99
  import EmojiDialog from './message-input-toolbar/emoji-dialog-mobile/emoji-dialog-mobile.vue';
97
100
  import { isH5, isPC } from '../../utils/env';
98
- import { ToolbarButtonModel, ToolbarDisplayType, InputToolbarModel } from '../../interface';
101
+ import { ToolbarButtonModel, ToolbarDisplayType, InputToolbarModel, QuickOrderModel } from '../../interface';
99
102
  import { isSupportedLang } from '../../utils/';
100
103
  import Log from '../../utils/logger';
101
104
  import MessageToolbarButton from './message-toolbar-button/index.vue';
102
105
  import TUILocales from '../../locales';
103
106
  import { Toast, TOAST_TYPE } from '../common/Toast/index-web';
104
107
  import state from '../../utils/state.js';
105
- import { switchReadStatus } from '../../utils/utils';
108
+ import { switchReadStatus,isNonEmptyObject } from '../../utils/utils';
106
109
 
107
- const { ref, onMounted, onUnmounted, computed, nextTick } = vue;
110
+ const { ref, onMounted, onUnmounted, computed } = vue;
108
111
 
109
112
  interface IProps {
110
113
  canCloseChat?: boolean;
@@ -125,6 +128,7 @@ interface IProps {
125
128
  inputToolbarList?: InputToolbarModel[];
126
129
  showReadStatus?: number;
127
130
  showTyping?: number;
131
+ bottomQuickOrder?: QuickOrderModel;
128
132
  }
129
133
 
130
134
  const emits = defineEmits(['closeChat']);
@@ -139,6 +143,7 @@ const emojiOpen = ref(false);
139
143
  const toolShowH5 = ref(false);
140
144
  const languages = Object.keys(TUILocales);
141
145
  const quoteMessage = ref<IMessageModel>();
146
+ const showBottomQuickOrder = ref(false);
142
147
  const props = withDefaults(defineProps<IProps>(), {
143
148
  canCloseChat: true,
144
149
  SDKAppID: 0,
@@ -247,6 +252,9 @@ try {
247
252
  setAvatarNickName();
248
253
  setShowReadStatus();
249
254
  setShowTyping();
255
+ if (isNonEmptyObject(props.bottomQuickOrder)) {
256
+ showBottomQuickOrder.value = true;
257
+ }
250
258
  } catch (e) {
251
259
  console.log(e)
252
260
  }
@@ -389,6 +397,10 @@ function onQuoteMessageUpdated(options?: {
389
397
  quoteMessage.value = undefined;
390
398
  }
391
399
  }
400
+
401
+ function closeBottomQuickOrder() {
402
+ showBottomQuickOrder.value = false;
403
+ }
392
404
  </script>
393
405
 
394
406
  <style scoped lang="scss" src="./style/index.scss">
@@ -35,6 +35,8 @@ import fileIcon from '../../../../assets/files.svg';
35
35
  import fileIconH5 from '../../../../assets/file-h5.png';
36
36
  import { isPC, isH5 } from '../../../../utils/env';
37
37
  import { isEnabledMessageReadReceiptGlobal, getTo } from '../../../../utils/utils';
38
+ import Log from '../../../../utils/logger';
39
+ import { Toast, TOAST_TYPE } from '../../../common/Toast/index-web';
38
40
  const { ref } = vue;
39
41
 
40
42
  const inputRef = ref();
@@ -63,7 +65,18 @@ const sendFileMessage = (e: any) => {
63
65
  needReadReceipt: isEnabledMessageReadReceiptGlobal(),
64
66
  } as SendMessageParams;
65
67
  const sendMessageOptions: SendMessageOptions = {};
66
- TUIChatService.sendFileMessage(options, sendMessageOptions);
68
+ TUIChatService.sendFileMessage(options, sendMessageOptions)
69
+ .catch((err) => {
70
+ Toast({
71
+ message:
72
+ err.code === 2402 ?
73
+ TUITranslateService.t('AIDesk.file 大小超过 100MB,无法发送')
74
+ : err.message,
75
+ type: TOAST_TYPE.ERROR,
76
+ duration: 5000,
77
+ });
78
+ Log.l(`Send File Failed:${err.code} ${err.message}`);
79
+ });
67
80
  e.target.value = '';
68
81
  };
69
82
  </script>
@@ -38,6 +38,8 @@ import ToolbarItemContainer from '../toolbar-item-container/index.vue';
38
38
  import imageIcon from '../../../../assets/image.svg';
39
39
  import imageUniIcon from '../../../../assets/image-uni.png';
40
40
  import { getTo, isEnabledMessageReadReceiptGlobal } from '../../../../utils/utils';
41
+ import Log from '../../../../utils/logger';
42
+ import { Toast, TOAST_TYPE } from '../../../common/Toast/index-web';
41
43
  const { ref, computed } = vue;
42
44
 
43
45
  const props = defineProps({
@@ -119,7 +121,18 @@ const sendImageMessage = (files: any) => {
119
121
  needReadReceipt: isEnabledMessageReadReceiptGlobal(),
120
122
  } as SendMessageParams;
121
123
  const sendMessageOptions: SendMessageOptions = {};
122
- TUIChatService.sendImageMessage(options, sendMessageOptions);
124
+ TUIChatService.sendImageMessage(options, sendMessageOptions)
125
+ .catch((err) => {
126
+ Toast({
127
+ message:
128
+ err.code === 2253 ?
129
+ TUITranslateService.t('AIDesk.image 大小超过 20MB,无法发送')
130
+ : err.message,
131
+ type: TOAST_TYPE.ERROR,
132
+ duration: 5000,
133
+ });
134
+ Log.l(`Send Image Failed:${err.code} ${err.message}`);
135
+ });
123
136
  };
124
137
  const sendVideoMessage = (file: any) => {
125
138
  if (!file) {
@@ -134,7 +147,18 @@ const sendVideoMessage = (file: any) => {
134
147
  needReadReceipt: isEnabledMessageReadReceiptGlobal(),
135
148
  } as SendMessageParams;
136
149
  const sendMessageOptions: SendMessageOptions = {};
137
- TUIChatService.sendVideoMessage(options, sendMessageOptions);
150
+ TUIChatService.sendVideoMessage(options, sendMessageOptions)
151
+ .catch((err) => {
152
+ Toast({
153
+ message:
154
+ err.code === 2351 ?
155
+ TUITranslateService.t('AIDesk.video 大小超过 100MB,无法发送')
156
+ : err.message,
157
+ type: TOAST_TYPE.ERROR,
158
+ duration: 5000,
159
+ });
160
+ Log.l(`Send Video Failed:${err.code} ${err.message}`);
161
+ });
138
162
  };
139
163
  </script>
140
164
 
@@ -35,6 +35,8 @@ import ToolbarItemContainer from '../toolbar-item-container/index.vue';
35
35
  import videoIcon from '../../../../assets/video.svg';
36
36
  import videoIconH5 from '../../../../assets/video_h5.svg'
37
37
  import { isEnabledMessageReadReceiptGlobal, getTo } from '../../../../utils/utils';
38
+ import Log from '../../../../utils/logger';
39
+ import { Toast, TOAST_TYPE } from '../../../common/Toast/index-web';
38
40
  const { ref } = vue;
39
41
 
40
42
  const props = defineProps({
@@ -89,7 +91,18 @@ const sendVideoMessage = (file: any) => {
89
91
  needReadReceipt: isEnabledMessageReadReceiptGlobal(),
90
92
  } as SendMessageParams;
91
93
  const sendMessageOptions: SendMessageOptions = {};
92
- TUIChatService.sendVideoMessage(options, sendMessageOptions);
94
+ TUIChatService.sendVideoMessage(options, sendMessageOptions)
95
+ .catch((err) => {
96
+ Toast({
97
+ message:
98
+ err.code === 2351 ?
99
+ TUITranslateService.t('AIDesk.video 大小超过 100MB,无法发送')
100
+ : err.message,
101
+ type: TOAST_TYPE.ERROR,
102
+ duration: 5000,
103
+ });
104
+ Log.l(`Send Video Failed:${err.code} ${err.message}`);
105
+ });
93
106
  };
94
107
  </script>
95
108
 
@@ -0,0 +1,201 @@
1
+ <template>
2
+ <div :class="isPC ? 'bottom-quick-order-line':''">
3
+ <div :class="['bottom-quick-order-container',isPC && 'bottom-quick-order-container-pc']">
4
+ <div @click="closeOrder">
5
+ <Icon class="close-icon" :file="cardToolbarCloseIcon" width="12px" height="12px"/>
6
+ </div>
7
+ <div v-for="(item,index) in topCustomField" :key="index" class="quick-order-custom-field">
8
+ <div class="custom-field-line">
9
+ <div v-if="item.name" class="order-name">
10
+ {{ item.name }}:
11
+ </div>
12
+ <div class="order-value">
13
+ {{ item.value }}
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="quick-order-main">
18
+ <img
19
+ v-if="props.bottomQuickOrder.pic"
20
+ class="quick-order-img"
21
+ :src="props.bottomQuickOrder.pic"
22
+ >
23
+ <div class="quick-order-information">
24
+ <div class="quick-order-title">
25
+ {{ props.bottomQuickOrder.header }}
26
+ </div>
27
+ <div class="quick-order-description-block">
28
+ <div class="quick-order-description">
29
+ {{ props.bottomQuickOrder.desc }}
30
+ </div>
31
+ <div v-if="bottomCustomField.length === 0" class="quick-order-link" @click="sendOrder">
32
+ {{ TUITranslateService.t("发送") }}
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ <div v-for="(item,index) in bottomCustomField" :key="index" class="quick-order-custom-field">
38
+ <div class="custom-field-line">
39
+ <div v-if="item.name" class="order-name">
40
+ {{ item.name }}:
41
+ </div>
42
+ <div class="order-value">
43
+ {{ item.value }}
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <div v-if="bottomCustomField.length !== 0" class="quick-order-link" @click="sendOrder">
48
+ {{ TUITranslateService.t("发送") }}
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </template>
53
+ <script lang="ts" setup>
54
+ import vue from '../../../../adapter-vue';
55
+ import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
56
+ import cardToolbarCloseIcon from '../../../../assets/dialog-close.png';
57
+ import Icon from '../../../common/Icon.vue';
58
+ import { QuickOrderModel } from '../../../../interface';
59
+ import { isPC } from '../../../../utils/env';
60
+ const { computed } = vue;
61
+ interface IProps {
62
+ bottomQuickOrder: QuickOrderModel;
63
+ }
64
+ const emits = defineEmits(['closeOrder','sendOrder']);
65
+
66
+ const props = withDefaults(defineProps<IProps>(), {
67
+ bottomQuickOrder: () => ({} as QuickOrderModel),
68
+ });
69
+
70
+ const customField = computed(() => {
71
+ return Array.isArray(props.bottomQuickOrder?.customField) ? props.bottomQuickOrder.customField : [];
72
+ });
73
+
74
+ const topCustomField = computed(() => {
75
+ return customField.value.filter((item) => item.position === 'top');
76
+ });
77
+
78
+ const bottomCustomField = computed(() => {
79
+ return customField.value.filter((item) => item.position !== 'top');
80
+ });
81
+
82
+ const sendOrder = () => {
83
+ emits('sendOrder');
84
+ };
85
+
86
+ const closeOrder = () => {
87
+ emits('closeOrder');
88
+ };
89
+ </script>
90
+ <style scoped lang="scss">
91
+ .bottom-quick-order-line {
92
+ display: flex;
93
+ align-items: flex-end;
94
+ }
95
+ .bottom-quick-order-container {
96
+ display: flex;
97
+ flex-direction: column;
98
+ background: #fff;
99
+ padding: 10px 30px 10px 10px;
100
+ border-radius: 8px;
101
+ margin: 0 10px 0 10px;
102
+ position: relative;
103
+ font-family: PingFangSC-Regular;
104
+ }
105
+ .bottom-quick-order-container-pc {
106
+ width: 300px;
107
+ box-shadow: rgb(204, 204, 204) 0px 0px 10px;
108
+ margin-bottom: 5px;
109
+ }
110
+ .quick-order-custom-field {
111
+ font-size: 12px;
112
+ max-width: 100%;
113
+ overflow: hidden;
114
+ }
115
+ .custom-field-line {
116
+ display: flex;
117
+ gap: 5px;
118
+ font-size: 12px;
119
+ line-height: 22px;
120
+ }
121
+ .order-name {
122
+ color: #878787;
123
+ margin-right: 3px;
124
+ white-space: nowrap;
125
+ flex-shrink: 0;
126
+ }
127
+ .order-value {
128
+ flex: 1;
129
+ min-width: 0;
130
+ white-space: nowrap;
131
+ overflow: hidden;
132
+ text-overflow: ellipsis;
133
+ }
134
+ .quick-order-main {
135
+ display: flex;
136
+ margin: 10px 0 10px 0;
137
+ }
138
+ .quick-order-img {
139
+ width: 60px;
140
+ height: 60px;
141
+ border-radius: 10px;
142
+ margin-right: 15px;
143
+ flex-shrink: 0;
144
+ object-fit: cover;
145
+ }
146
+
147
+ .quick-order-information {
148
+ width: 100%;
149
+ margin-right: 5px;
150
+ display: flex;
151
+ flex-direction: column;
152
+ justify-content: space-between;
153
+
154
+ .quick-order-title {
155
+ color: #000000;
156
+ font-size: 14px;
157
+ display: -webkit-box;
158
+ overflow: hidden;
159
+ text-overflow: ellipsis;
160
+ -webkit-line-clamp: 2;
161
+ -webkit-box-orient: vertical;
162
+ word-break: break-word;
163
+ overflow-wrap: anywhere;
164
+ }
165
+
166
+ .quick-order-description-block {
167
+ display: flex;
168
+ justify-content: space-between;
169
+ align-items: center;
170
+ gap: 5px;
171
+ }
172
+
173
+ .quick-order-description {
174
+ font-size: 16px;
175
+ max-width: 120px;
176
+ color: #1C66E5;
177
+ overflow: hidden;
178
+ text-overflow: ellipsis;
179
+ white-space: nowrap;
180
+ font-weight: 500;
181
+ flex: 0 1 120px;
182
+ }
183
+ }
184
+ .quick-order-link {
185
+ cursor: pointer;
186
+ background-color: #1c66e5;
187
+ color: #ffffff;
188
+ font-size: 12px;
189
+ padding: 2px 18px;
190
+ line-height: 22px;
191
+ border-radius: 23px;
192
+ flex: 0 0 auto;
193
+ margin-left: auto;
194
+ }
195
+ .close-icon {
196
+ position: absolute;
197
+ right: 10px;
198
+ top: 10px;
199
+ cursor: pointer;
200
+ }
201
+ </style>
@@ -172,6 +172,13 @@
172
172
  :imageList="imageMessageList"
173
173
  @close="onImagePreviewerClose"
174
174
  />
175
+ <BottomQuickOrder
176
+ class="bottom-quick-order"
177
+ v-if="isNonEmptyObject(props.bottomQuickOrder) && props.showBottomQuickOrder"
178
+ :bottomQuickOrder="props.bottomQuickOrder"
179
+ @closeOrder="closeBottomQuickOrder"
180
+ @sendOrder="sendBottomQuickOrder"
181
+ />
175
182
  </div>
176
183
  </div>
177
184
  </template>
@@ -208,6 +215,7 @@ import MessageRevoked from './message-tool/message-revoked.vue';
208
215
  import MessagePlugin from '../message-list/message-elements/message-desk/message-plugin-web.vue';
209
216
  import MessageThinking from './message-elements/message-thinking.vue';
210
217
  import ScrollButton from './scroll-button/index.vue';
218
+ import BottomQuickOrder from './bottom-quick-order/index.vue';
211
219
  import { isPluginMessage } from './message-elements/message-desk/index';
212
220
  import Dialog from '../../common/Dialog/index.vue';
213
221
  import ImagePreviewer from '../../common/ImagePreviewer/index-web.vue';
@@ -217,10 +225,12 @@ import chatStorage from '../../../utils/chatStorage';
217
225
  import {
218
226
  isEnabledMessageReadReceiptGlobal,
219
227
  deepCopy,
228
+ isNonEmptyObject,
220
229
  } from '../../../utils/utils';
221
230
  import { isMessageInvisible, isThinkingMessage, isThinkingMessageOverTime, JSONToObject } from '../../../utils/index';
222
231
  import { isCustomerConversation } from '../../../index';
223
232
  import { CUSTOM_MESSAGE_SRC } from '../../../constant';
233
+ import { QuickOrderModel } from '../../../interface';
224
234
 
225
235
  interface ScrollConfig {
226
236
  scrollToMessage?: IMessageModel;
@@ -231,9 +241,18 @@ interface ScrollConfig {
231
241
  };
232
242
  }
233
243
 
244
+ interface IProps {
245
+ bottomQuickOrder?: QuickOrderModel;
246
+ showBottomQuickOrder: boolean;
247
+ }
248
+ const props = withDefaults(defineProps<IProps>(), {
249
+ showBottomQuickOrder: false,
250
+ });
251
+
234
252
  interface IEmits {
235
253
  (key: 'closeInputToolBar'): void;
236
254
  (key: 'handleEditor', message: IMessageModel, type: string): void;
255
+ (key: 'closeBottomQuickOrder'): void;
237
256
  }
238
257
 
239
258
  const emits = defineEmits<IEmits>();
@@ -742,6 +761,26 @@ function setAudioPlayed(messageID: string) {
742
761
  };
743
762
  }
744
763
 
764
+ function closeBottomQuickOrder() {
765
+ emits('closeBottomQuickOrder');
766
+ }
767
+
768
+ function sendBottomQuickOrder() {
769
+ TUIChatService.sendCustomMessage({
770
+ to: currentConversationID.value.replace('C2C', ''),
771
+ conversationType: 'C2C',
772
+ payload: {
773
+ data: JSON.stringify({
774
+ src: CUSTOM_MESSAGE_SRC.PRODUCT_CARD,
775
+ content: props.bottomQuickOrder,
776
+ customerServicePlugin: 0,
777
+ }),
778
+ },
779
+ needReadReceipt: isEnabledMessageReadReceiptGlobal(),
780
+ });
781
+ closeBottomQuickOrder();
782
+ }
783
+
745
784
  defineExpose({
746
785
  scrollToLatestMessage,
747
786
  });
@@ -752,4 +791,11 @@ defineExpose({
752
791
  .row-reverse {
753
792
  flex-direction: row-reverse;
754
793
  }
794
+ .bottom-quick-order {
795
+ position: absolute;
796
+ bottom: 0;
797
+ right: 0;
798
+ z-index: 1000;
799
+ width: 100%;
800
+ }
755
801
  </style>
@@ -19,7 +19,12 @@ export const marked = new Marked(
19
19
  if (href) {
20
20
  // 匹配以 http:// 或 https:// 开头,所有 URL 主体字符,遇到第一个非主体字符(如中文括号、空格、表情符号等)时停止
21
21
  let ret = href.replace(/https?:\/\/[\w\-./?=&:#]+(?=[^\w\-./?=&:#]|$)/g, (matchedUrl) => {
22
- return `<a target="_blank" rel="noreferrer noopenner" class="message-marked_link" href="${matchedUrl || ''}" title="${title}">${matchedUrl}</a>`;
22
+ let isURLInText = false;
23
+ if (matchedUrl !== href) {
24
+ // 如果 text 里包含 url,我们就用 matchedUrl 作为 a 标签的值;否则用 text 作为值
25
+ isURLInText = true;
26
+ }
27
+ return `<a target="_blank" rel="noreferrer noopenner" class="message-marked_link" href="${matchedUrl || ''}" title="${title}">${isURLInText ? matchedUrl : text}</a>`;
23
28
  });
24
29
  if (ret === href) {
25
30
  Log.w(`Unable to extract url, href:${href}`);
@@ -17,7 +17,7 @@
17
17
  </div>
18
18
  <div
19
19
  v-if="
20
- payload.src === CUSTOM_MESSAGE_SRC.ROBOT_MSG
20
+ payload.src === CUSTOM_MESSAGE_SRC.ROBOT_MSG && payload.subtype === 'welcome_msg'
21
21
  "
22
22
  >
23
23
  <MessageIMRobotWelcome
@@ -7,7 +7,7 @@
7
7
  <FormBranch
8
8
  :title="content.header"
9
9
  :list="content.items"
10
- :selectedContent="content.selected?.content"
10
+ :selectedContent="content.selected && content.selected.content"
11
11
  @input-click="handleContentListItemClick"
12
12
  />
13
13
  </div>
@@ -18,7 +18,7 @@
18
18
  >
19
19
  <FormInput
20
20
  :title="content.header"
21
- :selectedContent="content.selected?.content"
21
+ :selectedContent="content.selected && content.selected.content"
22
22
  @input-submit="handleFormSaveInputSubmit"
23
23
  />
24
24
  </div>
@@ -2,38 +2,58 @@
2
2
  <div
3
3
  class="message-product-card"
4
4
  >
5
- <image
6
- v-if="isApp"
7
- class="product-img"
8
- :src="props.payload.content.pic"
9
- />
10
- <img
11
- v-else
12
- class="product-img"
13
- :src="props.payload.content.pic"
14
- >
15
- <div class="product-card-information">
16
- <div class="product-card-title">
17
- {{ props.payload.content.header }}
5
+ <div v-for="(item,index) in topCustomField" :key="index" class="product-card-custom-field">
6
+ <div class="custom-field-line">
7
+ <div v-if="item.name" class="order-name">
8
+ {{ item.name }}:
9
+ </div>
10
+ <div class="order-value">
11
+ {{ item.value }}
12
+ </div>
18
13
  </div>
19
- <div class="product-card-description-block">
14
+ </div>
15
+ <div class="product-card-main">
16
+ <img
17
+ v-if="props.payload.content.pic"
18
+ class="product-img"
19
+ :src="props.payload.content.pic"
20
+ >
21
+ <div class="product-card-information">
22
+ <div class="product-card-title">
23
+ {{ props.payload.content.header }}
24
+ </div>
25
+ <div class="product-card-description-block">
20
26
  <div class="product-card-description">
21
- {{ props.payload.content.desc }}
27
+ {{ props.payload.content.desc }}
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ <div v-for="(item,index) in bottomCustomField" :key="index" class="product-card-custom-field">
34
+ <div class="custom-field-line">
35
+ <div v-if="item.name" class="order-name">
36
+ {{ item.name }}:
37
+ </div>
38
+ <div class="order-value">
39
+ {{ item.value }}
22
40
  </div>
23
- <div class="product-card-link" @click="jumpProductCard">
24
- {{TUITranslateService.t("AIDesk.跳转")}}
25
- </div>
26
41
  </div>
27
42
  </div>
43
+ <div class="product-card-link" @click="jumpProductCard">
44
+ {{ TUITranslateService.t("AIDesk.跳转") }}
45
+ </div>
28
46
  </div>
29
47
  </template>
30
48
 
31
49
  <script lang="ts">
50
+ import vue from '../../../../../../adapter-vue';
32
51
  import { customerServicePayloadType } from '../../../../../../interface';
33
52
  import { isApp } from '../../../../../../utils/env';
34
- import {TUITranslateService} from '@tencentcloud/chat-uikit-engine';
35
- // eslint-disable-next-line
36
- declare var uni: any;
53
+ import { openSafeUrl } from '../../../../../../utils/utils';
54
+ import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
55
+ import Log from '../../../../../../utils/logger';
56
+ const { computed } = vue;
37
57
 
38
58
  interface Props {
39
59
  payload: customerServicePayloadType;
@@ -49,92 +69,125 @@ export default {
49
69
  emits: ['sendMessage'],
50
70
  setup(props: Props) {
51
71
  const jumpProductCard = () => {
52
- if (window) {
53
- window.open(props.payload.content.url, '_blank');
72
+ const { url } = props.payload.content;
73
+ if (url) {
74
+ openSafeUrl(props.payload.content.url);
54
75
  } else {
55
- // uni && uni.navigateTo({ url: `/TUIKit/components/TUIChat/web-view?url=${props.payload.content.url}` });
56
- // #ifdef APP-PLUS
57
- // @ts-ignore
58
- plus.runtime.openURL(props.payload.content.url);
59
- // #endif
60
- // #ifdef H5
61
- // @ts-ignore
62
- window.open(props.payload.content.url);
63
- // #endif
76
+ Log.w('Missing required url');
64
77
  }
65
78
  };
79
+ const customField = computed(() => {
80
+ return Array.isArray(props.payload.content.customField) ? props.payload.content.customField : [];
81
+ });
82
+
83
+ const topCustomField = computed(() => {
84
+ return customField.value.filter((item) => item.position === 'top');
85
+ });
86
+
87
+ const bottomCustomField = computed(() => {
88
+ return customField.value.filter((item) => item.position !== 'top');
89
+ });
66
90
  return {
67
91
  props,
68
92
  isApp,
69
93
  jumpProductCard,
70
- TUITranslateService
94
+ TUITranslateService,
95
+ topCustomField,
96
+ bottomCustomField,
71
97
  };
72
98
  },
73
99
  };
74
100
  </script>
75
101
  <style lang="scss" scoped>
76
102
  .message-product-card {
77
- min-width: 200px;
103
+ min-width: 100%;
78
104
  max-width: 400px;
105
+ margin-top: 2px;
106
+ font-family: PingFangSC-Regular;
79
107
  display: flex;
80
-
81
- .product-img {
82
- width: 75px;
83
- height: 75px;
84
- border-radius: 10px;
85
- flex-shrink: 0;
86
- object-fit: cover;
87
- }
88
-
89
- .product-card-information {
90
- width:100%;
91
- margin-left: 15px;
92
- margin-right:5px;
108
+ flex-direction: column;
109
+ .product-card-main {
93
110
  display: flex;
94
- flex-direction: column;
95
- justify-content: space-between;
111
+ margin: 10px 0 10px 0;
112
+ }
113
+ }
114
+ .product-img {
115
+ width: 60px;
116
+ height: 60px;
117
+ border-radius: 10px;
118
+ flex-shrink: 0;
119
+ object-fit: cover;
120
+ margin-right: 15px;
121
+ align-self: center;
122
+ }
96
123
 
97
- .product-card-title {
98
- max-width: 200px;
99
- min-width: 100px;
100
- color: #000000;
101
- font-size: 14px;
102
- display: -webkit-box;
103
- overflow: hidden;
104
- text-overflow: ellipsis;
105
- -webkit-line-clamp: 2;
106
- -webkit-box-orient: vertical;
107
- overflow-wrap: break-word;
108
- word-break: normal;
109
- }
124
+ .product-card-information {
125
+ width:100%;
126
+ margin-right: 5px;
127
+ display: flex;
128
+ flex-direction: column;
129
+ justify-content: space-between;
110
130
 
111
- .product-card-description-block {
112
- display:flex;
113
- justify-content: space-between;
114
- align-items: center;
115
- gap: 5px;
116
- }
131
+ .product-card-title {
132
+ color: #000000;
133
+ font-size: 12px;
134
+ display: -webkit-box;
135
+ overflow: hidden;
136
+ text-overflow: ellipsis;
137
+ -webkit-line-clamp: 2;
138
+ -webkit-box-orient: vertical;
139
+ word-break: break-word;
140
+ overflow-wrap: anywhere;
141
+ }
117
142
 
118
- .product-card-description {
119
- font-size: 12px;
120
- max-width: 60px;
121
- color: #1c66e5;
122
- overflow: hidden;
123
- text-overflow: ellipsis;
124
- white-space: nowrap;
125
- font-weight: 600;
126
- flex: 0 1 60px;
127
- }
143
+ .product-card-description-block {
144
+ display:flex;
145
+ justify-content: space-between;
146
+ align-items: center;
147
+ gap: 5px;
148
+ }
128
149
 
129
- .product-card-link {
130
- cursor: pointer;
131
- background-color: #1c66e5;
132
- color:#ffffff;
133
- padding:2px 12px;
134
- border-radius: 12px;
135
- flex: 0 0 auto;
136
- margin-left: auto;
137
- }
150
+ .product-card-description {
151
+ font-size: 14px;
152
+ max-width: 100px;
153
+ color: #1C66E5;
154
+ overflow: hidden;
155
+ text-overflow: ellipsis;
156
+ white-space: nowrap;
157
+ font-weight: 600;
158
+ flex: 0 1 100px;
138
159
  }
139
160
  }
161
+ .product-card-custom-field {
162
+ color: #000;
163
+ margin: 0 0 5px 0;
164
+ }
165
+ .custom-field-line {
166
+ display: flex;
167
+ font-size: 12px;
168
+ gap: 5px;
169
+ overflow: hidden;
170
+ .order-name {
171
+ color: #878787;
172
+ margin-right: 3px;
173
+ white-space: nowrap;
174
+ flex-shrink: 0;
175
+ }
176
+ .order-value {
177
+ overflow: hidden;
178
+ text-overflow: ellipsis;
179
+ white-space: nowrap;
180
+ }
181
+ }
182
+ .product-card-link {
183
+ cursor: pointer;
184
+ background-color: #1c66e5;
185
+ color: #ffffff;
186
+ padding: 2px 18px;
187
+ border-radius: 23px;
188
+ font-size: 12px;
189
+ line-height: 22px;
190
+ flex: 0 0 auto;
191
+ margin-left: auto;
192
+ }
140
193
  </style>
@@ -12,6 +12,7 @@
12
12
  flex: 1;
13
13
  overflow: hidden;
14
14
  display: flex;
15
+ background-image: none;
15
16
  }
16
17
 
17
18
  &-message-input {
package/constant.ts CHANGED
@@ -148,14 +148,14 @@ export const TOOLBAR_BUTTON_TYPE = {
148
148
  HUMAN_SERVICE: 'humanService',
149
149
  SERVICE_RATING: 'serviceRating',
150
150
  END_HUMAN_SERVICE: 'endHumanService',
151
- }
151
+ };
152
152
  export const INPUT_TOOLBAR_TYPE = {
153
153
  EMOJI: 'emoji',
154
154
  IMAGE: 'image',
155
155
  FILE: 'file',
156
156
  VIDEO: 'video',
157
157
  RATING: 'rating',
158
- }
158
+ };
159
159
 
160
160
  export const USER_DEFAULT_AVATAR = 'https://web.sdk.qcloud.com/im/desk/assets/user_default_avatar.png';
161
161
 
@@ -165,4 +165,4 @@ export enum ReadState {
165
165
  AllRead,
166
166
  NotShow,
167
167
  PartiallyRead,
168
- }
168
+ };
package/interface.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TOOLBAR_BUTTON_TYPE,INPUT_TOOLBAR_TYPE } from './constant';
1
+ import { TOOLBAR_BUTTON_TYPE, INPUT_TOOLBAR_TYPE } from './constant';
2
2
  export interface customerServicePayloadType {
3
3
  chatbotPlugin?: number | string;
4
4
  customerServicePlugin?: number | string;
@@ -211,4 +211,18 @@ export interface InputToolbarModel {
211
211
  isEnabled?: number, // 是否显示
212
212
  renderCondition?: () => {},// [UIKit] 是否显示
213
213
  clickEvent?: () => void,// [UIKit] 点击事件
214
+ }
215
+
216
+ export interface QuickOrderCustomFieldModel {
217
+ name?: string,
218
+ value: string,
219
+ position: string,
220
+ }
221
+
222
+ export interface QuickOrderModel {
223
+ header: string,
224
+ desc: string,
225
+ pic?: string,
226
+ url: string,
227
+ customField?: QuickOrderCustomFieldModel[],
214
228
  }
@@ -1,7 +1,7 @@
1
1
  const AIDesk = {
2
2
  "结束人工会话": "End human service",
3
3
  "转人工服务": "Human service",
4
- "跳转": "Goto",
4
+ "跳转": "Open",
5
5
  "立即填写": "Fill now",
6
6
  "已提交": "Submitted",
7
7
  "提交": "Submit",
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Context misunderstanding",
28
28
  "格式不规范":"Improper format",
29
29
  "内容不完整":"Incomplete content",
30
+ "file 大小超过 100MB,无法发送":"Unable to send the file as it exceeds 100 MB",
31
+ "image 大小超过 20MB,无法发送":"Unable to send the image as it exceeds 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Unable to send the video as it exceeds 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Maling pag-unawa sa konteksto",
28
28
  "格式不规范":"Hindi tamang pormat",
29
29
  "内容不完整":"Hindi kumpletong nilalaman",
30
+ "file 大小超过 100MB,无法发送":"Lampas sa 100MB ang file, hindi maipadala",
31
+ "image 大小超过 20MB,无法发送":"Hindi maipadala ang larawan dahil lampas ito sa 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Hindi maipadala ang video dahil lampas ito sa 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Kesalahan pemahaman konteks",
28
28
  "格式不规范":"Format tidak standar",
29
29
  "内容不完整":"Konten tidak lengkap",
30
+ "file 大小超过 100MB,无法发送":"Ukuran file melebihi 100MB, tidak dapat dikirim",
31
+ "image 大小超过 20MB,无法发送":"Tidak dapat mengirim gambar karena melebihi 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Tidak dapat mengirim video karena melebihi 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"文脈の理解誤り",
28
28
  "格式不规范":"形式が不適切",
29
29
  "内容不完整":"内容が不完全",
30
+ "file 大小超过 100MB,无法发送":"ファイルサイズが100MBを超えているため送信できません",
31
+ "image 大小超过 20MB,无法发送":"画像のサイズが20MBを超えているため送信できません",
32
+ "video 大小超过 100MB,无法发送":"動画のサイズが100MBを超えているため送信できません",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Kesilapan pemahaman konteks",
28
28
  "格式不规范":"Format tidak standard",
29
29
  "内容不完整":"Kandungan tidak lengkap",
30
+ "file 大小超过 100MB,无法发送":"Saiz fail melebihi 100MB, tidak boleh dihantar",
31
+ "image 大小超过 20MB,无法发送":"Tidak dapat menghantar gambar kerana melebihi 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Tidak dapat menghantar video kerana melebihi 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Ошибка понимания контекста",
28
28
  "格式不规范":"Нестандартный формат",
29
29
  "内容不完整":"Неполное содержание",
30
+ "file 大小超过 100MB,无法发送":"Размер файла превышает 100MB, отправка невозможна",
31
+ "image 大小超过 20MB,无法发送":"Невозможно отправить изображение, так как оно превышает 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Невозможно отправить видео, так как оно превышает 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"ความเข้าใจบริบทผิดพลาด",
28
28
  "格式不规范":"รูปแบบไม่ถูกต้อง",
29
29
  "内容不完整":"เนื้อหาไม่สมบูรณ์",
30
+ "file 大小超过 100MB,无法发送":"ขนาดไฟล์เกิน 100MB ไม่สามารถส่งได้",
31
+ "image 大小超过 20MB,无法发送":"ไม่สามารถส่งรูปภาพได้เนื่องจากขนาดเกิน 20 MB",
32
+ "video 大小超过 100MB,无法发送":"ไม่สามารถส่งวิดีโอได้เนื่องจากขนาดเกิน 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Hiểu sai ngữ cảnh",
28
28
  "格式不规范":"Định dạng không chuẩn",
29
29
  "内容不完整":"Nội dung không đầy đủ",
30
+ "file 大小超过 100MB,无法发送":"Kích thước tệp vượt quá 100MB, không thể gửi",
31
+ "image 大小超过 20MB,无法发送":"Không thể gửi hình ảnh do vượt quá 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Không thể gửi video do vượt quá 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"上下文理解错误",
28
28
  "格式不规范":"格式不规范",
29
29
  "内容不完整":"内容不完整",
30
+ "file 大小超过 100MB,无法发送": "file 大小超过 100MB,无法发送",
31
+ "image 大小超过 20MB,无法发送":"image 大小超过 20MB,无法发送",
32
+ "video 大小超过 100MB,无法发送":"video 大小超过 100MB,无法发送",
30
33
  }
31
34
  export default AIDesk;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"上下文理解錯誤",
28
28
  "格式不规范":"格式不規範",
29
29
  "内容不完整":"內容不完整",
30
+ "file 大小超过 100MB,无法发送":"檔案大小超過100MB,無法傳送",
31
+ "image 大小超过 20MB,无法发送":"圖片大小超過20MB,無法傳送",
32
+ "video 大小超过 100MB,无法发送":"影片大小超過100MB,無法傳送",
30
33
  }
31
34
  export default AIDesk;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencentcloud/ai-desk-customer-vue",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Vue UIKit for AI Desk",
5
5
  "main": "index",
6
6
  "keywords": [
package/utils/utils.ts CHANGED
@@ -206,3 +206,10 @@ export function switchReadStatus(value: number) {
206
206
  export function getTo(conversation: IConversationModel): string {
207
207
  return conversation?.groupProfile?.groupID || conversation?.userProfile?.userID;
208
208
  }
209
+
210
+ export function isNonEmptyObject(obj: any): boolean {
211
+ if (obj && Object.getPrototypeOf(obj) === Object.prototype && Object.keys(obj).length > 0) {
212
+ return true;
213
+ }
214
+ return false;
215
+ }