@chenghongyu/xpt-file-viewer 1.1.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.
- package/.vscode/extensions.json +3 -0
- package/README.md +5 -0
- package/auto-imports.d.ts +12 -0
- package/components.d.ts +45 -0
- package/dist.zip +0 -0
- package/index.html +13 -0
- package/package.json +100 -0
- package/presets/eslint/.eslintrc-auto-import.json +462 -0
- package/presets/plugins/html.ts +14 -0
- package/presets/shared/env.ts +46 -0
- package/presets/shared/resolvers.ts +29 -0
- package/presets/tov.ts +132 -0
- package/presets/types/auto-imports.d.ts +806 -0
- package/presets/types/components.d.ts +164 -0
- package/presets/types/env.d.ts +17 -0
- package/public/favicon.svg +1 -0
- package/public/icons.svg +24 -0
- package/public/testfile/README.md +5 -0
- package/public/testfile/useFileContextMenu.js +74 -0
- package/public/testfile//345/217/221/347/245/250.pdf +0 -0
- package/public/testfile//346/216/245/346/224/266/345/207/275.docx +0 -0
- package/public/testfile//350/247/204/346/240/274/345/236/213/345/217/2671.txt +263 -0
- package/public/testfile//350/247/206/351/242/2211.mp4 +0 -0
- package/public/testfile//351/237/263/351/242/2211.mp3 +0 -0
- package/src/App.vue +8 -0
- package/src/api/admin/admin-2fa.js +18 -0
- package/src/api/admin/admin-download-log.js +37 -0
- package/src/api/admin/admin-login-log.js +9 -0
- package/src/api/admin/admin-permission.js +9 -0
- package/src/api/admin/admin-setting.js +121 -0
- package/src/api/admin/admin-share.js +20 -0
- package/src/api/admin/admin-short-link.js +49 -0
- package/src/api/admin/admin-sso.js +38 -0
- package/src/api/admin/admin-storage.js +189 -0
- package/src/api/admin/admin-user.js +61 -0
- package/src/api/home/common.js +9 -0
- package/src/api/home/file-operator.js +89 -0
- package/src/api/home/home.js +87 -0
- package/src/api/home/install.js +18 -0
- package/src/api/home/login.js +9 -0
- package/src/api/home/only-office.js +10 -0
- package/src/api/home/share.js +115 -0
- package/src/api/home/user.js +74 -0
- package/src/api/tools/tools-115.js +17 -0
- package/src/api/tools/tools-gd.js +13 -0
- package/src/api/tools/tools-s3.js +25 -0
- package/src/api/tools/tools-sharepoint.js +19 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/icons/401.svg +1 -0
- package/src/assets/icons/403.svg +45 -0
- package/src/assets/icons/404.svg +1 -0
- package/src/assets/icons/500.svg +1 -0
- package/src/assets/icons/admin-login.svg +1 -0
- package/src/assets/icons/document.svg +1 -0
- package/src/assets/icons/empty.svg +145 -0
- package/src/assets/icons/file-type-apk.svg +1 -0
- package/src/assets/icons/file-type-archive.svg +1 -0
- package/src/assets/icons/file-type-audio.svg +1 -0
- package/src/assets/icons/file-type-back.svg +1 -0
- package/src/assets/icons/file-type-css.svg +1 -0
- package/src/assets/icons/file-type-deb.svg +1 -0
- package/src/assets/icons/file-type-dll.svg +1 -0
- package/src/assets/icons/file-type-doc.svg +1 -0
- package/src/assets/icons/file-type-document.svg +1 -0
- package/src/assets/icons/file-type-docx.svg +1 -0
- package/src/assets/icons/file-type-exe.svg +1 -0
- package/src/assets/icons/file-type-expression.svg +1 -0
- package/src/assets/icons/file-type-file.svg +1 -0
- package/src/assets/icons/file-type-folder.svg +1 -0
- package/src/assets/icons/file-type-html.svg +1 -0
- package/src/assets/icons/file-type-image.svg +1 -0
- package/src/assets/icons/file-type-java.svg +1 -0
- package/src/assets/icons/file-type-js.svg +1 -0
- package/src/assets/icons/file-type-less.svg +1 -0
- package/src/assets/icons/file-type-md.svg +1 -0
- package/src/assets/icons/file-type-office.svg +1 -0
- package/src/assets/icons/file-type-pdf.svg +1 -0
- package/src/assets/icons/file-type-php.svg +1 -0
- package/src/assets/icons/file-type-ppt.svg +1 -0
- package/src/assets/icons/file-type-pptx.svg +1 -0
- package/src/assets/icons/file-type-py.svg +1 -0
- package/src/assets/icons/file-type-rb.svg +1 -0
- package/src/assets/icons/file-type-root.svg +1 -0
- package/src/assets/icons/file-type-rpm.svg +1 -0
- package/src/assets/icons/file-type-rust.svg +1 -0
- package/src/assets/icons/file-type-script.svg +1 -0
- package/src/assets/icons/file-type-text.svg +1 -0
- package/src/assets/icons/file-type-three3d.svg +5 -0
- package/src/assets/icons/file-type-vbs.svg +1 -0
- package/src/assets/icons/file-type-video.svg +1 -0
- package/src/assets/icons/file-type-xls.svg +1 -0
- package/src/assets/icons/file-type-xlsx.svg +1 -0
- package/src/assets/icons/file-type-xml.svg +1 -0
- package/src/assets/icons/file-type-yaml.svg +1 -0
- package/src/assets/icons/file-upload.svg +1 -0
- package/src/assets/icons/github.svg +1 -0
- package/src/assets/icons/install-step.svg +40 -0
- package/src/assets/icons/reset-password.svg +1 -0
- package/src/assets/icons/storage-aliyun.svg +1 -0
- package/src/assets/icons/storage-baidu.svg +1 -0
- package/src/assets/icons/storage-doge-cloud.svg +207 -0
- package/src/assets/icons/storage-ftp.svg +13 -0
- package/src/assets/icons/storage-google-drive.svg +8 -0
- package/src/assets/icons/storage-huawei.svg +1 -0
- package/src/assets/icons/storage-local.svg +11 -0
- package/src/assets/icons/storage-minio.svg +1 -0
- package/src/assets/icons/storage-onedrive-china.svg +18 -0
- package/src/assets/icons/storage-onedrive.svg +4 -0
- package/src/assets/icons/storage-open115.svg +23 -0
- package/src/assets/icons/storage-qiniu.svg +1 -0
- package/src/assets/icons/storage-s3.svg +5 -0
- package/src/assets/icons/storage-sftp.svg +13 -0
- package/src/assets/icons/storage-sharepoint-china.svg +23 -0
- package/src/assets/icons/storage-sharepoint.svg +1 -0
- package/src/assets/icons/storage-tencent.svg +9 -0
- package/src/assets/icons/storage-ufile.svg +14 -0
- package/src/assets/icons/storage-upyun.svg +1 -0
- package/src/assets/icons/storage-webdav.svg +1 -0
- package/src/assets/icons/upload.svg +50 -0
- package/src/assets/icons/zfile-basic.svg +17 -0
- package/src/assets/icons/zfile-horizontal.svg +16 -0
- package/src/assets/icons/zfile.svg +1 -0
- package/src/assets/vite.svg +1 -0
- package/src/assets/vue.svg +1 -0
- package/src/components/HelloWorld.vue +319 -0
- package/src/components/common/QrCodePreview.vue +118 -0
- package/src/components/common/dialog/ZDialog.vue +171 -0
- package/src/components/common/dialog/types.ts +19 -0
- package/src/components/common/dialog/useDialog.ts +18 -0
- package/src/components/common/dialog/useDialogWithForm.ts +21 -0
- package/src/components/copy.vue +133 -0
- package/src/components/file/preview/AudioPlayer.vue +333 -0
- package/src/components/file/preview/CopyCode.vue +47 -0
- package/src/components/file/preview/FileGallery.vue +199 -0
- package/src/components/file/preview/ImageViewer.vue +432 -0
- package/src/components/file/preview/KkFileViewer.vue +86 -0
- package/src/components/file/preview/KkFileViewerDialog.vue +42 -0
- package/src/components/file/preview/MarkdownViewer.vue +102 -0
- package/src/components/file/preview/MarkdownViewerAsyncLoading.vue +17 -0
- package/src/components/file/preview/MarkdownViewerDialogAsyncLoading.vue +12 -0
- package/src/components/file/preview/OfficeViewer.vue +76 -0
- package/src/components/file/preview/OfficeViewerDialog.vue +55 -0
- package/src/components/file/preview/PdfViewer.vue +157 -0
- package/src/components/file/preview/PdfViewerDialog.vue +41 -0
- package/src/components/file/preview/TextViewer.vue +232 -0
- package/src/components/file/preview/TextViewerAsyncLoading.vue +22 -0
- package/src/components/file/preview/TextViewerDialog.vue +53 -0
- package/src/components/file/preview/Three3dPreview.vue +114 -0
- package/src/components/file/preview/Three3dPreviewDialog.vue +50 -0
- package/src/components/file/preview/VideoPlayer.vue +341 -0
- package/src/components/file/preview/VideoPlayerAsyncLoading.vue +45 -0
- package/src/components/file/preview/VideoPlayerDialog.vue +51 -0
- package/src/components/file/selectFolder/SelectFolder.vue +208 -0
- package/src/components/file/selectFolder/index.ts +50 -0
- package/src/components/file/selectFolder/types.ts +5 -0
- package/src/components/fileReview/AudioPlayer-copy.vue +333 -0
- package/src/components/fileReview/PdfViewer-copy.vue +157 -0
- package/src/components/fileReview/TextViewer-copy.vue +44 -0
- package/src/components/fileReview/VideoPlayer-copy.vue +341 -0
- package/src/components/messageBox/confirm/confirm.vue +137 -0
- package/src/components/messageBox/confirm/index.ts +27 -0
- package/src/components/messageBox/confirm/types.ts +15 -0
- package/src/components/messageBox/messageBox.ts +9 -0
- package/src/components/messageBox/prompt/index.ts +27 -0
- package/src/components/messageBox/prompt/prompt.vue +178 -0
- package/src/components/messageBox/prompt/types.ts +24 -0
- package/src/components/vue-codemirror/editor.vue +212 -0
- package/src/components/vue-codemirror/encodings.ts +27 -0
- package/src/components/vue-codemirror/index.vue +380 -0
- package/src/components/vue-codemirror/lang-code/cpp/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/css/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/dockerfile/index.ts +5 -0
- package/src/components/vue-codemirror/lang-code/erlang/index.ts +6 -0
- package/src/components/vue-codemirror/lang-code/go/index.ts +5 -0
- package/src/components/vue-codemirror/lang-code/html/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/java/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/javascript/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/json/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/jsx/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/lua/index.ts +6 -0
- package/src/components/vue-codemirror/lang-code/markdown/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/mysql/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/nginx/index.ts +6 -0
- package/src/components/vue-codemirror/lang-code/perl/index.ts +5 -0
- package/src/components/vue-codemirror/lang-code/pgsql/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/php/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/powershell/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/python/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/r/index.ts +5 -0
- package/src/components/vue-codemirror/lang-code/ruby/index.ts +5 -0
- package/src/components/vue-codemirror/lang-code/rust/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/shell/index.ts +5 -0
- package/src/components/vue-codemirror/lang-code/sql/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/stylus/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/swift/index.ts +4 -0
- package/src/components/vue-codemirror/lang-code/toml/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/tsx/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/typescript/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/vb/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/vbscript/index.ts +3 -0
- package/src/components/vue-codemirror/lang-code/xml/index.ts +2 -0
- package/src/components/vue-codemirror/lang-code/yaml/index.ts +3 -0
- package/src/components/vue-codemirror/languages.ts +8 -0
- package/src/components/vue-codemirror/themes.ts +5 -0
- package/src/components/vue-codemirror/toolbar.vue +183 -0
- package/src/components/vue-codemirror/types.ts +12 -0
- package/src/components.d.ts +49 -0
- package/src/composables/admin/layout/admin-layout.js +53 -0
- package/src/composables/admin/link/useLinkSetting.js +16 -0
- package/src/composables/admin/sso/baseSsoConfig.js +54 -0
- package/src/composables/admin/sso/useSsoConfig.js +176 -0
- package/src/composables/admin/storage/storage-copy.js +89 -0
- package/src/composables/admin/storage/storage-filter.js +64 -0
- package/src/composables/admin/storage/storage-list.js +202 -0
- package/src/composables/admin/storage/storage-password.js +101 -0
- package/src/composables/admin/storage/storage-readme.js +102 -0
- package/src/composables/admin/storage/utils/open115-util.js +61 -0
- package/src/composables/admin/useAdminSetting.js +60 -0
- package/src/composables/admin/useClientInfo.js +20 -0
- package/src/composables/admin/user/user-copy.js +79 -0
- package/src/composables/file/useBatchOperatorResult.js +19 -0
- package/src/composables/file/useFileContextMenu.js +74 -0
- package/src/composables/file/useFileData.js +243 -0
- package/src/composables/file/useFileLink.js +175 -0
- package/src/composables/file/useFileLoading.js +41 -0
- package/src/composables/file/useFileLongPressEvent.js +71 -0
- package/src/composables/file/useFileOperator.js +347 -0
- package/src/composables/file/useFilePreview.js +99 -0
- package/src/composables/file/useFilePwd.js +138 -0
- package/src/composables/file/useFileSelect.js +105 -0
- package/src/composables/file/useFileShare.js +39 -0
- package/src/composables/file/useFileUpload.js +1045 -0
- package/src/composables/file/useKkFileViewDialog.js +24 -0
- package/src/composables/file/useOfficeViewerDialog.js +20 -0
- package/src/composables/file/usePdfViewerDialog.js +22 -0
- package/src/composables/file/useShareActions.js +94 -0
- package/src/composables/file/useShareTableOperator.js +84 -0
- package/src/composables/file/useTableOperator.js +211 -0
- package/src/composables/file/useTextViewerDialog.js +22 -0
- package/src/composables/file/useThree3dPreviewDialog.js +23 -0
- package/src/composables/file/useVideoPlayerDialog.js +19 -0
- package/src/composables/header/useHeaderBreadcrumb.js +111 -0
- package/src/composables/header/useHeaderStorageList.js +150 -0
- package/src/composables/header/useSetting.js +58 -0
- package/src/composables/share/useShareData.js +178 -0
- package/src/composables/useDarks.ts +4 -0
- package/src/composables/useRouterData.js +41 -0
- package/src/constant/index.js +193 -0
- package/src/http/index.js +153 -0
- package/src/http/request.js +31 -0
- package/src/main.ts +23 -0
- package/src/stores/file-data.ts +108 -0
- package/src/stores/global-config.ts +115 -0
- package/src/stores/storage-config.ts +123 -0
- package/src/style.css +296 -0
- package/src/styles/admin.scss +91 -0
- package/src/styles/code-editor-variables.scss +52 -0
- package/src/styles/element-plus.scss +22 -0
- package/src/styles/error-page.css +26 -0
- package/src/styles/main.css +142 -0
- package/src/styles/tailwind/index.scss +33 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/models/base.ts +34 -0
- package/src/utils/models/file.ts +95 -0
- package/src/utils/models/path.ts +117 -0
- package/src/utils/models/qrcode.ts +21 -0
- package/src/utils/models/time.ts +132 -0
- package/src/utils/models/util.ts +42 -0
- package/src/utils/models/window.ts +14 -0
- package/stats.html +4950 -0
- package/tsconfig.app.json +16 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.ts +57 -0
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
import {ElLoading} from "element-plus";
|
|
2
|
+
import { newFolderReq, uploadFileReq } from "~/api/home/file-operator";
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import selfAxios from "~/http/request"
|
|
5
|
+
import { concatPath, fileSizeFormat, removeDuplicateSeparator } from "~/utils";
|
|
6
|
+
|
|
7
|
+
import useStorageConfigStore from "~/stores/storage-config";
|
|
8
|
+
let storageConfigStore = useStorageConfigStore();
|
|
9
|
+
|
|
10
|
+
import {hasDialog, hasSearchInputFocus, hasPasswordInputFocus} from "~/composables/file/useTableOperator";
|
|
11
|
+
|
|
12
|
+
// 拖拽上传状态, true 表示正有文件拖拽悬浮在上传框上
|
|
13
|
+
let dropState = ref(false);
|
|
14
|
+
import useRouterData from "~/composables/useRouterData";
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
// 是否已经初始化监听 watch, 防止重复引用该文件导致的重复 watch
|
|
18
|
+
let isInitWatch = false;
|
|
19
|
+
|
|
20
|
+
// 是否显示上传框.
|
|
21
|
+
const visible = ref(false);
|
|
22
|
+
|
|
23
|
+
// 当前上传模式, 'file' or 'folder'
|
|
24
|
+
const uploadMode = ref('');
|
|
25
|
+
|
|
26
|
+
// 正在上传或已完成文件列表
|
|
27
|
+
const uploadingFileList = reactive([
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
// 等待上传文件列表
|
|
31
|
+
const waitingFileList = reactive([]);
|
|
32
|
+
|
|
33
|
+
// 取消上传文件的 cancelTokenSource 映射表
|
|
34
|
+
const cancelTokenSourceMap = new Map();
|
|
35
|
+
|
|
36
|
+
// 正在上传或已完成文件映射表 key: fileInfo.index value: fileInfo
|
|
37
|
+
const uploadingFileMap = new Map();
|
|
38
|
+
|
|
39
|
+
// 文件上传 index
|
|
40
|
+
let uploadIndex = 0;
|
|
41
|
+
|
|
42
|
+
// 文件上传状态顺序
|
|
43
|
+
const uploadFileTypeSortMap = {
|
|
44
|
+
"error": 1,
|
|
45
|
+
"uploading": 2,
|
|
46
|
+
"waiting": 3,
|
|
47
|
+
"finished": 4,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default function useFileUpload() {
|
|
51
|
+
|
|
52
|
+
let { storageKey, currentPath } = useRouterData();
|
|
53
|
+
const maxFileUploads = storageConfigStore.globalConfig.maxFileUploads;
|
|
54
|
+
|
|
55
|
+
// 监听文件拖拽上传事件
|
|
56
|
+
const listenDropFile = (dropArea) => {
|
|
57
|
+
// 拖拽进入, 显示提示.
|
|
58
|
+
dropArea = document.querySelector('body');
|
|
59
|
+
|
|
60
|
+
const dropOrPasteUpload = (e) => {
|
|
61
|
+
let isPaste = e.clipboardData?.items?.length > 0;
|
|
62
|
+
let isDrop = e.dataTransfer?.files?.length > 0;
|
|
63
|
+
|
|
64
|
+
// 关闭提示
|
|
65
|
+
removeDragClass();
|
|
66
|
+
dropState.value = false;
|
|
67
|
+
|
|
68
|
+
// 如果是 focus 在搜索框, 则且是粘贴上传,则不处理上传.
|
|
69
|
+
if (hasSearchInputFocus() && isPaste) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 如果是拖拽上传,但已经打开了 dialog,则不处理上传.
|
|
74
|
+
if (hasDialog() && isDrop) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 如果 focus 在密码输入框,则不处理上传.
|
|
79
|
+
if (hasPasswordInputFocus()) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 如果粘贴的是纯文本,则不处理上传.
|
|
84
|
+
if (isPaste && e.clipboardData?.types?.length === 1 && e.clipboardData?.types[0] === 'text/plain') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!storageKey.value) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 如果不允许文件操作,直接返回.
|
|
93
|
+
if (!storageConfigStore.permission.upload) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//阻止事件冒泡
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
//阻止事件的默认行为
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
|
|
102
|
+
// 打开全屏 loading
|
|
103
|
+
const loadingInstance = ElLoading.service({
|
|
104
|
+
text: '文件读取中...',
|
|
105
|
+
background: 'rgba(0, 0, 0, .3)'
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const items = e.clipboardData?.items || e.dataTransfer?.items;
|
|
109
|
+
|
|
110
|
+
getFilesByDataTransferItemList(items).then(async (fileList) => {
|
|
111
|
+
// 关闭 loading
|
|
112
|
+
loadingInstance.close()
|
|
113
|
+
|
|
114
|
+
// 如果用户没有新建文件夹权限, 则不允许上传文件夹
|
|
115
|
+
if (!storageConfigStore.permission.newFolder) {
|
|
116
|
+
fileList = fileList.filter((item) => {
|
|
117
|
+
if (item.dropUploadPath) {
|
|
118
|
+
return removeDuplicateSeparator(item.dropUploadPath).split('/').length <= 2;
|
|
119
|
+
}
|
|
120
|
+
if (item.webkitRelativePath) {
|
|
121
|
+
return removeDuplicateSeparator(item.webkitRelativePath).split('/').length <= 2;
|
|
122
|
+
}
|
|
123
|
+
if (item.path) {
|
|
124
|
+
return removeDuplicateSeparator(item.path).split('/').length <= 2;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 如果没有文件, 则直接返回
|
|
131
|
+
if (fileList.length === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 上传文件过多时,提示.
|
|
136
|
+
if (fileList.length > 100) {
|
|
137
|
+
const action = await ElMessageBox.confirm(`文件数量为 ${fileList.length} 个,是否确认上传?`, '提示', {
|
|
138
|
+
confirmButtonText: '确定',
|
|
139
|
+
cancelButtonText: '取消',
|
|
140
|
+
type: 'success'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (action !== 'confirm') {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 先创建所有文件夹
|
|
149
|
+
const createFoldersLoadingInstance = ElLoading.service({
|
|
150
|
+
text: '创建文件夹中...',
|
|
151
|
+
background: 'rgba(0, 0, 0, .3)'
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// 获取上传中包含的空文件夹,存储到 createdFolderSet 中并从 fileList 中移除
|
|
155
|
+
let createdFolderSet = new Set();
|
|
156
|
+
fileList = fileList.filter((file) => {
|
|
157
|
+
// 如果包含 webkitRelativePath, 则表示是文件夹上传, 需要获取文件完整路径
|
|
158
|
+
if (file.webkitRelativePath || file.dropUploadPath) {
|
|
159
|
+
let pathStr = file.webkitRelativePath || file.dropUploadPath;
|
|
160
|
+
|
|
161
|
+
if (!pathStr.startsWith('/')) {
|
|
162
|
+
pathStr = '/' + pathStr;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pathStr = pathStr.substring(0, pathStr.lastIndexOf('/'));
|
|
166
|
+
|
|
167
|
+
let prePath = '';
|
|
168
|
+
for (let subPath of pathStr.split("/")) {
|
|
169
|
+
if (subPath) {
|
|
170
|
+
if (!prePath) {
|
|
171
|
+
prePath = "/" + subPath;
|
|
172
|
+
} else {
|
|
173
|
+
prePath = prePath + '/' + subPath;
|
|
174
|
+
}
|
|
175
|
+
createdFolderSet.add(prePath);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else if (file.isEmptyDirectory) {
|
|
179
|
+
createdFolderSet.add(file.path);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
console.log('[upload] 要提前创建的文件夹:', createdFolderSet);
|
|
186
|
+
|
|
187
|
+
// 创建指定的文件夹列表方法
|
|
188
|
+
async function handleCreateFolders(folderPathList) {
|
|
189
|
+
let uploadToPath = currentPath.value;
|
|
190
|
+
for (let path of folderPathList) {
|
|
191
|
+
let basePath = path.substring(0, path.lastIndexOf('/'));
|
|
192
|
+
let pathName = path.substring(path.lastIndexOf('/') + 1);
|
|
193
|
+
let param = {
|
|
194
|
+
storageKey: storageKey.value,
|
|
195
|
+
path: concatPath(uploadToPath, basePath),
|
|
196
|
+
name: pathName
|
|
197
|
+
}
|
|
198
|
+
console.log('[upload] 创建文件夹: path:', param.path, ', name:', param.name);
|
|
199
|
+
try {
|
|
200
|
+
await newFolderReq(param);
|
|
201
|
+
console.log('[upload] 创建文件夹成功:', path);
|
|
202
|
+
} catch {
|
|
203
|
+
console.log('[upload] 创建文件夹失败:', path);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (storageConfigStore.folderConfig.metadata.needCreateFolderBeforeUpload === true) {
|
|
209
|
+
await handleCreateFolders(createdFolderSet).finally(() => {
|
|
210
|
+
createFoldersLoadingInstance.close();
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
createFoldersLoadingInstance.close();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 如果去除所有空文件夹下没有文件, 则直接返回
|
|
217
|
+
if (fileList.length === 0) {
|
|
218
|
+
console.log('[upload] 去除所有空文件夹下没有文件, 则直接返回,无需上传文件');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 上传文件
|
|
223
|
+
visible.value = true;
|
|
224
|
+
fileList.forEach((item) => {
|
|
225
|
+
beforeUpload({
|
|
226
|
+
file: item
|
|
227
|
+
});
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 最后拖拽的目标元素
|
|
233
|
+
let lastEnterTarget = null;
|
|
234
|
+
// 拖拽进入时,记录当前拖拽的目标元素,并添加防止拖拽穿透的 class
|
|
235
|
+
dropArea.addEventListener('dragenter', (event) => {
|
|
236
|
+
if (!storageKey.value) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (visible.value) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 如果不允许文件操作,直接返回.
|
|
245
|
+
if (!storageConfigStore.permission.upload) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
lastEnterTarget = event.target;
|
|
250
|
+
addDragClass();
|
|
251
|
+
event.stopPropagation();
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
dropState.value = true;
|
|
254
|
+
}, false);
|
|
255
|
+
|
|
256
|
+
// 拖拽进入后, 移动位置时, 记录拖拽状态
|
|
257
|
+
dropArea.addEventListener("dragover", function(event) {
|
|
258
|
+
if (!storageKey.value) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (visible.value) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 如果不允许文件操作,直接返回.
|
|
267
|
+
if (!storageConfigStore.permission.upload) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
event.stopPropagation();
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
dropState.value = true;
|
|
274
|
+
}, false);
|
|
275
|
+
|
|
276
|
+
// 拖拽离开, 移除防拖拽穿透 class, 并关闭拖拽提示
|
|
277
|
+
dropArea.addEventListener("dragleave", function(event) {
|
|
278
|
+
if (!storageKey.value) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (visible.value) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 如果不允许文件操作,直接返回.
|
|
287
|
+
if (!storageConfigStore.permission.upload) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if(lastEnterTarget === event.target){
|
|
292
|
+
removeDragClass();
|
|
293
|
+
dropState.value = false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
event.stopPropagation();
|
|
297
|
+
event.preventDefault();
|
|
298
|
+
}, false);
|
|
299
|
+
|
|
300
|
+
// 拖拽放下时, 移除防拖拽穿透 class, 并关闭拖拽提示, 开始上传
|
|
301
|
+
dropArea.addEventListener('drop', dropOrPasteUpload, false);
|
|
302
|
+
|
|
303
|
+
// ctrl + v 粘贴文件时, 开始上传
|
|
304
|
+
dropArea.addEventListener('paste', dropOrPasteUpload, false);
|
|
305
|
+
|
|
306
|
+
// 根据拖拽或粘贴事件的 DataTransferItemList 列表获取文件列表
|
|
307
|
+
const getFilesByDataTransferItemList = async (dataTransferItemList) => {
|
|
308
|
+
// 储存获取到的文件列表
|
|
309
|
+
let fileList = [];
|
|
310
|
+
let DirectoryEntryList = [];
|
|
311
|
+
|
|
312
|
+
if (dataTransferItemList) {
|
|
313
|
+
// 拖拽对象列表转换成数组
|
|
314
|
+
let items = new Array(...dataTransferItemList);
|
|
315
|
+
// 获得 DirectoryEntry 对象列表
|
|
316
|
+
for (let index = 0; index < items.length; index++) {
|
|
317
|
+
let e = items[index];
|
|
318
|
+
let item = null;
|
|
319
|
+
// 兼容不同内核的浏览器
|
|
320
|
+
if (e.webkitGetAsEntry) {
|
|
321
|
+
item = e.webkitGetAsEntry();
|
|
322
|
+
if (!item) {
|
|
323
|
+
item = e.getAsFile();
|
|
324
|
+
if (item) {
|
|
325
|
+
fileList.push(item);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} else if (e.getAsEntry) {
|
|
330
|
+
item = e.getAsEntry();
|
|
331
|
+
} else {
|
|
332
|
+
ElMessage.warning("浏览器不支持拖拽上传");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (item) {
|
|
336
|
+
DirectoryEntryList.push(item);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (DirectoryEntryList.length > 0) {
|
|
340
|
+
for (let index = 0; index < DirectoryEntryList.length; index++) {
|
|
341
|
+
let item = DirectoryEntryList[index];
|
|
342
|
+
if (item) {
|
|
343
|
+
//获取文件夹目录
|
|
344
|
+
let FileTree = await getFileTree(item);
|
|
345
|
+
// 拿到目录下的所有文件
|
|
346
|
+
if (Array.isArray(FileTree)) {
|
|
347
|
+
//展平文件夹
|
|
348
|
+
flattenArray(FileTree, fileList);
|
|
349
|
+
} else {
|
|
350
|
+
//方便后续处理,单文件时也包装成数组
|
|
351
|
+
fileList.push(FileTree);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return fileList;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 添加 class, 防止拖拽穿透到子元素上
|
|
362
|
+
const addDragClass = () => {
|
|
363
|
+
dropArea.classList.add('dragging-over');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 拖拽完成, 移除 class
|
|
367
|
+
const removeDragClass = () => {
|
|
368
|
+
dropArea.classList.remove('dragging-over');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 获取文件
|
|
373
|
+
*/
|
|
374
|
+
function fileSync(item) {
|
|
375
|
+
return new Promise((resolve, reject) => {
|
|
376
|
+
item.file(res => {
|
|
377
|
+
resolve(res);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 读取文件夹下的文件
|
|
383
|
+
function readEntriesSync(dirReader) {
|
|
384
|
+
return new Promise((rel, rej) => {
|
|
385
|
+
dirReader.readEntries(res => {
|
|
386
|
+
rel(res);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 展平数组
|
|
393
|
+
* @param array 需要展平的数组
|
|
394
|
+
* @param result 展平后的数组
|
|
395
|
+
*
|
|
396
|
+
*/
|
|
397
|
+
function flattenArray(array, result) {
|
|
398
|
+
for (let i = 0; i < array.length; i++) {
|
|
399
|
+
if (Array.isArray(array[i])) {
|
|
400
|
+
flattenArray(array[i], result);
|
|
401
|
+
} else {
|
|
402
|
+
result.push(array[i]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 获取文件目录结构树
|
|
409
|
+
*
|
|
410
|
+
*/
|
|
411
|
+
async function getFileTree(item) {
|
|
412
|
+
let path = item.fullPath || "";
|
|
413
|
+
let dir = [];
|
|
414
|
+
if (item.isFile) {
|
|
415
|
+
let resFile = await fileSync(item);
|
|
416
|
+
resFile.dropUploadPath = path;
|
|
417
|
+
return resFile;
|
|
418
|
+
// item 为文件夹时
|
|
419
|
+
} else if (item.isDirectory) {
|
|
420
|
+
let dirReader = item.createReader();
|
|
421
|
+
let entries = await readEntriesSync(dirReader);
|
|
422
|
+
// 如果目录为空,返回当前路径或目录信息
|
|
423
|
+
if (entries.length === 0) {
|
|
424
|
+
return { path, isEmptyDirectory: true };
|
|
425
|
+
}
|
|
426
|
+
for (let i = 0; i < entries.length; i++) {
|
|
427
|
+
let proItem = await getFileTree(entries[i]);
|
|
428
|
+
dir.push(proItem);
|
|
429
|
+
}
|
|
430
|
+
return dir;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// 打开上传文件 dialog
|
|
436
|
+
const openUploadDialog = () => {
|
|
437
|
+
visible.value = true;
|
|
438
|
+
uploadMode.value = 'file';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 打开上传文件夹 dialog
|
|
442
|
+
const openUploadFolderDialog = () => {
|
|
443
|
+
visible.value = true;
|
|
444
|
+
uploadMode.value = 'folder';
|
|
445
|
+
nextTick(() => {
|
|
446
|
+
document.getElementsByClassName('el-upload__input')[0].webkitdirectory = true;
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 上传文件前的一些操作
|
|
452
|
+
* @param param
|
|
453
|
+
*/
|
|
454
|
+
const beforeUpload = (param) => {
|
|
455
|
+
uploadFile(param.file, param.uploadBasePath);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// 文件上传操作.
|
|
459
|
+
const uploadFile = async (file, uploadBasePath) => {
|
|
460
|
+
const fileIndex = uploadIndex++;
|
|
461
|
+
|
|
462
|
+
uploadBasePath = uploadBasePath || currentPath.value;
|
|
463
|
+
|
|
464
|
+
let uploadToPath = uploadBasePath;
|
|
465
|
+
|
|
466
|
+
// 如果包含 webkitRelativePath, 则表示是文件夹上传, 需要获取文件完整路径
|
|
467
|
+
if (file.webkitRelativePath || file.dropUploadPath) {
|
|
468
|
+
let pathStr = file.webkitRelativePath || file.dropUploadPath;
|
|
469
|
+
if (!pathStr.startsWith('/')) {
|
|
470
|
+
pathStr = '/' + pathStr;
|
|
471
|
+
}
|
|
472
|
+
let pathList = pathStr.split('/');
|
|
473
|
+
pathList.forEach((item, index) => {
|
|
474
|
+
let isFirstItem = 0 === index;
|
|
475
|
+
let isLastItem = pathList.length - 1 === index;
|
|
476
|
+
|
|
477
|
+
if (isFirstItem || isLastItem) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (item) {
|
|
482
|
+
uploadToPath += ('/' + item);
|
|
483
|
+
}
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let param = {
|
|
488
|
+
storageKey: storageKey.value,
|
|
489
|
+
path: removeDuplicateSeparator(uploadToPath),
|
|
490
|
+
name: file.name,
|
|
491
|
+
size: file.size
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log('[upload] 当前上传信息:', param, ', 当前同时上传文件数:',uploadProgressInfoStatistics.value.totalUploading, '限制同时上传文件数:', maxFileUploads);
|
|
495
|
+
if (uploadProgressInfoStatistics.value.totalUploading >= maxFileUploads) {
|
|
496
|
+
console.log(`[upload] 上传文件数超出 ${maxFileUploads}, 等待上传`);
|
|
497
|
+
waitingFileList.push({
|
|
498
|
+
index: fileIndex,
|
|
499
|
+
file: file,
|
|
500
|
+
uploadBasePath: uploadBasePath,
|
|
501
|
+
});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let uploadFileInfo = {
|
|
506
|
+
name: file.name,
|
|
507
|
+
size: file.size,
|
|
508
|
+
speed: '-',
|
|
509
|
+
progress: 0,
|
|
510
|
+
loaded: 0,
|
|
511
|
+
status: 'uploading',
|
|
512
|
+
startTime: Date.now(),
|
|
513
|
+
file: file,
|
|
514
|
+
index: fileIndex
|
|
515
|
+
}
|
|
516
|
+
uploadingFileList.push(uploadFileInfo);
|
|
517
|
+
cancelTokenSourceMap.set(fileIndex, axios.CancelToken.source());
|
|
518
|
+
uploadingFileMap.set(fileIndex, uploadingFileList[uploadingFileList.length - 1]);
|
|
519
|
+
|
|
520
|
+
uploadFileReq(param).then((res) => {
|
|
521
|
+
// 监听取消上传事件
|
|
522
|
+
const { on } = useEventBus(`cancel-upload-${fileIndex}`);
|
|
523
|
+
on(() => {
|
|
524
|
+
let cancelTokenSource = cancelTokenSourceMap.get(fileIndex);
|
|
525
|
+
if (cancelTokenSource) {
|
|
526
|
+
cancelTokenSource.cancel();
|
|
527
|
+
// 从上传列表中移除当前要取消的文件
|
|
528
|
+
uploadingFileList.find((item, index) => {
|
|
529
|
+
let b = item.name === file.name;
|
|
530
|
+
if (b) {
|
|
531
|
+
uploadingFileList.splice(index, 1);
|
|
532
|
+
}
|
|
533
|
+
return b;
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
if (storageConfigStore.folderConfig.metadata.uploadType === 'PROXY') {
|
|
539
|
+
fileProxyUpload(file, res.data, fileIndex);
|
|
540
|
+
} else if (storageConfigStore.folderConfig.metadata.uploadType === 'S3') {
|
|
541
|
+
s3FileUpload(file, res.data, fileIndex);
|
|
542
|
+
} else if (storageConfigStore.folderConfig.metadata.uploadType === 'MICROSOFT') {
|
|
543
|
+
onedriveUpload(file, res.data, fileIndex);
|
|
544
|
+
} else if (storageConfigStore.folderConfig.metadata.uploadType === 'UPYUN') {
|
|
545
|
+
upyunFileUpload(file, res.data, fileIndex);
|
|
546
|
+
}
|
|
547
|
+
}).catch((err) => {
|
|
548
|
+
baseOnUploadError(fileIndex, err)
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const silentUploadFile = (file) => {
|
|
553
|
+
let uploadToPath = currentPath.value;
|
|
554
|
+
let param = {
|
|
555
|
+
storageKey: storageKey.value,
|
|
556
|
+
path: removeDuplicateSeparator(uploadToPath),
|
|
557
|
+
name: file.name,
|
|
558
|
+
size: file.size
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
uploadFileReq(param).then((res) => {
|
|
562
|
+
const fileIndex = uploadIndex++;
|
|
563
|
+
|
|
564
|
+
const loadingInstance = ElLoading.service({
|
|
565
|
+
text: '上传中...',
|
|
566
|
+
background: 'rgba(0, 0, 0, .3)'
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
const onUploadFinish = () => {
|
|
570
|
+
ElMessage.success('保存成功');
|
|
571
|
+
loadingInstance.close();
|
|
572
|
+
}
|
|
573
|
+
const onUploadError = (err) => {
|
|
574
|
+
ElMessage.error('保存失败: ' + parseErrMessage(err));
|
|
575
|
+
loadingInstance.close();
|
|
576
|
+
}
|
|
577
|
+
if (storageConfigStore.folderConfig.metadata.uploadType === 'PROXY') {
|
|
578
|
+
fileProxyUpload(file, res.data, fileIndex, true, onUploadFinish, onUploadError);
|
|
579
|
+
} else if (storageConfigStore.folderConfig.metadata.uploadType === 'S3') {
|
|
580
|
+
s3FileUpload(file, res.data, fileIndex, true, onUploadFinish, onUploadError);
|
|
581
|
+
} else if (storageConfigStore.folderConfig.metadata.uploadType === 'MICROSOFT') {
|
|
582
|
+
onedriveUpload(file, res.data, fileIndex, true, onUploadFinish, onUploadError);
|
|
583
|
+
} else if (storageConfigStore.folderConfig.metadata.uploadType === 'UPYUN') {
|
|
584
|
+
upyunFileUpload(file, res.data, fileIndex, true, onUploadFinish, onUploadError);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 服务器代理上传
|
|
590
|
+
const fileProxyUpload = (file, uploadUrl, fileIndex, silentMode=false, onSuccess, onError) => {
|
|
591
|
+
let formData = new FormData();
|
|
592
|
+
formData.append("file", file);
|
|
593
|
+
selfAxios({
|
|
594
|
+
method: "PUT",
|
|
595
|
+
url: uploadUrl,
|
|
596
|
+
data: formData,
|
|
597
|
+
config: {
|
|
598
|
+
headers: {
|
|
599
|
+
'Content-Type': 'multipart/form-data'
|
|
600
|
+
},
|
|
601
|
+
cancelToken: cancelTokenSourceMap.get(fileIndex)?.token,
|
|
602
|
+
onUploadProgress: silentMode ? null : (progressEvent) => {
|
|
603
|
+
baseOnUploadProgress(progressEvent, fileIndex, true);
|
|
604
|
+
},
|
|
605
|
+
containToken: true
|
|
606
|
+
}
|
|
607
|
+
}).then(() => {
|
|
608
|
+
if (onSuccess) {
|
|
609
|
+
onSuccess();
|
|
610
|
+
} else {
|
|
611
|
+
baseOnUploadFinish(fileIndex);
|
|
612
|
+
}
|
|
613
|
+
}).catch((err) => {
|
|
614
|
+
if (onError) {
|
|
615
|
+
onError(err);
|
|
616
|
+
} else {
|
|
617
|
+
baseOnUploadError(fileIndex, err);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// s3 通用上传
|
|
623
|
+
const s3FileUpload = (file, uploadUrl, fileIndex, silentMode=false, onSuccess, onError) => {
|
|
624
|
+
// 处理 uploadUrl 中的 Content-Type 参数,如果则获取赋值到一个变量,用于请求时设置,并从 url 中移除这个参数
|
|
625
|
+
let contentType = '';
|
|
626
|
+
if (uploadUrl.includes('Content-Type=')) {
|
|
627
|
+
let url = new URL(uploadUrl);
|
|
628
|
+
contentType = url.searchParams.get('Content-Type');
|
|
629
|
+
url.searchParams.delete('Content-Type');
|
|
630
|
+
uploadUrl = url.toString();
|
|
631
|
+
}
|
|
632
|
+
axios.put(uploadUrl, file, {
|
|
633
|
+
withCredentials: false,
|
|
634
|
+
headers: {
|
|
635
|
+
'Content-Type': contentType
|
|
636
|
+
},
|
|
637
|
+
cancelToken: cancelTokenSourceMap.get(fileIndex)?.token,
|
|
638
|
+
onUploadProgress: silentMode ? null : (progressEvent) => {
|
|
639
|
+
baseOnUploadProgress(progressEvent, fileIndex);
|
|
640
|
+
}
|
|
641
|
+
}).then(() => {
|
|
642
|
+
if (onSuccess) {
|
|
643
|
+
onSuccess();
|
|
644
|
+
} else {
|
|
645
|
+
baseOnUploadFinish(fileIndex);
|
|
646
|
+
}
|
|
647
|
+
}).catch((err) => {
|
|
648
|
+
if (onError) {
|
|
649
|
+
onError(err);
|
|
650
|
+
} else {
|
|
651
|
+
baseOnUploadError(fileIndex, err);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// OneDrive SharePoint 上传
|
|
657
|
+
const onedriveUpload = (file, uploadUrl, fileIndex, silentMode=false, onSuccess, onError) => {
|
|
658
|
+
let index = 1; // 当前块数
|
|
659
|
+
let start = 0; // 开始字节数
|
|
660
|
+
let end = 0; // 结束字节数
|
|
661
|
+
let fileSize = file.size; // 文件大小
|
|
662
|
+
const MAX_FILE_SIZE = 104857599; // 每块大小 100M
|
|
663
|
+
|
|
664
|
+
if (fileSize === 0) {
|
|
665
|
+
baseOnUploadError(fileIndex, '当前存储类型不支持上传空文件');
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// 分块上传
|
|
670
|
+
const uploadBlock = () => {
|
|
671
|
+
// 计算每块的开始和结束位置
|
|
672
|
+
if (start + MAX_FILE_SIZE >= fileSize) {
|
|
673
|
+
end = fileSize;
|
|
674
|
+
} else {
|
|
675
|
+
end = start + MAX_FILE_SIZE;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (index > 1) {
|
|
679
|
+
cancelTokenSourceMap.set(fileIndex, axios.CancelToken.source());
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// 截取文件每块上传
|
|
683
|
+
let fileBlock = file.slice(start, end);
|
|
684
|
+
axios.put(`${uploadUrl}`, fileBlock, {
|
|
685
|
+
cancelToken: cancelTokenSourceMap.get(fileIndex)?.token,
|
|
686
|
+
timeout: 10000000,
|
|
687
|
+
headers: {
|
|
688
|
+
'Content-Type': 'application/octet-stream',
|
|
689
|
+
'Content-Range': `bytes ${start}-${end - 1}/${file.size}`
|
|
690
|
+
},
|
|
691
|
+
type: 'sync',
|
|
692
|
+
withCredentials: false,
|
|
693
|
+
onUploadProgress: silentMode ? null : (progressEvent) => {
|
|
694
|
+
if (progressEvent.lengthComputable || progressEvent.loaded) {
|
|
695
|
+
let uploadFileInfo = uploadingFileMap.get(fileIndex);
|
|
696
|
+
|
|
697
|
+
const realLoaded = progressEvent.loaded + start;
|
|
698
|
+
|
|
699
|
+
uploadFileInfo.size = fileSize;
|
|
700
|
+
uploadFileInfo.loaded = realLoaded;
|
|
701
|
+
uploadFileInfo.progress = Math.round(realLoaded / fileSize * 100);
|
|
702
|
+
uploadFileInfo.speed = fileSizeFormat(Math.round(realLoaded / (Date.now() - uploadFileInfo.startTime) * 1000));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}).then((response) => {
|
|
706
|
+
if (response.status === 202) {
|
|
707
|
+
start += MAX_FILE_SIZE;
|
|
708
|
+
index += 1;
|
|
709
|
+
uploadBlock();
|
|
710
|
+
} else if (response.status === 201 || response.status === 200) {
|
|
711
|
+
if (onSuccess) {
|
|
712
|
+
onSuccess();
|
|
713
|
+
} else {
|
|
714
|
+
baseOnUploadFinish(fileIndex);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}).catch((e) => {
|
|
718
|
+
if (onError) {
|
|
719
|
+
onError(e);
|
|
720
|
+
} else {
|
|
721
|
+
baseOnUploadError(fileIndex, e);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
uploadBlock();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// 又拍云上传
|
|
730
|
+
const upyunFileUpload = (file, uploadUrl, fileIndex, silentMode=false, onSuccess, onError) => {
|
|
731
|
+
let uploadInfo = JSON.parse(uploadUrl);
|
|
732
|
+
let formData = new FormData();
|
|
733
|
+
formData.append('name', file.name);
|
|
734
|
+
formData.append("authorization", uploadInfo.signature);
|
|
735
|
+
formData.append("policy", uploadInfo.policy);
|
|
736
|
+
formData.append("file", file);
|
|
737
|
+
|
|
738
|
+
axios.post(uploadInfo.url, formData, {
|
|
739
|
+
withCredentials: false,
|
|
740
|
+
cancelToken: cancelTokenSourceMap.get(fileIndex)?.token,
|
|
741
|
+
onUploadProgress: silentMode ? null : (progressEvent) => {
|
|
742
|
+
baseOnUploadProgress(progressEvent, fileIndex);
|
|
743
|
+
}
|
|
744
|
+
}).then(() => {
|
|
745
|
+
if (onSuccess) {
|
|
746
|
+
onSuccess();
|
|
747
|
+
} else {
|
|
748
|
+
baseOnUploadFinish(fileIndex);
|
|
749
|
+
}
|
|
750
|
+
}).catch((err) => {
|
|
751
|
+
if (onError) {
|
|
752
|
+
onError(err);
|
|
753
|
+
} else {
|
|
754
|
+
baseOnUploadError(fileIndex, err);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const parseErrMessage = (err) => {
|
|
760
|
+
if (!err) {
|
|
761
|
+
return '上传失败: 未知错误';
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const isAxiosErr = typeof axios?.isAxiosError === 'function' ? axios.isAxiosError(err) : err?.name === 'AxiosError';
|
|
765
|
+
|
|
766
|
+
const extractRequestOrigin = (config) => {
|
|
767
|
+
if (!config) {
|
|
768
|
+
return '';
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const { url, baseURL } = config;
|
|
772
|
+
|
|
773
|
+
const resolveUrl = () => {
|
|
774
|
+
if (baseURL) {
|
|
775
|
+
if (url?.startsWith('http')) {
|
|
776
|
+
return url;
|
|
777
|
+
}
|
|
778
|
+
try {
|
|
779
|
+
return new URL(url || '', baseURL).toString();
|
|
780
|
+
} catch (_) {
|
|
781
|
+
try {
|
|
782
|
+
if (typeof window !== 'undefined') {
|
|
783
|
+
return new URL(baseURL, window.location.origin).toString();
|
|
784
|
+
}
|
|
785
|
+
} catch (_) {
|
|
786
|
+
return baseURL;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (url?.startsWith('http')) {
|
|
792
|
+
return url;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (typeof window !== 'undefined') {
|
|
796
|
+
try {
|
|
797
|
+
return new URL(url || '', window.location.origin).toString();
|
|
798
|
+
} catch (_) {
|
|
799
|
+
return '';
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return '';
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
try {
|
|
807
|
+
const resolvedUrl = resolveUrl();
|
|
808
|
+
if (!resolvedUrl) {
|
|
809
|
+
return '';
|
|
810
|
+
}
|
|
811
|
+
return new URL(resolvedUrl).origin;
|
|
812
|
+
} catch (_) {
|
|
813
|
+
return '';
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
if (isAxiosErr) {
|
|
818
|
+
const targetOrigin = extractRequestOrigin(err.config);
|
|
819
|
+
|
|
820
|
+
if (!err.response) {
|
|
821
|
+
const lowerMessage = err.message?.toLowerCase?.();
|
|
822
|
+
if (err.code === 'ERR_NETWORK' || lowerMessage?.includes('network error')) {
|
|
823
|
+
return `上传失败: 浏览器拦截了跨域请求或目标服务不可达${targetOrigin ? ` (${targetOrigin})` : ''},请检查存储服务的跨域配置或网络状态`;
|
|
824
|
+
}
|
|
825
|
+
return `上传失败: 网络异常或目标服务无响应${targetOrigin ? ` (${targetOrigin})` : ''}`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const status = err.response?.status;
|
|
829
|
+
const statusText = err.response?.statusText;
|
|
830
|
+
const data = err.response?.data;
|
|
831
|
+
|
|
832
|
+
const context = [];
|
|
833
|
+
if (status) {
|
|
834
|
+
context.push(`HTTP ${status}${statusText ? ` ${statusText}` : ''}`);
|
|
835
|
+
}
|
|
836
|
+
if (targetOrigin) {
|
|
837
|
+
context.push(`target ${targetOrigin}`);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
let detail = '';
|
|
841
|
+
if (data) {
|
|
842
|
+
if (typeof data === 'string') {
|
|
843
|
+
detail = data;
|
|
844
|
+
} else if (typeof data === 'object') {
|
|
845
|
+
if (data.code) {
|
|
846
|
+
context.push(`code ${data.code}`);
|
|
847
|
+
}
|
|
848
|
+
detail = data.msg || data.message || data.error || '';
|
|
849
|
+
if (!detail) {
|
|
850
|
+
try {
|
|
851
|
+
detail = JSON.stringify(data);
|
|
852
|
+
} catch (_) {
|
|
853
|
+
detail = '';
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const contextStr = context.length ? ` (${context.join(', ')})` : '';
|
|
860
|
+
return `上传失败${contextStr}${detail ? `: ${detail}` : ''}`;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (typeof err === 'string') {
|
|
864
|
+
return err;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return err?.message ? `上传失败: ${err.message}` : '上传失败';
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// 通用上传结束设置.
|
|
871
|
+
const baseOnUploadError = (fileIndex, err) => {
|
|
872
|
+
let uploadFileInfo = uploadingFileMap.get(fileIndex);
|
|
873
|
+
uploadFileInfo.status = 'error';
|
|
874
|
+
uploadFileInfo.endTime = Date.now();
|
|
875
|
+
uploadFileInfo.msg = parseErrMessage(err);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// 通用上传结束设置.
|
|
879
|
+
const baseOnUploadFinish = (fileIndex) => {
|
|
880
|
+
let uploadFileInfo = uploadingFileMap.get(fileIndex);
|
|
881
|
+
uploadFileInfo.progress = 100;
|
|
882
|
+
uploadFileInfo.status = 'finished';
|
|
883
|
+
uploadFileInfo.endTime = Date.now();
|
|
884
|
+
uploadFileInfo.msg = '上传成功';
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// 通用上传进度条处理
|
|
888
|
+
const baseOnUploadProgress = (progressEvent, fileIndex, isProxyUpload) => {
|
|
889
|
+
let uploadFileInfo = uploadingFileMap.get(fileIndex);
|
|
890
|
+
uploadFileInfo.size = progressEvent.total;
|
|
891
|
+
uploadFileInfo.loaded = progressEvent.loaded;
|
|
892
|
+
uploadFileInfo.progress = Math.round(progressEvent.loaded / progressEvent.total * 100);
|
|
893
|
+
uploadFileInfo.speed = fileSizeFormat(Math.round(progressEvent.loaded / (Date.now() - uploadFileInfo.startTime) * 1000));
|
|
894
|
+
|
|
895
|
+
console.log('[upload] uploadFileInfo', uploadFileInfo, isProxyUpload);
|
|
896
|
+
if (isProxyUpload && uploadFileInfo.progress === 100) {
|
|
897
|
+
uploadFileInfo.msg = '上传完成, 服务器中转中...';
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// 取消上传请求
|
|
902
|
+
const cancelUpload = (item) => {
|
|
903
|
+
ElMessageBox.confirm(`是否确定取消文件 <span class="text-blue-400">${item.name}</span> 上传?`, '提示', {
|
|
904
|
+
confirmButtonText: '确定',
|
|
905
|
+
cancelButtonText: '返回',
|
|
906
|
+
type: 'warning',
|
|
907
|
+
dangerouslyUseHTMLString: true,
|
|
908
|
+
callback: action => {
|
|
909
|
+
if (action === 'confirm') {
|
|
910
|
+
useEventBus(`cancel-upload-${item.index}`).emit();
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// 上传文件信息
|
|
917
|
+
const uploadProgressInfoStatistics = computed(() => {
|
|
918
|
+
let totalSize = uploadingFileList.length;
|
|
919
|
+
let totalUploading = 0;
|
|
920
|
+
let totalFinish = 0;
|
|
921
|
+
uploadingFileList.forEach((item) => {
|
|
922
|
+
if (item.status === 'uploading') {
|
|
923
|
+
totalUploading++;
|
|
924
|
+
} else if (item.status === 'finished') {
|
|
925
|
+
totalFinish++;
|
|
926
|
+
}
|
|
927
|
+
})
|
|
928
|
+
|
|
929
|
+
let totalUploadingAndWaiting = totalUploading + waitingFileList.length;
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
return {
|
|
933
|
+
totalSize,
|
|
934
|
+
totalUploading,
|
|
935
|
+
totalFinish,
|
|
936
|
+
totalUploadingAndWaiting
|
|
937
|
+
};
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
// 上传文件排序结果
|
|
941
|
+
const uploadProgressInfoSorted = computed(() => {
|
|
942
|
+
let result = [];
|
|
943
|
+
result.push(...uploadingFileList);
|
|
944
|
+
waitingFileList.forEach((item) => {
|
|
945
|
+
result.push({
|
|
946
|
+
name: item.file.name,
|
|
947
|
+
size: item.file.size,
|
|
948
|
+
status: 'waiting',
|
|
949
|
+
msg: '排队中...',
|
|
950
|
+
index: item.index
|
|
951
|
+
})
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
result.sort((a, b) => {
|
|
955
|
+
let aStatus = a.status;
|
|
956
|
+
let bStatus = b.status;
|
|
957
|
+
if (aStatus !== bStatus) {
|
|
958
|
+
return uploadFileTypeSortMap[aStatus] - uploadFileTypeSortMap[bStatus];
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (a.startTime !== b.startTime) {
|
|
962
|
+
return a.startTime - b.startTime;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// 如果状态一样,则按照开始时间排序
|
|
966
|
+
return a.endTime - b.endTime;
|
|
967
|
+
});
|
|
968
|
+
console.log('[upload] uploadProgressInfoSorted', result);
|
|
969
|
+
// return mockUploadData;
|
|
970
|
+
return result;
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
if (!isInitWatch) {
|
|
974
|
+
watch(() => uploadProgressInfoStatistics.value.totalUploading, (newValue) => {
|
|
975
|
+
if (newValue < maxFileUploads) {
|
|
976
|
+
console.log('[upload] 检测到上传中的文件个数小于最大上传限制.');
|
|
977
|
+
if (waitingFileList.length === 0) {
|
|
978
|
+
console.log('[upload] 等待上传的文件数为 0, 无需继续上传.');
|
|
979
|
+
} else {
|
|
980
|
+
let spliceList = waitingFileList.splice(0, 1);
|
|
981
|
+
let fileItem = spliceList[0];
|
|
982
|
+
beforeUpload({
|
|
983
|
+
file: fileItem.file,
|
|
984
|
+
uploadBasePath: fileItem.uploadBasePath
|
|
985
|
+
});
|
|
986
|
+
console.log('[upload] 开始从等待队列中获取上传文件: ', fileItem.file.name);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
isInitWatch = true;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const removeUploadFileByIndex = (fileIndex) => {
|
|
994
|
+
if (fileIndex === null || fileIndex === undefined) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
let removeIndex = uploadingFileList.findIndex((item, index) => {
|
|
999
|
+
if (item.index === fileIndex) {
|
|
1000
|
+
uploadingFileList.splice(index, 1);
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
})
|
|
1004
|
+
|
|
1005
|
+
if (removeIndex === -1) {
|
|
1006
|
+
removeIndex = waitingFileList.findIndex((item, index) => {
|
|
1007
|
+
if (item.index === fileIndex) {
|
|
1008
|
+
waitingFileList.splice(index, 1);
|
|
1009
|
+
return true;
|
|
1010
|
+
}
|
|
1011
|
+
})
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (removeIndex !== -1) {
|
|
1015
|
+
uploadingFileMap.delete(fileIndex)
|
|
1016
|
+
cancelTokenSourceMap.delete(fileIndex);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const clearALlFinishedUploadFile = () => {
|
|
1021
|
+
let deleteCount = 0;
|
|
1022
|
+
for (let i = uploadingFileList.length - 1; i >= 0; i--) {
|
|
1023
|
+
let item = uploadingFileList[i];
|
|
1024
|
+
if (item.status === 'finished') {
|
|
1025
|
+
deleteCount++;
|
|
1026
|
+
uploadingFileList.splice(i, 1);
|
|
1027
|
+
uploadingFileMap.delete(item.index)
|
|
1028
|
+
cancelTokenSourceMap.delete(item.index);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return deleteCount;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const retryUpload = (item) => {
|
|
1035
|
+
console.log('[upload] 重新上传文件', item);
|
|
1036
|
+
removeUploadFileByIndex(item.index);
|
|
1037
|
+
uploadFile(item.file);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
visible, uploadMode, openUploadDialog, openUploadFolderDialog, cancelUpload, dropState, listenDropFile,
|
|
1042
|
+
beforeUpload, uploadFile, uploadProgressInfoSorted, uploadProgressInfoStatistics,
|
|
1043
|
+
clearALlFinishedUploadFile, removeUploadFileByIndex, retryUpload, silentUploadFile
|
|
1044
|
+
}
|
|
1045
|
+
}
|