Bladeren bron

feat(voucher): 实现代金券管理功能优化

- 更新代金券API接口地址和方法名
- 修改代金券表单字段和验证逻辑
- 调整代金券详情页面显示字段
- 优化代金券库存和限购数量验证
- 改进代金券有效期和不可用日期处理
- 完善代金券提交参数组装逻辑
- 修复代金券编辑模式数据加载问题
- 调整代金券列表页面按钮显示条件
- 更新代金券相关枚举值类型为字符串
- 优化代金券表单字段联动验证
congxuesong 1 maand geleden
bovenliggende
commit
31f3998786

+ 3 - 3
src/api/modules/voucherManagement.ts

@@ -14,11 +14,11 @@ export const updateStatus = params => {
 export const updateNum = params => {
   return http.get(PORT_NONE + `/PcGroupBuy/updateNum11`, params);
 };
-export const saveDraft = params => {
-  return http.post(PORT_NONE + `/PcGroupBuy/saveDraft`, params);
+export const addOrUpdateCoupon = params => {
+  return http.post(PORT_NONE + `/couponPlatform/addOrUpdateCoupon`, params);
 };
 export const getVoucherDetail = params => {
-  return http.get(PORT_NONE + `/store/info/getDetail`, params);
+  return http.get(PORT_NONE + `/couponPlatform/getCouponDetail`, params);
 };
 export const getMenuByStoreId = params => {
   return http.get(PORT_NONE + `/menuPlatform/getMenuByStoreId`, params);

+ 2 - 2
src/views/groupPackageManagement/newGroup.vue

@@ -1221,7 +1221,7 @@ const uploadSingleFile = async (file: UploadFile) => {
   }
 
   const formData = new FormData();
-  const storeId = Number(localGet("createdId") || 104);
+  const storeId = Number(localGet("createdId"));
   const rawFile = file.raw as File;
   const sortValue = generateImgSort();
 
@@ -1877,7 +1877,7 @@ const handleSubmit = async (type?) => {
   };
   paramsObj.lifeGroupBuyMain.status = type ? "0" : "1";
   paramsObj.lifeGroupBuyMain.groupType = localGet("businessSection") || "1";
-  paramsObj.lifeGroupBuyMain.storeId = localGet("createdId") || "104";
+  paramsObj.lifeGroupBuyMain.storeId = localGet("createdId");
   if (id.value) {
     paramsObj.lifeGroupBuyMain.id = id.value;
   }

+ 19 - 25
src/views/voucherManagement/index.vue

@@ -44,18 +44,11 @@
           type="primary"
           link
           @click="toDetail(scope.row)"
-          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看详情)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看详情) && scope.row.dataType != 1"
         >
           查看详情
         </el-button>
-        <el-button
-          link
-          type="primary"
-          @click="editRow(scope.row)"
-          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.编辑)"
-        >
-          编辑
-        </el-button>
+        <el-button link type="primary" @click="editRow(scope.row)" v-if="scope.row.dataType == 1"> 编辑 </el-button>
         <el-button
           link
           type="primary"
@@ -69,10 +62,10 @@
     <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
       <el-form ref="ruleFormRef" :model="formInventory" :rules="rules" @submit.prevent>
         <el-form-item label="套餐名">
-          {{ formInventory.packageName }}
+          {{ formInventory.name }}
         </el-form-item>
         <el-form-item label="剩余库存">
-          {{ formInventory.inventoryNum }}
+          {{ formInventory.singleQty }}
         </el-form-item>
         <el-form-item label="修改库存" prop="newInventory">
           <el-input v-model="formInventory.newInventory" placeholder="请输入" />
@@ -88,7 +81,7 @@
   </div>
 </template>
 
-<script setup lang="tsx" name="groupPackageManagement">
+<script setup lang="tsx" name="voucherManagement">
 import { computed, onActivated, onMounted, reactive, ref, watch } from "vue";
 import { useRouter } from "vue-router";
 import type { FormInstance, FormRules } from "element-plus";
@@ -104,8 +97,8 @@ const router = useRouter();
 const dialogFormVisible = ref(false);
 const formInventory: any = ref({
   id: "",
-  packageName: "",
-  inventoryNum: "",
+  name: "",
+  singleQty: "",
   newInventory: ""
 });
 const rowData = ref<any>();
@@ -130,11 +123,6 @@ const statusEnum = [
   { value: "1", label: "审核通过" },
   { value: "2", label: "审核驳回" }
 ];
-const statusEnum1 = [
-  { value: [1], label: "待审核" },
-  { value: [2, 4, 5, 6, 7], label: "审核通过" },
-  { value: 3, label: "审核驳回" }
-];
 // ProTable 实例(需要在使用它的地方之前定义)
 const proTable = ref<ProTableInstance>();
 
@@ -267,17 +255,23 @@ watch(
   currentAuditStatus,
   newStatus => {
     if (!newStatus || newStatus === "-2") {
-      // 审核状态为空时,确保 activeName 为草稿
       activeName.value = "";
     }
   },
   { immediate: true }
 );
+
+const dataType = computed(() => {
+  if (!activeName.value) return 2;
+  return activeName.value === "0" ? 1 : 0;
+});
+
 // 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
 const initParam = reactive({
   storeId: localGet("createdId") || "",
   groupType: localGet("businessSection") || "1",
-  status: activeName
+  status: activeName,
+  dataType: dataType
 });
 const type = ref(false);
 // 页面加载时触发查询
@@ -343,8 +337,8 @@ const changeTypes = async (row: any, status: string) => {
 };
 const changeInventory = (row: any) => {
   formInventory.value.id = row.id;
-  formInventory.value.packageName = row.packageName;
-  formInventory.value.inventoryNum = row.inventoryNum;
+  formInventory.value.name = row.name;
+  formInventory.value.singleQty = row.singleQty;
   formInventory.value.newInventory = "";
   dialogFormVisible.value = true;
 };
@@ -367,8 +361,8 @@ const closeDialog = () => {
   dialogFormVisible.value = false;
   formInventory.value = {
     id: "",
-    packageName: "",
-    inventoryNum: "",
+    name: "",
+    singleQty: "",
     newInventory: ""
   };
 };

+ 189 - 65
src/views/voucherManagement/newVoucher.vue

@@ -68,10 +68,10 @@
                 </el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="" prop="validityDays" v-if="voucherModel.expirationType == 0">
+            <el-form-item label="" prop="expirationDate" v-if="voucherModel.expirationType == 0">
               <div class="expiration-date-container">
                 <span class="expiration-label">用户购买</span>
-                <el-input-number v-model="voucherModel.validityDays" placeholder="请输入" :min="0" class="expiration-input" />
+                <el-input-number v-model="voucherModel.expirationDate" placeholder="请输入" :min="0" class="expiration-input" />
                 <span class="expiration-label">天内有效</span>
               </div>
             </el-form-item>
@@ -79,7 +79,7 @@
               <el-date-picker
                 v-model="voucherModel.validityPeriod"
                 type="daterange"
-                value-format="YYYY-MM-DD"
+                value-format="x"
                 range-separator="-"
                 start-placeholder="开始时间"
                 end-placeholder="结束时间"
@@ -236,7 +236,7 @@ import { ElMessage } from "element-plus";
 import { Plus, Delete } from "@element-plus/icons-vue";
 import { useRoute, useRouter } from "vue-router";
 import type { FormInstance } from "element-plus";
-import { getVoucherDetail, getHolidayList } from "@/api/modules/voucherManagement";
+import { getVoucherDetail, getHolidayList, addOrUpdateCoupon } from "@/api/modules/voucherManagement";
 import {
   validatePositiveNumber,
   validatePositiveInteger,
@@ -248,6 +248,7 @@ import {
   validatePriceFormat,
   validatePriceComparison
 } from "@/utils/eleValidate";
+import { localGet } from "@/utils";
 
 // ==================== 响应式数据定义 ====================
 
@@ -303,28 +304,60 @@ const rules = reactive({
   startDate: [
     { required: true, message: "请选择开始售卖时间" },
     {
-      validator: validateDateRange(
-        () => voucherModel.value.startDate,
-        () => voucherModel.value.endDate,
-        "开始售卖时间不能早于当前时间",
-        "结束售卖时间不能早于当前时间",
-        "开始售卖时间必须早于结束售卖时间",
-        true
-      ),
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value) {
+          callback();
+          return;
+        }
+        const selectedDate = new Date(value);
+        const today = new Date();
+        today.setHours(0, 0, 0, 0);
+        // 验证不能早于今天
+        if (selectedDate < today) {
+          callback(new Error("开始售卖时间不能早于当前时间"));
+          return;
+        }
+        // 验证开始时间必须早于结束时间
+        const endDate = voucherModel.value.endDate;
+        if (endDate) {
+          const end = new Date(endDate);
+          if (selectedDate >= end) {
+            callback(new Error("开始售卖时间必须早于结束售卖时间"));
+            return;
+          }
+        }
+        callback();
+      },
       trigger: "change"
     }
   ],
   endDate: [
     { required: true, message: "请选择结束售卖时间" },
     {
-      validator: validateDateRange(
-        () => voucherModel.value.startDate,
-        () => voucherModel.value.endDate,
-        "开始售卖时间不能早于当前时间",
-        "结束售卖时间不能早于当前时间",
-        "开始售卖时间必须早于结束售卖时间",
-        true
-      ),
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value) {
+          callback();
+          return;
+        }
+        const selectedDate = new Date(value);
+        const today = new Date();
+        today.setHours(0, 0, 0, 0);
+        // 验证不能早于今天
+        if (selectedDate < today) {
+          callback(new Error("结束售卖时间不能早于当前时间"));
+          return;
+        }
+        // 验证结束时间必须晚于开始时间
+        const startDate = voucherModel.value.startDate;
+        if (startDate) {
+          const start = new Date(startDate);
+          if (selectedDate <= start) {
+            callback(new Error("开始售卖时间必须早于结束售卖时间"));
+            return;
+          }
+        }
+        callback();
+      },
       trigger: "change"
     }
   ],
@@ -342,7 +375,7 @@ const rules = reactive({
     }
   ],
   expirationType: [{ required: true, message: "请选择有效期类型" }],
-  validityDays: [
+  expirationDate: [
     {
       required: true,
       validator: (rule: any, value: any, callback: any) => {
@@ -426,12 +459,70 @@ const rules = reactive({
     {
       validator: validatePositiveInteger("单日可用数量必须为正整数", { required: false }),
       trigger: "blur"
+    },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        // 如果值为空,不进行验证
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const stock = voucherModel.value.singleQty;
+        // 如果库存为空,不进行验证(由库存的验证规则处理)
+        if (!stock || stock.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const useNum = Number(value);
+        const stockNum = Number(stock);
+        // 如果转换失败,不进行验证(由其他验证规则处理)
+        if (isNaN(useNum) || isNaN(stockNum)) {
+          callback();
+          return;
+        }
+        // 验证单日可用数量不能多于库存
+        if (useNum > stockNum) {
+          callback(new Error("单日可用数量不能多于库存"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
     }
   ],
   purchaseLimitCode: [
     {
       validator: validatePositiveInteger("限购数量必须为正整数", { required: false }),
       trigger: "blur"
+    },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        // 如果值为空,不进行验证
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const stock = voucherModel.value.singleQty;
+        // 如果库存为空,不进行验证(由库存的验证规则处理)
+        if (!stock || stock.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const limitNum = Number(value);
+        const stockNum = Number(stock);
+        // 如果转换失败,不进行验证(由其他验证规则处理)
+        if (isNaN(limitNum) || isNaN(stockNum)) {
+          callback();
+          return;
+        }
+        // 验证限购数量不能多于库存
+        if (limitNum > stockNum) {
+          callback(new Error("限购数量不能多于库存"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
     }
   ],
   applyDesc: [
@@ -470,13 +561,13 @@ const voucherModel = ref<any>({
   // 使用时间(虚拟字段,用于表单验证)
   usageTime: null,
   // 有效期类型:0-指定天数,1-指定时间段内可用
-  expirationType: 0,
+  expirationType: "0",
   // 有效期天数(当expirationType为0时使用)
-  validityDays: 0,
+  expirationDate: 0,
   // 有效期时间段(当expirationType为1时使用)
   validityPeriod: [],
   // 不可用日期类型:0-全部日期可用,1-限制日期
-  unusedType: 0,
+  unusedType: "0",
   // 限制日期 - 星期选择(数组,存储选中的星期值)
   unavailableWeekdays: [],
   // 限制日期 - 节日选择(数组,存储选中的节日值)
@@ -490,7 +581,7 @@ const voucherModel = ref<any>({
   // 限购数量
   purchaseLimitCode: "",
   // 适用范围类型:0-全场通用,1-部分不可用
-  applyType: 1,
+  applyType: "1",
   // 适用范围(当applyType为1时使用)
   applyDesc: "",
   // 补充说明
@@ -509,21 +600,21 @@ const hourOptions = ref(
 
 // 有效期类型列表
 const validityPeriodList = ref([
-  { value: 0, label: "指定天数" },
-  { value: 1, label: "指定时间段内可用" }
+  { value: "0", label: "指定天数" },
+  { value: "1", label: "指定时间段内可用" }
 ]);
 
 // 不可用日期类型列表
 const unavailableDateTypeList = ref([
-  { value: 0, label: "全部日期可用" },
-  { value: 1, label: "限制日期" },
-  { value: 2, label: "自定义不可用日期" }
+  { value: "0", label: "全部日期可用" },
+  { value: "1", label: "限制日期" }
+  // { value: '2', label: "自定义不可用日期" }
 ]);
 
 // 适用范围类型列表
 const applicableScopeList = ref([
-  { value: 1, label: "全场通用" },
-  { value: 2, label: "部分不可用" }
+  { value: "1", label: "全场通用" },
+  { value: "2", label: "部分不可用" }
 ]);
 
 // 星期选项列表
@@ -603,6 +694,26 @@ watch(
 );
 
 /**
+ * 监听库存变化
+ * 当库存改变时,重新验证单日可用数量和限购数量
+ */
+watch(
+  () => voucherModel.value.singleQty,
+  () => {
+    if (voucherModel.value.singleCanUse) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("singleCanUse");
+      });
+    }
+    if (voucherModel.value.purchaseLimitCode) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("purchaseLimitCode");
+      });
+    }
+  }
+);
+
+/**
  * 监听使用开始时间变化
  * 更新虚拟字段以支持表单验证
  */
@@ -674,21 +785,30 @@ onMounted(async () => {
 
   // 编辑模式下加载数据
   if (type.value != "add") {
-    // TODO: 加载代金券详情数据
-    // let res: any = await getVoucherDetail({ id: id.value });
-    // voucherModel.value = res.data;
-    // 确保星期和节日字段存在
-    // if (voucherModel.value.unusedType == 1) {
-    //   const listVal = voucherModel.value.unusedDate ? voucherModel.value.unusedDate.split(";") : [];
-    //   voucherModel.value.unavailableWeekdays = listVal[0] ? listVal[0].split(",").filter((item: string) => item) : [];
-    //   voucherModel.value.unavailableHolidays = listVal[1] ? listVal[1].split(",").filter((item: string) => item) : [];
-    // }
-    // 确保自定义不可用日期字段存在
-    // if (voucherModel.value.unusedType === 2) {
-    //   if (!voucherModel.value.disableDateList || voucherModel.value.disableDateList.length === 0) {
-    //     voucherModel.value.disableDateList = [null];
-    //   }
-    // }
+    let res: any = await getVoucherDetail({ id: id.value });
+    voucherModel.value = { ...voucherModel.value, ...res.data };
+    // 处理有效期时间段:将时间戳字符串转换为数字数组
+    if (voucherModel.value.validityPeriod && voucherModel.value.expirationType == 1) {
+      const periodArray = voucherModel.value.validityPeriod.split(",");
+      voucherModel.value.validityPeriod = periodArray
+        .map((item: string) => Number(item.trim()))
+        .filter((item: number) => !isNaN(item));
+    } else {
+      voucherModel.value.validityPeriod = [];
+    }
+    // 确保星期和节日字段存在;
+    if (voucherModel.value.unusedType == 1) {
+      const listVal = voucherModel.value.unusedDate ? voucherModel.value.unusedDate.split(";") : [];
+      voucherModel.value.unavailableWeekdays = listVal[0] ? listVal[0].split(",").filter((item: string) => item) : [];
+      voucherModel.value.unavailableHolidays = listVal[1] ? listVal[1].split(",").filter((item: string) => item) : [];
+    }
+    // 确保自定义不可用日期字段存在;
+    if (voucherModel.value.unusedType === 2) {
+      if (!voucherModel.value.disableDateList || voucherModel.value.disableDateList.length === 0) {
+        voucherModel.value.disableDateList = [null];
+      }
+    }
+    console.log(voucherModel.value);
   }
 });
 
@@ -776,12 +896,17 @@ const ruleFormRef = ref<FormInstance>(); // 表单引用
  * 提交数据(新增/编辑)
  * 验证表单,通过后调用相应的API接口
  */
-const handleSubmit = (submitType?: string) => {
+const handleSubmit = async (submitType?: string) => {
   // 组装提交参数
   let params: any = { ...voucherModel.value };
-  // 处理有效期
-  if (params.expirationType == 1) {
+  params.storeId = localGet("createdId");
+  params.status = 1;
+  // 处理有效期:只有当expirationType为1(指定时间段内可用)时才处理validityPeriod
+  if (params.expirationType == 1 && params.validityPeriod && Array.isArray(params.validityPeriod)) {
     params.validityPeriod = params.validityPeriod.join(",");
+  } else if (params.expirationType == 0) {
+    // 指定天数模式,不需要validityPeriod字段
+    params.validityPeriod = "";
   }
   // 处理不可用日期
   if (params.unusedType == 1) {
@@ -795,31 +920,30 @@ const handleSubmit = (submitType?: string) => {
         .join(";");
     }
   }
-
+  params.dataType = submitType ? 1 : 0;
   delete params.unavailableWeekdays;
   delete params.unavailableHolidays;
   delete params.disableDateList;
   console.log("提交参数:", params);
   if (submitType) {
+    if (!voucherModel.value.name) {
+      ElMessage.warning("请填写代金券名称");
+      return;
+    }
+    let res: any = await addOrUpdateCoupon(params);
+    if (res && res.code == 200) {
+      ElMessage.success("保存成功");
+      goBack();
+    }
     return;
   }
-
   // 验证表单
   ruleFormRef.value!.validate(async (valid: boolean) => {
     if (!valid) return;
-
-    // TODO: 调用API保存数据
-    // if (submitType === 'draft') {
-    //   await saveVoucherDraft(params);
-    // } else {
-    //   await saveVoucher(params);
-    // }
-
-    if (submitType === "draft") {
-      ElMessage.success("草稿保存成功");
-    } else {
-      ElMessage.success("代金券创建成功");
-      router.go(-1);
+    let res: any = await addOrUpdateCoupon(params);
+    if (res && res.code == 200) {
+      ElMessage.success("保存成功");
+      goBack();
     }
   });
 };