|
|
@@ -4,9 +4,36 @@ import { ElMessage } from "element-plus";
|
|
|
import { AI_UPLOAD_FILES_PUBLIC_BASE, BASE_AI_URL } from "@/utils/config";
|
|
|
import { withSimpleUploadOverlay } from "@/utils/withSimpleUploadOverlay";
|
|
|
|
|
|
-/** 非 TUS 简单上传接口路径(与 Apifox「上传文件-非TUS」一致;开发环境经 VITE_PROXY /ai-upload 转发) */
|
|
|
+/** 简单上传的路径(不含 base) */
|
|
|
const SIMPLE_UPLOAD_PATH = "/upload/simple";
|
|
|
|
|
|
+/**
|
|
|
+ * 已由 uploadFilesToOss 弹出过 ElMessage,调用方勿重复 error
|
|
|
+ * @param {unknown} err
|
|
|
+ */
|
|
|
+export function isUploadApiErrorAlreadyMessaged(err) {
|
|
|
+ return Boolean(err && typeof err === "object" && /** @type {{ __uploadMessageShown?: boolean }} */ (err).__uploadMessageShown);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 用户取消上传(关闭弹层、AbortSignal)或请求被顶替:不向用户弹出技术性英文(如 signal is aborted without reason)
|
|
|
+ * @param {unknown} err
|
|
|
+ * @returns {boolean}
|
|
|
+ */
|
|
|
+export function isUploadUserCancelledError(err) {
|
|
|
+ if (err == null || typeof err !== "object") return false;
|
|
|
+ const o = /** @type {{ name?: unknown; message?: unknown }} */ (err);
|
|
|
+ if (String(o.name ?? "") === "AbortError") return true;
|
|
|
+ const msg = String(o.message ?? "").toLowerCase();
|
|
|
+ if (!msg) return false;
|
|
|
+ return (
|
|
|
+ msg.includes("signal is aborted") ||
|
|
|
+ msg.includes("the user aborted") ||
|
|
|
+ msg.includes("aborted a request") ||
|
|
|
+ msg.includes("body stream was aborted")
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
/** 从路径或 fileType 解析文件后缀(如 jpg、mp4),用于生成带格式的文件名 */
|
|
|
export function getFileExtension(filePath, fileType) {
|
|
|
const pathPart = (filePath || "").split("?")[0];
|
|
|
@@ -334,14 +361,24 @@ async function postFileToSimpleUpload(file, fetchOptions = {}) {
|
|
|
|
|
|
const { signal } = fetchOptions;
|
|
|
|
|
|
- const res = await fetch(`${base}${SIMPLE_UPLOAD_PATH}`, {
|
|
|
- method: "POST",
|
|
|
- headers,
|
|
|
- /** 不带跨域 Cookie,减轻上传服务 CORS 要求(鉴权仅用 Authorization) */
|
|
|
- credentials: "omit",
|
|
|
- body: formData,
|
|
|
- signal: signal ?? undefined
|
|
|
- });
|
|
|
+ const uploadUrl = `${base}${SIMPLE_UPLOAD_PATH}`;
|
|
|
+ let res;
|
|
|
+ try {
|
|
|
+ res = await fetch(uploadUrl, {
|
|
|
+ method: "POST",
|
|
|
+ headers,
|
|
|
+ /** 不带跨域 Cookie,减轻上传服务 CORS 要求(鉴权仅用 Authorization) */
|
|
|
+ credentials: "omit",
|
|
|
+ body: formData,
|
|
|
+ signal: signal ?? undefined
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ if (isUploadUserCancelledError(err)) {
|
|
|
+ throw err instanceof Error ? err : new Error(String(err ?? "AbortError"));
|
|
|
+ }
|
|
|
+ console.error("[upload/simple] 上传请求失败:", uploadUrl, err);
|
|
|
+ throw new Error("上传失败");
|
|
|
+ }
|
|
|
|
|
|
const rawText = await res.text();
|
|
|
let parsed = null;
|
|
|
@@ -462,11 +499,18 @@ export async function uploadFilesToOss(files, _fileType, options = {}) {
|
|
|
} catch (e) {
|
|
|
closeLoading();
|
|
|
console.error("上传失败", e);
|
|
|
- if (e?.name === "AbortError") {
|
|
|
+ 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;
|
|
|
}
|
|
|
}
|