cnhis-design-vue 2.1.18 → 2.1.19

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 (126) hide show
  1. package/CHANGELOG.md +2219 -2219
  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 +207 -206
  12. package/es/big-table/style.css +1 -1
  13. package/es/breadcrumb/index.js +8 -8
  14. package/es/button/index.js +31 -31
  15. package/es/calendar/index.js +8 -8
  16. package/es/captcha/index.js +3 -3
  17. package/es/card/index.js +8 -8
  18. package/es/carousel/index.js +8 -8
  19. package/es/cascader/index.js +8 -8
  20. package/es/checkbox/index.js +9 -9
  21. package/es/col/index.js +8 -8
  22. package/es/collapse/index.js +8 -8
  23. package/es/color-picker/index.js +1 -1
  24. package/es/comment/index.js +8 -8
  25. package/es/config-provider/index.js +8 -8
  26. package/es/date-picker/index.js +8 -8
  27. package/es/descriptions/index.js +8 -8
  28. package/es/divider/index.js +8 -8
  29. package/es/drag-layout/index.js +3 -3
  30. package/es/drawer/index.js +8 -8
  31. package/es/dropdown/index.js +8 -8
  32. package/es/editor/index.js +1 -1
  33. package/es/empty/index.js +8 -8
  34. package/es/fabric-chart/index.js +427 -314
  35. package/es/fabric-chart/style.css +1 -1
  36. package/es/form/index.js +8 -8
  37. package/es/form-model/index.js +8 -8
  38. package/es/form-table/index.js +62 -62
  39. package/es/index/index.js +1078 -963
  40. package/es/index/style.css +1 -1
  41. package/es/input/index.js +9 -9
  42. package/es/input-number/index.js +8 -8
  43. package/es/layout/index.js +8 -8
  44. package/es/list/index.js +8 -8
  45. package/es/locale-provider/index.js +8 -8
  46. package/es/map/index.js +9 -9
  47. package/es/mentions/index.js +8 -8
  48. package/es/menu/index.js +8 -8
  49. package/es/message/index.js +8 -8
  50. package/es/multi-chat/index.js +92 -92
  51. package/es/multi-chat-client/index.js +86 -86
  52. package/es/multi-chat-history/index.js +4 -4
  53. package/es/multi-chat-record/index.js +14 -14
  54. package/es/multi-chat-setting/index.js +27 -27
  55. package/es/multi-chat-sip/index.js +1 -1
  56. package/es/notification/index.js +8 -8
  57. package/es/page-header/index.js +8 -8
  58. package/es/pagination/index.js +8 -8
  59. package/es/popconfirm/index.js +8 -8
  60. package/es/popover/index.js +8 -8
  61. package/es/progress/index.js +8 -8
  62. package/es/radio/index.js +9 -9
  63. package/es/rate/index.js +8 -8
  64. package/es/result/index.js +8 -8
  65. package/es/row/index.js +8 -8
  66. package/es/scale-view/index.js +33 -33
  67. package/es/select/index.js +11 -11
  68. package/es/select-label/index.js +11 -11
  69. package/es/select-person/index.js +20 -20
  70. package/es/skeleton/index.js +8 -8
  71. package/es/slider/index.js +8 -8
  72. package/es/space/index.js +8 -8
  73. package/es/spin/index.js +8 -8
  74. package/es/statistic/index.js +8 -8
  75. package/es/steps/index.js +8 -8
  76. package/es/switch/index.js +8 -8
  77. package/es/table-filter/index.js +142 -141
  78. package/es/table-filter/style.css +1 -1
  79. package/es/tabs/index.js +8 -8
  80. package/es/tag/index.js +9 -9
  81. package/es/time-picker/index.js +8 -8
  82. package/es/timeline/index.js +8 -8
  83. package/es/tooltip/index.js +8 -8
  84. package/es/transfer/index.js +8 -8
  85. package/es/tree/index.js +8 -8
  86. package/es/tree-select/index.js +8 -8
  87. package/es/upload/index.js +8 -8
  88. package/es/verification-code/index.js +2 -2
  89. package/lib/cui.common.js +1073 -958
  90. package/lib/cui.umd.js +1073 -958
  91. package/lib/cui.umd.min.js +77 -77
  92. package/package.json +106 -106
  93. package/packages/big-table/src/BigTable.vue +3039 -3038
  94. package/packages/big-table/src/assets/style/table-base.less +370 -370
  95. package/packages/big-table/src/components/AutoLayoutButton.vue +270 -270
  96. package/packages/big-table/src/utils/batchEditing.js +610 -610
  97. package/packages/big-table/src/utils/bigTableProps.js +95 -95
  98. package/packages/button/src/ButtonPrint/components/IdentityVerification.vue +181 -181
  99. package/packages/button/src/ButtonPrint/index.vue +728 -728
  100. package/packages/fabric-chart/src/components/TimeScaleValue.vue +113 -117
  101. package/packages/fabric-chart/src/const/defaultVaule.js +59 -59
  102. package/packages/fabric-chart/src/fabric-chart/FabricPolylines.vue +1035 -960
  103. package/packages/fabric-chart/src/fabric-chart/FabricScaleValue.vue +135 -135
  104. package/packages/fabric-chart/src/fabric-chart/FabricTextGroup.vue +558 -558
  105. package/packages/fabric-chart/src/fabric-chart2/FabricTop.vue +172 -172
  106. package/packages/fabric-chart/src/mixins/draw.js +70 -62
  107. package/packages/fabric-chart/src/mixins/eventCommon.js +9 -10
  108. package/packages/multi-chat/chat/chatFooter.vue +1598 -1598
  109. package/packages/multi-chat/chat/chatMain.vue +1442 -1442
  110. package/packages/multi-chat/chat/quickReply.vue +439 -439
  111. package/packages/multi-chat/chat/scrollList.vue +1232 -1232
  112. package/packages/multi-chat/setting/baseInfo/index.vue +1316 -1316
  113. package/packages/multi-chat/store/actions.js +448 -448
  114. package/packages/multi-chat/store/state.js +112 -112
  115. package/packages/scale-view/formitem/r-choice.vue +714 -714
  116. package/packages/scale-view/scaleView.vue +2010 -2010
  117. package/packages/select-person/select-person.vue +1658 -1658
  118. package/packages/table-filter/src/base-search-com/BaseSearch.vue +2462 -2462
  119. package/packages/table-filter/src/components/c-tree-select/tree-select.vue +336 -336
  120. package/packages/table-filter/src/components/multi-select/multi-select.vue +219 -219
  121. package/packages/table-filter/src/components/out-quick-search/out-quick-search.vue +340 -340
  122. package/packages/table-filter/src/components/search-condition/SearchCondition.vue +3 -2
  123. package/packages/table-filter/src/const/dataOptions.js +43 -43
  124. package/packages/table-filter/src/mixins/mixins.js +695 -695
  125. package/packages/table-filter/src/quick-search/QuickSearch.vue +2083 -2083
  126. package/src/directive/preventReClick.js +12 -12
@@ -1,1598 +1,1598 @@
1
- <template>
2
- <a-layout-footer style="position: relative;cursor:pointer" v-show="!queueItem || !hideQueueBtns">
3
- <div class="message-operate">
4
- <a-upload
5
- v-if="isShow('PICTURE')"
6
- class="operate-icon"
7
- v-decorator="[
8
- 'upload',
9
- {
10
- valuePropName: 'fileList'
11
- }
12
- ]"
13
- :beforeUpload="beforeUpload"
14
- >
15
- <a-tooltip placement="top">
16
- <template slot="title">{{ i18nText('1.9.181') }}</template>
17
- <svg-icon icon-class="liaotiantupian1" class="toolbar"></svg-icon>
18
- </a-tooltip>
19
- </a-upload>
20
- <a-popover :title="i18nText('1.9.354')" trigger="click" v-model="visible">
21
- <a slot="content">
22
- <div class="emoji-wrapper">
23
- <p v-for="(item, index) in emojiList" :key="index" @click="() => handleEmojiClick(item)" :class="['twa', 'emoji', item]"></p>
24
- </div>
25
- </a>
26
-
27
- <a-tooltip placement="top">
28
- <template slot="title">{{ i18nText('1.9.354') }}</template>
29
- <svg-icon icon-class="liaotianbiaoqing1" @click="showEmoji" class="toolbar" v-if="isShow('SMILINEFACE')"></svg-icon>
30
- </a-tooltip>
31
- </a-popover>
32
- <template v-for="(item, index) in toolbar">
33
- <a-tooltip placement="top" :key="index">
34
- <template slot="title">{{ item.name }}</template>
35
- <svg-icon :icon-class="item.icon" class="toolbar" :class="{ 'closed-toolbar': closedSession }" @click="handleToolBarClick(item)"></svg-icon>
36
- </a-tooltip>
37
- </template>
38
- <!-- 快捷回复 -->
39
- <a-tooltip placement="top" :title="'快捷回复'">
40
- <a-popover trigger="click" overlayClassName="quick-reply" placement="topLeft" v-model="quickShow">
41
- <template slot="content">
42
- <quick-reply :quickShow.sync="quickShow" :quickData="quickData"></quick-reply>
43
- </template>
44
- <svg-icon icon-class="liaotiankuaijiehuifu1" class="toolbar" @click="openQuickData" v-if="isShow('FASTREPLY') && isServer"></svg-icon>
45
- </a-popover>
46
- </a-tooltip>
47
- <a-tooltip :title="'语音通话'">
48
- <svg-icon icon-class="liaotianyuyindianhua" @click="handleVideoClick('voice')" class="toolbar" v-if="isShow('VOICE_CALL')"></svg-icon>
49
- <!-- <img
50
- width="20"
51
- src="../img/multi-voice.png"
52
- @click="handleVideoClick('voice')"
53
- v-if="isShow('VOICE_CALL') && isServer"
54
- /> -->
55
- </a-tooltip>
56
- <a-tooltip :title="'发送语音'">
57
- <svg-icon icon-class="liaotianyuyin1" @click="handleVoice(true)" class="toolbar" :class="{ 'click-voice': showVoice }" v-if="isShow('VOICE')"></svg-icon>
58
- <!-- <img
59
- width="20"
60
- src="../img/multi-video.png"
61
- @click="handleVideoClick('video')"
62
- v-if="isShow('VIDEO') && isServer"
63
- /> -->
64
- </a-tooltip>
65
- <a-tooltip :title="'视频通话'">
66
- <svg-icon icon-class="liaotianshipin1" @click="handleVideoClick('video')" class="toolbar" v-if="isShow('VIDEO')"></svg-icon>
67
- <!-- <img
68
- width="20"
69
- src="../img/multi-video.png"
70
- @click="handleVideoClick('video')"
71
- v-if="isShow('VIDEO') && isServer"
72
- /> -->
73
- </a-tooltip>
74
- </div>
75
- <div class="message" style="text-align: left">
76
- <div class="content-edit">
77
- <div
78
- ref="contentEdit"
79
- @click="saveRange"
80
- @keyup.exact="saveRange"
81
- @change="changeData"
82
- @input="handleInput"
83
- @compositionstart="() => (this.ping = true)"
84
- @compositionend="() => (this.ping = false)"
85
- @keydown="handleKeyDown"
86
- @paste="handlePaste"
87
- contenteditable="true"
88
- ></div>
89
- </div>
90
- <span class="prompt">Enter {{ i18nText('1.2.1.11.6') }}, Ctrl + Enter {{ i18nText('1.9.356') }}</span>
91
- <a-button :disabled="sendDisabled" class="message-send" @click="handleMsgSend">{{ i18nText('1.2.1.11.6') }}</a-button>
92
- </div>
93
- <div id="canvas-wrapper" style="display: none">
94
- <canvas id="canvas"></canvas>
95
- </div>
96
- <template v-if="showOverlay">
97
- <div class="overlay" v-if="(!onChating || sessionEnd) && !queueItem"></div>
98
- </template>
99
- <div class="queue-page" v-if="queueItem">
100
- <div v-if="accessDesc" class="queue-tips">{{ accessDesc }}</div>
101
- <div class="queue-btns">
102
- <div v-if="enableTransfer" class="toSession" @click="handleOpenTransfer">
103
- {{ i18nText('1.10.201') }}
104
- </div>
105
- <div class="toSession primary" @click="handleAccess">
106
- {{ accessText || i18nText('1.9.352') }}
107
- </div>
108
- <div v-if="enableRefuse" class="toSession danger" @click="openRefuseReason">
109
- {{ i18nText('1.9.353') }}
110
- </div>
111
- </div>
112
- </div>
113
- <!-- 操作栏弹窗 -->
114
- <a-modal
115
- :wrapClassName="isFullScreen ? 'chat-footer-modal-big' : 'chat-footer-modal-small'"
116
- :title="modalData.name"
117
- :visible="modalShow"
118
- :maskClosable="false"
119
- :mask="true"
120
- :width="isFullScreen ? '100%' : '80%'"
121
- destroyOnClose
122
- :okText="i18nText('1.2.1.11.6')"
123
- :footer="null"
124
- @cancel="
125
- () => {
126
- this.modalShow = false;
127
- }
128
- "
129
- @ok="handleOk"
130
- >
131
- <iframe id="toolbarIframe" v-if="modalData.targetType === 'LINK_ADDRESS'" width="100%" height="100%" frameborder="0" :src="modalData.address" allow="camera;midi"></iframe>
132
- </a-modal>
133
- <!-- 接入拦截 -->
134
- <a-modal
135
- :title="accessInterceptTitle"
136
- :visible="accessIntercept"
137
- :maskClosable="false"
138
- :mask="true"
139
- :footer="null"
140
- destroyOnClose
141
- :width="600"
142
- :bodyStyle="{ height: '280px' }"
143
- @cancel="
144
- () => {
145
- this.accessIntercept = false;
146
- }
147
- "
148
- >
149
- <iframe width="100%" height="100%" frameborder="0" :src="accessInterceptUrl"></iframe>
150
- </a-modal>
151
- <!-- 语音消息 -->
152
- <voice v-if="showVoice" :showVoice="showVoice" @closeVoice="handleVoice"></voice>
153
- <ModalUserTransfer v-if="enableTransfer" :title="i18nText('1.10.201')" :visible.sync="transferVisible" :assemblyId="assemblyId" @ok="handleTransfer" />
154
- <ModalRefuseReason v-if="refuseVisible" :visible.sync="refuseVisible" :reasonList="refuseReasonList" @ok="handleRefuse" />
155
- </a-layout-footer>
156
- </template>
157
-
158
- <script>
159
- import { Layout, Tooltip, Upload, Modal, Popover, Button } from 'ant-design-vue';
160
- import SvgIcon from '@/component/svg/index.vue';
161
- import ImageCompress from '../utils/compressImage';
162
- import { parseParams } from '../utils/panelsetting';
163
-
164
- import { mapState, mapGetters, mapMutations, mapActions } from '../store/helper';
165
- import fetch, { qs } from '@/utils/chatFetch';
166
- import QuickReply from './quickReply';
167
- import voice from './voice';
168
- import ModalUserTransfer from '../components/modal-user-transfer';
169
- import ModalRefuseReason from '../components/modal-refuse-reason';
170
- import vexutils from '@/utils/vexutils';
171
- export default {
172
- inject: ['store', 'i18nText', 'dispatchEvent', 'registerEvent', 'unregisterEvent'],
173
- components: {
174
- AModal: Modal,
175
- [Layout.Footer.name]: Layout.Footer,
176
- [Tooltip.name]: Tooltip,
177
- [Upload.name]: Upload,
178
- [Button.name]: Button,
179
- [Popover.name]: Popover,
180
- SvgIcon,
181
- ModalRefuseReason,
182
- ModalUserTransfer,
183
- QuickReply,
184
- voice
185
- },
186
- props: {
187
- // 隐藏排队状态底部信息
188
- hideQueueBtns: {
189
- type: Boolean,
190
- default: false
191
- },
192
- validTalkingEnv: {
193
- type: Function
194
- },
195
- curChatType: {
196
- type: String
197
- },
198
- clientFinish: {
199
- type: Boolean,
200
- default: true
201
- },
202
- addvisible: {
203
- type: Boolean,
204
- default: false
205
- },
206
- isShowVoice: {
207
- type: Boolean,
208
- default: false
209
- }
210
- },
211
- data() {
212
- return {
213
- // message: "",
214
- visible: false,
215
- modalShow: false,
216
- modalData: {},
217
- ping: false,
218
- templateId: null, // 卡片模板
219
- isFullScreen: false,
220
- emojiList: require('../utils/emoji.json'),
221
- quickData: [],
222
- quickShow: false,
223
- screenshot: false,
224
- screenshotFile: null,
225
- timer: null,
226
- accessIntercept: false,
227
- curToolbarItem: {},
228
- showVoice: false,
229
- transferVisible: false,
230
- refuseVisible: false,
231
-
232
- apiResult: {}, // 前置条件的返回值
233
- };
234
- },
235
- computed: {
236
- ...mapGetters([
237
- 'userInfo',
238
- 'assemblyId',
239
- 'sessionId',
240
- 'serviceId',
241
- 'onChating',
242
- 'roomId',
243
- 'isServer',
244
- 'assemblySetting',
245
- 'chatType',
246
- 'sessionEnd',
247
- 'closedSession',
248
- 'clientParams',
249
- 'footerMessage',
250
- 'queueItem',
251
- 'sessionType',
252
- 'groupMembers',
253
- 'curScrollItem'
254
- ]),
255
- ...mapState({
256
- classify: state => state.lastClassify || state.classify
257
- }),
258
- message() {
259
- return this.footerMessage;
260
- },
261
- sendDisabled() {
262
- return this.message.length === 0;
263
- },
264
- orgId() {
265
- return this.userInfo?.sysParams?.orgId;
266
- },
267
- userId() {
268
- return this.userInfo?.sysParams?.userId;
269
- },
270
- toolbar() {
271
- if (!this.isServer) return [];
272
- let toolbar = this.assemblySetting.toolbar || [];
273
- toolbar = toolbar.filter(item => {
274
- const { source, isChecked, isCloseChecked, isClosedAble } = item;
275
- if (source === 'PC' && (
276
- (!this.closedSession && isChecked === 'Y') ||
277
- (this.closedSession && isCloseChecked === 'Y' && isChecked === 'Y' && isClosedAble === 'Y')
278
- )
279
- ){
280
- const showCondition = item.showCondition || [];
281
- const i = showCondition.findIndex(({ p_name, p_value }) => {
282
- const params = this.clientParams;
283
- const value = params[p_name];
284
- if (p_value.startsWith('form.')) {
285
- return value !== p_name[p_value.slice(5)];
286
- }
287
- return value !== p_value;
288
- });
289
- return i < 0;
290
- }
291
- return false;
292
- });
293
- return toolbar;
294
- },
295
- isListClassify() {
296
- return this.assemblySetting.isListClassify === 'Y';
297
- },
298
- showOverlay() {
299
- return this.curChatType != 'robot' || this.clientFinish;
300
- },
301
- accessText() {
302
- let obj = this.assemblySetting.serverSetting.serverInfo.filter(item => item.key === 'ongoingButton')[0];
303
- return obj?.value;
304
- },
305
- accessDesc() {
306
- let obj = this.assemblySetting.serverSetting.serverInfo.filter(item => item.key === 'ongoingDescription')[0];
307
- return obj?.value;
308
- },
309
- accessInterceptObj() {
310
- let obj = this.assemblySetting.accessInterceptSetting?.filter(item => item.source === 'PC');
311
- if (obj && obj[0]) {
312
- return obj[0];
313
- }
314
- return undefined;
315
- },
316
- // 拦截url
317
- accessInterceptUrl() {
318
- const { accessInterceptObj: obj, apiResult } = this;
319
- if (obj) {
320
- return this.getLinkAddress(obj,{result:apiResult});
321
- }
322
- return '';
323
- },
324
- // 拦截title
325
- accessInterceptTitle() {
326
- const { accessInterceptObj: obj } = this;
327
- if (obj) {
328
- let res = obj.titleI18n ? this.i18nText(obj.titleI18n) : obj.title;
329
- return res || '接入拦截';
330
- }
331
- return '接入拦截';
332
- },
333
- // 是否开启 排队转接
334
- enableTransfer() {
335
- if (this.assemblySetting.type === 'single') {
336
- let setting = null;
337
- if (this.isListClassify) {
338
- const classify = this.classify;
339
- const { listClassify = [] } = this.assemblySetting;
340
- const target = listClassify.find(item => item.classify === classify);
341
- setting = target ? target.serverFunctionSetting : null;
342
- } else {
343
- setting = this.assemblySetting?.serverSetting?.functionSetting;
344
- }
345
- if (setting) {
346
- const v = setting.find(item => item.function === 'TRANSFER_BEFORE');
347
- return v ? v.isChecked == 'Y' : false;
348
- }
349
- }
350
- return false;
351
- },
352
- enableRefuse() {
353
- let setting = null;
354
- if (this.isListClassify) {
355
- const classify = this.classify;
356
- const { listClassify = [] } = this.assemblySetting;
357
- const target = listClassify.find(item => item.classify === classify);
358
- setting = target ? target.serverFunctionSetting : null;
359
- } else {
360
- setting = this.assemblySetting?.serverSetting?.functionSetting;
361
- }
362
- if (setting) {
363
- const v = setting.find(item => item.function === 'ACCESS_REFUSE');
364
- return v ? v.isChecked == 'Y' : false;
365
- }
366
- return false;
367
- },
368
- refuseReasonList() {
369
- const list = this.assemblySetting?.refuseReasonSetting?.list || [];
370
- return list.filter(v => !!v.text);
371
- }
372
- },
373
-
374
- mounted() {
375
- this.bindEvents();
376
- },
377
- methods: {
378
- ...mapMutations([
379
- 'setLastClassify',
380
- 'setSessionTimer',
381
- 'setScrollQueueId',
382
- 'setCurScrollItem',
383
- 'setIsAppendMsg',
384
- 'setMessage',
385
- 'setShowVideo',
386
- 'setCaller',
387
- 'setMedicalOrPrescription',
388
- 'setModalVisible',
389
- 'setFooterMessage',
390
- 'setQueueItem',
391
- 'setSessionId',
392
- 'setCurrentTab',
393
- 'setClientId',
394
- 'setOnChating',
395
- 'setClientParams',
396
- 'setSessionHistoryList',
397
- 'setSessionEnd',
398
- 'setQuestionId',
399
- 'setDictionaryKey',
400
- 'setMultiVideoShow',
401
- 'setVideoStatus',
402
- 'setVideoMode',
403
- 'setAddMemberType',
404
- 'setShowAudio',
405
- 'setVideoMembers',
406
- 'setLastCurrentTab',
407
- 'setIsRecorderVoice',
408
- 'setVideoData',
409
- 'setSessionType'
410
- ]),
411
- ...mapMutations({
412
- clearMsgList: 'setMsgList'
413
- }),
414
- ...mapActions(['sendMessage', 'sendRobotMessage', 'handleBotChat', 'setMsgList', 'setChatTimer']),
415
- deleteStyle(value) {
416
- // 去掉样式
417
- value = value.replace(/style\s*?=\s*?(['"])[\s\S]*?\1/g, '');
418
- value = value.replace(/data\s*?=\s*?(['"])[\s\S]*?\1/g, '');
419
- value = value.replace(/class\s*?=\s*?(['"])[\s\S]*?\1/g, match => {
420
- if (match.includes('twa')) {
421
- return match;
422
- } else {
423
- return '';
424
- }
425
- });
426
- value = value.replace(/face\s*?=\s*?(['"])[\s\S]*?\1/g, '');
427
- value = value.replace(/<a/g, '<span');
428
- value = value.replace(/a>/g, 'span>');
429
- value = value.replace(/<i/g, '<span');
430
- value = value.replace(/i>/g, 'span>');
431
- value = value.replace(/<u/g, '<span');
432
- value = value.replace(/u>/g, 'span>');
433
- value = value.replace(/<strong/g, '<span');
434
- value = value.replace(/strong>/g, 'span>');
435
- value = value.replace(/<h\d/g, '<span');
436
- value = value.replace(/h\d>/g, 'span>');
437
- value = value.replace(/<b>/g, '<span>');
438
- value = value.replace(/b>/g, 'span>');
439
- value = value.replace(/<font/g, '<span');
440
- value = value.replace(/font>/g, 'span>');
441
- return value;
442
- },
443
- // 更新 message值 (未用v-html,修改后需要手动设置聊天框html内容)
444
- updateMessage(value) {
445
- this._editValue = value;
446
- this.setFooterMessage(`${value}`);
447
- const editRef = this.$refs.contentEdit;
448
- if (!editRef) return;
449
- editRef.innerHTML = value;
450
- },
451
- // 根据 聊天框 html节点内容 设置 message值 (未用v-html,html内容更改需主动设置message值)
452
- setMessageByHTML() {
453
- const editRef = this.$refs.contentEdit;
454
- if (!editRef) return;
455
- const message = editRef.innerHTML;
456
- this._editValue = message;
457
- this.setFooterMessage(`${message}`);
458
- },
459
-
460
- showEmoji() {
461
- // this.visible = true;
462
- },
463
-
464
- isShow(type) {
465
- if (type === 'SMILINEFACE' && this.curChatType === 'robot') return true;
466
- if (this.isListClassify) {
467
- let label;
468
- if (this.isServer) {
469
- label = 'serverFunctionSetting';
470
- } else {
471
- label = 'clientFunctionSetting';
472
- }
473
- let classifyItem = this.assemblySetting.listClassify.filter(item => item.classify === this.classify);
474
- if (!classifyItem || !classifyItem.length) return;
475
- let funs = classifyItem[0][label] || [];
476
- return funs.find(item => item.function === type)?.isChecked === 'Y';
477
- }
478
- let label;
479
- if (this.isServer) {
480
- label = 'serverSetting';
481
- } else {
482
- label = 'clientSetting';
483
- }
484
- let funs = this.assemblySetting[label]?.functionSetting || [];
485
- return funs.find(item => item.function === type)?.isChecked === 'Y';
486
- },
487
-
488
- handleEmojiClick(item) {
489
- let emojiHtml = `<p contenteditable="false" class="twa emoji ${item}"></p>&nbsp`;
490
- this.insertToEdit(emojiHtml);
491
- },
492
- /* edit-content光标定位 */
493
- resetSelection() {
494
- let edit = this.$refs.contentEdit;
495
- let sel, range;
496
- if (window.getSelection && document.createRange) {
497
- range = document.createRange();
498
- range.selectNodeContents(edit);
499
- range.collapse(true);
500
- range.setEnd(edit, edit.childNodes.length);
501
- range.setStart(edit, edit.childNodes.length);
502
- sel = window.getSelection();
503
- sel.removeAllRanges();
504
- sel.addRange(range);
505
- }
506
- },
507
-
508
- insertToEdit(value, isEditing) {
509
- const { _editSel, _editRange } = this;
510
- let sel, range;
511
- if (window.getSelection) {
512
- if (!isEditing) {
513
- if (_editRange) {
514
- sel = _editSel;
515
- range = _editRange;
516
- } else {
517
- // 初始状态没有focus聊天框,需要手动focus 获取光标位置
518
- const editRef = this.$refs.contentEdit;
519
- if (!editRef) return;
520
- editRef.focus();
521
- this.saveRange();
522
- sel = this._editSel;
523
- range = this._editRange;
524
- }
525
- } else {
526
- sel = window.getSelection();
527
- range = sel.getRangeAt(0);
528
- }
529
- range.deleteContents();
530
- let frag;
531
- if (range.createContextualFragment) {
532
- frag = range.createContextualFragment(value);
533
- } else {
534
- const el = document.createElement('div');
535
- el.innerHTML = value;
536
- frag = document.createDocumentFragment();
537
- let node, lastNode;
538
- while ((node = el.firstChild)) {
539
- lastNode = frag.appendChild(node); // 之所以能终止循环,是因为el的child一旦添加到dom后,作为fragment里的的child就没了。那么el.firstChild就会返回null
540
- }
541
- }
542
- const lastNode = frag.lastChild;
543
- range.insertNode(frag);
544
- // 把光标挪到插入的元素后面
545
- range.setStartAfter(lastNode);
546
- sel.removeAllRanges();
547
- sel.addRange(range);
548
- } else {
549
- range = isEditing || !_editRange ? document.selection.createRange() : _editRange;
550
- range.pasteHTML(value);
551
- range.select();
552
- }
553
- this.setMessageByHTML();
554
- },
555
- saveRange() {
556
- if (window.getSelection) {
557
- this._editSel = window.getSelection();
558
- this._editRange = this._editSel.getRangeAt(0);
559
- } else {
560
- this._editRange = document.selection.createRange();
561
- }
562
- },
563
- handleInput(e) {
564
- if (e.inputType === 'deleteContentBackward') {
565
- this.screenshot = false;
566
- this.screenshotFile = null;
567
- }
568
- setTimeout(() => {
569
- if (this.ping || this.pasteTimer) return;
570
- this.setMessageByHTML();
571
- }, 0);
572
- },
573
- changeData(e) {
574
- let message = e.srcElement.innerHTML;
575
- if (e.inputType === 'deleteContentBackward') {
576
- let div = document.createElement('div');
577
- div.innerHTML = e.srcElement.innerHTML;
578
- if (div.lastChild?.className?.startsWith('twa emoji')) {
579
- div.removeChild(div.lastChild);
580
- message = div.innerHTML;
581
- }
582
- this.screenshot = false;
583
- this.screenshotFile = null;
584
- }
585
-
586
- setTimeout(() => {
587
- if (this.ping) return;
588
- this.setFooterMessage(`${message}`);
589
- this.$nextTick().then(() => {
590
- this.resetSelection();
591
- this.ping = false;
592
- });
593
- }, 0);
594
- },
595
-
596
- beforeUpload(data) {
597
- let { type } = data;
598
- if (!type.startsWith('image/')) {
599
- this.$message.warning(`${this.i18nText('1.9.357')}!`);
600
- return;
601
- }
602
- let that = this;
603
- this.$confirm({
604
- title: this.i18nText('1.9.331'),
605
- content: `${this.i18nText('1.9.358')}?`,
606
- okText: '确定',
607
- cancelText: '取消',
608
- onOk() {
609
- return that.uploadImg(data);
610
- },
611
- onCancel() {
612
- return false;
613
- }
614
- });
615
- },
616
-
617
- /* 压缩上传 */
618
- uploadImg(file) {
619
- const hide = this.$message.loading(`${this.i18nText('1.9.199')}...`, 0);
620
- new ImageCompress(file, 'canvas', fetch, {
621
- url: '/hospitalUpload/picture'
622
- })
623
- .execute()
624
- .then(({ data }) => {
625
- if (data.result === 'SUCCESS') {
626
- this.doMsgSend(1, data.map.url);
627
- hide();
628
- } else {
629
- hide();
630
- this.$message.info(data.msg || `${this.i18nText('1.9.174')}!`);
631
- }
632
- })
633
- .catch(() => {
634
- hide();
635
- this.$message.info(`${this.i18nText('1.9.174')}!`);
636
- })
637
- .finally(() => {
638
- this.screenshotFile = null;
639
- this.screenshot = false;
640
- this.updateMessage('');
641
- });
642
- },
643
-
644
- /* 发送消息 */
645
- doMsgSend(type, content) {
646
- let body = {
647
- content: type === 0 ? `<div>${content}</div>` : content,
648
- type: type
649
- };
650
- if (this.curChatType === 'robot') {
651
- // this.setQuestionId("");
652
- // this.setDictionaryKey("");
653
- this.sendRobotMessage(content);
654
- } else {
655
- this.sendMessage(body);
656
- }
657
- },
658
- handleMsgSend() {
659
- // 截图上传
660
- if (this.screenshot) {
661
- this.uploadImg(this.screenshotFile);
662
- return;
663
- }
664
- if (this.chatType === 'bot') {
665
- this.handleBotChat(this.message).then(isHuman => {
666
- if (isHuman) {
667
- this.$parent.$parent.$parent.initWebSocket();
668
- }
669
- this.updateMessage('');
670
- });
671
- return;
672
- }
673
-
674
- const editRef = this.$refs.contentEdit;
675
- if (editRef) {
676
- const message = editRef.innerText;
677
- if (message.length > 2000) {
678
- this.$message.warning('请控制在2000字以内!');
679
- return;
680
- }
681
- }
682
- // let reg = /(.*?)(<div><br><\/div>)*?$/;
683
- let reg = /(.*?)(<div>(<span>)?<br>(<\/span>)?<\/div>)*?$/;
684
- let message = this.message.match(reg);
685
- if (message[2]) {
686
- this.updateMessage(message[1]);
687
- }
688
- this.updateMessage(this.message.replace(/style/g, 'data'));
689
- if (this.message.trim()) {
690
- this.doMsgSend(0, this.message);
691
- } else {
692
- return this.$message.warning(this.i18nText('1.9.359'));
693
- }
694
- this.updateMessage('');
695
- },
696
- handleKeyDown(e) {
697
- // 判断enter 发送还是换行
698
- if (e.keyCode === 13) {
699
- e.preventDefault();
700
- if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
701
- this.handleWrap();
702
- } else {
703
- this.handleMsgSend();
704
- }
705
- }
706
- },
707
- handleWrap() {
708
- let ctn = `<br>`;
709
- // 非ie环境 元素后面没有node的时候换行需要两个<br>
710
- if (!/msie/.test(navigator.userAgent) && window.getSelection) {
711
- const focusNode = window.getSelection().focusNode;
712
- let nextSibling = focusNode.nextSibling;
713
- if (focusNode === this.$refs.contentEdit) {
714
- nextSibling = focusNode.childNodes[0];
715
- }
716
- do {
717
- if (!nextSibling || nextSibling.nodeValue || nextSibling.tagName == 'BR') break;
718
- } while ((nextSibling = nextSibling.nextSibling));
719
- if (!nextSibling) {
720
- ctn += ctn;
721
- }
722
- }
723
- this.insertToEdit(ctn);
724
- this.$nextTick().then(() => {
725
- this.ping = false;
726
- });
727
- },
728
- handleVideoClick(type) {
729
- // this.curScrollItem.type 2多人 1单人
730
- if (this.sessionType === 2) {
731
- this.$emit('update:addvisible', true);
732
- this.setAddMemberType(2);
733
- this.setVideoMode(type === 'voice' ? 1 : 2);
734
- } else {
735
- const openTalking = sip => {
736
- this.setShowAudio(true);
737
- this.setVideoMode(type === 'voice' ? 1 : 2);
738
- this.$nextTick(() => {
739
- this.setVideoMembers([]);
740
- });
741
- this.setVideoData({
742
- callerSipKey: sip?.username,
743
- callerSipName: sip?.name
744
- });
745
- };
746
- if (this.validTalkingEnv) {
747
- this.validTalkingEnv().then(res => {
748
- if (res.status) {
749
- openTalking(res.sip);
750
- }
751
- });
752
- } else {
753
- openTalking();
754
- }
755
- }
756
- // this.groupMembers.length > 2
757
- return;
758
- },
759
- handleVoice(val) {
760
- val && this.setIsRecorderVoice(true);
761
- this.showVoice = val;
762
- },
763
- showPrescription() {
764
- this.setModalVisible(true);
765
- this.setMedicalOrPrescription('prescription');
766
- },
767
- showMedicalRecord() {
768
- this.setModalVisible(true);
769
- this.setMedicalOrPrescription('medical');
770
- },
771
- beofreToolBarClick(item) {
772
- // 操作栏前置条件
773
- return new Promise((resolve, reject) => {
774
- const { precondition } = item;
775
- if (precondition) {
776
- this.requestPreCondition(precondition)
777
- .then(res => {
778
- const result = res.data.result === 'SUCCESS';
779
- !result && this.$message.error(res.data.resultMsg || this.i18nText('1.9.22'));
780
- resolve(result);
781
- })
782
- .catch(error => {
783
- this.$message.error(error.resultMsg || this.i18nText('1.9.22'));
784
- resolve(false);
785
- });
786
- } else {
787
- resolve(true);
788
- }
789
- });
790
- },
791
- async handleToolBarClick(item) {
792
- const validate = await this.beofreToolBarClick(item);
793
- if (!validate) return;
794
- let { openMode, name, templateId, isFullScreen } = item;
795
- this.dispatchEvent('click_toolbar', { ...item });
796
- if (!item.address) return;
797
- this.curToolbarItem = item;
798
- this.templateId = templateId;
799
- this.isFullScreen = isFullScreen === 'Y';
800
- let address = this.getLinkAddress(item);
801
- if (openMode === 'WINDOW') {
802
- window.open(address, name);
803
- return;
804
- }
805
- if (openMode === 'EJECT') {
806
- this.modalShow = true;
807
- this.modalData = Object.assign({}, item, { address });
808
- this.$nextTick().then(() => {
809
- window.addEventListener('message', this.iframeEvent);
810
- });
811
- return;
812
- }
813
- },
814
- iframeEvent(event) {
815
- const method = event?.data?.method;
816
- this[method]?.(event);
817
- },
818
- // 发送
819
- handleParentMessageSend({ data, source, origin }) {
820
- let params = {
821
- assemblyId: this.assemblyId,
822
- sessionId: this.sessionId,
823
- orgId: this.orgId
824
- };
825
- params = Object.assign(params, data.data);
826
- let { actionType } = this.curToolbarItem;
827
- actionType && Object.assign(params, { actionType });
828
- return fetch.post('/chat/access/sendToolBarData', qs.stringify(params)).then(({ data }) => {
829
- if (data.result === 'SUCCESS') {
830
- source.postMessage({ status: 0, resultMsg: data.resultMsg }, origin);
831
- } else {
832
- source.postMessage({ status: 1, resultMsg: data.resultMsg }, origin);
833
- }
834
- });
835
- },
836
- // 关闭
837
- handleClose() {
838
- this.modalShow = false;
839
- window.removeEventListener('message', this.iframeEvent);
840
- },
841
- getLinkAddress(item,paramsData= {}) {
842
- let t = {talbe:{},form:{},sys:{}};
843
- Object.assign(t,paramsData ||{})
844
- let { address = '', params = [] } = item;
845
- let urlParams = [];
846
- params.forEach(({ p_name, p_value }) => {
847
- let value;
848
- if (p_value.startsWith('form.')) {
849
- value = this.clientParams[p_value.slice(5)];
850
- } else if( /\${result.(.*?)\}/g.test(p_value)) {
851
- value = vexutils.handleSysParams(p_value,t)
852
- } else {
853
- value = p_value;
854
- }
855
- urlParams.push(`${p_name}=${value}`);
856
- });
857
- if(/\${result.(.*?)\}/g.test(address)){
858
- address = vexutils.handleSysParams(address,t)
859
- }
860
- let { actionType } = this.curToolbarItem;
861
- actionType && urlParams.push(`actionType=${actionType}`);
862
- if (address.includes('?')) {
863
- address += `&${urlParams.join('&')}`;
864
- } else {
865
- address += `?${urlParams.join('&')}`;
866
- }
867
- return address;
868
- },
869
- handleOk() {
870
- window.handleParentMessageSend();
871
- },
872
- // 剪切板粘贴
873
- handlePaste(e) {
874
- const cbd = e.clipboardData;
875
- const ua = window.navigator.userAgent;
876
- // 如果是 Safari 直接 return
877
- if (!(e.clipboardData && e.clipboardData.items)) {
878
- this.handlePasteText(e);
879
- return;
880
- }
881
- if (
882
- cbd.items &&
883
- cbd.items.length === 2 &&
884
- cbd.items[0].kind === 'string' &&
885
- cbd.items[1].kind === 'file' &&
886
- cbd.types &&
887
- cbd.types.length === 2 &&
888
- cbd.types[0] === 'text/plain' &&
889
- cbd.types[1] === 'Files' &&
890
- ua.match(/Macintosh/i) &&
891
- Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49
892
- ) {
893
- this.handlePasteText(e);
894
- return;
895
- }
896
- const hasFile = this.handleImgPaste(cbd.items);
897
- if (hasFile) {
898
- e.stopPropagation();
899
- e.preventDefault();
900
- return;
901
- }
902
- this.handlePasteText(e);
903
- },
904
- handleImgPaste(files) {
905
- let hasFile = false;
906
- for (let i = 0; i < files.length; i++) {
907
- let item = files[i];
908
- if (item.kind == 'file') {
909
- hasFile = true;
910
- // blob 就是从剪切板获得的文件,可以进行上传或其他操作
911
- const blob = item.getAsFile();
912
- if (blob.size === 0) {
913
- return;
914
- }
915
- const reader = new FileReader();
916
- const imgs = new Image();
917
- imgs.file = blob;
918
- reader.onload = e => {
919
- imgs.src = e.target.result;
920
- imgs.width = '150';
921
- this.updateMessage(this.htmlToString(imgs));
922
- this.$nextTick().then(() => {
923
- this.resetSelection();
924
- this.screenshot = true;
925
- this.screenshotFile = blob;
926
- });
927
- };
928
- reader.readAsDataURL(blob);
929
- }
930
- }
931
- return hasFile;
932
- },
933
- // 处理文本粘贴
934
- handlePasteText() {
935
- if (this.pasteTimer) clearTimeout(this.pasteTimer);
936
- const ele = this.$refs.contentEdit;
937
- if (!ele) return;
938
- let before = ele.innerHTML;
939
- this.pasteTimer = setTimeout(() => {
940
- function getPasteHtml(before, after) {
941
- // 计算粘贴后html不同的位置起点和终点(插入"abc",则得出sPos为0,ePos为2)
942
- var sPos = -1,
943
- ePos = -1;
944
- for (var i = 0, len = after.length; i < len; ++i) {
945
- if (sPos == -1 && before.substr(i, 1) != after.substr(i, 1)) sPos = i;
946
- if (ePos == -1 && before.substr(before.length - i - 1, 1) != after.substr(after.length - i - 1, 1)) ePos = i;
947
-
948
- if ((sPos != -1 && ePos != -1) || len - 1 - ePos <= sPos) break;
949
- }
950
- if (sPos == -1 || ePos == -1) return;
951
- ePos = len - 1 - ePos;
952
-
953
- if (ePos <= sPos) {
954
- // 遇到像ab,粘贴ab后变成abab这种了,这是需要从sPos开始往后找,找到和before一样的为止
955
- i = sPos;
956
- var beforeSPosNext = before.substr(i + 1, 10); // 取后10个来对比,如果没有10个,则取最多
957
- while (++i < len) {
958
- if (beforeSPosNext == after.substr(i, beforeSPosNext.length)) {
959
- ePos = i;
960
- break;
961
- }
962
- }
963
- i == len && (ePos = len - 1);
964
- }
965
- // 如果有<和>,则需要计算在内
966
- if (after.substr(sPos - 1, 1) == '<') --sPos;
967
- if (after.substr(ePos + 1, 1) == '>') ++ePos;
968
-
969
- // 分段获得文本,前半段 插入段 后半段
970
- var pastedText = after.substring(sPos, ePos + 1),
971
- formerText = after.substr(0, sPos),
972
- latterText = after.substr(sPos + pastedText.length);
973
-
974
- // 判断formerText和pastedText是否闭合了
975
- var lastLT = formerText.lastIndexOf('<'),
976
- lastGT = formerText.lastIndexOf('>');
977
- if (lastGT < lastLT) {
978
- // 标签断了xxxxx<
979
- pastedText = formerText.slice(lastLT) + pastedText;
980
- formerText = formerText.slice(0, lastLT);
981
- }
982
-
983
- lastLT = pastedText.lastIndexOf('<');
984
- lastGT = pastedText.lastIndexOf('>');
985
- if (lastGT < lastLT) {
986
- // 标签断了<xxxxxx
987
- var _insertGT = latterText.indexOf('>') + 1;
988
- pastedText += latterText.slice(0, _insertGT);
989
- latterText = latterText.slice(_insertGT);
990
- }
991
- return [formerText, pastedText, latterText];
992
- }
993
- var after = ele.innerHTML;
994
- const data = getPasteHtml(before, after);
995
- if (data) {
996
- let [formerText, insertText, latterText] = data;
997
- insertText = this.deleteStyle(insertText);
998
- ele.innerHTML = formerText + insertText + "<span class='pasteCaretPosHelper'></span>" + latterText; // 插入pasteCaretPosHelper以帮助定位光标位置
999
- var pasteCaretPosHelper = ele.querySelector('.pasteCaretPosHelper'),
1000
- range,
1001
- selection;
1002
- if (pasteCaretPosHelper) {
1003
- if (document.createRange) {
1004
- // Chrome, IE 9+
1005
- range = document.createRange();
1006
- range.setStartAfter(pasteCaretPosHelper);
1007
- range.collapse(false);
1008
- selection = window.getSelection();
1009
- selection.removeAllRanges();
1010
- selection.addRange(range);
1011
- } else if (document.selection) {
1012
- // IE 8 and lower
1013
- range = document.body.createTextRange();
1014
- range.moveToElementText(pasteCaretPosHelper);
1015
- range.collapse(false);
1016
- range.select();
1017
- }
1018
- pasteCaretPosHelper.parentNode.removeChild(pasteCaretPosHelper);
1019
- }
1020
- }
1021
- this.setMessageByHTML();
1022
- this.saveRange();
1023
- this.pasteTimer = null;
1024
- }, 50);
1025
- },
1026
- // 拖拽上传图片
1027
- handleFiles(files) {
1028
- this.handleImgPaste(files);
1029
- },
1030
- htmlToString(dom) {
1031
- let div = document.createElement('div');
1032
- div.appendChild(dom);
1033
- return div.innerHTML;
1034
- },
1035
- /**
1036
- * 接入拦截
1037
- */
1038
- handleAccessIntercept(result) {
1039
- let { data } = result;
1040
- let { status, data: param } = data;
1041
- if (status === 1) {
1042
- this.accessChat(param).finally(() => {
1043
- this.accessIntercept = false;
1044
- });
1045
- } else {
1046
- this.accessIntercept = false;
1047
- }
1048
- },
1049
- /* 客服点击接入按钮进入会话 */
1050
- async handleAccess() {
1051
- this.apiResult = {};
1052
- let accessInterceptValidate = this.handleAccessInterceptValue();
1053
- let data = null; // 接口参数
1054
- if (accessInterceptValidate && this.accessInterceptUrl) {
1055
- const r = await this.beforeAccessIntercept();
1056
- if (!r) return;
1057
- if (r.value == 'Y') {
1058
- this.accessIntercept = true;
1059
- this.$nextTick().then(() => {
1060
- window.addEventListener('message', this.handleAccessIntercept);
1061
- });
1062
- return;
1063
- } else {
1064
- data = r.data; // 获取接入拦截返回参数
1065
- }
1066
- }
1067
- clearTimeout(this.timer);
1068
- this.timer = setTimeout(() => {
1069
- this.accessChat(data);
1070
- }, 300);
1071
- },
1072
- // 条件转换请求
1073
- requestPreCondition(precondition) {
1074
- const { targetType, targetName, targetId, params } = precondition;
1075
- const data = parseParams(params, this.clientParams);
1076
- let url = '';
1077
- if (targetType === 'API') {
1078
- data.url = targetName;
1079
- url = `/api/executeApi`;
1080
- } else {
1081
- data.sqlEnterId = targetId;
1082
- url = `/form/saveSqlEnter`;
1083
- }
1084
- return fetch.post(url, qs.stringify(data));
1085
- },
1086
- async beforeAccessIntercept() {
1087
- function parseResult(r) {
1088
- let value = 'Y';
1089
- const data = r.split('&').reduce((obj, str) => {
1090
- const [k, v] = str.split('=');
1091
- if (k) {
1092
- if (k == 'value') {
1093
- value = v || value;
1094
- } else {
1095
- obj = obj || {};
1096
- obj[k] = v || '';
1097
- }
1098
- }
1099
- return obj;
1100
- }, null);
1101
- return {
1102
- value,
1103
- data
1104
- };
1105
- }
1106
- this.apiResult = {};
1107
- const accessInterceptSetting = this.assemblySetting.accessInterceptSetting || [];
1108
- const setting = accessInterceptSetting.find(item => item.source == 'PC' && item.isChecked == 'Y' && item.precondition);
1109
- if (setting) {
1110
- const precondition = setting.precondition;
1111
- try {
1112
- let res = await this.requestPreCondition(precondition);
1113
- this.apiResult = res?.data?.map || {};
1114
- if (res.data.result === 'SUCCESS') {
1115
- let value = res.data.map?.result;
1116
- value = value ? JSON.parse(value) : {};
1117
- return value.value ? parseResult(value.value) : false;
1118
- } else {
1119
- this.$message.error(res.data.resultMsg || this.i18nText('1.9.22'));
1120
- return false;
1121
- }
1122
- } catch (error) {
1123
- this.$message.error(error.resultMsg || this.i18nText('1.9.22'));
1124
- return false;
1125
- }
1126
- }
1127
- return {
1128
- value: 'Y',
1129
- data: null
1130
- };
1131
- },
1132
- handleAccessInterceptValue() {
1133
- let { listClassify = [], isListClassify, serverSetting = {} } = this.assemblySetting;
1134
- let list = [];
1135
- if (isListClassify != 'Y') {
1136
- let { functionSetting = [] } = serverSetting;
1137
- list = functionSetting;
1138
- } else {
1139
- let curList = listClassify.find(item => item.classify == this.classify);
1140
- if (!curList) return false;
1141
- list = curList.serverFunctionSetting || [];
1142
- }
1143
- if (!list.length) return true;
1144
- let curItem = list.find(item => item.function == 'ACCESS_INTERCEPT');
1145
- if (!curItem) return true;
1146
- return curItem.isChecked == 'Y';
1147
- },
1148
- accessChat(data) {
1149
- let { bid: userId, username, userType } = this.queueItem;
1150
- let params = {
1151
- assemblyId: this.assemblyId,
1152
- userId,
1153
- userType,
1154
- lastSessionId: this.sessionId,
1155
- relateId: this.clientParams.relateId
1156
- };
1157
- if (data) {
1158
- params.addParams = JSON.stringify(data);
1159
- }
1160
- return fetch.post('/chat/service/accessChat', qs.stringify(params)).then(({ data }) => {
1161
- if (data.result === 'SUCCESS') {
1162
- let { countdown, clientParams, sessionType, countdownDesc } = data.map;
1163
- this.setSessionId(data.map.sessionId);
1164
- this.clearMsgList();
1165
- this.setMsgList();
1166
- this.setCurrentTab('session');
1167
- this.setLastCurrentTab('session');
1168
- this.setClientId(username);
1169
- this.setOnChating(true);
1170
- this.setSessionEnd(false);
1171
- this.setQueueItem(null);
1172
- sessionType && this.setSessionType(sessionType);
1173
- if (countdown >= 0) {
1174
- this.setChatTimer({ countdown, countdownDesc });
1175
- }
1176
- this.setClientParams(clientParams);
1177
- this.getSessionHistoryList();
1178
- this.dispatchEvent('user_accessSuccess', {
1179
- map: data.map,
1180
- user: { ...this.curScrollItem }
1181
- });
1182
- } else {
1183
- this.$message.warning(data.resultMsg);
1184
- }
1185
- });
1186
- },
1187
- handleOpenTransfer() {
1188
- if (this.enableTransfer) {
1189
- this.transferVisible = true;
1190
- }
1191
- },
1192
- handleTransfer(data) {
1193
- const receiveId = data.converUserId;
1194
- if (receiveId) {
1195
- if (this.userId == receiveId) {
1196
- this.$message.warning(this.i18nText('1.2.7.14.1'));
1197
- return;
1198
- }
1199
- this.transferVisible = false;
1200
- fetch({
1201
- url: '/chat/service/transferQueue',
1202
- method: 'POST',
1203
- data: qs.stringify({
1204
- relateId: this.clientParams.relateId,
1205
- orgId: this.orgId,
1206
- assemblyId: this.assemblyId,
1207
- userId: this.curScrollItem?.bid,
1208
- userType: this.curScrollItem?.userType || '',
1209
- receiveId: receiveId,
1210
- deptId: data.deptId,
1211
- remark: data.converMark
1212
- })
1213
- })
1214
- .then(({ data }) => {
1215
- if (data.result === 'SUCCESS') {
1216
- this.dispatchEvent('user_transferQueueSuccess', {
1217
- user: { ...this.curScrollItem }
1218
- });
1219
- this.$message.success(this.i18nText('1.9.336'));
1220
- this.clearCurrentSession(true);
1221
- } else {
1222
- this.$message.warning(data.resultMsg || this.i18nText('1.10.201') + this.i18nText('1.9.22'));
1223
- }
1224
- })
1225
- .catch(err => {
1226
- this.$message.warning(err.resultMsg || this.i18nText('1.10.201') + this.i18nText('1.9.22'));
1227
- });
1228
- } else {
1229
- this.$message.warning(this.i18nText('1.9.335'));
1230
- }
1231
- },
1232
- getSessionHistoryList() {
1233
- this.setSessionHistoryList([]);
1234
- if (this.assemblySetting.recordScopeSetting === 'CURRENT') return;
1235
- fetch
1236
- .get('/chat/service/getPastSessionList', {
1237
- params: { sessionId: this.sessionId }
1238
- })
1239
- .then(({ data }) => {
1240
- if (data.result === 'SUCCESS') {
1241
- let list = data.list.map(item => {
1242
- if (item.id === this.sessionId || this.type === 'session') {
1243
- item.onChating = true;
1244
- }
1245
- return item;
1246
- });
1247
- this.setSessionHistoryList(list);
1248
- }
1249
- });
1250
- },
1251
- openQuickData() {
1252
- // this.quickShow = true;
1253
- if (this.quickData.length < 1) {
1254
- fetch
1255
- .get('/fastReply/getChatList', {
1256
- params: {
1257
- category: this.assemblySetting.chatPublicCategory
1258
- }
1259
- })
1260
- .then(({ data }) => {
1261
- if (data.result === 'SUCCESS') {
1262
- this.quickData = data.list;
1263
- }
1264
- });
1265
- }
1266
- },
1267
- // 清除当前会话
1268
- clearCurrentSession(update = false) {
1269
- this.clearMsgList([]);
1270
- this.setSessionHistoryList([]);
1271
- this.setSessionTimer('');
1272
- this.setClientId('');
1273
- this.setSessionId('');
1274
- this.setScrollQueueId('');
1275
- this.setLastClassify('');
1276
- this.setCurScrollItem(null);
1277
- this.setQueueItem(null);
1278
- this.setClientParams(null);
1279
- if (update) {
1280
- // 重新设置 currentTab 触发 scrollList 组件 更新排队列表
1281
- const currentTab = this.store.state?.currentTab;
1282
- if (currentTab === 'queue') {
1283
- this.setCurrentTab('');
1284
- this.$nextTick(() => {
1285
- this.setCurrentTab(this.store.state?.currentTab || currentTab);
1286
- });
1287
- }
1288
- }
1289
- },
1290
- openRefuseReason() {
1291
- if (this.enableRefuse) {
1292
- this.refuseVisible = true;
1293
- }
1294
- },
1295
- handleRefuse(remark) {
1296
- if (!this.enableRefuse) return;
1297
- fetch({
1298
- url: '/chat/service/accessRefuse',
1299
- method: 'POST',
1300
- data: qs.stringify({
1301
- relateId: this.clientParams.relateId,
1302
- orgId: this.orgId,
1303
- assemblyId: this.assemblyId,
1304
- userId: this.curScrollItem?.bid,
1305
- userType: this.curScrollItem?.userType || '',
1306
- remark: remark || '其他'
1307
- })
1308
- })
1309
- .then(({ data }) => {
1310
- if (data.result === 'SUCCESS') {
1311
- this.dispatchEvent('user_refuseSuccess', {
1312
- user: { ...this.curScrollItem }
1313
- });
1314
- this.$message.success(this.i18nText('1.10.184'));
1315
- this.clearCurrentSession(true);
1316
- } else {
1317
- this.$message.warning(data.resultMsg || this.i18nText('1.9.353') + this.i18nText('1.9.22'));
1318
- }
1319
- })
1320
- .catch(err => {
1321
- this.$message.warning(err.resultMsg || this.i18nText('1.9.353') + this.i18nText('1.9.22'));
1322
- });
1323
- this.refuseVisible = false;
1324
- },
1325
- bindEvents() {
1326
- const handleDrop = e => {
1327
- e.stopPropagation();
1328
- e.preventDefault();
1329
- this.handleFiles(e.dataTransfer.items);
1330
- };
1331
- document.addEventListener('drop', handleDrop, false);
1332
- this.registerEvent('accessCurrentSession', this.handleAccess);
1333
- this.registerEvent('refuseCurrentSession', this.openRefuseReason);
1334
- this.registerEvent('transferCurrentQueueSession', this.handleOpenTransfer);
1335
- this.$on('hook:beforeDestroy', () => {
1336
- document.removeEventListener('drop', handleDrop, false);
1337
- window.removeEventListener('message', this.iframeEvent);
1338
- window.removeEventListener('message', this.handleAccessIntercept);
1339
- this.unregisterEvent('accessCurrentSession', this.handleAccess);
1340
- this.unregisterEvent('refuseCurrentSession', this.openRefuseReason);
1341
- this.unregisterEvent('transferCurrentQueueSession', this.handleOpenTransfer);
1342
- });
1343
- }
1344
- },
1345
- watch: {
1346
- accessIntercept: {
1347
- handler(val) {
1348
- if (!val) {
1349
- window.removeEventListener('message', this.handleAccessIntercept);
1350
- }
1351
- }
1352
- },
1353
- message: {
1354
- handler(v) {
1355
- // 使用 _editValue 判断是否组件外修改 message,并更新聊天框
1356
- if (this._editValue !== v) {
1357
- this.updateMessage(v);
1358
- }
1359
- }
1360
- }
1361
- }
1362
- };
1363
- </script>
1364
-
1365
- <style lang="less">
1366
- .chat-footer-modal-big {
1367
- padding: 10px;
1368
- &.ant-modal-wrap {
1369
- overflow: none;
1370
- }
1371
- .ant-modal {
1372
- width: 100%;
1373
- height: 100%;
1374
- top: 0;
1375
- padding-bottom: 0;
1376
- }
1377
- .ant-modal-content {
1378
- height: 100%;
1379
- }
1380
- .ant-modal-body {
1381
- height: calc(100% - 55px);
1382
- }
1383
- }
1384
- .chat-footer-modal-small {
1385
- &.ant-modal-wrap {
1386
- overflow: none;
1387
- }
1388
- .ant-modal {
1389
- width: 80%;
1390
- height: 80%;
1391
- padding-bottom: 0;
1392
- top: 60px;
1393
- }
1394
- .ant-modal-content {
1395
- height: 100%;
1396
- }
1397
- .ant-modal-body {
1398
- height: 100%;
1399
- padding: 0;
1400
- }
1401
- }
1402
-
1403
- </style>
1404
- <style lang="less" scoped>
1405
- .message-operate {
1406
- display: flex;
1407
- align-items: center;
1408
- height: 40px;
1409
- line-height: 18px;
1410
- background: #fff;
1411
- overflow-x: auto;
1412
- overflow-y: hidden;
1413
- border-top: 1px solid #e6e6e6;
1414
- &::-webkit-scrollbar {
1415
- height: 5px;
1416
- }
1417
- > img {
1418
- // > .operate-icon {
1419
- margin-left: 10px;
1420
- }
1421
- /deep/ .ant-tabs .ant-tabs-top-content > .ant-tabs-tabpane {
1422
- height: 300px !important;
1423
- overflow: auto !important;
1424
- }
1425
- /deep/ .ant-upload-list {
1426
- display: none;
1427
- }
1428
- /deep/ .ant-upload {
1429
- height: 32px;
1430
- vertical-align: middle;
1431
- display: inline-block;
1432
- }
1433
- .no-click {
1434
- pointer-events: none;
1435
- opacity: 0.4;
1436
- }
1437
- .svg-icon {
1438
- color: rgba(0, 0, 0, 0.6);
1439
- }
1440
- }
1441
- .emoji-wrapper {
1442
- width: 500px;
1443
- height: 300px;
1444
- overflow: auto;
1445
- display: grid;
1446
- grid-template-columns: repeat(10, 1fr);
1447
- .emoji {
1448
- display: inline-block;
1449
- font-size: 20px;
1450
- margin: 10px;
1451
- text-align: center;
1452
- }
1453
- &::-webkit-scrollbar {
1454
- width: 5px;
1455
- }
1456
- }
1457
- .message {
1458
- position: relative;
1459
- height: 150px;
1460
- .message-send {
1461
- position: absolute;
1462
- bottom: 8px;
1463
- right: 14px;
1464
- height: 40px;
1465
- border-radius: 20px;
1466
- width: 86px;
1467
- font-weight: 600;
1468
- font-size: 18px;
1469
- color: #fff;
1470
- background-color: @primary-color;
1471
- border-color: @primary-color;
1472
- &:hover {
1473
- background-color: rgba(@primary-color, 0.7);
1474
- border-color: rgba(@primary-color, 0.7);
1475
- }
1476
- &[disabled]:hover,
1477
- &[disabled] {
1478
- background-color: #f5f5f5;
1479
- border-color: #d9d9d9;
1480
- color: #bebebe;
1481
- }
1482
- }
1483
- .prompt {
1484
- position: absolute;
1485
- bottom: 16px;
1486
- right: 120px;
1487
- color: #a6a6a6;
1488
- }
1489
- }
1490
- .infinite-container {
1491
- overflow-y: auto;
1492
- }
1493
- .message-wrapper {
1494
- position: relative;
1495
- .loading-icon {
1496
- position: absolute;
1497
- top: 0;
1498
- left: 50%;
1499
- }
1500
- }
1501
- .content-edit {
1502
- width: 100%;
1503
- height: 100%;
1504
- padding: 0 16px 56px;
1505
- word-break: break-word;
1506
- &::-webkit-scrollbar {
1507
- width: 5px;
1508
- }
1509
- cursor: text;
1510
- > div {
1511
- height: 100%;
1512
- outline: none;
1513
- overflow-y: auto;
1514
- overflow-x: hidden;
1515
- }
1516
- }
1517
- .overlay {
1518
- position: absolute;
1519
- width: 100%;
1520
- height: 100%;
1521
- top: 0;
1522
- cursor: not-allowed;
1523
- }
1524
- .queue-page {
1525
- display: flex;
1526
- flex-direction: column;
1527
- align-items: center;
1528
- justify-content: center;
1529
- position: absolute;
1530
- width: 100%;
1531
- height: 100%;
1532
- top: 0;
1533
- background: #fff;
1534
- z-index: 500;
1535
- border-top: 1px solid #e6e6e6;
1536
- .queue-tips {
1537
- font-size: 16px;
1538
- color: #272727;
1539
- line-height: 22px;
1540
- margin-bottom: 16px;
1541
- }
1542
- .queue-btns {
1543
- display: flex;
1544
- justify-content: center;
1545
- }
1546
- .toSession {
1547
- width: 80px;
1548
- height: 80px;
1549
- line-height: 80px;
1550
- text-align: center;
1551
- background: linear-gradient(180deg, #ffffff 0%, #f5f5f5 100%);
1552
- box-shadow: 0px 4px 9px 0px rgba(186, 186, 186, 0.79);
1553
- border-radius: 50%;
1554
- color: #4b4b4b;
1555
- font-size: 20px;
1556
- font-weight: 500;
1557
- font-family: PingFangSC-Medium, PingFang SC;
1558
- transition: transform 0.3s ease;
1559
- &.primary {
1560
- background: linear-gradient(180deg, #2d7aff 0%, #2c5df4 100%);
1561
- box-shadow: 0px 5px 8px 0px rgba(44, 101, 247, 0.39);
1562
- color: #fff;
1563
- }
1564
- &.danger {
1565
- background: linear-gradient(180deg, #ff4b4b 0%, #c80606 100%);
1566
- box-shadow: 0px 5px 8px 0px rgba(217, 26, 26, 0.35);
1567
- color: #fff;
1568
- }
1569
- &:hover {
1570
- transform: scale(1.2);
1571
- }
1572
- & + .toSession {
1573
- margin-left: 36px;
1574
- }
1575
- }
1576
- }
1577
- .toolbar {
1578
- // margin-left: 10px;
1579
- margin: 0 5px;
1580
- font-size: 20px;
1581
- outline: none;
1582
- padding: 6px;
1583
- box-sizing: content-box;
1584
- &:hover {
1585
- background-color: #ebebeb;
1586
- border-radius: 6px;
1587
- }
1588
- }
1589
- .closed-toolbar {
1590
- z-index: 100;
1591
- }
1592
- /deep/ p {
1593
- margin-bottom: 0;
1594
- }
1595
- .click-voice {
1596
- color: #2d7aff;
1597
- }
1598
- </style>
1
+ <template>
2
+ <a-layout-footer style="position: relative;cursor:pointer" v-show="!queueItem || !hideQueueBtns">
3
+ <div class="message-operate">
4
+ <a-upload
5
+ v-if="isShow('PICTURE')"
6
+ class="operate-icon"
7
+ v-decorator="[
8
+ 'upload',
9
+ {
10
+ valuePropName: 'fileList'
11
+ }
12
+ ]"
13
+ :beforeUpload="beforeUpload"
14
+ >
15
+ <a-tooltip placement="top">
16
+ <template slot="title">{{ i18nText('1.9.181') }}</template>
17
+ <svg-icon icon-class="liaotiantupian1" class="toolbar"></svg-icon>
18
+ </a-tooltip>
19
+ </a-upload>
20
+ <a-popover :title="i18nText('1.9.354')" trigger="click" v-model="visible">
21
+ <a slot="content">
22
+ <div class="emoji-wrapper">
23
+ <p v-for="(item, index) in emojiList" :key="index" @click="() => handleEmojiClick(item)" :class="['twa', 'emoji', item]"></p>
24
+ </div>
25
+ </a>
26
+
27
+ <a-tooltip placement="top">
28
+ <template slot="title">{{ i18nText('1.9.354') }}</template>
29
+ <svg-icon icon-class="liaotianbiaoqing1" @click="showEmoji" class="toolbar" v-if="isShow('SMILINEFACE')"></svg-icon>
30
+ </a-tooltip>
31
+ </a-popover>
32
+ <template v-for="(item, index) in toolbar">
33
+ <a-tooltip placement="top" :key="index">
34
+ <template slot="title">{{ item.name }}</template>
35
+ <svg-icon :icon-class="item.icon" class="toolbar" :class="{ 'closed-toolbar': closedSession }" @click="handleToolBarClick(item)"></svg-icon>
36
+ </a-tooltip>
37
+ </template>
38
+ <!-- 快捷回复 -->
39
+ <a-tooltip placement="top" :title="'快捷回复'">
40
+ <a-popover trigger="click" overlayClassName="quick-reply" placement="topLeft" v-model="quickShow">
41
+ <template slot="content">
42
+ <quick-reply :quickShow.sync="quickShow" :quickData="quickData"></quick-reply>
43
+ </template>
44
+ <svg-icon icon-class="liaotiankuaijiehuifu1" class="toolbar" @click="openQuickData" v-if="isShow('FASTREPLY') && isServer"></svg-icon>
45
+ </a-popover>
46
+ </a-tooltip>
47
+ <a-tooltip :title="'语音通话'">
48
+ <svg-icon icon-class="liaotianyuyindianhua" @click="handleVideoClick('voice')" class="toolbar" v-if="isShow('VOICE_CALL')"></svg-icon>
49
+ <!-- <img
50
+ width="20"
51
+ src="../img/multi-voice.png"
52
+ @click="handleVideoClick('voice')"
53
+ v-if="isShow('VOICE_CALL') && isServer"
54
+ /> -->
55
+ </a-tooltip>
56
+ <a-tooltip :title="'发送语音'">
57
+ <svg-icon icon-class="liaotianyuyin1" @click="handleVoice(true)" class="toolbar" :class="{ 'click-voice': showVoice }" v-if="isShow('VOICE')"></svg-icon>
58
+ <!-- <img
59
+ width="20"
60
+ src="../img/multi-video.png"
61
+ @click="handleVideoClick('video')"
62
+ v-if="isShow('VIDEO') && isServer"
63
+ /> -->
64
+ </a-tooltip>
65
+ <a-tooltip :title="'视频通话'">
66
+ <svg-icon icon-class="liaotianshipin1" @click="handleVideoClick('video')" class="toolbar" v-if="isShow('VIDEO')"></svg-icon>
67
+ <!-- <img
68
+ width="20"
69
+ src="../img/multi-video.png"
70
+ @click="handleVideoClick('video')"
71
+ v-if="isShow('VIDEO') && isServer"
72
+ /> -->
73
+ </a-tooltip>
74
+ </div>
75
+ <div class="message" style="text-align: left">
76
+ <div class="content-edit">
77
+ <div
78
+ ref="contentEdit"
79
+ @click="saveRange"
80
+ @keyup.exact="saveRange"
81
+ @change="changeData"
82
+ @input="handleInput"
83
+ @compositionstart="() => (this.ping = true)"
84
+ @compositionend="() => (this.ping = false)"
85
+ @keydown="handleKeyDown"
86
+ @paste="handlePaste"
87
+ contenteditable="true"
88
+ ></div>
89
+ </div>
90
+ <span class="prompt">Enter {{ i18nText('1.2.1.11.6') }}, Ctrl + Enter {{ i18nText('1.9.356') }}</span>
91
+ <a-button :disabled="sendDisabled" class="message-send" @click="handleMsgSend">{{ i18nText('1.2.1.11.6') }}</a-button>
92
+ </div>
93
+ <div id="canvas-wrapper" style="display: none">
94
+ <canvas id="canvas"></canvas>
95
+ </div>
96
+ <template v-if="showOverlay">
97
+ <div class="overlay" v-if="(!onChating || sessionEnd) && !queueItem"></div>
98
+ </template>
99
+ <div class="queue-page" v-if="queueItem">
100
+ <div v-if="accessDesc" class="queue-tips">{{ accessDesc }}</div>
101
+ <div class="queue-btns">
102
+ <div v-if="enableTransfer" class="toSession" @click="handleOpenTransfer">
103
+ {{ i18nText('1.10.201') }}
104
+ </div>
105
+ <div class="toSession primary" @click="handleAccess">
106
+ {{ accessText || i18nText('1.9.352') }}
107
+ </div>
108
+ <div v-if="enableRefuse" class="toSession danger" @click="openRefuseReason">
109
+ {{ i18nText('1.9.353') }}
110
+ </div>
111
+ </div>
112
+ </div>
113
+ <!-- 操作栏弹窗 -->
114
+ <a-modal
115
+ :wrapClassName="isFullScreen ? 'chat-footer-modal-big' : 'chat-footer-modal-small'"
116
+ :title="modalData.name"
117
+ :visible="modalShow"
118
+ :maskClosable="false"
119
+ :mask="true"
120
+ :width="isFullScreen ? '100%' : '80%'"
121
+ destroyOnClose
122
+ :okText="i18nText('1.2.1.11.6')"
123
+ :footer="null"
124
+ @cancel="
125
+ () => {
126
+ this.modalShow = false;
127
+ }
128
+ "
129
+ @ok="handleOk"
130
+ >
131
+ <iframe id="toolbarIframe" v-if="modalData.targetType === 'LINK_ADDRESS'" width="100%" height="100%" frameborder="0" :src="modalData.address" allow="camera;midi"></iframe>
132
+ </a-modal>
133
+ <!-- 接入拦截 -->
134
+ <a-modal
135
+ :title="accessInterceptTitle"
136
+ :visible="accessIntercept"
137
+ :maskClosable="false"
138
+ :mask="true"
139
+ :footer="null"
140
+ destroyOnClose
141
+ :width="600"
142
+ :bodyStyle="{ height: '280px' }"
143
+ @cancel="
144
+ () => {
145
+ this.accessIntercept = false;
146
+ }
147
+ "
148
+ >
149
+ <iframe width="100%" height="100%" frameborder="0" :src="accessInterceptUrl"></iframe>
150
+ </a-modal>
151
+ <!-- 语音消息 -->
152
+ <voice v-if="showVoice" :showVoice="showVoice" @closeVoice="handleVoice"></voice>
153
+ <ModalUserTransfer v-if="enableTransfer" :title="i18nText('1.10.201')" :visible.sync="transferVisible" :assemblyId="assemblyId" @ok="handleTransfer" />
154
+ <ModalRefuseReason v-if="refuseVisible" :visible.sync="refuseVisible" :reasonList="refuseReasonList" @ok="handleRefuse" />
155
+ </a-layout-footer>
156
+ </template>
157
+
158
+ <script>
159
+ import { Layout, Tooltip, Upload, Modal, Popover, Button } from 'ant-design-vue';
160
+ import SvgIcon from '@/component/svg/index.vue';
161
+ import ImageCompress from '../utils/compressImage';
162
+ import { parseParams } from '../utils/panelsetting';
163
+
164
+ import { mapState, mapGetters, mapMutations, mapActions } from '../store/helper';
165
+ import fetch, { qs } from '@/utils/chatFetch';
166
+ import QuickReply from './quickReply';
167
+ import voice from './voice';
168
+ import ModalUserTransfer from '../components/modal-user-transfer';
169
+ import ModalRefuseReason from '../components/modal-refuse-reason';
170
+ import vexutils from '@/utils/vexutils';
171
+ export default {
172
+ inject: ['store', 'i18nText', 'dispatchEvent', 'registerEvent', 'unregisterEvent'],
173
+ components: {
174
+ AModal: Modal,
175
+ [Layout.Footer.name]: Layout.Footer,
176
+ [Tooltip.name]: Tooltip,
177
+ [Upload.name]: Upload,
178
+ [Button.name]: Button,
179
+ [Popover.name]: Popover,
180
+ SvgIcon,
181
+ ModalRefuseReason,
182
+ ModalUserTransfer,
183
+ QuickReply,
184
+ voice
185
+ },
186
+ props: {
187
+ // 隐藏排队状态底部信息
188
+ hideQueueBtns: {
189
+ type: Boolean,
190
+ default: false
191
+ },
192
+ validTalkingEnv: {
193
+ type: Function
194
+ },
195
+ curChatType: {
196
+ type: String
197
+ },
198
+ clientFinish: {
199
+ type: Boolean,
200
+ default: true
201
+ },
202
+ addvisible: {
203
+ type: Boolean,
204
+ default: false
205
+ },
206
+ isShowVoice: {
207
+ type: Boolean,
208
+ default: false
209
+ }
210
+ },
211
+ data() {
212
+ return {
213
+ // message: "",
214
+ visible: false,
215
+ modalShow: false,
216
+ modalData: {},
217
+ ping: false,
218
+ templateId: null, // 卡片模板
219
+ isFullScreen: false,
220
+ emojiList: require('../utils/emoji.json'),
221
+ quickData: [],
222
+ quickShow: false,
223
+ screenshot: false,
224
+ screenshotFile: null,
225
+ timer: null,
226
+ accessIntercept: false,
227
+ curToolbarItem: {},
228
+ showVoice: false,
229
+ transferVisible: false,
230
+ refuseVisible: false,
231
+
232
+ apiResult: {}, // 前置条件的返回值
233
+ };
234
+ },
235
+ computed: {
236
+ ...mapGetters([
237
+ 'userInfo',
238
+ 'assemblyId',
239
+ 'sessionId',
240
+ 'serviceId',
241
+ 'onChating',
242
+ 'roomId',
243
+ 'isServer',
244
+ 'assemblySetting',
245
+ 'chatType',
246
+ 'sessionEnd',
247
+ 'closedSession',
248
+ 'clientParams',
249
+ 'footerMessage',
250
+ 'queueItem',
251
+ 'sessionType',
252
+ 'groupMembers',
253
+ 'curScrollItem'
254
+ ]),
255
+ ...mapState({
256
+ classify: state => state.lastClassify || state.classify
257
+ }),
258
+ message() {
259
+ return this.footerMessage;
260
+ },
261
+ sendDisabled() {
262
+ return this.message.length === 0;
263
+ },
264
+ orgId() {
265
+ return this.userInfo?.sysParams?.orgId;
266
+ },
267
+ userId() {
268
+ return this.userInfo?.sysParams?.userId;
269
+ },
270
+ toolbar() {
271
+ if (!this.isServer) return [];
272
+ let toolbar = this.assemblySetting.toolbar || [];
273
+ toolbar = toolbar.filter(item => {
274
+ const { source, isChecked, isCloseChecked, isClosedAble } = item;
275
+ if (source === 'PC' && (
276
+ (!this.closedSession && isChecked === 'Y') ||
277
+ (this.closedSession && isCloseChecked === 'Y' && isChecked === 'Y' && isClosedAble === 'Y')
278
+ )
279
+ ){
280
+ const showCondition = item.showCondition || [];
281
+ const i = showCondition.findIndex(({ p_name, p_value }) => {
282
+ const params = this.clientParams;
283
+ const value = params[p_name];
284
+ if (p_value.startsWith('form.')) {
285
+ return value !== p_name[p_value.slice(5)];
286
+ }
287
+ return value !== p_value;
288
+ });
289
+ return i < 0;
290
+ }
291
+ return false;
292
+ });
293
+ return toolbar;
294
+ },
295
+ isListClassify() {
296
+ return this.assemblySetting.isListClassify === 'Y';
297
+ },
298
+ showOverlay() {
299
+ return this.curChatType != 'robot' || this.clientFinish;
300
+ },
301
+ accessText() {
302
+ let obj = this.assemblySetting.serverSetting.serverInfo.filter(item => item.key === 'ongoingButton')[0];
303
+ return obj?.value;
304
+ },
305
+ accessDesc() {
306
+ let obj = this.assemblySetting.serverSetting.serverInfo.filter(item => item.key === 'ongoingDescription')[0];
307
+ return obj?.value;
308
+ },
309
+ accessInterceptObj() {
310
+ let obj = this.assemblySetting.accessInterceptSetting?.filter(item => item.source === 'PC');
311
+ if (obj && obj[0]) {
312
+ return obj[0];
313
+ }
314
+ return undefined;
315
+ },
316
+ // 拦截url
317
+ accessInterceptUrl() {
318
+ const { accessInterceptObj: obj, apiResult } = this;
319
+ if (obj) {
320
+ return this.getLinkAddress(obj,{result:apiResult});
321
+ }
322
+ return '';
323
+ },
324
+ // 拦截title
325
+ accessInterceptTitle() {
326
+ const { accessInterceptObj: obj } = this;
327
+ if (obj) {
328
+ let res = obj.titleI18n ? this.i18nText(obj.titleI18n) : obj.title;
329
+ return res || '接入拦截';
330
+ }
331
+ return '接入拦截';
332
+ },
333
+ // 是否开启 排队转接
334
+ enableTransfer() {
335
+ if (this.assemblySetting.type === 'single') {
336
+ let setting = null;
337
+ if (this.isListClassify) {
338
+ const classify = this.classify;
339
+ const { listClassify = [] } = this.assemblySetting;
340
+ const target = listClassify.find(item => item.classify === classify);
341
+ setting = target ? target.serverFunctionSetting : null;
342
+ } else {
343
+ setting = this.assemblySetting?.serverSetting?.functionSetting;
344
+ }
345
+ if (setting) {
346
+ const v = setting.find(item => item.function === 'TRANSFER_BEFORE');
347
+ return v ? v.isChecked == 'Y' : false;
348
+ }
349
+ }
350
+ return false;
351
+ },
352
+ enableRefuse() {
353
+ let setting = null;
354
+ if (this.isListClassify) {
355
+ const classify = this.classify;
356
+ const { listClassify = [] } = this.assemblySetting;
357
+ const target = listClassify.find(item => item.classify === classify);
358
+ setting = target ? target.serverFunctionSetting : null;
359
+ } else {
360
+ setting = this.assemblySetting?.serverSetting?.functionSetting;
361
+ }
362
+ if (setting) {
363
+ const v = setting.find(item => item.function === 'ACCESS_REFUSE');
364
+ return v ? v.isChecked == 'Y' : false;
365
+ }
366
+ return false;
367
+ },
368
+ refuseReasonList() {
369
+ const list = this.assemblySetting?.refuseReasonSetting?.list || [];
370
+ return list.filter(v => !!v.text);
371
+ }
372
+ },
373
+
374
+ mounted() {
375
+ this.bindEvents();
376
+ },
377
+ methods: {
378
+ ...mapMutations([
379
+ 'setLastClassify',
380
+ 'setSessionTimer',
381
+ 'setScrollQueueId',
382
+ 'setCurScrollItem',
383
+ 'setIsAppendMsg',
384
+ 'setMessage',
385
+ 'setShowVideo',
386
+ 'setCaller',
387
+ 'setMedicalOrPrescription',
388
+ 'setModalVisible',
389
+ 'setFooterMessage',
390
+ 'setQueueItem',
391
+ 'setSessionId',
392
+ 'setCurrentTab',
393
+ 'setClientId',
394
+ 'setOnChating',
395
+ 'setClientParams',
396
+ 'setSessionHistoryList',
397
+ 'setSessionEnd',
398
+ 'setQuestionId',
399
+ 'setDictionaryKey',
400
+ 'setMultiVideoShow',
401
+ 'setVideoStatus',
402
+ 'setVideoMode',
403
+ 'setAddMemberType',
404
+ 'setShowAudio',
405
+ 'setVideoMembers',
406
+ 'setLastCurrentTab',
407
+ 'setIsRecorderVoice',
408
+ 'setVideoData',
409
+ 'setSessionType'
410
+ ]),
411
+ ...mapMutations({
412
+ clearMsgList: 'setMsgList'
413
+ }),
414
+ ...mapActions(['sendMessage', 'sendRobotMessage', 'handleBotChat', 'setMsgList', 'setChatTimer']),
415
+ deleteStyle(value) {
416
+ // 去掉样式
417
+ value = value.replace(/style\s*?=\s*?(['"])[\s\S]*?\1/g, '');
418
+ value = value.replace(/data\s*?=\s*?(['"])[\s\S]*?\1/g, '');
419
+ value = value.replace(/class\s*?=\s*?(['"])[\s\S]*?\1/g, match => {
420
+ if (match.includes('twa')) {
421
+ return match;
422
+ } else {
423
+ return '';
424
+ }
425
+ });
426
+ value = value.replace(/face\s*?=\s*?(['"])[\s\S]*?\1/g, '');
427
+ value = value.replace(/<a/g, '<span');
428
+ value = value.replace(/a>/g, 'span>');
429
+ value = value.replace(/<i/g, '<span');
430
+ value = value.replace(/i>/g, 'span>');
431
+ value = value.replace(/<u/g, '<span');
432
+ value = value.replace(/u>/g, 'span>');
433
+ value = value.replace(/<strong/g, '<span');
434
+ value = value.replace(/strong>/g, 'span>');
435
+ value = value.replace(/<h\d/g, '<span');
436
+ value = value.replace(/h\d>/g, 'span>');
437
+ value = value.replace(/<b>/g, '<span>');
438
+ value = value.replace(/b>/g, 'span>');
439
+ value = value.replace(/<font/g, '<span');
440
+ value = value.replace(/font>/g, 'span>');
441
+ return value;
442
+ },
443
+ // 更新 message值 (未用v-html,修改后需要手动设置聊天框html内容)
444
+ updateMessage(value) {
445
+ this._editValue = value;
446
+ this.setFooterMessage(`${value}`);
447
+ const editRef = this.$refs.contentEdit;
448
+ if (!editRef) return;
449
+ editRef.innerHTML = value;
450
+ },
451
+ // 根据 聊天框 html节点内容 设置 message值 (未用v-html,html内容更改需主动设置message值)
452
+ setMessageByHTML() {
453
+ const editRef = this.$refs.contentEdit;
454
+ if (!editRef) return;
455
+ const message = editRef.innerHTML;
456
+ this._editValue = message;
457
+ this.setFooterMessage(`${message}`);
458
+ },
459
+
460
+ showEmoji() {
461
+ // this.visible = true;
462
+ },
463
+
464
+ isShow(type) {
465
+ if (type === 'SMILINEFACE' && this.curChatType === 'robot') return true;
466
+ if (this.isListClassify) {
467
+ let label;
468
+ if (this.isServer) {
469
+ label = 'serverFunctionSetting';
470
+ } else {
471
+ label = 'clientFunctionSetting';
472
+ }
473
+ let classifyItem = this.assemblySetting.listClassify.filter(item => item.classify === this.classify);
474
+ if (!classifyItem || !classifyItem.length) return;
475
+ let funs = classifyItem[0][label] || [];
476
+ return funs.find(item => item.function === type)?.isChecked === 'Y';
477
+ }
478
+ let label;
479
+ if (this.isServer) {
480
+ label = 'serverSetting';
481
+ } else {
482
+ label = 'clientSetting';
483
+ }
484
+ let funs = this.assemblySetting[label]?.functionSetting || [];
485
+ return funs.find(item => item.function === type)?.isChecked === 'Y';
486
+ },
487
+
488
+ handleEmojiClick(item) {
489
+ let emojiHtml = `<p contenteditable="false" class="twa emoji ${item}"></p>&nbsp`;
490
+ this.insertToEdit(emojiHtml);
491
+ },
492
+ /* edit-content光标定位 */
493
+ resetSelection() {
494
+ let edit = this.$refs.contentEdit;
495
+ let sel, range;
496
+ if (window.getSelection && document.createRange) {
497
+ range = document.createRange();
498
+ range.selectNodeContents(edit);
499
+ range.collapse(true);
500
+ range.setEnd(edit, edit.childNodes.length);
501
+ range.setStart(edit, edit.childNodes.length);
502
+ sel = window.getSelection();
503
+ sel.removeAllRanges();
504
+ sel.addRange(range);
505
+ }
506
+ },
507
+
508
+ insertToEdit(value, isEditing) {
509
+ const { _editSel, _editRange } = this;
510
+ let sel, range;
511
+ if (window.getSelection) {
512
+ if (!isEditing) {
513
+ if (_editRange) {
514
+ sel = _editSel;
515
+ range = _editRange;
516
+ } else {
517
+ // 初始状态没有focus聊天框,需要手动focus 获取光标位置
518
+ const editRef = this.$refs.contentEdit;
519
+ if (!editRef) return;
520
+ editRef.focus();
521
+ this.saveRange();
522
+ sel = this._editSel;
523
+ range = this._editRange;
524
+ }
525
+ } else {
526
+ sel = window.getSelection();
527
+ range = sel.getRangeAt(0);
528
+ }
529
+ range.deleteContents();
530
+ let frag;
531
+ if (range.createContextualFragment) {
532
+ frag = range.createContextualFragment(value);
533
+ } else {
534
+ const el = document.createElement('div');
535
+ el.innerHTML = value;
536
+ frag = document.createDocumentFragment();
537
+ let node, lastNode;
538
+ while ((node = el.firstChild)) {
539
+ lastNode = frag.appendChild(node); // 之所以能终止循环,是因为el的child一旦添加到dom后,作为fragment里的的child就没了。那么el.firstChild就会返回null
540
+ }
541
+ }
542
+ const lastNode = frag.lastChild;
543
+ range.insertNode(frag);
544
+ // 把光标挪到插入的元素后面
545
+ range.setStartAfter(lastNode);
546
+ sel.removeAllRanges();
547
+ sel.addRange(range);
548
+ } else {
549
+ range = isEditing || !_editRange ? document.selection.createRange() : _editRange;
550
+ range.pasteHTML(value);
551
+ range.select();
552
+ }
553
+ this.setMessageByHTML();
554
+ },
555
+ saveRange() {
556
+ if (window.getSelection) {
557
+ this._editSel = window.getSelection();
558
+ this._editRange = this._editSel.getRangeAt(0);
559
+ } else {
560
+ this._editRange = document.selection.createRange();
561
+ }
562
+ },
563
+ handleInput(e) {
564
+ if (e.inputType === 'deleteContentBackward') {
565
+ this.screenshot = false;
566
+ this.screenshotFile = null;
567
+ }
568
+ setTimeout(() => {
569
+ if (this.ping || this.pasteTimer) return;
570
+ this.setMessageByHTML();
571
+ }, 0);
572
+ },
573
+ changeData(e) {
574
+ let message = e.srcElement.innerHTML;
575
+ if (e.inputType === 'deleteContentBackward') {
576
+ let div = document.createElement('div');
577
+ div.innerHTML = e.srcElement.innerHTML;
578
+ if (div.lastChild?.className?.startsWith('twa emoji')) {
579
+ div.removeChild(div.lastChild);
580
+ message = div.innerHTML;
581
+ }
582
+ this.screenshot = false;
583
+ this.screenshotFile = null;
584
+ }
585
+
586
+ setTimeout(() => {
587
+ if (this.ping) return;
588
+ this.setFooterMessage(`${message}`);
589
+ this.$nextTick().then(() => {
590
+ this.resetSelection();
591
+ this.ping = false;
592
+ });
593
+ }, 0);
594
+ },
595
+
596
+ beforeUpload(data) {
597
+ let { type } = data;
598
+ if (!type.startsWith('image/')) {
599
+ this.$message.warning(`${this.i18nText('1.9.357')}!`);
600
+ return;
601
+ }
602
+ let that = this;
603
+ this.$confirm({
604
+ title: this.i18nText('1.9.331'),
605
+ content: `${this.i18nText('1.9.358')}?`,
606
+ okText: '确定',
607
+ cancelText: '取消',
608
+ onOk() {
609
+ return that.uploadImg(data);
610
+ },
611
+ onCancel() {
612
+ return false;
613
+ }
614
+ });
615
+ },
616
+
617
+ /* 压缩上传 */
618
+ uploadImg(file) {
619
+ const hide = this.$message.loading(`${this.i18nText('1.9.199')}...`, 0);
620
+ new ImageCompress(file, 'canvas', fetch, {
621
+ url: '/hospitalUpload/picture'
622
+ })
623
+ .execute()
624
+ .then(({ data }) => {
625
+ if (data.result === 'SUCCESS') {
626
+ this.doMsgSend(1, data.map.url);
627
+ hide();
628
+ } else {
629
+ hide();
630
+ this.$message.info(data.msg || `${this.i18nText('1.9.174')}!`);
631
+ }
632
+ })
633
+ .catch(() => {
634
+ hide();
635
+ this.$message.info(`${this.i18nText('1.9.174')}!`);
636
+ })
637
+ .finally(() => {
638
+ this.screenshotFile = null;
639
+ this.screenshot = false;
640
+ this.updateMessage('');
641
+ });
642
+ },
643
+
644
+ /* 发送消息 */
645
+ doMsgSend(type, content) {
646
+ let body = {
647
+ content: type === 0 ? `<div>${content}</div>` : content,
648
+ type: type
649
+ };
650
+ if (this.curChatType === 'robot') {
651
+ // this.setQuestionId("");
652
+ // this.setDictionaryKey("");
653
+ this.sendRobotMessage(content);
654
+ } else {
655
+ this.sendMessage(body);
656
+ }
657
+ },
658
+ handleMsgSend() {
659
+ // 截图上传
660
+ if (this.screenshot) {
661
+ this.uploadImg(this.screenshotFile);
662
+ return;
663
+ }
664
+ if (this.chatType === 'bot') {
665
+ this.handleBotChat(this.message).then(isHuman => {
666
+ if (isHuman) {
667
+ this.$parent.$parent.$parent.initWebSocket();
668
+ }
669
+ this.updateMessage('');
670
+ });
671
+ return;
672
+ }
673
+
674
+ const editRef = this.$refs.contentEdit;
675
+ if (editRef) {
676
+ const message = editRef.innerText;
677
+ if (message.length > 2000) {
678
+ this.$message.warning('请控制在2000字以内!');
679
+ return;
680
+ }
681
+ }
682
+ // let reg = /(.*?)(<div><br><\/div>)*?$/;
683
+ let reg = /(.*?)(<div>(<span>)?<br>(<\/span>)?<\/div>)*?$/;
684
+ let message = this.message.match(reg);
685
+ if (message[2]) {
686
+ this.updateMessage(message[1]);
687
+ }
688
+ this.updateMessage(this.message.replace(/style/g, 'data'));
689
+ if (this.message.trim()) {
690
+ this.doMsgSend(0, this.message);
691
+ } else {
692
+ return this.$message.warning(this.i18nText('1.9.359'));
693
+ }
694
+ this.updateMessage('');
695
+ },
696
+ handleKeyDown(e) {
697
+ // 判断enter 发送还是换行
698
+ if (e.keyCode === 13) {
699
+ e.preventDefault();
700
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
701
+ this.handleWrap();
702
+ } else {
703
+ this.handleMsgSend();
704
+ }
705
+ }
706
+ },
707
+ handleWrap() {
708
+ let ctn = `<br>`;
709
+ // 非ie环境 元素后面没有node的时候换行需要两个<br>
710
+ if (!/msie/.test(navigator.userAgent) && window.getSelection) {
711
+ const focusNode = window.getSelection().focusNode;
712
+ let nextSibling = focusNode.nextSibling;
713
+ if (focusNode === this.$refs.contentEdit) {
714
+ nextSibling = focusNode.childNodes[0];
715
+ }
716
+ do {
717
+ if (!nextSibling || nextSibling.nodeValue || nextSibling.tagName == 'BR') break;
718
+ } while ((nextSibling = nextSibling.nextSibling));
719
+ if (!nextSibling) {
720
+ ctn += ctn;
721
+ }
722
+ }
723
+ this.insertToEdit(ctn);
724
+ this.$nextTick().then(() => {
725
+ this.ping = false;
726
+ });
727
+ },
728
+ handleVideoClick(type) {
729
+ // this.curScrollItem.type 2多人 1单人
730
+ if (this.sessionType === 2) {
731
+ this.$emit('update:addvisible', true);
732
+ this.setAddMemberType(2);
733
+ this.setVideoMode(type === 'voice' ? 1 : 2);
734
+ } else {
735
+ const openTalking = sip => {
736
+ this.setShowAudio(true);
737
+ this.setVideoMode(type === 'voice' ? 1 : 2);
738
+ this.$nextTick(() => {
739
+ this.setVideoMembers([]);
740
+ });
741
+ this.setVideoData({
742
+ callerSipKey: sip?.username,
743
+ callerSipName: sip?.name
744
+ });
745
+ };
746
+ if (this.validTalkingEnv) {
747
+ this.validTalkingEnv().then(res => {
748
+ if (res.status) {
749
+ openTalking(res.sip);
750
+ }
751
+ });
752
+ } else {
753
+ openTalking();
754
+ }
755
+ }
756
+ // this.groupMembers.length > 2
757
+ return;
758
+ },
759
+ handleVoice(val) {
760
+ val && this.setIsRecorderVoice(true);
761
+ this.showVoice = val;
762
+ },
763
+ showPrescription() {
764
+ this.setModalVisible(true);
765
+ this.setMedicalOrPrescription('prescription');
766
+ },
767
+ showMedicalRecord() {
768
+ this.setModalVisible(true);
769
+ this.setMedicalOrPrescription('medical');
770
+ },
771
+ beofreToolBarClick(item) {
772
+ // 操作栏前置条件
773
+ return new Promise((resolve, reject) => {
774
+ const { precondition } = item;
775
+ if (precondition) {
776
+ this.requestPreCondition(precondition)
777
+ .then(res => {
778
+ const result = res.data.result === 'SUCCESS';
779
+ !result && this.$message.error(res.data.resultMsg || this.i18nText('1.9.22'));
780
+ resolve(result);
781
+ })
782
+ .catch(error => {
783
+ this.$message.error(error.resultMsg || this.i18nText('1.9.22'));
784
+ resolve(false);
785
+ });
786
+ } else {
787
+ resolve(true);
788
+ }
789
+ });
790
+ },
791
+ async handleToolBarClick(item) {
792
+ const validate = await this.beofreToolBarClick(item);
793
+ if (!validate) return;
794
+ let { openMode, name, templateId, isFullScreen } = item;
795
+ this.dispatchEvent('click_toolbar', { ...item });
796
+ if (!item.address) return;
797
+ this.curToolbarItem = item;
798
+ this.templateId = templateId;
799
+ this.isFullScreen = isFullScreen === 'Y';
800
+ let address = this.getLinkAddress(item);
801
+ if (openMode === 'WINDOW') {
802
+ window.open(address, name);
803
+ return;
804
+ }
805
+ if (openMode === 'EJECT') {
806
+ this.modalShow = true;
807
+ this.modalData = Object.assign({}, item, { address });
808
+ this.$nextTick().then(() => {
809
+ window.addEventListener('message', this.iframeEvent);
810
+ });
811
+ return;
812
+ }
813
+ },
814
+ iframeEvent(event) {
815
+ const method = event?.data?.method;
816
+ this[method]?.(event);
817
+ },
818
+ // 发送
819
+ handleParentMessageSend({ data, source, origin }) {
820
+ let params = {
821
+ assemblyId: this.assemblyId,
822
+ sessionId: this.sessionId,
823
+ orgId: this.orgId
824
+ };
825
+ params = Object.assign(params, data.data);
826
+ let { actionType } = this.curToolbarItem;
827
+ actionType && Object.assign(params, { actionType });
828
+ return fetch.post('/chat/access/sendToolBarData', qs.stringify(params)).then(({ data }) => {
829
+ if (data.result === 'SUCCESS') {
830
+ source.postMessage({ status: 0, resultMsg: data.resultMsg }, origin);
831
+ } else {
832
+ source.postMessage({ status: 1, resultMsg: data.resultMsg }, origin);
833
+ }
834
+ });
835
+ },
836
+ // 关闭
837
+ handleClose() {
838
+ this.modalShow = false;
839
+ window.removeEventListener('message', this.iframeEvent);
840
+ },
841
+ getLinkAddress(item,paramsData= {}) {
842
+ let t = {talbe:{},form:{},sys:{}};
843
+ Object.assign(t,paramsData ||{})
844
+ let { address = '', params = [] } = item;
845
+ let urlParams = [];
846
+ params.forEach(({ p_name, p_value }) => {
847
+ let value;
848
+ if (p_value.startsWith('form.')) {
849
+ value = this.clientParams[p_value.slice(5)];
850
+ } else if( /\${result.(.*?)\}/g.test(p_value)) {
851
+ value = vexutils.handleSysParams(p_value,t)
852
+ } else {
853
+ value = p_value;
854
+ }
855
+ urlParams.push(`${p_name}=${value}`);
856
+ });
857
+ if(/\${result.(.*?)\}/g.test(address)){
858
+ address = vexutils.handleSysParams(address,t)
859
+ }
860
+ let { actionType } = this.curToolbarItem;
861
+ actionType && urlParams.push(`actionType=${actionType}`);
862
+ if (address.includes('?')) {
863
+ address += `&${urlParams.join('&')}`;
864
+ } else {
865
+ address += `?${urlParams.join('&')}`;
866
+ }
867
+ return address;
868
+ },
869
+ handleOk() {
870
+ window.handleParentMessageSend();
871
+ },
872
+ // 剪切板粘贴
873
+ handlePaste(e) {
874
+ const cbd = e.clipboardData;
875
+ const ua = window.navigator.userAgent;
876
+ // 如果是 Safari 直接 return
877
+ if (!(e.clipboardData && e.clipboardData.items)) {
878
+ this.handlePasteText(e);
879
+ return;
880
+ }
881
+ if (
882
+ cbd.items &&
883
+ cbd.items.length === 2 &&
884
+ cbd.items[0].kind === 'string' &&
885
+ cbd.items[1].kind === 'file' &&
886
+ cbd.types &&
887
+ cbd.types.length === 2 &&
888
+ cbd.types[0] === 'text/plain' &&
889
+ cbd.types[1] === 'Files' &&
890
+ ua.match(/Macintosh/i) &&
891
+ Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49
892
+ ) {
893
+ this.handlePasteText(e);
894
+ return;
895
+ }
896
+ const hasFile = this.handleImgPaste(cbd.items);
897
+ if (hasFile) {
898
+ e.stopPropagation();
899
+ e.preventDefault();
900
+ return;
901
+ }
902
+ this.handlePasteText(e);
903
+ },
904
+ handleImgPaste(files) {
905
+ let hasFile = false;
906
+ for (let i = 0; i < files.length; i++) {
907
+ let item = files[i];
908
+ if (item.kind == 'file') {
909
+ hasFile = true;
910
+ // blob 就是从剪切板获得的文件,可以进行上传或其他操作
911
+ const blob = item.getAsFile();
912
+ if (blob.size === 0) {
913
+ return;
914
+ }
915
+ const reader = new FileReader();
916
+ const imgs = new Image();
917
+ imgs.file = blob;
918
+ reader.onload = e => {
919
+ imgs.src = e.target.result;
920
+ imgs.width = '150';
921
+ this.updateMessage(this.htmlToString(imgs));
922
+ this.$nextTick().then(() => {
923
+ this.resetSelection();
924
+ this.screenshot = true;
925
+ this.screenshotFile = blob;
926
+ });
927
+ };
928
+ reader.readAsDataURL(blob);
929
+ }
930
+ }
931
+ return hasFile;
932
+ },
933
+ // 处理文本粘贴
934
+ handlePasteText() {
935
+ if (this.pasteTimer) clearTimeout(this.pasteTimer);
936
+ const ele = this.$refs.contentEdit;
937
+ if (!ele) return;
938
+ let before = ele.innerHTML;
939
+ this.pasteTimer = setTimeout(() => {
940
+ function getPasteHtml(before, after) {
941
+ // 计算粘贴后html不同的位置起点和终点(插入"abc",则得出sPos为0,ePos为2)
942
+ var sPos = -1,
943
+ ePos = -1;
944
+ for (var i = 0, len = after.length; i < len; ++i) {
945
+ if (sPos == -1 && before.substr(i, 1) != after.substr(i, 1)) sPos = i;
946
+ if (ePos == -1 && before.substr(before.length - i - 1, 1) != after.substr(after.length - i - 1, 1)) ePos = i;
947
+
948
+ if ((sPos != -1 && ePos != -1) || len - 1 - ePos <= sPos) break;
949
+ }
950
+ if (sPos == -1 || ePos == -1) return;
951
+ ePos = len - 1 - ePos;
952
+
953
+ if (ePos <= sPos) {
954
+ // 遇到像ab,粘贴ab后变成abab这种了,这是需要从sPos开始往后找,找到和before一样的为止
955
+ i = sPos;
956
+ var beforeSPosNext = before.substr(i + 1, 10); // 取后10个来对比,如果没有10个,则取最多
957
+ while (++i < len) {
958
+ if (beforeSPosNext == after.substr(i, beforeSPosNext.length)) {
959
+ ePos = i;
960
+ break;
961
+ }
962
+ }
963
+ i == len && (ePos = len - 1);
964
+ }
965
+ // 如果有<和>,则需要计算在内
966
+ if (after.substr(sPos - 1, 1) == '<') --sPos;
967
+ if (after.substr(ePos + 1, 1) == '>') ++ePos;
968
+
969
+ // 分段获得文本,前半段 插入段 后半段
970
+ var pastedText = after.substring(sPos, ePos + 1),
971
+ formerText = after.substr(0, sPos),
972
+ latterText = after.substr(sPos + pastedText.length);
973
+
974
+ // 判断formerText和pastedText是否闭合了
975
+ var lastLT = formerText.lastIndexOf('<'),
976
+ lastGT = formerText.lastIndexOf('>');
977
+ if (lastGT < lastLT) {
978
+ // 标签断了xxxxx<
979
+ pastedText = formerText.slice(lastLT) + pastedText;
980
+ formerText = formerText.slice(0, lastLT);
981
+ }
982
+
983
+ lastLT = pastedText.lastIndexOf('<');
984
+ lastGT = pastedText.lastIndexOf('>');
985
+ if (lastGT < lastLT) {
986
+ // 标签断了<xxxxxx
987
+ var _insertGT = latterText.indexOf('>') + 1;
988
+ pastedText += latterText.slice(0, _insertGT);
989
+ latterText = latterText.slice(_insertGT);
990
+ }
991
+ return [formerText, pastedText, latterText];
992
+ }
993
+ var after = ele.innerHTML;
994
+ const data = getPasteHtml(before, after);
995
+ if (data) {
996
+ let [formerText, insertText, latterText] = data;
997
+ insertText = this.deleteStyle(insertText);
998
+ ele.innerHTML = formerText + insertText + "<span class='pasteCaretPosHelper'></span>" + latterText; // 插入pasteCaretPosHelper以帮助定位光标位置
999
+ var pasteCaretPosHelper = ele.querySelector('.pasteCaretPosHelper'),
1000
+ range,
1001
+ selection;
1002
+ if (pasteCaretPosHelper) {
1003
+ if (document.createRange) {
1004
+ // Chrome, IE 9+
1005
+ range = document.createRange();
1006
+ range.setStartAfter(pasteCaretPosHelper);
1007
+ range.collapse(false);
1008
+ selection = window.getSelection();
1009
+ selection.removeAllRanges();
1010
+ selection.addRange(range);
1011
+ } else if (document.selection) {
1012
+ // IE 8 and lower
1013
+ range = document.body.createTextRange();
1014
+ range.moveToElementText(pasteCaretPosHelper);
1015
+ range.collapse(false);
1016
+ range.select();
1017
+ }
1018
+ pasteCaretPosHelper.parentNode.removeChild(pasteCaretPosHelper);
1019
+ }
1020
+ }
1021
+ this.setMessageByHTML();
1022
+ this.saveRange();
1023
+ this.pasteTimer = null;
1024
+ }, 50);
1025
+ },
1026
+ // 拖拽上传图片
1027
+ handleFiles(files) {
1028
+ this.handleImgPaste(files);
1029
+ },
1030
+ htmlToString(dom) {
1031
+ let div = document.createElement('div');
1032
+ div.appendChild(dom);
1033
+ return div.innerHTML;
1034
+ },
1035
+ /**
1036
+ * 接入拦截
1037
+ */
1038
+ handleAccessIntercept(result) {
1039
+ let { data } = result;
1040
+ let { status, data: param } = data;
1041
+ if (status === 1) {
1042
+ this.accessChat(param).finally(() => {
1043
+ this.accessIntercept = false;
1044
+ });
1045
+ } else {
1046
+ this.accessIntercept = false;
1047
+ }
1048
+ },
1049
+ /* 客服点击接入按钮进入会话 */
1050
+ async handleAccess() {
1051
+ this.apiResult = {};
1052
+ let accessInterceptValidate = this.handleAccessInterceptValue();
1053
+ let data = null; // 接口参数
1054
+ if (accessInterceptValidate && this.accessInterceptUrl) {
1055
+ const r = await this.beforeAccessIntercept();
1056
+ if (!r) return;
1057
+ if (r.value == 'Y') {
1058
+ this.accessIntercept = true;
1059
+ this.$nextTick().then(() => {
1060
+ window.addEventListener('message', this.handleAccessIntercept);
1061
+ });
1062
+ return;
1063
+ } else {
1064
+ data = r.data; // 获取接入拦截返回参数
1065
+ }
1066
+ }
1067
+ clearTimeout(this.timer);
1068
+ this.timer = setTimeout(() => {
1069
+ this.accessChat(data);
1070
+ }, 300);
1071
+ },
1072
+ // 条件转换请求
1073
+ requestPreCondition(precondition) {
1074
+ const { targetType, targetName, targetId, params } = precondition;
1075
+ const data = parseParams(params, this.clientParams);
1076
+ let url = '';
1077
+ if (targetType === 'API') {
1078
+ data.url = targetName;
1079
+ url = `/api/executeApi`;
1080
+ } else {
1081
+ data.sqlEnterId = targetId;
1082
+ url = `/form/saveSqlEnter`;
1083
+ }
1084
+ return fetch.post(url, qs.stringify(data));
1085
+ },
1086
+ async beforeAccessIntercept() {
1087
+ function parseResult(r) {
1088
+ let value = 'Y';
1089
+ const data = r.split('&').reduce((obj, str) => {
1090
+ const [k, v] = str.split('=');
1091
+ if (k) {
1092
+ if (k == 'value') {
1093
+ value = v || value;
1094
+ } else {
1095
+ obj = obj || {};
1096
+ obj[k] = v || '';
1097
+ }
1098
+ }
1099
+ return obj;
1100
+ }, null);
1101
+ return {
1102
+ value,
1103
+ data
1104
+ };
1105
+ }
1106
+ this.apiResult = {};
1107
+ const accessInterceptSetting = this.assemblySetting.accessInterceptSetting || [];
1108
+ const setting = accessInterceptSetting.find(item => item.source == 'PC' && item.isChecked == 'Y' && item.precondition);
1109
+ if (setting) {
1110
+ const precondition = setting.precondition;
1111
+ try {
1112
+ let res = await this.requestPreCondition(precondition);
1113
+ this.apiResult = res?.data?.map || {};
1114
+ if (res.data.result === 'SUCCESS') {
1115
+ let value = res.data.map?.result;
1116
+ value = value ? JSON.parse(value) : {};
1117
+ return value.value ? parseResult(value.value) : false;
1118
+ } else {
1119
+ this.$message.error(res.data.resultMsg || this.i18nText('1.9.22'));
1120
+ return false;
1121
+ }
1122
+ } catch (error) {
1123
+ this.$message.error(error.resultMsg || this.i18nText('1.9.22'));
1124
+ return false;
1125
+ }
1126
+ }
1127
+ return {
1128
+ value: 'Y',
1129
+ data: null
1130
+ };
1131
+ },
1132
+ handleAccessInterceptValue() {
1133
+ let { listClassify = [], isListClassify, serverSetting = {} } = this.assemblySetting;
1134
+ let list = [];
1135
+ if (isListClassify != 'Y') {
1136
+ let { functionSetting = [] } = serverSetting;
1137
+ list = functionSetting;
1138
+ } else {
1139
+ let curList = listClassify.find(item => item.classify == this.classify);
1140
+ if (!curList) return false;
1141
+ list = curList.serverFunctionSetting || [];
1142
+ }
1143
+ if (!list.length) return true;
1144
+ let curItem = list.find(item => item.function == 'ACCESS_INTERCEPT');
1145
+ if (!curItem) return true;
1146
+ return curItem.isChecked == 'Y';
1147
+ },
1148
+ accessChat(data) {
1149
+ let { bid: userId, username, userType } = this.queueItem;
1150
+ let params = {
1151
+ assemblyId: this.assemblyId,
1152
+ userId,
1153
+ userType,
1154
+ lastSessionId: this.sessionId,
1155
+ relateId: this.clientParams.relateId
1156
+ };
1157
+ if (data) {
1158
+ params.addParams = JSON.stringify(data);
1159
+ }
1160
+ return fetch.post('/chat/service/accessChat', qs.stringify(params)).then(({ data }) => {
1161
+ if (data.result === 'SUCCESS') {
1162
+ let { countdown, clientParams, sessionType, countdownDesc } = data.map;
1163
+ this.setSessionId(data.map.sessionId);
1164
+ this.clearMsgList();
1165
+ this.setMsgList();
1166
+ this.setCurrentTab('session');
1167
+ this.setLastCurrentTab('session');
1168
+ this.setClientId(username);
1169
+ this.setOnChating(true);
1170
+ this.setSessionEnd(false);
1171
+ this.setQueueItem(null);
1172
+ sessionType && this.setSessionType(sessionType);
1173
+ if (countdown >= 0) {
1174
+ this.setChatTimer({ countdown, countdownDesc });
1175
+ }
1176
+ this.setClientParams(clientParams);
1177
+ this.getSessionHistoryList();
1178
+ this.dispatchEvent('user_accessSuccess', {
1179
+ map: data.map,
1180
+ user: { ...this.curScrollItem }
1181
+ });
1182
+ } else {
1183
+ this.$message.warning(data.resultMsg);
1184
+ }
1185
+ });
1186
+ },
1187
+ handleOpenTransfer() {
1188
+ if (this.enableTransfer) {
1189
+ this.transferVisible = true;
1190
+ }
1191
+ },
1192
+ handleTransfer(data) {
1193
+ const receiveId = data.converUserId;
1194
+ if (receiveId) {
1195
+ if (this.userId == receiveId) {
1196
+ this.$message.warning(this.i18nText('1.2.7.14.1'));
1197
+ return;
1198
+ }
1199
+ this.transferVisible = false;
1200
+ fetch({
1201
+ url: '/chat/service/transferQueue',
1202
+ method: 'POST',
1203
+ data: qs.stringify({
1204
+ relateId: this.clientParams.relateId,
1205
+ orgId: this.orgId,
1206
+ assemblyId: this.assemblyId,
1207
+ userId: this.curScrollItem?.bid,
1208
+ userType: this.curScrollItem?.userType || '',
1209
+ receiveId: receiveId,
1210
+ deptId: data.deptId,
1211
+ remark: data.converMark
1212
+ })
1213
+ })
1214
+ .then(({ data }) => {
1215
+ if (data.result === 'SUCCESS') {
1216
+ this.dispatchEvent('user_transferQueueSuccess', {
1217
+ user: { ...this.curScrollItem }
1218
+ });
1219
+ this.$message.success(this.i18nText('1.9.336'));
1220
+ this.clearCurrentSession(true);
1221
+ } else {
1222
+ this.$message.warning(data.resultMsg || this.i18nText('1.10.201') + this.i18nText('1.9.22'));
1223
+ }
1224
+ })
1225
+ .catch(err => {
1226
+ this.$message.warning(err.resultMsg || this.i18nText('1.10.201') + this.i18nText('1.9.22'));
1227
+ });
1228
+ } else {
1229
+ this.$message.warning(this.i18nText('1.9.335'));
1230
+ }
1231
+ },
1232
+ getSessionHistoryList() {
1233
+ this.setSessionHistoryList([]);
1234
+ if (this.assemblySetting.recordScopeSetting === 'CURRENT') return;
1235
+ fetch
1236
+ .get('/chat/service/getPastSessionList', {
1237
+ params: { sessionId: this.sessionId }
1238
+ })
1239
+ .then(({ data }) => {
1240
+ if (data.result === 'SUCCESS') {
1241
+ let list = data.list.map(item => {
1242
+ if (item.id === this.sessionId || this.type === 'session') {
1243
+ item.onChating = true;
1244
+ }
1245
+ return item;
1246
+ });
1247
+ this.setSessionHistoryList(list);
1248
+ }
1249
+ });
1250
+ },
1251
+ openQuickData() {
1252
+ // this.quickShow = true;
1253
+ if (this.quickData.length < 1) {
1254
+ fetch
1255
+ .get('/fastReply/getChatList', {
1256
+ params: {
1257
+ category: this.assemblySetting.chatPublicCategory
1258
+ }
1259
+ })
1260
+ .then(({ data }) => {
1261
+ if (data.result === 'SUCCESS') {
1262
+ this.quickData = data.list;
1263
+ }
1264
+ });
1265
+ }
1266
+ },
1267
+ // 清除当前会话
1268
+ clearCurrentSession(update = false) {
1269
+ this.clearMsgList([]);
1270
+ this.setSessionHistoryList([]);
1271
+ this.setSessionTimer('');
1272
+ this.setClientId('');
1273
+ this.setSessionId('');
1274
+ this.setScrollQueueId('');
1275
+ this.setLastClassify('');
1276
+ this.setCurScrollItem(null);
1277
+ this.setQueueItem(null);
1278
+ this.setClientParams(null);
1279
+ if (update) {
1280
+ // 重新设置 currentTab 触发 scrollList 组件 更新排队列表
1281
+ const currentTab = this.store.state?.currentTab;
1282
+ if (currentTab === 'queue') {
1283
+ this.setCurrentTab('');
1284
+ this.$nextTick(() => {
1285
+ this.setCurrentTab(this.store.state?.currentTab || currentTab);
1286
+ });
1287
+ }
1288
+ }
1289
+ },
1290
+ openRefuseReason() {
1291
+ if (this.enableRefuse) {
1292
+ this.refuseVisible = true;
1293
+ }
1294
+ },
1295
+ handleRefuse(remark) {
1296
+ if (!this.enableRefuse) return;
1297
+ fetch({
1298
+ url: '/chat/service/accessRefuse',
1299
+ method: 'POST',
1300
+ data: qs.stringify({
1301
+ relateId: this.clientParams.relateId,
1302
+ orgId: this.orgId,
1303
+ assemblyId: this.assemblyId,
1304
+ userId: this.curScrollItem?.bid,
1305
+ userType: this.curScrollItem?.userType || '',
1306
+ remark: remark || '其他'
1307
+ })
1308
+ })
1309
+ .then(({ data }) => {
1310
+ if (data.result === 'SUCCESS') {
1311
+ this.dispatchEvent('user_refuseSuccess', {
1312
+ user: { ...this.curScrollItem }
1313
+ });
1314
+ this.$message.success(this.i18nText('1.10.184'));
1315
+ this.clearCurrentSession(true);
1316
+ } else {
1317
+ this.$message.warning(data.resultMsg || this.i18nText('1.9.353') + this.i18nText('1.9.22'));
1318
+ }
1319
+ })
1320
+ .catch(err => {
1321
+ this.$message.warning(err.resultMsg || this.i18nText('1.9.353') + this.i18nText('1.9.22'));
1322
+ });
1323
+ this.refuseVisible = false;
1324
+ },
1325
+ bindEvents() {
1326
+ const handleDrop = e => {
1327
+ e.stopPropagation();
1328
+ e.preventDefault();
1329
+ this.handleFiles(e.dataTransfer.items);
1330
+ };
1331
+ document.addEventListener('drop', handleDrop, false);
1332
+ this.registerEvent('accessCurrentSession', this.handleAccess);
1333
+ this.registerEvent('refuseCurrentSession', this.openRefuseReason);
1334
+ this.registerEvent('transferCurrentQueueSession', this.handleOpenTransfer);
1335
+ this.$on('hook:beforeDestroy', () => {
1336
+ document.removeEventListener('drop', handleDrop, false);
1337
+ window.removeEventListener('message', this.iframeEvent);
1338
+ window.removeEventListener('message', this.handleAccessIntercept);
1339
+ this.unregisterEvent('accessCurrentSession', this.handleAccess);
1340
+ this.unregisterEvent('refuseCurrentSession', this.openRefuseReason);
1341
+ this.unregisterEvent('transferCurrentQueueSession', this.handleOpenTransfer);
1342
+ });
1343
+ }
1344
+ },
1345
+ watch: {
1346
+ accessIntercept: {
1347
+ handler(val) {
1348
+ if (!val) {
1349
+ window.removeEventListener('message', this.handleAccessIntercept);
1350
+ }
1351
+ }
1352
+ },
1353
+ message: {
1354
+ handler(v) {
1355
+ // 使用 _editValue 判断是否组件外修改 message,并更新聊天框
1356
+ if (this._editValue !== v) {
1357
+ this.updateMessage(v);
1358
+ }
1359
+ }
1360
+ }
1361
+ }
1362
+ };
1363
+ </script>
1364
+
1365
+ <style lang="less">
1366
+ .chat-footer-modal-big {
1367
+ padding: 10px;
1368
+ &.ant-modal-wrap {
1369
+ overflow: none;
1370
+ }
1371
+ .ant-modal {
1372
+ width: 100%;
1373
+ height: 100%;
1374
+ top: 0;
1375
+ padding-bottom: 0;
1376
+ }
1377
+ .ant-modal-content {
1378
+ height: 100%;
1379
+ }
1380
+ .ant-modal-body {
1381
+ height: calc(100% - 55px);
1382
+ }
1383
+ }
1384
+ .chat-footer-modal-small {
1385
+ &.ant-modal-wrap {
1386
+ overflow: none;
1387
+ }
1388
+ .ant-modal {
1389
+ width: 80%;
1390
+ height: 80%;
1391
+ padding-bottom: 0;
1392
+ top: 60px;
1393
+ }
1394
+ .ant-modal-content {
1395
+ height: 100%;
1396
+ }
1397
+ .ant-modal-body {
1398
+ height: 100%;
1399
+ padding: 0;
1400
+ }
1401
+ }
1402
+
1403
+ </style>
1404
+ <style lang="less" scoped>
1405
+ .message-operate {
1406
+ display: flex;
1407
+ align-items: center;
1408
+ height: 40px;
1409
+ line-height: 18px;
1410
+ background: #fff;
1411
+ overflow-x: auto;
1412
+ overflow-y: hidden;
1413
+ border-top: 1px solid #e6e6e6;
1414
+ &::-webkit-scrollbar {
1415
+ height: 5px;
1416
+ }
1417
+ > img {
1418
+ // > .operate-icon {
1419
+ margin-left: 10px;
1420
+ }
1421
+ /deep/ .ant-tabs .ant-tabs-top-content > .ant-tabs-tabpane {
1422
+ height: 300px !important;
1423
+ overflow: auto !important;
1424
+ }
1425
+ /deep/ .ant-upload-list {
1426
+ display: none;
1427
+ }
1428
+ /deep/ .ant-upload {
1429
+ height: 32px;
1430
+ vertical-align: middle;
1431
+ display: inline-block;
1432
+ }
1433
+ .no-click {
1434
+ pointer-events: none;
1435
+ opacity: 0.4;
1436
+ }
1437
+ .svg-icon {
1438
+ color: rgba(0, 0, 0, 0.6);
1439
+ }
1440
+ }
1441
+ .emoji-wrapper {
1442
+ width: 500px;
1443
+ height: 300px;
1444
+ overflow: auto;
1445
+ display: grid;
1446
+ grid-template-columns: repeat(10, 1fr);
1447
+ .emoji {
1448
+ display: inline-block;
1449
+ font-size: 20px;
1450
+ margin: 10px;
1451
+ text-align: center;
1452
+ }
1453
+ &::-webkit-scrollbar {
1454
+ width: 5px;
1455
+ }
1456
+ }
1457
+ .message {
1458
+ position: relative;
1459
+ height: 150px;
1460
+ .message-send {
1461
+ position: absolute;
1462
+ bottom: 8px;
1463
+ right: 14px;
1464
+ height: 40px;
1465
+ border-radius: 20px;
1466
+ width: 86px;
1467
+ font-weight: 600;
1468
+ font-size: 18px;
1469
+ color: #fff;
1470
+ background-color: @primary-color;
1471
+ border-color: @primary-color;
1472
+ &:hover {
1473
+ background-color: rgba(@primary-color, 0.7);
1474
+ border-color: rgba(@primary-color, 0.7);
1475
+ }
1476
+ &[disabled]:hover,
1477
+ &[disabled] {
1478
+ background-color: #f5f5f5;
1479
+ border-color: #d9d9d9;
1480
+ color: #bebebe;
1481
+ }
1482
+ }
1483
+ .prompt {
1484
+ position: absolute;
1485
+ bottom: 16px;
1486
+ right: 120px;
1487
+ color: #a6a6a6;
1488
+ }
1489
+ }
1490
+ .infinite-container {
1491
+ overflow-y: auto;
1492
+ }
1493
+ .message-wrapper {
1494
+ position: relative;
1495
+ .loading-icon {
1496
+ position: absolute;
1497
+ top: 0;
1498
+ left: 50%;
1499
+ }
1500
+ }
1501
+ .content-edit {
1502
+ width: 100%;
1503
+ height: 100%;
1504
+ padding: 0 16px 56px;
1505
+ word-break: break-word;
1506
+ &::-webkit-scrollbar {
1507
+ width: 5px;
1508
+ }
1509
+ cursor: text;
1510
+ > div {
1511
+ height: 100%;
1512
+ outline: none;
1513
+ overflow-y: auto;
1514
+ overflow-x: hidden;
1515
+ }
1516
+ }
1517
+ .overlay {
1518
+ position: absolute;
1519
+ width: 100%;
1520
+ height: 100%;
1521
+ top: 0;
1522
+ cursor: not-allowed;
1523
+ }
1524
+ .queue-page {
1525
+ display: flex;
1526
+ flex-direction: column;
1527
+ align-items: center;
1528
+ justify-content: center;
1529
+ position: absolute;
1530
+ width: 100%;
1531
+ height: 100%;
1532
+ top: 0;
1533
+ background: #fff;
1534
+ z-index: 500;
1535
+ border-top: 1px solid #e6e6e6;
1536
+ .queue-tips {
1537
+ font-size: 16px;
1538
+ color: #272727;
1539
+ line-height: 22px;
1540
+ margin-bottom: 16px;
1541
+ }
1542
+ .queue-btns {
1543
+ display: flex;
1544
+ justify-content: center;
1545
+ }
1546
+ .toSession {
1547
+ width: 80px;
1548
+ height: 80px;
1549
+ line-height: 80px;
1550
+ text-align: center;
1551
+ background: linear-gradient(180deg, #ffffff 0%, #f5f5f5 100%);
1552
+ box-shadow: 0px 4px 9px 0px rgba(186, 186, 186, 0.79);
1553
+ border-radius: 50%;
1554
+ color: #4b4b4b;
1555
+ font-size: 20px;
1556
+ font-weight: 500;
1557
+ font-family: PingFangSC-Medium, PingFang SC;
1558
+ transition: transform 0.3s ease;
1559
+ &.primary {
1560
+ background: linear-gradient(180deg, #2d7aff 0%, #2c5df4 100%);
1561
+ box-shadow: 0px 5px 8px 0px rgba(44, 101, 247, 0.39);
1562
+ color: #fff;
1563
+ }
1564
+ &.danger {
1565
+ background: linear-gradient(180deg, #ff4b4b 0%, #c80606 100%);
1566
+ box-shadow: 0px 5px 8px 0px rgba(217, 26, 26, 0.35);
1567
+ color: #fff;
1568
+ }
1569
+ &:hover {
1570
+ transform: scale(1.2);
1571
+ }
1572
+ & + .toSession {
1573
+ margin-left: 36px;
1574
+ }
1575
+ }
1576
+ }
1577
+ .toolbar {
1578
+ // margin-left: 10px;
1579
+ margin: 0 5px;
1580
+ font-size: 20px;
1581
+ outline: none;
1582
+ padding: 6px;
1583
+ box-sizing: content-box;
1584
+ &:hover {
1585
+ background-color: #ebebeb;
1586
+ border-radius: 6px;
1587
+ }
1588
+ }
1589
+ .closed-toolbar {
1590
+ z-index: 100;
1591
+ }
1592
+ /deep/ p {
1593
+ margin-bottom: 0;
1594
+ }
1595
+ .click-voice {
1596
+ color: #2d7aff;
1597
+ }
1598
+ </style>