@yxhl/specter-pui-vtk 1.0.82 → 1.0.83
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 +2479 -2414
- 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/VtkUpload.vue +137 -72
package/package.json
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
<div class="vtk-upload">
|
|
3
3
|
<!-- 拖拽 / 点击上传区域 -->
|
|
4
4
|
<div
|
|
5
|
-
v-if="listType !== 'picture-card'"
|
|
6
|
-
:class="['vtk-upload__dragger', { 'is-dragover': isDragover, 'is-disabled': disabled }]"
|
|
7
|
-
|
|
5
|
+
v-if="listType !== 'picture-card' && !detail"
|
|
6
|
+
:class="['vtk-upload__dragger', { 'is-dragover': isDragover, 'is-disabled': disabled }]"
|
|
7
|
+
title="点击上传"
|
|
8
|
+
@click="!disabled && triggerInput()"
|
|
8
9
|
@dragover.prevent="onDragover"
|
|
9
10
|
@dragleave.prevent="isDragover = false"
|
|
10
11
|
@drop.prevent="onDrop"
|
|
@@ -27,12 +28,12 @@
|
|
|
27
28
|
>
|
|
28
29
|
<v-img :src="file.url || file.preview" cover class="fill-height" />
|
|
29
30
|
<div class="vtk-upload__picture-card-mask">
|
|
30
|
-
<VBtn
|
|
31
|
-
<VIcon>mdi-eye</VIcon>
|
|
32
|
-
</VBtn>
|
|
33
|
-
<VBtn v-if="!disabled" icon size="x-small" variant="text" color="white" @click.stop="handleRemove(file)">
|
|
34
|
-
<VIcon>mdi-delete</VIcon>
|
|
35
|
-
</VBtn>
|
|
31
|
+
<VBtn icon size="x-small" variant="text" color="white" title="查看" @click.stop="handlePreview(file)">
|
|
32
|
+
<VIcon>mdi-eye</VIcon>
|
|
33
|
+
</VBtn>
|
|
34
|
+
<VBtn v-if="!disabled && !detail" icon size="x-small" variant="text" color="white" title="删除" @click.stop="handleRemove(file)">
|
|
35
|
+
<VIcon>mdi-delete</VIcon>
|
|
36
|
+
</VBtn>
|
|
36
37
|
</div>
|
|
37
38
|
<!-- 上传进度 -->
|
|
38
39
|
<div v-if="file.status === 'uploading'" class="vtk-upload__picture-card-progress">
|
|
@@ -42,9 +43,10 @@
|
|
|
42
43
|
|
|
43
44
|
<!-- 添加按钮 -->
|
|
44
45
|
<div
|
|
45
|
-
v-if="!disabled && (limit === 0 || fileList.length < limit)"
|
|
46
|
-
:class="['vtk-upload__picture-card-add', { 'is-dragover': isDragover }]"
|
|
47
|
-
|
|
46
|
+
v-if="!disabled && !detail && (limit === 0 || fileList.length < limit)"
|
|
47
|
+
:class="['vtk-upload__picture-card-add', { 'is-dragover': isDragover }]"
|
|
48
|
+
title="添加文件"
|
|
49
|
+
@click="triggerInput()"
|
|
48
50
|
@dragover.prevent="onDragover"
|
|
49
51
|
@dragleave.prevent="isDragover = false"
|
|
50
52
|
@drop.prevent="onDrop"
|
|
@@ -77,13 +79,14 @@
|
|
|
77
79
|
<span class="vtk-upload__list-item-name flex-grow-1 text-truncate" @click="handlePreview(file)" :title="file.name">{{ file.name }}</span>
|
|
78
80
|
<span v-if="file.status === 'uploading'" class="text-caption text-grey ml-2">{{ file.percentage }}%</span>
|
|
79
81
|
<VBtn
|
|
80
|
-
v-if="!disabled"
|
|
82
|
+
v-if="!disabled && !detail"
|
|
81
83
|
icon
|
|
82
84
|
size="x-small"
|
|
83
|
-
variant="text"
|
|
84
|
-
color="grey"
|
|
85
|
-
class="ml-1"
|
|
86
|
-
|
|
85
|
+
variant="text"
|
|
86
|
+
color="grey"
|
|
87
|
+
class="ml-1"
|
|
88
|
+
title="删除"
|
|
89
|
+
@click="handleRemove(file)"
|
|
87
90
|
>
|
|
88
91
|
<VIcon size="16">mdi-close</VIcon>
|
|
89
92
|
</VBtn>
|
|
@@ -102,28 +105,31 @@
|
|
|
102
105
|
<VDialog v-model="previewVisible" fullscreen scrollable @click:outside="closePreview">
|
|
103
106
|
<VCard class="vtk-upload__preview-card">
|
|
104
107
|
<v-toolbar color="primary" density="comfortable">
|
|
105
|
-
<v-toolbar-title class="text-truncate">{{ previewFile?.name }}</v-toolbar-title>
|
|
106
|
-
<VSpacer />
|
|
107
|
-
<
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<VBtn icon variant="text" @click="zoomPreview(0.3)">
|
|
112
|
-
<VIcon>mdi-magnify-
|
|
113
|
-
</VBtn>
|
|
114
|
-
<VBtn icon variant="text" @click="
|
|
115
|
-
<VIcon>mdi-
|
|
116
|
-
</VBtn>
|
|
117
|
-
<VBtn icon variant="text" @click="rotatePreview(90)">
|
|
118
|
-
<VIcon>mdi-rotate-
|
|
119
|
-
</VBtn>
|
|
120
|
-
<VBtn icon variant="text" @click="
|
|
121
|
-
<VIcon>mdi-
|
|
122
|
-
</VBtn>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
</
|
|
108
|
+
<v-toolbar-title class="text-truncate">{{ previewFile?.name }}</v-toolbar-title>
|
|
109
|
+
<VSpacer />
|
|
110
|
+
<VBtn icon variant="text" title="新窗口打开" @click="openCurrentPreview">
|
|
111
|
+
<VIcon>mdi-open-in-new</VIcon>
|
|
112
|
+
</VBtn>
|
|
113
|
+
<template v-if="isImage(previewFile)">
|
|
114
|
+
<VBtn icon variant="text" title="缩小" @click="zoomPreview(-0.3)">
|
|
115
|
+
<VIcon>mdi-magnify-minus-outline</VIcon>
|
|
116
|
+
</VBtn>
|
|
117
|
+
<VBtn icon variant="text" title="放大" @click="zoomPreview(0.3)">
|
|
118
|
+
<VIcon>mdi-magnify-plus-outline</VIcon>
|
|
119
|
+
</VBtn>
|
|
120
|
+
<VBtn icon variant="text" title="向左旋转" @click="rotatePreview(-90)">
|
|
121
|
+
<VIcon>mdi-rotate-left</VIcon>
|
|
122
|
+
</VBtn>
|
|
123
|
+
<VBtn icon variant="text" title="向右旋转" @click="rotatePreview(90)">
|
|
124
|
+
<VIcon>mdi-rotate-right</VIcon>
|
|
125
|
+
</VBtn>
|
|
126
|
+
<VBtn icon variant="text" title="重置" @click="resetPreviewTransform">
|
|
127
|
+
<VIcon>mdi-refresh</VIcon>
|
|
128
|
+
</VBtn>
|
|
129
|
+
</template>
|
|
130
|
+
<VBtn icon variant="text" title="关闭" @click="closePreview">
|
|
131
|
+
<VIcon>mdi-close</VIcon>
|
|
132
|
+
</VBtn>
|
|
127
133
|
</v-toolbar>
|
|
128
134
|
<div class="vtk-upload__preview-body">
|
|
129
135
|
<div v-if="isImage(previewFile)" class="vtk-upload__preview-image-wrap">
|
|
@@ -204,10 +210,15 @@ const props = defineProps({
|
|
|
204
210
|
default: true,
|
|
205
211
|
},
|
|
206
212
|
/** 是否禁用 */
|
|
207
|
-
disabled: {
|
|
208
|
-
type: Boolean,
|
|
209
|
-
default: false,
|
|
210
|
-
},
|
|
213
|
+
disabled: {
|
|
214
|
+
type: Boolean,
|
|
215
|
+
default: false,
|
|
216
|
+
},
|
|
217
|
+
/** 详情模式:隐藏上传和删除入口,仅展示已上传文件并保留查看 */
|
|
218
|
+
detail: {
|
|
219
|
+
type: Boolean,
|
|
220
|
+
default: false,
|
|
221
|
+
},
|
|
211
222
|
/** 附加请求头 */
|
|
212
223
|
headers: {
|
|
213
224
|
type: Object,
|
|
@@ -263,22 +274,70 @@ const previewTranslateY = ref(0);
|
|
|
263
274
|
const previewDragState = ref(null);
|
|
264
275
|
|
|
265
276
|
// 内部维护文件列表
|
|
266
|
-
const fileList = ref([
|
|
277
|
+
const fileList = ref([]);
|
|
267
278
|
|
|
268
279
|
watch(
|
|
269
280
|
() => props.modelValue,
|
|
270
281
|
(val) => {
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
fileList.value = [...val];
|
|
282
|
+
// 统一接收外部传入的 URL 列表或文件对象列表。
|
|
283
|
+
fileList.value = normalizeFileList(val || []);
|
|
274
284
|
},
|
|
275
285
|
);
|
|
276
286
|
|
|
277
287
|
/* -------------------- 工具函数 -------------------- */
|
|
278
|
-
let uidCounter = 0;
|
|
279
|
-
const genUid = () => `vtk-upload-${Date.now()}-${uidCounter++}`;
|
|
280
|
-
|
|
281
|
-
const
|
|
288
|
+
let uidCounter = 0;
|
|
289
|
+
const genUid = () => `vtk-upload-${Date.now()}-${uidCounter++}`;
|
|
290
|
+
|
|
291
|
+
const getFileNameFromUrl = (url) => {
|
|
292
|
+
const cleanUrl = String(url || '').split('?')[0].split('#')[0];
|
|
293
|
+
const name = cleanUrl.substring(cleanUrl.lastIndexOf('/') + 1);
|
|
294
|
+
try {
|
|
295
|
+
return decodeURIComponent(name || cleanUrl || 'file');
|
|
296
|
+
} catch (e) {
|
|
297
|
+
return name || cleanUrl || 'file';
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const normalizeFileList = (value) => {
|
|
302
|
+
if (!Array.isArray(value)) return [];
|
|
303
|
+
|
|
304
|
+
return value
|
|
305
|
+
.filter((item) => item)
|
|
306
|
+
.map((item) => {
|
|
307
|
+
if (typeof item === 'string') {
|
|
308
|
+
return {
|
|
309
|
+
uid: genUid(),
|
|
310
|
+
name: getFileNameFromUrl(item),
|
|
311
|
+
size: 0,
|
|
312
|
+
type: '',
|
|
313
|
+
status: 'success',
|
|
314
|
+
percentage: 100,
|
|
315
|
+
raw: null,
|
|
316
|
+
url: item,
|
|
317
|
+
preview: '',
|
|
318
|
+
response: null,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
uid: item.uid || genUid(),
|
|
324
|
+
name: item.name || getFileNameFromUrl(item.url || item.preview),
|
|
325
|
+
size: item.size || 0,
|
|
326
|
+
type: item.type || '',
|
|
327
|
+
status: item.status || 'success',
|
|
328
|
+
percentage: item.percentage ?? 100,
|
|
329
|
+
raw: item.raw || null,
|
|
330
|
+
url: item.url || '',
|
|
331
|
+
preview: item.preview || '',
|
|
332
|
+
response: item.response || null,
|
|
333
|
+
...item,
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
fileList.value = normalizeFileList(props.modelValue || []);
|
|
339
|
+
|
|
340
|
+
const isImage = (file) => {
|
|
282
341
|
if (!file) return false;
|
|
283
342
|
return /image\//.test(file.type) || /\.(png|jpg|jpeg|gif|bmp|webp|svg)$/i.test(file.name || '');
|
|
284
343
|
};
|
|
@@ -299,19 +358,20 @@ const triggerInput = () => {
|
|
|
299
358
|
};
|
|
300
359
|
|
|
301
360
|
/* -------------------- 拖拽 -------------------- */
|
|
302
|
-
const onDragover = () => {
|
|
303
|
-
if (!props.disabled) isDragover.value = true;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const onDrop = (e) => {
|
|
307
|
-
isDragover.value = false;
|
|
308
|
-
if (props.disabled) return;
|
|
309
|
-
processFiles(Array.from(e.dataTransfer.files));
|
|
310
|
-
};
|
|
361
|
+
const onDragover = () => {
|
|
362
|
+
if (!props.disabled && !props.detail) isDragover.value = true;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const onDrop = (e) => {
|
|
366
|
+
isDragover.value = false;
|
|
367
|
+
if (props.disabled || props.detail) return;
|
|
368
|
+
processFiles(Array.from(e.dataTransfer.files));
|
|
369
|
+
};
|
|
311
370
|
|
|
312
371
|
/* -------------------- input change -------------------- */
|
|
313
|
-
const onInputChange = (e) => {
|
|
314
|
-
|
|
372
|
+
const onInputChange = (e) => {
|
|
373
|
+
if (props.disabled || props.detail) return;
|
|
374
|
+
processFiles(Array.from(e.target.files));
|
|
315
375
|
// 清空,允许重复选同一文件
|
|
316
376
|
e.target.value = '';
|
|
317
377
|
};
|
|
@@ -485,20 +545,25 @@ const endPreviewDrag = (event) => {
|
|
|
485
545
|
previewDragState.value = null;
|
|
486
546
|
};
|
|
487
547
|
|
|
488
|
-
const openFileInNewTab = (file, fileUrl) => {
|
|
489
|
-
if (fileUrl) {
|
|
490
|
-
window.open(fileUrl, '_blank');
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
548
|
+
const openFileInNewTab = (file, fileUrl) => {
|
|
549
|
+
if (fileUrl) {
|
|
550
|
+
window.open(fileUrl, '_blank');
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
493
553
|
|
|
494
554
|
if (file.raw instanceof File || file.raw instanceof Blob) {
|
|
495
555
|
const blobUrl = URL.createObjectURL(file.raw);
|
|
496
556
|
const win = window.open(blobUrl, '_blank');
|
|
497
|
-
win?.addEventListener('unload', () => URL.revokeObjectURL(blobUrl));
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
const
|
|
557
|
+
win?.addEventListener('unload', () => URL.revokeObjectURL(blobUrl));
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const openCurrentPreview = () => {
|
|
562
|
+
if (!previewFile.value) return;
|
|
563
|
+
openFileInNewTab(previewFile.value, previewFile.value._previewSrc || previewFile.value.url || previewFile.value.preview);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const handlePreview = (file) => {
|
|
502
567
|
emit('preview', file);
|
|
503
568
|
|
|
504
569
|
const fileUrl = file.url || file.preview;
|