|
|
@@ -521,7 +521,8 @@ async function fetchOssStsToken(fetchOptions = {}) {
|
|
|
function buildOssObjectKey(prefix, file) {
|
|
|
const dir = String(prefix || "uploads/").replace(/^\/+/, "");
|
|
|
const normalizedDir = dir.endsWith("/") ? dir : `${dir}/`;
|
|
|
- const ext = getFileExtension(file.name, "image");
|
|
|
+ const fileKind = String(file?.type || "").startsWith("video/") ? "video" : "image";
|
|
|
+ const ext = getFileExtension(file.name, fileKind);
|
|
|
const safeName = (file.name || "file").replace(/[/\\?%*:|"<>]/g, "_").replace(/\s+/g, "_");
|
|
|
const base = safeName.includes(".") ? safeName.slice(0, safeName.lastIndexOf(".")) : safeName;
|
|
|
return `${normalizedDir}${Date.now()}_${Math.random().toString(36).slice(2, 10)}_${base || "file"}.${ext}`;
|
|
|
@@ -668,12 +669,25 @@ async function requestOssFinalizeAudit(file, objectKey, fetchOptions = {}) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 官方相册:先 GET /upload/oss/sts-token,再前端直传 OSS
|
|
|
+ * STS 直传 OSS 后 POST finalize 审核(单文件核心,无弹层)
|
|
|
+ * @param {File} file
|
|
|
+ * @param {{ signal?: AbortSignal }} [fetchOptions]
|
|
|
+ * @returns {Promise<{ fileUrl: string; downloadUrl: string }>}
|
|
|
+ */
|
|
|
+async function putFileToOssWithStsAndFinalize(file, fetchOptions = {}) {
|
|
|
+ const uploaded = await putFileToOssWithSts(file, fetchOptions);
|
|
|
+ const audit = await requestOssFinalizeAudit(file, uploaded.objectKey, fetchOptions);
|
|
|
+ const downloadUrl = audit.downloadUrl?.trim() || uploaded.downloadUrl;
|
|
|
+ return { fileUrl: uploaded.fileUrl, downloadUrl };
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 单文件 OSS + 审核,带可选全局上传弹层
|
|
|
* @param {File} file
|
|
|
* @param {{ showLoading?: boolean; skipSimpleUploadOverlay?: boolean; uploadSuccessMessage?: string | null; uploadOverlayTitle?: string }} [options]
|
|
|
* @returns {Promise<{ fileUrl: string; downloadUrl: string }>}
|
|
|
*/
|
|
|
-export async function uploadFileToOssStsWithDownloadMeta(file, options = {}) {
|
|
|
+async function uploadSingleFileWithOssOverlay(file, options = {}) {
|
|
|
const { showLoading = false, skipSimpleUploadOverlay = false, uploadSuccessMessage, uploadOverlayTitle } = options;
|
|
|
if (!(file instanceof File)) {
|
|
|
throw new Error("请选择要上传的文件");
|
|
|
@@ -686,13 +700,7 @@ export async function uploadFileToOssStsWithDownloadMeta(file, options = {}) {
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- const runUpload = async signal => {
|
|
|
- const fetchOpts = signal ? { signal } : {};
|
|
|
- const uploaded = await putFileToOssWithSts(file, fetchOpts);
|
|
|
- const audit = await requestOssFinalizeAudit(file, uploaded.objectKey, fetchOpts);
|
|
|
- const downloadUrl = audit.downloadUrl?.trim() || uploaded.downloadUrl;
|
|
|
- return { fileUrl: uploaded.fileUrl, downloadUrl };
|
|
|
- };
|
|
|
+ const runUpload = async signal => putFileToOssWithStsAndFinalize(file, signal ? { signal } : {});
|
|
|
|
|
|
let result;
|
|
|
if (skipSimpleUploadOverlay) {
|
|
|
@@ -731,6 +739,19 @@ export async function uploadFileToOssStsWithDownloadMeta(file, options = {}) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * GET sts-token → OSS 直传 → POST finalize 审核
|
|
|
+ * @param {File} file
|
|
|
+ * @param {{ showLoading?: boolean; skipSimpleUploadOverlay?: boolean; uploadSuccessMessage?: string | null; uploadOverlayTitle?: string }} [options]
|
|
|
+ * @returns {Promise<{ fileUrl: string; downloadUrl: string }>}
|
|
|
+ */
|
|
|
+export async function uploadFileToOssStsWithDownloadMeta(file, options = {}) {
|
|
|
+ return uploadSingleFileWithOssOverlay(file, options);
|
|
|
+}
|
|
|
+
|
|
|
+/** 与 uploadFileToOssStsWithDownloadMeta 相同 */
|
|
|
+export const uploadFileToOssWithDownloadMeta = uploadFileToOssStsWithDownloadMeta;
|
|
|
+
|
|
|
+/**
|
|
|
* POST multipart:字段 file;解析展示用地址与业务保存用的 download_url
|
|
|
* @param {File} file
|
|
|
* @param {{ signal?: AbortSignal }} [fetchOptions]
|
|
|
@@ -852,14 +873,10 @@ async function postFileToSimpleUpload(file, fetchOptions = {}) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 上传文件:图片与视频均走同一接口 POST /upload/simple,formData 键 file。
|
|
|
+ * 上传文件:GET sts-token → OSS 直传 → POST finalize 审核
|
|
|
* @param {File | File[] | FileList} files 浏览器文件对象;支持单个 File、数组或 FileList
|
|
|
* @param {string} [_fileType] 保留参数,兼容旧调用(当前不参与分支)
|
|
|
* @param {{ showLoading?: boolean; skipSimpleUploadOverlay?: boolean; uploadSuccessMessage?: string | null; uploadOverlayTitle?: string }} [options]
|
|
|
- * showLoading:在未使用全局上传弹层时,用 ElMessage 提示上传中
|
|
|
- * skipSimpleUploadOverlay:为 true 时不展示 PopupLoading(不弹「上传成功」)
|
|
|
- * uploadSuccessMessage:传给弹层,`null` 表示上传成功不 toast(默认「上传成功」)
|
|
|
- * uploadOverlayTitle:弹层标题
|
|
|
* @returns {Promise<string[]>} 上传成功后的文件 URL 列表
|
|
|
*/
|
|
|
export async function uploadFilesToOss(files, _fileType, options = {}) {
|
|
|
@@ -878,9 +895,10 @@ export async function uploadFilesToOss(files, _fileType, options = {}) {
|
|
|
try {
|
|
|
const runUpload = async signal => {
|
|
|
const uploadedUrls = [];
|
|
|
+ const fetchOpts = signal ? { signal } : {};
|
|
|
for (const file of fileArr) {
|
|
|
- const url = await postFileToSimpleUpload(file, signal ? { signal } : {});
|
|
|
- uploadedUrls.push(url);
|
|
|
+ const { fileUrl, downloadUrl } = await putFileToOssWithStsAndFinalize(file, fetchOpts);
|
|
|
+ uploadedUrls.push(downloadUrl || fileUrl);
|
|
|
}
|
|
|
return uploadedUrls;
|
|
|
};
|
|
|
@@ -922,66 +940,6 @@ export async function uploadFilesToOss(files, _fileType, options = {}) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 单文件上传:返回列表展示用地址与业务保存用的 `download_url`(响应中无则为空字符串)
|
|
|
- * @param {File} file
|
|
|
- * @param {{ showLoading?: boolean; skipSimpleUploadOverlay?: boolean; uploadSuccessMessage?: string | null; uploadOverlayTitle?: string }} [options]
|
|
|
- * @returns {Promise<{ fileUrl: string; downloadUrl: string }>}
|
|
|
- */
|
|
|
-export async function uploadFileToOssWithDownloadMeta(file, options = {}) {
|
|
|
- const { showLoading = false, skipSimpleUploadOverlay = false, uploadSuccessMessage, uploadOverlayTitle } = options;
|
|
|
- if (!(file instanceof File)) {
|
|
|
- throw new Error("请选择要上传的文件");
|
|
|
- }
|
|
|
-
|
|
|
- let closeLoading = () => {};
|
|
|
- if (showLoading && skipSimpleUploadOverlay) {
|
|
|
- const loading = ElMessage({ message: "上传中...", type: "info", duration: 0, showClose: false });
|
|
|
- closeLoading = () => loading.close();
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- const runUpload = async signal => {
|
|
|
- const { url, downloadUrl } = await postFileToSimpleUploadDetailed(file, signal ? { signal } : {});
|
|
|
- return { fileUrl: url, downloadUrl };
|
|
|
- };
|
|
|
-
|
|
|
- let result;
|
|
|
- if (skipSimpleUploadOverlay) {
|
|
|
- const overlaySignal = useSimpleUploadOverlayStore().getActiveAbortSignal?.();
|
|
|
- result = await runUpload(overlaySignal ?? null);
|
|
|
- } else {
|
|
|
- const overlayOpts =
|
|
|
- uploadSuccessMessage !== undefined || uploadOverlayTitle
|
|
|
- ? {
|
|
|
- title: uploadOverlayTitle,
|
|
|
- successMessage: uploadSuccessMessage
|
|
|
- }
|
|
|
- : undefined;
|
|
|
- result = await withSimpleUploadOverlay(signal => runUpload(signal), overlayOpts);
|
|
|
- }
|
|
|
-
|
|
|
- closeLoading();
|
|
|
- return result;
|
|
|
- } catch (e) {
|
|
|
- closeLoading();
|
|
|
- console.error("上传失败", e);
|
|
|
- if (isUploadUserCancelledError(e)) {
|
|
|
- throw e;
|
|
|
- }
|
|
|
- const msg = e?.message || "上传失败";
|
|
|
- ElMessage.error(msg);
|
|
|
- try {
|
|
|
- if (e && typeof e === "object") {
|
|
|
- Object.defineProperty(e, "__uploadMessageShown", { value: true, enumerable: false, configurable: true });
|
|
|
- }
|
|
|
- } catch (_) {
|
|
|
- /* ignore */
|
|
|
- }
|
|
|
- throw e;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
* 上传单个文件,返回 URL 字符串
|
|
|
* @param {File} file
|
|
|
* @param {string} [fileType]
|
|
|
@@ -1016,7 +974,7 @@ export async function uploadFormDataToOss(formData, fileType, options = {}) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * FormData 走 `/upload/simple` 后,包装成旧组件习惯的 `{ code, msg, data: { fileUrl }, fileUrl }`(并非请求 `/file/uploadMore`)
|
|
|
+ * FormData OSS 上传后,包装成旧组件习惯的 `{ code, msg, data: { fileUrl }, fileUrl }`
|
|
|
* @param {FormData} formData
|
|
|
* @param {{ showLoading?: boolean }} [options]
|
|
|
* @returns {Promise<{ code: number; msg: string; data: { fileUrl: string }; fileUrl: string }>}
|