@yxhl/specter-pui-vtk 1.0.102 → 1.0.103
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.
- package/dist/specter-pui-vtk.css +1 -1
- package/dist/specter-pui.es.js +3997 -3714
- package/dist/specter-pui.es.js.map +1 -1
- package/dist/specter-pui.umd.js +1 -1
- package/dist/specter-pui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/assembly/VtkImg.vue +366 -142
- package/src/components/assembly/VtkUpload.vue +237 -114
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
:key="file.uid"
|
|
27
27
|
class="vtk-upload__picture-card-item"
|
|
28
28
|
>
|
|
29
|
-
<v-img :key="file.url || file.preview || file.uid" :src="file.url || file.preview" cover class="fill-height" />
|
|
29
|
+
<v-img :key="file.url || file.preview || file.uid" :src="file.url || file.preview" cover class="fill-height" />
|
|
30
30
|
<div class="vtk-upload__picture-card-mask">
|
|
31
31
|
<VBtn icon size="x-small" variant="text" color="white" title="查看" @click.stop="handlePreview(file)">
|
|
32
32
|
<VIcon>mdi-eye</VIcon>
|
|
@@ -217,6 +217,11 @@ const props = defineProps({
|
|
|
217
217
|
type: Number,
|
|
218
218
|
default: 0,
|
|
219
219
|
},
|
|
220
|
+
/** 单张图片最大像素(宽*高),超过则拒绝上传;传 0 表示不限制 */
|
|
221
|
+
maxPixels: {
|
|
222
|
+
type: Number,
|
|
223
|
+
default: 50_000_000,
|
|
224
|
+
},
|
|
220
225
|
/** 列表类型:text | picture | picture-card */
|
|
221
226
|
listType: {
|
|
222
227
|
type: String,
|
|
@@ -297,10 +302,10 @@ const previewTranslateX = ref(0);
|
|
|
297
302
|
const previewTranslateY = ref(0);
|
|
298
303
|
const previewDragState = ref(null);
|
|
299
304
|
|
|
300
|
-
// 内部维护文件列表
|
|
301
|
-
const fileList = ref([]);
|
|
302
|
-
let isSyncingModel = false;
|
|
303
|
-
const localPreviewUrls = new Set();
|
|
305
|
+
// 内部维护文件列表
|
|
306
|
+
const fileList = ref([]);
|
|
307
|
+
let isSyncingModel = false;
|
|
308
|
+
const localPreviewUrls = new Set();
|
|
304
309
|
|
|
305
310
|
watch(
|
|
306
311
|
() => props.modelValue,
|
|
@@ -310,47 +315,139 @@ watch(
|
|
|
310
315
|
return;
|
|
311
316
|
}
|
|
312
317
|
|
|
313
|
-
// Accept external URL lists or file object lists.
|
|
314
|
-
revokeAllLocalPreviews();
|
|
315
|
-
fileList.value = normalizeFileList(val || []);
|
|
316
|
-
},
|
|
317
|
-
);
|
|
318
|
+
// Accept external URL lists or file object lists.
|
|
319
|
+
revokeAllLocalPreviews();
|
|
320
|
+
fileList.value = normalizeFileList(val || []);
|
|
321
|
+
},
|
|
322
|
+
);
|
|
318
323
|
|
|
319
324
|
/* -------------------- 工具函数 -------------------- */
|
|
320
325
|
let uidCounter = 0;
|
|
321
326
|
const genUid = () => `vtk-upload-${Date.now()}-${uidCounter++}`;
|
|
322
327
|
|
|
323
|
-
const getFileNameFromUrl = (url) => {
|
|
324
|
-
const cleanUrl = String(url || '').split('?')[0].split('#')[0];
|
|
325
|
-
const name = cleanUrl.substring(cleanUrl.lastIndexOf('/') + 1);
|
|
326
|
-
try {
|
|
327
|
-
return decodeURIComponent(name || cleanUrl || 'file');
|
|
328
|
+
const getFileNameFromUrl = (url) => {
|
|
329
|
+
const cleanUrl = String(url || '').split('?')[0].split('#')[0];
|
|
330
|
+
const name = cleanUrl.substring(cleanUrl.lastIndexOf('/') + 1);
|
|
331
|
+
try {
|
|
332
|
+
return decodeURIComponent(name || cleanUrl || 'file');
|
|
328
333
|
} catch (e) {
|
|
329
334
|
return name || cleanUrl || 'file';
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const isImageUrl = (url) => {
|
|
334
|
-
const value = String(url || '');
|
|
335
|
-
const cleanUrl = value.split('?')[0].split('#')[0];
|
|
336
|
-
return /^data:image\//i.test(value) || /\.(png|jpg|jpeg|gif|bmp|webp|svg)$/i.test(cleanUrl);
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
if (!
|
|
341
|
-
|
|
342
|
-
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const isImageUrl = (url) => {
|
|
339
|
+
const value = String(url || '');
|
|
340
|
+
const cleanUrl = value.split('?')[0].split('#')[0];
|
|
341
|
+
return /^data:image\//i.test(value) || /\.(png|jpg|jpeg|gif|bmp|webp|svg)$/i.test(cleanUrl);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const isImageLike = (raw) => {
|
|
345
|
+
if (!raw) return false;
|
|
346
|
+
return /^image\//.test(raw.type || '') || isImageUrl(raw.name || '');
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const readUInt16BE = (bytes, offset) => (bytes[offset] << 8) | bytes[offset + 1];
|
|
350
|
+
const readUInt16LE = (bytes, offset) => bytes[offset] | (bytes[offset + 1] << 8);
|
|
351
|
+
const readUInt32BE = (bytes, offset) => (
|
|
352
|
+
((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]) >>> 0
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// 按字节解析图片头部宽高(PNG / JPEG / GIF / WEBP),禁止整图解码
|
|
356
|
+
const parseDimensions = (bytes) => {
|
|
357
|
+
const length = bytes.length;
|
|
358
|
+
|
|
359
|
+
// PNG:魔数 89 50 4E 47,IHDR 后宽 16 / 高 20(大端 uint32)
|
|
360
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
|
|
361
|
+
return { w: readUInt32BE(bytes, 16), h: readUInt32BE(bytes, 20) };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// GIF:魔数 GIF8,宽 6-7 / 高 8-9(小端 uint16)
|
|
365
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38) {
|
|
366
|
+
return { w: readUInt16LE(bytes, 6), h: readUInt16LE(bytes, 8) };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// JPEG:扫 SOF 段(0xC0–0xCF,排除 C4/C8/CC),高在段起点 +5、宽在 +7(大端 uint16)
|
|
370
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
371
|
+
const sofMarkers = new Set([
|
|
372
|
+
0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
|
|
373
|
+
]);
|
|
374
|
+
let offset = 2;
|
|
375
|
+
while (offset + 9 < length) {
|
|
376
|
+
if (bytes[offset] !== 0xff) { offset += 1; continue; }
|
|
377
|
+
const segmentStart = offset;
|
|
378
|
+
let p = offset;
|
|
379
|
+
while (p < length && bytes[p] === 0xff) p += 1;
|
|
380
|
+
if (p >= length - 1) break;
|
|
381
|
+
const marker = bytes[p];
|
|
382
|
+
if (marker === 0x00 || marker === 0xd8 || marker === 0xd9) { offset = p + 1; continue; }
|
|
383
|
+
if (sofMarkers.has(marker)) {
|
|
384
|
+
return { w: readUInt16BE(bytes, segmentStart + 7), h: readUInt16BE(bytes, segmentStart + 5) };
|
|
385
|
+
}
|
|
386
|
+
if (p + 2 >= length) break;
|
|
387
|
+
const segLength = readUInt16BE(bytes, p + 2);
|
|
388
|
+
if (segLength < 2) break;
|
|
389
|
+
offset = p + 2 + segLength;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// WEBP:RIFF....WEBP
|
|
394
|
+
if (
|
|
395
|
+
bytes.length >= 30
|
|
396
|
+
&& bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46
|
|
397
|
+
&& bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50
|
|
398
|
+
) {
|
|
399
|
+
const fourcc = String.fromCharCode(bytes[12], bytes[13], bytes[14], bytes[15]);
|
|
400
|
+
if (fourcc === 'VP8X') {
|
|
401
|
+
return {
|
|
402
|
+
w: 1 + ((bytes[24]) | (bytes[25] << 8) | (bytes[26] << 16)),
|
|
403
|
+
h: 1 + ((bytes[27]) | (bytes[28] << 8) | (bytes[29] << 16)),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (fourcc === 'VP8 ') {
|
|
407
|
+
return {
|
|
408
|
+
w: bytes[26] | ((bytes[27] & 0x3f) << 8),
|
|
409
|
+
h: bytes[28] | ((bytes[29] & 0x3f) << 8),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
if (fourcc === 'VP8L' && length >= 25) {
|
|
413
|
+
return {
|
|
414
|
+
w: 1 + (((bytes[22] & 0x3f) << 8) | bytes[21]),
|
|
415
|
+
h: 1 + (((bytes[24] & 0x0f) << 8) | bytes[23]),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return { w: 0, h: 0 };
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// 只读本地文件头部 8KB 拿尺寸;解析失败返回 null(不阻塞普通图片上传)
|
|
424
|
+
const parseImageSizeFromFile = async (raw) => {
|
|
425
|
+
if (!(raw instanceof Blob)) return null;
|
|
426
|
+
try {
|
|
427
|
+
const buffer = await raw.slice(0, 8192).arrayBuffer();
|
|
428
|
+
const { w, h } = parseDimensions(new Uint8Array(buffer));
|
|
429
|
+
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return null;
|
|
430
|
+
return { w, h };
|
|
431
|
+
} catch (error) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const normalizeFileList = (value) => {
|
|
437
|
+
if (!Array.isArray(value)) return [];
|
|
438
|
+
|
|
439
|
+
return value
|
|
343
440
|
.filter((item) => item)
|
|
344
441
|
.map((item) => {
|
|
345
442
|
if (typeof item === 'string') {
|
|
346
443
|
return {
|
|
347
|
-
uid: genUid(),
|
|
348
|
-
name: getFileNameFromUrl(item),
|
|
349
|
-
size: 0,
|
|
350
|
-
type: props.listType === 'picture-card' || isImageUrl(item) ? 'image/*' : '',
|
|
351
|
-
status: 'success',
|
|
352
|
-
percentage: 100,
|
|
353
|
-
raw: null,
|
|
444
|
+
uid: genUid(),
|
|
445
|
+
name: getFileNameFromUrl(item),
|
|
446
|
+
size: 0,
|
|
447
|
+
type: props.listType === 'picture-card' || isImageUrl(item) ? 'image/*' : '',
|
|
448
|
+
status: 'success',
|
|
449
|
+
percentage: 100,
|
|
450
|
+
raw: null,
|
|
354
451
|
url: item,
|
|
355
452
|
preview: '',
|
|
356
453
|
response: null,
|
|
@@ -358,13 +455,13 @@ const normalizeFileList = (value) => {
|
|
|
358
455
|
}
|
|
359
456
|
|
|
360
457
|
return {
|
|
361
|
-
uid: item.uid || genUid(),
|
|
362
|
-
name: item.name || getFileNameFromUrl(item.url || item.preview),
|
|
363
|
-
size: item.size || 0,
|
|
364
|
-
type: item.type || (props.listType === 'picture-card' && (item.url || item.preview) ? 'image/*' : ''),
|
|
365
|
-
status: item.status || 'success',
|
|
366
|
-
percentage: item.percentage ?? 100,
|
|
367
|
-
raw: item.raw || null,
|
|
458
|
+
uid: item.uid || genUid(),
|
|
459
|
+
name: item.name || getFileNameFromUrl(item.url || item.preview),
|
|
460
|
+
size: item.size || 0,
|
|
461
|
+
type: item.type || (props.listType === 'picture-card' && (item.url || item.preview) ? 'image/*' : ''),
|
|
462
|
+
status: item.status || 'success',
|
|
463
|
+
percentage: item.percentage ?? 100,
|
|
464
|
+
raw: item.raw || null,
|
|
368
465
|
url: item.url || '',
|
|
369
466
|
preview: item.preview || '',
|
|
370
467
|
response: item.response || null,
|
|
@@ -375,10 +472,10 @@ const normalizeFileList = (value) => {
|
|
|
375
472
|
|
|
376
473
|
fileList.value = normalizeFileList(props.modelValue || []);
|
|
377
474
|
|
|
378
|
-
const isImage = (file) => {
|
|
379
|
-
if (!file) return false;
|
|
380
|
-
return /image\//.test(file.type) || isImageUrl(file.name) || isImageUrl(file.url) || isImageUrl(file.preview);
|
|
381
|
-
};
|
|
475
|
+
const isImage = (file) => {
|
|
476
|
+
if (!file) return false;
|
|
477
|
+
return /image\//.test(file.type) || isImageUrl(file.name) || isImageUrl(file.url) || isImageUrl(file.preview);
|
|
478
|
+
};
|
|
382
479
|
|
|
383
480
|
const fileIcon = (file) => {
|
|
384
481
|
if (file.status === 'error') return 'mdi-file-alert-outline';
|
|
@@ -386,21 +483,35 @@ const fileIcon = (file) => {
|
|
|
386
483
|
return 'mdi-file-outline';
|
|
387
484
|
};
|
|
388
485
|
|
|
389
|
-
const getPreviewSrc = (file) => file?.url || file?.preview || '';
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
};
|
|
486
|
+
const getPreviewSrc = (file) => file?.url || file?.preview || '';
|
|
487
|
+
|
|
488
|
+
// 拦截反馈:优先用全局 toast,找不到则降级 alert
|
|
489
|
+
const showBlockMessage = (message) => {
|
|
490
|
+
try {
|
|
491
|
+
const toast = window.$vtk?.message?.toast;
|
|
492
|
+
if (typeof toast === 'function') {
|
|
493
|
+
toast(message, { color: 'error' });
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.warn('VtkUpload: failed to show message toast', error);
|
|
498
|
+
}
|
|
499
|
+
window.alert?.(message);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const revokeLocalPreview = (file) => {
|
|
503
|
+
const preview = file?.preview;
|
|
504
|
+
if (preview && localPreviewUrls.has(preview)) {
|
|
505
|
+
URL.revokeObjectURL(preview);
|
|
506
|
+
localPreviewUrls.delete(preview);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const revokeAllLocalPreviews = () => {
|
|
511
|
+
fileList.value.forEach(revokeLocalPreview);
|
|
512
|
+
localPreviewUrls.forEach((url) => URL.revokeObjectURL(url));
|
|
513
|
+
localPreviewUrls.clear();
|
|
514
|
+
};
|
|
404
515
|
|
|
405
516
|
const previewImageList = computed(() => fileList.value.filter((file) => isImage(file) && getPreviewSrc(file)));
|
|
406
517
|
|
|
@@ -450,7 +561,7 @@ const processFiles = (rawFiles) => {
|
|
|
450
561
|
return;
|
|
451
562
|
}
|
|
452
563
|
|
|
453
|
-
rawFiles.forEach((raw) => {
|
|
564
|
+
rawFiles.forEach(async (raw) => {
|
|
454
565
|
// 体积检查
|
|
455
566
|
if (props.maxSize > 0 && raw.size / 1024 / 1024 > props.maxSize) {
|
|
456
567
|
const errFile = buildFile(raw, 'error');
|
|
@@ -458,22 +569,34 @@ const processFiles = (rawFiles) => {
|
|
|
458
569
|
return;
|
|
459
570
|
}
|
|
460
571
|
|
|
572
|
+
// 分辨率检查:只读图片头部字节,超阈值则拒绝
|
|
573
|
+
if (props.maxPixels > 0 && isImageLike(raw)) {
|
|
574
|
+
const dims = await parseImageSizeFromFile(raw);
|
|
575
|
+
if (dims && dims.w * dims.h > props.maxPixels) {
|
|
576
|
+
const errFile = buildFile(raw, 'error');
|
|
577
|
+
const message = `图片 ${raw.name} 分辨率(宽*高)超出允许限制`;
|
|
578
|
+
showBlockMessage(message);
|
|
579
|
+
emit('error', new Error(message), errFile, fileList.value);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
461
584
|
const file = buildFile(raw, 'ready');
|
|
462
585
|
|
|
463
|
-
// 生成本地预览地址,保证 picture-card 选图后立即回显缩略图。
|
|
464
|
-
if (isImage(raw)) {
|
|
465
|
-
if (typeof URL !== 'undefined' && URL.createObjectURL) {
|
|
466
|
-
file.preview = URL.createObjectURL(raw);
|
|
467
|
-
localPreviewUrls.add(file.preview);
|
|
468
|
-
} else {
|
|
469
|
-
const reader = new FileReader();
|
|
470
|
-
reader.onload = (e) => {
|
|
471
|
-
file.preview = e.target.result;
|
|
472
|
-
emit('change', file, fileList.value);
|
|
473
|
-
};
|
|
474
|
-
reader.readAsDataURL(raw);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
586
|
+
// 生成本地预览地址,保证 picture-card 选图后立即回显缩略图。
|
|
587
|
+
if (isImage(raw)) {
|
|
588
|
+
if (typeof URL !== 'undefined' && URL.createObjectURL) {
|
|
589
|
+
file.preview = URL.createObjectURL(raw);
|
|
590
|
+
localPreviewUrls.add(file.preview);
|
|
591
|
+
} else {
|
|
592
|
+
const reader = new FileReader();
|
|
593
|
+
reader.onload = (e) => {
|
|
594
|
+
file.preview = e.target.result;
|
|
595
|
+
emit('change', file, fileList.value);
|
|
596
|
+
};
|
|
597
|
+
reader.readAsDataURL(raw);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
477
600
|
|
|
478
601
|
const beforeHook = props.beforeUpload ? props.beforeUpload(raw) : true;
|
|
479
602
|
Promise.resolve(beforeHook).then((result) => {
|
|
@@ -536,12 +659,12 @@ const uploadFile = (file) => {
|
|
|
536
659
|
Request.http(props.action, formData, 'POST', headers).then((res) => {
|
|
537
660
|
file.status = 'success';
|
|
538
661
|
file.response = res;
|
|
539
|
-
// 兼容 { data: 'url' } 和 { data: { url: '...' } } 两种结构
|
|
540
|
-
const url = typeof res?.data === 'string' ? res.data : (res?.data?.url || res?.url || '');
|
|
541
|
-
if (url) {
|
|
542
|
-
file.url = url;
|
|
543
|
-
revokeLocalPreview(file);
|
|
544
|
-
}
|
|
662
|
+
// 兼容 { data: 'url' } 和 { data: { url: '...' } } 两种结构
|
|
663
|
+
const url = typeof res?.data === 'string' ? res.data : (res?.data?.url || res?.url || '');
|
|
664
|
+
if (url) {
|
|
665
|
+
file.url = url;
|
|
666
|
+
revokeLocalPreview(file);
|
|
667
|
+
}
|
|
545
668
|
console.log('[VtkUpload] res:', res, '| file.url:', file.url, '| fileList:', JSON.parse(JSON.stringify(fileList.value)));
|
|
546
669
|
syncModel();
|
|
547
670
|
emit('success', res, file, fileList.value);
|
|
@@ -556,11 +679,11 @@ const uploadFile = (file) => {
|
|
|
556
679
|
|
|
557
680
|
/* -------------------- 移除文件 -------------------- */
|
|
558
681
|
const handleRemove = (file) => {
|
|
559
|
-
const doRemove = () => {
|
|
560
|
-
revokeLocalPreview(file);
|
|
561
|
-
fileList.value = fileList.value.filter((f) => f.uid !== file.uid);
|
|
562
|
-
syncModel();
|
|
563
|
-
emit('remove', file, fileList.value);
|
|
682
|
+
const doRemove = () => {
|
|
683
|
+
revokeLocalPreview(file);
|
|
684
|
+
fileList.value = fileList.value.filter((f) => f.uid !== file.uid);
|
|
685
|
+
syncModel();
|
|
686
|
+
emit('remove', file, fileList.value);
|
|
564
687
|
};
|
|
565
688
|
|
|
566
689
|
if (props.beforeRemove) {
|
|
@@ -715,10 +838,10 @@ watch(previewVisible, (visible) => {
|
|
|
715
838
|
resetPreviewTransform();
|
|
716
839
|
});
|
|
717
840
|
|
|
718
|
-
onBeforeUnmount(() => {
|
|
719
|
-
window.removeEventListener('keydown', handlePreviewKeydown);
|
|
720
|
-
revokeAllLocalPreviews();
|
|
721
|
-
});
|
|
841
|
+
onBeforeUnmount(() => {
|
|
842
|
+
window.removeEventListener('keydown', handlePreviewKeydown);
|
|
843
|
+
revokeAllLocalPreviews();
|
|
844
|
+
});
|
|
722
845
|
|
|
723
846
|
/* -------------------- 对外暴露方法 -------------------- */
|
|
724
847
|
/** 手动触发未上传文件的上传 */
|
|
@@ -729,11 +852,11 @@ const submit = () => {
|
|
|
729
852
|
};
|
|
730
853
|
|
|
731
854
|
/** 清空文件列表 */
|
|
732
|
-
const clearFiles = () => {
|
|
733
|
-
revokeAllLocalPreviews();
|
|
734
|
-
fileList.value = [];
|
|
735
|
-
syncModel();
|
|
736
|
-
};
|
|
855
|
+
const clearFiles = () => {
|
|
856
|
+
revokeAllLocalPreviews();
|
|
857
|
+
fileList.value = [];
|
|
858
|
+
syncModel();
|
|
859
|
+
};
|
|
737
860
|
|
|
738
861
|
/** 手动移除某个文件 */
|
|
739
862
|
const remove = (file) => handleRemove(file);
|
|
@@ -892,24 +1015,24 @@ defineExpose({ submit, clearFiles, remove });
|
|
|
892
1015
|
position: relative;
|
|
893
1016
|
}
|
|
894
1017
|
|
|
895
|
-
.vtk-upload__preview-nav {
|
|
896
|
-
position: fixed;
|
|
897
|
-
top: 50%;
|
|
898
|
-
z-index: 10;
|
|
899
|
-
width: 50px;
|
|
900
|
-
height: 50px;
|
|
901
|
-
color: #fff;
|
|
902
|
-
background: rgba(0, 0, 0, 0.5);
|
|
903
|
-
transform: translateY(-50%);
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
.vtk-upload__preview-nav--prev {
|
|
907
|
-
left: 20px;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
.vtk-upload__preview-nav--next {
|
|
911
|
-
right: 20px;
|
|
912
|
-
}
|
|
1018
|
+
.vtk-upload__preview-nav {
|
|
1019
|
+
position: fixed;
|
|
1020
|
+
top: 50%;
|
|
1021
|
+
z-index: 10;
|
|
1022
|
+
width: 50px;
|
|
1023
|
+
height: 50px;
|
|
1024
|
+
color: #fff;
|
|
1025
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1026
|
+
transform: translateY(-50%);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.vtk-upload__preview-nav--prev {
|
|
1030
|
+
left: 20px;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.vtk-upload__preview-nav--next {
|
|
1034
|
+
right: 20px;
|
|
1035
|
+
}
|
|
913
1036
|
|
|
914
1037
|
.vtk-upload__preview-image-wrap {
|
|
915
1038
|
min-width: 100%;
|