@yxhl/specter-pui-vtk 1.0.90 → 1.0.92
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 +2772 -2690
- 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/VtkEmptyNew.vue +16 -18
- package/src/components/assembly/VtkImg.vue +119 -36
- package/src/index.js +15 -11
package/package.json
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<v-card class="pa-4 mx-auto text-center pt-10" color="transparent" elevation="0" style="border: none !important;box-shadow: none !important;">
|
|
3
|
-
<v-img contain width="120" class="mx-auto" src="
|
|
4
|
-
<p class="text-grey mt-1">{{ text || "暂无数据" }}</p>
|
|
5
|
-
</v-card>
|
|
6
|
-
</template>
|
|
7
|
-
|
|
8
|
-
<script setup>
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type: String,
|
|
17
|
-
default: null,
|
|
1
|
+
<template>
|
|
2
|
+
<v-card class="pa-4 mx-auto text-center pt-10" color="transparent" elevation="0" style="border: none !important;box-shadow: none !important;">
|
|
3
|
+
<v-img contain width="120" class="mx-auto" :src="emptyImg" alt="" />
|
|
4
|
+
<p class="text-grey mt-1">{{ text || "暂无数据" }}</p>
|
|
5
|
+
</v-card>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup>
|
|
9
|
+
import emptyImg from '@/assets/img/empty.svg';
|
|
10
|
+
|
|
11
|
+
// 定义 props
|
|
12
|
+
defineProps({
|
|
13
|
+
text: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: null,
|
|
18
16
|
},
|
|
19
17
|
items: {
|
|
20
18
|
type: Array,
|
|
@@ -23,4 +21,4 @@ const props = defineProps({
|
|
|
23
21
|
});
|
|
24
22
|
</script>
|
|
25
23
|
|
|
26
|
-
<style lang="scss" scoped></style>
|
|
24
|
+
<style lang="scss" scoped></style>
|
|
@@ -326,9 +326,9 @@ const getImageFileExtension = (blob) => {
|
|
|
326
326
|
}
|
|
327
327
|
};
|
|
328
328
|
|
|
329
|
-
const getCurrentImageFileName = (blob) => {
|
|
330
|
-
const extension = getImageFileExtension(blob);
|
|
331
|
-
const titleName = currentImageTitle.value?.trim();
|
|
329
|
+
const getCurrentImageFileName = (blob) => {
|
|
330
|
+
const extension = getImageFileExtension(blob);
|
|
331
|
+
const titleName = currentImageTitle.value?.trim();
|
|
332
332
|
|
|
333
333
|
if (titleName) {
|
|
334
334
|
const safeTitle = titleName.replace(/[\\/:*?"<>|]/g, '_').replace(/\.[a-z0-9]+$/i, '');
|
|
@@ -343,19 +343,86 @@ const getCurrentImageFileName = (blob) => {
|
|
|
343
343
|
return urlName ? `${urlName}.${extension}` : `image.${extension}`;
|
|
344
344
|
} catch (error) {
|
|
345
345
|
return `image.${extension}`;
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const isDownloadRiskWebView = () => {
|
|
350
|
+
const ua = window.navigator?.userAgent || '';
|
|
351
|
+
const hasEmbeddedBridge = typeof window.dd !== 'undefined' || typeof window.AlipayJSBridge !== 'undefined';
|
|
352
|
+
return hasEmbeddedBridge
|
|
353
|
+
|| /DingTalk|AliApp\(DingTalk|zjzwfw|zzd|ZJGovernment|ZhejiangGovernment|GovDing|Electron|WebView|; wv\)/i.test(ua);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const getDownloadFailureReason = (error) => {
|
|
357
|
+
const message = error?.message || '';
|
|
358
|
+
const name = error?.name || '';
|
|
359
|
+
|
|
360
|
+
if (name === 'AbortError') return '已取消下载';
|
|
361
|
+
if (name === 'NotAllowedError' || name === 'SecurityError') return '当前客户端阻止了文件保存';
|
|
362
|
+
if (name === 'QuotaExceededError') return '存储空间不足或客户端限制写入';
|
|
363
|
+
if (/Failed to fetch|NetworkError|Load failed/i.test(message)) {
|
|
364
|
+
return '图片地址不允许跨域读取,或当前网络无法访问图片';
|
|
365
|
+
}
|
|
366
|
+
if (/HTTP error! status:\s*(\d+)/i.test(message)) {
|
|
367
|
+
return `图片请求失败,状态码 ${message.match(/HTTP error! status:\s*(\d+)/i)?.[1]}`;
|
|
368
|
+
}
|
|
369
|
+
if (/Response is not an image/i.test(message)) return '下载地址返回的不是图片文件';
|
|
370
|
+
if (/Downloaded image is empty/i.test(message)) return '下载到的图片文件为空';
|
|
371
|
+
if (/Downloaded file is not an image/i.test(message)) return '下载到的文件不是图片格式';
|
|
372
|
+
if (/Downloaded image cannot be decoded/i.test(message)) return '下载到的图片无法被浏览器解析';
|
|
373
|
+
|
|
374
|
+
return message || '未知错误';
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const validateImageBlob = async (blob) => {
|
|
378
|
+
if (!blob || blob.size <= 0) {
|
|
379
|
+
throw new Error('Downloaded image is empty');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const extension = getImageFileExtension(blob);
|
|
383
|
+
const mimeType = blob.type?.split(';')[0]?.trim().toLowerCase();
|
|
384
|
+
if (mimeType && !mimeType.startsWith('image/')) {
|
|
385
|
+
throw new Error(`Downloaded file is not an image: ${mimeType}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (typeof window.createImageBitmap === 'function' && mimeType !== 'image/svg+xml') {
|
|
389
|
+
try {
|
|
390
|
+
const bitmap = await window.createImageBitmap(blob);
|
|
391
|
+
bitmap.close?.();
|
|
392
|
+
return;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.warn('VtkImg: createImageBitmap validation failed, fallback to Image validation', error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (extension === 'svg') return;
|
|
399
|
+
|
|
400
|
+
await new Promise((resolve, reject) => {
|
|
401
|
+
const url = window.URL.createObjectURL(blob);
|
|
402
|
+
const image = new Image();
|
|
403
|
+
|
|
404
|
+
image.onload = () => {
|
|
405
|
+
window.URL.revokeObjectURL(url);
|
|
406
|
+
resolve();
|
|
407
|
+
};
|
|
408
|
+
image.onerror = () => {
|
|
409
|
+
window.URL.revokeObjectURL(url);
|
|
410
|
+
reject(new Error('Downloaded image cannot be decoded'));
|
|
411
|
+
};
|
|
412
|
+
image.src = url;
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const triggerBrowserDownload = (blob, fileName) => {
|
|
417
|
+
const url = window.URL.createObjectURL(blob);
|
|
418
|
+
const link = document.createElement('a');
|
|
419
|
+
link.href = url;
|
|
420
|
+
link.download = fileName;
|
|
421
|
+
document.body.appendChild(link);
|
|
422
|
+
link.click();
|
|
423
|
+
document.body.removeChild(link);
|
|
424
|
+
setTimeout(() => window.URL.revokeObjectURL(url), 60 * 1000);
|
|
425
|
+
};
|
|
359
426
|
|
|
360
427
|
const triggerBrowserUrlDownload = (url, fileName) => {
|
|
361
428
|
const link = document.createElement('a');
|
|
@@ -384,11 +451,15 @@ const showDownloadToast = (message, color = 'info') => {
|
|
|
384
451
|
}
|
|
385
452
|
};
|
|
386
453
|
|
|
387
|
-
const getDownloadFileHandle = async (fileName, blob) => {
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
454
|
+
const getDownloadFileHandle = async (fileName, blob) => {
|
|
455
|
+
if (isDownloadRiskWebView()) {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (typeof window.showSaveFilePicker !== 'function') {
|
|
460
|
+
showDownloadToast('当前浏览器不支持选择保存目录,将使用默认下载方式。', 'warning');
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
392
463
|
|
|
393
464
|
const extension = getImageFileExtension(blob);
|
|
394
465
|
const mimeType = imageExtensionMimeMap[extension] || blob?.type || 'image/png';
|
|
@@ -467,11 +538,12 @@ const downloadCurrentImage = async () => {
|
|
|
467
538
|
return;
|
|
468
539
|
}
|
|
469
540
|
|
|
470
|
-
try {
|
|
471
|
-
const blob = await fetchCurrentImageBlob();
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
541
|
+
try {
|
|
542
|
+
const blob = await fetchCurrentImageBlob();
|
|
543
|
+
await validateImageBlob(blob);
|
|
544
|
+
const fileName = getCurrentImageFileName(blob);
|
|
545
|
+
const fileHandle = await getDownloadFileHandle(fileName, blob);
|
|
546
|
+
if (fileHandle === false) return;
|
|
475
547
|
|
|
476
548
|
if (fileHandle) {
|
|
477
549
|
const writable = await fileHandle.createWritable();
|
|
@@ -479,16 +551,27 @@ const downloadCurrentImage = async () => {
|
|
|
479
551
|
await writable.close();
|
|
480
552
|
showDownloadToast('图片下载成功。', 'success');
|
|
481
553
|
return;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
triggerBrowserDownload(blob, fileName);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
triggerBrowserDownload(blob, fileName);
|
|
557
|
+
if (isDownloadRiskWebView()) {
|
|
558
|
+
showDownloadToast('已使用兼容方式下载图片,如文件仍不可用,请在系统浏览器中打开后保存。');
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
showDownloadToast('图片已开始下载。', 'success');
|
|
563
|
+
} catch (error) {
|
|
564
|
+
const failureReason = getDownloadFailureReason(error);
|
|
565
|
+
console.error(`下载图片失败:${failureReason}`, error);
|
|
566
|
+
triggerBrowserUrlDownload(downloadUrl, getCurrentImageFileName());
|
|
567
|
+
if (isDownloadRiskWebView()) {
|
|
568
|
+
showDownloadToast(`下载失败:${failureReason}。已打开原图,请在系统浏览器中保存。`, 'warning');
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
showDownloadToast(`下载失败:${failureReason}。已切换为浏览器下载。`, 'warning');
|
|
573
|
+
}
|
|
574
|
+
};
|
|
492
575
|
|
|
493
576
|
const resetImageTransform = () => {
|
|
494
577
|
imageRotation.value = 0;
|
package/src/index.js
CHANGED
|
@@ -11,9 +11,10 @@ export { default as VtkDatePicker } from "./components/assembly/VtkDatePicker.vu
|
|
|
11
11
|
export { default as VtkDateSelector } from "./components/assembly/VtkDateSelector.vue";
|
|
12
12
|
export { default as VtkDateTimePicker } from "./components/assembly/VtkDateTimePicker.vue";
|
|
13
13
|
export { default as VtkDept } from "./components/assembly/VtkDept.vue";
|
|
14
|
-
export { default as VtkEmpty } from "./components/assembly/VtkEmpty.vue";
|
|
15
|
-
export { default as
|
|
16
|
-
export { default as
|
|
14
|
+
export { default as VtkEmpty } from "./components/assembly/VtkEmpty.vue";
|
|
15
|
+
export { default as VtkEmptyNew } from "./components/assembly/VtkEmptyNew.vue";
|
|
16
|
+
export { default as VtkFab } from "./components/assembly/VtkFab.vue";
|
|
17
|
+
export { default as VtkFormItem } from "./components/assembly/VtkFormItem.vue";
|
|
17
18
|
export { default as VtkImg } from "./components/assembly/VtkImg.vue";
|
|
18
19
|
export { default as VtkPage } from "./components/assembly/VtkPage.vue";
|
|
19
20
|
export { default as VtkPdf } from "./components/assembly/VtkPdf.vue";
|
|
@@ -55,9 +56,10 @@ import VtkDatePicker from "./components/assembly/VtkDatePicker.vue";
|
|
|
55
56
|
import VtkDateSelector from "./components/assembly/VtkDateSelector.vue";
|
|
56
57
|
import VtkDateTimePicker from "./components/assembly/VtkDateTimePicker.vue";
|
|
57
58
|
import VtkDept from "./components/assembly/VtkDept.vue";
|
|
58
|
-
import VtkEmpty from "./components/assembly/VtkEmpty.vue";
|
|
59
|
-
import
|
|
60
|
-
import
|
|
59
|
+
import VtkEmpty from "./components/assembly/VtkEmpty.vue";
|
|
60
|
+
import VtkEmptyNew from "./components/assembly/VtkEmptyNew.vue";
|
|
61
|
+
import VtkFab from "./components/assembly/VtkFab.vue";
|
|
62
|
+
import VtkFormItem from "./components/assembly/VtkFormItem.vue";
|
|
61
63
|
import VtkImg from "./components/assembly/VtkImg.vue";
|
|
62
64
|
import VtkPage from "./components/assembly/VtkPage.vue";
|
|
63
65
|
import VtkPdf from "./components/assembly/VtkPdf.vue";
|
|
@@ -80,9 +82,10 @@ function install(app, options = {}) {
|
|
|
80
82
|
VtkDateSelector,
|
|
81
83
|
VtkDateTimePicker,
|
|
82
84
|
VtkDept,
|
|
83
|
-
VtkEmpty,
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
VtkEmpty,
|
|
86
|
+
VtkEmptyNew,
|
|
87
|
+
VtkFab,
|
|
88
|
+
VtkFormItem,
|
|
86
89
|
VtkImg,
|
|
87
90
|
VtkPage,
|
|
88
91
|
VtkPdf,
|
|
@@ -128,8 +131,9 @@ export default {
|
|
|
128
131
|
VtkDateSelector,
|
|
129
132
|
VtkDateTimePicker,
|
|
130
133
|
VtkDept,
|
|
131
|
-
VtkEmpty,
|
|
132
|
-
|
|
134
|
+
VtkEmpty,
|
|
135
|
+
VtkEmptyNew,
|
|
136
|
+
VtkFab,
|
|
133
137
|
VtkFormItem,
|
|
134
138
|
VtkImg,
|
|
135
139
|
VtkPage,
|