zhuli 1 месяц назад
Родитель
Сommit
8b0a7af56a

+ 173 - 0
src/api/upload.js

@@ -0,0 +1,173 @@
+import { useUserStore } from "@/stores/modules/user";
+import { ElMessage } from "element-plus";
+
+/**
+ * 门店/文件相关接口基址(与 src/api/modules/upload.ts 中 httpStoreAlienStore 一致)
+ * 若签名接口部署在其它域名,可改为 VITE_API_URL_PLATFORM 等
+ */
+function getStoreApiBase() {
+  return String(import.meta.env.VITE_API_URL_STORE || "").replace(/\/$/, "");
+}
+
+/** 从路径或 fileType 解析文件后缀(如 jpg、mp4),用于生成带格式的文件名 */
+export function getFileExtension(filePath, fileType) {
+  const pathPart = (filePath || "").split("?")[0];
+  const lastSegment = pathPart.split("/").pop() || "";
+  const dot = lastSegment.lastIndexOf(".");
+  if (dot > 0) {
+    const ext = lastSegment.slice(dot + 1).toLowerCase();
+    if (/^[a-z0-9]+$/i.test(ext) && ext.length <= 5) return ext;
+  }
+  if (fileType === "video") return "mp4";
+  if (fileType === "image") return "jpg";
+  return "jpg";
+}
+
+/**
+ * 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[]}
+ */
+function normalizeFiles(input) {
+  if (input == null) return [];
+  if (input instanceof File) return [input];
+  if (typeof FileList !== "undefined" && input instanceof FileList) {
+    return Array.from(input);
+  }
+  if (Array.isArray(input)) {
+    return input.filter(item => item instanceof File);
+  }
+  throw new Error("请传入 File、File[] 或 FileList");
+}
+
+/**
+ * 单个 File 上传到 OSS(POST multipart,字段与 1.js / OSS4 直传一致)
+ * @param {Record<string, any>} signRes 签名接口返回
+ * @param {File} file
+ * @param {string} key 对象 key(含 dir 前缀)
+ * @returns {Promise<string>} 文件访问 URL
+ */
+async function postFileToOss(signRes, file, key) {
+  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, {
+    method: "POST",
+    body: formData
+  });
+
+  if (res.status !== 200) {
+    let msg = "上传失败";
+    try {
+      const t = await res.text();
+      if (t) msg = t.slice(0, 200);
+    } catch (_) {
+      /* ignore */
+    }
+    throw new Error(msg);
+  }
+
+  const text = await res.text();
+  if (text && typeof text === "string" && text.trim().startsWith("http")) {
+    return text.trim();
+  }
+  return signRes.host.replace(/\/$/, "") + "/" + key;
+}
+
+/**
+ * 上传文件到 OSS:先 GET 获取签名,再 POST 到 OSS host(与 src/api/1.js 逻辑一致,Web 使用 File / FormData)
+ * @param {File | File[] | FileList} files 浏览器文件对象;支持单个 File、数组或 FileList
+ * @param {string} [fileType] 文件类型(如 'image' | 'video'),用于在文件名无后缀时推断格式
+ * @param {{ showLoading?: boolean }} [options] showLoading 为 true 时用 ElMessage 提示上传中(非阻塞)
+ * @returns {Promise<string[]>} 上传成功后的文件 URL 列表
+ */
+export async function uploadFilesToOss(files, fileType, options = {}) {
+  const { showLoading = false } = options;
+  const fileArr = normalizeFiles(files);
+  if (fileArr.length === 0) {
+    throw new Error("请选择要上传的文件");
+  }
+
+  let closeLoading = () => {};
+  if (showLoading) {
+    const loading = ElMessage({ message: "上传中...", type: "info", duration: 0, showClose: false });
+    closeLoading = () => loading.close();
+  }
+
+  try {
+    const signRes = await fetchOssSignature();
+    const uploadedUrls = [];
+
+    for (let i = 0; i < fileArr.length; i++) {
+      const file = fileArr[i];
+      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);
+      uploadedUrls.push(url);
+    }
+
+    closeLoading();
+    return uploadedUrls;
+  } catch (e) {
+    closeLoading();
+    console.error("上传失败", e);
+    const msg = e?.message || "上传失败";
+    ElMessage.error(msg);
+    throw e;
+  }
+}
+
+/**
+ * 上传单个文件,返回 URL 字符串
+ * @param {File} file
+ * @param {string} [fileType]
+ * @param {{ showLoading?: boolean }} [options]
+ * @returns {Promise<string>}
+ */
+export async function uploadFileToOss(file, fileType, options) {
+  const urls = await uploadFilesToOss(file, fileType, options);
+  return urls[0];
+}

+ 15 - 19
src/views/appoinmentManagement/classifyManagement.vue

@@ -83,7 +83,7 @@ import { Plus } from "@element-plus/icons-vue";
 import ProTable from "@/components/ProTable/index.vue";
 import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
 import { scheduleList, scheduleDel, scheduleAdd, scheduleEdit, scheduleDetail } from "@/api/modules/scheduledService";
-import { uploadDecorationImage } from "@/api/modules/storeDecoration";
+import { uploadFileToOss } from "@/api/upload.js";
 import { localGet } from "@/utils";
 
 export interface CategoryRow {
@@ -189,30 +189,26 @@ const rules: FormRules = {
 };
 
 async function handlePlaneImageUpload(options: UploadRequestOptions) {
-  const file = (options.file as any).raw || options.file;
+  const uploadFileItem = options.file as UploadUserFile;
+  const raw = uploadFileItem.raw || uploadFileItem;
+  const file = raw instanceof File ? raw : null;
   if (!file) return;
-  const fd = new FormData();
-  fd.append("file", file);
-  (options.file as any).status = "uploading";
+
+  uploadFileItem.status = "uploading";
   try {
-    const result: any = await uploadDecorationImage(fd);
-    let fileUrl = "";
-    if (Array.isArray(result?.data) && result.data.length > 0) fileUrl = result.data[0];
-    else if (typeof result?.data === "string") fileUrl = result.data;
-    else if (result?.data?.fileUrl) fileUrl = result.data.fileUrl;
-    else if (result?.data?.url) fileUrl = result.data.url;
+    const fileUrl = await uploadFileToOss(file, "image");
     if (fileUrl) {
-      (options.file as any).status = "success";
-      (options.file as any).url = fileUrl;
-      (options.file as any).response = { url: fileUrl };
+      uploadFileItem.status = "success";
+      uploadFileItem.url = fileUrl;
+      uploadFileItem.response = { url: fileUrl };
       if (!form.planeImageUrls.includes(fileUrl)) form.planeImageUrls.push(fileUrl);
     } else {
-      (options.file as any).status = "fail";
-      ElMessage.error("上传失败");
+      uploadFileItem.status = "fail";
+      ElMessage.error("上传失败,未返回地址");
     }
-  } catch (e: any) {
-    (options.file as any).status = "fail";
-    ElMessage.error(e?.message || "上传失败");
+  } catch {
+    uploadFileItem.status = "fail";
+    // OSS 签名/网络错误已在 upload.js 中提示
   }
   formRef.value?.validateField("planeImageUrls").catch(() => {});
 }

+ 16 - 39
src/views/dynamicManagement/publishDynamic.vue

@@ -132,7 +132,8 @@ import { ArrowLeft, Plus, Location, Search, ZoomIn, Delete } from "@element-plus
 import type { FormInstance, FormRules, UploadUserFile, UploadFile } from "element-plus";
 // import { publishDynamic, saveDraft, uploadDynamicImage } from "@/api/modules/dynamicManagement";
 // import { addOrUpdateDynamic } from "@/api/modules/dynamicManagement";
-import { uploadImg, addOrUpdateDynamic } from "@/api/modules/newLoginApi";
+import { addOrUpdateDynamic } from "@/api/modules/newLoginApi";
+import { uploadFilesToOss } from "@/api/upload.js";
 import { useUserStore } from "@/stores/modules/user";
 import { getUserDraftDynamics, getInputPrompt } from "@/api/modules/newLoginApi";
 
@@ -371,51 +372,27 @@ const uploadSingleFile = async (file: UploadFile) => {
   }
 
   const rawFile = file.raw as File;
-  const uploadFormData = new FormData();
-  uploadFormData.append("file", rawFile);
 
   file.status = "uploading";
   file.percentage = 0;
   uploading.value = true;
 
   try {
-    console.log("开始上传文件:", rawFile.name, "类型:", rawFile.type);
-
-    // 图片和视频都使用同一个上传接口
-    const result: any = await uploadImg(uploadFormData);
-
-    console.log("上传接口返回:", result);
-
-    if (result?.code === 200 && result.data) {
-      let fileUrl = "";
-
-      // 处理不同的返回格式
-      if (Array.isArray(result.data) && result.data.length > 0) {
-        fileUrl = result.data[0]; // 数组格式
-      } else if (typeof result.data === "string") {
-        fileUrl = result.data; // 字符串格式
-      } else if (result.data.url) {
-        fileUrl = result.data.url; // 对象格式
-      }
-
-      if (fileUrl) {
-        file.status = "success";
-        file.percentage = 100;
-        file.url = fileUrl;
-        file.response = { url: fileUrl };
+    const isVideo = rawFile.type.startsWith("video/");
+    const urls = await uploadFilesToOss(rawFile, isVideo ? "video" : "image");
+    const fileUrl = urls[0];
+    if (!fileUrl) {
+      ElMessage.error("上传失败,未返回文件地址");
+      throw new Error("上传失败,未返回文件地址");
+    }
 
-        console.log("上传成功,文件URL:", fileUrl);
+    file.status = "success";
+    file.percentage = 100;
+    file.url = fileUrl;
+    file.response = { url: fileUrl };
 
-        // 添加到 formData.images
-        if (!formData.images.includes(fileUrl)) {
-          formData.images.push(fileUrl);
-          console.log("添加到 images 数组,当前长度:", formData.images.length);
-        }
-      } else {
-        throw new Error("上传接口返回数据格式错误");
-      }
-    } else {
-      throw new Error(result?.msg || "文件上传失败");
+    if (!formData.images.includes(fileUrl)) {
+      formData.images.push(fileUrl);
     }
   } catch (error: any) {
     console.error("上传失败:", error);
@@ -427,7 +404,7 @@ const uploadSingleFile = async (file: UploadFile) => {
     if (index > -1) {
       fileList.value.splice(index, 1);
     }
-    ElMessage.error(error?.message || "文件上传失败");
+    // OSS 网络/签名错误已在 upload.js 中 ElMessage,避免重复弹窗
   } finally {
     uploading.value = false;
     fileList.value = [...fileList.value];

+ 16 - 25
src/views/dynamicManagement/reviewAppeal.vue

@@ -227,7 +227,8 @@ import { useRouter } from "vue-router";
 import { ElMessage } from "element-plus";
 import { User, Plus, VideoPlay } from "@element-plus/icons-vue";
 import type { FormInstance, FormRules, UploadUserFile } from "element-plus";
-import { getList, addAppealNew, saveComment2, uploadImg, getRatingCount } from "@/api/modules/newLoginApi";
+import { getList, addAppealNew, saveComment2, getRatingCount } from "@/api/modules/newLoginApi";
+import { uploadFileToOss } from "@/api/upload.js";
 import { localGet } from "@/utils";
 import { useUserStore } from "@/stores/modules/user";
 
@@ -277,8 +278,7 @@ const currentReviewId = ref("");
 
 const appealFormData = reactive({
   reason: "",
-  images: [] as string[],
-  files: [] as File[], // 保存原始的 File 对象
+  images: [] as string[], // 申诉凭证:OSS 图片 URL(与 fileList 顺序一致)
   fileList: [] as UploadUserFile[]
 });
 const hasStoreReply = (review: any) => {
@@ -558,12 +558,11 @@ const closeAppealDialog = () => {
   Object.assign(appealFormData, {
     reason: "",
     images: [],
-    files: [],
     fileList: []
   });
 };
 
-// 提交申诉(与商家端 addAppealNew 一致:FormData 含 appealReason、storeId、commentId、file_0/file_1...,响应 result: 0成功 1失败 2已存在 3敏感词 4超长
+// 提交申诉(与商家端 addAppealNew 一致:FormData 含 appealReason、storeId、commentId、file_0/file_1...;凭证已为 OSS 地址时按 URL 字符串提交
 const submitAppeal = async () => {
   if (!appealFormRef.value) return;
 
@@ -577,8 +576,8 @@ const submitAppeal = async () => {
       formData.append("appealReason", appealFormData.reason);
       formData.append("storeId", String(storeId));
       formData.append("commentId", String(currentReviewId.value));
-      appealFormData.files.forEach((file: File, index: number) => {
-        formData.append(`file_${index}`, file);
+      appealFormData.images.forEach((url: string, index: number) => {
+        formData.append(`file_${index}`, url);
       });
 
       const res: any = await addAppealNew(formData);
@@ -612,40 +611,32 @@ const handlePreview = (file: UploadUserFile) => {
 
 // 移除图片
 const handleRemove = (file: UploadUserFile, fileList: UploadUserFile[]) => {
-  console.log("remove", file);
-  // 找到对应的索引并删除
   const index = appealFormData.fileList.findIndex(f => f.uid === file.uid);
   if (index !== -1) {
     appealFormData.images.splice(index, 1);
-    appealFormData.files.splice(index, 1);
   }
   appealFormData.fileList = fileList;
 };
 
-// 自定义上传方法
+/** OSS 直传后交给 el-upload 展示网络地址 */
 const handleUpload = async (options: any) => {
   const { file, onSuccess, onError } = options;
 
   try {
-    // 保存原始的 File 对象用于提交时上传
-    appealFormData.files.push(file);
-
-    // 创建本地预览URL
-    const previewUrl = URL.createObjectURL(file);
-    onSuccess({ url: previewUrl });
-
-    // 将预览URL添加到images数组
-    appealFormData.images.push(previewUrl);
-    console.log("图片已添加:", file.name);
+    const url = await uploadFileToOss(file as File, "image");
+    if (!url) {
+      throw new Error("上传失败,未返回地址");
+    }
+    appealFormData.images.push(url);
+    onSuccess({ url });
   } catch (error: any) {
     onError(error);
-    ElMessage.error(error?.msg || "添加图片失败");
+    // 错误提示由 upload.js 内 ElMessage 处理
   }
 };
 
-// 上传成功回调
-const handleUploadSuccess = (response: any, file: any) => {
-  console.log("上传成功回调:", response, file);
+const handleUploadSuccess = (_response: any, _file: any) => {
+  // 列表与 images 已在 handleUpload 中维护
 };
 
 // 初始化

+ 14 - 26
src/views/licenseManagement/entertainmentLicense.vue

@@ -125,11 +125,11 @@ import { Picture, Plus } from "@element-plus/icons-vue";
 import type { UploadProps, UploadFile } from "element-plus";
 import {
   getEntertainmentBusinessLicense,
-  uploadContractImage,
   submitEntertainmentLicenseReview,
   getEntertainmentChangeRecords,
   getStoreEntertainmentLicenceStatus
 } from "@/api/modules/licenseManagement";
+import { uploadFileToOss } from "@/api/upload.js";
 import { localGet } from "@/utils";
 
 // 状态映射对象
@@ -404,38 +404,26 @@ const uploadSingleFile = async (file: UploadFile) => {
     return;
   }
   const rawFile = file.raw as File;
-  const formData = new FormData();
-  formData.append("file", rawFile);
-  formData.append("user", "text");
   file.status = "uploading";
   file.percentage = 0;
   uploading.value = true;
 
   try {
-    // 上传过程中保持进度为 0,避免接口异常时进度条误显示 100%
-    const result: any = await uploadContractImage(formData);
-    if (result?.code === 200 && result.data) {
-      // 处理单个文件的上传结果
-      let imageUrl = result.data[0];
-      if (!imageUrl) {
-        throw new Error("上传成功但未获取到图片URL");
-      }
+    const imageUrl = await uploadFileToOss(rawFile, "image");
+    if (!imageUrl) {
+      throw new Error("上传成功但未获取到图片URL");
+    }
 
-      file.status = "success";
-      file.percentage = 100;
-      // 保存图片URL到文件对象
-      file.url = imageUrl;
-      file.response = { url: imageUrl };
+    file.status = "success";
+    file.percentage = 100;
+    file.url = imageUrl;
+    file.response = { url: imageUrl };
 
-      // 保存图片URL
-      if (!Array.isArray(imageUrlList.value)) {
-        imageUrlList.value = [];
-      }
-      if (!imageUrlList.value.includes(imageUrl)) {
-        imageUrlList.value.push(imageUrl);
-      }
-    } else {
-      throw new Error(result?.msg || "图片上传失败");
+    if (!Array.isArray(imageUrlList.value)) {
+      imageUrlList.value = [];
+    }
+    if (!imageUrlList.value.includes(imageUrl)) {
+      imageUrlList.value.push(imageUrl);
     }
   } catch (error: any) {
     file.status = "fail";

+ 25 - 37
src/views/operationManagement/newActivity.vue

@@ -245,7 +245,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
 import { Plus } from "@element-plus/icons-vue";
 import { useRoute, useRouter } from "vue-router";
 import { addActivity, getActivityDetail, getCouponList, updateActivity } from "@/api/modules/operationManagement";
-import { uploadContractImage } from "@/api/modules/licenseManagement";
+import { uploadFileToOss } from "@/api/upload.js";
 import { localGet } from "@/utils";
 
 // ==================== 响应式数据定义 ====================
@@ -920,49 +920,37 @@ const uploadSingleFile = async (file: UploadFile, uploadType: string) => {
     return;
   }
   const rawFile = file.raw as File;
-  const formData = new FormData();
-  formData.append("file", rawFile);
-  formData.append("user", "text");
   file.status = "uploading";
   file.percentage = 0;
   uploading.value = true;
 
   try {
-    const result: any = await uploadContractImage(formData);
-    if (result?.code === 200 && result.data) {
-      let imageUrl = result.data[0];
-      if (!imageUrl) {
-        throw new Error("上传成功但未获取到图片URL");
-      }
+    const imageUrl = await uploadFileToOss(rawFile, "image");
+    if (!imageUrl) {
+      throw new Error("上传成功但未获取到图片URL");
+    }
 
-      file.status = "success";
-      file.percentage = 100;
-      file.url = imageUrl;
-      file.response = { url: imageUrl };
+    file.status = "success";
+    file.percentage = 100;
+    file.url = imageUrl;
+    file.response = { url: imageUrl };
 
-      if (uploadType === "title") {
-        titleImageUrl.value = imageUrl;
-        activityModel.value.activityTitleImg = { url: imageUrl };
-        activityModel.value.activityTitleImage = imageUrl;
-        // 触发表单验证
-        nextTick(() => {
-          ruleFormRef.value?.validateField("activityTitleImage");
-        });
-      } else if (uploadType === "detail") {
-        // 支持多张图片,将所有成功上传的图片URL组合
-        const successFiles = detailFileList.value.filter((f: any) => f.status === "success" && f.url);
-        const imageUrls = successFiles.map((f: any) => f.url);
-        // 如果有多张图片,用逗号连接;如果只有一张,保持字符串格式
-        detailImageUrl.value = imageUrls.length > 0 ? imageUrls.join(",") : "";
-        activityModel.value.activityDetailImg = imageUrls.length > 0 ? { url: imageUrls.join(",") } : null;
-        activityModel.value.activityDetailImage = imageUrls.length > 0 ? imageUrls.join(",") : "";
-        // 触发表单验证
-        nextTick(() => {
-          ruleFormRef.value?.validateField("activityDetailImage");
-        });
-      }
-    } else {
-      throw new Error(result?.msg || "图片上传失败");
+    if (uploadType === "title") {
+      titleImageUrl.value = imageUrl;
+      activityModel.value.activityTitleImg = { url: imageUrl };
+      activityModel.value.activityTitleImage = imageUrl;
+      nextTick(() => {
+        ruleFormRef.value?.validateField("activityTitleImage");
+      });
+    } else if (uploadType === "detail") {
+      const successFiles = detailFileList.value.filter((f: any) => f.status === "success" && f.url);
+      const imageUrls = successFiles.map((f: any) => f.url);
+      detailImageUrl.value = imageUrls.length > 0 ? imageUrls.join(",") : "";
+      activityModel.value.activityDetailImg = imageUrls.length > 0 ? { url: imageUrls.join(",") } : null;
+      activityModel.value.activityDetailImage = imageUrls.length > 0 ? imageUrls.join(",") : "";
+      nextTick(() => {
+        ruleFormRef.value?.validateField("activityDetailImage");
+      });
     }
   } catch (error: any) {
     // 上传失败时保持进度条为 0

+ 20 - 40
src/views/priceList/edit.vue

@@ -240,7 +240,7 @@
             <div class="image-upload-wrap">
               <UploadImgs
                 v-model:file-list="detailImageFileList"
-                :api="uploadImgStore"
+                :api="handleCustomImageUpload"
                 :limit="9"
                 :file-size="5"
                 :width="'100px'"
@@ -375,7 +375,7 @@ import {
 } from "@/api/modules/priceList";
 import { localGet } from "@/utils";
 import UploadImgs from "@/components/Upload/Imgs.vue";
-import { uploadImgStore } from "@/api/modules/upload";
+import { uploadFilesToOss } from "@/api/upload.js";
 
 const router = useRouter();
 const route = useRoute();
@@ -550,46 +550,26 @@ function calcRecommendedPrice() {
   }
 }
 
-// 自定义上传函数,正确处理响应格式(参考 personnelConfig/index.vue)
+/** OSS 直传(UploadImgs 读取 response.fileUrl) */
 const handleCustomImageUpload = async (formData: FormData): Promise<any> => {
-  try {
-    const response: any = await uploadImgStore(formData);
-
-    // API 返回格式: { code: 200, success: true, data: ["https://..."], msg: "操作成功" }
-    // 需要提取 response.data[0] 作为图片 URL
-    let imageUrl = "";
-
-    if (response && (response.code === 200 || response.code === "200")) {
-      if (Array.isArray(response.data) && response.data.length > 0) {
-        imageUrl = response.data[0];
-      } else if (typeof response.data === "string") {
-        imageUrl = response.data;
-      } else if (response.data?.fileUrl) {
-        imageUrl = response.data.fileUrl;
-      } else if (response.fileUrl) {
-        imageUrl = response.fileUrl;
-      }
-    }
-
-    if (!imageUrl) {
-      throw new Error("无法提取图片URL");
-    }
-
-    // 返回格式需要兼容 Imgs.vue 组件的处理逻辑
-    // 返回一个对象,其中 fileUrl 是图片URL,这样 uploadSuccess 会使用 response.fileUrl
-    const result = {
-      fileUrl: imageUrl, // 供 uploadSuccess 使用(会设置 uploadFile.url = response.fileUrl)
-      data: [imageUrl], // 备用
-      code: response.code,
-      msg: response.msg,
-      success: response.success
-    };
-
-    return result;
-  } catch (error) {
-    console.error("自定义上传函数 - 上传失败:", error);
-    throw error;
+  const raw = formData.get("file");
+  const file = raw instanceof File ? raw : null;
+  if (!file) {
+    throw new Error("请选择文件");
+  }
+  const isVideo = typeof file.type === "string" && file.type.startsWith("video/");
+  const urls = await uploadFilesToOss(file, isVideo ? "video" : "image");
+  const fileUrl = urls[0];
+  if (!fileUrl) {
+    throw new Error("上传失败,未返回地址");
   }
+  return {
+    fileUrl,
+    data: [fileUrl],
+    code: 200,
+    success: true,
+    msg: "操作成功"
+  };
 };
 
 // 统一处理文件列表变化,更新 formModel.images(用逗号拼接)

+ 59 - 96
src/views/storeDecoration/add.vue

@@ -10,9 +10,9 @@
         <!-- 装修类型 -->
         <el-form-item label="装修类型" prop="renovationType">
           <el-radio-group v-model="formData.renovationType">
-            <el-radio :label="1">新房装修</el-radio>
-            <el-radio :label="2">旧房改造</el-radio>
-            <el-radio :label="3">局部装修</el-radio>
+            <el-radio :label="1"> 新房装修 </el-radio>
+            <el-radio :label="2"> 旧房改造 </el-radio>
+            <el-radio :label="3"> 局部装修 </el-radio>
           </el-radio-group>
         </el-form-item>
 
@@ -106,7 +106,9 @@
             @clear="handleCityClear"
           >
             <template #suffix>
-              <el-icon class="cursor-pointer" @click="showCityDialog = true"><ArrowDown /></el-icon>
+              <el-icon class="cursor-pointer" @click="showCityDialog = true">
+                <ArrowDown />
+              </el-icon>
             </template>
           </el-input>
         </el-form-item>
@@ -128,9 +130,9 @@
         <el-form-item prop="agreementConfirmed">
           <el-checkbox v-model="formData.agreementConfirmed" :true-label="1" :false-label="0">
             我已阅读并同意
-            <el-link type="primary" :underline="false" @click="handleShowAgreement">《用户服务协议》</el-link>
+            <el-link type="primary" :underline="false" @click="handleShowAgreement"> 《用户服务协议》 </el-link>
-            <el-link type="primary" :underline="false" @click="handleShowPrivacy">《隐私政策》</el-link>
+            <el-link type="primary" :underline="false" @click="handleShowPrivacy"> 《隐私政策》 </el-link>
             ,允许装修商家查看我的需求信息并与我联系
           </el-checkbox>
         </el-form-item>
@@ -138,8 +140,8 @@
 
       <template #footer>
         <div class="dialog-footer">
-          <el-button @click="handleClose">返回</el-button>
-          <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
+          <el-button @click="handleClose"> 返回 </el-button>
+          <el-button type="primary" :loading="submitLoading" @click="handleSubmit"> 确定 </el-button>
         </div>
       </template>
     </el-dialog>
@@ -179,8 +181,8 @@
       </div>
       <template #footer>
         <div class="dialog-footer">
-          <el-button @click="showCityDialog = false">取消</el-button>
-          <el-button type="primary" @click="handleConfirmCity">确定</el-button>
+          <el-button @click="showCityDialog = false"> 取消 </el-button>
+          <el-button type="primary" @click="handleConfirmCity"> 确定 </el-button>
         </div>
       </template>
     </el-dialog>
@@ -462,7 +464,7 @@
       </div>
       <template #footer>
         <div class="dialog-footer-center">
-          <el-button type="primary" @click="agreementDialogVisible = false">已阅读并同意协议内容</el-button>
+          <el-button type="primary" @click="agreementDialogVisible = false"> 已阅读并同意协议内容 </el-button>
         </div>
       </template>
     </el-dialog>
@@ -539,7 +541,7 @@
       </div>
       <template #footer>
         <div class="dialog-footer-center">
-          <el-button type="primary" @click="privacyDialogVisible = false">已阅读并同意协议内容</el-button>
+          <el-button type="primary" @click="privacyDialogVisible = false"> 已阅读并同意协议内容 </el-button>
         </div>
       </template>
     </el-dialog>
@@ -549,9 +551,17 @@
 <script setup lang="ts" name="decorationAdd">
 import { ref, reactive, onMounted } from "vue";
 import { useRoute, useRouter } from "vue-router";
-import { ElMessage, type FormInstance, type UploadFile, type UploadProps, type UploadRequestOptions } from "element-plus";
+import {
+  ElMessage,
+  type FormInstance,
+  type UploadFile,
+  type UploadProps,
+  type UploadRequestOptions,
+  type UploadUserFile
+} from "element-plus";
 import { Plus, ArrowDown } from "@element-plus/icons-vue";
-import { saveOrUpdateDecoration, getDistrict, uploadDecorationImage } from "@/api/modules/storeDecoration";
+import { saveOrUpdateDecoration, getDistrict } from "@/api/modules/storeDecoration";
+import { uploadFileToOss } from "@/api/upload.js";
 import { localGet } from "@/utils";
 
 const route = useRoute();
@@ -600,7 +610,7 @@ const selectedCityName = ref("");
 const selectedDistrictName = ref("");
 
 // 禁用「当天之前」的日期(核心方法)
-const disabledBeforeToday = (date) => {
+const disabledBeforeToday = date => {
   // 初始化今天的日期(重置时分秒为0,避免时间戳干扰)
   const today = new Date();
   today.setHours(0, 0, 0, 0);
@@ -778,82 +788,50 @@ const beforeUpload: UploadProps["beforeUpload"] = (rawFile: File) => {
   return true;
 };
 
-// 图片上传 - 点击加号时调用 /file/uploadMore 接口
+// 图片上传:OSS 直传(@/api/upload.js)
 const handleImageUpload = async (options: UploadRequestOptions) => {
-  // 获取文件对象,可能是 options.file 或 options.file.raw
-  const file = options.file.raw || options.file;
-  
+  const uploadFileItem = options.file as UploadUserFile;
+  const raw = uploadFileItem.raw || uploadFileItem;
+  const file = raw instanceof File ? raw : null;
+
   if (!file) {
-    console.error("文件对象不存在");
     ElMessage.error("文件对象不存在");
     return;
   }
 
-  console.log("开始上传文件:", file.name, "类型:", file.type, "大小:", file.size);
-
-  const uploadFormData = new FormData();
-  uploadFormData.append("file", file);
-
-  options.file.status = "uploading";
-  options.file.percentage = 0;
+  uploadFileItem.status = "uploading";
+  uploadFileItem.percentage = 0;
 
   try {
-    console.log("调用 /alienStore/file/uploadMore 接口上传文件");
-    // 调用 /alienStore/file/uploadMore 接口上传文件
-    const result: any = await uploadDecorationImage(uploadFormData);
-    console.log("上传接口返回结果:", result);
-
-    if (result?.code === 200 || result?.code === 0) {
-      let fileUrl = "";
-
-      // 处理不同的返回格式
-      if (Array.isArray(result.data) && result.data.length > 0) {
-        fileUrl = result.data[0];
-      } else if (typeof result.data === "string") {
-        fileUrl = result.data;
-      } else if (result.data?.fileUrl) {
-        fileUrl = result.data.fileUrl;
-      } else if (result.data?.url) {
-        fileUrl = result.data.url;
-      } else if (result.fileUrl) {
-        fileUrl = result.fileUrl;
-      } else if (result.url) {
-        fileUrl = result.url;
-      }
+    const fileUrl = await uploadFileToOss(file, "image");
+    if (!fileUrl) {
+      throw new Error("上传失败,未返回文件地址");
+    }
 
-      if (fileUrl) {
-        options.file.status = "success";
-        options.file.percentage = 100;
-        options.file.url = fileUrl;
-        options.file.response = { url: fileUrl };
-
-        // 获取图片路径后,记录到 attachmentUrls 数组中,提交时会传递给保存接口
-        if (!formData.attachmentUrls.includes(fileUrl)) {
-          formData.attachmentUrls.push(fileUrl);
-          console.log("文件上传成功,路径已记录:", fileUrl);
-          console.log("当前附件列表:", formData.attachmentUrls);
-        }
-        options.onSuccess?.(result);
-        // 触发表单验证
-        formRef.value?.validateField("attachmentUrls");
-      } else {
-        console.error("上传接口返回数据格式错误:", result);
-        throw new Error("上传接口返回数据格式错误");
-      }
-    } else {
-      throw new Error(result?.msg || result?.message || "文件上传失败");
+    uploadFileItem.status = "success";
+    uploadFileItem.percentage = 100;
+    uploadFileItem.url = fileUrl;
+    uploadFileItem.response = { url: fileUrl };
+
+    if (!formData.attachmentUrls.includes(fileUrl)) {
+      formData.attachmentUrls.push(fileUrl);
     }
+    options.onSuccess?.({ code: 200, data: fileUrl, url: fileUrl });
+    formRef.value?.validateField("attachmentUrls");
   } catch (error: any) {
     console.error("文件上传失败:", error);
-    options.file.status = "fail";
-    if (options.file.url && options.file.url.startsWith("blob:")) {
-      URL.revokeObjectURL(options.file.url);
+    uploadFileItem.status = "fail";
+    if (uploadFileItem.url && uploadFileItem.url.startsWith("blob:")) {
+      URL.revokeObjectURL(uploadFileItem.url);
     }
-    const index = fileList.value.findIndex(f => f.uid === options.file.uid);
+    const index = fileList.value.findIndex(f => f.uid === uploadFileItem.uid);
     if (index > -1) {
       fileList.value.splice(index, 1);
     }
-    ElMessage.error(error?.message || "文件上传失败");
+    if (error?.message === "上传失败,未返回文件地址") {
+      ElMessage.error(error.message);
+    }
+    // 其余错误(含 OSS 签名/网络)已在 upload.js 中提示
     options.onError?.(error);
   }
 };
@@ -906,7 +884,7 @@ const handleSubmit = async () => {
     submitLoading.value = true;
     try {
       // 确保 attachmentUrls 是数组格式,包含所有上传成功的图片路径
-      const attachmentUrlsList = Array.isArray(formData.attachmentUrls) 
+      const attachmentUrlsList = Array.isArray(formData.attachmentUrls)
         ? formData.attachmentUrls.filter(url => url && url.trim() !== "")
         : [];
 
@@ -983,82 +961,67 @@ onMounted(() => {
 <style lang="scss" scoped>
 .add-container {
   :deep(.el-dialog__body) {
-    padding: 20px;
     max-height: 70vh;
+    padding: 20px;
     overflow-y: auto;
   }
-
   :deep(.el-form-item) {
     margin-bottom: 20px;
   }
-
   :deep(.el-radio-group) {
     display: flex;
     gap: 20px;
   }
-
   .upload-tip {
-    font-size: 12px;
-    color: #999;
     margin-top: 5px;
+    font-size: 12px;
+    color: #999999;
   }
-
   .dialog-footer {
-    text-align: center;
     padding: 20px 0 0;
+    text-align: center;
   }
-
   :deep(.el-upload--picture-card) {
     width: 100px;
     height: 100px;
   }
-
   :deep(.el-upload-list--picture-card .el-upload-list__item) {
     width: 100px;
     height: 100px;
   }
-
   .city-selector {
     padding: 10px 0;
   }
-
   .cursor-pointer {
     cursor: pointer;
   }
-
   .agreement-content {
     height: 50vh;
     padding: 10px;
     overflow-y: auto;
     font-size: 14px;
     line-height: 1.6;
-
     h3 {
       margin-top: 20px;
       margin-bottom: 10px;
       font-weight: 600;
     }
-
     p {
       margin-bottom: 10px;
     }
-
     ul {
-      margin-left: 20px;
       margin-bottom: 10px;
-
+      margin-left: 20px;
       li {
         margin-bottom: 5px;
       }
     }
   }
-
   .dialog-footer-center {
     display: flex;
     align-items: center;
     justify-content: center;
     width: 100%;
-
     .el-button {
       width: 406px;
       height: 60px;

+ 27 - 33
src/views/storeDecoration/officialPhotoAlbum/index.vue

@@ -185,7 +185,7 @@ import {
   getOfficialImgList,
   deleteOfficialImg
 } from "@/api/modules/storeDecoration";
-import { uploadImg } from "@/api/modules/newLoginApi";
+import { uploadFilesToOss } from "@/api/upload.js";
 
 // 扩展图片类型
 interface AlbumImage extends UploadUserFile {
@@ -395,34 +395,31 @@ const validateImageDimensions = (file: File): Promise<boolean> => {
   });
 };
 
-// 自定义图片上传API
+// 自定义图片上传 API(OSS 直传)
 const customImageUploadApi = async (formData: FormData): Promise<any> => {
-  const file = formData.get("file") as File;
+  const raw = formData.get("file");
+  const file = raw instanceof File ? raw : null;
+  if (!file) {
+    throw new Error("请选择文件");
+  }
 
-  // 先进行尺寸校验
   try {
     await validateImageDimensions(file);
   } catch (error) {
     throw error;
   }
 
-  // 校验通过后调用实际上传接口
-  const result: any = await uploadImg(formData);
-
-  // 处理返回格式:可能是 { data: { fileUrl: string } } 或 { data: string[] }
-  const fileUrl = result?.data?.fileUrl || result?.data?.[0] || result?.fileUrl;
+  const urls = await uploadFilesToOss(file, "image");
+  const fileUrl = urls[0];
+  if (!fileUrl) {
+    throw new Error("上传失败,未返回地址");
+  }
 
-  // 返回组件期望的格式
   const response = {
-    data: {
-      fileUrl: fileUrl
-    },
-    fileUrl: fileUrl // 也提供 fileUrl 字段,确保组件能正确获取
+    data: { fileUrl },
+    fileUrl
   };
-
-  // 确保上传成功后立即更新视图
   await nextTick();
-
   return response;
 };
 
@@ -471,34 +468,31 @@ const handleVideoUploadSuccess = async (url: string) => {
   }
 };
 
-// 自定义视频上传API(使用与图片相同的上传接口
+// 自定义视频上传 API(OSS 直传
 const customVideoUploadApi = async (formData: FormData): Promise<any> => {
-  const file = formData.get("file") as File;
+  const raw = formData.get("file");
+  const file = raw instanceof File ? raw : null;
+  if (!file) {
+    throw new Error("请选择文件");
+  }
 
-  // 视频大小校验(500MB)
   const fileSizeMB = file.size / 1024 / 1024;
   if (fileSizeMB > 500) {
     ElMessage.warning(`视频大小不能超过 500MB,当前为 ${fileSizeMB.toFixed(2)}MB`);
     throw new Error("视频大小超过限制");
   }
 
-  // 调用与图片相同的上传接口
-  const result: any = await uploadImg(formData);
-
-  // 处理返回格式:可能是 { data: { fileUrl: string } } 或 { data: string[] }
-  const fileUrl = result?.data?.fileUrl || result?.data?.[0] || result?.fileUrl;
+  const urls = await uploadFilesToOss(file, "video");
+  const fileUrl = urls[0];
+  if (!fileUrl) {
+    throw new Error("上传失败,未返回地址");
+  }
 
-  // 返回组件期望的格式
   const response = {
-    data: {
-      fileUrl: fileUrl
-    },
-    fileUrl: fileUrl // 也提供 fileUrl 字段,确保组件能正确获取
+    data: { fileUrl },
+    fileUrl
   };
-
-  // 确保上传成功后立即更新视图
   await nextTick();
-
   return response;
 };
 

+ 35 - 60
src/views/storeDecoration/personnelConfig/index.vue

@@ -243,7 +243,7 @@
             :width="'150px'"
             :height="'150px'"
             :file-size="9999"
-            :api="uploadImg"
+            :api="personnelOssUploadAvatar"
             :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
             :border-radius="'8px'"
           />
@@ -333,7 +333,7 @@ import ProTable from "@/components/ProTable/index.vue";
 import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
 import UploadImg from "@/components/Upload/Img.vue";
 import UploadImgs from "@/components/Upload/Imgs.vue";
-import { uploadImg } from "@/api/modules/newLoginApi";
+import { uploadFileToOss, uploadFilesToOss } from "@/api/upload.js";
 import type { UploadUserFile } from "element-plus";
 import { localGet } from "@/utils";
 import {
@@ -649,66 +649,41 @@ const formatTime = (time: string | null | undefined) => {
 // 弹窗标题
 const dialogTitle = computed(() => (editId.value !== null ? "编辑" : "新建"));
 
-// 自定义上传函数,正确处理响应格式
-const handleCustomUpload = async (formData: FormData): Promise<any> => {
-  try {
-    const response: any = await uploadImg(formData);
-    console.log("=== 自定义上传函数 ===");
-    console.log("完整响应:", response);
-
-    // API 返回格式: { code: 200, success: true, data: ["https://..."], msg: "操作成功" }
-    // 需要提取 response.data[0] 作为图片 URL
-    let imageUrl = "";
-
-    if (response && (response.code === 200 || response.code === "200")) {
-      if (Array.isArray(response.data) && response.data.length > 0) {
-        imageUrl = response.data[0];
-      } else if (typeof response.data === "string") {
-        imageUrl = response.data;
-      } else if (response.data?.fileUrl) {
-        imageUrl = response.data.fileUrl;
-      } else if (response.fileUrl) {
-        imageUrl = response.fileUrl;
-      }
-    }
-
-    console.log("提取的图片URL:", imageUrl);
-
-    if (!imageUrl) {
-      console.error("无法提取图片URL,响应格式可能不正确");
-      throw new Error("无法提取图片URL");
-    }
-
-    // 返回格式需要兼容 Imgs.vue 组件的处理逻辑
-    // Imgs.vue 的 handleHttpUpload 中:
-    // const { data } = await api(formData);
-    // if (props.onSuccess) {
-    //   props.onSuccess(data.fileUrl ? data.fileUrl : data[0]);
-    // }
-    // options.onSuccess(data);
-    //
-    // uploadSuccess 函数会检查 response 的类型:
-    // - 如果是字符串,直接使用
-    // - 如果是数组,使用 response[0]
-    // - 如果有 fileUrl,使用 response.fileUrl
-    //
-    // 所以需要返回一个对象,其中 fileUrl 是图片URL,这样 uploadSuccess 会使用 response.fileUrl
-    const result = {
-      fileUrl: imageUrl, // 供 uploadSuccess 使用(会设置 uploadFile.url = response.fileUrl)
-      data: [imageUrl], // 备用
-      code: response.code,
-      msg: response.msg,
-      success: response.success
-    };
-
-    console.log("返回给组件的数据:", result);
-    console.log("==================");
+/**
+ * 头像上传:兼容 UploadImg(Img.vue)里 `const { data } = await api(formData)` 的解构写法
+ */
+const personnelOssUploadAvatar = async (formData: FormData): Promise<{ data: { fileUrl: string } }> => {
+  const raw = formData.get("file");
+  const file = raw instanceof File ? raw : null;
+  if (!file) {
+    throw new Error("请选择文件");
+  }
+  const url = await uploadFileToOss(file, "image");
+  return { data: { fileUrl: url } };
+};
 
-    return result;
-  } catch (error) {
-    console.error("自定义上传函数 - 上传失败:", error);
-    throw error;
+/**
+ * 背景图/视频:OSS 直传,返回格式兼容 UploadImgs(读取 response.fileUrl)
+ */
+const handleCustomUpload = async (formData: FormData): Promise<any> => {
+  const raw = formData.get("file");
+  const file = raw instanceof File ? raw : null;
+  if (!file) {
+    throw new Error("请选择文件");
+  }
+  const isVideo = typeof file.type === "string" && file.type.startsWith("video/");
+  const urls = await uploadFilesToOss(file, isVideo ? "video" : "image");
+  const fileUrl = urls[0];
+  if (!fileUrl) {
+    throw new Error("上传失败,未返回地址");
   }
+  return {
+    fileUrl,
+    data: [fileUrl],
+    code: 200,
+    success: true,
+    msg: "操作成功"
+  };
 };
 
 // 表单数据

+ 9 - 17
src/views/storeDecoration/storeEntranceMap/index.vue

@@ -74,7 +74,7 @@ import { ref, reactive, onMounted } from "vue";
 import { ElMessage, ElNotification } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import UploadImg from "@/components/Upload/Img.vue";
-import { uploadImg } from "@/api/modules/newLoginApi";
+import { uploadFileToOss } from "@/api/upload.js";
 import { getEntranceImg, saveEntranceImg } from "@/api/modules/storeDecoration";
 import { localGet } from "@/utils";
 
@@ -94,23 +94,15 @@ const validateImageDimensions = (file: File): Promise<boolean> => {
   });
 };
 
-// 自定义上传API(已移除尺寸校验)
-const customUploadApi = async (formData: FormData): Promise<any> => {
-  // 已移除尺寸校验,直接上传
-
-  // 校验通过后调用实际上传接口
-  const response: any = await uploadImg(formData);
-  // 处理返回格式:{ code, success, data: string[], msg }
-  // UploadImg组件期望的格式:{ data: { fileUrl: string } } 或 { data: string[] }
-  if (response && response.code === 200 && response.data && Array.isArray(response.data) && response.data.length > 0) {
-    // 返回组件期望的格式:组件会检查 data.fileUrl,如果没有则取 data[0]
-    return {
-      data: {
-        fileUrl: response.data[0] // 取数组第一个元素作为图片URL
-      }
-    };
+/** OSS 直传;UploadImg 内为 `const { data } = await api(formData)`,需返回 `{ data: { fileUrl } }` */
+const customUploadApi = async (formData: FormData): Promise<{ data: { fileUrl: string } }> => {
+  const raw = formData.get("file");
+  const file = raw instanceof File ? raw : null;
+  if (!file) {
+    throw new Error("请选择文件");
   }
-  throw new Error(response?.msg || "上传失败");
+  const url = await uploadFileToOss(file, "image");
+  return { data: { fileUrl: url } };
 };
 
 // 表单校验规则

+ 19 - 8
src/views/storeDecoration/storeHeadMap/index.vue

@@ -146,7 +146,8 @@ import { ArrowRight } from "@element-plus/icons-vue";
 import Sortable from "sortablejs";
 import UploadImg from "@/components/Upload/Img.vue";
 import UploadImgs from "@/components/Upload/Imgs.vue";
-import { uploadImg, getStoreOcrData } from "@/api/modules/newLoginApi";
+import { getStoreOcrData } from "@/api/modules/newLoginApi";
+import { uploadFilesToOss } from "@/api/upload.js";
 import { getStoreHeadImg, saveStoreHeadImg } from "@/api/modules/storeDecoration";
 import { getDetail } from "@/api/modules/homeEntry";
 import { localGet } from "@/utils";
@@ -237,15 +238,25 @@ const displayImages = computed(() => {
   return formData.multipleImages.slice(0, 3).map(item => item.url || "");
 });
 
-// 处理上传图片
+/**
+ * OSS 直传:同时满足 UploadImg(解构 data)与 UploadImgs(读 response.fileUrl)
+ */
 const uploadImageResult = async (formData: FormData): Promise<any> => {
-  const response: any = await uploadImg(formData);
-  if (response.code == 200) {
-    return {
-      fileUrl: response.data[0] // 取数组第一个元素作为图片URL,直接返回 fileUrl
-    };
+  const raw = formData.get("file");
+  const file = raw instanceof File ? raw : null;
+  if (!file) {
+    throw new Error("请选择文件");
+  }
+  const isVideo = typeof file.type === "string" && file.type.startsWith("video/");
+  const urls = await uploadFilesToOss(file, isVideo ? "video" : "image");
+  const url = urls[0];
+  if (!url) {
+    throw new Error("上传失败,未返回地址");
   }
-  throw new Error(response?.msg || "上传失败");
+  return {
+    fileUrl: url,
+    data: { fileUrl: url }
+  };
 };
 
 // 单图模式表单校验规则