sh-view 2.8.1 → 2.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.eslintrc.js +25 -20
  2. package/other.js +8 -8
  3. package/package.json +9 -6
  4. package/packages/components/index.js +91 -91
  5. package/packages/components/sh-alert/alert.ts +30 -0
  6. package/packages/components/sh-alert/index.vue +143 -168
  7. package/packages/components/sh-badge/index.vue +242 -242
  8. package/packages/components/sh-calendar/index.vue +650 -650
  9. package/packages/components/sh-card/index.vue +148 -148
  10. package/packages/components/sh-code-editor/index.vue +19 -19
  11. package/packages/components/sh-col/index.vue +92 -92
  12. package/packages/components/sh-corner/index.vue +230 -230
  13. package/packages/components/sh-count-to/index.vue +131 -131
  14. package/packages/components/sh-date/index.vue +301 -301
  15. package/packages/components/sh-drawer/index.vue +579 -579
  16. package/packages/components/sh-drawer/scrollbar.js +78 -78
  17. package/packages/components/sh-empty/index.vue +42 -42
  18. package/packages/components/sh-form/js/props.js +76 -76
  19. package/packages/components/sh-form/js/useForm.js +229 -229
  20. package/packages/components/sh-header/index.vue +261 -260
  21. package/packages/components/sh-icon/css/default/ionicons.svg +869 -869
  22. package/packages/components/sh-icon/css/font/iconfont.json +247 -247
  23. package/packages/components/sh-icon/index.vue +41 -41
  24. package/packages/components/sh-image/index.vue +133 -133
  25. package/packages/components/sh-list/index.vue +146 -146
  26. package/packages/components/sh-loading/index.vue +53 -53
  27. package/packages/components/sh-modal/index.vue +188 -188
  28. package/packages/components/sh-noticebar/index.vue +215 -215
  29. package/packages/components/sh-poptip/index.vue +597 -597
  30. package/packages/components/sh-progress/index.vue +276 -276
  31. package/packages/components/sh-pull-refresh/index.vue +289 -289
  32. package/packages/components/sh-result/index.vue +114 -114
  33. package/packages/components/sh-row/index.vue +66 -66
  34. package/packages/components/sh-split/components/trigger.vue +33 -33
  35. package/packages/components/sh-split/index.vue +342 -342
  36. package/packages/components/sh-table/components/importModal.vue +363 -363
  37. package/packages/components/sh-table/components/sh-column.vue +68 -68
  38. package/packages/components/sh-table/js/excel_to_json.js +313 -313
  39. package/packages/components/sh-table/js/props.js +305 -305
  40. package/packages/components/sh-table/js/tableMethods.js +167 -167
  41. package/packages/components/sh-table/js/useTable.js +636 -636
  42. package/packages/components/sh-table/table.vue +217 -217
  43. package/packages/components/sh-tabs/index.vue +426 -426
  44. package/packages/components/sh-tag/index.vue +168 -168
  45. package/packages/components/sh-toolbar/index.vue +182 -182
  46. package/packages/components/sh-tree/components/table-tree.vue +289 -289
  47. package/packages/components/sh-tree/mixin/treeProps.js +122 -122
  48. package/packages/components/sh-upload/index.vue +535 -535
  49. package/packages/components/sh-water-fall/index.vue +80 -80
  50. package/packages/components/sh-water-mark/index.vue +96 -96
  51. package/packages/css/index.js +4 -4
  52. package/packages/directive/index.js +19 -19
  53. package/packages/directive/module/click-out.js +14 -14
  54. package/packages/directive/module/draggable.js +42 -42
  55. package/packages/directive/module/line-clamp.js +22 -22
  56. package/packages/directive/module/prevent-click.js +18 -18
  57. package/packages/directive/module/resize.js +14 -14
  58. package/packages/directive/module/ripple.js +166 -166
  59. package/packages/index.js +39 -39
  60. package/packages/mixin/index.js +86 -86
  61. package/packages/other/sh-cron-modal/components/cron-content.vue +294 -294
  62. package/packages/other/sh-cron-modal/index.vue +81 -81
  63. package/packages/other/sh-cron-modal/mixin/cron-emits.js +1 -1
  64. package/packages/other/sh-cron-modal/mixin/cron-props.js +9 -9
  65. package/packages/other/sh-cron-modal/tabs/cron-week-box.vue +126 -126
  66. package/packages/other/sh-menu/index.vue +326 -326
  67. package/packages/other/sh-menu/menu-group-content.vue +136 -136
  68. package/packages/other/sh-menu/menu-item-content.vue +71 -71
  69. package/packages/other/sh-menu-card/index.vue +250 -250
  70. package/packages/other/sh-menu-card/menu-box.vue +87 -87
  71. package/packages/other/sh-preview/components/sh-excel.vue +163 -163
  72. package/packages/other/sh-preview/js/data-hook.js +41 -41
  73. package/packages/other/sh-preview/js/data-props.js +15 -15
  74. package/packages/other/sh-system-tip/index.vue +115 -115
  75. package/packages/utils/resize.js +69 -70
  76. package/packages/utils/transfer-queue.js +12 -12
  77. package/packages/vxeTable/index.js +193 -184
  78. package/packages/vxeTable/plugins/export.js +450 -450
  79. package/packages/vxeTable/render/cell/vxe-render-img.vue +27 -27
  80. package/packages/vxeTable/render/cell/vxe-render-table.vue +51 -51
  81. package/packages/vxeTable/render/cell/vxe-render-time.vue +44 -44
  82. package/packages/vxeTable/render/cell/vxe-render-tree.vue +70 -70
  83. package/packages/vxeTable/render/filters/vxe-filter-input.vue +26 -26
  84. package/packages/vxeTable/render/filters/vxe-filter-time.vue +26 -26
  85. package/packages/vxeTable/render/globalRenders.jsx +514 -514
  86. package/packages/vxeTable/render/mixin/cell-hooks.js +198 -198
  87. package/packages/vxeTable/render/mixin/cell-props.js +23 -23
  88. package/packages/vxeTable/render/mixin/filter-hooks.js +46 -46
  89. package/tsconfig.json +25 -0
  90. package/types/component.d.ts +1 -0
  91. package/types/index.ts +0 -0
@@ -1,535 +1,535 @@
1
- <template>
2
- <div class="sh-upload" :class="{ list: !card }">
3
- <slot name="head"></slot>
4
- <div
5
- v-if="!disabled"
6
- v-show="fileList.length < limit"
7
- class="upload-component"
8
- :class="upClass"
9
- :style="upStyles"
10
- @click="handleClick"
11
- @drop.prevent="onDrop"
12
- @paste="handlePaste"
13
- @dragover.prevent="dragOver = true"
14
- @dragleave.prevent="dragOver = false">
15
- <input ref="inputRef" type="file" class="sh-upload-input" :multiple="multiple" :webkitdirectory="webkitdirectory" :accept="accept" @change="handleChange" />
16
- <slot>
17
- <div class="upload-box" :class="{ drag, card }" :style="upStyles">
18
- <sh-icon v-if="icon" class="upload-box-icon" :type="icon" :size="iconSize"></sh-icon>
19
- <span class="upload-box-text">上传附件</span>
20
- </div>
21
- </slot>
22
- </div>
23
- <ul v-if="fileList.length" class="upload-list">
24
- <li v-for="(file, index) in fileList" :key="index" class="upload-list-item">
25
- <slot name="fileItem" v-bind="file">
26
- <div class="upload-list-title" :title="file.name">{{ file.name }}</div>
27
- <div v-if="file.status !== 'success'" class="upload-list-progress">
28
- <sh-progress v-if="file.showProgress" v-bind="progressConfig(file)"></sh-progress>
29
- </div>
30
- <div v-else class="upload-list-action">
31
- <template v-for="listAct in listActions" :key="listAct.code">
32
- <span class="action-item" @click="onActionClick(listAct, file)"><sh-icon :type="listAct.icon" :title="listAct.title" size="18" /></span>
33
- </template>
34
- </div>
35
- </slot>
36
- </li>
37
- </ul>
38
- <slot name="foot"></slot>
39
- </div>
40
- </template>
41
-
42
- <script>
43
- import { defineComponent, computed, getCurrentInstance, ref, watch, onBeforeMount } from 'vue'
44
- import ajax from './js/ajax'
45
- export default defineComponent({
46
- name: 'ShUpload',
47
- props: {
48
- modelValue: {
49
- type: Array,
50
- default() {
51
- return []
52
- }
53
- },
54
- baseUrl: {
55
- type: String,
56
- default: ''
57
- },
58
- width: {
59
- type: [String, Number],
60
- default: 80
61
- },
62
- height: {
63
- type: [String, Number],
64
- default: 80
65
- },
66
- limit: {
67
- type: Number,
68
- default: 1
69
- },
70
- card: {
71
- type: Boolean
72
- },
73
- drag: {
74
- type: Boolean
75
- },
76
- icon: {
77
- type: String,
78
- default: 'ios-folder'
79
- },
80
- iconSize: {
81
- type: [Number, String]
82
- },
83
- params: {
84
- type: Object,
85
- default() {
86
- return {}
87
- }
88
- },
89
- actionList: {
90
- type: Array,
91
- default() {
92
- return ['detail', 'download', 'delete']
93
- }
94
- },
95
- resKey: {
96
- type: String,
97
- default: 'data'
98
- },
99
- action: {
100
- type: String,
101
- default: 'fileservice/v2/upload'
102
- },
103
- headers: {
104
- type: Object,
105
- default() {
106
- return {}
107
- }
108
- },
109
- disabled: {
110
- type: Boolean,
111
- default: false
112
- },
113
- maxSize: {
114
- type: Number,
115
- default: 1024 * 2 // 文件大小限制,单位 kb
116
- },
117
- accept: {
118
- type: String,
119
- default: '.doc,.docx,.xls,.xlsx,.txt,.pdf,.jpg,.jpeg,.png,.txt,.7z,.zip,.rar'
120
- },
121
- name: {
122
- type: String,
123
- default: 'file'
124
- },
125
- beforeUpload: {
126
- type: Function
127
- },
128
- webkitdirectory: {
129
- type: Boolean,
130
- default: false // 是否开启选择文件夹,部分浏览器适用
131
- },
132
- withCredentials: {
133
- type: Boolean,
134
- default: false // 支持发送 cookie 凭证信息
135
- },
136
- paste: {
137
- type: Boolean,
138
- default: false // 是否支持粘贴上传文件
139
- },
140
- format: {
141
- type: Array,
142
- default() {
143
- return []
144
- }
145
- },
146
- onFormatError: {
147
- type: Function
148
- },
149
- onExceededSize: {
150
- type: Function
151
- },
152
- onProgress: {
153
- type: Function,
154
- default() {}
155
- },
156
- onSuccess: {
157
- type: Function,
158
- default() {}
159
- },
160
- onError: {
161
- type: Function,
162
- default() {}
163
- },
164
- onRemove: {
165
- type: Function,
166
- default() {}
167
- },
168
- onPreview: {
169
- type: Function,
170
- default() {}
171
- },
172
- onDownload: {
173
- type: Function,
174
- default() {}
175
- }
176
- },
177
- emits: ['update:modelValue', 'action'],
178
- setup(props, context) {
179
- const { proxy } = getCurrentInstance()
180
- const { $vUtils } = proxy
181
- const { emit, slots } = context
182
-
183
- const inputRef = ref()
184
- const tempIndex = ref(1)
185
- const dragOver = ref(false)
186
- const fileList = ref([])
187
-
188
- const multiple = computed(() => props.limit > 1)
189
- const upStyles = computed(() => {
190
- return props.card ? { width: `${props.width}px`, height: `${props.height}px` } : {}
191
- })
192
- const upClass = computed(() => {
193
- return {
194
- 'sh-upload-list': !props.card,
195
- 'sh-upload-select': !props.drag,
196
- 'sh-upload-drag': props.drag,
197
- 'sh-upload-dragOver': props.drag && dragOver.value
198
- }
199
- })
200
- const fileFormat = computed(() => (props.format.length ? props.format : props.accept.split(',').map(_ => String(_).replace('.', ''))))
201
- const listActions = computed(() => {
202
- let list = [
203
- { title: '查看文件', icon: 'ios-eye-outline', code: 'detail' },
204
- { title: '下载文件', icon: 'ios-download', code: 'download' }
205
- ]
206
- if (!props.disabled) {
207
- list.push({ title: '删除文件', icon: 'ios-trash-outline', code: 'delete' })
208
- }
209
- return list.filter(item => props.actionList.includes(item.code))
210
- })
211
-
212
- const progressConfig = file => {
213
- return {
214
- textInside: true,
215
- strokeWidth: 20,
216
- percent: parseInt(file.percentage, 10),
217
- status: file.status
218
- }
219
- }
220
- const handleClick = () => {
221
- if (props.disabled) return
222
- inputRef.value.click()
223
- }
224
- const handleChange = e => {
225
- const files = e.target.files
226
- if (!files) return
227
- uploadFiles(files)
228
- inputRef.value.value = null
229
- }
230
- const handleStart = file => {
231
- file.uid = Date.now() + tempIndex.value++
232
- const _file = {
233
- status: 'active',
234
- name: file.name,
235
- size: file.size,
236
- percentage: 0,
237
- uid: file.uid,
238
- showProgress: true
239
- }
240
- fileList.value.push(_file)
241
- }
242
- const handlePaste = e => {
243
- if (props.disabled) return
244
- if (props.paste) {
245
- uploadFiles(e.clipboardData.files)
246
- }
247
- }
248
- const handleProgress = (e, file) => {
249
- const _file = getFile(file)
250
- props.onProgress(e, _file, fileList.value)
251
- _file.percentage = e.percent || 0
252
- }
253
- const handleSuccess = (res, file) => {
254
- const _file = getFile(file)
255
- if (_file) {
256
- _file.status = 'success'
257
- _file.response = res
258
- emitValue()
259
- props.onSuccess(res, _file, fileList.value)
260
- setTimeout(() => {
261
- _file.showProgress = false
262
- }, 1000)
263
- }
264
- }
265
- const handleError = (err, response, file) => {
266
- const _file = getFile(file)
267
- const fileArray = fileList.value
268
- _file.status = 'wrong'
269
- fileArray.splice(fileArray.indexOf(_file), 1)
270
- props.onError(err, response, file)
271
- }
272
- const handleRemove = file => {
273
- const fileArray = fileList.value
274
- fileArray.splice(fileArray.indexOf(file), 1)
275
- emitValue()
276
- props.onRemove(file, fileArray)
277
- }
278
- const handlePreview = file => {
279
- const fileArray = fileList.value
280
- fileArray.splice(fileArray.indexOf(file), 1)
281
- props.onRemove(file, fileArray)
282
- }
283
- const handleDownload = file => {
284
- const fileArray = fileList.value
285
- fileArray.splice(fileArray.indexOf(file), 1)
286
- props.onDownload(file, fileArray)
287
- }
288
- const onDrop = e => {
289
- dragOver.value = false
290
- if (props.disabled) return
291
- uploadFiles(e.dataTransfer.files)
292
- }
293
- const uploadFiles = files => {
294
- let postFiles = Array.prototype.slice.call(files)
295
- if (!multiple.value) postFiles = postFiles.slice(0, 1)
296
- if (postFiles.length === 0) return
297
- postFiles.forEach(file => {
298
- upload(file)
299
- })
300
- }
301
- const upload = file => {
302
- if (!props.beforeUpload) {
303
- return post(file)
304
- }
305
- const before = props.beforeUpload(file)
306
- if (before && before.then) {
307
- before.then(processedFile => {
308
- if (Object.prototype.toString.call(processedFile) === '[object File]') {
309
- post(processedFile)
310
- } else {
311
- post(file)
312
- }
313
- })
314
- } else if (before !== false) {
315
- post(file)
316
- }
317
- }
318
- const post = async file => {
319
- let { headers, name, params, action, withCredentials } = props
320
- if (validateFile) {
321
- let valResult = validateFile(file)
322
- if (!valResult) return
323
- }
324
- handleStart(file)
325
- ajax({
326
- headers: headers,
327
- withCredentials: withCredentials,
328
- file: file,
329
- data: params,
330
- filename: name,
331
- action: action,
332
- onProgress: e => {
333
- handleProgress(e, file)
334
- },
335
- onSuccess: res => {
336
- handleSuccess(res, file)
337
- },
338
- onError: (err, response) => {
339
- handleError(err, response, file)
340
- }
341
- })
342
- }
343
- const getFile = file => {
344
- const fileArray = fileList.value
345
- let target
346
- fileArray.every(item => {
347
- target = file.uid === item.uid ? item : null
348
- return !target
349
- })
350
- return target
351
- }
352
- const clearFiles = () => {
353
- fileList.value = []
354
- emitValue()
355
- }
356
- const emitValue = () => {
357
- let list = getModelValue ? getModelValue() : fileList.value
358
- emit('update:modelValue', list)
359
- }
360
-
361
- // 上传校验
362
- const validateFile = file => {
363
- let { action, maxSize } = props
364
- // check format
365
- if (fileFormat.value.length) {
366
- const _file_format = file.name.split('.').pop().toLocaleLowerCase()
367
- const checked = fileFormat.value.some(item => item.toLocaleLowerCase() === _file_format)
368
- if (!checked) {
369
- let errorMsg = `格式不正确,请上传 ${fileFormat.value.join(' ')} 格式文件`
370
- props.onFormatError ? props.onFormatError(file, errorMsg, fileList.value) : proxy.msginfo(errorMsg)
371
- return
372
- }
373
- }
374
- // check maxSize
375
- if (maxSize && file.size > maxSize * 1024) {
376
- let errorMsg = `文件大小不能超过 ${maxSize / 1024} M`
377
- props.onExceededSize ? props.onExceededSize(file, errorMsg, fileList.value) : proxy.msginfo(errorMsg)
378
- return
379
- }
380
- if (!action) {
381
- proxy.msginfo('上传地址不能为空')
382
- return
383
- }
384
- return true
385
- }
386
- // 附件操作点击事件
387
- const onActionClick = (obj, file) => {
388
- if (obj.code === 'delete') {
389
- handleRemove(file)
390
- } else if (obj.code === 'detail') {
391
- handlePreview(file)
392
- } else if (obj.code === 'download') {
393
- handleDownload(file)
394
- }
395
- emit('action', obj)
396
- }
397
- // 获取双向绑定值
398
- const getModelValue = () => {
399
- return fileList.value.map(item => {
400
- let url = $vUtils.get(item, `response.${props.resKey}`)
401
- return { name: item.name, url: url }
402
- })
403
- }
404
-
405
- watch(
406
- () => props.modelValue,
407
- value => {
408
- fileList.value = value
409
- },
410
- {
411
- immediate: true,
412
- deep: true
413
- }
414
- )
415
-
416
- return {
417
- inputRef,
418
- fileList,
419
- upClass,
420
- upStyles,
421
- dragOver,
422
- multiple,
423
- listActions,
424
- handleClick,
425
- handlePaste,
426
- handleChange,
427
- progressConfig,
428
- onDrop,
429
- onActionClick
430
- }
431
- }
432
- })
433
- </script>
434
-
435
- <style scoped lang="scss">
436
- .sh-upload {
437
- display: inline-block;
438
- vertical-align: top;
439
- &.list {
440
- display: block;
441
- width: 100%;
442
- }
443
- .upload-component {
444
- border: 1px solid var(--vxe-table-border-color);
445
- background-color: var(--vxe-table-body-background-color);
446
- border-radius: var(--vxe-border-radius);
447
- cursor: pointer;
448
- input[type='file'] {
449
- display: none;
450
- }
451
- .upload-box {
452
- display: inline-block;
453
- padding: 6px 10px;
454
- box-sizing: border-box;
455
- .upload-box-icon {
456
- margin-right: 5px;
457
- }
458
- &.card,
459
- &.drag {
460
- display: inline-flex;
461
- flex-direction: column;
462
- align-items: center;
463
- justify-content: center;
464
- .upload-box-icon {
465
- margin-bottom: 8px;
466
- }
467
- }
468
- &.drag {
469
- min-height: 100px;
470
- }
471
- }
472
- &:hover {
473
- border: 1px solid var(--vxe-primary-color);
474
- }
475
- &.sh-upload-select {
476
- display: inline-block;
477
- vertical-align: top;
478
- }
479
- &.sh-upload-drag {
480
- text-align: center;
481
- cursor: pointer;
482
- position: relative;
483
- overflow: hidden;
484
- &:hover {
485
- border: 1px dashed var(--vxe-primary-color);
486
- }
487
- }
488
- &.sh-upload-dragOver {
489
- border: 2px dashed var(--vxe-primary-color);
490
- }
491
- }
492
- .upload-list {
493
- display: block;
494
- .upload-list-item {
495
- display: flex;
496
- align-items: stretch;
497
- border: 1px solid var(--vxe-table-border-color);
498
- background-color: var(--vxe-table-body-background-color);
499
- position: relative;
500
- .upload-list-title {
501
- padding: 6px 10px;
502
- flex: 1;
503
- display: block;
504
- text-overflow: ellipsis;
505
- white-space: nowrap;
506
- overflow: hidden;
507
- }
508
- .upload-list-action {
509
- display: inline-flex;
510
- align-items: center;
511
- color: var(--vxe-primary-color);
512
- .action-item {
513
- display: inline-flex;
514
- align-items: center;
515
- padding: 0 5px;
516
- height: 100%;
517
- cursor: pointer;
518
- &:hover {
519
- background-color: var(--vxe-primary-darken-color);
520
- }
521
- }
522
- }
523
- .upload-list-progress {
524
- position: absolute;
525
- left: 0;
526
- right: 0;
527
- top: 0;
528
- bottom: 0;
529
- display: flex;
530
- align-items: center;
531
- }
532
- }
533
- }
534
- }
535
- </style>
1
+ <template>
2
+ <div class="sh-upload" :class="{ list: !card }">
3
+ <slot name="head"></slot>
4
+ <div
5
+ v-if="!disabled"
6
+ v-show="fileList.length < limit"
7
+ class="upload-component"
8
+ :class="upClass"
9
+ :style="upStyles"
10
+ @click="handleClick"
11
+ @drop.prevent="onDrop"
12
+ @paste="handlePaste"
13
+ @dragover.prevent="dragOver = true"
14
+ @dragleave.prevent="dragOver = false">
15
+ <input ref="inputRef" type="file" class="sh-upload-input" :multiple="multiple" :webkitdirectory="webkitdirectory" :accept="accept" @change="handleChange" />
16
+ <slot>
17
+ <div class="upload-box" :class="{ drag, card }" :style="upStyles">
18
+ <sh-icon v-if="icon" class="upload-box-icon" :type="icon" :size="iconSize"></sh-icon>
19
+ <span class="upload-box-text">上传附件</span>
20
+ </div>
21
+ </slot>
22
+ </div>
23
+ <ul v-if="fileList.length" class="upload-list">
24
+ <li v-for="(file, index) in fileList" :key="index" class="upload-list-item">
25
+ <slot name="fileItem" v-bind="file">
26
+ <div class="upload-list-title" :title="file.name">{{ file.name }}</div>
27
+ <div v-if="file.status !== 'success'" class="upload-list-progress">
28
+ <sh-progress v-if="file.showProgress" v-bind="progressConfig(file)"></sh-progress>
29
+ </div>
30
+ <div v-else class="upload-list-action">
31
+ <template v-for="listAct in listActions" :key="listAct.code">
32
+ <span class="action-item" @click="onActionClick(listAct, file)"><sh-icon :type="listAct.icon" :title="listAct.title" size="18" /></span>
33
+ </template>
34
+ </div>
35
+ </slot>
36
+ </li>
37
+ </ul>
38
+ <slot name="foot"></slot>
39
+ </div>
40
+ </template>
41
+
42
+ <script>
43
+ import { defineComponent, computed, getCurrentInstance, ref, watch, onBeforeMount } from 'vue'
44
+ import ajax from './js/ajax'
45
+ export default defineComponent({
46
+ name: 'ShUpload',
47
+ props: {
48
+ modelValue: {
49
+ type: Array,
50
+ default() {
51
+ return []
52
+ }
53
+ },
54
+ baseUrl: {
55
+ type: String,
56
+ default: ''
57
+ },
58
+ width: {
59
+ type: [String, Number],
60
+ default: 80
61
+ },
62
+ height: {
63
+ type: [String, Number],
64
+ default: 80
65
+ },
66
+ limit: {
67
+ type: Number,
68
+ default: 1
69
+ },
70
+ card: {
71
+ type: Boolean
72
+ },
73
+ drag: {
74
+ type: Boolean
75
+ },
76
+ icon: {
77
+ type: String,
78
+ default: 'ios-folder'
79
+ },
80
+ iconSize: {
81
+ type: [Number, String]
82
+ },
83
+ params: {
84
+ type: Object,
85
+ default() {
86
+ return {}
87
+ }
88
+ },
89
+ actionList: {
90
+ type: Array,
91
+ default() {
92
+ return ['detail', 'download', 'delete']
93
+ }
94
+ },
95
+ resKey: {
96
+ type: String,
97
+ default: 'data'
98
+ },
99
+ action: {
100
+ type: String,
101
+ default: 'fileservice/v2/upload'
102
+ },
103
+ headers: {
104
+ type: Object,
105
+ default() {
106
+ return {}
107
+ }
108
+ },
109
+ disabled: {
110
+ type: Boolean,
111
+ default: false
112
+ },
113
+ maxSize: {
114
+ type: Number,
115
+ default: 1024 * 2 // 文件大小限制,单位 kb
116
+ },
117
+ accept: {
118
+ type: String,
119
+ default: '.doc,.docx,.xls,.xlsx,.txt,.pdf,.jpg,.jpeg,.png,.txt,.7z,.zip,.rar'
120
+ },
121
+ name: {
122
+ type: String,
123
+ default: 'file'
124
+ },
125
+ beforeUpload: {
126
+ type: Function
127
+ },
128
+ webkitdirectory: {
129
+ type: Boolean,
130
+ default: false // 是否开启选择文件夹,部分浏览器适用
131
+ },
132
+ withCredentials: {
133
+ type: Boolean,
134
+ default: false // 支持发送 cookie 凭证信息
135
+ },
136
+ paste: {
137
+ type: Boolean,
138
+ default: false // 是否支持粘贴上传文件
139
+ },
140
+ format: {
141
+ type: Array,
142
+ default() {
143
+ return []
144
+ }
145
+ },
146
+ onFormatError: {
147
+ type: Function
148
+ },
149
+ onExceededSize: {
150
+ type: Function
151
+ },
152
+ onProgress: {
153
+ type: Function,
154
+ default() {}
155
+ },
156
+ onSuccess: {
157
+ type: Function,
158
+ default() {}
159
+ },
160
+ onError: {
161
+ type: Function,
162
+ default() {}
163
+ },
164
+ onRemove: {
165
+ type: Function,
166
+ default() {}
167
+ },
168
+ onPreview: {
169
+ type: Function,
170
+ default() {}
171
+ },
172
+ onDownload: {
173
+ type: Function,
174
+ default() {}
175
+ }
176
+ },
177
+ emits: ['update:modelValue', 'action'],
178
+ setup(props, context) {
179
+ const { proxy } = getCurrentInstance()
180
+ const { $vUtils } = proxy
181
+ const { emit, slots } = context
182
+
183
+ const inputRef = ref()
184
+ const tempIndex = ref(1)
185
+ const dragOver = ref(false)
186
+ const fileList = ref([])
187
+
188
+ const multiple = computed(() => props.limit > 1)
189
+ const upStyles = computed(() => {
190
+ return props.card ? { width: `${props.width}px`, height: `${props.height}px` } : {}
191
+ })
192
+ const upClass = computed(() => {
193
+ return {
194
+ 'sh-upload-list': !props.card,
195
+ 'sh-upload-select': !props.drag,
196
+ 'sh-upload-drag': props.drag,
197
+ 'sh-upload-dragOver': props.drag && dragOver.value
198
+ }
199
+ })
200
+ const fileFormat = computed(() => (props.format.length ? props.format : props.accept.split(',').map(_ => String(_).replace('.', ''))))
201
+ const listActions = computed(() => {
202
+ let list = [
203
+ { title: '查看文件', icon: 'ios-eye-outline', code: 'detail' },
204
+ { title: '下载文件', icon: 'ios-download', code: 'download' }
205
+ ]
206
+ if (!props.disabled) {
207
+ list.push({ title: '删除文件', icon: 'ios-trash-outline', code: 'delete' })
208
+ }
209
+ return list.filter(item => props.actionList.includes(item.code))
210
+ })
211
+
212
+ const progressConfig = file => {
213
+ return {
214
+ textInside: true,
215
+ strokeWidth: 20,
216
+ percent: parseInt(file.percentage, 10),
217
+ status: file.status
218
+ }
219
+ }
220
+ const handleClick = () => {
221
+ if (props.disabled) return
222
+ inputRef.value.click()
223
+ }
224
+ const handleChange = e => {
225
+ const files = e.target.files
226
+ if (!files) return
227
+ uploadFiles(files)
228
+ inputRef.value.value = null
229
+ }
230
+ const handleStart = file => {
231
+ file.uid = Date.now() + tempIndex.value++
232
+ const _file = {
233
+ status: 'active',
234
+ name: file.name,
235
+ size: file.size,
236
+ percentage: 0,
237
+ uid: file.uid,
238
+ showProgress: true
239
+ }
240
+ fileList.value.push(_file)
241
+ }
242
+ const handlePaste = e => {
243
+ if (props.disabled) return
244
+ if (props.paste) {
245
+ uploadFiles(e.clipboardData.files)
246
+ }
247
+ }
248
+ const handleProgress = (e, file) => {
249
+ const _file = getFile(file)
250
+ props.onProgress(e, _file, fileList.value)
251
+ _file.percentage = e.percent || 0
252
+ }
253
+ const handleSuccess = (res, file) => {
254
+ const _file = getFile(file)
255
+ if (_file) {
256
+ _file.status = 'success'
257
+ _file.response = res
258
+ emitValue()
259
+ props.onSuccess(res, _file, fileList.value)
260
+ setTimeout(() => {
261
+ _file.showProgress = false
262
+ }, 1000)
263
+ }
264
+ }
265
+ const handleError = (err, response, file) => {
266
+ const _file = getFile(file)
267
+ const fileArray = fileList.value
268
+ _file.status = 'wrong'
269
+ fileArray.splice(fileArray.indexOf(_file), 1)
270
+ props.onError(err, response, file)
271
+ }
272
+ const handleRemove = file => {
273
+ const fileArray = fileList.value
274
+ fileArray.splice(fileArray.indexOf(file), 1)
275
+ emitValue()
276
+ props.onRemove(file, fileArray)
277
+ }
278
+ const handlePreview = file => {
279
+ const fileArray = fileList.value
280
+ fileArray.splice(fileArray.indexOf(file), 1)
281
+ props.onRemove(file, fileArray)
282
+ }
283
+ const handleDownload = file => {
284
+ const fileArray = fileList.value
285
+ fileArray.splice(fileArray.indexOf(file), 1)
286
+ props.onDownload(file, fileArray)
287
+ }
288
+ const onDrop = e => {
289
+ dragOver.value = false
290
+ if (props.disabled) return
291
+ uploadFiles(e.dataTransfer.files)
292
+ }
293
+ const uploadFiles = files => {
294
+ let postFiles = Array.prototype.slice.call(files)
295
+ if (!multiple.value) postFiles = postFiles.slice(0, 1)
296
+ if (postFiles.length === 0) return
297
+ postFiles.forEach(file => {
298
+ upload(file)
299
+ })
300
+ }
301
+ const upload = file => {
302
+ if (!props.beforeUpload) {
303
+ return post(file)
304
+ }
305
+ const before = props.beforeUpload(file)
306
+ if (before && before.then) {
307
+ before.then(processedFile => {
308
+ if (Object.prototype.toString.call(processedFile) === '[object File]') {
309
+ post(processedFile)
310
+ } else {
311
+ post(file)
312
+ }
313
+ })
314
+ } else if (before !== false) {
315
+ post(file)
316
+ }
317
+ }
318
+ const post = async file => {
319
+ let { headers, name, params, action, withCredentials } = props
320
+ if (validateFile) {
321
+ let valResult = validateFile(file)
322
+ if (!valResult) return
323
+ }
324
+ handleStart(file)
325
+ ajax({
326
+ headers: headers,
327
+ withCredentials: withCredentials,
328
+ file: file,
329
+ data: params,
330
+ filename: name,
331
+ action: action,
332
+ onProgress: e => {
333
+ handleProgress(e, file)
334
+ },
335
+ onSuccess: res => {
336
+ handleSuccess(res, file)
337
+ },
338
+ onError: (err, response) => {
339
+ handleError(err, response, file)
340
+ }
341
+ })
342
+ }
343
+ const getFile = file => {
344
+ const fileArray = fileList.value
345
+ let target
346
+ fileArray.every(item => {
347
+ target = file.uid === item.uid ? item : null
348
+ return !target
349
+ })
350
+ return target
351
+ }
352
+ const clearFiles = () => {
353
+ fileList.value = []
354
+ emitValue()
355
+ }
356
+ const emitValue = () => {
357
+ let list = getModelValue ? getModelValue() : fileList.value
358
+ emit('update:modelValue', list)
359
+ }
360
+
361
+ // 上传校验
362
+ const validateFile = file => {
363
+ let { action, maxSize } = props
364
+ // check format
365
+ if (fileFormat.value.length) {
366
+ const _file_format = file.name.split('.').pop().toLocaleLowerCase()
367
+ const checked = fileFormat.value.some(item => item.toLocaleLowerCase() === _file_format)
368
+ if (!checked) {
369
+ let errorMsg = `格式不正确,请上传 ${fileFormat.value.join(' ')} 格式文件`
370
+ props.onFormatError ? props.onFormatError(file, errorMsg, fileList.value) : proxy.msginfo(errorMsg)
371
+ return
372
+ }
373
+ }
374
+ // check maxSize
375
+ if (maxSize && file.size > maxSize * 1024) {
376
+ let errorMsg = `文件大小不能超过 ${maxSize / 1024} M`
377
+ props.onExceededSize ? props.onExceededSize(file, errorMsg, fileList.value) : proxy.msginfo(errorMsg)
378
+ return
379
+ }
380
+ if (!action) {
381
+ proxy.msginfo('上传地址不能为空')
382
+ return
383
+ }
384
+ return true
385
+ }
386
+ // 附件操作点击事件
387
+ const onActionClick = (obj, file) => {
388
+ if (obj.code === 'delete') {
389
+ handleRemove(file)
390
+ } else if (obj.code === 'detail') {
391
+ handlePreview(file)
392
+ } else if (obj.code === 'download') {
393
+ handleDownload(file)
394
+ }
395
+ emit('action', obj)
396
+ }
397
+ // 获取双向绑定值
398
+ const getModelValue = () => {
399
+ return fileList.value.map(item => {
400
+ let url = $vUtils.get(item, `response.${props.resKey}`)
401
+ return { name: item.name, url: url }
402
+ })
403
+ }
404
+
405
+ watch(
406
+ () => props.modelValue,
407
+ value => {
408
+ fileList.value = value
409
+ },
410
+ {
411
+ immediate: true,
412
+ deep: true
413
+ }
414
+ )
415
+
416
+ return {
417
+ inputRef,
418
+ fileList,
419
+ upClass,
420
+ upStyles,
421
+ dragOver,
422
+ multiple,
423
+ listActions,
424
+ handleClick,
425
+ handlePaste,
426
+ handleChange,
427
+ progressConfig,
428
+ onDrop,
429
+ onActionClick
430
+ }
431
+ }
432
+ })
433
+ </script>
434
+
435
+ <style scoped lang="scss">
436
+ .sh-upload {
437
+ display: inline-block;
438
+ vertical-align: top;
439
+ &.list {
440
+ display: block;
441
+ width: 100%;
442
+ }
443
+ .upload-component {
444
+ border: 1px solid var(--vxe-table-border-color);
445
+ background-color: var(--vxe-table-body-background-color);
446
+ border-radius: var(--vxe-border-radius);
447
+ cursor: pointer;
448
+ input[type='file'] {
449
+ display: none;
450
+ }
451
+ .upload-box {
452
+ display: inline-block;
453
+ padding: 6px 10px;
454
+ box-sizing: border-box;
455
+ .upload-box-icon {
456
+ margin-right: 5px;
457
+ }
458
+ &.card,
459
+ &.drag {
460
+ display: inline-flex;
461
+ flex-direction: column;
462
+ align-items: center;
463
+ justify-content: center;
464
+ .upload-box-icon {
465
+ margin-bottom: 8px;
466
+ }
467
+ }
468
+ &.drag {
469
+ min-height: 100px;
470
+ }
471
+ }
472
+ &:hover {
473
+ border: 1px solid var(--vxe-primary-color);
474
+ }
475
+ &.sh-upload-select {
476
+ display: inline-block;
477
+ vertical-align: top;
478
+ }
479
+ &.sh-upload-drag {
480
+ text-align: center;
481
+ cursor: pointer;
482
+ position: relative;
483
+ overflow: hidden;
484
+ &:hover {
485
+ border: 1px dashed var(--vxe-primary-color);
486
+ }
487
+ }
488
+ &.sh-upload-dragOver {
489
+ border: 2px dashed var(--vxe-primary-color);
490
+ }
491
+ }
492
+ .upload-list {
493
+ display: block;
494
+ .upload-list-item {
495
+ display: flex;
496
+ align-items: stretch;
497
+ border: 1px solid var(--vxe-table-border-color);
498
+ background-color: var(--vxe-table-body-background-color);
499
+ position: relative;
500
+ .upload-list-title {
501
+ padding: 6px 10px;
502
+ flex: 1;
503
+ display: block;
504
+ text-overflow: ellipsis;
505
+ white-space: nowrap;
506
+ overflow: hidden;
507
+ }
508
+ .upload-list-action {
509
+ display: inline-flex;
510
+ align-items: center;
511
+ color: var(--vxe-primary-color);
512
+ .action-item {
513
+ display: inline-flex;
514
+ align-items: center;
515
+ padding: 0 5px;
516
+ height: 100%;
517
+ cursor: pointer;
518
+ &:hover {
519
+ background-color: var(--vxe-primary-darken-color);
520
+ }
521
+ }
522
+ }
523
+ .upload-list-progress {
524
+ position: absolute;
525
+ left: 0;
526
+ right: 0;
527
+ top: 0;
528
+ bottom: 0;
529
+ display: flex;
530
+ align-items: center;
531
+ }
532
+ }
533
+ }
534
+ }
535
+ </style>