|
|
@@ -1,14 +1,9 @@
|
|
|
import { useUserStore } from "@/stores/modules/user";
|
|
|
import { ElMessage } from "element-plus";
|
|
|
-import { isAiImageModerationEnabled, uploadFileViaAi, isLikelyVideoFileForAiUpload } from "@/api/modules/aiImageUpload";
|
|
|
+import { AI_UPLOAD_FILES_PUBLIC_BASE, BASE_AI_URL } from "@/utils/config";
|
|
|
|
|
|
-/**
|
|
|
- * 门店/文件相关接口基址(与 src/api/modules/upload.ts 中 httpStoreAlienStore 一致)
|
|
|
- * 若签名接口部署在其它域名,可改为 VITE_API_URL_PLATFORM 等
|
|
|
- */
|
|
|
-function getStoreApiBase() {
|
|
|
- return String(import.meta.env.VITE_API_URL_STORE || "").replace(/\/$/, "");
|
|
|
-}
|
|
|
+/** 非 TUS 简单上传接口路径(与 Apifox「上传文件-非TUS」一致;开发环境经 VITE_PROXY /ai-upload 转发) */
|
|
|
+const SIMPLE_UPLOAD_PATH = "/upload/simple";
|
|
|
|
|
|
/** 从路径或 fileType 解析文件后缀(如 jpg、mp4),用于生成带格式的文件名 */
|
|
|
export function getFileExtension(filePath, fileType) {
|
|
|
@@ -25,34 +20,6 @@ export function getFileExtension(filePath, fileType) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * GET 获取 OSS 直传签名(与 uni 版 1.js 一致)
|
|
|
- * @returns {Promise<Record<string, any>>}
|
|
|
- */
|
|
|
-async function fetchOssSignature() {
|
|
|
- const base = getStoreApiBase();
|
|
|
- if (!base) {
|
|
|
- throw new Error("未配置 VITE_API_URL_STORE");
|
|
|
- }
|
|
|
- const userStore = useUserStore();
|
|
|
- const res = await fetch(`${base}/oss/direct/new/signature`, {
|
|
|
- method: "GET",
|
|
|
- headers: {
|
|
|
- Authorization: userStore.token || ""
|
|
|
- },
|
|
|
- credentials: "include"
|
|
|
- });
|
|
|
- if (!res.ok) {
|
|
|
- throw new Error("获取签名失败");
|
|
|
- }
|
|
|
- const body = await res.json();
|
|
|
- const data = body.data ?? body;
|
|
|
- if (!data || !data.host) {
|
|
|
- throw new Error(body.msg || "获取签名失败");
|
|
|
- }
|
|
|
- return data;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
* 将入参统一为 File[]
|
|
|
* @param {File | File[] | FileList} input
|
|
|
* @returns {File[]}
|
|
|
@@ -70,58 +37,374 @@ function normalizeFiles(input) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 单个 File 上传到 OSS(POST multipart,字段与 1.js / OSS4 直传一致)
|
|
|
- * @param {Record<string, any>} signRes 签名接口返回
|
|
|
+ * 常见「可直接用于 img/video src」的字段(含蛇形命名)
|
|
|
+ * 注意:勿把 upload_id / filename 放前面——/upload/simple 会返回 upload_id 与完整 URL 并存,
|
|
|
+ * 若先按「裸 id」拼到 /files/ 会得到错误地址,图片会裂图。
|
|
|
+ */
|
|
|
+const URL_LIKE_KEYS = [
|
|
|
+ "download_url",
|
|
|
+ "downloadUrl",
|
|
|
+ "preview_url",
|
|
|
+ "previewUrl",
|
|
|
+ "url",
|
|
|
+ "fileUrl",
|
|
|
+ "file_url",
|
|
|
+ "accessUrl",
|
|
|
+ "access_url",
|
|
|
+ "ossUrl",
|
|
|
+ "cdnUrl",
|
|
|
+ "path",
|
|
|
+ "filePath",
|
|
|
+ "file_path",
|
|
|
+ "objectKey",
|
|
|
+ "object_key",
|
|
|
+ "key",
|
|
|
+ "location",
|
|
|
+ "href"
|
|
|
+];
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将接口返回的路径或 id 规范为可访问 URL
|
|
|
+ * @param {string} raw
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+function normalizeToFileUrl(raw) {
|
|
|
+ const t = String(raw ?? "").trim();
|
|
|
+ if (!t) return "";
|
|
|
+ if (/^https?:\/\//i.test(t)) return t;
|
|
|
+ if (t.startsWith("//")) return `https:${t}`;
|
|
|
+
|
|
|
+ const filesBase = String(AI_UPLOAD_FILES_PUBLIC_BASE || "").replace(/\/$/, "");
|
|
|
+ if (filesBase.startsWith("http")) {
|
|
|
+ try {
|
|
|
+ if (t.startsWith("/")) {
|
|
|
+ return new URL(t, new URL(`${filesBase}/`).origin).href;
|
|
|
+ }
|
|
|
+ if (/^files\//i.test(t)) {
|
|
|
+ return new URL(`/${t.replace(/^\/+/, "")}`, new URL(`${filesBase}/`).origin).href;
|
|
|
+ }
|
|
|
+ // 相对路径 dir/file.ext(无协议)
|
|
|
+ if (t.includes("/") && !t.startsWith("/") && !/^[a-z]+:/i.test(t)) {
|
|
|
+ return `${filesBase}/${t.replace(/^\/+/, "")}`;
|
|
|
+ }
|
|
|
+ // 仅返回 uuid/文件名 等,按「对外文件基址 + 片段」拼接
|
|
|
+ if (!t.includes("/") && !t.includes("\\") && t.length <= 512) {
|
|
|
+ return `${filesBase}/${encodeURIComponent(t)}`;
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ /* ignore */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 从对象上按已知字段取「可能是地址」的字符串
|
|
|
+ * @param {unknown} o
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+function pickUrlLikeFromObject(o) {
|
|
|
+ if (!o || typeof o !== "object" || Array.isArray(o)) return "";
|
|
|
+ const r = /** @type {Record<string, unknown>} */ (o);
|
|
|
+ for (const k of URL_LIKE_KEYS) {
|
|
|
+ const v = r[k];
|
|
|
+ if (typeof v === "string" && v.trim()) {
|
|
|
+ const abs = normalizeToFileUrl(v);
|
|
|
+ if (abs) return abs;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 深度查找第一个 http(s) 字符串
|
|
|
+ * @param {unknown} val
|
|
|
+ * @param {number} depth
|
|
|
+ * @param {number} maxDepth
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+function deepFindHttpUrl(val, depth = 0, maxDepth = 8) {
|
|
|
+ if (depth > maxDepth || val == null) return "";
|
|
|
+ if (typeof val === "string") {
|
|
|
+ const t = val.trim();
|
|
|
+ return /^https?:\/\//i.test(t) ? t : "";
|
|
|
+ }
|
|
|
+ if (Array.isArray(val)) {
|
|
|
+ for (const item of val) {
|
|
|
+ const u = deepFindHttpUrl(item, depth + 1, maxDepth);
|
|
|
+ if (u) return u;
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ if (typeof val === "object") {
|
|
|
+ for (const k of Object.keys(val)) {
|
|
|
+ const u = deepFindHttpUrl(/** @type {Record<string, unknown>} */ (val)[k], depth + 1, maxDepth);
|
|
|
+ if (u) return u;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 从 JSON 体中解析文件访问地址(兼容多种后端约定)
|
|
|
+ * @param {unknown} body
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+function pickUrlFromJsonBody(body) {
|
|
|
+ if (body == null) return "";
|
|
|
+
|
|
|
+ if (typeof body === "string") {
|
|
|
+ return normalizeToFileUrl(body) || (body.trim().startsWith("http") ? body.trim() : "");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof body !== "object" || Array.isArray(body)) {
|
|
|
+ return deepFindHttpUrl(body);
|
|
|
+ }
|
|
|
+
|
|
|
+ const b = /** @type {Record<string, unknown>} */ (body);
|
|
|
+ const unwrap = v => {
|
|
|
+ if (v == null) return null;
|
|
|
+ if (typeof v === "object" && !Array.isArray(v) && /** @type {Record<string, unknown>} */ (v).data !== undefined) {
|
|
|
+ return /** @type {Record<string, unknown>} */ (v).data;
|
|
|
+ }
|
|
|
+ return v;
|
|
|
+ };
|
|
|
+
|
|
|
+ const candidates = [unwrap(b.data), b.data, b.result, unwrap(b.result), b.payload, b.body, b];
|
|
|
+
|
|
|
+ for (const d of candidates) {
|
|
|
+ if (d == null) continue;
|
|
|
+ if (typeof d === "string") {
|
|
|
+ const u = normalizeToFileUrl(d) || (d.trim().startsWith("http") ? d.trim() : "");
|
|
|
+ if (u) return u;
|
|
|
+ } else if (typeof d === "object") {
|
|
|
+ let u = pickUrlLikeFromObject(d);
|
|
|
+ if (u) return u;
|
|
|
+ u = deepFindHttpUrl(d);
|
|
|
+ if (u) return u;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return deepFindHttpUrl(body);
|
|
|
+}
|
|
|
+
|
|
|
+/** 审核服务返回的违规大类 → 面向用户的短说明(与业务文案风格一致) */
|
|
|
+const VIOLATION_CATEGORY_USER_HINT = {
|
|
|
+ GAMBLING: "图片涉及赌博等违规内容",
|
|
|
+ PORNOGRAPHY: "图片涉及色情等违规内容",
|
|
|
+ PORN: "图片涉及色情等违规内容",
|
|
|
+ ADULT: "图片涉及色情等违规内容",
|
|
|
+ SEXUAL: "图片涉及色情等违规内容",
|
|
|
+ DRUGS: "图片涉及毒品等违规内容",
|
|
|
+ DRUG: "图片涉及毒品等违规内容",
|
|
|
+ VIOLENCE: "图片涉及暴力、血腥等违规内容",
|
|
|
+ GORE: "图片涉及暴力、血腥等违规内容",
|
|
|
+ POLITICAL: "图片涉及不当政治或敏感信息",
|
|
|
+ POLITICS: "图片涉及不当政治或敏感信息",
|
|
|
+ SPAM: "图片涉及违规推广或垃圾信息",
|
|
|
+ ABUSE: "图片涉及辱骂、人身攻击等不文明内容",
|
|
|
+ ILLEGAL: "图片含违法违规内容"
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 根据 reason / 关键词生成友好提示(与 aiImageUpload 中审核话术对齐)
|
|
|
+ * @param {string} raw
|
|
|
+ * @returns {string}
|
|
|
+ */
|
|
|
+/** 仅返回「原因」短句,后缀由 formatSimpleUploadModerationMessage 统一拼接 */
|
|
|
+function mapSimpleModerationReasonToTip(raw) {
|
|
|
+ const s = raw == null ? "" : String(raw).trim();
|
|
|
+ if (!s) return "";
|
|
|
+ if (/赌|赌博|casino|gambl|GAMBLING/i.test(s)) return "图片涉及赌博等违规内容";
|
|
|
+ if (/色情|淫秽|porn|sex|PORNOGRAPHY|PORN/i.test(s)) return "图片涉及色情等违规内容";
|
|
|
+ if (/毒品|涉毒|drug|DRUG/i.test(s)) return "图片涉及毒品等违规内容";
|
|
|
+ if (/暴力|血腥|violence|gore|VIOLENCE/i.test(s)) return "图片涉及暴力、血腥等违规内容";
|
|
|
+ if (/辱骂|谩骂|人身攻击|abuse|insult/i.test(s)) return "图片涉及不文明内容";
|
|
|
+ if (/政治|谣言/i.test(s)) return "图片涉及违规信息";
|
|
|
+ if (/广告|spam|营销/i.test(s)) return "图片涉及违规推广内容";
|
|
|
+ if (/违法|违禁|illegal/i.test(s)) return "图片含违法违规内容";
|
|
|
+ if (/黄赌毒/i.test(s)) return "请勿上传涉黄、涉赌、涉毒等违规内容";
|
|
|
+ if (/审核|moderat|violation|flagged|VLM/i.test(s)) return "图片未通过内容审核";
|
|
|
+ return "";
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析 /upload/simple 审核未通过等响应(saved: false + moderation)
|
|
|
+ * @param {unknown} parsed
|
|
|
+ * @returns {string} 非空则可直接作为 ElMessage / Error 文案
|
|
|
+ */
|
|
|
+function formatSimpleUploadModerationMessage(parsed) {
|
|
|
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "";
|
|
|
+ const p = /** @type {Record<string, unknown>} */ (parsed);
|
|
|
+ const notSaved = p.saved === false;
|
|
|
+ const mod = p.moderation;
|
|
|
+ const hasModeration = mod != null && typeof mod === "object";
|
|
|
+ let flagged = false;
|
|
|
+ if (hasModeration) {
|
|
|
+ const results = /** @type {Record<string, unknown>} */ (mod).results;
|
|
|
+ if (Array.isArray(results) && results[0] && typeof results[0] === "object") {
|
|
|
+ flagged = /** @type {Record<string, unknown>} */ (results[0]).flagged === true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!notSaved && !flagged) return "";
|
|
|
+
|
|
|
+ const apiMsg = String(p.message ?? p.msg ?? "").trim();
|
|
|
+
|
|
|
+ let firstResult = null;
|
|
|
+ if (hasModeration) {
|
|
|
+ const results = /** @type {Record<string, unknown>} */ (mod).results;
|
|
|
+ if (Array.isArray(results) && results[0] && typeof results[0] === "object") {
|
|
|
+ firstResult = /** @type {Record<string, unknown>} */ (results[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let reason = "";
|
|
|
+ let firstCategory = "";
|
|
|
+ if (firstResult) {
|
|
|
+ reason = String(firstResult.reason ?? "").trim();
|
|
|
+ const cats = firstResult.violation_categories;
|
|
|
+ if (Array.isArray(cats) && cats[0] && typeof cats[0] === "object") {
|
|
|
+ firstCategory = String(/** @type {Record<string, unknown>} */ (cats[0]).category ?? "")
|
|
|
+ .trim()
|
|
|
+ .toUpperCase();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const suffix = "未通过审核,文件未保存。请更换后重试";
|
|
|
+
|
|
|
+ if (firstCategory && VIOLATION_CATEGORY_USER_HINT[firstCategory]) {
|
|
|
+ return `${VIOLATION_CATEGORY_USER_HINT[firstCategory]},${suffix}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ const fromReason = mapSimpleModerationReasonToTip(reason);
|
|
|
+ if (fromReason) {
|
|
|
+ return `${fromReason.replace(/。$/, "")},${suffix}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (apiMsg) {
|
|
|
+ if (/请更换|更换后|重新选择|换一张/i.test(apiMsg)) return apiMsg;
|
|
|
+ if (/未保存|未通过/.test(apiMsg)) return `${apiMsg.replace(/。$/, "")}。请更换后重试`;
|
|
|
+ return `${apiMsg.replace(/。$/, "")},${suffix}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return "图片未通过内容审核,文件未保存。请更换后重试";
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 业务层 code / success(HTTP 200 但业务失败时常有)
|
|
|
+ * @param {unknown} parsed
|
|
|
+ */
|
|
|
+function assertSimpleUploadBusinessOk(parsed) {
|
|
|
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
|
+ const p = /** @type {Record<string, unknown>} */ (parsed);
|
|
|
+ if (p.success === false) {
|
|
|
+ throw new Error(String(p.msg ?? p.message ?? p.error ?? "上传失败"));
|
|
|
+ }
|
|
|
+ const c = p.code;
|
|
|
+ if (c === undefined || c === null) return;
|
|
|
+ const ok = c === 200 || c === 0 || c === "200" || c === "0";
|
|
|
+ if (!ok) {
|
|
|
+ throw new Error(String(p.msg ?? p.message ?? p.error ?? "上传失败"));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * POST multipart:字段 file;可选 filename(此处不传则服务端用原名)
|
|
|
* @param {File} file
|
|
|
- * @param {string} key 对象 key(含 dir 前缀)
|
|
|
* @returns {Promise<string>} 文件访问 URL
|
|
|
*/
|
|
|
-async function postFileToOss(signRes, file, key) {
|
|
|
+async function postFileToSimpleUpload(file) {
|
|
|
+ const base = String(BASE_AI_URL || "").replace(/\/$/, "");
|
|
|
+ if (!base) {
|
|
|
+ throw new Error("未配置上传服务地址(VITE_AI_UPLOAD_BASE 或默认 /ai-upload)");
|
|
|
+ }
|
|
|
+
|
|
|
const formData = new FormData();
|
|
|
- formData.append("success_action_status", "200");
|
|
|
- formData.append("policy", signRes.policy);
|
|
|
- formData.append("x-oss-signature", signRes.signature);
|
|
|
- formData.append("x-oss-signature-version", "OSS4-HMAC-SHA256");
|
|
|
- formData.append("x-oss-credential", signRes.x_oss_credential || signRes["x-oss-credential"] || "");
|
|
|
- formData.append("x-oss-date", signRes.x_oss_date || signRes["x-oss-date"] || "");
|
|
|
- formData.append("key", key);
|
|
|
- if (signRes.security_token != null) {
|
|
|
- formData.append("x-oss-security-token", signRes.security_token);
|
|
|
- }
|
|
|
- formData.append("file", file, file.name);
|
|
|
-
|
|
|
- const res = await fetch(signRes.host, {
|
|
|
+ formData.append("file", file, file.name || "file");
|
|
|
+
|
|
|
+ const userStore = useUserStore();
|
|
|
+ const res = await fetch(`${base}${SIMPLE_UPLOAD_PATH}`, {
|
|
|
method: "POST",
|
|
|
+ headers: {
|
|
|
+ Authorization: userStore.token || ""
|
|
|
+ },
|
|
|
+ credentials: "include",
|
|
|
body: formData
|
|
|
});
|
|
|
|
|
|
- if (res.status !== 200) {
|
|
|
- let msg = "上传失败";
|
|
|
+ const rawText = await res.text();
|
|
|
+ let parsed = null;
|
|
|
+ const trimmed = rawText.trim();
|
|
|
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
|
try {
|
|
|
- const t = await res.text();
|
|
|
- if (t) msg = t.slice(0, 200);
|
|
|
+ parsed = JSON.parse(rawText);
|
|
|
} catch (_) {
|
|
|
/* ignore */
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!res.ok) {
|
|
|
+ const modHint = formatSimpleUploadModerationMessage(parsed);
|
|
|
+ let msg = modHint;
|
|
|
+ if (!msg && parsed && typeof parsed === "object") {
|
|
|
+ const p = /** @type {Record<string, unknown>} */ (parsed);
|
|
|
+ const m = p.msg ?? p.message ?? p.error;
|
|
|
+ if (m != null && String(m)) msg = String(m);
|
|
|
+ }
|
|
|
+ if (!msg && trimmed) msg = trimmed.slice(0, 200);
|
|
|
+ if (!msg) msg = "上传失败";
|
|
|
throw new Error(msg);
|
|
|
}
|
|
|
|
|
|
- const text = await res.text();
|
|
|
- if (text && typeof text === "string" && text.trim().startsWith("http")) {
|
|
|
- return text.trim();
|
|
|
+ try {
|
|
|
+ assertSimpleUploadBusinessOk(parsed);
|
|
|
+ } catch (bizErr) {
|
|
|
+ console.error("[upload/simple] 业务失败", bizErr, trimmed.slice(0, 500));
|
|
|
+ throw bizErr;
|
|
|
+ }
|
|
|
+
|
|
|
+ const moderationUserTip = formatSimpleUploadModerationMessage(parsed);
|
|
|
+ if (moderationUserTip) {
|
|
|
+ throw new Error(moderationUserTip);
|
|
|
}
|
|
|
- return signRes.host.replace(/\/$/, "") + "/" + key;
|
|
|
+
|
|
|
+ let url = pickUrlFromJsonBody(parsed);
|
|
|
+ if (!url && trimmed.startsWith("http")) {
|
|
|
+ url = trimmed;
|
|
|
+ }
|
|
|
+ if (!url) {
|
|
|
+ const fromHeader =
|
|
|
+ res.headers.get("x-file-url") ||
|
|
|
+ res.headers.get("x-url") ||
|
|
|
+ res.headers.get("x-oss-url") ||
|
|
|
+ res.headers.get("file-url") ||
|
|
|
+ "";
|
|
|
+ if (fromHeader?.trim()) {
|
|
|
+ const h = fromHeader.trim();
|
|
|
+ url = normalizeToFileUrl(h) || (h.startsWith("http") ? h : "");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!url) {
|
|
|
+ const loc = res.headers.get("location") || res.headers.get("Location") || "";
|
|
|
+ if (loc) {
|
|
|
+ url = normalizeToFileUrl(loc.trim()) || (loc.trim().startsWith("http") ? loc.trim() : "");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!url) {
|
|
|
+ console.error("[upload/simple] 无法解析文件地址,响应片段:", trimmed.slice(0, 800));
|
|
|
+ throw new Error("上传失败,未返回文件地址");
|
|
|
+ }
|
|
|
+ return url;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 上传文件:视频一律走 Tus + moderateVideo / 轮询(uploadFileViaAi),不走 OSS;
|
|
|
- * 开启 AI 时图片走 Tus+审核,否则图片与其它类型走 OSS 直传。
|
|
|
+ * 上传文件:图片与视频均走同一接口 POST /upload/simple,formData 键 file。
|
|
|
* @param {File | File[] | FileList} files 浏览器文件对象;支持单个 File、数组或 FileList
|
|
|
- * @param {string} [fileType] 文件类型(如 'image' | 'video'),用于在文件名无后缀时推断格式
|
|
|
+ * @param {string} [_fileType] 保留参数,兼容旧调用(当前不参与分支)
|
|
|
* @param {{ showLoading?: boolean }} [options] showLoading 为 true 时用 ElMessage 提示上传中(非阻塞)
|
|
|
* @returns {Promise<string[]>} 上传成功后的文件 URL 列表
|
|
|
*/
|
|
|
-export async function uploadFilesToOss(files, fileType, options = {}) {
|
|
|
+export async function uploadFilesToOss(files, _fileType, options = {}) {
|
|
|
const { showLoading = false } = options;
|
|
|
const fileArr = normalizeFiles(files);
|
|
|
if (fileArr.length === 0) {
|
|
|
@@ -135,41 +418,11 @@ export async function uploadFilesToOss(files, fileType, options = {}) {
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- let signRes = null;
|
|
|
const uploadedUrls = [];
|
|
|
- const useAiImage = isAiImageModerationEnabled();
|
|
|
-
|
|
|
- for (let i = 0; i < fileArr.length; i++) {
|
|
|
- const file = fileArr[i];
|
|
|
- const isImageBranch = fileType === "image" || (!fileType && file.type && String(file.type).startsWith("image/"));
|
|
|
- const isVideoBranch = fileType === "video" || isLikelyVideoFileForAiUpload(file);
|
|
|
-
|
|
|
- if (isVideoBranch) {
|
|
|
- const url = await uploadFileViaAi(file, { skipModerate: !useAiImage });
|
|
|
- uploadedUrls.push(url);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if (useAiImage && isImageBranch) {
|
|
|
- const url = await uploadFileViaAi(file);
|
|
|
- uploadedUrls.push(url);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if (!signRes) {
|
|
|
- signRes = await fetchOssSignature();
|
|
|
- }
|
|
|
- const filePath = file.name || "";
|
|
|
- const ext = getFileExtension(filePath, fileType);
|
|
|
- const rawBase = filePath.split("/").pop() || "";
|
|
|
- const baseName = rawBase.replace(/\.[^.]+$/, "") || `file_${Date.now()}_${i}`;
|
|
|
- const fileName = baseName.includes(".") ? baseName : `${baseName}.${ext}`;
|
|
|
- const key = (signRes.dir ? signRes.dir.replace(/\/$/, "") + "/" : "") + fileName;
|
|
|
-
|
|
|
- const url = await postFileToOss(signRes, file, key);
|
|
|
+ for (const file of fileArr) {
|
|
|
+ const url = await postFileToSimpleUpload(file);
|
|
|
uploadedUrls.push(url);
|
|
|
}
|
|
|
-
|
|
|
closeLoading();
|
|
|
return uploadedUrls;
|
|
|
} catch (e) {
|