浏览代码

修复跨域

liuxiaole 15 小时之前
父节点
当前提交
07635c3052

+ 2 - 3
.env.development

@@ -24,9 +24,8 @@ VITE_API_URL_PLATFORM = /api/alienStorePlatform
 
 # 开发环境跨域代理,支持配置多个
 # VITE_PROXY = [["/api","https://api.ailien.shop"]] #生产环境
-# /ai-upload、/ai-moderate 供 Tus 上传与 AI 审核(见 src/utils/config.ts、src/api/modules/aiImageUpload.ts)
-# 图片上传默认走 AI 审核;若需恢复仅 OSS/uploadMore,设置 VITE_UPLOAD_IMAGE_AI_MODERATE=false
-VITE_PROXY = [["/api","http://120.26.186.130:8000"],["/ai-upload","https://upload.ailien.shop:8443"],["/ai-moderate","https://verify.ailien.shop:8444"]] # 邹建宇
+# /ai-upload 供 Tus 上传(见 src/utils/config.ts、src/api/modules/aiImageUpload.ts)
+VITE_PROXY = [["/api","http://120.26.186.130:8000"],["/ai-upload","https://upload.ailien.shop:8443"]] # 邹建宇
 
 
 # WebSocket 基础地址(分享等能力,与商家端一致)

+ 7 - 2
.env.production

@@ -37,9 +37,14 @@ VITE_PROXY = [["/alienStore","http://120.26.186.130:8000/alienStore"]]
 # AI接口
 VITE_PROXY_AI = [["/ai-api","http://124.93.18.180:9000"]]
 
-# Tus 上传与内容审核(生产构建默认见 src/utils/config.ts;同源反代时可覆盖)
+# 上传请求:不配则走同源 /ai-upload(Nginx 反代示例)
+# location /ai-upload/ {
+#   proxy_pass https://upload.ailien.shop:8443/;
+#   proxy_set_header Host upload.ailien.shop;
+#   proxy_ssl_server_name on;
+# }
+# 仅在上传服务已配置完整 CORS 时才直连:
 # VITE_AI_UPLOAD_BASE = https://upload.ailien.shop:8443
-# VITE_AI_MODERATE_BASE = https://verify.ailien.shop:8444
 # VITE_AI_FILES_PUBLIC_BASE = https://upload.ailien.shop:8443/files
 
 # 接口加密配置

+ 10 - 370
src/api/modules/aiImageUpload.ts

@@ -1,15 +1,12 @@
 /**
- * Web 端:Tus 分片上传 + AI 图片审核(与 Apifox / uni 版协议对齐)
+ * Web 端:Tus 分片上传(与 Apifox / uni 版协议对齐);独立审核走上传服务 /upload/simple 等,不再调用 verify 审核域
  */
 import { useUserStore } from "@/stores/modules/user";
-import { ResultEnum } from "@/enums/httpEnum";
-import { AI_UPLOAD_FILES_PUBLIC_BASE, BASE_AI_MODERATE_URL, BASE_AI_URL, BASE_DEV_UPLOAD_SIMPLE } from "@/utils/config";
+import { AI_UPLOAD_FILES_PUBLIC_BASE, BASE_AI_URL, BASE_DEV_UPLOAD_SIMPLE } from "@/utils/config";
 
 const TUS_VERSION = "1.0.0";
 const TUS_CHUNK_SIZE = 1024 * 1024;
 
-const DEFAULT_MODERATE_USER_TIP = "图片未通过内容审核,请更换后重试";
-
 function authHeader(): string {
   try {
     return useUserStore().token || "";
@@ -18,23 +15,6 @@ function authHeader(): string {
   }
 }
 
-function mapModerateReasonToUserMessage(raw?: string | null): string {
-  const s = raw == null ? "" : String(raw).trim();
-  if (!s) return DEFAULT_MODERATE_USER_TIP;
-
-  if (/赌|赌博|casino|gambl/i.test(s)) return "图片涉及赌博等违规内容,请更换后重试";
-  if (/色情|淫秽|porn|sex/i.test(s)) return "图片涉及色情等违规内容,请更换后重试";
-  if (/暴力|血腥|violence|gore/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|vlm|violation|flagged/i.test(s)) return DEFAULT_MODERATE_USER_TIP;
-
-  return DEFAULT_MODERATE_USER_TIP;
-}
-
 function pickPayload(res: unknown): unknown {
   if (res == null) return null;
   if (typeof res === "object" && !Array.isArray(res) && (res as any).data !== undefined) {
@@ -161,7 +141,7 @@ export async function uploadFileViaDevSimpleEndpoint(file: File): Promise<{ file
       Authorization: authHeader()
     },
     body: fd,
-    credentials: "include"
+    credentials: "omit"
   });
 
   let body: unknown = null;
@@ -340,232 +320,8 @@ export async function finalizeUploadSession(uploadId: string, data: Record<strin
   return body;
 }
 
-export interface ModerateImageParams {
-  text?: string;
-  image_urls?: string | string[];
-  file?: File | null;
-}
-
-/** POST multipart /api/v1/moderate,文件字段名 files */
-export async function moderateImage(params: ModerateImageParams = {}): Promise<any> {
-  const urlsStr = Array.isArray(params.image_urls)
-    ? params.image_urls.filter(Boolean).join(",")
-    : String(params.image_urls ?? "");
-
-  const fd = new FormData();
-  fd.append("text", String(params.text ?? ""));
-  fd.append("image_urls", urlsStr);
-  if (params.file) {
-    fd.append("files", params.file, params.file.name);
-  }
-
-  const res = await fetch(`${BASE_AI_MODERATE_URL}/api/v1/moderate`, {
-    method: "POST",
-    headers: {
-      Authorization: authHeader()
-    },
-    body: fd
-  });
-
-  let body: any = null;
-  const ct = res.headers.get("content-type") || "";
-  if (ct.includes("application/json")) {
-    try {
-      body = await res.json();
-    } catch {
-      body = null;
-    }
-  } else {
-    const t = await res.text();
-    try {
-      body = t ? JSON.parse(t) : null;
-    } catch {
-      body = { raw: t };
-    }
-  }
-
-  if (res.status < 200 || res.status >= 300) {
-    throw new Error(`审核请求失败(${res.status})`);
-  }
-  if (body && typeof body === "object" && body.code !== undefined && body.code !== 200 && body.code !== 0) {
-    throw new Error(body.msg || body.message || "审核请求失败");
-  }
-  return body;
-}
-
-export interface ModerateVideoParams {
-  /** 可选;与图片同一套 Tus 上传后应只传 `video_url`,不在此再传文件 */
-  file?: File | null;
-  filePath?: string;
-  path?: string;
-  /** 服务端视频地址,无 Tus 时可传空字符串 */
-  video_path?: string;
-  /** 标题/简介等 */
-  text?: string;
-  /** 远程 http(s) 视频地址 */
-  video_url?: string;
-}
-
-/**
- * 仅提交「已上传视频」的审核任务:POST `/api/v1/video/submit`
- * 与图片共用 Tus 上传拿到地址后,传 `video_url`;`getVideoModerateResult` 查询审核进度/结果。
- */
-export async function moderateVideo(params: ModerateVideoParams = {}): Promise<any> {
-  const fd = new FormData();
-  fd.append("video_path", String(params.video_path ?? ""));
-  fd.append("text", String(params.text ?? ""));
-  fd.append("video_url", String(params.video_url ?? ""));
-
-  const file = params.file ?? null;
-  if (file) {
-    fd.append("file", file, file.name);
-  }
-
-  const res = await fetch(`${BASE_AI_MODERATE_URL}/api/v1/video/submit`, {
-    method: "POST",
-    headers: {
-      Authorization: authHeader()
-    },
-    body: fd
-  });
-
-  let body: any = null;
-  const ct = res.headers.get("content-type") || "";
-  if (ct.includes("application/json")) {
-    try {
-      body = await res.json();
-    } catch {
-      body = null;
-    }
-  } else {
-    const t = await res.text();
-    try {
-      body = t ? JSON.parse(t) : null;
-    } catch {
-      body = { raw: t };
-    }
-  }
-
-  if (res.status < 200 || res.status >= 300) {
-    throw new Error(`视频审核提交失败(${res.status})`);
-  }
-  if (body && typeof body === "object" && body.code !== undefined && body.code !== 200 && body.code !== 0) {
-    throw new Error(body.msg || body.message || "视频审核提交失败");
-  }
-  return body;
-}
-
-/**
- * 查询视频审核进度与结果:GET `/api/v1/video/status/{task_id}`
- * @param data.task_id 或传入字符串即 task_id
- */
-export async function getVideoModerateResult(data: { task_id?: string } | string): Promise<any> {
-  const taskId = typeof data === "string" ? data : String(data?.task_id ?? "").trim();
-  if (!taskId) {
-    throw new Error("缺少 task_id");
-  }
-
-  const res = await fetch(`${BASE_AI_MODERATE_URL}/api/v1/video/status/${encodeURIComponent(taskId)}`, {
-    method: "GET",
-    headers: {
-      Authorization: authHeader()
-    }
-  });
-
-  let body: any = null;
-  const ct = res.headers.get("content-type") || "";
-  if (ct.includes("application/json")) {
-    try {
-      body = await res.json();
-    } catch {
-      body = null;
-    }
-  } else {
-    const t = await res.text();
-    try {
-      body = t ? JSON.parse(t) : null;
-    } catch {
-      body = { raw: t };
-    }
-  }
-
-  if (res.status < 200 || res.status >= 300) {
-    throw new Error(`查询视频审核状态失败(${res.status})`);
-  }
-  if (body && typeof body === "object" && body.code !== undefined && body.code !== 200 && body.code !== 0) {
-    throw new Error(body.msg || body.message || "查询视频审核状态失败");
-  }
-  return body;
-}
-
-function mapVideoModerateReasonToUserMessage(raw?: string | null): string {
-  return mapModerateReasonToUserMessage(raw).replace(/图片/g, "视频");
-}
-
-function pickVideoTaskIdFromSubmit(res: unknown): string {
-  const r = res as Record<string, any> | null | undefined;
-  if (!r || typeof r !== "object") return "";
-  let cur: any = r.data !== undefined ? r.data : r;
-  if (cur && typeof cur === "object" && cur.data !== undefined) cur = cur.data;
-  if (cur && typeof cur === "object") {
-    return String(cur.task_id ?? cur.taskId ?? cur.taskID ?? "").trim();
-  }
-  return "";
-}
-
-function flattenVideoStatusPayload(body: Record<string, any>): Record<string, any> {
-  const d = body?.data;
-  if (d && typeof d === "object" && !Array.isArray(d)) {
-    return { ...body, ...d };
-  }
-  return body;
-}
-
-function isVideoModerationStatusTerminal(payload: Record<string, any>): boolean {
-  const p = flattenVideoStatusPayload(payload);
-  const s = String(p?.status ?? p?.state ?? "").toLowerCase();
-  const terminal = [
-    "completed",
-    "complete",
-    "success",
-    "succeeded",
-    "done",
-    "finished",
-    "failed",
-    "fail",
-    "rejected",
-    "error",
-    "cancelled",
-    "canceled"
-  ];
-  if (terminal.includes(s)) return true;
-  const r = p?.result;
-  if (r && typeof r === "object" && (r.flagged !== undefined || r.passed !== undefined || r.approved !== undefined)) {
-    return true;
-  }
-  return false;
-}
-
-function isVideoModerationRejectedPayload(payload: Record<string, any>): boolean {
-  const p = flattenVideoStatusPayload(payload);
-  if (p?.flagged === true) return true;
-  const r = p?.result;
-  if (r && typeof r === "object" && r.flagged === true) return true;
-  const s = String(p?.status ?? "").toLowerCase();
-  if (["failed", "fail", "rejected", "error"].includes(s)) return true;
-  return false;
-}
-
-function pickVideoRejectReason(payload: Record<string, any>): string | null {
-  const p = flattenVideoStatusPayload(payload);
-  const r = p?.result;
-  const fromResult = r && typeof r === "object" ? (r.reason ?? r.message) : null;
-  const raw = fromResult ?? p?.reason ?? p?.message ?? p?.msg;
-  return raw != null && typeof raw === "string" ? raw : null;
-}
-
 /**
- * 是否为视频(MIME 或后缀)。上传与图片同走 Tus;通过后以 `moderateVideo({ video_url })` + 轮询区分审核链路。
+ * 是否为视频(MIME 或后缀),用于 Tus 上传命名等
  */
 export function isLikelyVideoFileForAiUpload(file: File): boolean {
   if (!(file instanceof File)) return false;
@@ -613,139 +369,26 @@ async function uploadViaTusToPublicUrl(file: File, onProgress?: (p: number) => v
   return fileUrl;
 }
 
-/**
- * Tus 已得到视频 URL:`moderateVideo` 仅提交审核(video_url),`getVideoModerateResult` 轮询至结束。
- */
-async function submitAndAwaitVideoModeration(fileUrl: string, onProgress?: (p: number) => void): Promise<void> {
-  const report = (n: number) => onProgress?.(Math.min(100, Math.max(0, Math.round(n))));
-  const submitRes: any = await moderateVideo({ video_url: fileUrl, text: "", video_path: "" });
-  report(92);
-  const taskId = pickVideoTaskIdFromSubmit(submitRes);
-  const submitFlat =
-    submitRes && typeof submitRes === "object" ? flattenVideoStatusPayload(submitRes as Record<string, any>) : {};
-
-  if (!taskId) {
-    if (isVideoModerationStatusTerminal(submitFlat)) {
-      if (isVideoModerationRejectedPayload(submitFlat)) {
-        throw new Error(mapVideoModerateReasonToUserMessage(pickVideoRejectReason(submitFlat)));
-      }
-      return;
-    }
-    const c = submitRes?.code;
-    if (c === 200 || c === 0) return;
-    throw new Error("视频审核提交未返回任务信息,请稍后重试");
-  }
-
-  const maxMs = 5 * 60 * 1000;
-  const step = 2000;
-  const t0 = Date.now();
-  let pollCount = 0;
-  while (Date.now() - t0 < maxMs) {
-    const raw: any = await getVideoModerateResult(taskId);
-    const flat = raw && typeof raw === "object" ? flattenVideoStatusPayload(raw as Record<string, any>) : {};
-    pollCount += 1;
-    report(Math.min(98, 92 + Math.min(6, pollCount)));
-
-    if (isVideoModerationStatusTerminal(flat)) {
-      if (isVideoModerationRejectedPayload(flat)) {
-        throw new Error(mapVideoModerateReasonToUserMessage(pickVideoRejectReason(flat)));
-      }
-      return;
-    }
-    await new Promise<void>(resolve => setTimeout(resolve, step));
-  }
-  throw new Error("视频审核等待超时,请稍后重试");
-}
-
 export interface UploadFileViaAiOptions {
   /** 0~100 */
   onProgress?: (progress: number) => void;
-  /** 为 true 时跳过审核(仅上传) */
+  /** 已废弃;保留仅为兼容旧调用,不再触发独立审核服务 */
   skipModerate?: boolean;
 }
 
 /**
- * 图片与视频均:Tus 上传 → finalize 得同一套访问 URL。
- * 图片再 `moderateImage`;视频再 `moderateVideo({ video_url })` + `getVideoModerateResult` 轮询。`skipModerate` 则只上传不审。
+ * 图片与视频:Tus 上传 → finalize 得访问 URL(不再调用 verify / ai-moderate)
  */
 export async function uploadFileViaAi(file: File, options: UploadFileViaAiOptions = {}): Promise<string> {
-  const { onProgress, skipModerate } = options;
+  const { onProgress } = options;
 
   const report = (p: number) => {
     onProgress?.(Math.min(100, Math.max(0, Math.round(p))));
   };
 
-  try {
-    const fileUrl = await uploadViaTusToPublicUrl(file, onProgress);
-
-    if (!skipModerate) {
-      if (isLikelyVideoFileForAiUpload(file)) {
-        await submitAndAwaitVideoModeration(fileUrl, onProgress);
-      } else {
-        const moderateRes = await moderateImage({
-          text: "",
-          image_urls: fileUrl,
-          file
-        });
-        const first = moderateRes?.results?.[0];
-        if (first?.flagged) {
-          if (first.reason) console.warn("[AI审核]", first.reason);
-          throw new Error(mapModerateReasonToUserMessage(first.reason));
-        }
-      }
-    }
-
-    report(100);
-    return fileUrl;
-  } catch (e) {
-    throw e;
-  }
-}
-
-/** 与 /file/uploadMore 成功态兼容,供 Upload 组件、http 封装统一消费 */
-export type UploadMoreLikeResponse = {
-  code: number;
-  msg: string;
-  data: { fileUrl: string };
-  /** 部分组件直接读顶层 fileUrl */
-  fileUrl: string;
-};
-
-export function isAiImageModerationEnabled(): boolean {
-  return import.meta.env.VITE_UPLOAD_IMAGE_AI_MODERATE !== "false";
-}
-
-/** 图/视频均 Tus 上传;图 moderateImage、视频 moderateVideo(url)+轮询;返回与 uploadMore 相近结构 */
-export async function uploadImageAsUploadMoreResponse(file: File): Promise<UploadMoreLikeResponse> {
-  const fileUrl = await uploadFileViaAi(file);
-  return {
-    code: ResultEnum.SUCCESS,
-    msg: "success",
-    data: { fileUrl },
-    fileUrl
-  };
-}
-
-/**
- * FormData 内含字段 `file` 且为图片或视频时走 Tus + 对应审核(图 moderateImage,视频 moderateVideo 的 video_url + 轮询),否则走原接口
- */
-export function runUploadMoreWithOptionalAi<T = UploadMoreLikeResponse>(
-  params: FormData,
-  fallback: () => Promise<T>
-): Promise<T> {
-  if (!isAiImageModerationEnabled()) {
-    return fallback();
-  }
-  const file = params.get("file");
-  if (!(file instanceof File)) {
-    return fallback();
-  }
-  const isImage = typeof file.type === "string" && file.type.startsWith("image/");
-  const isVideo = isLikelyVideoFileForAiUpload(file);
-  if (isImage || isVideo) {
-    return uploadImageAsUploadMoreResponse(file) as Promise<T>;
-  }
-  return fallback();
+  const fileUrl = await uploadViaTusToPublicUrl(file, onProgress);
+  report(100);
+  return fileUrl;
 }
 
 /** 多文件顺序上传,整体进度 0~100 */
@@ -775,9 +418,6 @@ export const aiImageApi = {
   getUploadProgress,
   deleteUploadSession,
   finalizeUploadSession,
-  moderateImage,
-  moderateVideo,
-  getVideoModerateResult,
   uploadFileViaAi,
   uploadFilesViaAi,
   uploadFileViaDevSimpleEndpoint

+ 11 - 3
src/api/modules/licenseManagement.ts

@@ -3,6 +3,7 @@ import { PORT_NONE } from "@/api/config/servicePort";
 import http from "@/api";
 import http_store from "@/api/indexStore";
 import httpApi from "@/api/indexApi";
+import { uploadFormDataToOss } from "@/api/upload.js";
 
 // 获取营业执照
 export const getBusinessLicense = params => {
@@ -83,9 +84,16 @@ export const submitContractReview = params => {
   return http.post(PORT_NONE + `/license/uploadRenewalContract`, params);
 };
 
-// 上传合同图片
-export const uploadContractImage = (formData: FormData, onProgress?: (progress: number) => void) => {
-  return http.upload("/file/uploadMore", formData, onProgress, import.meta.env.VITE_API_URL as string);
+// 上传合同图片(统一走 @/api/upload.js;兼容旧逻辑 result.data[0] 为 URL)
+export const uploadContractImage = async (formData: FormData, onProgress?: (progress: number) => void) => {
+  void onProgress;
+  const { data } = await uploadFormDataToOss(formData, "image");
+  const url = data.fileUrl;
+  return {
+    code: 200,
+    msg: "success",
+    data: [url] as unknown as string[]
+  };
 };
 
 // OCR 二次校验接口

+ 3 - 7
src/api/modules/newLoginApi.ts

@@ -1,7 +1,7 @@
 import type { Login } from "@/api/interface";
 import httpLogin from "@/api/indexApi";
 import { Upload } from "@/api/interface/index";
-import { runUploadMoreWithOptionalAi } from "@/api/modules/aiImageUpload";
+import { uploadFormDataSimpleCompat } from "@/api/upload.js";
 // 获取图片验证码
 export const getImgCode = () => {
   return httpLogin.get(
@@ -62,12 +62,8 @@ export const getInputPrompt = params => {
 export const getDistrict = params => {
   return httpLogin.get(`/alienStore/gaode/getDistrict`, params);
 };
-//文件上传(图片默认走 Tus + AI 审核,可设 VITE_UPLOAD_IMAGE_AI_MODERATE=false 关闭)
-export const uploadImg = (params: FormData) => {
-  return runUploadMoreWithOptionalAi(params, () =>
-    httpLogin.post<Upload.ResFileUrl>(`/alienStore/file/uploadMore`, params, { cancel: false })
-  );
-};
+// 文件上传(统一走 @/api/upload.js → /upload/simple)
+export const uploadImg = (params: FormData) => uploadFormDataSimpleCompat(params);
 // 发布/更新动态(新接口)
 export const addOrUpdateDynamic = (params: {
   address?: string; // 经纬度

+ 5 - 14
src/api/modules/storeDecoration.ts

@@ -2,8 +2,7 @@ import { ResPage, StoreUser } from "@/api/interface/index";
 import { PORT_NONE } from "@/api/config/servicePort";
 import http from "@/api";
 import httpApi from "@/api/indexApi";
-import { Upload } from "@/api/interface/index";
-import { runUploadMoreWithOptionalAi } from "@/api/modules/aiImageUpload";
+import { uploadFormDataSimpleCompat } from "@/api/upload.js";
 
 /**
  * @name 商铺用户模块
@@ -63,19 +62,11 @@ export const saveOrUpdateDecoration = (params: any) => {
   return httpApi.post(`/alienStore/renovation/requirement/saveOrUpdate`, params);
 };
 
-// 上传房屋图纸 - 使用 /alienStore/file/uploadMore 接口
-export const uploadDecorationImage = (params: FormData) => {
-  return runUploadMoreWithOptionalAi(params, () =>
-    httpApi.post<Upload.ResFileUrl>(`/alienStore/file/uploadMore`, params, { cancel: false })
-  );
-};
+// 上传房屋图纸(统一走 @/api/upload.js → /upload/simple)
+export const uploadDecorationImage = (params: FormData) => uploadFormDataSimpleCompat(params);
 
-// 聊天图片/视频上传(与商家端一致,使用同一接口)
-export const uploadChatFile = (params: FormData) => {
-  return runUploadMoreWithOptionalAi(params, () =>
-    httpApi.post<Upload.ResFileUrl>(`/alienStore/file/uploadMore`, params, { cancel: false })
-  );
-};
+// 聊天图片/视频上传
+export const uploadChatFile = (params: FormData) => uploadFormDataSimpleCompat(params);
 
 // 删除装修需求
 export const deleteDecoration = (params: { id: number | string }) => {

+ 5 - 62
src/api/modules/upload.ts

@@ -1,67 +1,10 @@
-import { Upload } from "@/api/interface/index";
-import { runUploadMoreWithOptionalAi } from "@/api/modules/aiImageUpload";
-import { PORT1 } from "@/api/config/servicePort";
-import { PORT_NONE } from "@/api/config/servicePort";
-import httpStore from "@/api/indexStore";
-import http from "@/api";
-import axios from "axios";
-import { ResultEnum } from "@/enums/httpEnum";
-import { useUserStore } from "@/stores/modules/user";
-import { ElMessage } from "element-plus";
-import { LOGIN_URL } from "@/config";
-import router from "@/routers";
-
-// 使用 alienStore 前缀的 axios 实例(用于价目表等页面上传)
-const httpStoreAlienStore = axios.create({
-  baseURL: import.meta.env.VITE_API_URL_STORE as string,
-  timeout: ResultEnum.TIMEOUT as number,
-  withCredentials: true
-});
-httpStoreAlienStore.interceptors.request.use(
-  config => {
-    const userStore = useUserStore();
-    if (config.headers) (config.headers as any).Authorization = userStore.token;
-    return config;
-  },
-  error => Promise.reject(error)
-);
-httpStoreAlienStore.interceptors.response.use(
-  response => {
-    const data = response.data;
-    const userStore = useUserStore();
-    if (data.code == ResultEnum.OVERDUE) {
-      userStore.setToken("");
-      router.replace(LOGIN_URL);
-      ElMessage.error(data.msg);
-      return Promise.reject(data);
-    }
-    if (data.code && data.code !== ResultEnum.SUCCESS) {
-      ElMessage.error(data.msg);
-      return Promise.reject(data);
-    }
-    return data;
-  },
-  error => Promise.reject(error)
-);
+import { uploadFormDataSimpleCompat } from "@/api/upload.js";
 
 /**
- * @name 文件上传模块
+ * @name 文件上传模块(图片/视频统一走 @/api/upload.js → /upload/simple)
  */
-// 图片上传(默认使用 alienStorePlatform)
-export const uploadImg = (params: FormData) => {
-  return runUploadMoreWithOptionalAi(params, () =>
-    httpStore.post<Upload.ResFileUrl>(PORT_NONE + `/file/uploadMore`, params, { cancel: false })
-  );
-};
+export const uploadImg = (params: FormData) => uploadFormDataSimpleCompat(params);
 
-// 图片上传(使用 alienStore 前缀,用于价目表等页面)
-export const uploadImgStore = (params: FormData) => {
-  return runUploadMoreWithOptionalAi(params, () =>
-    httpStoreAlienStore.post<Upload.ResFileUrl>(PORT_NONE + `/file/uploadMore`, params, { cancel: false })
-  );
-};
+export const uploadImgStore = (params: FormData) => uploadFormDataSimpleCompat(params);
 
-// 视频上传
-export const uploadVideo = (params: FormData) => {
-  return http.post<Upload.ResFileUrl>(PORT_NONE + `/file/upload/video`, params, { cancel: false });
-};
+export const uploadVideo = (params: FormData) => uploadFormDataSimpleCompat(params);

+ 40 - 7
src/api/upload.js

@@ -323,12 +323,17 @@ async function postFileToSimpleUpload(file) {
   formData.append("file", file, file.name || "file");
 
   const userStore = useUserStore();
+  const headers = {};
+  const token = userStore.token || "";
+  if (token) {
+    headers.Authorization = token;
+  }
+
   const res = await fetch(`${base}${SIMPLE_UPLOAD_PATH}`, {
     method: "POST",
-    headers: {
-      Authorization: userStore.token || ""
-    },
-    credentials: "include",
+    headers,
+    /** 不带跨域 Cookie,减轻上传服务 CORS 要求(鉴权仅用 Authorization) */
+    credentials: "omit",
     body: formData
   });
 
@@ -449,15 +454,43 @@ export async function uploadFileToOss(file, fileType, options) {
 /**
  * 兼容 UploadImg 等组件的 api 格式:接收 FormData,返回 { data: { fileUrl } }
  * @param {FormData} formData 包含 file 字段
- * @param {string} [fileType] 文件类型,默认 'image'
+ * @param {string} [fileType] 不传或传空时按 file.type 自动区分 image / video
  * @param {{ showLoading?: boolean }} [options]
  * @returns {Promise<{ data: { fileUrl: string } }>}
  */
-export async function uploadFormDataToOss(formData, fileType = "image", options = {}) {
+export async function uploadFormDataToOss(formData, fileType, options = {}) {
   const file = formData.get("file");
   if (!file || !(file instanceof File)) {
     throw new Error("请选择要上传的文件");
   }
-  const url = await uploadFileToOss(file, fileType, options);
+  const inferred =
+    fileType === undefined || fileType === null || fileType === ""
+      ? String(file.type || "").startsWith("video/")
+        ? "video"
+        : "image"
+      : fileType;
+  const url = await uploadFileToOss(file, inferred, options);
   return { data: { fileUrl: url } };
 }
+
+/**
+ * FormData 走 `/upload/simple` 后,包装成旧组件习惯的 `{ code, msg, data: { fileUrl }, fileUrl }`(并非请求 `/file/uploadMore`)
+ * @param {FormData} formData
+ * @param {{ showLoading?: boolean }} [options]
+ * @returns {Promise<{ code: number; msg: string; data: { fileUrl: string }; fileUrl: string }>}
+ */
+export async function uploadFormDataSimpleCompat(formData, options = {}) {
+  const file = formData.get("file");
+  if (!file || !(file instanceof File)) {
+    throw new Error("请选择要上传的文件");
+  }
+  const isVideo = String(file.type || "").startsWith("video/");
+  const inner = await uploadFormDataToOss(formData, isVideo ? "video" : "image", options);
+  const fileUrl = inner.data.fileUrl;
+  return {
+    code: 200,
+    msg: "success",
+    data: inner.data,
+    fileUrl
+  };
+}

+ 2 - 6
src/typings/global.d.ts

@@ -62,14 +62,10 @@ declare interface ViteEnv {
   VITE_CRYPTO_KEY: string;
   VITE_CRYPTO_IV: string;
   VITE_WS_BASE?: string;
-  /** Tus 上传服务根(可选,不配则开发走 /ai-upload 代理、生产走 upload.ailien.shop) */
+  /** 上传 API 请求根(可选;不配则默认同源 /ai-upload,依赖 Vite/Nginx 反代) */
   VITE_AI_UPLOAD_BASE?: string;
-  /** AI 审核服务根(可选,不配则开发走 /ai-moderate 代理) */
-  VITE_AI_MODERATE_BASE?: string;
-  /** 上传完成后对外访问 URL 前缀,默认 http://upload.ailien.shop:8088/files */
+  /** 上传完成后对外访问 URL 前缀 */
   VITE_AI_FILES_PUBLIC_BASE?: string;
-  /** 设为 false 时图片仍走原 OSS / uploadMore,不经过 Tus + AI 审核 */
-  VITE_UPLOAD_IMAGE_AI_MODERATE?: string;
   /** 可选;官方相册视频 simple 上传服务根,不配则开发走相对路径 /dev-upload-ailien/...、生产默认 upload.ailien.shop:8443 */
   VITE_DEV_UPLOAD_SIMPLE_BASE?: string;
 }

+ 4 - 11
src/utils/config.ts

@@ -1,18 +1,11 @@
 /**
- * AI Tus 上传与内容审核服务基址(与 uni 端逻辑对齐,适配 Web)
- * 开发环境默认走 Vite 代理前缀,需在 .env.development 的 VITE_PROXY 中配置 /ai-upload、/ai-moderate
+ * Tus / simple 上传服务「请求」基址(与 uni 端对齐)
+ * - 未配置 VITE_AI_UPLOAD_BASE 时默认 **同源相对路径 `/ai-upload`**,由 Vite 开发代理或 Nginx 反代到实际上传域,避免浏览器直连 upload 域产生跨域。
+ * - 若必须浏览器直连上传 HTTPS 域名,请在 .env 设置 VITE_AI_UPLOAD_BASE,并确保该域已正确配置 CORS(含 Authorization、OPTIONS)。
  */
 const trimSlash = (s: string) => s.replace(/\/$/, "");
 
-export const BASE_AI_URL = trimSlash(
-  String(import.meta.env.VITE_AI_UPLOAD_BASE || "").trim() ||
-    (import.meta.env.DEV ? "/ai-upload" : "https://upload.ailien.shop:8443")
-);
-
-export const BASE_AI_MODERATE_URL = trimSlash(
-  String(import.meta.env.VITE_AI_MODERATE_BASE || "").trim() ||
-    (import.meta.env.DEV ? "/ai-moderate" : "https://verify.ailien.shop:8444")
-);
+export const BASE_AI_URL = trimSlash(String(import.meta.env.VITE_AI_UPLOAD_BASE || "").trim() || "/ai-upload");
 
 /** 上传完成后对外可访问的文件 URL:`${AI_UPLOAD_FILES_PUBLIC_BASE}/${uploadId}` */
 export const AI_UPLOAD_FILES_PUBLIC_BASE = trimSlash(

+ 5 - 5
src/views/storeDecoration/officialPhotoAlbum/index.vue

@@ -214,7 +214,7 @@ import {
   deleteOfficialImg
 } from "@/api/modules/storeDecoration";
 import { uploadFilesToOss } from "@/api/upload.js";
-import { uploadFileViaDevSimpleEndpoint } from "@/api/modules/aiImageUpload";
+import { uploadFormDataToOss } from "@/api/upload.js";
 
 /** 普通官方相册图 */
 const OFFICIAL_IMG_TYPE_ALBUM = 2;
@@ -526,15 +526,15 @@ const customVideoUploadApi = async (formData: FormData): Promise<any> => {
     throw new Error("视频大小超过限制");
   }
 
-  const { fileUrl, coverUrl } = await uploadFileViaDevSimpleEndpoint(file);
+  const { data } = await uploadFormDataToOss(formData, "video");
+  const fileUrl = data?.fileUrl;
   if (!fileUrl) {
     throw new Error("上传失败,未返回地址");
   }
 
   const response = {
-    data: { fileUrl, ...(coverUrl ? { coverUrl } : {}) },
-    fileUrl,
-    ...(coverUrl ? { coverUrl } : {})
+    data: { fileUrl },
+    fileUrl
   };
   await nextTick();
   return response;