@ecolovo/uniapp-components 1.0.0

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 (97) hide show
  1. package/components/aspect/aspect.vue +43 -0
  2. package/components/badge/badge.vue +88 -0
  3. package/components/button/button.vue +409 -0
  4. package/components/checkbox/checkbox.vue +183 -0
  5. package/components/checkbox-group/checkbox-group.vue +101 -0
  6. package/components/checkbox-group2/checkbox-group2.vue +138 -0
  7. package/components/checkbox-group2/checkbox.vue +103 -0
  8. package/components/data-list/data-list.vue +130 -0
  9. package/components/date-picker/date-picker-view.vue +181 -0
  10. package/components/date-picker/date-picker.vue +237 -0
  11. package/components/datetime-picker/datetime-picker-view.vue +209 -0
  12. package/components/datetime-picker/datetime-picker.vue +235 -0
  13. package/components/dialog/dialog-content.vue +162 -0
  14. package/components/dialog/dialog.vue +166 -0
  15. package/components/dropdown/dropdown.vue +145 -0
  16. package/components/empty/empty.vue +102 -0
  17. package/components/flex/flex.vue +76 -0
  18. package/components/form/form.vue +43 -0
  19. package/components/form/mixin.js +13 -0
  20. package/components/form-item/form-item.vue +141 -0
  21. package/components/icon/icon.vue +114 -0
  22. package/components/image/image.vue +61 -0
  23. package/components/input/input.vue +154 -0
  24. package/components/input/password-input.vue +95 -0
  25. package/components/list-page/list-page.js +10 -0
  26. package/components/list-page/list-page.vue +150 -0
  27. package/components/load-more/load-more.vue +97 -0
  28. package/components/loading/animations/loading-animation1.vue +92 -0
  29. package/components/loading/animations/loading-animation2.vue +28 -0
  30. package/components/loading/animations/loading-animation3.vue +81 -0
  31. package/components/loading/animations/loading-animation4.vue +74 -0
  32. package/components/loading/loading.vue +91 -0
  33. package/components/message-box/message-box.vue +60 -0
  34. package/components/number-dialog/number-dialog.vue +360 -0
  35. package/components/number-input/number-input.vue +352 -0
  36. package/components/numpad/numpad.vue +224 -0
  37. package/components/page/page-body.vue +227 -0
  38. package/components/page/page-footer.vue +75 -0
  39. package/components/page/page-header.vue +222 -0
  40. package/components/page/page.vue +128 -0
  41. package/components/picker/picker-popup.vue +295 -0
  42. package/components/picker/picker.vue +151 -0
  43. package/components/popover/popover-content.vue +255 -0
  44. package/components/popover/popover-mask.vue +113 -0
  45. package/components/popover/popover.vue +84 -0
  46. package/components/popup/popup-content.vue +162 -0
  47. package/components/popup/popup.vue +168 -0
  48. package/components/price/price.vue +104 -0
  49. package/components/search/search.vue +220 -0
  50. package/components/searching/searching.vue +86 -0
  51. package/components/selector/selector.vue +86 -0
  52. package/components/switch/switch.vue +180 -0
  53. package/components/tab/tab.vue +235 -0
  54. package/components/table/table.vue +112 -0
  55. package/components/textarea/textarea.vue +192 -0
  56. package/components/time-picker/time-picker-view.vue +127 -0
  57. package/components/time-picker/time-picker.vue +140 -0
  58. package/components/uploader/download.js +81 -0
  59. package/components/uploader/file-uploader.vue +52 -0
  60. package/components/uploader/files.js +225 -0
  61. package/components/uploader/media-uploader.vue +133 -0
  62. package/components/uploader/mixin.js +106 -0
  63. package/components/uploader/uni.js +38 -0
  64. package/components/uploader/upload-button.vue +29 -0
  65. package/components/uploader/uploader copy.vue +547 -0
  66. package/components/uploader/uploader.vue +117 -0
  67. package/configs/index.js +41 -0
  68. package/easyCom.js +25 -0
  69. package/init.js +17 -0
  70. package/mixins/componentName.js +32 -0
  71. package/mixins/listPage.js +1 -0
  72. package/mixins/windowInfo.js +1 -0
  73. package/package.json +12 -0
  74. package/resources/iconfont/uniapp_ui_icon/demo.css +539 -0
  75. package/resources/iconfont/uniapp_ui_icon/demo_index.html +555 -0
  76. package/resources/iconfont/uniapp_ui_icon/iconfont.css +78 -0
  77. package/resources/iconfont/uniapp_ui_icon/iconfont.js +1 -0
  78. package/resources/iconfont/uniapp_ui_icon/iconfont.json +121 -0
  79. package/resources/iconfont/uniapp_ui_icon/iconfont.woff2 +0 -0
  80. package/resources/image/empty.svg +13 -0
  81. package/styles/global.scss +24 -0
  82. package/styles/index.scss +2 -0
  83. package/styles/utils/css-var.scss +87 -0
  84. package/styles/utils/ellipsis.scss +26 -0
  85. package/styles/utils/global-var.scss +11 -0
  86. package/styles/utils/index.scss +5 -0
  87. package/styles/utils/selector.scss +5 -0
  88. package/styles/utils/z-index.scss +10 -0
  89. package/styles/var/component.scss +242 -0
  90. package/styles/var/index.scss +2 -0
  91. package/styles/var/theme.scss +40 -0
  92. package/utils/date.js +151 -0
  93. package/utils/floor.js +20 -0
  94. package/utils/freezeDeep.js +19 -0
  95. package/utils/price.js +32 -0
  96. package/utils/round.js +22 -0
  97. package/utils/windowInfo.js +77 -0
@@ -0,0 +1,547 @@
1
+ <template>
2
+ <view :class="[componentClassNames, isCardView ? $style['Uploader--card'] : $style['Uploader--list']]">
3
+ <template v-if="isCardView">
4
+ <!-- 附件列表 -->
5
+ <view
6
+ v-for="(item, index) in currentValue"
7
+ :key="index"
8
+ :class="$style['Uploader--file']"
9
+ @click="previewFile(item)"
10
+ >
11
+ <view
12
+ v-if="!computedReadonly && item.uploadStatus !== 'uploading'"
13
+ :class="$style['Uploader--icon-close']"
14
+ @click.stop="deleteFile(item)"
15
+ >
16
+ </view>
17
+ <image
18
+ v-if="item.fileType === FILE_TYPE_IMAGE"
19
+ :class="$style['Uploader--image']"
20
+ mode="aspectFill"
21
+ :src="item.fileUrl"
22
+ />
23
+ <video
24
+ v-if="item.fileType === FILE_TYPE_VIDEO"
25
+ :id="'video_' + item.fileId"
26
+ :muted="true"
27
+ :src="item.fileUrl"
28
+ :controls="false"
29
+ :show-progress="false"
30
+ :show-fullscreen-btn="false"
31
+ :show-play-btn="false"
32
+ :show-center-play-btn="false"
33
+ :show-loading="false"
34
+ :enable-progress-gesture="false"
35
+ object-fit="fill"
36
+ :class="[$style['Uploader--image'], $style['Uploader--video']]"
37
+ @click.stop.prevent="previewVideo(item)"
38
+ @fullscreenchange="stopPreviewVideo"
39
+ ></video>
40
+ <view v-if="item.uploadStatus === 'error'" :class="$style['Uploader--error']">
41
+ <view>上传</view>
42
+ <view>失败</view>
43
+ </view>
44
+ <view v-else-if="item.uploadStatus === 'uploading'" :class="$style['Uploader--uploading']">
45
+ <icon-cpn icon="loading" :class="$style['Uploader--icon-loading']" />
46
+ 上传中
47
+ </view>
48
+ </view>
49
+ <view
50
+ v-if="!computedReadonly && (max <= 0 || currentValue.length < max)"
51
+ :class="$style['Uploader--file']"
52
+ @click="beforeChoose"
53
+ >
54
+ <icon-cpn icon="add-photo" :class="$style['Uploader--uploader-icon']" />
55
+ <view :class="$style['Uploader--uploader-text']">
56
+ <slot>添加图片</slot>
57
+ </view>
58
+ </view>
59
+ </template>
60
+ <template v-else>
61
+ <button-cpn
62
+ v-if="!computedReadonly && (max <= 0 || currentValue.length < max)"
63
+ plain
64
+ :class="$style['Uploader--uploader']"
65
+ @click="beforeChoose"
66
+ >
67
+ <icon-cpn icon="plus-circle" />
68
+ 上传附件
69
+ </button-cpn>
70
+ <!-- 附件列表 -->
71
+ <view v-for="(item, index) in currentValue" :key="index">
72
+ <view :class="$style['Uploader--file']" @click="previewFile(item)">
73
+ <icon-cpn icon="attachment" :class="$style['Uploader--icon-attachment']" />
74
+ <view :class="$style['Uploader--file-name']">{{ item.fileName || item.name }}</view>
75
+ <icon-cpn
76
+ v-if="!computedReadonly && item.uploadStatus !== 'uploading'"
77
+ icon="close-circle-filled"
78
+ expand-tap-area
79
+ :class="$style['Uploader--icon-close']"
80
+ @click.native.stop="deleteFile(item)"
81
+ />
82
+ <view v-else-if="item.uploadStatus === 'uploading'" :class="$style['Uploader--uploading']">
83
+ <icon-cpn icon="loading" />
84
+ </view>
85
+ </view>
86
+ <view v-if="item.uploadStatus === 'error'" :class="$style['Uploader--error']">上传失败</view>
87
+ </view>
88
+ </template>
89
+
90
+ <popup-cpn v-if="isSelectTypeVisible" ref="popup" v-model="isSelectTypeVisible" :confirm="false">
91
+ <button-cpn :class="$style['Uploader--button']" @click="choose('file')">从聊天记录选择文件</button-cpn>
92
+ <button-cpn :class="$style['Uploader--button']" @click="choose()">
93
+ 拍摄或从相册选择{{ mediaTypeName }}
94
+ </button-cpn>
95
+ </popup-cpn>
96
+
97
+ <dialog-cpn v-if="isShowVideoPreview" v-model="isShowVideoPreview">
98
+ <template #custom>
99
+ <view :class="$style['Uploader--preview-video-wrapper']">
100
+ <video
101
+ :src="previewVideoUrl"
102
+ :autoplay="true"
103
+ :muted="true"
104
+ show-mute-btn
105
+ :class="$style['Uploader--preview-video']"
106
+ ></video>
107
+ <icon-cpn
108
+ icon="clear"
109
+ :class="$style['Uploader--preview-video-close']"
110
+ @click="isShowVideoPreview = false"
111
+ ></icon-cpn>
112
+ </view>
113
+ </template>
114
+ </dialog-cpn>
115
+ </view>
116
+ </template>
117
+
118
+ <script>
119
+ import componentName from '../../mixins/componentName';
120
+ import ButtonCpn from '../button/button.vue';
121
+ import DialogCpn from '../dialog/dialog.vue';
122
+ import IconCpn from '../icon/icon.vue';
123
+ import PopupCpn from '../popup/popup.vue';
124
+ import {
125
+ FILE_TYPE_IMAGE,
126
+ FILE_TYPE_VIDEO,
127
+ FILE_TYPE_FILE,
128
+ typeStringValidator,
129
+ typeStringToArr,
130
+ chooseFile,
131
+ isImage,
132
+ isVideo,
133
+ } from './files';
134
+ import { openDocument } from './uni';
135
+
136
+ export default {
137
+ name: 'Uploader',
138
+ options: {
139
+ virtualHost: true,
140
+ styleIsolation: 'shared',
141
+ },
142
+ components: { ButtonCpn, DialogCpn, IconCpn, PopupCpn },
143
+ mixins: [componentName],
144
+ inject: {
145
+ injectReadonly: {
146
+ from: 'readonly',
147
+ default: false,
148
+ },
149
+ },
150
+ props: {
151
+ /** 绑定值 */
152
+ value: {
153
+ type: Array,
154
+ default: () => [],
155
+ },
156
+ /** 最大上传数量 */
157
+ max: {
158
+ type: Number,
159
+ default: 9,
160
+ },
161
+ /** 是否使用卡片样式,不设置的话,如果 type 不包含文件类型,也会使用卡片样式 */
162
+ card: {
163
+ type: Boolean,
164
+ default: null,
165
+ },
166
+ /** 上传文件类型 */
167
+ type: {
168
+ type: String,
169
+ validator: typeStringValidator,
170
+ default: 'all',
171
+ },
172
+ /** 选择文件时限制文件类型,仅 type 为 file 或 all 时有效 */
173
+ selectExtension: {
174
+ type: String,
175
+ default: '',
176
+ },
177
+ /** 选中文件后文件类型限制 */
178
+ allowedExtension: {
179
+ type: String,
180
+ default: '',
181
+ },
182
+ /** 是否只读 */
183
+ readonly: {
184
+ type: Boolean,
185
+ default: false,
186
+ },
187
+ },
188
+ data() {
189
+ return {
190
+ FILE_TYPE_IMAGE,
191
+ FILE_TYPE_VIDEO,
192
+ /** 是否显示选择文件类型弹窗 */
193
+ isSelectTypeVisible: false,
194
+ isShowVideoPreview: false,
195
+ previewVideoUrl: '',
196
+ };
197
+ },
198
+ computed: {
199
+ currentValue: {
200
+ get: function () {
201
+ return this.value === null ? this.innerValue : this.value;
202
+ },
203
+ set: function (value) {
204
+ this.$emit('input', value);
205
+ this.innerValue = value;
206
+ this.$emit('change', value);
207
+ },
208
+ },
209
+ computedReadonly() {
210
+ return this.readonly || this.injectReadonly;
211
+ },
212
+ /** 上传文件类型数组 */
213
+ typeList() {
214
+ return typeStringToArr(this.type);
215
+ },
216
+ /** 是否可以选择图片类型 */
217
+ isImageType() {
218
+ return this.typeList.includes(FILE_TYPE_IMAGE);
219
+ },
220
+ /** 是否可以选择视频类型 */
221
+ isVideoType() {
222
+ return this.typeList.includes(FILE_TYPE_VIDEO);
223
+ },
224
+ /** 是否可以选择文件类型 */
225
+ isFileType() {
226
+ return this.typeList.includes(FILE_TYPE_FILE);
227
+ },
228
+ /** 是否使用卡片样式 */
229
+ isCardView() {
230
+ return typeof this.card === 'boolean' ? this.card : this.isFileType ? false : true;
231
+ },
232
+ /** 媒体文件类型 */
233
+ mediaType() {
234
+ return [...(this.isImageType ? [FILE_TYPE_IMAGE] : []), ...(this.isVideoType ? [FILE_TYPE_VIDEO] : [])];
235
+ },
236
+ /** 媒体文件类型中文文本 */
237
+ mediaTypeName() {
238
+ return [...(this.isImageType ? ['图片'] : []), ...(this.isVideoType ? ['视频'] : [])].join('、');
239
+ },
240
+ /** 选择文件时过滤的文件后缀数组 */
241
+ selectExtensionList() {
242
+ return this.selectExtension ? this.selectExtension.split(',') : undefined;
243
+ },
244
+ /** 选择文件后过滤的文件后缀数组 */
245
+ allowedExtensionList() {
246
+ return this.allowedExtension ? this.allowedExtension.split(',') : undefined;
247
+ },
248
+ /** 图片预览列表 */
249
+ previewImageList() {
250
+ return this.currentValue
251
+ .filter((item) => item.fileType === FILE_TYPE_IMAGE)
252
+ .map((item) => item.path || item.fileUrl);
253
+ },
254
+ },
255
+ created() {
256
+ // 处理初始值
257
+ this.currentValue = (this.value || []).map((item) => {
258
+ item = { ...item, filePreviewUrl: item.filePreviewUrl || item.fileUrl };
259
+ if (!item.fileType) {
260
+ item.fileType = isImage(item.fileExt || item.fileName || item.fileUrl.split('?').shift())
261
+ ? FILE_TYPE_IMAGE
262
+ : isVideo(item.fileExt || item.fileName || item.fileUrl.split('?').shift())
263
+ ? FILE_TYPE_VIDEO
264
+ : FILE_TYPE_FILE;
265
+ }
266
+ return item;
267
+ });
268
+ },
269
+ methods: {
270
+ /** 选择前判断文件类型 */
271
+ async beforeChoose() {
272
+ // 包含选择文件,且包含图片或视频,需要提示用户选择
273
+ if (this.isFileType && this.mediaType.length > 0) {
274
+ this.isSelectTypeVisible = true;
275
+ return;
276
+ }
277
+
278
+ this.choose(this.isFileType ? 'file' : 'media');
279
+ },
280
+ /** 选择文件 */
281
+ async choose(type = 'media') {
282
+ this.$refs.popup?.close();
283
+
284
+ if (type === 'file') {
285
+ type = ['file'];
286
+ } else {
287
+ type = this.mediaType;
288
+ }
289
+
290
+ const { files, invalidFiles } = await chooseFile({
291
+ max: this.max <= 0 ? 9 : this.max - this.currentValue.length,
292
+ type: type,
293
+ selectExtension: this.selectExtensionList,
294
+ allowedExtension: this.allowedExtensionList,
295
+ });
296
+ if (invalidFiles.length > 0) {
297
+ this.$toast('已剔除不符合格式的文件');
298
+ }
299
+ this.currentValue = this.currentValue.concat(files.map((item) => ({ ...item, uploadStatus: 'uploading' })));
300
+ if (this.max > 0 && this.currentValue.length >= this.max) {
301
+ this.$toast(`最多只能上传${this.max}个文件`);
302
+ this.currentValue = this.currentValue.slice(0, this.max);
303
+ }
304
+ this.upload(files);
305
+ },
306
+ /** 上传文件 */
307
+ upload(files) {
308
+ files.map(async (file) => {
309
+ try {
310
+ const res = null;
311
+ // const res = await httpUpload({ url: apis.common.uploadFile.url, file });
312
+ const index = this.currentValue.findIndex((item) => item.path === file.path);
313
+ if (index < 0) {
314
+ return;
315
+ }
316
+ const currentValue = [...this.currentValue];
317
+ currentValue.splice(index, 1, {
318
+ fileId: res.fileId,
319
+ fileName: res.fileName,
320
+ filePreviewUrl: res.filePreviewUrl,
321
+ fileUrl: res.fileUrl,
322
+ fileType: file.type,
323
+ });
324
+ this.currentValue = currentValue;
325
+ return this.currentValue[index];
326
+ } catch {
327
+ const index = this.currentValue.findIndex((item) => item.path === file.path);
328
+ if (index < 0) {
329
+ return;
330
+ }
331
+ const currentValue = [...this.currentValue];
332
+ currentValue.splice(index, 1, { ...file, uploadStatus: 'error' });
333
+ this.currentValue = currentValue;
334
+ }
335
+ });
336
+ },
337
+ /** 删除文件 */
338
+ deleteFile(file) {
339
+ const index = this.currentValue.findIndex((item) => file === item);
340
+ if (index >= 0) {
341
+ const currentValue = [...this.currentValue];
342
+ currentValue.splice(index, 1);
343
+ this.currentValue = currentValue;
344
+ }
345
+ },
346
+ /**
347
+ * @param {UploadFile} file 文件
348
+ */
349
+ previewFile(file) {
350
+ if (file.fileType === FILE_TYPE_IMAGE) {
351
+ this.previewImage(this.previewImageList.findIndex((item) => item === file.fileUrl));
352
+ }
353
+ if (file.fileType === FILE_TYPE_FILE) {
354
+ openDocument(file.fileUrl, file.fileName).catch((err) => {
355
+ this.$toast(err);
356
+ });
357
+ }
358
+ },
359
+ /** 预览图片 */
360
+ previewImage(index) {
361
+ uni.previewImage({
362
+ current: index,
363
+ urls: this.previewImageList,
364
+ });
365
+ },
366
+ /** 预览视频 */
367
+ previewVideo(file) {
368
+ this.isShowVideoPreview = true;
369
+ this.previewVideoUrl = file.fileUrl;
370
+ },
371
+ },
372
+ };
373
+ </script>
374
+
375
+ <style lang="scss" module>
376
+ @use '../../styles/index' as *;
377
+
378
+ :host {
379
+ display: block;
380
+ }
381
+ :host(.display-inline) {
382
+ display: inline;
383
+ }
384
+
385
+ .Uploader {
386
+ width: 100%;
387
+ position: relative;
388
+ }
389
+
390
+ .Uploader--card {
391
+ display: flex;
392
+ flex-wrap: wrap;
393
+ text-align: center;
394
+ margin: 0 calc(-1 * #{get-value('size-l')}) calc(-1 * #{get-value('size-l')}) 0;
395
+
396
+ .Uploader--file {
397
+ position: relative;
398
+ width: 160rpx;
399
+ height: 160rpx;
400
+ margin: 0 get-value('size-l') get-value('size-l') 0;
401
+ background: get-value('color-background');
402
+ border-radius: get-value('size-radius-xxs');
403
+ color: get-value('color-info');
404
+ font-size: get-value('size-font-s');
405
+ display: flex;
406
+ flex-direction: column;
407
+ justify-content: center;
408
+ align-items: center;
409
+ z-index: 0;
410
+ }
411
+ .Uploader--image {
412
+ width: 100%;
413
+ height: 100%;
414
+ border-radius: get-value('size-radius-xxs');
415
+ }
416
+ // .Uploader--icon-close {
417
+ // position: absolute;
418
+ // top: 0;
419
+ // right: 0;
420
+ // transform: translate(30%, -30%);
421
+ // color: get-value('color-danger');
422
+ // font-size: get-value('size-font-l');
423
+ // z-index: 1;
424
+ // }
425
+ .Uploader--icon-close {
426
+ position: absolute;
427
+ top: 0;
428
+ right: 0;
429
+ z-index: 1;
430
+ transform: translate(25%, -25%);
431
+ width: get-value('size-xxxl');
432
+ height: get-value('size-xxxl');
433
+ background: red;
434
+ border-radius: 50%;
435
+ &:before,
436
+ &:after {
437
+ content: '';
438
+ position: absolute;
439
+ top: 50%;
440
+ left: 50%;
441
+ height: 70%;
442
+ width: 10%;
443
+ background: #fff;
444
+ }
445
+ &:before {
446
+ transform: translate(-50%, -50%) rotate(45deg);
447
+ }
448
+ &:after {
449
+ transform: translate(-50%, -50%) rotate(-45deg);
450
+ }
451
+ }
452
+ .Uploader--uploader-icon {
453
+ font-size: 48rpx;
454
+ color: get-value('color-info-6');
455
+ }
456
+ .Uploader--uploading {
457
+ position: absolute;
458
+ top: 50%;
459
+ left: 50%;
460
+ transform: translate(-50%, -50%);
461
+ display: flex;
462
+ flex-direction: column;
463
+ justify-content: center;
464
+ align-items: center;
465
+ }
466
+ .Uploader--icon-loading {
467
+ font-size: get-value('size-font-l');
468
+ }
469
+ .Uploader--error {
470
+ position: absolute;
471
+ top: 50%;
472
+ left: 50%;
473
+ width: 70%;
474
+ height: 70%;
475
+ transform: translate(-50%, -50%);
476
+ border-radius: 50%;
477
+ background: rgba(255, 255, 255, 0.75);
478
+ display: flex;
479
+ flex-direction: column;
480
+ justify-content: center;
481
+ align-items: center;
482
+ }
483
+ }
484
+
485
+ .Uploader--list {
486
+ .Uploader--uploader {
487
+ #{UTILS__VAR_NAME(height, button)}: 88rpx;
488
+ #{UTILS__VAR_NAME(border-style, button)}: dashed;
489
+ }
490
+ .Uploader--file {
491
+ position: relative;
492
+ margin-top: get-value('size-s');
493
+ width: 100%;
494
+ height: 88rpx;
495
+ padding: 0 get-value('size-s');
496
+ display: flex;
497
+ justify-content: start;
498
+ align-items: center;
499
+ background: get-value('color-background');
500
+ border-radius: get-value('size-radius-xxs');
501
+ }
502
+ .Uploader--file-name {
503
+ flex: 1;
504
+ @include UTILS__ELLIPSIS();
505
+ }
506
+ .Uploader--icon-attachment {
507
+ font-size: get-value('size-font-l');
508
+ color: get-value('color-primary');
509
+ margin-right: get-value('size-xxs');
510
+ }
511
+ .Uploader--uploading,
512
+ .Uploader--icon-close {
513
+ position: relative;
514
+ font-size: get-value('size-font-l');
515
+ color: get-value('color-info');
516
+ #{UTILS__VAR_NAME(tap-size, icon)}: 200rpx;
517
+ }
518
+ .Uploader--error {
519
+ color: get-value('color-danger');
520
+ font-size: get-value('size-font-s');
521
+ margin: get-value('size-xxs') 0 get-value('size-s') get-value('size-s');
522
+ }
523
+ .Uploader--button {
524
+ margin-bottom: get-value('size-m');
525
+ }
526
+ }
527
+
528
+ .Uploader--preview-video-wrapper {
529
+ width: 80vw;
530
+ height: 75vh;
531
+ display: flex;
532
+ justify-content: center;
533
+ align-items: center;
534
+ }
535
+ .Uploader--preview-video {
536
+ width: 100%;
537
+ height: 100%;
538
+ }
539
+ .Uploader--preview-video-close {
540
+ position: absolute;
541
+ color: get-value('color-white');
542
+ bottom: -20rpx;
543
+ left: 50%;
544
+ transform: translate(-50%, 100%);
545
+ font-size: 80rpx;
546
+ }
547
+ </style>
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <view :class="componentClassNames">
3
+ <view v-if="card" :class="$style['Uploader--card-list']" :style="{ '--columns': columns }">
4
+ <!-- 已上传文件列表 -->
5
+ <aspect-cpn v-for="img in value" :key="img" :class="$style['Uploader--card-item']" :style="styleWidth">
6
+ <image-cpn
7
+ :src="img"
8
+ :preview-list="value"
9
+ :class="$style['Uploader--card-image']"
10
+ :style="styleSize"
11
+ mode="aspectFill"
12
+ >
13
+ </image-cpn>
14
+ </aspect-cpn>
15
+ <!-- 上传按钮 -->
16
+ <aspect-cpn :class="$style['Uploader--card-item']" :style="styleWidth">
17
+ <button-cpn plain :class="$style['Uploader--card-uploader']">
18
+ <view style="line-height: 1.3em">
19
+ <view>上传</view>
20
+ <view>图片</view>
21
+ </view>
22
+ </button-cpn>
23
+ </aspect-cpn>
24
+ </view>
25
+ <view v-else :class="$style['Uploader--list-view']">
26
+ <view>
27
+ <button-cpn>上传</button-cpn>
28
+ </view>
29
+ <view>
30
+ <view v-for="file in value" :key="file" :class="$style['Uploader--file-item']">{{ file }}</view>
31
+ </view>
32
+ </view>
33
+ </view>
34
+ </template>
35
+
36
+ <script>
37
+ import componentName from '../../mixins/componentName';
38
+ import AspectCpn from '../aspect/aspect.vue';
39
+ import ButtonCpn from '../button/button.vue';
40
+ import ImageCpn from '../image/image.vue';
41
+
42
+ export default {
43
+ name: 'AppUploader',
44
+ options: {
45
+ virtualHost: true,
46
+ styleIsolation: 'shared',
47
+ },
48
+ components: { AspectCpn, ButtonCpn, ImageCpn },
49
+ mixins: [componentName],
50
+ props: {
51
+ /** 文件列表 */
52
+ value: {
53
+ type: Array,
54
+ default: () => [],
55
+ },
56
+ /** 是否使用卡片样式,不设置的话,如果 type 不包含文件类型,也会使用卡片样式 */
57
+ card: {
58
+ type: Boolean,
59
+ default: false,
60
+ },
61
+ /** 显示宽度 */
62
+ columns: {
63
+ type: [String, Number],
64
+ default: 4,
65
+ },
66
+ /** 显示宽度 */
67
+ width: {
68
+ type: [String, Number],
69
+ default: '',
70
+ },
71
+ },
72
+ computed: {
73
+ styleWidth() {
74
+ if (!this.width) return '';
75
+ const width = typeof this.width === 'number' ? `${this.width}rpx` : this.width;
76
+ return `width: ${width};`;
77
+ },
78
+ },
79
+ methods: {},
80
+ };
81
+ </script>
82
+
83
+ <style lang="scss" module>
84
+ .Uploader {
85
+ width: 100%;
86
+ }
87
+
88
+ .Uploader--card-uploader {
89
+ height: 100%;
90
+ border-color: #ccc;
91
+ border-radius: 16rpx;
92
+ }
93
+ .Uploader--card-list {
94
+ display: flex;
95
+ flex-wrap: wrap;
96
+ gap: 12rpx;
97
+ }
98
+ .Uploader--card-item {
99
+ width: calc(100% / var(--columns) - 12rpx * (var(--columns) - 1) / var(--columns));
100
+ }
101
+ .Uploader--card-image {
102
+ border-radius: 16rpx;
103
+ }
104
+ .Uploader--list-view {
105
+ width: 100%;
106
+ }
107
+ .Uploader--file-item {
108
+ &:first-child {
109
+ margin-top: 20rpx;
110
+ }
111
+ padding: 15rpx 0;
112
+ font-size: 28rpx;
113
+ white-space: nowrap;
114
+ overflow: hidden;
115
+ text-overflow: ellipsis;
116
+ }
117
+ </style>
@@ -0,0 +1,41 @@
1
+ /** 组件前缀 */
2
+ let componentPrefix = 'app';
3
+
4
+ /** 设置组件前缀 */
5
+ function setComponentPrefix(prefix) {
6
+ componentPrefix = prefix;
7
+ }
8
+
9
+ /** iconfont 图标 map */
10
+ const iconMap = new Map();
11
+
12
+ /** 添加单个图标 */
13
+ function addIconToMap(fontFamily, prefix, name) {
14
+ if (iconMap.has(name)) {
15
+ console.error(
16
+ `图标库 "${fontFamily}" 中的图标 "${name}" 在图标库 "${
17
+ iconMap.get(name).fontFamily
18
+ }" 中已存在,这个图标将被忽略`
19
+ );
20
+ return;
21
+ }
22
+ iconMap.set(name, {
23
+ fontFamily,
24
+ prefix,
25
+ name,
26
+ });
27
+ }
28
+
29
+ /** 添加一组图标,传入 iconfont.json 文件内容 */
30
+ function addIcons(iconJson) {
31
+ const fontFamily = iconJson.font_family;
32
+ const prefix = iconJson.css_prefix_text;
33
+ const glyphs = iconJson.glyphs;
34
+ glyphs.forEach((glyph) => {
35
+ addIconToMap(fontFamily, prefix, glyph.font_class);
36
+ });
37
+ }
38
+
39
+ addIcons(require('../resources/iconfont/uniapp_ui_icon/iconfont.json'));
40
+
41
+ export { componentPrefix, setComponentPrefix, iconMap, addIcons };