Explorar o código

增加运营活动ai生成活动图

zhaoyang hai 5 días
pai
achega
ce7f21b10d
Modificáronse 1 ficheiros con 160 adicións e 240 borrados
  1. 160 240
      src/views/operationManagement/newActivity.vue

+ 160 - 240
src/views/operationManagement/newActivity.vue

@@ -59,31 +59,28 @@
               <el-input v-model="activityModel.couponQuantity" placeholder="请输入" maxlength="5" />
             </el-form-item>
 
-            <!-- AI生成活动宣传图 -->
-            <el-form-item label="活动宣传图描述">
-              <div class="ai-generate-wrapper">
-                <el-input
-                  v-model="aiPromptText"
-                  :rows="4"
-                  maxlength="200"
-                  placeholder="请描述您想举办的活动图,AI助手会帮您自动生成海报图~"
-                  show-word-limit
-                  type="textarea"
-                />
-                <el-button
-                  :disabled="!aiPromptText.trim() || aiGenerating"
-                  :loading="aiGenerating"
-                  class="generate-btn"
-                  type="primary"
-                  @click="handleAIGenerate"
-                >
-                  {{ aiGenerating ? "生成中..." : "生成图片" }}
-                </el-button>
-              </div>
+            <!-- 上传图片方式 -->
+            <el-form-item label="活动图片类型" prop="uploadImgType">
+              <el-radio-group v-model="activityModel.uploadImgType">
+                <el-radio :label="1"> 本地上传 </el-radio>
+                <el-radio :label="2"> AI生成 </el-radio>
+              </el-radio-group>
+            </el-form-item>
+
+            <!-- 图片描述(当选择使用描述时显示) -->
+            <el-form-item v-if="activityModel.uploadImgType === 2" label="图片描述" prop="imgDescribe">
+              <el-input
+                v-model="activityModel.imgDescribe"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入图片描述"
+                maxlength="500"
+                show-word-limit
+              />
             </el-form-item>
 
             <!-- 活动标题图 -->
-            <el-form-item label="活动标题图" prop="activityTitleImage">
+            <el-form-item v-if="activityModel.uploadImgType === 1" label="活动标题图" prop="activityTitleImage">
               <div class="upload-item-wrapper">
                 <div class="upload-area upload-area-horizontal-21-9" :class="{ 'upload-full': titleFileList.length >= 1 }">
                   <el-upload
@@ -98,11 +95,10 @@
                     :on-preview="handlePictureCardPreview"
                     :on-remove="handleTitleRemove"
                     :show-file-list="true"
-                    :class="{ 'upload-hidden': aiGenerated }"
                     list-type="picture-card"
                   >
                     <template #trigger>
-                      <div v-if="titleFileList.length < 1 && !aiGenerated" class="upload-trigger-card el-upload--picture-card">
+                      <div v-if="titleFileList.length < 1" class="upload-trigger-card el-upload--picture-card">
                         <el-icon>
                           <Plus />
                         </el-icon>
@@ -110,14 +106,12 @@
                     </template>
                   </el-upload>
                 </div>
-                <div v-if="!aiGenerated" class="upload-hint">
-                  请上传21:9尺寸图片效果更佳,支持jpg、jpeg、png格式,上传图片不得超过20M
-                </div>
+                <div class="upload-hint">请上传21:9尺寸图片效果更佳,支持jpg、jpeg、png格式,上传图片不得超过20M</div>
               </div>
             </el-form-item>
 
             <!-- 活动详情图 -->
-            <el-form-item label="活动详情图" prop="activityDetailImage">
+            <el-form-item v-if="activityModel.uploadImgType === 1" label="活动详情图" prop="activityDetailImage">
               <div class="upload-item-wrapper">
                 <div class="upload-area upload-area-vertical" :class="{ 'upload-full': detailFileList.length >= 1 }">
                   <el-upload
@@ -132,11 +126,10 @@
                     :on-preview="handlePictureCardPreview"
                     :on-remove="handleDetailRemove"
                     :show-file-list="true"
-                    :class="{ 'upload-hidden': aiGenerated }"
                     list-type="picture-card"
                   >
                     <template #trigger>
-                      <div v-if="detailFileList.length < 1 && !aiGenerated" class="upload-trigger-card el-upload--picture-card">
+                      <div v-if="detailFileList.length < 1" class="upload-trigger-card el-upload--picture-card">
                         <el-icon>
                           <Plus />
                         </el-icon>
@@ -144,7 +137,7 @@
                     </template>
                   </el-upload>
                 </div>
-                <div v-if="!aiGenerated" class="upload-hint">请上传竖版图片,支持jpg、jpeg、png格式,上传图片不得超过20M</div>
+                <div class="upload-hint">请上传竖版图片,支持jpg、jpeg、png格式,上传图片不得超过20M</div>
               </div>
             </el-form-item>
           </div>
@@ -173,7 +166,7 @@
  * 运营活动管理 - 新增/编辑页面
  * 功能:支持运营活动的新增和编辑操作
  */
-import { computed, nextTick, onMounted, reactive, ref } from "vue";
+import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
 import type { FormInstance, UploadFile, UploadProps } from "element-plus";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { Plus } from "@element-plus/icons-vue";
@@ -183,8 +176,7 @@ import {
   getActivityDetail,
   getActivityRuleOptions,
   getCouponList,
-  updateActivity,
-  generatePromotionImage
+  updateActivity
 } from "@/api/modules/operationManagement";
 import { uploadContractImage } from "@/api/modules/licenseManagement";
 import { localGet } from "@/utils";
@@ -205,11 +197,6 @@ const ruleFormRef = ref<FormInstance>();
 // 优惠券列表
 const couponList = ref<any[]>([]);
 
-// AI生成相关
-const aiPromptText = ref<string>("");
-const aiGenerating = ref<boolean>(false);
-const aiGenerated = ref<boolean>(false);
-
 // 文件上传相关
 const titleFileList = ref<UploadFile[]>([]);
 const detailFileList = ref<UploadFile[]>([]);
@@ -328,13 +315,31 @@ const rules = reactive({
       trigger: "blur"
     }
   ],
+  uploadImgType: [{ required: true, message: "请选择上传图片方式", trigger: "change" }],
+  imgDescribe: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (activityModel.value.uploadImgType === 2) {
+          if (!value || value.trim() === "") {
+            callback(new Error("请输入图片描述"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: ["blur", "change"]
+    }
+  ],
   activityTitleImage: [
     {
       required: true,
       validator: (rule: any, value: any, callback: any) => {
-        if (!titleImageUrl.value) {
-          callback(new Error("请上传活动标题图"));
-          return;
+        if (activityModel.value.uploadImgType === 1) {
+          if (!titleImageUrl.value) {
+            callback(new Error("请上传活动标题图"));
+            return;
+          }
         }
         callback();
       },
@@ -345,9 +350,11 @@ const rules = reactive({
     {
       required: true,
       validator: (rule: any, value: any, callback: any) => {
-        if (!detailImageUrl.value) {
-          callback(new Error("请上传活动详情图"));
-          return;
+        if (activityModel.value.uploadImgType === 1) {
+          if (!detailImageUrl.value) {
+            callback(new Error("请上传活动详情图"));
+            return;
+          }
         }
         callback();
       },
@@ -379,7 +386,11 @@ const activityModel = ref<any>({
   // 优惠券ID
   couponId: "",
   // 优惠券发放数量
-  couponQuantity: ""
+  couponQuantity: "",
+  // 上传图片方式:1-正常用户,2-使用描述
+  uploadImgType: 1,
+  // 图片描述(当uploadImgType为2时使用)
+  imgDescribe: ""
 });
 
 // ==================== 日期选择器禁用规则 ====================
@@ -468,12 +479,6 @@ const handleTitleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) =>
     URL.revokeObjectURL(file.url);
   }
   titleFileList.value = [...uploadFiles];
-
-  // 如果删除的是AI生成的图片,重置AI生成状态
-  if (aiGenerated.value && uploadFiles.length === 0) {
-    aiGenerated.value = false;
-  }
-
   ElMessage.success("图片已删除");
 };
 
@@ -498,12 +503,6 @@ const handleDetailRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) =>
     URL.revokeObjectURL(file.url);
   }
   detailFileList.value = [...uploadFiles];
-
-  // 如果删除的是AI生成的图片,重置AI生成状态
-  if (aiGenerated.value && uploadFiles.length === 0) {
-    aiGenerated.value = false;
-  }
-
   ElMessage.success("图片已删除");
 };
 
@@ -776,6 +775,43 @@ const handlePictureCardPreview = (file: any) => {
   imageViewerVisible.value = true;
 };
 
+// ==================== 监听器 ====================
+
+/**
+ * 监听上传图片方式变化,切换时清除相关数据并重新验证
+ */
+watch(
+  () => activityModel.value.uploadImgType,
+  (newVal, oldVal) => {
+    if (oldVal === undefined) return; // 初始化时不处理
+
+    nextTick(() => {
+      if (newVal === 2) {
+        // 切换到使用描述:清除图片数据
+        titleFileList.value = [];
+        detailFileList.value = [];
+        titleImageUrl.value = "";
+        detailImageUrl.value = "";
+        activityModel.value.activityTitleImg = null;
+        activityModel.value.activityDetailImg = null;
+        activityModel.value.activityTitleImage = null;
+        activityModel.value.activityDetailImage = null;
+        // 清除图片字段的验证
+        ruleFormRef.value?.clearValidate(["activityTitleImage", "activityDetailImage"]);
+        // 验证描述字段
+        ruleFormRef.value?.validateField("imgDescribe");
+      } else if (newVal === 1) {
+        // 切换到正常用户:清除描述数据
+        activityModel.value.imgDescribe = "";
+        // 清除描述字段的验证
+        ruleFormRef.value?.clearValidate("imgDescribe");
+        // 验证图片字段
+        ruleFormRef.value?.validateField(["activityTitleImage", "activityDetailImage"]);
+      }
+    });
+  }
+);
+
 // ==================== 生命周期钩子 ====================
 
 /**
@@ -825,34 +861,49 @@ onMounted(async () => {
         if (res.data.startTime && res.data.endTime) {
           activityModel.value.activityTimeRange = [res.data.startTime, res.data.endTime];
         }
-        // 如果有标题图片,添加到文件列表
-        if (res.data.activityTitleImgUrl) {
-          const titleImgUrl = res.data.activityTitleImgUrl;
-          if (titleImgUrl) {
-            titleImageUrl.value = titleImgUrl;
-            activityModel.value.activityTitleImg = res.data.activityTitleImgUrl;
-            activityModel.value.activityTitleImage = titleImgUrl;
-            const titleFile = handleImageParam(titleImgUrl);
-            titleFileList.value = [titleFile];
-          }
-        }
-        // 如果有详情图片,添加到文件列表
-        if (res.data.activityDetailImgUrl) {
-          const detailImgUrl = res.data.activityDetailImgUrl;
-          if (detailImgUrl) {
-            detailImageUrl.value = detailImgUrl;
-            activityModel.value.activityDetailImg = res.data.activityDetailImgUrl;
-            activityModel.value.activityDetailImage = detailImgUrl;
-            const detailFile = handleImageParam(detailImgUrl);
-            detailFileList.value = [detailFile];
-          }
-        }
         // 加载活动规则
         if (res.data.activityRule) {
           activityModel.value.activityRule = res.data.activityRule.split(",");
         } else {
           activityModel.value.activityRule = [];
         }
+        // 加载上传图片方式
+        if (res.data.uploadImgType !== undefined) {
+          activityModel.value.uploadImgType = res.data.uploadImgType;
+        } else {
+          activityModel.value.uploadImgType = 1; // 默认为正常用户
+        }
+        // 加载图片描述
+        if (res.data.imgDescribe) {
+          activityModel.value.imgDescribe = res.data.imgDescribe;
+        } else {
+          activityModel.value.imgDescribe = "";
+        }
+        // 根据上传图片方式决定是否加载图片数据
+        if (activityModel.value.uploadImgType === 1) {
+          // 如果有标题图片,添加到文件列表
+          if (res.data.activityTitleImgUrl) {
+            const titleImgUrl = res.data.activityTitleImgUrl;
+            if (titleImgUrl) {
+              titleImageUrl.value = titleImgUrl;
+              activityModel.value.activityTitleImg = res.data.activityTitleImgUrl;
+              activityModel.value.activityTitleImage = titleImgUrl;
+              const titleFile = handleImageParam(titleImgUrl);
+              titleFileList.value = [titleFile];
+            }
+          }
+          // 如果有详情图片,添加到文件列表
+          if (res.data.activityDetailImgUrl) {
+            const detailImgUrl = res.data.activityDetailImgUrl;
+            if (detailImgUrl) {
+              detailImageUrl.value = detailImgUrl;
+              activityModel.value.activityDetailImg = res.data.activityDetailImgUrl;
+              activityModel.value.activityDetailImage = detailImgUrl;
+              const detailFile = handleImageParam(detailImgUrl);
+              detailFileList.value = [detailFile];
+            }
+          }
+        }
       }
     } catch (error) {
       console.error("加载活动详情失败:", error);
@@ -873,146 +924,19 @@ const goBack = () => {
 };
 
 /**
- * AI生成活动宣传图
- */
-const handleAIGenerate = async () => {
-  return;
-  if (!aiPromptText.value.trim()) {
-    ElMessage.warning("请输入活动宣传图描述");
-    return;
-  }
-
-  aiGenerating.value = true;
-
-  try {
-    const res: any = await generatePromotionImage({
-      text: aiPromptText.value.trim()
-    });
-
-    if (res && res.code === 200 && res.data) {
-      const { banner_image, vertical_image } = res.data;
-
-      // 处理横版图片(活动标题图)
-      if (banner_image && banner_image.image_base64) {
-        const bannerFile = await base64ToFile(banner_image.image_base64, `banner_${Date.now()}.png`, "image/png");
-        const bannerUploadFile: UploadFile = {
-          uid: Date.now(),
-          name: bannerFile.name,
-          status: "success",
-          percentage: 100,
-          url: `data:image/png;base64,${banner_image.image_base64}`,
-          raw: bannerFile
-        } as UploadFile;
-
-        titleFileList.value = [bannerUploadFile];
-        titleImageUrl.value = `data:image/png;base64,${banner_image.image_base64}`;
-        activityModel.value.activityTitleImg = { url: titleImageUrl.value };
-        activityModel.value.activityTitleImage = titleImageUrl.value;
-      }
-
-      // 处理竖版图片(活动详情图)
-      if (vertical_image && vertical_image.image_base64) {
-        const verticalFile = await base64ToFile(vertical_image.image_base64, `vertical_${Date.now()}.png`, "image/png");
-        const verticalUploadFile: UploadFile = {
-          uid: Date.now() + 1,
-          name: verticalFile.name,
-          status: "success",
-          percentage: 100,
-          url: `data:image/png;base64,${vertical_image.image_base64}`,
-          raw: verticalFile
-        } as UploadFile;
-
-        detailFileList.value = [verticalUploadFile];
-        detailImageUrl.value = `data:image/png;base64,${vertical_image.image_base64}`;
-        activityModel.value.activityDetailImg = { url: detailImageUrl.value };
-        activityModel.value.activityDetailImage = detailImageUrl.value;
-      }
-
-      aiGenerated.value = true;
-
-      // 触发表单验证
-      nextTick(() => {
-        ruleFormRef.value?.validateField("activityTitleImage");
-        ruleFormRef.value?.validateField("activityDetailImage");
-      });
-
-      ElMessage.success("图片生成成功");
-    } else {
-      ElMessage.error(res?.message || "生成失败");
-    }
-  } catch (error: any) {
-    console.error("AI生成失败:", error);
-    ElMessage.error(error?.message || "生成失败,请稍后重试");
-  } finally {
-    aiGenerating.value = false;
-  }
-};
-
-/**
- * Base64转File对象
- */
-const base64ToFile = async (base64: string, filename: string, mimeType: string): Promise<File> => {
-  // 如果base64包含data URL前缀,去除它
-  const base64Data = base64.includes(",") ? base64.split(",")[1] : base64;
-
-  // 将base64转换为二进制数据
-  const byteCharacters = atob(base64Data);
-  const byteNumbers = new Array(byteCharacters.length);
-  for (let i = 0; i < byteCharacters.length; i++) {
-    byteNumbers[i] = byteCharacters.charCodeAt(i);
-  }
-  const byteArray = new Uint8Array(byteNumbers);
-  const blob = new Blob([byteArray], { type: mimeType });
-
-  // 创建File对象
-  return new File([blob], filename, { type: mimeType });
-};
-
-/**
  * 提交表单
  */
 const handleSubmit = async () => {
   if (!ruleFormRef.value) return;
 
-  // 如果有未上传的图片,阻止提交
-  if (hasUnuploadedImages.value) {
+  // 如果选择正常用户方式且有未上传的图片,阻止提交
+  if (activityModel.value.uploadImgType === 1 && hasUnuploadedImages.value) {
     ElMessage.warning("请等待图片上传完成后再提交");
     return;
   }
 
   await ruleFormRef.value.validate(async valid => {
     if (valid) {
-      // 如果是AI生成的图片,需要先上传
-      if (aiGenerated.value) {
-        try {
-          // 上传标题图
-          if (titleFileList.value.length > 0 && titleFileList.value[0].raw) {
-            const titleFormData = new FormData();
-            titleFormData.append("file", titleFileList.value[0].raw);
-            titleFormData.append("user", "text");
-            const titleResult: any = await uploadContractImage(titleFormData);
-            if (titleResult?.code === 200 && titleResult.data) {
-              titleImageUrl.value = titleResult.data[0];
-            }
-          }
-
-          // 上传详情图
-          if (detailFileList.value.length > 0 && detailFileList.value[0].raw) {
-            const detailFormData = new FormData();
-            detailFormData.append("file", detailFileList.value[0].raw);
-            detailFormData.append("user", "text");
-            const detailResult: any = await uploadContractImage(detailFormData);
-            if (detailResult?.code === 200 && detailResult.data) {
-              detailImageUrl.value = detailResult.data[0];
-            }
-          }
-        } catch (error) {
-          console.error("上传AI生成图片失败:", error);
-          ElMessage.error("上传图片失败");
-          return;
-        }
-      }
-
       const [startTime, endTime] = activityModel.value.activityTimeRange || [];
       const params: any = {
         activityName: activityModel.value.activityName,
@@ -1022,20 +946,39 @@ const handleSubmit = async () => {
         activityRule: activityModel.value.activityRule.join(","),
         couponId: activityModel.value.couponId,
         couponQuantity: activityModel.value.couponQuantity,
-        activityTitleImg: {
+        uploadImgType: activityModel.value.uploadImgType,
+        storeId: localGet("createdId"),
+        groupType: localGet("businessSection"),
+        status: 1 // 1-待审核
+      };
+
+      // 根据上传图片方式设置不同的参数
+      if (activityModel.value.uploadImgType === 1) {
+        // 正常用户:上传图片
+        params.activityTitleImg = {
           imgUrl: titleImageUrl.value,
           imgSort: 0,
           storeId: localGet("createdId")
-        },
-        activityDetailImg: {
+        };
+        params.activityDetailImg = {
           imgUrl: detailImageUrl.value,
           imgSort: 0,
           storeId: localGet("createdId")
-        },
-        storeId: localGet("createdId"),
-        groupType: localGet("businessSection"),
-        status: 1 // 1-待审核
-      };
+        };
+      } else if (activityModel.value.uploadImgType === 2) {
+        // 使用描述:提交描述文本,但依然保留图片字段,imgUrl为空
+        params.imgDescribe = activityModel.value.imgDescribe;
+        params.activityTitleImg = {
+          imgUrl: "",
+          imgSort: 0,
+          storeId: localGet("createdId")
+        };
+        params.activityDetailImg = {
+          imgUrl: "",
+          imgSort: 0,
+          storeId: localGet("createdId")
+        };
+      }
 
       try {
         let res: any;
@@ -1107,22 +1050,6 @@ const handleSubmit = async () => {
   }
 }
 
-/* AI生成包装器 */
-.ai-generate-wrapper {
-  display: flex;
-  gap: 12px;
-  align-items: flex-start;
-  width: 100%;
-  :deep(.el-textarea) {
-    flex: 1;
-  }
-  .generate-btn {
-    flex-shrink: 0;
-    align-self: flex-start;
-    margin-top: 2px;
-  }
-}
-
 /* 上传项容器 */
 .upload-item-wrapper {
   display: flex;
@@ -1138,13 +1065,6 @@ const handleSubmit = async () => {
   color: #909399;
 }
 
-/* 隐藏上传触发器 */
-:deep(.upload-hidden) {
-  .el-upload--picture-card {
-    display: none !important;
-  }
-}
-
 /* 规则表格容器 */
 .rule-table-container {
   margin-top: 20px;