cnhis-design-vue 2.1.22 → 2.1.23

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 (122) hide show
  1. package/CHANGELOG.md +2280 -2270
  2. package/es/affix/index.js +8 -8
  3. package/es/age/index.js +10 -10
  4. package/es/alert/index.js +8 -8
  5. package/es/anchor/index.js +8 -8
  6. package/es/auto-complete/index.js +8 -8
  7. package/es/avatar/index.js +8 -8
  8. package/es/back-top/index.js +8 -8
  9. package/es/badge/index.js +8 -8
  10. package/es/base/index.js +8 -8
  11. package/es/big-table/index.js +164 -164
  12. package/es/breadcrumb/index.js +8 -8
  13. package/es/button/index.js +31 -31
  14. package/es/calendar/index.js +8 -8
  15. package/es/captcha/index.js +3 -3
  16. package/es/card/index.js +8 -8
  17. package/es/carousel/index.js +8 -8
  18. package/es/cascader/index.js +8 -8
  19. package/es/checkbox/index.js +9 -9
  20. package/es/col/index.js +8 -8
  21. package/es/collapse/index.js +8 -8
  22. package/es/color-picker/index.js +1 -1
  23. package/es/comment/index.js +8 -8
  24. package/es/config-provider/index.js +8 -8
  25. package/es/date-picker/index.js +8 -8
  26. package/es/descriptions/index.js +8 -8
  27. package/es/divider/index.js +8 -8
  28. package/es/drag-layout/index.js +3 -3
  29. package/es/drawer/index.js +8 -8
  30. package/es/dropdown/index.js +8 -8
  31. package/es/editor/index.js +1 -1
  32. package/es/empty/index.js +8 -8
  33. package/es/fabric-chart/index.js +39 -39
  34. package/es/form/index.js +8 -8
  35. package/es/form-model/index.js +8 -8
  36. package/es/form-table/index.js +62 -62
  37. package/es/index/index.js +708 -685
  38. package/es/index/style.css +1 -1
  39. package/es/input/index.js +9 -9
  40. package/es/input-number/index.js +8 -8
  41. package/es/layout/index.js +8 -8
  42. package/es/list/index.js +8 -8
  43. package/es/locale-provider/index.js +8 -8
  44. package/es/map/index.js +9 -9
  45. package/es/mentions/index.js +8 -8
  46. package/es/menu/index.js +8 -8
  47. package/es/message/index.js +8 -8
  48. package/es/multi-chat/index.js +92 -92
  49. package/es/multi-chat-client/index.js +86 -86
  50. package/es/multi-chat-history/index.js +4 -4
  51. package/es/multi-chat-record/index.js +14 -14
  52. package/es/multi-chat-setting/index.js +27 -27
  53. package/es/multi-chat-sip/index.js +1 -1
  54. package/es/notification/index.js +8 -8
  55. package/es/page-header/index.js +8 -8
  56. package/es/pagination/index.js +8 -8
  57. package/es/popconfirm/index.js +8 -8
  58. package/es/popover/index.js +8 -8
  59. package/es/progress/index.js +8 -8
  60. package/es/radio/index.js +9 -9
  61. package/es/rate/index.js +8 -8
  62. package/es/result/index.js +8 -8
  63. package/es/row/index.js +8 -8
  64. package/es/scale-view/index.js +33 -33
  65. package/es/select/index.js +11 -11
  66. package/es/select-label/index.js +11 -11
  67. package/es/select-person/index.js +81 -58
  68. package/es/select-person/style.css +1 -1
  69. package/es/skeleton/index.js +8 -8
  70. package/es/slider/index.js +8 -8
  71. package/es/space/index.js +8 -8
  72. package/es/spin/index.js +8 -8
  73. package/es/statistic/index.js +8 -8
  74. package/es/steps/index.js +8 -8
  75. package/es/switch/index.js +8 -8
  76. package/es/table-filter/index.js +142 -142
  77. package/es/tabs/index.js +8 -8
  78. package/es/tag/index.js +9 -9
  79. package/es/time-picker/index.js +8 -8
  80. package/es/timeline/index.js +8 -8
  81. package/es/tooltip/index.js +8 -8
  82. package/es/transfer/index.js +8 -8
  83. package/es/tree/index.js +8 -8
  84. package/es/tree-select/index.js +8 -8
  85. package/es/upload/index.js +8 -8
  86. package/es/verification-code/index.js +2 -2
  87. package/lib/cui.common.js +659 -636
  88. package/lib/cui.umd.js +659 -636
  89. package/lib/cui.umd.min.js +13 -13
  90. package/package.json +107 -107
  91. package/packages/big-table/src/BigTable.vue +3044 -3044
  92. package/packages/big-table/src/assets/style/table-base.less +370 -370
  93. package/packages/big-table/src/components/AutoLayoutButton.vue +270 -270
  94. package/packages/big-table/src/utils/batchEditing.js +610 -610
  95. package/packages/big-table/src/utils/bigTableProps.js +95 -95
  96. package/packages/button/src/ButtonPrint/components/IdentityVerification.vue +181 -181
  97. package/packages/button/src/ButtonPrint/index.vue +728 -728
  98. package/packages/fabric-chart/src/components/TimeScaleValue.vue +113 -113
  99. package/packages/fabric-chart/src/const/defaultVaule.js +59 -59
  100. package/packages/fabric-chart/src/fabric-chart/FabricPolylines.vue +1066 -1066
  101. package/packages/fabric-chart/src/fabric-chart/FabricScaleValue.vue +135 -135
  102. package/packages/fabric-chart/src/fabric-chart/FabricTextGroup.vue +558 -558
  103. package/packages/fabric-chart/src/fabric-chart2/FabricTop.vue +172 -172
  104. package/packages/multi-chat/chat/chatFooter.vue +1594 -1594
  105. package/packages/multi-chat/chat/chatMain.vue +1466 -1466
  106. package/packages/multi-chat/chat/quickReply.vue +439 -439
  107. package/packages/multi-chat/chat/scrollList.vue +1232 -1232
  108. package/packages/multi-chat/setting/baseInfo/index.vue +1316 -1316
  109. package/packages/multi-chat/store/actions.js +448 -448
  110. package/packages/multi-chat/store/state.js +112 -112
  111. package/packages/scale-view/formitem/r-choice.vue +714 -714
  112. package/packages/scale-view/scaleView.vue +2010 -2010
  113. package/packages/select-person/select-person.vue +1680 -1658
  114. package/packages/table-filter/src/base-search-com/BaseSearch.vue +2462 -2462
  115. package/packages/table-filter/src/components/c-tree-select/tree-select.vue +336 -336
  116. package/packages/table-filter/src/components/multi-select/multi-select.vue +219 -219
  117. package/packages/table-filter/src/components/out-quick-search/out-quick-search.vue +340 -340
  118. package/packages/table-filter/src/components/search-condition/SearchCondition.vue +1825 -1825
  119. package/packages/table-filter/src/const/dataOptions.js +43 -43
  120. package/packages/table-filter/src/mixins/mixins.js +695 -695
  121. package/packages/table-filter/src/quick-search/QuickSearch.vue +2109 -2109
  122. package/src/directive/preventReClick.js +12 -12
@@ -1,1466 +1,1466 @@
1
- <template>
2
- <div class="message-wrapper">
3
- <chat-header :hideHeader="hideHeader" @openPortraitPanel="handleOpenPortraitPanel" v-if="isServer && enable"></chat-header>
4
- <a-layout-content>
5
- <slot></slot>
6
- <div v-if="showBroadcast" class="broadcast-wrap">
7
- <div v-if="broadcastCarousel" class="carousel-item" :class="{ 'has-link': assemblySetting.broadcast.PC.list[0].url }" @click="goToBroadcastLink(assemblySetting.broadcast.PC.list[0])">
8
- <a-icon type="sound" />
9
- <span class="ellips">
10
- {{ assemblySetting.broadcast.PC.list[0].content }}
11
- </span>
12
- </div>
13
- <template v-else>
14
- <a-carousel dotPosition="right" autoplay :dots="false">
15
- <div class="carousel-item" v-for="(item, i) in assemblySetting.broadcast.PC.list" :key="i" :class="{ 'has-link': item.url }" @click="goToBroadcastLink(item)">
16
- <a-icon type="sound" />
17
- <span class="ellips">{{ item.content }}</span>
18
- </div>
19
- </a-carousel>
20
- </template>
21
- </div>
22
- <div v-if="sessionHistoryList.length > 1 && !(!onChating && currentTab === 'end') && !sessionEnd" style="position: absolute; top: 0; left: 20px; z-index: 1">
23
- <a-radio-group :value="selectedSession" @change="handleSessionIdChange" buttonStyle="solid">
24
- <a-radio-button :value="item.id + '-' + item.fromId" v-for="item in sessionHistoryList" :key="item.id + item.fromId">{{ item.fromName }}</a-radio-button>
25
- </a-radio-group>
26
- </div>
27
- <div class="online-message" ref="message-wrapper" v-infinite-scroll="handleInfiniteOnLoad" @scroll="scrollEvent">
28
- <template v-for="(item, index) in msgList">
29
- <div :key="index" v-if="item">
30
- <p v-if="isNeedShowTime(item, index)" class="message-time">
31
- {{ formatDate(item.createdTime) }}
32
- </p>
33
- <div v-if="item.recallFlag" style="margin: 10px 0">
34
- <template v-if="handleRecallFlag(item)">
35
- {{ getRecallText(item, isServer) }}
36
- <a style="text-decoration: underline" @click="reEditMessage(item)" v-if="[0, 4].includes(item.content && item.content.type) && onChating && isServer">,{{ i18nText('1.9.368') }}</a>
37
- </template>
38
- <template v-else>
39
- {{ getRecallText(item) || `${item.fromName} ${i18nText('1.9.365')}` }}
40
- </template>
41
- </div>
42
- <!-- 系统消息 -->
43
- <div class="system-msg" v-else-if="isSystemMsg(item)">
44
- <a-icon class="system-icon" theme="filled" type="exclamation-circle" />
45
- <div>
46
- <span v-html="getSystemMsgContent(item)"></span>
47
- <span class="system-btn" v-for="(btn, index) in getTemplateButton(item)" @click="handleTemplateButton(btn)" :key="index">{{ btn.title }}</span>
48
- </div>
49
- </div>
50
- <div
51
- v-else-if="!item.recallFlag"
52
- class="message-item"
53
- :class="[getMessageItemClass(item), { 'read-status': enableReadRecord && item.readStatus }]"
54
- :data-rid="item.readStatus === 'N' ? item.id : ''"
55
- >
56
- <img v-if="!isServer && item.fromPortrait" class="msg-avatar" :src="item.fromPortrait" />
57
-
58
- <div v-else-if="!isServer && item.fromName" class="first-name">
59
- {{ item.fromName && item.fromName.slice(0, 2) }}
60
- </div>
61
- <img v-else-if="!isServer" class="msg-avatar" :src="getAvatar(item)" width="40" height="40" />
62
-
63
- <template v-else>
64
- <img :src="customerStaffIcon" class="msg-avatar" width="40" height="40" v-if="item.fromId === 'SYSTEM'" />
65
-
66
- <img src="../img/system_message.png" class="msg-avatar" width="40" height="40" v-else-if="item.content && item.content.source === 1" />
67
-
68
- <img v-else-if="item.portrait" class="msg-avatar" :src="item.portrait" />
69
-
70
- <div v-else class="first-name">
71
- {{ item.fromName && item.fromName.slice(0, 2) }}
72
- </div>
73
- </template>
74
- <div @contextmenu="handleContextmenu($event, item)" style="display: flex">
75
- <a-spin v-if="item.sending">
76
- <a-icon slot="indicator" type="loading" style="font-size: 24px" spin />
77
- </a-spin>
78
- <a-popconfirm @confirm="handleSendFail(item)" :okText="i18nText('1.1.1.1.3')" :cancelText="i18nText('2.7.1.14')">
79
- <a-icon type="exclamation-circle" slot="icon" />
80
- <div slot="title">
81
- <span>确认重新发送该消息?</span>
82
- </div>
83
- <a-icon v-if="item.fail" theme="filled" type="exclamation-circle" style="font-size: 16px; display: flex; align-items: center; color: red" />
84
- </a-popconfirm>
85
- <div class="content-wrap" v-if="item.content.type === 0">
86
- <span class="right-time">
87
- {{ formatDate(item.createdTime || item.sendTime, true) }}
88
- </span>
89
- <div
90
- :class="{
91
- content: true,
92
- 'has-dictionary': item.dictionaryValues && item.dictionaryValues.length,
93
- 'has-intention': item.intentionList && item.intentionList.length
94
- }"
95
- >
96
- <p v-html="getContentHtml(item)"></p>
97
- <template v-if="item.content.attachments && item.content.attachments.length > 0">
98
- <div v-for="(v, index) in item.content.attachments" :key="index" class="robot-item">
99
- <a v-if="v.type == 1" :href="v.url" target="_blank"> {{ v.desc }}(超链) </a>
100
- <a @click.prevent.stop="$emit('sendQuestion', v, item)" v-if="v.type == 2 || v.type == 3" href="javascript:void(0);">
101
- {{ v.desc }}
102
- </a>
103
- </div>
104
- </template>
105
- <div v-if="item.intentionList && item.intentionList.length" class="intention-wrap">
106
- <div v-for="(v, index) in item.intentionList" :key="index" @click.prevent.stop="$emit('sendIntention', v)">
107
- <svg-icon :icon-class="v.icon || 'moren'" style="font-size: 26px"></svg-icon>
108
- <span>{{ v.name }}</span>
109
- </div>
110
- </div>
111
- </div>
112
- <ul class="dictionary-ul" v-if="item.dictionaryValues && item.dictionaryValues.length">
113
- <li v-for="(v, i) in item.dictionaryValues" :key="i" @click.prevent.stop="$emit('sendDictionary', v, item)">
114
- {{ v }}
115
- </li>
116
- </ul>
117
- <div class="client-btn" v-if="item.autoAccess && item.autoAccess === 'false'">
118
- <svg-icon icon-class="kefu"></svg-icon>
119
- <span @click="$emit('goCustomServe')">转人工客服</span>
120
- </div>
121
- </div>
122
- <div v-else-if="item.content.type === 1" class="upload-image">
123
- <MsgPicture style="max-width: 345px" :image="item.content.content" @click="show(item)" />
124
- </div>
125
- <div class="content-wrap" v-else-if="item.content.type === 2">
126
- <span class="right-time">
127
- {{ formatDate(item.createdTime || item.sendTime, true) }}
128
- </span>
129
- <div class="content">
130
- <img style="width: 16px;margin-right:8px" src="../img/multi-video.png" />
131
- {{ item.content.content }}&nbsp;
132
- <span @click.prevent.stop="reCall(item, 'video')" class="re-call" v-if="item.content.isRefuse == 1">
133
- {{ handleReCall(item) }}
134
- </span>
135
- </div>
136
- </div>
137
- <div class="content-wrap" v-else-if="item.content.type === 5">
138
- <span class="right-time">
139
- {{ formatDate(item.createdTime || item.sendTime, true) }}
140
- </span>
141
- <div class="content">
142
- <img style="width: 16px;margin-right:8px" src="../img/multi-voice.png" />
143
- {{ item.content.content }}&nbsp;
144
- <span @click.prevent.stop="reCall(item, 'voice')" class="re-call" v-if="item.content.isRefuse == 1">
145
- {{ handleReCall(item) }}
146
- </span>
147
- </div>
148
- </div>
149
- <div class="content-wrap" v-else-if="item.content.type === 6">
150
- <span class="right-time">
151
- {{ formatDate(item.createdTime || item.sendTime, true) }}
152
- </span>
153
- <div class="voice-bg content" @click.prevent.stop="playAudio(item)">
154
- <img v-if="item.content.isPlay" src="../img/audio-play.gif" alt="" />
155
- <img v-else src="../img/audio-new.png" alt="" />
156
- <span class="voice-text" :style="AudioStyle(item)"> {{ item.content.duration || 1 }}" </span>
157
- </div>
158
- </div>
159
- <template v-else-if="item.content.type === 4">
160
- <msg-describe v-if="isDescribeMsg(item)" :item="item" />
161
- <div v-else class="content-wrap">
162
- <span class="right-time">
163
- {{ formatDate(item.createdTime || item.sendTime, true) }}
164
- </span>
165
- <MsgPrescription :data="item" :isServer="isServer" :theme="isServer ? '' : 'customer'" @handleTemplateDetail="handleTemplateDetail" @handleTemplateButton="handleTemplateButton" />
166
- </div>
167
- </template>
168
- </div>
169
- <ReadStatus v-if="enableReadRecord" :status="item.readStatus" @click="openReadRecord($event, item)" />
170
- </div>
171
- </div>
172
- </template>
173
- <ul class="menu-main" ref="contextMenu" :style="contextMenuStyle" v-if="contextMenuShow">
174
- <template v-for="(item, index) in rightClickSetting">
175
- <li :key="index" class="menu-item" @click="() => handleRightClick(contextMenuItem, item)">
176
- {{ item.rightClickName }}
177
- </li>
178
- </template>
179
- </ul>
180
- <div class="session-end" v-if="sessionEnd && !isServer">
181
- <span class="end">{{ i18nText('1.9.370') }}</span>
182
- </div>
183
- <a-spin v-if="loading" class="loading-icon" />
184
- </div>
185
- <!-- 图片预览 -->
186
- <viewer class="viewer" ref="viewer" :images="images" @inited="inited" :options="options" style="display: none">
187
- <template slot-scope="scope">
188
- <img v-for="src in scope.images" :src="src" :key="src" />
189
- </template>
190
- </viewer>
191
- <!-- 卡片详情 -->
192
- <a-modal
193
- wrapClassName="chat-footer-modal-small"
194
- :title="modalData.title || i18nText('1.1.1.10')"
195
- :visible="modalShow"
196
- :maskClosable="false"
197
- :mask="true"
198
- width="80%"
199
- destroyOnClose
200
- :okText="i18nText('1.2.1.11.6')"
201
- :footer="null"
202
- @cancel="handleClose"
203
- >
204
- <iframe id="chat-footer-modal" v-if="modalData.targetType === 'LINK_ADDRESS'" width="100%" height="100%" frameborder="0" :src="modalData.address" allow="camera;midi"></iframe>
205
- </a-modal>
206
- </a-layout-content>
207
- <audio style="display: none" controls autoplay ref="audio" preload="auto" crossOrigin="anonymous" media-player="audioPlayer" :src="curAudioUrl" @ended="audioEnd"></audio>
208
- <ReadRecord v-if="readRecordVisible" :style="readRecordStyle" :list="readRecordList" @close="closeReadRecord" />
209
- </div>
210
- </template>
211
-
212
- <script>
213
- import { Layout, Modal, Icon, Spin, Popconfirm, Carousel, Radio } from 'ant-design-vue';
214
- import SvgIcon from '@/component/svg/index.vue';
215
- import InfiniteScroll from '@/directive/scroll';
216
- import { mapGetters, mapMutations, mapActions } from '../store/helper';
217
- import fetch, { qs } from '@/utils/chatFetch';
218
- import MsgDescribe from '../components/msg-describe';
219
- import MsgPicture from '../components/msg-picture';
220
- import MsgPrescription from '../components/msg-prescription';
221
- import ReadStatus from '../components/read-status';
222
- import ReadRecord from '../components/read-record';
223
- import chatHeader from './chatHeader';
224
- import viewerOptions from './mixins/viewerOptions';
225
- import { getRecallText } from '../utils';
226
- import vexutils from '@/utils/vexutils';
227
- import ObserverScroll from '../utils/observer-scroll';
228
-
229
- const customerStaffIcon = require('../img/customer_staff.png');
230
- export default {
231
- mixins: [viewerOptions],
232
- inject: ['store', 'dispatchEvent', 'registerEvent', 'unregisterEvent', 'i18nText'],
233
- props: {
234
- hideHeader: {
235
- type: Boolean,
236
- default: false
237
- },
238
- robotAvatar: {
239
- type: String,
240
- default: ''
241
- },
242
- curChatType: {
243
- type: String
244
- },
245
- isShowPortraitPanel: {
246
- type: Boolean
247
- },
248
- activatedTime: {
249
- type: Number
250
- }
251
- },
252
- data() {
253
- return {
254
- loading: false,
255
- // 卡片详情
256
- modalShow: false,
257
- modalData: {},
258
- // 客户头像
259
- // 右键操作
260
- contextMenuStyle: {
261
- position: 'fixed',
262
- top: 0,
263
- left: 0
264
- },
265
- contextMenuShow: false,
266
- contextMenuItem: null,
267
- // 查看历史消息滚动定位
268
- scrollHeight: 0,
269
- curAudioUrl: '',
270
- lastAudioItem: null,
271
- readRecordStyle: '',
272
- readRecordVisible: false,
273
- readRecordList: []
274
- };
275
- },
276
- computed: {
277
- ...mapGetters([
278
- 'userInfo',
279
- 'currentTab',
280
- 'isAppendMsg',
281
- 'message',
282
- 'msgList',
283
- 'sessionId',
284
- 'serviceId',
285
- 'scrollTo',
286
- 'isServer',
287
- 'clientId',
288
- 'sessionEnd',
289
- 'assemblyId',
290
- 'footerMessage',
291
- 'portraitPanelParams',
292
- 'assemblySetting',
293
- 'clientParams',
294
- 'onChating',
295
- 'classify',
296
- 'collapsed',
297
- 'barStatus',
298
- 'sessionHistoryList',
299
- 'enable',
300
- 'appendList',
301
- 'queueItem',
302
- 'isRecorderVoice'
303
- ]),
304
- orgId() {
305
- const getters = this.store.getters;
306
- return this.isServer ? this.userInfo?.sysParams?.orgId : getters.accessParams.orgId || getters.queryParams.orgId;
307
- },
308
- userId() {
309
- const getters = this.store.getters;
310
- return this.isServer ? this.userInfo?.sysParams?.userId : getters.accessParams.userId || getters.chatUserId || getters.queryParams.userId;
311
- },
312
- enableReadRecord() {
313
- return this.assemblySetting?.readStatus === 'Y';
314
- },
315
- customerStaffIcon() {
316
- return this.robotAvatar || customerStaffIcon;
317
- },
318
- selectedSession() {
319
- let sessionMap = this.sessionHistoryList.find(item => item.fromId === this.serviceId);
320
- if (sessionMap) {
321
- return sessionMap.id + '-' + sessionMap.fromId;
322
- }
323
- return '';
324
- },
325
- isListClassify() {
326
- return this.assemblySetting.isListClassify === 'Y';
327
- },
328
- showPanel() {
329
- return this.assemblySetting.portraitPanelSetting?.targetIs;
330
- },
331
- showBroadcast() {
332
- let res = (!this.isServer && this.assemblySetting?.broadcast?.PC?.isChecked == 'Y' && this.assemblySetting?.broadcast?.PC?.list?.length > 0) || false;
333
- return res;
334
- },
335
- broadcastCarousel() {
336
- return (this.assemblySetting.broadcast && this.assemblySetting.broadcast.PC.list.length == 1) || false;
337
- },
338
- rightClickSetting() {
339
- const { isServer } = this;
340
- if (!isServer) return [];
341
- const isSelf = this.contextMenuItem.fromId === this.serviceId;
342
- const setting = this.assemblySetting?.rightClickSetting || [];
343
- const doArr = ['resend', 'rollBack'];
344
- return setting.filter(item => {
345
- const { rightClickDo } = item;
346
- return isSelf || doArr.indexOf(rightClickDo) < 0;
347
- });
348
- },
349
- handleReCall() {
350
- return function(item) {
351
- if (item.content?.source === 1) {
352
- return '[点击回拨]';
353
- } else if (item.senderFlag == 1) {
354
- return `[点击${this.isServer ? '回拨' : '重拨'}]`;
355
- }
356
- if (item.senderFlag == 2) {
357
- return `[点击${this.isServer ? '重拨' : '回拨'}]`;
358
- }
359
- if (this.isServer) {
360
- let isRight = item.fromId !== 'SYSTEM' && item.fromId && item.fromId === this.serviceId;
361
- return `[点击${isRight ? '重拨' : '回拨'}]`;
362
- } else {
363
- let isRight = (item.fromId !== 'SYSTEM' && item.toId === 'SYSTEM') || item.fromId === this.clientId || (item.mode === 'bot' && !item.fromId);
364
- return `[点击${isRight ? '重拨' : '回拨'}]`;
365
- }
366
- };
367
- },
368
- handleRecallFlag() {
369
- return function(item) {
370
- return this.serviceId == item.fromId;
371
- };
372
- },
373
- AudioStyle() {
374
- return function(item) {
375
- let { duration = 1 } = item.content;
376
- if (!duration) {
377
- duration = 1;
378
- }
379
- let curWidth = (+duration / 60) * 400;
380
- return {
381
- width: curWidth < 13 ? 13 : curWidth + 'px'
382
- };
383
- };
384
- },
385
- showTimeDuration() {
386
- let { variableSetting = null } = this.assemblySetting || {};
387
- if (!variableSetting) return 3;
388
- let matchItem = variableSetting.find(v => v.name === 'CHAT_SEPARATION_TIME');
389
- if (!matchItem) return 3;
390
- if (!+matchItem.value) return 3;
391
- return +matchItem.value;
392
- },
393
- images() {
394
- return this.msgList.filter(v => !v.recallFlag && v.content.type == 1)?.map(v => v.content?.content);
395
- }
396
- },
397
- created() {
398
- this.attachEvent();
399
- },
400
- methods: {
401
- ...mapMutations([
402
- 'setIsAppendMsg',
403
- 'setMsgList',
404
- 'setScrollTo',
405
- 'setFooterMessage',
406
- 'setIsScrollListChange',
407
- 'setListChangeItem',
408
- 'setSessionId',
409
- 'setOnChating',
410
- 'setServiceId',
411
- 'setShowAudio',
412
- 'setVideoMode',
413
- 'setVideoMembers',
414
- 'setIsRecorderVoice'
415
- ]),
416
- ...mapActions({
417
- getEarlierMsg: 'setMsgList',
418
- handleBotChat: 'handleBotChat',
419
- sendMessage: 'sendMessage'
420
- }),
421
- getMessageItemClass(item) {
422
- // content.source === 1, 代表为系统消息, 系统统一左侧展示
423
- if (item.content?.source === 1) {
424
- return {
425
- left: true
426
- };
427
- }
428
-
429
- // 服务端 senderFlag 1 左 2 右
430
- // 客户端 senderFlag 1 右 2 左
431
- if (item.content?.source === 1) {
432
- return {
433
- left: true
434
- };
435
- } else if (item.senderFlag == 1) {
436
- if (this.isServer) {
437
- return {
438
- left: true
439
- };
440
- } else {
441
- return {
442
- right: true
443
- };
444
- }
445
- }
446
- if (item.senderFlag == 2) {
447
- if (this.isServer) {
448
- return {
449
- right: true
450
- };
451
- } else {
452
- return {
453
- left: true
454
- };
455
- }
456
- }
457
- if (this.isServer) {
458
- return {
459
- right: item.fromId !== 'SYSTEM' && item.fromId && item.fromId === this.serviceId,
460
- left: item.fromId === 'SYSTEM' || item.fromId === this.clientId || item.content?.source === 1 || !item.fromId
461
- };
462
- } else {
463
- let isRight, isLeft;
464
- if (this.curChatType == 'robot') {
465
- isRight = item.fromId !== 'SYSTEM';
466
- isLeft = item.fromId === 'SYSTEM';
467
- } else {
468
- isRight = (item.fromId !== 'SYSTEM' && item.toId === 'SYSTEM') || item.fromId === this.clientId || (item.mode === 'bot' && !item.fromId);
469
- isLeft = item.fromId === 'SYSTEM' || item.fromId !== this.clientId || item.content?.source === 1;
470
- }
471
- return {
472
- right: isRight,
473
- left: isLeft
474
- };
475
- }
476
- },
477
- /* 向上滚动刷新 */
478
- handleInfiniteOnLoad() {
479
- if (this.curChatType === 'robot') return;
480
- this.loading = true;
481
- this.getEarlierMsg().then(msg => {
482
- if (msg) {
483
- this.$message.info(msg);
484
- }
485
- this.loading = false;
486
- });
487
- },
488
- show(item) {
489
- const src = item.content.content;
490
- // this.images = vexutils.imgs2imgArr(src);
491
- this.$viewer.view(this.images.findIndex(v => v === src));
492
- this.$viewer.show();
493
- this.readSingleMsg(item);
494
- },
495
- /* 滚动条定位 */
496
- handleScrollTo(scrollTo) {
497
- let target = this.$refs['message-wrapper'];
498
- this.$nextTick().then(() => {
499
- if (!target) return;
500
- let top = target.scrollHeight;
501
- if (scrollTo === 'current') {
502
- top = target.scrollHeight - this.scrollHeight;
503
- }
504
- if (typeof scrollTo === 'number') {
505
- top = scrollTo;
506
- }
507
- target.scrollTo({
508
- left: 0,
509
- top,
510
- behavior: 'instant'
511
- });
512
- this.scrollHeight = 0;
513
- });
514
- this.setScrollTo('');
515
- },
516
-
517
- /**
518
- * 是否需要显示时间
519
- */
520
- isNeedShowTime(item, index) {
521
- const curMsgTime = item.createdTime || new Date().getTime();
522
- let duration = 60 * 1000 * this.showTimeDuration;
523
- let lastMsgTime;
524
- if (index == 0) {
525
- lastMsgTime = 0;
526
- } else {
527
- lastMsgTime = this.msgList[index - 1].createdTime;
528
- }
529
- if (Number(curMsgTime) - Number(lastMsgTime) > duration) {
530
- return true;
531
- } else {
532
- return false;
533
- }
534
- },
535
- formatDate(date) {
536
- return vexutils.formatDate(date, true);
537
- },
538
- getAvatar(item) {
539
- if (item.content && item.content.source === 1) {
540
- return require('../img/system_message.png');
541
- }
542
- if (item.mode === 'bot' && !item.fromId) {
543
- return require('../img/default.png');
544
- }
545
- if (this.isServer) {
546
- if (item.fromId === this.clientId || !item.fromId) {
547
- return require('../img/default.png');
548
- } else {
549
- return this.customerStaffIcon;
550
- }
551
- } else {
552
- if (this.curChatType == 'robot') {
553
- if (item.fromId === 'SYSTEM') {
554
- return this.customerStaffIcon;
555
- } else {
556
- return require('../img/default.png');
557
- }
558
- } else {
559
- if (item.fromId !== this.clientId) {
560
- return this.customerStaffIcon;
561
- } else {
562
- return require('../img/default.png');
563
- }
564
- }
565
- }
566
- },
567
- getContent(item) {
568
- let content;
569
- if (vexutils.isJSON(item.content.content)) {
570
- content = JSON.parse(item.content.content);
571
- } else {
572
- content = item.content.content;
573
- }
574
- return content;
575
- },
576
- getContentHtml(item) {
577
- return !~item.content.content.indexOf('emoji') ? this.$xss(item.content.content) : item.content.content;
578
- },
579
- getTemplateTitle(item) {
580
- return this.getContent(item)?.title;
581
- },
582
- showArrow(item) {
583
- let setting = this.getContent(item)?.customerSetting || {};
584
- if (this.isServer) {
585
- setting = this.getContent(item)?.serverSetting || {};
586
- }
587
- let { address } = setting;
588
- if (!address) return false;
589
- return true;
590
- },
591
- getTemplateTitleIcon(item) {
592
- return this.getContent(item)?.icon;
593
- },
594
- getTemplateContent(item) {
595
- let content = this.getContent(item);
596
- let array = [];
597
- try {
598
- array = content.content.split('##');
599
- } catch {
600
- console.log(content, 'error');
601
- }
602
- array = array.map(item => `<div>${item}</div>`);
603
- return array.join('');
604
- },
605
- getSystemMsgContent(item) {
606
- let content = this.getContent(item);
607
- let array = [];
608
- try {
609
- array = content.content.split('##');
610
- } catch {
611
- console.log(content, 'error');
612
- }
613
- return array.join('<br>');
614
- },
615
- getTemplateButton(item) {
616
- let content = this.getContent(item);
617
- const setting = this.isServer ? content.serverSetting : content.customerSetting;
618
- return setting?.toolbar_button || [];
619
- },
620
- // 处理模板按钮,详情等跳转
621
- handleTemplate(data, dispatch = true) {
622
- let { targetType, address, params = [], openMode } = data;
623
- dispatch &&
624
- this.dispatchEvent('click_templateCard', {
625
- ...data
626
- });
627
- if (!address) return;
628
- if (targetType === 'LINK_ADDRESS') {
629
- let urlParams = [];
630
- params.forEach(({ p_name, p_value }) => {
631
- urlParams.push(`${p_name}=${p_value}`);
632
- });
633
- if (address.includes('?')) {
634
- address += `&${urlParams.join('&')}`;
635
- } else {
636
- address += `?${urlParams.join('&')}`;
637
- }
638
- if (openMode === 'WINDOW') {
639
- window.open(address);
640
- return;
641
- }
642
- if (openMode === 'EJECT') {
643
- this.modalShow = true;
644
- this.modalData = Object.assign({}, data, {
645
- address
646
- });
647
- this.$nextTick().then(() => {
648
- window.addEventListener('message', this.iframeEvent);
649
- });
650
- return;
651
- }
652
- }
653
- },
654
- iframeEvent(event) {
655
- const method = event?.data?.method;
656
- this[method]?.(event);
657
- },
658
- // 发送
659
- handleParentMessageSend({ data, source, origin }) {
660
- let params = {
661
- assemblyId: this.assemblyId,
662
- sessionId: this.sessionId,
663
- orgId: this.orgId
664
- };
665
- params = Object.assign(params, data.data);
666
- return fetch.post('/chat/access/sendToolBarData', qs.stringify(params)).then(({ data }) => {
667
- if (data.result === 'SUCCESS') {
668
- source.postMessage({ status: 0, resultMsg: data.resultMsg }, origin);
669
- } else {
670
- source.postMessage({ status: 1, resultMsg: data.resultMsg }, origin);
671
- }
672
- });
673
- },
674
- // 关闭
675
- handleClose() {
676
- this.modalShow = false;
677
- window.removeEventListener('message', this.iframeEvent);
678
- },
679
- handleTemplateDetail(item) {
680
- let content = this.getContent(item) || {};
681
- let setting = content.customerSetting || {};
682
- if (this.isServer) {
683
- setting = content.serverSetting || {};
684
- }
685
- this.readSingleMsg(item);
686
- this.handleTemplate(setting);
687
- },
688
- handleTemplateButton(btn) {
689
- this.handleTemplate(btn);
690
- },
691
- // 消息撤回
692
- handleRecall(item) {
693
- let params = {
694
- assemblyId: this.assemblyId,
695
- sessionId: this.sessionId,
696
- messageId: item.id,
697
- messageType: item.content?.type,
698
- userId: this.userInfo?.sysParams?.userId || ''
699
- };
700
-
701
- fetch.post('/chat/service/recallMessage', qs.stringify(params)).then(({ data }) => {
702
- if (data.result === 'SUCCESS') {
703
- this.dispatchEvent('msg_recallSuccess', {
704
- message: { ...item },
705
- ...params
706
- });
707
- this.$set(item, 'recallFlag', 1);
708
- // 撤回成功提示
709
- let title = this.getContent(item)?.title;
710
- let text = title ? `已撤回[${title}]` : '已撤回';
711
- this.$message.success(text);
712
-
713
- if (!this.isLastMessage(item.id)) return;
714
- this.setIsScrollListChange(true);
715
- this.setListChangeItem({
716
- opt: 1,
717
- from: this.clientId,
718
- sessionId: this.sessionId,
719
- msgCount: 0,
720
- lastContentRecallFlag: 1,
721
- lastContent: vexutils.clone(item.content, true),
722
- lastTime: new Date().getTime()
723
- });
724
- } else {
725
- this.$message.error(data.resultMsg);
726
- }
727
- });
728
- },
729
- handleJumpUrl(contextMenuItem, item) {
730
- const title = item.rightClickName;
731
- const url = item.rightClickUrl.replace(/{(.*?)}/g, (match, p) => {
732
- return eval(p);
733
- });
734
- this.dispatchEvent('open_jumpUrl', {
735
- title: title,
736
- url: url
737
- });
738
- },
739
- // 右键菜单功能
740
- handleRightClick(contextMenuItem, item) {
741
- if (item.rightClickDo === 'rollBack') {
742
- this.handleRecall(contextMenuItem);
743
- } else if (item.rightClickDo === 'jumpUrl') {
744
- this.handleJumpUrl(contextMenuItem, item);
745
- } else if (item.rightClickDo === 'resend') {
746
- this.resendMessage(contextMenuItem);
747
- }
748
- },
749
- resendMessage(item) {
750
- fetch({
751
- method: 'POST',
752
- url: '/chat/service/resendMessage',
753
- data: qs.stringify({
754
- assemblyId: this.assemblyId,
755
- sessionId: this.sessionId,
756
- messageId: item.id
757
- })
758
- });
759
- },
760
- // 重新编辑
761
- reEditMessage(item) {
762
- if (item.content?.type === 4) {
763
- let content = this.getContent(item);
764
- this.handleTemplate(content.serverSetting.reEditSetting, false);
765
- return;
766
- }
767
- this.setFooterMessage(this.footerMessage + item.content.content.slice(5, -6));
768
- },
769
- observeMsg() {
770
- if (this.enableReadRecord) {
771
- if (!this._observer) {
772
- this._observer = new ObserverScroll(this.$refs['message-wrapper'], this.readMsg);
773
- this.$on('hook:beforeDestroy', () => {
774
- this._observer.remove();
775
- this._observer = null;
776
- });
777
- }
778
- this._observer.disconnect();
779
- if (this.msgList.length) {
780
- this.$nextTick(() => {
781
- const el = this.$refs['message-wrapper'];
782
- const els = el?.querySelectorAll('.message-item.left');
783
- els && this._observer.observe(Array.from(els).filter(item => item.getAttribute('data-rid')));
784
- });
785
- }
786
- }
787
- },
788
- readMsg(ids, enableVaid = true, notify = true, post = true) {
789
- const msgList = this.msgList;
790
- const idKeys = msgList.reduce((obj, item, i) => {
791
- const id = item?.id;
792
- if (id) obj[id] = i;
793
- return obj;
794
- }, {});
795
- const data = [];
796
- const validFn = (item, type) => {
797
- /**
798
- * 针对文本、表情消息类型,只要在聊天区域内,视为已读,
799
- * 图片、语音类型消息,需要点击查看后,才视为已读,
800
- * 模板 类型消息,如果没有详情链接,只要在聊天区域内,视为已读,若有详情链接,需要点击查看后,才视为已读
801
- */
802
- const jumpType = `,6,1,`;
803
- if (jumpType.includes(type)) return false;
804
- if (type == 4 && !this.isDescribeMsg(item)) {
805
- let content = this.getContent(item) || {};
806
- let setting = (this.isServer ? content.serverSetting : content.customerSetting) || {};
807
- return !setting?.address;
808
- }
809
- return true;
810
- };
811
- const value = ids.filter(id => {
812
- const i = idKeys[id];
813
- let item = msgList[i];
814
- if (item) {
815
- const type = item.content.type;
816
- if (!enableVaid || validFn(item, type)) {
817
- item.readStatus = 'Y';
818
- data.push({ id, type });
819
- return true;
820
- }
821
- return false;
822
- }
823
- });
824
- if (!value.length) return;
825
- notify &&
826
- this.dispatchEvent('msg_readed', {
827
- data,
828
- assemblyId: this.assemblyId,
829
- sessionId: this.sessionId
830
- });
831
- post && this.postReadMessage({ ids: value.join(',') });
832
- },
833
- postReadMessage(data) {
834
- fetch.post(
835
- `/chat/${this.isServer ? 'service' : 'access'}/readMessage`,
836
- qs.stringify({
837
- assemblyId: this.assemblyId,
838
- orgId: this.orgId,
839
- userId: this.userId,
840
- sessionId: this.sessionId,
841
- ...data
842
- })
843
- );
844
- },
845
- readSingleMsg(item) {
846
- if (this.enableReadRecord && item.readStatus === 'N') {
847
- const cls = this.getMessageItemClass(item);
848
- if (!cls?.left) return;
849
- this.readMsg([item.id], false);
850
- }
851
- },
852
- closeReadRecord() {
853
- this.readRecordVisible = false;
854
- this.readRecordList = [];
855
- },
856
- openReadRecord(event, item) {
857
- const readStatus = item.readStatus;
858
- if (readStatus) {
859
- const cls = this.getMessageItemClass(item);
860
- if (!cls.right) return;
861
- const rect = event.target.getBoundingClientRect();
862
- const w = 440;
863
- const h = 340;
864
- const top = Math.max(0, Math.min(document.body.clientHeight - h, rect.top - h / 2));
865
- const left = Math.max(0, Math.min(document.body.clientWidth - w, rect.left - w));
866
- fetch({
867
- url: `/chat/${this.isServer ? 'service' : 'access'}/getMessageMember`,
868
- method: 'get',
869
- params: {
870
- assemblyId: this.assemblyId,
871
- messageId: item.id,
872
- orgId: this.isServer ? undefined : this.orgId
873
- }
874
- }).then(res => {
875
- const list = res.data?.list || [];
876
- this.readRecordStyle = `top:${top}px;left:${left}px;position:fixed;z-index:10`;
877
- this.readRecordVisible = list.length > 0;
878
- this.readRecordList = Object.freeze(list);
879
- if (item.readStatus && list.length) {
880
- const unread = list.filter(item => item.readStatus == 'N').length;
881
- const single = list.length < 2;
882
- const status = unread > 0 ? (single ? 'N' : `${unread}`) : single ? 'Y' : 'ALL';
883
- // 检查是否更新人数
884
- if (item.readStatus != status) {
885
- item.readStatus = status;
886
- }
887
- }
888
- });
889
- }
890
- },
891
- isLastMessage(id) {
892
- return this.msgList.findIndex(msg => msg.id === id) === this.msgList.length - 1;
893
- },
894
- isSystemMsg(item) {
895
- return item.content.type == 4 && this.getContent(item).styleId == '2';
896
- },
897
- isDescribeMsg(item) {
898
- return this.getContent(item).styleId == '3';
899
- },
900
- async handleOpenPortraitPanel(item) {
901
- if (!this.showPanel || item.fromId === this.serviceId || !this.isServer || !this.isShowPortraitPanel) return;
902
- const { portraitPanelParams, clientParams } = this;
903
- const panelSetting = this.assemblySetting?.portraitPanelSetting;
904
- this.dispatchEvent('open_portraitPanel', {
905
- panelSetting,
906
- clientParams,
907
- panelParams: portraitPanelParams,
908
- targetId: panelSetting?.targetId,
909
- targetType: panelSetting?.targetType
910
- });
911
- },
912
- handleContextmenu(e, item) {
913
- // 系统消息,客户消息,待接入消息,历史消息不允许撤回
914
- // 模板-描述消息禁用右键
915
- if (item.content?.source === 1 || !this.sessionId || (this.sessionId && !this.onChating) || (item.content.type === 4 && this.isDescribeMsg(item))) return;
916
- this.contextMenuItem = item;
917
- if (this.rightClickSetting.length < 1) return;
918
- e.preventDefault();
919
- let left = e.pageX + 'px';
920
- let top = e.pageY + 'px';
921
- this.contextMenuStyle = Object.assign(this.contextMenuStyle, {
922
- top,
923
- left
924
- });
925
- this.contextMenuShow = true;
926
- },
927
- scrollEvent(e) {
928
- console.log('e.target.scrollTop->', e.target.scrollTop);
929
- this._scrollTop = e.target.scrollTop;
930
- },
931
- /* 事件绑定 */
932
- attachEvent() {
933
- this.scrollEvent = vexutils.debounce(this.scrollEvent, 800);
934
- const handleClick = () => {
935
- this.contextMenuShow = false;
936
- };
937
- const handleRasize = () => {
938
- if (this.readRecordVisible) {
939
- let style = this.readRecordStyle;
940
- const top = style.match(topReg)?.[1];
941
- const topReg = /top:([\d.]+)/;
942
- const maxH = document.body.clientHeight - 360;
943
- if (top && maxH < top) {
944
- style = style.replace(topReg, `top:${maxH}`);
945
- }
946
- const leftReg = /left:([\d.]+)/;
947
- const left = style.match(leftReg)?.[1];
948
- const maxW = document.body.clientWidth - 440;
949
- if (left && maxW < left) {
950
- style = style.replace(leftReg, `left:${maxW}`);
951
- }
952
- this.readRecordStyle = style;
953
- }
954
- };
955
- window.addEventListener('resize', handleRasize);
956
- document.addEventListener('click', handleClick);
957
- this.registerEvent('openPortraitPanel', this.handleOpenPortraitPanel);
958
- const readMessage = data => {
959
- this.readMsg(data.ids.split(','), false, false, false);
960
- this.postReadMessage(data);
961
- };
962
- this.registerEvent('readMessage', readMessage);
963
- this.$on('hook:beforeDestroy', () => {
964
- window.removeEventListener('resize', handleRasize);
965
- document.removeEventListener('click', handleClick);
966
- this.unregisterEvent('openPortraitPanel', this.handleOpenPortraitPanel);
967
- this.unregisterEvent('readMessage', readMessage);
968
- });
969
- },
970
- goToBroadcastLink(item) {
971
- if (!item.url) return;
972
- window.open(item.url);
973
- },
974
- /**
975
- * 客服切换
976
- */
977
- handleSessionIdChange(e) {
978
- let [sessionId, serviceId] = e.target.value.split('-');
979
- this.setSessionId(sessionId);
980
- this.setServiceId(serviceId);
981
- let isOnChating = this.sessionHistoryList.find(item => item.fromId === serviceId).onChating;
982
- this.setOnChating(!!isOnChating);
983
- this.setMsgList([]);
984
- this.getEarlierMsg();
985
- },
986
- handleSendFail(item) {
987
- this.sendMessage(Object.assign(item.content, { failItem: item }));
988
- },
989
- reCall(type) {
990
- this.setShowAudio(true);
991
- this.setVideoMode(type === 'voice' ? 1 : 2);
992
- this.$nextTick(() => {
993
- this.setVideoMembers([]);
994
- });
995
- },
996
- playAudio(item) {
997
- let { content = '' } = item?.content || {};
998
- if (!content) return;
999
- if (this.lastAudioItem && (item.id !== this.lastAudioItem.id || item.createdTime !== this.lastAudioItem.createdTime)) {
1000
- this.$set(this.lastAudioItem.content, 'isPlay', false);
1001
- }
1002
-
1003
- this.readSingleMsg(item);
1004
- this.lastAudioItem = item;
1005
- this.$set(item.content, 'isPlay', !item.content.isPlay);
1006
- this.$nextTick(() => {
1007
- this.curAudioUrl = content;
1008
- let audioRef = this.$refs['audio'];
1009
- if (!audioRef) return;
1010
- audioRef.load();
1011
- setTimeout(() => {
1012
- if (item.content.isPlay) {
1013
- let playPromise = audioRef.play();
1014
- if (playPromise !== undefined) {
1015
- playPromise.then(() => {
1016
- audioRef.play();
1017
- });
1018
- }
1019
- } else {
1020
- audioRef.pause();
1021
- this.curAudioUrl = '';
1022
- }
1023
- });
1024
- });
1025
- },
1026
- audioEnd() {
1027
- if (!this.lastAudioItem) return;
1028
- this.$set(this.lastAudioItem.content, 'isPlay', false);
1029
- },
1030
- getRecallText(item, flag = false) {
1031
- return getRecallText(this, item, flag);
1032
- }
1033
- },
1034
- directives: {
1035
- InfiniteScroll
1036
- },
1037
- components: {
1038
- AModal: Modal,
1039
- [Layout.Content.name]: Layout.Content,
1040
- [Spin.name]: Spin,
1041
- [Icon.name]: Icon,
1042
- [Popconfirm.name]: Popconfirm,
1043
- [Radio.Button.name]: Radio.Button,
1044
- [Radio.Group.name]: Radio.Group,
1045
- [Carousel.name]: Carousel,
1046
- SvgIcon,
1047
- MsgPrescription,
1048
- ReadStatus,
1049
- ReadRecord,
1050
- MsgPicture,
1051
- MsgDescribe,
1052
- chatHeader
1053
- },
1054
- watch: {
1055
- msgList() {
1056
- this.observeMsg();
1057
- },
1058
- isAppendMsg: {
1059
- immediate: true,
1060
- handler(isAppendMsg) {
1061
- if (isAppendMsg) {
1062
- let newArr = [];
1063
- if (vexutils.isPlainObject(this.message)) {
1064
- newArr = [...this.msgList, this.message];
1065
- } else if (vexutils.isArray(this.message)) {
1066
- newArr = [...this.msgList, ...this.message];
1067
- }
1068
- this.setMsgList(newArr);
1069
- this.setIsAppendMsg(false);
1070
- this.setScrollTo('bottom');
1071
- }
1072
- }
1073
- },
1074
- scrollTo: {
1075
- immediate: true,
1076
- handler(scrollTo) {
1077
- if (scrollTo) {
1078
- this.handleScrollTo(scrollTo);
1079
- }
1080
- }
1081
- },
1082
- activatedTime: {
1083
- immediate: true,
1084
- handler(value) {
1085
- this.handleScrollTo(this._scrollTop);
1086
- }
1087
- },
1088
- /**
1089
- * 图片懒加载处理
1090
- */
1091
- appendList: {
1092
- handler() {
1093
- if (!this.scrollHeight) {
1094
- let target = this.$refs['message-wrapper'];
1095
- this.scrollHeight = target.scrollHeight;
1096
- }
1097
- if (this.appendList.length === this.msgList.length || this.appendList.length === 1) {
1098
- this.setScrollTo('bottom');
1099
- } else {
1100
- this.setScrollTo('current');
1101
- }
1102
- }
1103
- },
1104
- isRecorderVoice: {
1105
- immediate: true,
1106
- handler(val) {
1107
- if (!val) return;
1108
- this.curAudioUrl = '';
1109
- this.setIsRecorderVoice(false);
1110
- if (!this.lastAudioItem) return;
1111
- this.$set(this.lastAudioItem.content, 'isPlay', false);
1112
- }
1113
- }
1114
- }
1115
- };
1116
- </script>
1117
-
1118
- <style lang="less" scoped>
1119
- @import '../style/emoji.css';
1120
- @import '../style/message.mixin.less';
1121
- .message-wrapper {
1122
- .createMessageStyle(#ebebeb, @primary-color);
1123
- flex: 1;
1124
- height: 0;
1125
- // position: relative;
1126
- display: flex;
1127
- flex-direction: column;
1128
- overflow-x: hidden;
1129
- .broadcast-wrap {
1130
- height: 30px;
1131
- text-align: center;
1132
- font-size: 14px;
1133
- background-color: #fffbe8;
1134
- /deep/ .carousel-item {
1135
- display: inline-flex !important;
1136
- justify-content: center;
1137
- align-items: center;
1138
- width: 100%;
1139
- height: 30px;
1140
- text-align: center;
1141
- &.has-link {
1142
- color: #ed6a0c;
1143
- cursor: pointer;
1144
- }
1145
- span {
1146
- display: inline-block;
1147
- max-width: calc(100% - 30px);
1148
- margin-left: 4px;
1149
- }
1150
- }
1151
- }
1152
-
1153
- .loading-icon {
1154
- position: absolute;
1155
- top: 0;
1156
- left: 50%;
1157
- }
1158
- }
1159
- /deep/ .ant-layout-content {
1160
- flex: 1;
1161
- display: flex;
1162
- flex-direction: column;
1163
- position: relative;
1164
- }
1165
- .online-message {
1166
- flex: 1;
1167
- height: 100%;
1168
- // height: 608px;
1169
- overflow-y: auto;
1170
- overflow-x: hidden;
1171
- padding: 10px 22px;
1172
- // margin: 10px 0;
1173
- text-align: center;
1174
- &::-webkit-scrollbar {
1175
- width: 5px;
1176
- }
1177
- .message-time {
1178
- display: inline-block;
1179
- color: #666;
1180
- font-size: 14px;
1181
- border-radius: 4px;
1182
- padding: 4px;
1183
- margin: 0 auto;
1184
- margin-bottom: 16px;
1185
- }
1186
- > div {
1187
- margin-bottom: 16px;
1188
- }
1189
- .message-item {
1190
- display: flex;
1191
- /deep/ .chat-read-status-text {
1192
- display: none;
1193
- }
1194
- &.right.read-status {
1195
- padding-bottom: 24px;
1196
- position: relative;
1197
- /deep/ .chat-read-status-text {
1198
- display: block;
1199
- position: absolute;
1200
- right: 50px;
1201
- bottom: 0;
1202
- }
1203
- }
1204
- .right-time {
1205
- display: none;
1206
- position: absolute;
1207
- top: -16px;
1208
- left: 10px;
1209
- font-size: 12px;
1210
- color: #b0b1b1;
1211
- white-space: nowrap;
1212
- }
1213
-
1214
- .content-wrap {
1215
- padding: 0 10px;
1216
- text-align: left;
1217
- position: relative;
1218
- &:hover {
1219
- .right-time {
1220
- display: block;
1221
- }
1222
- }
1223
-
1224
- .robot-item {
1225
- text-align: left;
1226
- a {
1227
- text-decoration: underline;
1228
- }
1229
- }
1230
- .intention-wrap {
1231
- display: flex;
1232
- flex-wrap: wrap;
1233
- padding: 8px 0 4px 0;
1234
- background-color: #fff;
1235
- border-radius: 0 0 8px 8px;
1236
- > div {
1237
- display: inline-flex;
1238
- flex-direction: column;
1239
- align-items: center;
1240
- width: 60px;
1241
- height: 54px;
1242
- cursor: pointer;
1243
-
1244
- &:hover {
1245
- color: #5585f5;
1246
- span {
1247
- color: #5585f5;
1248
- }
1249
- }
1250
- }
1251
- span {
1252
- margin-top: 8px;
1253
- max-width: 90%;
1254
- text-overflow: ellipsis;
1255
- overflow: hidden;
1256
- white-space: nowrap;
1257
- font-size: 12px;
1258
- color: #6a6a6a;
1259
- }
1260
- }
1261
- .dictionary-ul {
1262
- padding-top: 8px;
1263
- display: flex;
1264
- flex-wrap: wrap;
1265
- li {
1266
- padding: 4px 16px;
1267
- margin-right: 16px;
1268
- font-size: 14px;
1269
- color: #5585f5;
1270
- background: #efefef;
1271
- border-radius: 16px;
1272
- cursor: pointer;
1273
- }
1274
- }
1275
- .client-btn {
1276
- display: inline-block;
1277
- padding: 4px 8px;
1278
- margin-top: 8px;
1279
- color: #fff;
1280
- font-size: 12px;
1281
- border-radius: 20px;
1282
- background: #5585f5;
1283
- cursor: pointer;
1284
- }
1285
- .re-call {
1286
- cursor: pointer;
1287
- color: #5585f5;
1288
- }
1289
- .voice-bg {
1290
- display: flex;
1291
- align-items: center;
1292
- cursor: pointer;
1293
- img {
1294
- mix-blend-mode: difference;
1295
- display: inline-block;
1296
- width: 14px;
1297
- }
1298
- .voice-text {
1299
- display: inline-block;
1300
- }
1301
- span {
1302
- margin-left: 8px;
1303
- }
1304
- }
1305
- }
1306
- .has-dictionary {
1307
- padding: 0;
1308
- text-align: left;
1309
- background: unset;
1310
- }
1311
- .content {
1312
- padding: 10px;
1313
- background: #ebebeb;
1314
- border-radius: 0px 8px 8px 8px;
1315
- max-width: 500px;
1316
- margin: 0px;
1317
- line-height: 20px;
1318
- font-size: 14px;
1319
- color: #000;
1320
- text-align: left;
1321
- word-break: break-word;
1322
- /deep/ p {
1323
- margin: 0;
1324
- }
1325
- }
1326
- .upload-image {
1327
- margin-left: 8px;
1328
- }
1329
- &.right {
1330
- flex-direction: row-reverse;
1331
- .right-time {
1332
- left: unset;
1333
- right: 10px;
1334
- }
1335
- .content-wrap {
1336
- text-align: right;
1337
- .robot-item {
1338
- text-align: right;
1339
- }
1340
- .voice-bg {
1341
- flex-direction: row-reverse;
1342
- align-self: flex-end;
1343
- img {
1344
- transform: rotateY(180deg);
1345
- }
1346
- span {
1347
- margin-left: 0;
1348
- margin-right: 8px;
1349
- text-align: right;
1350
- }
1351
- }
1352
- }
1353
- .content-wrap .content {
1354
- background-color: #e7f0ff;
1355
- border-radius: 8px 0px 8px 8px;
1356
- border: 1px solid #e7f0ff;
1357
- box-sizing: border-box;
1358
- }
1359
- .upload-image {
1360
- margin-right: 8px;
1361
- }
1362
- }
1363
- }
1364
- .bot-message {
1365
- display: block;
1366
- text-decoration: underline;
1367
- color: #5585f5;
1368
- cursor: pointer;
1369
- }
1370
- .session-end {
1371
- display: flex;
1372
- justify-content: center;
1373
- margin-top: 15px;
1374
- margin-bottom: 25px;
1375
- .end {
1376
- display: flex;
1377
- justify-content: center;
1378
- width: 30%;
1379
- height: 36px;
1380
- line-height: 36px;
1381
- background-color: #e8e8e8;
1382
- color: #939393;
1383
- font-size: 14px;
1384
- border-radius: 18px;
1385
- }
1386
- }
1387
- .menu-tip {
1388
- font-size: 20px;
1389
- align-self: flex-end;
1390
- margin-bottom: 8px;
1391
- background: #f6f6f6;
1392
- height: 25px;
1393
- line-height: 25px;
1394
- width: 30px;
1395
- border-radius: 4px;
1396
- }
1397
- .menu-item {
1398
- :hover {
1399
- background: red;
1400
- }
1401
- }
1402
- .menu-main {
1403
- width: 100px;
1404
- background-color: #101627;
1405
- color: #fff;
1406
- text-align: left;
1407
- border-radius: 6px;
1408
- padding: 10px 0;
1409
- z-index: 3000;
1410
- cursor: pointer;
1411
- .menu-item {
1412
- padding: 0 10px;
1413
- }
1414
- .menu-item:hover {
1415
- background-color: #efefef;
1416
- color: #000;
1417
- }
1418
- }
1419
- .first-name {
1420
- width: 40px;
1421
- height: 40px;
1422
- line-height: 40px;
1423
- text-align: center;
1424
- background: #5585f5;
1425
- color: #fff;
1426
- border-radius: 100%;
1427
- font-size: 15px;
1428
- }
1429
- .msg-avatar {
1430
- width: 40px;
1431
- height: 40px;
1432
- border-radius: 50%;
1433
-
1434
- object-fit: cover;
1435
- background: #fff;
1436
- }
1437
- /deep/ .ant-spin-spinning {
1438
- display: flex;
1439
- align-items: center;
1440
- }
1441
- .system-msg {
1442
- margin: 10px 0;
1443
- text-align: left;
1444
- line-height: 20px;
1445
- word-wrap: break-word;
1446
- display: flex;
1447
- justify-content: center;
1448
- .system-icon {
1449
- font-size: 18px;
1450
- color: #ffc200;
1451
- vertical-align: middle;
1452
- margin-right: 10px;
1453
- margin-top: 1px;
1454
- }
1455
- .system-btn {
1456
- color: @primary-color;
1457
- margin-left: 8px;
1458
- cursor: pointer;
1459
- text-decoration: underline;
1460
- }
1461
- }
1462
- }
1463
- /deep/ p {
1464
- margin-bottom: 0;
1465
- }
1466
- </style>
1
+ <template>
2
+ <div class="message-wrapper">
3
+ <chat-header :hideHeader="hideHeader" @openPortraitPanel="handleOpenPortraitPanel" v-if="isServer && enable"></chat-header>
4
+ <a-layout-content>
5
+ <slot></slot>
6
+ <div v-if="showBroadcast" class="broadcast-wrap">
7
+ <div v-if="broadcastCarousel" class="carousel-item" :class="{ 'has-link': assemblySetting.broadcast.PC.list[0].url }" @click="goToBroadcastLink(assemblySetting.broadcast.PC.list[0])">
8
+ <a-icon type="sound" />
9
+ <span class="ellips">
10
+ {{ assemblySetting.broadcast.PC.list[0].content }}
11
+ </span>
12
+ </div>
13
+ <template v-else>
14
+ <a-carousel dotPosition="right" autoplay :dots="false">
15
+ <div class="carousel-item" v-for="(item, i) in assemblySetting.broadcast.PC.list" :key="i" :class="{ 'has-link': item.url }" @click="goToBroadcastLink(item)">
16
+ <a-icon type="sound" />
17
+ <span class="ellips">{{ item.content }}</span>
18
+ </div>
19
+ </a-carousel>
20
+ </template>
21
+ </div>
22
+ <div v-if="sessionHistoryList.length > 1 && !(!onChating && currentTab === 'end') && !sessionEnd" style="position: absolute; top: 0; left: 20px; z-index: 1">
23
+ <a-radio-group :value="selectedSession" @change="handleSessionIdChange" buttonStyle="solid">
24
+ <a-radio-button :value="item.id + '-' + item.fromId" v-for="item in sessionHistoryList" :key="item.id + item.fromId">{{ item.fromName }}</a-radio-button>
25
+ </a-radio-group>
26
+ </div>
27
+ <div class="online-message" ref="message-wrapper" v-infinite-scroll="handleInfiniteOnLoad" @scroll="scrollEvent">
28
+ <template v-for="(item, index) in msgList">
29
+ <div :key="index" v-if="item">
30
+ <p v-if="isNeedShowTime(item, index)" class="message-time">
31
+ {{ formatDate(item.createdTime) }}
32
+ </p>
33
+ <div v-if="item.recallFlag" style="margin: 10px 0">
34
+ <template v-if="handleRecallFlag(item)">
35
+ {{ getRecallText(item, isServer) }}
36
+ <a style="text-decoration: underline" @click="reEditMessage(item)" v-if="[0, 4].includes(item.content && item.content.type) && onChating && isServer">,{{ i18nText('1.9.368') }}</a>
37
+ </template>
38
+ <template v-else>
39
+ {{ getRecallText(item) || `${item.fromName} ${i18nText('1.9.365')}` }}
40
+ </template>
41
+ </div>
42
+ <!-- 系统消息 -->
43
+ <div class="system-msg" v-else-if="isSystemMsg(item)">
44
+ <a-icon class="system-icon" theme="filled" type="exclamation-circle" />
45
+ <div>
46
+ <span v-html="getSystemMsgContent(item)"></span>
47
+ <span class="system-btn" v-for="(btn, index) in getTemplateButton(item)" @click="handleTemplateButton(btn)" :key="index">{{ btn.title }}</span>
48
+ </div>
49
+ </div>
50
+ <div
51
+ v-else-if="!item.recallFlag"
52
+ class="message-item"
53
+ :class="[getMessageItemClass(item), { 'read-status': enableReadRecord && item.readStatus }]"
54
+ :data-rid="item.readStatus === 'N' ? item.id : ''"
55
+ >
56
+ <img v-if="!isServer && item.fromPortrait" class="msg-avatar" :src="item.fromPortrait" />
57
+
58
+ <div v-else-if="!isServer && item.fromName" class="first-name">
59
+ {{ item.fromName && item.fromName.slice(0, 2) }}
60
+ </div>
61
+ <img v-else-if="!isServer" class="msg-avatar" :src="getAvatar(item)" width="40" height="40" />
62
+
63
+ <template v-else>
64
+ <img :src="customerStaffIcon" class="msg-avatar" width="40" height="40" v-if="item.fromId === 'SYSTEM'" />
65
+
66
+ <img src="../img/system_message.png" class="msg-avatar" width="40" height="40" v-else-if="item.content && item.content.source === 1" />
67
+
68
+ <img v-else-if="item.portrait" class="msg-avatar" :src="item.portrait" />
69
+
70
+ <div v-else class="first-name">
71
+ {{ item.fromName && item.fromName.slice(0, 2) }}
72
+ </div>
73
+ </template>
74
+ <div @contextmenu="handleContextmenu($event, item)" style="display: flex">
75
+ <a-spin v-if="item.sending">
76
+ <a-icon slot="indicator" type="loading" style="font-size: 24px" spin />
77
+ </a-spin>
78
+ <a-popconfirm @confirm="handleSendFail(item)" :okText="i18nText('1.1.1.1.3')" :cancelText="i18nText('2.7.1.14')">
79
+ <a-icon type="exclamation-circle" slot="icon" />
80
+ <div slot="title">
81
+ <span>确认重新发送该消息?</span>
82
+ </div>
83
+ <a-icon v-if="item.fail" theme="filled" type="exclamation-circle" style="font-size: 16px; display: flex; align-items: center; color: red" />
84
+ </a-popconfirm>
85
+ <div class="content-wrap" v-if="item.content.type === 0">
86
+ <span class="right-time">
87
+ {{ formatDate(item.createdTime || item.sendTime, true) }}
88
+ </span>
89
+ <div
90
+ :class="{
91
+ content: true,
92
+ 'has-dictionary': item.dictionaryValues && item.dictionaryValues.length,
93
+ 'has-intention': item.intentionList && item.intentionList.length
94
+ }"
95
+ >
96
+ <p v-html="getContentHtml(item)"></p>
97
+ <template v-if="item.content.attachments && item.content.attachments.length > 0">
98
+ <div v-for="(v, index) in item.content.attachments" :key="index" class="robot-item">
99
+ <a v-if="v.type == 1" :href="v.url" target="_blank"> {{ v.desc }}(超链) </a>
100
+ <a @click.prevent.stop="$emit('sendQuestion', v, item)" v-if="v.type == 2 || v.type == 3" href="javascript:void(0);">
101
+ {{ v.desc }}
102
+ </a>
103
+ </div>
104
+ </template>
105
+ <div v-if="item.intentionList && item.intentionList.length" class="intention-wrap">
106
+ <div v-for="(v, index) in item.intentionList" :key="index" @click.prevent.stop="$emit('sendIntention', v)">
107
+ <svg-icon :icon-class="v.icon || 'moren'" style="font-size: 26px"></svg-icon>
108
+ <span>{{ v.name }}</span>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ <ul class="dictionary-ul" v-if="item.dictionaryValues && item.dictionaryValues.length">
113
+ <li v-for="(v, i) in item.dictionaryValues" :key="i" @click.prevent.stop="$emit('sendDictionary', v, item)">
114
+ {{ v }}
115
+ </li>
116
+ </ul>
117
+ <div class="client-btn" v-if="item.autoAccess && item.autoAccess === 'false'">
118
+ <svg-icon icon-class="kefu"></svg-icon>
119
+ <span @click="$emit('goCustomServe')">转人工客服</span>
120
+ </div>
121
+ </div>
122
+ <div v-else-if="item.content.type === 1" class="upload-image">
123
+ <MsgPicture style="max-width: 345px" :image="item.content.content" @click="show(item)" />
124
+ </div>
125
+ <div class="content-wrap" v-else-if="item.content.type === 2">
126
+ <span class="right-time">
127
+ {{ formatDate(item.createdTime || item.sendTime, true) }}
128
+ </span>
129
+ <div class="content">
130
+ <img style="width: 16px;margin-right:8px" src="../img/multi-video.png" />
131
+ {{ item.content.content }}&nbsp;
132
+ <span @click.prevent.stop="reCall(item, 'video')" class="re-call" v-if="item.content.isRefuse == 1">
133
+ {{ handleReCall(item) }}
134
+ </span>
135
+ </div>
136
+ </div>
137
+ <div class="content-wrap" v-else-if="item.content.type === 5">
138
+ <span class="right-time">
139
+ {{ formatDate(item.createdTime || item.sendTime, true) }}
140
+ </span>
141
+ <div class="content">
142
+ <img style="width: 16px;margin-right:8px" src="../img/multi-voice.png" />
143
+ {{ item.content.content }}&nbsp;
144
+ <span @click.prevent.stop="reCall(item, 'voice')" class="re-call" v-if="item.content.isRefuse == 1">
145
+ {{ handleReCall(item) }}
146
+ </span>
147
+ </div>
148
+ </div>
149
+ <div class="content-wrap" v-else-if="item.content.type === 6">
150
+ <span class="right-time">
151
+ {{ formatDate(item.createdTime || item.sendTime, true) }}
152
+ </span>
153
+ <div class="voice-bg content" @click.prevent.stop="playAudio(item)">
154
+ <img v-if="item.content.isPlay" src="../img/audio-play.gif" alt="" />
155
+ <img v-else src="../img/audio-new.png" alt="" />
156
+ <span class="voice-text" :style="AudioStyle(item)"> {{ item.content.duration || 1 }}" </span>
157
+ </div>
158
+ </div>
159
+ <template v-else-if="item.content.type === 4">
160
+ <msg-describe v-if="isDescribeMsg(item)" :item="item" />
161
+ <div v-else class="content-wrap">
162
+ <span class="right-time">
163
+ {{ formatDate(item.createdTime || item.sendTime, true) }}
164
+ </span>
165
+ <MsgPrescription :data="item" :isServer="isServer" :theme="isServer ? '' : 'customer'" @handleTemplateDetail="handleTemplateDetail" @handleTemplateButton="handleTemplateButton" />
166
+ </div>
167
+ </template>
168
+ </div>
169
+ <ReadStatus v-if="enableReadRecord" :status="item.readStatus" @click="openReadRecord($event, item)" />
170
+ </div>
171
+ </div>
172
+ </template>
173
+ <ul class="menu-main" ref="contextMenu" :style="contextMenuStyle" v-if="contextMenuShow">
174
+ <template v-for="(item, index) in rightClickSetting">
175
+ <li :key="index" class="menu-item" @click="() => handleRightClick(contextMenuItem, item)">
176
+ {{ item.rightClickName }}
177
+ </li>
178
+ </template>
179
+ </ul>
180
+ <div class="session-end" v-if="sessionEnd && !isServer">
181
+ <span class="end">{{ i18nText('1.9.370') }}</span>
182
+ </div>
183
+ <a-spin v-if="loading" class="loading-icon" />
184
+ </div>
185
+ <!-- 图片预览 -->
186
+ <viewer class="viewer" ref="viewer" :images="images" @inited="inited" :options="options" style="display: none">
187
+ <template slot-scope="scope">
188
+ <img v-for="src in scope.images" :src="src" :key="src" />
189
+ </template>
190
+ </viewer>
191
+ <!-- 卡片详情 -->
192
+ <a-modal
193
+ wrapClassName="chat-footer-modal-small"
194
+ :title="modalData.title || i18nText('1.1.1.10')"
195
+ :visible="modalShow"
196
+ :maskClosable="false"
197
+ :mask="true"
198
+ width="80%"
199
+ destroyOnClose
200
+ :okText="i18nText('1.2.1.11.6')"
201
+ :footer="null"
202
+ @cancel="handleClose"
203
+ >
204
+ <iframe id="chat-footer-modal" v-if="modalData.targetType === 'LINK_ADDRESS'" width="100%" height="100%" frameborder="0" :src="modalData.address" allow="camera;midi"></iframe>
205
+ </a-modal>
206
+ </a-layout-content>
207
+ <audio style="display: none" controls autoplay ref="audio" preload="auto" crossOrigin="anonymous" media-player="audioPlayer" :src="curAudioUrl" @ended="audioEnd"></audio>
208
+ <ReadRecord v-if="readRecordVisible" :style="readRecordStyle" :list="readRecordList" @close="closeReadRecord" />
209
+ </div>
210
+ </template>
211
+
212
+ <script>
213
+ import { Layout, Modal, Icon, Spin, Popconfirm, Carousel, Radio } from 'ant-design-vue';
214
+ import SvgIcon from '@/component/svg/index.vue';
215
+ import InfiniteScroll from '@/directive/scroll';
216
+ import { mapGetters, mapMutations, mapActions } from '../store/helper';
217
+ import fetch, { qs } from '@/utils/chatFetch';
218
+ import MsgDescribe from '../components/msg-describe';
219
+ import MsgPicture from '../components/msg-picture';
220
+ import MsgPrescription from '../components/msg-prescription';
221
+ import ReadStatus from '../components/read-status';
222
+ import ReadRecord from '../components/read-record';
223
+ import chatHeader from './chatHeader';
224
+ import viewerOptions from './mixins/viewerOptions';
225
+ import { getRecallText } from '../utils';
226
+ import vexutils from '@/utils/vexutils';
227
+ import ObserverScroll from '../utils/observer-scroll';
228
+
229
+ const customerStaffIcon = require('../img/customer_staff.png');
230
+ export default {
231
+ mixins: [viewerOptions],
232
+ inject: ['store', 'dispatchEvent', 'registerEvent', 'unregisterEvent', 'i18nText'],
233
+ props: {
234
+ hideHeader: {
235
+ type: Boolean,
236
+ default: false
237
+ },
238
+ robotAvatar: {
239
+ type: String,
240
+ default: ''
241
+ },
242
+ curChatType: {
243
+ type: String
244
+ },
245
+ isShowPortraitPanel: {
246
+ type: Boolean
247
+ },
248
+ activatedTime: {
249
+ type: Number
250
+ }
251
+ },
252
+ data() {
253
+ return {
254
+ loading: false,
255
+ // 卡片详情
256
+ modalShow: false,
257
+ modalData: {},
258
+ // 客户头像
259
+ // 右键操作
260
+ contextMenuStyle: {
261
+ position: 'fixed',
262
+ top: 0,
263
+ left: 0
264
+ },
265
+ contextMenuShow: false,
266
+ contextMenuItem: null,
267
+ // 查看历史消息滚动定位
268
+ scrollHeight: 0,
269
+ curAudioUrl: '',
270
+ lastAudioItem: null,
271
+ readRecordStyle: '',
272
+ readRecordVisible: false,
273
+ readRecordList: []
274
+ };
275
+ },
276
+ computed: {
277
+ ...mapGetters([
278
+ 'userInfo',
279
+ 'currentTab',
280
+ 'isAppendMsg',
281
+ 'message',
282
+ 'msgList',
283
+ 'sessionId',
284
+ 'serviceId',
285
+ 'scrollTo',
286
+ 'isServer',
287
+ 'clientId',
288
+ 'sessionEnd',
289
+ 'assemblyId',
290
+ 'footerMessage',
291
+ 'portraitPanelParams',
292
+ 'assemblySetting',
293
+ 'clientParams',
294
+ 'onChating',
295
+ 'classify',
296
+ 'collapsed',
297
+ 'barStatus',
298
+ 'sessionHistoryList',
299
+ 'enable',
300
+ 'appendList',
301
+ 'queueItem',
302
+ 'isRecorderVoice'
303
+ ]),
304
+ orgId() {
305
+ const getters = this.store.getters;
306
+ return this.isServer ? this.userInfo?.sysParams?.orgId : getters.accessParams.orgId || getters.queryParams.orgId;
307
+ },
308
+ userId() {
309
+ const getters = this.store.getters;
310
+ return this.isServer ? this.userInfo?.sysParams?.userId : getters.accessParams.userId || getters.chatUserId || getters.queryParams.userId;
311
+ },
312
+ enableReadRecord() {
313
+ return this.assemblySetting?.readStatus === 'Y';
314
+ },
315
+ customerStaffIcon() {
316
+ return this.robotAvatar || customerStaffIcon;
317
+ },
318
+ selectedSession() {
319
+ let sessionMap = this.sessionHistoryList.find(item => item.fromId === this.serviceId);
320
+ if (sessionMap) {
321
+ return sessionMap.id + '-' + sessionMap.fromId;
322
+ }
323
+ return '';
324
+ },
325
+ isListClassify() {
326
+ return this.assemblySetting.isListClassify === 'Y';
327
+ },
328
+ showPanel() {
329
+ return this.assemblySetting.portraitPanelSetting?.targetIs;
330
+ },
331
+ showBroadcast() {
332
+ let res = (!this.isServer && this.assemblySetting?.broadcast?.PC?.isChecked == 'Y' && this.assemblySetting?.broadcast?.PC?.list?.length > 0) || false;
333
+ return res;
334
+ },
335
+ broadcastCarousel() {
336
+ return (this.assemblySetting.broadcast && this.assemblySetting.broadcast.PC.list.length == 1) || false;
337
+ },
338
+ rightClickSetting() {
339
+ const { isServer } = this;
340
+ if (!isServer) return [];
341
+ const isSelf = this.contextMenuItem.fromId === this.serviceId;
342
+ const setting = this.assemblySetting?.rightClickSetting || [];
343
+ const doArr = ['resend', 'rollBack'];
344
+ return setting.filter(item => {
345
+ const { rightClickDo } = item;
346
+ return isSelf || doArr.indexOf(rightClickDo) < 0;
347
+ });
348
+ },
349
+ handleReCall() {
350
+ return function(item) {
351
+ if (item.content?.source === 1) {
352
+ return '[点击回拨]';
353
+ } else if (item.senderFlag == 1) {
354
+ return `[点击${this.isServer ? '回拨' : '重拨'}]`;
355
+ }
356
+ if (item.senderFlag == 2) {
357
+ return `[点击${this.isServer ? '重拨' : '回拨'}]`;
358
+ }
359
+ if (this.isServer) {
360
+ let isRight = item.fromId !== 'SYSTEM' && item.fromId && item.fromId === this.serviceId;
361
+ return `[点击${isRight ? '重拨' : '回拨'}]`;
362
+ } else {
363
+ let isRight = (item.fromId !== 'SYSTEM' && item.toId === 'SYSTEM') || item.fromId === this.clientId || (item.mode === 'bot' && !item.fromId);
364
+ return `[点击${isRight ? '重拨' : '回拨'}]`;
365
+ }
366
+ };
367
+ },
368
+ handleRecallFlag() {
369
+ return function(item) {
370
+ return this.serviceId == item.fromId;
371
+ };
372
+ },
373
+ AudioStyle() {
374
+ return function(item) {
375
+ let { duration = 1 } = item.content;
376
+ if (!duration) {
377
+ duration = 1;
378
+ }
379
+ let curWidth = (+duration / 60) * 400;
380
+ return {
381
+ width: curWidth < 13 ? 13 : curWidth + 'px'
382
+ };
383
+ };
384
+ },
385
+ showTimeDuration() {
386
+ let { variableSetting = null } = this.assemblySetting || {};
387
+ if (!variableSetting) return 3;
388
+ let matchItem = variableSetting.find(v => v.name === 'CHAT_SEPARATION_TIME');
389
+ if (!matchItem) return 3;
390
+ if (!+matchItem.value) return 3;
391
+ return +matchItem.value;
392
+ },
393
+ images() {
394
+ return this.msgList.filter(v => !v.recallFlag && v.content.type == 1)?.map(v => v.content?.content);
395
+ }
396
+ },
397
+ created() {
398
+ this.attachEvent();
399
+ },
400
+ methods: {
401
+ ...mapMutations([
402
+ 'setIsAppendMsg',
403
+ 'setMsgList',
404
+ 'setScrollTo',
405
+ 'setFooterMessage',
406
+ 'setIsScrollListChange',
407
+ 'setListChangeItem',
408
+ 'setSessionId',
409
+ 'setOnChating',
410
+ 'setServiceId',
411
+ 'setShowAudio',
412
+ 'setVideoMode',
413
+ 'setVideoMembers',
414
+ 'setIsRecorderVoice'
415
+ ]),
416
+ ...mapActions({
417
+ getEarlierMsg: 'setMsgList',
418
+ handleBotChat: 'handleBotChat',
419
+ sendMessage: 'sendMessage'
420
+ }),
421
+ getMessageItemClass(item) {
422
+ // content.source === 1, 代表为系统消息, 系统统一左侧展示
423
+ if (item.content?.source === 1) {
424
+ return {
425
+ left: true
426
+ };
427
+ }
428
+
429
+ // 服务端 senderFlag 1 左 2 右
430
+ // 客户端 senderFlag 1 右 2 左
431
+ if (item.content?.source === 1) {
432
+ return {
433
+ left: true
434
+ };
435
+ } else if (item.senderFlag == 1) {
436
+ if (this.isServer) {
437
+ return {
438
+ left: true
439
+ };
440
+ } else {
441
+ return {
442
+ right: true
443
+ };
444
+ }
445
+ }
446
+ if (item.senderFlag == 2) {
447
+ if (this.isServer) {
448
+ return {
449
+ right: true
450
+ };
451
+ } else {
452
+ return {
453
+ left: true
454
+ };
455
+ }
456
+ }
457
+ if (this.isServer) {
458
+ return {
459
+ right: item.fromId !== 'SYSTEM' && item.fromId && item.fromId === this.serviceId,
460
+ left: item.fromId === 'SYSTEM' || item.fromId === this.clientId || item.content?.source === 1 || !item.fromId
461
+ };
462
+ } else {
463
+ let isRight, isLeft;
464
+ if (this.curChatType == 'robot') {
465
+ isRight = item.fromId !== 'SYSTEM';
466
+ isLeft = item.fromId === 'SYSTEM';
467
+ } else {
468
+ isRight = (item.fromId !== 'SYSTEM' && item.toId === 'SYSTEM') || item.fromId === this.clientId || (item.mode === 'bot' && !item.fromId);
469
+ isLeft = item.fromId === 'SYSTEM' || item.fromId !== this.clientId || item.content?.source === 1;
470
+ }
471
+ return {
472
+ right: isRight,
473
+ left: isLeft
474
+ };
475
+ }
476
+ },
477
+ /* 向上滚动刷新 */
478
+ handleInfiniteOnLoad() {
479
+ if (this.curChatType === 'robot') return;
480
+ this.loading = true;
481
+ this.getEarlierMsg().then(msg => {
482
+ if (msg) {
483
+ this.$message.info(msg);
484
+ }
485
+ this.loading = false;
486
+ });
487
+ },
488
+ show(item) {
489
+ const src = item.content.content;
490
+ // this.images = vexutils.imgs2imgArr(src);
491
+ this.$viewer.view(this.images.findIndex(v => v === src));
492
+ this.$viewer.show();
493
+ this.readSingleMsg(item);
494
+ },
495
+ /* 滚动条定位 */
496
+ handleScrollTo(scrollTo) {
497
+ let target = this.$refs['message-wrapper'];
498
+ this.$nextTick().then(() => {
499
+ if (!target) return;
500
+ let top = target.scrollHeight;
501
+ if (scrollTo === 'current') {
502
+ top = target.scrollHeight - this.scrollHeight;
503
+ }
504
+ if (typeof scrollTo === 'number') {
505
+ top = scrollTo;
506
+ }
507
+ target.scrollTo({
508
+ left: 0,
509
+ top,
510
+ behavior: 'instant'
511
+ });
512
+ this.scrollHeight = 0;
513
+ });
514
+ this.setScrollTo('');
515
+ },
516
+
517
+ /**
518
+ * 是否需要显示时间
519
+ */
520
+ isNeedShowTime(item, index) {
521
+ const curMsgTime = item.createdTime || new Date().getTime();
522
+ let duration = 60 * 1000 * this.showTimeDuration;
523
+ let lastMsgTime;
524
+ if (index == 0) {
525
+ lastMsgTime = 0;
526
+ } else {
527
+ lastMsgTime = this.msgList[index - 1].createdTime;
528
+ }
529
+ if (Number(curMsgTime) - Number(lastMsgTime) > duration) {
530
+ return true;
531
+ } else {
532
+ return false;
533
+ }
534
+ },
535
+ formatDate(date) {
536
+ return vexutils.formatDate(date, true);
537
+ },
538
+ getAvatar(item) {
539
+ if (item.content && item.content.source === 1) {
540
+ return require('../img/system_message.png');
541
+ }
542
+ if (item.mode === 'bot' && !item.fromId) {
543
+ return require('../img/default.png');
544
+ }
545
+ if (this.isServer) {
546
+ if (item.fromId === this.clientId || !item.fromId) {
547
+ return require('../img/default.png');
548
+ } else {
549
+ return this.customerStaffIcon;
550
+ }
551
+ } else {
552
+ if (this.curChatType == 'robot') {
553
+ if (item.fromId === 'SYSTEM') {
554
+ return this.customerStaffIcon;
555
+ } else {
556
+ return require('../img/default.png');
557
+ }
558
+ } else {
559
+ if (item.fromId !== this.clientId) {
560
+ return this.customerStaffIcon;
561
+ } else {
562
+ return require('../img/default.png');
563
+ }
564
+ }
565
+ }
566
+ },
567
+ getContent(item) {
568
+ let content;
569
+ if (vexutils.isJSON(item.content.content)) {
570
+ content = JSON.parse(item.content.content);
571
+ } else {
572
+ content = item.content.content;
573
+ }
574
+ return content;
575
+ },
576
+ getContentHtml(item) {
577
+ return !~item.content.content.indexOf('emoji') ? this.$xss(item.content.content) : item.content.content;
578
+ },
579
+ getTemplateTitle(item) {
580
+ return this.getContent(item)?.title;
581
+ },
582
+ showArrow(item) {
583
+ let setting = this.getContent(item)?.customerSetting || {};
584
+ if (this.isServer) {
585
+ setting = this.getContent(item)?.serverSetting || {};
586
+ }
587
+ let { address } = setting;
588
+ if (!address) return false;
589
+ return true;
590
+ },
591
+ getTemplateTitleIcon(item) {
592
+ return this.getContent(item)?.icon;
593
+ },
594
+ getTemplateContent(item) {
595
+ let content = this.getContent(item);
596
+ let array = [];
597
+ try {
598
+ array = content.content.split('##');
599
+ } catch {
600
+ console.log(content, 'error');
601
+ }
602
+ array = array.map(item => `<div>${item}</div>`);
603
+ return array.join('');
604
+ },
605
+ getSystemMsgContent(item) {
606
+ let content = this.getContent(item);
607
+ let array = [];
608
+ try {
609
+ array = content.content.split('##');
610
+ } catch {
611
+ console.log(content, 'error');
612
+ }
613
+ return array.join('<br>');
614
+ },
615
+ getTemplateButton(item) {
616
+ let content = this.getContent(item);
617
+ const setting = this.isServer ? content.serverSetting : content.customerSetting;
618
+ return setting?.toolbar_button || [];
619
+ },
620
+ // 处理模板按钮,详情等跳转
621
+ handleTemplate(data, dispatch = true) {
622
+ let { targetType, address, params = [], openMode } = data;
623
+ dispatch &&
624
+ this.dispatchEvent('click_templateCard', {
625
+ ...data
626
+ });
627
+ if (!address) return;
628
+ if (targetType === 'LINK_ADDRESS') {
629
+ let urlParams = [];
630
+ params.forEach(({ p_name, p_value }) => {
631
+ urlParams.push(`${p_name}=${p_value}`);
632
+ });
633
+ if (address.includes('?')) {
634
+ address += `&${urlParams.join('&')}`;
635
+ } else {
636
+ address += `?${urlParams.join('&')}`;
637
+ }
638
+ if (openMode === 'WINDOW') {
639
+ window.open(address);
640
+ return;
641
+ }
642
+ if (openMode === 'EJECT') {
643
+ this.modalShow = true;
644
+ this.modalData = Object.assign({}, data, {
645
+ address
646
+ });
647
+ this.$nextTick().then(() => {
648
+ window.addEventListener('message', this.iframeEvent);
649
+ });
650
+ return;
651
+ }
652
+ }
653
+ },
654
+ iframeEvent(event) {
655
+ const method = event?.data?.method;
656
+ this[method]?.(event);
657
+ },
658
+ // 发送
659
+ handleParentMessageSend({ data, source, origin }) {
660
+ let params = {
661
+ assemblyId: this.assemblyId,
662
+ sessionId: this.sessionId,
663
+ orgId: this.orgId
664
+ };
665
+ params = Object.assign(params, data.data);
666
+ return fetch.post('/chat/access/sendToolBarData', qs.stringify(params)).then(({ data }) => {
667
+ if (data.result === 'SUCCESS') {
668
+ source.postMessage({ status: 0, resultMsg: data.resultMsg }, origin);
669
+ } else {
670
+ source.postMessage({ status: 1, resultMsg: data.resultMsg }, origin);
671
+ }
672
+ });
673
+ },
674
+ // 关闭
675
+ handleClose() {
676
+ this.modalShow = false;
677
+ window.removeEventListener('message', this.iframeEvent);
678
+ },
679
+ handleTemplateDetail(item) {
680
+ let content = this.getContent(item) || {};
681
+ let setting = content.customerSetting || {};
682
+ if (this.isServer) {
683
+ setting = content.serverSetting || {};
684
+ }
685
+ this.readSingleMsg(item);
686
+ this.handleTemplate(setting);
687
+ },
688
+ handleTemplateButton(btn) {
689
+ this.handleTemplate(btn);
690
+ },
691
+ // 消息撤回
692
+ handleRecall(item) {
693
+ let params = {
694
+ assemblyId: this.assemblyId,
695
+ sessionId: this.sessionId,
696
+ messageId: item.id,
697
+ messageType: item.content?.type,
698
+ userId: this.userInfo?.sysParams?.userId || ''
699
+ };
700
+
701
+ fetch.post('/chat/service/recallMessage', qs.stringify(params)).then(({ data }) => {
702
+ if (data.result === 'SUCCESS') {
703
+ this.dispatchEvent('msg_recallSuccess', {
704
+ message: { ...item },
705
+ ...params
706
+ });
707
+ this.$set(item, 'recallFlag', 1);
708
+ // 撤回成功提示
709
+ let title = this.getContent(item)?.title;
710
+ let text = title ? `已撤回[${title}]` : '已撤回';
711
+ this.$message.success(text);
712
+
713
+ if (!this.isLastMessage(item.id)) return;
714
+ this.setIsScrollListChange(true);
715
+ this.setListChangeItem({
716
+ opt: 1,
717
+ from: this.clientId,
718
+ sessionId: this.sessionId,
719
+ msgCount: 0,
720
+ lastContentRecallFlag: 1,
721
+ lastContent: vexutils.clone(item.content, true),
722
+ lastTime: new Date().getTime()
723
+ });
724
+ } else {
725
+ this.$message.error(data.resultMsg);
726
+ }
727
+ });
728
+ },
729
+ handleJumpUrl(contextMenuItem, item) {
730
+ const title = item.rightClickName;
731
+ const url = item.rightClickUrl.replace(/{(.*?)}/g, (match, p) => {
732
+ return eval(p);
733
+ });
734
+ this.dispatchEvent('open_jumpUrl', {
735
+ title: title,
736
+ url: url
737
+ });
738
+ },
739
+ // 右键菜单功能
740
+ handleRightClick(contextMenuItem, item) {
741
+ if (item.rightClickDo === 'rollBack') {
742
+ this.handleRecall(contextMenuItem);
743
+ } else if (item.rightClickDo === 'jumpUrl') {
744
+ this.handleJumpUrl(contextMenuItem, item);
745
+ } else if (item.rightClickDo === 'resend') {
746
+ this.resendMessage(contextMenuItem);
747
+ }
748
+ },
749
+ resendMessage(item) {
750
+ fetch({
751
+ method: 'POST',
752
+ url: '/chat/service/resendMessage',
753
+ data: qs.stringify({
754
+ assemblyId: this.assemblyId,
755
+ sessionId: this.sessionId,
756
+ messageId: item.id
757
+ })
758
+ });
759
+ },
760
+ // 重新编辑
761
+ reEditMessage(item) {
762
+ if (item.content?.type === 4) {
763
+ let content = this.getContent(item);
764
+ this.handleTemplate(content.serverSetting.reEditSetting, false);
765
+ return;
766
+ }
767
+ this.setFooterMessage(this.footerMessage + item.content.content.slice(5, -6));
768
+ },
769
+ observeMsg() {
770
+ if (this.enableReadRecord) {
771
+ if (!this._observer) {
772
+ this._observer = new ObserverScroll(this.$refs['message-wrapper'], this.readMsg);
773
+ this.$on('hook:beforeDestroy', () => {
774
+ this._observer.remove();
775
+ this._observer = null;
776
+ });
777
+ }
778
+ this._observer.disconnect();
779
+ if (this.msgList.length) {
780
+ this.$nextTick(() => {
781
+ const el = this.$refs['message-wrapper'];
782
+ const els = el?.querySelectorAll('.message-item.left');
783
+ els && this._observer.observe(Array.from(els).filter(item => item.getAttribute('data-rid')));
784
+ });
785
+ }
786
+ }
787
+ },
788
+ readMsg(ids, enableVaid = true, notify = true, post = true) {
789
+ const msgList = this.msgList;
790
+ const idKeys = msgList.reduce((obj, item, i) => {
791
+ const id = item?.id;
792
+ if (id) obj[id] = i;
793
+ return obj;
794
+ }, {});
795
+ const data = [];
796
+ const validFn = (item, type) => {
797
+ /**
798
+ * 针对文本、表情消息类型,只要在聊天区域内,视为已读,
799
+ * 图片、语音类型消息,需要点击查看后,才视为已读,
800
+ * 模板 类型消息,如果没有详情链接,只要在聊天区域内,视为已读,若有详情链接,需要点击查看后,才视为已读
801
+ */
802
+ const jumpType = `,6,1,`;
803
+ if (jumpType.includes(type)) return false;
804
+ if (type == 4 && !this.isDescribeMsg(item)) {
805
+ let content = this.getContent(item) || {};
806
+ let setting = (this.isServer ? content.serverSetting : content.customerSetting) || {};
807
+ return !setting?.address;
808
+ }
809
+ return true;
810
+ };
811
+ const value = ids.filter(id => {
812
+ const i = idKeys[id];
813
+ let item = msgList[i];
814
+ if (item) {
815
+ const type = item.content.type;
816
+ if (!enableVaid || validFn(item, type)) {
817
+ item.readStatus = 'Y';
818
+ data.push({ id, type });
819
+ return true;
820
+ }
821
+ return false;
822
+ }
823
+ });
824
+ if (!value.length) return;
825
+ notify &&
826
+ this.dispatchEvent('msg_readed', {
827
+ data,
828
+ assemblyId: this.assemblyId,
829
+ sessionId: this.sessionId
830
+ });
831
+ post && this.postReadMessage({ ids: value.join(',') });
832
+ },
833
+ postReadMessage(data) {
834
+ fetch.post(
835
+ `/chat/${this.isServer ? 'service' : 'access'}/readMessage`,
836
+ qs.stringify({
837
+ assemblyId: this.assemblyId,
838
+ orgId: this.orgId,
839
+ userId: this.userId,
840
+ sessionId: this.sessionId,
841
+ ...data
842
+ })
843
+ );
844
+ },
845
+ readSingleMsg(item) {
846
+ if (this.enableReadRecord && item.readStatus === 'N') {
847
+ const cls = this.getMessageItemClass(item);
848
+ if (!cls?.left) return;
849
+ this.readMsg([item.id], false);
850
+ }
851
+ },
852
+ closeReadRecord() {
853
+ this.readRecordVisible = false;
854
+ this.readRecordList = [];
855
+ },
856
+ openReadRecord(event, item) {
857
+ const readStatus = item.readStatus;
858
+ if (readStatus) {
859
+ const cls = this.getMessageItemClass(item);
860
+ if (!cls.right) return;
861
+ const rect = event.target.getBoundingClientRect();
862
+ const w = 440;
863
+ const h = 340;
864
+ const top = Math.max(0, Math.min(document.body.clientHeight - h, rect.top - h / 2));
865
+ const left = Math.max(0, Math.min(document.body.clientWidth - w, rect.left - w));
866
+ fetch({
867
+ url: `/chat/${this.isServer ? 'service' : 'access'}/getMessageMember`,
868
+ method: 'get',
869
+ params: {
870
+ assemblyId: this.assemblyId,
871
+ messageId: item.id,
872
+ orgId: this.isServer ? undefined : this.orgId
873
+ }
874
+ }).then(res => {
875
+ const list = res.data?.list || [];
876
+ this.readRecordStyle = `top:${top}px;left:${left}px;position:fixed;z-index:10`;
877
+ this.readRecordVisible = list.length > 0;
878
+ this.readRecordList = Object.freeze(list);
879
+ if (item.readStatus && list.length) {
880
+ const unread = list.filter(item => item.readStatus == 'N').length;
881
+ const single = list.length < 2;
882
+ const status = unread > 0 ? (single ? 'N' : `${unread}`) : single ? 'Y' : 'ALL';
883
+ // 检查是否更新人数
884
+ if (item.readStatus != status) {
885
+ item.readStatus = status;
886
+ }
887
+ }
888
+ });
889
+ }
890
+ },
891
+ isLastMessage(id) {
892
+ return this.msgList.findIndex(msg => msg.id === id) === this.msgList.length - 1;
893
+ },
894
+ isSystemMsg(item) {
895
+ return item.content.type == 4 && this.getContent(item).styleId == '2';
896
+ },
897
+ isDescribeMsg(item) {
898
+ return this.getContent(item).styleId == '3';
899
+ },
900
+ async handleOpenPortraitPanel(item) {
901
+ if (!this.showPanel || item.fromId === this.serviceId || !this.isServer || !this.isShowPortraitPanel) return;
902
+ const { portraitPanelParams, clientParams } = this;
903
+ const panelSetting = this.assemblySetting?.portraitPanelSetting;
904
+ this.dispatchEvent('open_portraitPanel', {
905
+ panelSetting,
906
+ clientParams,
907
+ panelParams: portraitPanelParams,
908
+ targetId: panelSetting?.targetId,
909
+ targetType: panelSetting?.targetType
910
+ });
911
+ },
912
+ handleContextmenu(e, item) {
913
+ // 系统消息,客户消息,待接入消息,历史消息不允许撤回
914
+ // 模板-描述消息禁用右键
915
+ if (item.content?.source === 1 || !this.sessionId || (this.sessionId && !this.onChating) || (item.content.type === 4 && this.isDescribeMsg(item))) return;
916
+ this.contextMenuItem = item;
917
+ if (this.rightClickSetting.length < 1) return;
918
+ e.preventDefault();
919
+ let left = e.pageX + 'px';
920
+ let top = e.pageY + 'px';
921
+ this.contextMenuStyle = Object.assign(this.contextMenuStyle, {
922
+ top,
923
+ left
924
+ });
925
+ this.contextMenuShow = true;
926
+ },
927
+ scrollEvent(e) {
928
+ console.log('e.target.scrollTop->', e.target.scrollTop);
929
+ this._scrollTop = e.target.scrollTop;
930
+ },
931
+ /* 事件绑定 */
932
+ attachEvent() {
933
+ this.scrollEvent = vexutils.debounce(this.scrollEvent, 800);
934
+ const handleClick = () => {
935
+ this.contextMenuShow = false;
936
+ };
937
+ const handleRasize = () => {
938
+ if (this.readRecordVisible) {
939
+ let style = this.readRecordStyle;
940
+ const top = style.match(topReg)?.[1];
941
+ const topReg = /top:([\d.]+)/;
942
+ const maxH = document.body.clientHeight - 360;
943
+ if (top && maxH < top) {
944
+ style = style.replace(topReg, `top:${maxH}`);
945
+ }
946
+ const leftReg = /left:([\d.]+)/;
947
+ const left = style.match(leftReg)?.[1];
948
+ const maxW = document.body.clientWidth - 440;
949
+ if (left && maxW < left) {
950
+ style = style.replace(leftReg, `left:${maxW}`);
951
+ }
952
+ this.readRecordStyle = style;
953
+ }
954
+ };
955
+ window.addEventListener('resize', handleRasize);
956
+ document.addEventListener('click', handleClick);
957
+ this.registerEvent('openPortraitPanel', this.handleOpenPortraitPanel);
958
+ const readMessage = data => {
959
+ this.readMsg(data.ids.split(','), false, false, false);
960
+ this.postReadMessage(data);
961
+ };
962
+ this.registerEvent('readMessage', readMessage);
963
+ this.$on('hook:beforeDestroy', () => {
964
+ window.removeEventListener('resize', handleRasize);
965
+ document.removeEventListener('click', handleClick);
966
+ this.unregisterEvent('openPortraitPanel', this.handleOpenPortraitPanel);
967
+ this.unregisterEvent('readMessage', readMessage);
968
+ });
969
+ },
970
+ goToBroadcastLink(item) {
971
+ if (!item.url) return;
972
+ window.open(item.url);
973
+ },
974
+ /**
975
+ * 客服切换
976
+ */
977
+ handleSessionIdChange(e) {
978
+ let [sessionId, serviceId] = e.target.value.split('-');
979
+ this.setSessionId(sessionId);
980
+ this.setServiceId(serviceId);
981
+ let isOnChating = this.sessionHistoryList.find(item => item.fromId === serviceId).onChating;
982
+ this.setOnChating(!!isOnChating);
983
+ this.setMsgList([]);
984
+ this.getEarlierMsg();
985
+ },
986
+ handleSendFail(item) {
987
+ this.sendMessage(Object.assign(item.content, { failItem: item }));
988
+ },
989
+ reCall(type) {
990
+ this.setShowAudio(true);
991
+ this.setVideoMode(type === 'voice' ? 1 : 2);
992
+ this.$nextTick(() => {
993
+ this.setVideoMembers([]);
994
+ });
995
+ },
996
+ playAudio(item) {
997
+ let { content = '' } = item?.content || {};
998
+ if (!content) return;
999
+ if (this.lastAudioItem && (item.id !== this.lastAudioItem.id || item.createdTime !== this.lastAudioItem.createdTime)) {
1000
+ this.$set(this.lastAudioItem.content, 'isPlay', false);
1001
+ }
1002
+
1003
+ this.readSingleMsg(item);
1004
+ this.lastAudioItem = item;
1005
+ this.$set(item.content, 'isPlay', !item.content.isPlay);
1006
+ this.$nextTick(() => {
1007
+ this.curAudioUrl = content;
1008
+ let audioRef = this.$refs['audio'];
1009
+ if (!audioRef) return;
1010
+ audioRef.load();
1011
+ setTimeout(() => {
1012
+ if (item.content.isPlay) {
1013
+ let playPromise = audioRef.play();
1014
+ if (playPromise !== undefined) {
1015
+ playPromise.then(() => {
1016
+ audioRef.play();
1017
+ });
1018
+ }
1019
+ } else {
1020
+ audioRef.pause();
1021
+ this.curAudioUrl = '';
1022
+ }
1023
+ });
1024
+ });
1025
+ },
1026
+ audioEnd() {
1027
+ if (!this.lastAudioItem) return;
1028
+ this.$set(this.lastAudioItem.content, 'isPlay', false);
1029
+ },
1030
+ getRecallText(item, flag = false) {
1031
+ return getRecallText(this, item, flag);
1032
+ }
1033
+ },
1034
+ directives: {
1035
+ InfiniteScroll
1036
+ },
1037
+ components: {
1038
+ AModal: Modal,
1039
+ [Layout.Content.name]: Layout.Content,
1040
+ [Spin.name]: Spin,
1041
+ [Icon.name]: Icon,
1042
+ [Popconfirm.name]: Popconfirm,
1043
+ [Radio.Button.name]: Radio.Button,
1044
+ [Radio.Group.name]: Radio.Group,
1045
+ [Carousel.name]: Carousel,
1046
+ SvgIcon,
1047
+ MsgPrescription,
1048
+ ReadStatus,
1049
+ ReadRecord,
1050
+ MsgPicture,
1051
+ MsgDescribe,
1052
+ chatHeader
1053
+ },
1054
+ watch: {
1055
+ msgList() {
1056
+ this.observeMsg();
1057
+ },
1058
+ isAppendMsg: {
1059
+ immediate: true,
1060
+ handler(isAppendMsg) {
1061
+ if (isAppendMsg) {
1062
+ let newArr = [];
1063
+ if (vexutils.isPlainObject(this.message)) {
1064
+ newArr = [...this.msgList, this.message];
1065
+ } else if (vexutils.isArray(this.message)) {
1066
+ newArr = [...this.msgList, ...this.message];
1067
+ }
1068
+ this.setMsgList(newArr);
1069
+ this.setIsAppendMsg(false);
1070
+ this.setScrollTo('bottom');
1071
+ }
1072
+ }
1073
+ },
1074
+ scrollTo: {
1075
+ immediate: true,
1076
+ handler(scrollTo) {
1077
+ if (scrollTo) {
1078
+ this.handleScrollTo(scrollTo);
1079
+ }
1080
+ }
1081
+ },
1082
+ activatedTime: {
1083
+ immediate: true,
1084
+ handler(value) {
1085
+ this.handleScrollTo(this._scrollTop);
1086
+ }
1087
+ },
1088
+ /**
1089
+ * 图片懒加载处理
1090
+ */
1091
+ appendList: {
1092
+ handler() {
1093
+ if (!this.scrollHeight) {
1094
+ let target = this.$refs['message-wrapper'];
1095
+ this.scrollHeight = target.scrollHeight;
1096
+ }
1097
+ if (this.appendList.length === this.msgList.length || this.appendList.length === 1) {
1098
+ this.setScrollTo('bottom');
1099
+ } else {
1100
+ this.setScrollTo('current');
1101
+ }
1102
+ }
1103
+ },
1104
+ isRecorderVoice: {
1105
+ immediate: true,
1106
+ handler(val) {
1107
+ if (!val) return;
1108
+ this.curAudioUrl = '';
1109
+ this.setIsRecorderVoice(false);
1110
+ if (!this.lastAudioItem) return;
1111
+ this.$set(this.lastAudioItem.content, 'isPlay', false);
1112
+ }
1113
+ }
1114
+ }
1115
+ };
1116
+ </script>
1117
+
1118
+ <style lang="less" scoped>
1119
+ @import '../style/emoji.css';
1120
+ @import '../style/message.mixin.less';
1121
+ .message-wrapper {
1122
+ .createMessageStyle(#ebebeb, @primary-color);
1123
+ flex: 1;
1124
+ height: 0;
1125
+ // position: relative;
1126
+ display: flex;
1127
+ flex-direction: column;
1128
+ overflow-x: hidden;
1129
+ .broadcast-wrap {
1130
+ height: 30px;
1131
+ text-align: center;
1132
+ font-size: 14px;
1133
+ background-color: #fffbe8;
1134
+ /deep/ .carousel-item {
1135
+ display: inline-flex !important;
1136
+ justify-content: center;
1137
+ align-items: center;
1138
+ width: 100%;
1139
+ height: 30px;
1140
+ text-align: center;
1141
+ &.has-link {
1142
+ color: #ed6a0c;
1143
+ cursor: pointer;
1144
+ }
1145
+ span {
1146
+ display: inline-block;
1147
+ max-width: calc(100% - 30px);
1148
+ margin-left: 4px;
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ .loading-icon {
1154
+ position: absolute;
1155
+ top: 0;
1156
+ left: 50%;
1157
+ }
1158
+ }
1159
+ /deep/ .ant-layout-content {
1160
+ flex: 1;
1161
+ display: flex;
1162
+ flex-direction: column;
1163
+ position: relative;
1164
+ }
1165
+ .online-message {
1166
+ flex: 1;
1167
+ height: 100%;
1168
+ // height: 608px;
1169
+ overflow-y: auto;
1170
+ overflow-x: hidden;
1171
+ padding: 10px 22px;
1172
+ // margin: 10px 0;
1173
+ text-align: center;
1174
+ &::-webkit-scrollbar {
1175
+ width: 5px;
1176
+ }
1177
+ .message-time {
1178
+ display: inline-block;
1179
+ color: #666;
1180
+ font-size: 14px;
1181
+ border-radius: 4px;
1182
+ padding: 4px;
1183
+ margin: 0 auto;
1184
+ margin-bottom: 16px;
1185
+ }
1186
+ > div {
1187
+ margin-bottom: 16px;
1188
+ }
1189
+ .message-item {
1190
+ display: flex;
1191
+ /deep/ .chat-read-status-text {
1192
+ display: none;
1193
+ }
1194
+ &.right.read-status {
1195
+ padding-bottom: 24px;
1196
+ position: relative;
1197
+ /deep/ .chat-read-status-text {
1198
+ display: block;
1199
+ position: absolute;
1200
+ right: 50px;
1201
+ bottom: 0;
1202
+ }
1203
+ }
1204
+ .right-time {
1205
+ display: none;
1206
+ position: absolute;
1207
+ top: -16px;
1208
+ left: 10px;
1209
+ font-size: 12px;
1210
+ color: #b0b1b1;
1211
+ white-space: nowrap;
1212
+ }
1213
+
1214
+ .content-wrap {
1215
+ padding: 0 10px;
1216
+ text-align: left;
1217
+ position: relative;
1218
+ &:hover {
1219
+ .right-time {
1220
+ display: block;
1221
+ }
1222
+ }
1223
+
1224
+ .robot-item {
1225
+ text-align: left;
1226
+ a {
1227
+ text-decoration: underline;
1228
+ }
1229
+ }
1230
+ .intention-wrap {
1231
+ display: flex;
1232
+ flex-wrap: wrap;
1233
+ padding: 8px 0 4px 0;
1234
+ background-color: #fff;
1235
+ border-radius: 0 0 8px 8px;
1236
+ > div {
1237
+ display: inline-flex;
1238
+ flex-direction: column;
1239
+ align-items: center;
1240
+ width: 60px;
1241
+ height: 54px;
1242
+ cursor: pointer;
1243
+
1244
+ &:hover {
1245
+ color: #5585f5;
1246
+ span {
1247
+ color: #5585f5;
1248
+ }
1249
+ }
1250
+ }
1251
+ span {
1252
+ margin-top: 8px;
1253
+ max-width: 90%;
1254
+ text-overflow: ellipsis;
1255
+ overflow: hidden;
1256
+ white-space: nowrap;
1257
+ font-size: 12px;
1258
+ color: #6a6a6a;
1259
+ }
1260
+ }
1261
+ .dictionary-ul {
1262
+ padding-top: 8px;
1263
+ display: flex;
1264
+ flex-wrap: wrap;
1265
+ li {
1266
+ padding: 4px 16px;
1267
+ margin-right: 16px;
1268
+ font-size: 14px;
1269
+ color: #5585f5;
1270
+ background: #efefef;
1271
+ border-radius: 16px;
1272
+ cursor: pointer;
1273
+ }
1274
+ }
1275
+ .client-btn {
1276
+ display: inline-block;
1277
+ padding: 4px 8px;
1278
+ margin-top: 8px;
1279
+ color: #fff;
1280
+ font-size: 12px;
1281
+ border-radius: 20px;
1282
+ background: #5585f5;
1283
+ cursor: pointer;
1284
+ }
1285
+ .re-call {
1286
+ cursor: pointer;
1287
+ color: #5585f5;
1288
+ }
1289
+ .voice-bg {
1290
+ display: flex;
1291
+ align-items: center;
1292
+ cursor: pointer;
1293
+ img {
1294
+ mix-blend-mode: difference;
1295
+ display: inline-block;
1296
+ width: 14px;
1297
+ }
1298
+ .voice-text {
1299
+ display: inline-block;
1300
+ }
1301
+ span {
1302
+ margin-left: 8px;
1303
+ }
1304
+ }
1305
+ }
1306
+ .has-dictionary {
1307
+ padding: 0;
1308
+ text-align: left;
1309
+ background: unset;
1310
+ }
1311
+ .content {
1312
+ padding: 10px;
1313
+ background: #ebebeb;
1314
+ border-radius: 0px 8px 8px 8px;
1315
+ max-width: 500px;
1316
+ margin: 0px;
1317
+ line-height: 20px;
1318
+ font-size: 14px;
1319
+ color: #000;
1320
+ text-align: left;
1321
+ word-break: break-word;
1322
+ /deep/ p {
1323
+ margin: 0;
1324
+ }
1325
+ }
1326
+ .upload-image {
1327
+ margin-left: 8px;
1328
+ }
1329
+ &.right {
1330
+ flex-direction: row-reverse;
1331
+ .right-time {
1332
+ left: unset;
1333
+ right: 10px;
1334
+ }
1335
+ .content-wrap {
1336
+ text-align: right;
1337
+ .robot-item {
1338
+ text-align: right;
1339
+ }
1340
+ .voice-bg {
1341
+ flex-direction: row-reverse;
1342
+ align-self: flex-end;
1343
+ img {
1344
+ transform: rotateY(180deg);
1345
+ }
1346
+ span {
1347
+ margin-left: 0;
1348
+ margin-right: 8px;
1349
+ text-align: right;
1350
+ }
1351
+ }
1352
+ }
1353
+ .content-wrap .content {
1354
+ background-color: #e7f0ff;
1355
+ border-radius: 8px 0px 8px 8px;
1356
+ border: 1px solid #e7f0ff;
1357
+ box-sizing: border-box;
1358
+ }
1359
+ .upload-image {
1360
+ margin-right: 8px;
1361
+ }
1362
+ }
1363
+ }
1364
+ .bot-message {
1365
+ display: block;
1366
+ text-decoration: underline;
1367
+ color: #5585f5;
1368
+ cursor: pointer;
1369
+ }
1370
+ .session-end {
1371
+ display: flex;
1372
+ justify-content: center;
1373
+ margin-top: 15px;
1374
+ margin-bottom: 25px;
1375
+ .end {
1376
+ display: flex;
1377
+ justify-content: center;
1378
+ width: 30%;
1379
+ height: 36px;
1380
+ line-height: 36px;
1381
+ background-color: #e8e8e8;
1382
+ color: #939393;
1383
+ font-size: 14px;
1384
+ border-radius: 18px;
1385
+ }
1386
+ }
1387
+ .menu-tip {
1388
+ font-size: 20px;
1389
+ align-self: flex-end;
1390
+ margin-bottom: 8px;
1391
+ background: #f6f6f6;
1392
+ height: 25px;
1393
+ line-height: 25px;
1394
+ width: 30px;
1395
+ border-radius: 4px;
1396
+ }
1397
+ .menu-item {
1398
+ :hover {
1399
+ background: red;
1400
+ }
1401
+ }
1402
+ .menu-main {
1403
+ width: 100px;
1404
+ background-color: #101627;
1405
+ color: #fff;
1406
+ text-align: left;
1407
+ border-radius: 6px;
1408
+ padding: 10px 0;
1409
+ z-index: 3000;
1410
+ cursor: pointer;
1411
+ .menu-item {
1412
+ padding: 0 10px;
1413
+ }
1414
+ .menu-item:hover {
1415
+ background-color: #efefef;
1416
+ color: #000;
1417
+ }
1418
+ }
1419
+ .first-name {
1420
+ width: 40px;
1421
+ height: 40px;
1422
+ line-height: 40px;
1423
+ text-align: center;
1424
+ background: #5585f5;
1425
+ color: #fff;
1426
+ border-radius: 100%;
1427
+ font-size: 15px;
1428
+ }
1429
+ .msg-avatar {
1430
+ width: 40px;
1431
+ height: 40px;
1432
+ border-radius: 50%;
1433
+
1434
+ object-fit: cover;
1435
+ background: #fff;
1436
+ }
1437
+ /deep/ .ant-spin-spinning {
1438
+ display: flex;
1439
+ align-items: center;
1440
+ }
1441
+ .system-msg {
1442
+ margin: 10px 0;
1443
+ text-align: left;
1444
+ line-height: 20px;
1445
+ word-wrap: break-word;
1446
+ display: flex;
1447
+ justify-content: center;
1448
+ .system-icon {
1449
+ font-size: 18px;
1450
+ color: #ffc200;
1451
+ vertical-align: middle;
1452
+ margin-right: 10px;
1453
+ margin-top: 1px;
1454
+ }
1455
+ .system-btn {
1456
+ color: @primary-color;
1457
+ margin-left: 8px;
1458
+ cursor: pointer;
1459
+ text-decoration: underline;
1460
+ }
1461
+ }
1462
+ }
1463
+ /deep/ p {
1464
+ margin-bottom: 0;
1465
+ }
1466
+ </style>