liuxiaole 1 tydzień temu
rodzic
commit
c25ae2b5d9

+ 52 - 16
src/views/operationManagement/newActivity.vue

@@ -81,8 +81,8 @@
           </div>
         </div>
 
-        <!-- 4. 活动详情图 -->
-        <div class="form-item-card">
+        <!-- 4. 活动详情图(仅营销活动需要) -->
+        <div v-if="activityModel.activityType === 'MARKETING'" class="form-item-card">
           <div class="form-item card-item">
             <div class="form-label form-label-col">活动详情图 <span class="required">*</span></div>
             <div class="detail-upload-wrapper upload-slot-dashed">
@@ -401,10 +401,11 @@ const imageViewerInitialIndex = ref(0);
 
 // 是否有未上传的图片
 const hasUnuploadedImages = computed(() => {
-  return (
-    titleFileList.value.some(file => file.status === "ready" || file.status === "uploading") ||
-    detailFileList.value.some(file => file.status === "ready" || file.status === "uploading")
-  );
+  const titlePending = titleFileList.value.some(file => file.status === "ready" || file.status === "uploading");
+  const detailPending =
+    activityModel.value.activityType === "MARKETING" &&
+    detailFileList.value.some(file => file.status === "ready" || file.status === "uploading");
+  return titlePending || detailPending;
 });
 
 // ==================== 表单验证规则 ====================
@@ -646,6 +647,10 @@ const rules = reactive({
     {
       required: true,
       validator: (_rule: any, _value: any, callback: any) => {
+        if (activityModel.value.activityType !== "MARKETING") {
+          callback();
+          return;
+        }
         const successFiles = detailFileList.value.filter((f: any) => f.status === "success" && f.url);
         if (successFiles.length === 0) {
           callback(new Error("请上传活动详情图"));
@@ -1204,9 +1209,10 @@ const handlegetTime = (val: string) => {
   return val || "";
 };
 
-/** 将 AI 接口返回写入 banner / 详情图 */
+/** 将 AI 接口返回写入 banner / 详情图(评价有礼仅写入 banner) */
 const applyPromotionImagesFromResponse = (data: any) => {
   if (!data || typeof data !== "object") return;
+  const isMarketing = activityModel.value.activityType === "MARKETING";
 
   const titleObj = data.activityTitleImg;
   if (titleObj && titleObj.imgUrl) {
@@ -1231,6 +1237,13 @@ const applyPromotionImagesFromResponse = (data: any) => {
     }
   }
 
+  if (!isMarketing) {
+    nextTick(() => {
+      ruleFormRef.value?.validateField(["activityTitleImage"]);
+    });
+    return;
+  }
+
   const detailObj = data.activityDetailImg;
   if (detailObj) {
     let urls: string[] = [];
@@ -1282,7 +1295,11 @@ const handleAiGenerate = async () => {
     if (res && res.code == 200) {
       applyPromotionImagesFromResponse(res.data);
       nextTick(() => {
-        ruleFormRef.value?.validateField(["activityTitleImage", "activityDetailImage"]);
+        const fields: string[] =
+          activityModel.value.activityType === "MARKETING"
+            ? ["activityTitleImage", "activityDetailImage"]
+            : ["activityTitleImage"];
+        ruleFormRef.value?.validateField(fields);
       });
       ElMessage.success(res.msg || "图片生成成功");
     } else {
@@ -1340,13 +1357,22 @@ watch(
           "voucherQuantity"
         ]);
       } else if (typeValue === "COMMENT") {
-        // 切换到评论有礼:清除营销活动相关字段
+        // 切换到评论有礼:清除营销活动相关字段(含活动详情图,评价有礼不需要)
         activityModel.value.signupTimeRange = [];
         activityModel.value.activityLimitPeople = "";
         activityModel.value.activityDetails = "";
+        const detailUids = new Set(detailFileList.value.map((f: any) => f.uid));
+        detailFileList.value.forEach((f: any) => {
+          if (f?.url && String(f.url).startsWith("blob:")) URL.revokeObjectURL(f.url);
+        });
+        detailFileList.value = [];
+        detailImageUrl.value = "";
+        activityModel.value.activityDetailImg = null;
+        activityModel.value.activityDetailImage = "";
+        pendingUploadFiles.value = pendingUploadFiles.value.filter((f: any) => !detailUids.has(f.uid));
         activityModel.value.discountCouponType = activityModel.value.rewardType === "VOUCHER" ? 1 : 2;
         // 清除营销活动字段的验证状态
-        ruleFormRef.value?.clearValidate(["signupTimeRange", "activityLimitPeople", "activityDetails"]);
+        ruleFormRef.value?.clearValidate(["signupTimeRange", "activityLimitPeople", "activityDetails", "activityDetailImage"]);
         // 加载优惠券列表
         loadCouponList();
       }
@@ -1579,10 +1605,9 @@ onMounted(async () => {
             titleFileList.value = [titleFile];
           }
         }
-        // 如果有详情图片,添加到文件列表(支持多张图片,可能是字符串或数组
-        if (res.data.activityDetailImgUrl) {
+        // 营销活动:加载详情图(评价有礼不需要
+        if (activityModel.value.activityType === "MARKETING" && res.data.activityDetailImgUrl) {
           let detailImgUrls: string[] = [];
-          // 如果是字符串,可能是逗号分隔的多张图片
           if (typeof res.data.activityDetailImgUrl === "string") {
             detailImgUrls = res.data.activityDetailImgUrl.split(",").filter((url: string) => url.trim());
           } else if (Array.isArray(res.data.activityDetailImgUrl)) {
@@ -1593,9 +1618,13 @@ onMounted(async () => {
             detailImageUrl.value = detailImgUrls.join(",");
             activityModel.value.activityDetailImg = res.data.activityDetailImgUrl;
             activityModel.value.activityDetailImage = detailImgUrls.join(",");
-            // 将多张图片添加到文件列表
             detailFileList.value = detailImgUrls.map((url: string) => handleImageParam(url));
           }
+        } else if (activityModel.value.activityType === "COMMENT") {
+          detailFileList.value = [];
+          detailImageUrl.value = "";
+          activityModel.value.activityDetailImg = null;
+          activityModel.value.activityDetailImage = "";
         }
       }
     } catch (error) {
@@ -1650,11 +1679,18 @@ const handleSubmit = async () => {
       // 活动时间:开始日期传当天 00:00:00,结束日期传当天 23:59:59
       const startTime = activityStartDate ? `${activityStartDate} 00:00:00` : "";
       const endTime = activityEndDate ? `${activityEndDate} 23:59:59` : "";
+      const detailUrls =
+        activityModel.value.activityType === "MARKETING" && detailImageUrl.value
+          ? detailImageUrl.value
+              .split(",")
+              .map((s: string) => s.trim())
+              .filter(Boolean)
+          : [];
       const auditParam = {
         text: activityModel.value.imgDescribe?.trim()
           ? `${activityModel.value.activityName},${activityModel.value.imgDescribe.trim()}`
           : activityModel.value.activityName,
-        image_urls: [titleImageUrl.value, ...(detailImageUrl.value ? detailImageUrl.value.split(",") : [])].filter(Boolean)
+        image_urls: [titleImageUrl.value, ...detailUrls].filter(Boolean)
       };
       const params: any = {
         activityType: activityModel.value.activityType,
@@ -1696,7 +1732,7 @@ const handleSubmit = async () => {
         storeId: localGet("createdId")
       };
       params.activityDetailImg = {
-        imgUrl: detailImageUrl.value,
+        imgUrl: activityModel.value.activityType === "MARKETING" ? detailImageUrl.value : "",
         imgSort: 0,
         storeId: localGet("createdId")
       };

+ 35 - 22
src/views/storeDecoration/officialPhotoAlbum/index.vue

@@ -187,12 +187,22 @@ import {
 } from "@/api/modules/storeDecoration";
 import { uploadFilesToOss } from "@/api/upload.js";
 
+/** 普通官方相册图 */
+const OFFICIAL_IMG_TYPE_ALBUM = 2;
+/** 环境 Tab 及约定走环境图类型的相册 Tab(如 id=182) */
+const OFFICIAL_IMG_TYPE_ENV_OR_SPECIAL_ALBUM = 4;
+
+/** 相册 Tab 的 name 为相册 id 字符串;列入此集合的 Tab 拉列表、保存时 imgType 均为 4 */
+const albumTabNamesWithImgType4 = new Set<string>(["182"]);
+
+const isAlbumTabImgType4 = (tabKey: string) => albumTabNamesWithImgType4.has(String(tabKey));
+
 // 扩展图片类型
 interface AlbumImage extends UploadUserFile {
   isCover?: boolean;
   businessId?: number; // 相册ID
   imgSort?: number; // 排序
-  imgType?: number; // 图片类型,固定为2
+  imgType?: number; // 相册默认 2;环境 / 特殊相册 Tab 为 4
   imgId?: number; // 图片ID(从服务器返回)
 }
 
@@ -217,7 +227,8 @@ interface Album {
 
 const createDialogVisible = ref(false);
 const createFormRef = ref<FormInstance>();
-const activeTab = ref("environment"); // 当前选中的tab:environment、video 或 相册ID
+// 环境 / 视频 / 相册 id(字符串);初始为空,相册列表加载后默认选中第一项
+const activeTab = ref("");
 const previewVisible = ref(false);
 const previewImageUrl = ref("");
 const previewVideoUrl = ref("");
@@ -538,11 +549,16 @@ const saveImageToServer = async (imgUrl: string) => {
 
     const imgSort = currentImages.filter((img: AlbumImage) => img.imgId).length + 1; // 排序为已保存图片数量+1
 
+    const imgType =
+      activeTab.value === "environment" || isAlbumTabImgType4(activeTab.value)
+        ? OFFICIAL_IMG_TYPE_ENV_OR_SPECIAL_ALBUM
+        : OFFICIAL_IMG_TYPE_ALBUM;
+
     const params = [
       {
         businessId: businessId,
         imgSort: imgSort,
-        imgType: 2, // 图片类型为2
+        imgType,
         imgUrl: imgUrl,
         storeId: Number(storeId)
       }
@@ -755,14 +771,8 @@ const handleDeleteAlbum = async () => {
       createDialogVisible.value = false;
       resetCreateForm();
 
-      // 重新获取相册列表
+      // 重新获取相册列表(当前 tab 若已不存在会自动选中第一项或清空)
       await getAlbumList();
-
-      // 如果删除的是当前选中的相册,切换到环境tab
-      if (String(editId.value) === activeTab.value) {
-        activeTab.value = "environment";
-        await loadEnvironmentImages();
-      }
     } else {
       ElMessage.error(res?.msg || "删除相册失败");
     }
@@ -1076,10 +1086,17 @@ const getAlbumList = async () => {
         videos: reactive<AlbumVideo[]>([]),
         coverImageId: album.coverImageId
       }));
-      // 默认选中"环境"tab
-      if (!activeTab.value || activeTab.value === "") {
-        activeTab.value = "environment";
-        await loadEnvironmentImages();
+      // 默认选中第一个相册;若当前 tab 仍有效则保留(如刷新列表、新建后切到最后一个)
+      const prev = activeTab.value;
+      const idSet = new Set(albumList.value.map(a => String(a.id)));
+      if (albumList.value.length > 0) {
+        const keepSpecial = prev === "environment" || prev === "video";
+        const keepAlbum = Boolean(prev && idSet.has(String(prev)));
+        if (!keepSpecial && !keepAlbum) {
+          activeTab.value = String(albumList.value[0].id);
+        }
+      } else if (prev !== "environment" && prev !== "video") {
+        activeTab.value = "";
       }
     }
   } catch (error) {
@@ -1100,7 +1117,7 @@ const loadEnvironmentImages = async () => {
     // 环境图片:使用 storeId 作为 businessId(需要根据实际API调整)
     // 或者如果环境图片不需要 businessId,可能需要使用其他API
     // 这里先使用 storeId 作为 businessId
-    const imageRes: any = await getOfficialImgList(Number(storeId), Number(storeId), 2);
+    const imageRes: any = await getOfficialImgList(Number(storeId), Number(storeId), OFFICIAL_IMG_TYPE_ENV_OR_SPECIAL_ALBUM);
 
     if (imageRes && (imageRes.code === 200 || imageRes.code === "200") && imageRes.data) {
       const imageList = Array.isArray(imageRes.data) ? imageRes.data : [];
@@ -1194,8 +1211,8 @@ const loadAlbumImages = async (albumId: string) => {
       return;
     }
 
-    // 加载图片(imgType = 2)
-    const imageRes: any = await getOfficialImgList(Number(albumId), Number(storeId), 2);
+    const listImgType = isAlbumTabImgType4(albumId) ? OFFICIAL_IMG_TYPE_ENV_OR_SPECIAL_ALBUM : OFFICIAL_IMG_TYPE_ALBUM;
+    const imageRes: any = await getOfficialImgList(Number(albumId), Number(storeId), listImgType);
 
     const current = albumList.value.find(album => String(album.id) === albumId);
     if (!current) {
@@ -1253,13 +1270,9 @@ const loadAlbumImages = async (albumId: string) => {
   }
 };
 
-// 页面初始化
+// 页面初始化:拉相册列表后默认第一项,图片由 watch(activeTab) 加载
 onMounted(async () => {
   await getAlbumList();
-  // 默认加载环境图片
-  if (activeTab.value === "environment") {
-    await loadEnvironmentImages();
-  }
 });
 </script>
 

+ 22 - 17
src/views/storeDecoration/storeHeadMap/index.vue

@@ -84,6 +84,9 @@ import { ref, reactive, computed, onMounted, watch, nextTick } from "vue";
 import { ElMessage, ElNotification } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import type { UploadUserFile } from "element-plus";
+
+/** 服务端图片主键,用于 saveOrUpdateImg 区分更新与新增 */
+type HeadMapUploadFile = UploadUserFile & { serverImgId?: number | string };
 import { ArrowRight } from "@element-plus/icons-vue";
 import Sortable from "sortablejs";
 import UploadImgs from "@/components/Upload/Imgs.vue";
@@ -145,7 +148,7 @@ const handleStoreOcr = async (params: { imageUrl: string }) => {
 };
 
 const formData = reactive({
-  multipleImages: [] as UploadUserFile[]
+  multipleImages: [] as HeadMapUploadFile[]
 });
 
 const previewData = reactive({
@@ -232,16 +235,17 @@ const getStoreHeadImgData = async () => {
       return;
     }
 
-    const res: any = await getStoreHeadImg(storeId, 21);
+    const res: any = await getStoreHeadImg(storeId, 20);
     if (res && (res.code === 200 || res.code === "200") && res.data) {
       const dataAny = res.data as any;
       const imgList = Array.isArray(dataAny) ? dataAny : dataAny.storeImgList || [];
       if (imgList.length > 0) {
         formData.multipleImages = imgList.map((item: any, index: number) => ({
-          uid: item.id || index,
+          uid: item.id != null ? item.id : `loaded-${index}-${Date.now()}`,
           name: `image-${index + 1}`,
           url: item.imgUrl,
-          status: "success"
+          status: "success",
+          serverImgId: item.id != null ? item.id : undefined
         }));
         nextTick(() => {
           initDragSort();
@@ -277,12 +281,6 @@ onMounted(async () => {
 
 const handleSave = async () => {
   if (!multipleFormRef.value) return;
-  try {
-    await multipleFormRef.value.validate();
-  } catch {
-    ElMessage.warning("至少上传3张图片");
-    return;
-  }
 
   const userInfo: any = localGet("geeker-user")?.userInfo || {};
   const storeId = userInfo.storeId;
@@ -293,16 +291,23 @@ const handleSave = async () => {
 
   loading.value = true;
   try {
-    const storeImgList = formData.multipleImages.map((item, index) => ({
-      imgType: 21,
-      imgUrl: item.url || "",
-      imgSort: index + 1,
-      storeId: Number(storeId)
-    }));
+    const storeImgList = formData.multipleImages.map((item, index) => {
+      const row: Record<string, unknown> = {
+        imgType: 20,
+        imgUrl: item.url || "",
+        imgSort: index + 1,
+        storeId: Number(storeId)
+      };
+      const sid = (item as HeadMapUploadFile).serverImgId;
+      if (sid != null && sid !== "") {
+        row.id = typeof sid === "string" ? Number(sid) || sid : sid;
+      }
+      return row;
+    });
 
     const params = {
       imgMode: 1,
-      imgType: 21,
+      imgType: 20,
       storeId: Number(storeId),
       storeImgList
     };

+ 4 - 4
src/views/ticketManagement/index.vue

@@ -240,10 +240,10 @@ const voucherColumns = reactive<ColumnProps<any>[]>([
     }
   },
   {
-    prop: "endDate",
+    prop: "validDate",
     label: "结束时间",
     render: (scope: any) => {
-      return scope.row.endDate?.replace(/-/g, "/") || "--";
+      return scope.row.validDate?.replace(/-/g, "/") || "--";
     }
   },
   {
@@ -279,10 +279,10 @@ const couponColumns = reactive<ColumnProps<any>[]>([
     }
   },
   {
-    prop: "endGetDate",
+    prop: "validDate",
     label: "结束时间",
     render: (scope: any) => {
-      return scope.row.endGetDate?.replace(/-/g, "/") || "--";
+      return scope.row.validDate?.replace(/-/g, "/") || "--";
     }
   },
   {