congxuesong 1 ماه پیش
والد
کامیت
65460d5ab1

+ 19 - 31
src/assets/json/authMenuList.json

@@ -64,7 +64,7 @@
     {
       "path": "/couponManagement",
       "name": "couponManagement",
-      "redirect": "/couponManagement/index",
+      "component": "/couponManagement/index",
       "meta": {
         "icon": "Ticket",
         "title": "优惠券管理",
@@ -76,21 +76,22 @@
       },
       "children": [
         {
-          "path": "/couponManagement/index",
-          "name": "couponManagementIndex",
-          "component": "/couponManagement/index",
+          "path": "/couponManagement/newCoupon",
+          "name": "couponManagementNewCoupon",
+          "component": "/couponManagement/newCoupon",
           "meta": {
-            "icon": "Ticket",
-            "title": "优惠券管理",
+            "icon": "Menu",
+            "title": "新建优惠券",
+            "activeMenu": "/couponManagement",
             "isLink": "",
-            "isHide": false,
+            "isHide": true,
             "isFull": false,
             "isAffix": false,
             "isKeepAlive": false
           }
         },
         {
-          "path": "/couponManagementDetail",
+          "path": "/couponManagement/detail",
           "name": "couponManagementDetail",
           "component": "/couponManagement/detail",
           "meta": {
@@ -109,7 +110,7 @@
     {
       "path": "/voucherManagement",
       "name": "voucherManagement",
-      "redirect": "/voucherManagement/index",
+      "component": "/voucherManagement/index",
       "meta": {
         "icon": "Money",
         "title": "代金券管理",
@@ -121,21 +122,22 @@
       },
       "children": [
         {
-          "path": "/voucherManagement/index",
-          "name": "voucherManagementIndex",
-          "component": "/voucherManagement/index",
+          "path": "/voucherManagement/newVoucher",
+          "name": "voucherManagementNewVoucher",
+          "component": "/voucherManagement/newVoucher",
           "meta": {
-            "icon": "Money",
-            "title": "代金券管理",
+            "icon": "Menu",
+            "title": "新建代金券",
+            "activeMenu": "/voucherManagement",
             "isLink": "",
-            "isHide": false,
+            "isHide": true,
             "isFull": false,
             "isAffix": false,
             "isKeepAlive": false
           }
         },
         {
-          "path": "/voucherManagementDetail",
+          "path": "/voucherManagement/detail",
           "name": "voucherManagementDetail",
           "component": "/voucherManagement/detail",
           "meta": {
@@ -154,7 +156,7 @@
     {
       "path": "/orderManagement",
       "name": "orderManagement",
-      "redirect": "/orderManagement/index",
+      "component": "/orderManagement/index",
       "meta": {
         "icon": "Document",
         "title": "订单管理",
@@ -166,20 +168,6 @@
       },
       "children": [
         {
-          "path": "/orderManagement/index",
-          "name": "orderManagementIndex",
-          "component": "/orderManagement/index",
-          "meta": {
-            "icon": "Document",
-            "title": "订单管理",
-            "isLink": "",
-            "isHide": false,
-            "isFull": false,
-            "isAffix": false,
-            "isKeepAlive": false
-          }
-        },
-        {
           "path": "/orderManagementDetail",
           "name": "orderManagementDetail",
           "component": "/orderManagement/detail",

+ 178 - 99
src/views/couponManagement/index.vue

@@ -3,78 +3,179 @@
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <el-button type="primary" :icon="Download" @click="exportInfoExcel(scope)"> 导出 </el-button>
-      </template>
-      <template #expand="scope">
-        {{ scope.row }}
+        <div class="table-header-btn">
+          <el-button :icon="Plus" class="button" type="primary" @click="newGroupBuying"> 新建优惠券 </el-button>
+          <el-tabs v-if="showTabs" v-model="activeName" class="tabs" @tab-click="handleClick">
+            <el-tab-pane v-for="tab in filteredTabOptions" :key="tab.name" :label="tab.label" :name="tab.name" />
+          </el-tabs>
+        </div>
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
         <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
         <template v-if="scope.row.status === '0'">
-          <el-button type="primary" link @click="changeTypes(scope.row, 'pass')"> 审核通过 </el-button>
-          <el-button type="primary" link @click="changeTypes(scope.row, '')"> 审核拒绝 </el-button>
+          <el-button link type="primary" @click="changeTypes(scope.row, 'on')"> 上架 </el-button>
+          <el-button link type="primary" @click="changeTypes(scope.row, 'off')"> 下架 </el-button>
+          <el-button link type="primary" @click="changeInventory(scope.row)"> 修改库存 </el-button>
         </template>
         <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
+        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
+        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
-
-    <el-dialog v-model="dialogFormVisible" title="审核拒绝" width="500">
-      <el-form :model="form">
-        <el-form-item label="" label-width="0">
-          <el-input v-model="form.comment" autocomplete="off" type="textarea" maxlength="200" />
+    <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
+      <el-form ref="ruleFormRef" :model="formInventory" :rules="rules" @submit.prevent>
+        <el-form-item label="套餐名">
+          {{ formInventory.packageName }}
+        </el-form-item>
+        <el-form-item label="剩余库存">
+          {{ formInventory.remainingInventory }}
+        </el-form-item>
+        <el-form-item label="修改库存" prop="newInventory">
+          <el-input v-model="formInventory.newInventory" placeholder="请输入" />
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="closeDialog"> 取消 </el-button>
-          <el-button type="primary" @click="handleSubmit"> 驳回 </el-button>
+          <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
         </div>
       </template>
     </el-dialog>
   </div>
 </template>
 
-<script setup lang="tsx" name="couponManagement">
-import { ref, reactive, onMounted, onActivated } from "vue";
+<script setup lang="tsx" name="groupPackageManagement">
+import { computed, onActivated, onMounted, reactive, ref, watch } from "vue";
 import { useRouter } from "vue-router";
+import type { FormInstance, FormRules } from "element-plus";
 import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
-import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { Download } from "@element-plus/icons-vue";
-import { audit, exportExcelStaffConfig, getStaffConfigList } from "@/api/modules/staffConfig";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { Plus } from "@element-plus/icons-vue";
+import { audit, getStaffConfigList } from "@/api/modules/staffConfig";
+import { getThaliList } from "@/api/modules/groupPackageManagement";
 
 const router = useRouter();
 const dialogFormVisible = ref(false);
-const form = reactive({
-  comment: ""
+const formInventory: any = ref({
+  id: "",
+  packageName: "",
+  remainingInventory: "",
+  newInventory: ""
 });
-
 const rowData = ref<any>();
+const activeName = ref("");
 
+const ruleFormRef = ref<FormInstance>();
+const rules = reactive<FormRules<RuleForm>>({
+  newInventory: [
+    { required: true, message: "请输入库存数量", trigger: "blur" },
+    {
+      pattern: /^(0|[1-9][0-9]*)$/,
+      message: "请输入整数,不允许输入小数,负数",
+      trigger: "blur"
+    }
+  ]
+});
 const statusEnum = [
-  { value: "0", label: "待审核" },
-  { value: "1", label: "审核通过" },
-  { value: "2", label: "审核拒绝" }
+  { value: "-1", label: "待审核" },
+  { value: "-2", label: "审核通过" },
+  { value: "0", label: "审核驳回" }
 ];
-// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
-const initParam = reactive({});
 
-// 定义 filterValues
-const filterValues = reactive({});
+// ProTable 实例(需要在使用它的地方之前定义)
+const proTable = ref<ProTableInstance>();
 
-const getStatusObj = (statusValue: string) => {
-  const statusObj = statusEnum.find(item => item.value === statusValue);
-  if (statusObj) {
-    filterValues.status = statusObj;
-  } else {
-    filterValues.status = "";
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "index",
+    label: "序号",
+    width: 100,
+    render: (scope: any) => {
+      return scope.$index + (proTable.value!.pageable.pageNum - 1) * proTable.value!.pageable.pageSize + 1;
+    }
+  },
+  {
+    prop: "groupName",
+    label: "优惠券名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "groupName",
+    label: "已领"
+  },
+  {
+    prop: "inventoryNum",
+    label: "剩余库存"
+  },
+  {
+    prop: "goodsId",
+    label: "结束时间"
+  },
+  {
+    prop: "status",
+    label: "状态"
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 330 }
+]);
+
+// 在 script setup 中添加
+const allTabOptions = [
+  { label: "全部", name: "" },
+  { label: "草稿", name: "0" },
+  { label: "进行中", name: "5" },
+  { label: "待审核", name: "1" },
+  { label: "审核拒绝", name: "3" },
+  { label: "已售罄", name: "4" },
+  { label: "已结束", name: "7" }
+];
+
+// 获取当前审核状态
+const currentAuditStatus = computed(() => {
+  if (!proTable.value?.searchParam) return "";
+  return proTable.value.searchParam.reviewType || "";
+});
+
+// 控制 el-tabs 是否显示:当审核状态为空或审核通过时显示
+const showTabs = computed(() => {
+  const status = currentAuditStatus.value;
+  return !status || status === "-2";
+});
+
+// 根据审核状态过滤 tabOptions:如果审核状态为空,只显示草稿;审核通过时,显示除草稿外的所有标签页
+const filteredTabOptions = computed(() => {
+  const status = currentAuditStatus.value;
+  if (!status) {
+    // 审核状态为空时,只显示草稿
+    return allTabOptions;
+  } else if (status === "-2") {
+    // 审核通过时,显示除草稿外的所有标签页
+    return allTabOptions.filter(tab => tab.name !== "1");
   }
-  return statusObj;
-};
+  return [];
+});
 
-// ProTable 实例
-const proTable = ref<ProTableInstance>();
+// 监听审核状态变化
+watch(
+  currentAuditStatus,
+  newStatus => {
+    if (!newStatus || newStatus === "-2") {
+      // 审核状态为空时,确保 activeName 为草稿
+      activeName.value = "";
+    }
+  },
+  { immediate: true }
+);
+// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
+const initParam = reactive({
+  storeId: "104",
+  groupType: "1",
+  status: activeName
+});
 
 // 页面加载时触发查询
 onMounted(() => {
@@ -99,50 +200,38 @@ const dataCallback = (data: any) => {
 // 默认不做操作就直接在 ProTable 组件上绑定	:requestApi="getUserList"
 const getTableList = (params: any) => {
   let newParams = JSON.parse(JSON.stringify(params));
-  return getStaffConfigList(newParams);
+  return getThaliList(newParams);
 };
 
 // 跳转详情页
 const toDetail = row => {
-  router.push(`/store/couponManagementDetail?id=${row.id}`);
+  router.push(`/couponManagement/detail?id=${row.id}&type=edit`);
 };
-
-// 表格配置项
-const columns = reactive<ColumnProps<any>[]>([
-  { type: "index", fixed: "left", label: "序号", width: 130 },
-  { prop: "storeName", label: "所属店铺" },
-  { prop: "name", label: "名称" },
-  { prop: "description", label: "描述" },
-  {
-    prop: "status",
-    label: "状态",
-    render: scope => {
-      const statusObj = getStatusObj(scope.row.status);
-      return statusObj ? statusObj.label : "未知状态";
-    },
-    search: {
-      el: "select"
-    },
-    enum: statusEnum,
-    fieldNames: { label: "label", value: "value" }
-  },
-  { prop: "operation", label: "操作", fixed: "right", width: 330 }
-]);
-
+const editRow = row => {};
+const deleteRow = row => {};
+const newGroupBuying = () => {
+  router.push(`/couponManagement/newCoupon?id=104&type=add`);
+};
+const handleClick = () => {};
 const changeTypes = (row: any, status: string) => {
   rowData.value = row;
-  if (status === "pass") {
+  if (status === "on") {
     handleChangeStatus(row, "1");
   } else {
-    form.comment = "";
-    dialogFormVisible.value = true;
+    handleChangeStatus(row, "2");
   }
 };
-
+const changeInventory = (row: any) => {
+  formInventory.value.id = 1;
+  formInventory.value.packageName = 1;
+  formInventory.value.remainingInventory = 1;
+  formInventory.value.newInventory = "";
+  dialogFormVisible.value = true;
+};
 const handleChangeStatus = async (row: any, status: string) => {
   try {
     let res = await audit({ id: row.id, status: status, rejectionReason: form.comment });
-    if (res.code === 200) {
+    if (res.code == 200) {
       proTable.value?.getTableList();
       if (status === "2") closeDialog();
       ElMessage.success("审核成功");
@@ -152,43 +241,25 @@ const handleChangeStatus = async (row: any, status: string) => {
   }
 };
 
-// 导出信息
-const exportInfoExcel = async scope => {
-  let res;
-  // 获取原始状态值(可能为数字、字符串或 undefined)
-  const rawStatus = proTable.value.searchParam.status;
-  // 转换为字符串(处理 undefined/null 为 "" 或保留原始字符串)
-  const statusParam = rawStatus !== undefined && rawStatus !== null ? String(rawStatus) : undefined;
-  // 将筛选条件作为参数传递给后台
-  res = await exportExcelStaffConfig({ status: statusParam });
-  if (res.code === 200) {
-    if (!res.data) {
-      ElMessage.error("暂无可下载数据");
-      return;
-    }
-    const exportFile = document.createElement("a");
-    exportFile.style.display = "none";
-    exportFile.download = `优惠券管理.xlsx`;
-    exportFile.href = `${res.data}?timestamp=${new Date().getTime()}`; // 添加时间戳防止缓存
-    document.body.appendChild(exportFile);
-    exportFile.click();
-    document.body.removeChild(exportFile);
-    ElMessage.success("下载成功");
-  }
-};
-
 // 弹窗提交
-const handleSubmit = () => {
-  if (!form.comment) {
-    ElMessage.error("请输入审批意见");
-    return;
-  }
-  handleChangeStatus(rowData.value, "2");
+const handleSubmit = async () => {
+  if (!ruleFormRef.value) return;
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      ElMessage.success("修改成功");
+      dialogFormVisible.value = false;
+    }
+  });
 };
 // 关闭弹窗;
 const closeDialog = () => {
   dialogFormVisible.value = false;
-  form.comment = "";
+  formInventory.value = {
+    id: "",
+    packageName: "",
+    remainingInventory: "",
+    newInventory: ""
+  };
 };
 </script>
 
@@ -200,4 +271,12 @@ const closeDialog = () => {
   word-wrap: break-word; // 长单词内换行
   white-space: normal; // 允许自然换行
 }
+.table-header-btn {
+  .tabs {
+    margin-top: 10px;
+    :deep(.el-tabs__nav-wrap::after) {
+      height: 0;
+    }
+  }
+}
 </style>

+ 453 - 0
src/views/couponManagement/newCoupon.vue

@@ -0,0 +1,453 @@
+<template>
+  <!-- 优惠券管理 - 新增页面 -->
+  <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
+    <div class="header">
+      <el-button @click="goBack"> 返回 </el-button>
+      <h2 class="title">新建优惠券</h2>
+    </div>
+    <el-form :model="couponModel" ref="ruleFormRef" :rules="rules" label-width="200px" class="formBox">
+      <div class="content">
+        <!-- 左侧内容区域 -->
+        <div class="contentLeft">
+          <!-- 优惠券名称 -->
+          <el-form-item label="优惠券名称" prop="couponName">
+            <el-input maxlength="50" v-model="couponModel.couponName" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 面值 -->
+          <el-form-item label="面值(元)" prop="faceValue">
+            <el-input v-model="couponModel.faceValue" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 开始领取时间 -->
+          <el-form-item label="开始领取时间" prop="startCollectionTime">
+            <el-date-picker
+              v-model="couponModel.startCollectionTime"
+              format="YYYY/MM/DD"
+              value-format="YYYY-MM-DD"
+              placeholder="请选择开始领取时间"
+              :disabled-date="disabledStartDate"
+            />
+          </el-form-item>
+          <!-- 结束领取时间 -->
+          <el-form-item label="结束领取时间" prop="endCollectionTime">
+            <el-date-picker
+              v-model="couponModel.endCollectionTime"
+              format="YYYY/MM/DD"
+              value-format="YYYY-MM-DD"
+              placeholder="请选择结束领取时间"
+              :disabled-date="disabledEndDate"
+            />
+          </el-form-item>
+          <!-- 有效期 -->
+          <el-form-item label="有效期" prop="validityPeriod">
+            <el-input v-model="couponModel.validityPeriod" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 库存 -->
+          <el-form-item label="库存" prop="inventory">
+            <el-input v-model="couponModel.inventory" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 用户领取规则 -->
+          <el-form-item label="用户领取规则" prop="collectionRule">
+            <el-radio-group v-model="couponModel.collectionRule" class="ml-4">
+              <el-radio :value="1"> 每日一领 </el-radio>
+              <el-radio :value="2"> 每周一领 </el-radio>
+              <el-radio :value="3"> 每月一领 </el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <!-- 用户是否需要收藏店铺领取 -->
+          <el-form-item label="用户是否需要收藏店铺领取" prop="needFavoriteStore">
+            <el-radio-group v-model="couponModel.needFavoriteStore" class="ml-4">
+              <el-radio :value="1"> 是 </el-radio>
+              <el-radio :value="0"> 否 </el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </div>
+        <!-- 右侧内容区域 -->
+        <div class="contentRight">
+          <!-- 是否有低消 -->
+          <el-form-item label="是否有低消" prop="hasMinimumSpend">
+            <el-radio-group v-model="couponModel.hasMinimumSpend" class="ml-4">
+              <el-radio :value="1"> 是 </el-radio>
+              <el-radio :value="0"> 否 </el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <!-- 最低消费金额 -->
+          <el-form-item label="最低消费金额" prop="minimumSpendAmount" v-if="couponModel.hasMinimumSpend === 1">
+            <el-input v-model="couponModel.minimumSpendAmount" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 补充说明 -->
+          <el-form-item label="补充说明" prop="additionalNotes">
+            <el-input
+              maxlength="300"
+              v-model="couponModel.additionalNotes"
+              :rows="4"
+              type="textarea"
+              placeholder="请输入"
+              show-word-limit
+            />
+          </el-form-item>
+        </div>
+      </div>
+    </el-form>
+    <!-- 底部按钮区域 -->
+    <div class="button-container">
+      <el-button @click="() => handleSubmit('draft')"> 存草稿 </el-button>
+      <el-button type="primary" @click="() => handleSubmit()"> 新建优惠券 </el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="newCoupon">
+/**
+ * 优惠券管理 - 新增页面
+ * 功能:支持优惠券的新增操作
+ */
+import { ref, reactive, watch, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+import { useRouter } from "vue-router";
+import type { FormInstance } from "element-plus";
+
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+
+// ==================== 表单验证规则 ====================
+const rules = reactive({
+  couponName: [{ required: true, message: "请输入优惠券名称" }],
+  faceValue: [
+    { required: true, message: "请输入面值" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const num = Number(value);
+        if (isNaN(num) || num <= 0) {
+          callback(new Error("面值必须为正数"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  startCollectionTime: [
+    { required: true, message: "请选择开始领取时间" },
+    {
+      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;
+        }
+        if (couponModel.value.endCollectionTime) {
+          const endDate = new Date(couponModel.value.endCollectionTime);
+          if (selectedDate >= endDate) {
+            callback(new Error("开始领取时间必须早于结束领取时间"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  endCollectionTime: [
+    { required: true, message: "请选择结束领取时间" },
+    {
+      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;
+        }
+        if (couponModel.value.startCollectionTime) {
+          const startDate = new Date(couponModel.value.startCollectionTime);
+          if (selectedDate <= startDate) {
+            callback(new Error("结束领取时间必须晚于开始领取时间"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  validityPeriod: [
+    { required: true, message: "请输入有效期" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const num = Number(value);
+        if (isNaN(num) || num <= 0 || !Number.isInteger(num)) {
+          callback(new Error("有效期必须为正整数"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  inventory: [
+    { required: true, message: "请输入库存" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const num = Number(value);
+        if (isNaN(num) || num <= 0 || !Number.isInteger(num)) {
+          callback(new Error("库存必须为正整数"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  minimumSpendAmount: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (couponModel.value.hasMinimumSpend === 1) {
+          if (!value || value.toString().trim() === "") {
+            callback(new Error("请输入最低消费金额"));
+            return;
+          }
+          const num = Number(value);
+          if (isNaN(num) || num <= 0) {
+            callback(new Error("最低消费金额必须为正数"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ]
+});
+
+// ==================== 优惠券信息数据模型 ====================
+const couponModel = ref<any>({
+  // 优惠券名称
+  couponName: "",
+  // 面值(元)
+  faceValue: "",
+  // 开始领取时间
+  startCollectionTime: "",
+  // 结束领取时间
+  endCollectionTime: "",
+  // 有效期
+  validityPeriod: "",
+  // 库存
+  inventory: "",
+  // 用户领取规则:1-每日一领,2-每周一领,3-每月一领
+  collectionRule: 1,
+  // 用户是否需要收藏店铺领取:1-是,0-否
+  needFavoriteStore: 1,
+  // 是否有低消:1-是,0-否
+  hasMinimumSpend: 1,
+  // 最低消费金额
+  minimumSpendAmount: "",
+  // 补充说明
+  additionalNotes: ""
+});
+
+// ==================== 监听器 ====================
+
+/**
+ * 监听开始领取时间变化
+ * 当开始时间改变时,重新验证结束时间
+ */
+watch(
+  () => couponModel.value.startCollectionTime,
+  () => {
+    if (couponModel.value.endCollectionTime) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("endCollectionTime");
+      });
+    }
+  }
+);
+
+/**
+ * 监听结束领取时间变化
+ * 当结束时间改变时,重新验证开始时间
+ */
+watch(
+  () => couponModel.value.endCollectionTime,
+  () => {
+    if (couponModel.value.startCollectionTime) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("startCollectionTime");
+      });
+    }
+  }
+);
+
+/**
+ * 监听是否有低消变化
+ * 当选择"否"时,清空最低消费金额
+ */
+watch(
+  () => couponModel.value.hasMinimumSpend,
+  newVal => {
+    if (newVal === 0) {
+      couponModel.value.minimumSpendAmount = "";
+      nextTick(() => {
+        ruleFormRef.value?.clearValidate("minimumSpendAmount");
+      });
+    }
+  }
+);
+
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+// ==================== 表单引用 ====================
+const ruleFormRef = ref<FormInstance>(); // 表单引用
+
+/**
+ * 提交数据(新增)
+ * 验证表单,通过后调用相应的API接口
+ */
+const handleSubmit = (submitType?: string) => {
+  // 验证表单
+  ruleFormRef.value!.validate(async (valid: boolean) => {
+    if (!valid) return;
+
+    // 组装提交参数
+    let params: any = { ...couponModel.value };
+
+    console.log("提交参数:", params);
+
+    // TODO: 调用API保存数据
+    // if (submitType === 'draft') {
+    //   await saveCouponDraft(params);
+    // } else {
+    //   await saveCoupon(params);
+    // }
+
+    if (submitType === "draft") {
+      ElMessage.success("草稿保存成功");
+    } else {
+      ElMessage.success("优惠券创建成功");
+      router.go(-1);
+    }
+  });
+};
+
+// ==================== 工具函数 ====================
+
+/**
+ * 禁用开始领取时间的日期
+ * 不能选择早于当前时间的日期
+ */
+const disabledStartDate = (time: Date) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  return time.getTime() < today.getTime();
+};
+
+/**
+ * 禁用结束领取时间的日期
+ * 不能选择早于当前时间的日期,也不能选择早于或等于开始领取时间的日期
+ */
+const disabledEndDate = (time: Date) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  if (time.getTime() < today.getTime()) {
+    return true;
+  }
+  if (couponModel.value.startCollectionTime) {
+    const startDate = new Date(couponModel.value.startCollectionTime);
+    startDate.setHours(0, 0, 0, 0);
+    return time.getTime() <= startDate.getTime();
+  }
+  return false;
+};
+</script>
+
+<style scoped lang="scss">
+/* 页面容器 */
+.table-box {
+  display: flex;
+  flex-direction: column;
+  height: auto !important;
+  min-height: 100%;
+}
+
+/* 头部区域 */
+.header {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+  border-bottom: 1px solid #e4e7ed;
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 20px;
+  width: 98%;
+  margin: 20px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+  }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+  }
+}
+
+/* 表单容器 */
+.formBox {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  min-height: 100%;
+}
+
+/* 底部按钮容器 - 居中显示 */
+.button-container {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  justify-content: center;
+  padding: 20px 0;
+  margin-top: 20px;
+}
+</style>

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

@@ -120,7 +120,7 @@ const columns = reactive<ColumnProps<any>[]>([
     }
   },
   {
-    prop: "goodsId",
+    prop: "groupNo",
     label: "团购编号",
     isShow: false,
     search: {
@@ -266,7 +266,7 @@ const toDetail = row => {
 const editRow = row => {};
 const deleteRow = row => {};
 const newGroupBuying = () => {
-  router.push(`/groupPackageManagement/newGroup?id=361&type=add`);
+  router.push(`/groupPackageManagement/newGroup?id=104&type=add`);
 };
 const handleClick = () => {};
 const changeTypes = (row: any, status: string) => {

+ 1 - 0
src/views/groupPackageManagement/newGroup.vue

@@ -1896,6 +1896,7 @@ const handleImageParam = (list: any[], result: any[]) => {
 .content {
   display: flex;
   flex: 1;
+  column-gap: 20px;
   width: 98%;
   margin: 20px auto;
 

+ 203 - 99
src/views/orderManagement/index.vue

@@ -3,78 +3,204 @@
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <el-button type="primary" :icon="Download" @click="exportInfoExcel(scope)"> 导出 </el-button>
-      </template>
-      <template #expand="scope">
-        {{ scope.row }}
+        <div class="table-header-btn">
+          <el-button :icon="Plus" class="button" type="primary" @click="newGroupBuying"> 新建代金券 </el-button>
+          <el-tabs v-if="showTabs" v-model="activeName" class="tabs" @tab-click="handleClick">
+            <el-tab-pane v-for="tab in filteredTabOptions" :key="tab.name" :label="tab.label" :name="tab.name" />
+          </el-tabs>
+        </div>
       </template>
+      <template #status="scope">
+        <p>团购状态:{{ allTabOptions.find(item => item.name === scope.row.groupType)?.label ?? "--" }}</p>
+        <p>审核状态:{{ statusEnum.find(item => item.value === scope.row.reviewType)?.label ?? "--" }}</p> </template
+      >:
       <!-- 表格操作 -->
       <template #operation="scope">
         <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
         <template v-if="scope.row.status === '0'">
-          <el-button type="primary" link @click="changeTypes(scope.row, 'pass')"> 审核通过 </el-button>
-          <el-button type="primary" link @click="changeTypes(scope.row, '')"> 审核拒绝 </el-button>
+          <el-button link type="primary" @click="changeTypes(scope.row, 'on')"> 上架 </el-button>
+          <el-button link type="primary" @click="changeTypes(scope.row, 'off')"> 下架 </el-button>
+          <el-button link type="primary" @click="changeInventory(scope.row)"> 修改库存 </el-button>
         </template>
         <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
+        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
+        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
-
-    <el-dialog v-model="dialogFormVisible" title="审核拒绝" width="500">
-      <el-form :model="form">
-        <el-form-item label="" label-width="0">
-          <el-input v-model="form.comment" autocomplete="off" type="textarea" maxlength="200" />
+    <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
+      <el-form ref="ruleFormRef" :model="formInventory" :rules="rules" @submit.prevent>
+        <el-form-item label="套餐名">
+          {{ formInventory.packageName }}
+        </el-form-item>
+        <el-form-item label="剩余库存">
+          {{ formInventory.remainingInventory }}
+        </el-form-item>
+        <el-form-item label="修改库存" prop="newInventory">
+          <el-input v-model="formInventory.newInventory" placeholder="请输入" />
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="closeDialog"> 取消 </el-button>
-          <el-button type="primary" @click="handleSubmit"> 驳回 </el-button>
+          <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
         </div>
       </template>
     </el-dialog>
   </div>
 </template>
 
-<script setup lang="tsx" name="orderManagement">
-import { ref, reactive, onMounted, onActivated } from "vue";
+<script setup lang="tsx" name="groupPackageManagement">
+import { computed, onActivated, onMounted, reactive, ref, watch } from "vue";
 import { useRouter } from "vue-router";
+import type { FormInstance, FormRules } from "element-plus";
 import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
-import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { Download } from "@element-plus/icons-vue";
-import { audit, exportExcelStaffConfig, getStaffConfigList } from "@/api/modules/staffConfig";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { Plus } from "@element-plus/icons-vue";
+import { audit, getStaffConfigList } from "@/api/modules/staffConfig";
+import { getThaliList } from "@/api/modules/groupPackageManagement";
 
 const router = useRouter();
 const dialogFormVisible = ref(false);
-const form = reactive({
-  comment: ""
+const formInventory: any = ref({
+  id: "",
+  packageName: "",
+  remainingInventory: "",
+  newInventory: ""
 });
-
 const rowData = ref<any>();
+const activeName = ref("");
 
+const ruleFormRef = ref<FormInstance>();
+const rules = reactive<FormRules<RuleForm>>({
+  newInventory: [
+    { required: true, message: "请输入库存数量", trigger: "blur" },
+    {
+      pattern: /^(0|[1-9][0-9]*)$/,
+      message: "请输入整数,不允许输入小数,负数",
+      trigger: "blur"
+    }
+  ]
+});
 const statusEnum = [
-  { value: "0", label: "待审核" },
-  { value: "1", label: "审核通过" },
-  { value: "2", label: "审核拒绝" }
+  { value: "-1", label: "待审核" },
+  { value: "-2", label: "审核通过" },
+  { value: "0", label: "审核驳回" }
 ];
-// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
-const initParam = reactive({});
 
-// 定义 filterValues
-const filterValues = reactive({});
+// ProTable 实例(需要在使用它的地方之前定义)
+const proTable = ref<ProTableInstance>();
 
-const getStatusObj = (statusValue: string) => {
-  const statusObj = statusEnum.find(item => item.value === statusValue);
-  if (statusObj) {
-    filterValues.status = statusObj;
-  } else {
-    filterValues.status = "";
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "index",
+    label: "序号",
+    width: 100,
+    render: (scope: any) => {
+      return scope.$index + (proTable.value!.pageable.pageNum - 1) * proTable.value!.pageable.pageSize + 1;
+    }
+  },
+  {
+    prop: "groupName",
+    label: "代金券名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "groupName",
+    label: "价格",
+    render: (scope: any) => {
+      return `¥${scope.row.preferentialPrice}`;
+    }
+  },
+  {
+    prop: "goodsId",
+    label: "已售"
+  },
+  {
+    prop: "inventoryNum",
+    label: "剩余库存"
+  },
+  {
+    prop: "goodsId",
+    label: "结束时间"
+  },
+  {
+    prop: "reviewType",
+    label: "审核状态",
+    isShow: false,
+    render: scope => {
+      const statusObj = statusEnum.find(item => item.value === scope.row.reviewType);
+      return statusObj ? statusObj.label : "--";
+    },
+    search: {
+      el: "select"
+    },
+    enum: statusEnum,
+    fieldNames: { label: "label", value: "value" }
+  },
+  {
+    prop: "status",
+    label: "状态"
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 330 }
+]);
+
+// 在 script setup 中添加
+const allTabOptions = [
+  { label: "全部", name: "" },
+  { label: "草稿", name: "0" },
+  { label: "进行中", name: "5" },
+  { label: "待审核", name: "1" },
+  { label: "审核拒绝", name: "3" },
+  { label: "已售罄", name: "4" },
+  { label: "已结束", name: "7" }
+];
+
+// 获取当前审核状态
+const currentAuditStatus = computed(() => {
+  if (!proTable.value?.searchParam) return "";
+  return proTable.value.searchParam.reviewType || "";
+});
+
+// 控制 el-tabs 是否显示:当审核状态为空或审核通过时显示
+const showTabs = computed(() => {
+  const status = currentAuditStatus.value;
+  return !status || status === "-2";
+});
+
+// 根据审核状态过滤 tabOptions:如果审核状态为空,只显示草稿;审核通过时,显示除草稿外的所有标签页
+const filteredTabOptions = computed(() => {
+  const status = currentAuditStatus.value;
+  if (!status) {
+    // 审核状态为空时,只显示草稿
+    return allTabOptions;
+  } else if (status === "-2") {
+    // 审核通过时,显示除草稿外的所有标签页
+    return allTabOptions.filter(tab => tab.name !== "1");
   }
-  return statusObj;
-};
+  return [];
+});
 
-// ProTable 实例
-const proTable = ref<ProTableInstance>();
+// 监听审核状态变化
+watch(
+  currentAuditStatus,
+  newStatus => {
+    if (!newStatus || newStatus === "-2") {
+      // 审核状态为空时,确保 activeName 为草稿
+      activeName.value = "";
+    }
+  },
+  { immediate: true }
+);
+// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
+const initParam = reactive({
+  storeId: "104",
+  groupType: "1",
+  status: activeName
+});
 
 // 页面加载时触发查询
 onMounted(() => {
@@ -99,50 +225,38 @@ const dataCallback = (data: any) => {
 // 默认不做操作就直接在 ProTable 组件上绑定	:requestApi="getUserList"
 const getTableList = (params: any) => {
   let newParams = JSON.parse(JSON.stringify(params));
-  return getStaffConfigList(newParams);
+  return getThaliList(newParams);
 };
 
 // 跳转详情页
 const toDetail = row => {
-  router.push(`/store/orderManagementDetail?id=${row.id}`);
+  router.push(`/voucherManagement/detail?id=${row.id}&type=edit`);
 };
-
-// 表格配置项
-const columns = reactive<ColumnProps<any>[]>([
-  { type: "index", fixed: "left", label: "序号", width: 130 },
-  { prop: "storeName", label: "所属店铺" },
-  { prop: "name", label: "名称" },
-  { prop: "description", label: "描述" },
-  {
-    prop: "status",
-    label: "状态",
-    render: scope => {
-      const statusObj = getStatusObj(scope.row.status);
-      return statusObj ? statusObj.label : "未知状态";
-    },
-    search: {
-      el: "select"
-    },
-    enum: statusEnum,
-    fieldNames: { label: "label", value: "value" }
-  },
-  { prop: "operation", label: "操作", fixed: "right", width: 330 }
-]);
-
+const editRow = row => {};
+const deleteRow = row => {};
+const newGroupBuying = () => {
+  router.push(`/voucherManagement/newVoucher?id=104&type=add`);
+};
+const handleClick = () => {};
 const changeTypes = (row: any, status: string) => {
   rowData.value = row;
-  if (status === "pass") {
+  if (status === "on") {
     handleChangeStatus(row, "1");
   } else {
-    form.comment = "";
-    dialogFormVisible.value = true;
+    handleChangeStatus(row, "2");
   }
 };
-
+const changeInventory = (row: any) => {
+  formInventory.value.id = 1;
+  formInventory.value.packageName = 1;
+  formInventory.value.remainingInventory = 1;
+  formInventory.value.newInventory = "";
+  dialogFormVisible.value = true;
+};
 const handleChangeStatus = async (row: any, status: string) => {
   try {
     let res = await audit({ id: row.id, status: status, rejectionReason: form.comment });
-    if (res.code === 200) {
+    if (res.code == 200) {
       proTable.value?.getTableList();
       if (status === "2") closeDialog();
       ElMessage.success("审核成功");
@@ -152,43 +266,25 @@ const handleChangeStatus = async (row: any, status: string) => {
   }
 };
 
-// 导出信息
-const exportInfoExcel = async scope => {
-  let res;
-  // 获取原始状态值(可能为数字、字符串或 undefined)
-  const rawStatus = proTable.value.searchParam.status;
-  // 转换为字符串(处理 undefined/null 为 "" 或保留原始字符串)
-  const statusParam = rawStatus !== undefined && rawStatus !== null ? String(rawStatus) : undefined;
-  // 将筛选条件作为参数传递给后台
-  res = await exportExcelStaffConfig({ status: statusParam });
-  if (res.code === 200) {
-    if (!res.data) {
-      ElMessage.error("暂无可下载数据");
-      return;
-    }
-    const exportFile = document.createElement("a");
-    exportFile.style.display = "none";
-    exportFile.download = `订单管理.xlsx`;
-    exportFile.href = `${res.data}?timestamp=${new Date().getTime()}`; // 添加时间戳防止缓存
-    document.body.appendChild(exportFile);
-    exportFile.click();
-    document.body.removeChild(exportFile);
-    ElMessage.success("下载成功");
-  }
-};
-
 // 弹窗提交
-const handleSubmit = () => {
-  if (!form.comment) {
-    ElMessage.error("请输入审批意见");
-    return;
-  }
-  handleChangeStatus(rowData.value, "2");
+const handleSubmit = async () => {
+  if (!ruleFormRef.value) return;
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      ElMessage.success("修改成功");
+      dialogFormVisible.value = false;
+    }
+  });
 };
 // 关闭弹窗;
 const closeDialog = () => {
   dialogFormVisible.value = false;
-  form.comment = "";
+  formInventory.value = {
+    id: "",
+    packageName: "",
+    remainingInventory: "",
+    newInventory: ""
+  };
 };
 </script>
 
@@ -200,4 +296,12 @@ const closeDialog = () => {
   word-wrap: break-word; // 长单词内换行
   white-space: normal; // 允许自然换行
 }
+.table-header-btn {
+  .tabs {
+    margin-top: 10px;
+    :deep(.el-tabs__nav-wrap::after) {
+      height: 0;
+    }
+  }
+}
 </style>

+ 203 - 99
src/views/voucherManagement/index.vue

@@ -3,78 +3,204 @@
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <el-button type="primary" :icon="Download" @click="exportInfoExcel(scope)"> 导出 </el-button>
-      </template>
-      <template #expand="scope">
-        {{ scope.row }}
+        <div class="table-header-btn">
+          <el-button :icon="Plus" class="button" type="primary" @click="newGroupBuying"> 新建代金券 </el-button>
+          <el-tabs v-if="showTabs" v-model="activeName" class="tabs" @tab-click="handleClick">
+            <el-tab-pane v-for="tab in filteredTabOptions" :key="tab.name" :label="tab.label" :name="tab.name" />
+          </el-tabs>
+        </div>
       </template>
+      <template #status="scope">
+        <p>团购状态:{{ allTabOptions.find(item => item.name === scope.row.groupType)?.label ?? "--" }}</p>
+        <p>审核状态:{{ statusEnum.find(item => item.value === scope.row.reviewType)?.label ?? "--" }}</p> </template
+      >:
       <!-- 表格操作 -->
       <template #operation="scope">
         <!-- 审批通过和拒绝按钮仅在状态为0时显示 -->
         <template v-if="scope.row.status === '0'">
-          <el-button type="primary" link @click="changeTypes(scope.row, 'pass')"> 审核通过 </el-button>
-          <el-button type="primary" link @click="changeTypes(scope.row, '')"> 审核拒绝 </el-button>
+          <el-button link type="primary" @click="changeTypes(scope.row, 'on')"> 上架 </el-button>
+          <el-button link type="primary" @click="changeTypes(scope.row, 'off')"> 下架 </el-button>
+          <el-button link type="primary" @click="changeInventory(scope.row)"> 修改库存 </el-button>
         </template>
         <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
+        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
+        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
-
-    <el-dialog v-model="dialogFormVisible" title="审核拒绝" width="500">
-      <el-form :model="form">
-        <el-form-item label="" label-width="0">
-          <el-input v-model="form.comment" autocomplete="off" type="textarea" maxlength="200" />
+    <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
+      <el-form ref="ruleFormRef" :model="formInventory" :rules="rules" @submit.prevent>
+        <el-form-item label="套餐名">
+          {{ formInventory.packageName }}
+        </el-form-item>
+        <el-form-item label="剩余库存">
+          {{ formInventory.remainingInventory }}
+        </el-form-item>
+        <el-form-item label="修改库存" prop="newInventory">
+          <el-input v-model="formInventory.newInventory" placeholder="请输入" />
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="closeDialog"> 取消 </el-button>
-          <el-button type="primary" @click="handleSubmit"> 驳回 </el-button>
+          <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
         </div>
       </template>
     </el-dialog>
   </div>
 </template>
 
-<script setup lang="tsx" name="voucherManagement">
-import { ref, reactive, onMounted, onActivated } from "vue";
+<script setup lang="tsx" name="groupPackageManagement">
+import { computed, onActivated, onMounted, reactive, ref, watch } from "vue";
 import { useRouter } from "vue-router";
+import type { FormInstance, FormRules } from "element-plus";
 import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
-import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { Download } from "@element-plus/icons-vue";
-import { audit, exportExcelStaffConfig, getStaffConfigList } from "@/api/modules/staffConfig";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { Plus } from "@element-plus/icons-vue";
+import { audit, getStaffConfigList } from "@/api/modules/staffConfig";
+import { getThaliList } from "@/api/modules/groupPackageManagement";
 
 const router = useRouter();
 const dialogFormVisible = ref(false);
-const form = reactive({
-  comment: ""
+const formInventory: any = ref({
+  id: "",
+  packageName: "",
+  remainingInventory: "",
+  newInventory: ""
 });
-
 const rowData = ref<any>();
+const activeName = ref("");
 
+const ruleFormRef = ref<FormInstance>();
+const rules = reactive<FormRules<RuleForm>>({
+  newInventory: [
+    { required: true, message: "请输入库存数量", trigger: "blur" },
+    {
+      pattern: /^(0|[1-9][0-9]*)$/,
+      message: "请输入整数,不允许输入小数,负数",
+      trigger: "blur"
+    }
+  ]
+});
 const statusEnum = [
-  { value: "0", label: "待审核" },
-  { value: "1", label: "审核通过" },
-  { value: "2", label: "审核拒绝" }
+  { value: "-1", label: "待审核" },
+  { value: "-2", label: "审核通过" },
+  { value: "0", label: "审核驳回" }
 ];
-// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
-const initParam = reactive({});
 
-// 定义 filterValues
-const filterValues = reactive({});
+// ProTable 实例(需要在使用它的地方之前定义)
+const proTable = ref<ProTableInstance>();
 
-const getStatusObj = (statusValue: string) => {
-  const statusObj = statusEnum.find(item => item.value === statusValue);
-  if (statusObj) {
-    filterValues.status = statusObj;
-  } else {
-    filterValues.status = "";
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "index",
+    label: "序号",
+    width: 100,
+    render: (scope: any) => {
+      return scope.$index + (proTable.value!.pageable.pageNum - 1) * proTable.value!.pageable.pageSize + 1;
+    }
+  },
+  {
+    prop: "groupName",
+    label: "代金券名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "groupName",
+    label: "价格",
+    render: (scope: any) => {
+      return `¥${scope.row.preferentialPrice}`;
+    }
+  },
+  {
+    prop: "goodsId",
+    label: "已售"
+  },
+  {
+    prop: "inventoryNum",
+    label: "剩余库存"
+  },
+  {
+    prop: "goodsId",
+    label: "结束时间"
+  },
+  {
+    prop: "reviewType",
+    label: "审核状态",
+    isShow: false,
+    render: scope => {
+      const statusObj = statusEnum.find(item => item.value === scope.row.reviewType);
+      return statusObj ? statusObj.label : "--";
+    },
+    search: {
+      el: "select"
+    },
+    enum: statusEnum,
+    fieldNames: { label: "label", value: "value" }
+  },
+  {
+    prop: "status",
+    label: "状态"
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 330 }
+]);
+
+// 在 script setup 中添加
+const allTabOptions = [
+  { label: "全部", name: "" },
+  { label: "草稿", name: "0" },
+  { label: "进行中", name: "5" },
+  { label: "待审核", name: "1" },
+  { label: "审核拒绝", name: "3" },
+  { label: "已售罄", name: "4" },
+  { label: "已结束", name: "7" }
+];
+
+// 获取当前审核状态
+const currentAuditStatus = computed(() => {
+  if (!proTable.value?.searchParam) return "";
+  return proTable.value.searchParam.reviewType || "";
+});
+
+// 控制 el-tabs 是否显示:当审核状态为空或审核通过时显示
+const showTabs = computed(() => {
+  const status = currentAuditStatus.value;
+  return !status || status === "-2";
+});
+
+// 根据审核状态过滤 tabOptions:如果审核状态为空,只显示草稿;审核通过时,显示除草稿外的所有标签页
+const filteredTabOptions = computed(() => {
+  const status = currentAuditStatus.value;
+  if (!status) {
+    // 审核状态为空时,只显示草稿
+    return allTabOptions;
+  } else if (status === "-2") {
+    // 审核通过时,显示除草稿外的所有标签页
+    return allTabOptions.filter(tab => tab.name !== "1");
   }
-  return statusObj;
-};
+  return [];
+});
 
-// ProTable 实例
-const proTable = ref<ProTableInstance>();
+// 监听审核状态变化
+watch(
+  currentAuditStatus,
+  newStatus => {
+    if (!newStatus || newStatus === "-2") {
+      // 审核状态为空时,确保 activeName 为草稿
+      activeName.value = "";
+    }
+  },
+  { immediate: true }
+);
+// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
+const initParam = reactive({
+  storeId: "104",
+  groupType: "1",
+  status: activeName
+});
 
 // 页面加载时触发查询
 onMounted(() => {
@@ -99,50 +225,38 @@ const dataCallback = (data: any) => {
 // 默认不做操作就直接在 ProTable 组件上绑定	:requestApi="getUserList"
 const getTableList = (params: any) => {
   let newParams = JSON.parse(JSON.stringify(params));
-  return getStaffConfigList(newParams);
+  return getThaliList(newParams);
 };
 
 // 跳转详情页
 const toDetail = row => {
-  router.push(`/store/voucherManagementDetail?id=${row.id}`);
+  router.push(`/voucherManagement/detail?id=${row.id}&type=edit`);
 };
-
-// 表格配置项
-const columns = reactive<ColumnProps<any>[]>([
-  { type: "index", fixed: "left", label: "序号", width: 130 },
-  { prop: "storeName", label: "所属店铺" },
-  { prop: "name", label: "名称" },
-  { prop: "description", label: "描述" },
-  {
-    prop: "status",
-    label: "状态",
-    render: scope => {
-      const statusObj = getStatusObj(scope.row.status);
-      return statusObj ? statusObj.label : "未知状态";
-    },
-    search: {
-      el: "select"
-    },
-    enum: statusEnum,
-    fieldNames: { label: "label", value: "value" }
-  },
-  { prop: "operation", label: "操作", fixed: "right", width: 330 }
-]);
-
+const editRow = row => {};
+const deleteRow = row => {};
+const newGroupBuying = () => {
+  router.push(`/voucherManagement/newVoucher?id=104&type=add`);
+};
+const handleClick = () => {};
 const changeTypes = (row: any, status: string) => {
   rowData.value = row;
-  if (status === "pass") {
+  if (status === "on") {
     handleChangeStatus(row, "1");
   } else {
-    form.comment = "";
-    dialogFormVisible.value = true;
+    handleChangeStatus(row, "2");
   }
 };
-
+const changeInventory = (row: any) => {
+  formInventory.value.id = 1;
+  formInventory.value.packageName = 1;
+  formInventory.value.remainingInventory = 1;
+  formInventory.value.newInventory = "";
+  dialogFormVisible.value = true;
+};
 const handleChangeStatus = async (row: any, status: string) => {
   try {
     let res = await audit({ id: row.id, status: status, rejectionReason: form.comment });
-    if (res.code === 200) {
+    if (res.code == 200) {
       proTable.value?.getTableList();
       if (status === "2") closeDialog();
       ElMessage.success("审核成功");
@@ -152,43 +266,25 @@ const handleChangeStatus = async (row: any, status: string) => {
   }
 };
 
-// 导出信息
-const exportInfoExcel = async scope => {
-  let res;
-  // 获取原始状态值(可能为数字、字符串或 undefined)
-  const rawStatus = proTable.value.searchParam.status;
-  // 转换为字符串(处理 undefined/null 为 "" 或保留原始字符串)
-  const statusParam = rawStatus !== undefined && rawStatus !== null ? String(rawStatus) : undefined;
-  // 将筛选条件作为参数传递给后台
-  res = await exportExcelStaffConfig({ status: statusParam });
-  if (res.code === 200) {
-    if (!res.data) {
-      ElMessage.error("暂无可下载数据");
-      return;
-    }
-    const exportFile = document.createElement("a");
-    exportFile.style.display = "none";
-    exportFile.download = `代金券管理.xlsx`;
-    exportFile.href = `${res.data}?timestamp=${new Date().getTime()}`; // 添加时间戳防止缓存
-    document.body.appendChild(exportFile);
-    exportFile.click();
-    document.body.removeChild(exportFile);
-    ElMessage.success("下载成功");
-  }
-};
-
 // 弹窗提交
-const handleSubmit = () => {
-  if (!form.comment) {
-    ElMessage.error("请输入审批意见");
-    return;
-  }
-  handleChangeStatus(rowData.value, "2");
+const handleSubmit = async () => {
+  if (!ruleFormRef.value) return;
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      ElMessage.success("修改成功");
+      dialogFormVisible.value = false;
+    }
+  });
 };
 // 关闭弹窗;
 const closeDialog = () => {
   dialogFormVisible.value = false;
-  form.comment = "";
+  formInventory.value = {
+    id: "",
+    packageName: "",
+    remainingInventory: "",
+    newInventory: ""
+  };
 };
 </script>
 
@@ -200,4 +296,12 @@ const closeDialog = () => {
   word-wrap: break-word; // 长单词内换行
   white-space: normal; // 允许自然换行
 }
+.table-header-btn {
+  .tabs {
+    margin-top: 10px;
+    :deep(.el-tabs__nav-wrap::after) {
+      height: 0;
+    }
+  }
+}
 </style>

+ 1018 - 0
src/views/voucherManagement/newVoucher.vue

@@ -0,0 +1,1018 @@
+<template>
+  <!-- 代金券管理 - 新增/编辑页面 -->
+  <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
+    <div class="header">
+      <el-button @click="goBack"> 返回 </el-button>
+      <h2 class="title">新建代金券</h2>
+    </div>
+    <el-form :model="voucherModel" ref="ruleFormRef" :rules="rules" label-width="120px" class="formBox">
+      <div class="content">
+        <!-- 左侧内容区域 -->
+        <div class="contentLeft">
+          <!-- 基础信息模块 -->
+          <div class="model">
+            <h3 style="font-weight: bold">基础信息:</h3>
+            <!-- 代金券名称 -->
+            <el-form-item label="代金券名称" prop="voucherName">
+              <el-input maxlength="50" v-model="voucherModel.voucherName" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 抵扣价格 -->
+            <el-form-item label="抵扣价格(¥)" prop="discountPrice">
+              <el-input v-model="voucherModel.discountPrice" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 售卖价格 -->
+            <el-form-item label="售卖价格(¥)" prop="sellingPrice">
+              <el-input v-model="voucherModel.sellingPrice" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 开始售卖时间 -->
+            <el-form-item label="开始售卖时间" prop="startSellingTime">
+              <el-date-picker
+                v-model="voucherModel.startSellingTime"
+                format="YYYY/MM/DD"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择开始售卖时间"
+                :disabled-date="disabledStartDate"
+              />
+            </el-form-item>
+            <!-- 结束售卖时间 -->
+            <el-form-item label="结束售卖时间" prop="endSellingTime">
+              <el-date-picker
+                v-model="voucherModel.endSellingTime"
+                format="YYYY/MM/DD"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择结束售卖时间"
+                :disabled-date="disabledEndDate"
+              />
+            </el-form-item>
+          </div>
+          <!-- 购买须知模块 -->
+          <div class="model">
+            <h3 style="font-weight: bold">购买须知:</h3>
+            <!-- 使用时间 -->
+            <el-form-item label="使用时间" prop="usageTime">
+              <el-date-picker
+                v-model="voucherModel.usageTime"
+                type="daterange"
+                value-format="YYYY-MM-DD"
+                range-separator="-"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                :disabled-date="disabledUsageDate"
+              />
+            </el-form-item>
+            <!-- 有效期 -->
+            <el-form-item label="有效期" prop="validityPeriodType">
+              <el-radio-group v-model="voucherModel.validityPeriodType" class="ml-4">
+                <el-radio v-for="status in validityPeriodList" :value="status.value" :key="status.value">
+                  {{ status.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="" prop="validityDays" v-if="voucherModel.validityPeriodType == 0">
+              <div class="expiration-date-container">
+                <span class="expiration-label">用户购买</span>
+                <el-input-number v-model="voucherModel.validityDays" placeholder="请输入" :min="1" class="expiration-input" />
+                <span class="expiration-label">天内有效</span>
+              </div>
+            </el-form-item>
+            <el-form-item label="" prop="validityPeriod" v-else>
+              <el-date-picker
+                v-model="voucherModel.validityPeriod"
+                type="daterange"
+                value-format="YYYY-MM-DD"
+                range-separator="-"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                :disabled-date="disabledValidityDate"
+              />
+            </el-form-item>
+            <!-- 不可用日期 -->
+            <el-form-item label="不可用日期" prop="unavailableDateType">
+              <el-radio-group v-model="voucherModel.unavailableDateType" class="ml-4">
+                <el-radio v-for="status in unavailableDateTypeList" :value="status.value" :key="status.value">
+                  {{ status.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <template v-if="voucherModel.unavailableDateType == 1">
+              <el-form-item label="" prop="unavailableWeekdays">
+                <div class="unavailable-dates-container">
+                  <!-- 星期选择 -->
+                  <div class="date-select-section">
+                    <div class="section-title">星期</div>
+                    <div class="button-group">
+                      <el-button
+                        v-for="weekday in weekdayList"
+                        :key="weekday.oName"
+                        :type="voucherModel.unavailableWeekdays?.includes(weekday.oName) ? 'primary' : ''"
+                        class="date-select-btn"
+                        @click="toggleWeekday(weekday.oName)"
+                      >
+                        {{ weekday.name }}
+                      </el-button>
+                    </div>
+                  </div>
+                </div>
+              </el-form-item>
+              <el-form-item label="" prop="unavailableHolidays">
+                <div class="unavailable-dates-container">
+                  <!-- 节日选择 -->
+                  <div class="date-select-section">
+                    <div class="section-title">节日</div>
+                    <div class="button-group">
+                      <el-button
+                        v-for="holiday in holidayList"
+                        :key="holiday.id"
+                        :type="voucherModel.unavailableHolidays?.includes(holiday.id) ? 'primary' : ''"
+                        class="date-select-btn"
+                        @click="toggleHoliday(holiday.id)"
+                      >
+                        {{ holiday.festivalName }}
+                      </el-button>
+                    </div>
+                  </div>
+                </div>
+              </el-form-item>
+            </template>
+            <el-form-item label="" prop="customUnavailableDates" v-else-if="voucherModel.unavailableDateType == 2">
+              <div class="date-picker-container">
+                <el-button :icon="Plus" class="add-date-btn" type="primary" @click="addDate"> 添加日期 </el-button>
+                <div v-for="(item, index) in dates" :key="index" class="date-item">
+                  <el-date-picker
+                    v-model="dates[index]"
+                    type="daterange"
+                    value-format="YYYY-MM-DD"
+                    range-separator="-"
+                    start-placeholder="开始时间"
+                    end-placeholder="结束时间"
+                    class="date-picker"
+                    :disabled-date="disabledCustomUnavailableDate"
+                  />
+                  <el-button
+                    :icon="Delete"
+                    type="danger"
+                    circle
+                    size="small"
+                    class="delete-btn"
+                    @click="removeDate(index)"
+                    v-show="dates.length > 1"
+                  />
+                </div>
+              </div>
+            </el-form-item>
+          </div>
+        </div>
+        <!-- 右侧内容区域 -->
+        <div class="contentRight">
+          <!-- 库存模块 -->
+          <div class="model">
+            <h3 style="font-weight: bold">库存:</h3>
+            <el-form-item label="库存" prop="inventory">
+              <el-input v-model="voucherModel.inventory" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+          </div>
+          <!-- 使用规则模块 -->
+          <div class="model">
+            <h3 style="font-weight: bold">使用规则:</h3>
+            <!-- 单日可用数量 -->
+            <el-form-item label="单日可用数量" prop="dailyAvailableQuantity">
+              <el-input v-model="voucherModel.dailyAvailableQuantity" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 限购数量 -->
+            <el-form-item label="限购数量" prop="purchaseLimitQuantity">
+              <el-input v-model="voucherModel.purchaseLimitQuantity" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 适用范围 -->
+            <el-form-item label="适用范围" prop="applicableScopeType">
+              <el-radio-group v-model="voucherModel.applicableScopeType" class="ml-4">
+                <el-radio v-for="status in applicableScopeList" :value="status.value" :key="status.value">
+                  {{ status.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="" prop="applicableScope" v-if="voucherModel.applicableScopeType == 1">
+              <el-input
+                maxlength="50"
+                v-model="voucherModel.applicableScope"
+                :rows="3"
+                type="textarea"
+                placeholder="请输入"
+                show-word-limit
+              />
+            </el-form-item>
+          </div>
+          <!-- 补充说明模块 -->
+          <div class="model">
+            <h3 style="font-weight: bold">补充说明:</h3>
+            <el-form-item label="补充说明" prop="additionalInstructions">
+              <el-input
+                maxlength="300"
+                v-model="voucherModel.additionalInstructions"
+                :rows="4"
+                type="textarea"
+                placeholder="请输入"
+                show-word-limit
+              />
+            </el-form-item>
+          </div>
+        </div>
+      </div>
+    </el-form>
+    <!-- 底部按钮区域 -->
+    <div class="button-container">
+      <el-button @click="handleSubmit('draft')"> 存草稿 </el-button>
+      <el-button type="primary" @click="handleSubmit"> 新建代金券 </el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="newVoucher">
+/**
+ * 代金券管理 - 新增/编辑页面
+ * 功能:支持代金券的新增和编辑操作
+ */
+import { ref, reactive, onMounted, watch, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+import { Plus, Delete } from "@element-plus/icons-vue";
+import { getHolidayList } from "@/api/modules/groupPackageManagement";
+import { useRouter, useRoute } from "vue-router";
+import type { FormInstance } from "element-plus";
+
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+const route = useRoute();
+
+// 页面状态
+const type = ref<string>(""); // 页面类型:add-新增, edit-编辑
+const id = ref<string>(""); // 页面ID参数
+
+// ==================== 表单验证规则 ====================
+const rules = reactive({
+  voucherName: [{ required: true, message: "请输入代金券名称" }],
+  discountPrice: [
+    { required: true, message: "请输入抵扣价格" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const num = Number(value);
+        if (isNaN(num) || num <= 0) {
+          callback(new Error("抵扣价格必须为正数"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  sellingPrice: [
+    { required: true, message: "请输入售卖价格" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const num = Number(value);
+        if (isNaN(num) || num <= 0) {
+          callback(new Error("售卖价格必须为正数"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  startSellingTime: [
+    { required: true, message: "请选择开始售卖时间" },
+    {
+      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;
+        }
+        if (voucherModel.value.endSellingTime) {
+          const endDate = new Date(voucherModel.value.endSellingTime);
+          if (selectedDate >= endDate) {
+            callback(new Error("开始售卖时间必须早于结束售卖时间"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  endSellingTime: [
+    { required: true, message: "请选择结束售卖时间" },
+    {
+      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;
+        }
+        if (voucherModel.value.startSellingTime) {
+          const startDate = new Date(voucherModel.value.startSellingTime);
+          if (selectedDate <= startDate) {
+            callback(new Error("结束售卖时间必须晚于开始售卖时间"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  usageTime: [
+    { required: true, message: "请选择使用时间" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value || value.length !== 2) {
+          callback();
+          return;
+        }
+        const startDate = new Date(value[0]);
+        const endDate = new Date(value[1]);
+        if (startDate >= endDate) {
+          callback(new Error("使用开始时间必须早于结束时间"));
+          return;
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  validityPeriodType: [{ required: true, message: "请选择有效期类型" }],
+  validityDays: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.validityPeriodType === 0) {
+          if (value === null || value === undefined || value === "") {
+            callback(new Error("请输入用户购买天数"));
+            return;
+          }
+          if (value <= 0) {
+            callback(new Error("天数必须大于0"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  validityPeriod: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.validityPeriodType === 1) {
+          if (!value || value.length !== 2) {
+            callback(new Error("请选择指定时间段"));
+            return;
+          }
+          const startDate = new Date(value[0]);
+          const endDate = new Date(value[1]);
+          if (startDate >= endDate) {
+            callback(new Error("开始时间必须早于结束时间"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  unavailableDateType: [{ required: true, message: "请选择不可用日期类型" }],
+  unavailableWeekdays: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.unavailableDateType === 1) {
+          if (!value || value.length === 0) {
+            callback(new Error("至少需要选择一个星期"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  unavailableHolidays: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.unavailableDateType === 1) {
+          if (!value || value.length === 0) {
+            callback(new Error("至少需要选择一个节日"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  customUnavailableDates: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.unavailableDateType === 2) {
+          if (!dates.value || dates.value.length === 0) {
+            callback(new Error("至少需要添加一个自定义不可用日期"));
+            return;
+          }
+          for (let i = 0; i < dates.value.length; i++) {
+            if (!dates.value[i] || dates.value[i].length !== 2) {
+              callback(new Error(`第${i + 1}个日期项未完整填写`));
+              return;
+            }
+            const startDate = new Date(dates.value[i][0]);
+            const endDate = new Date(dates.value[i][1]);
+            if (startDate >= endDate) {
+              callback(new Error(`第${i + 1}个日期项的开始时间必须早于结束时间`));
+              return;
+            }
+          }
+        }
+        callback();
+      },
+      trigger: "change"
+    }
+  ],
+  inventory: [
+    { required: true, message: "请输入库存" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        const num = Number(value);
+        if (isNaN(num) || num <= 0 || !Number.isInteger(num)) {
+          callback(new Error("库存必须为正整数"));
+          return;
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  dailyAvailableQuantity: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (value && value.toString().trim() !== "") {
+          const num = Number(value);
+          if (isNaN(num) || num <= 0 || !Number.isInteger(num)) {
+            callback(new Error("单日可用数量必须为正整数"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  purchaseLimitQuantity: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (value && value.toString().trim() !== "") {
+          const num = Number(value);
+          if (isNaN(num) || num <= 0 || !Number.isInteger(num)) {
+            callback(new Error("限购数量必须为正整数"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ],
+  applicableScopeType: [{ required: true, message: "请选择适用范围类型" }],
+  applicableScope: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.applicableScopeType === 1) {
+          if (!value || value.trim() === "") {
+            callback(new Error("请输入适用范围"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ]
+});
+
+// ==================== 代金券信息数据模型 ====================
+const voucherModel = ref<any>({
+  // 代金券名称
+  voucherName: "",
+  // 抵扣价格
+  discountPrice: "",
+  // 售卖价格
+  sellingPrice: "",
+  // 开始售卖时间
+  startSellingTime: "",
+  // 结束售卖时间
+  endSellingTime: "",
+  // 使用时间
+  usageTime: [],
+  // 有效期类型:0-指定天数,1-指定时间段内可用
+  validityPeriodType: 0,
+  // 有效期天数(当validityPeriodType为0时使用)
+  validityDays: 90,
+  // 有效期时间段(当validityPeriodType为1时使用)
+  validityPeriod: [],
+  // 不可用日期类型:0-全部日期可用,1-限制日期,2-自定义不可用日期
+  unavailableDateType: 0,
+  // 限制日期 - 星期选择(数组,存储选中的星期值)
+  unavailableWeekdays: [],
+  // 限制日期 - 节日选择(数组,存储选中的节日值)
+  unavailableHolidays: [],
+  // 库存
+  inventory: "",
+  // 单日可用数量
+  dailyAvailableQuantity: "",
+  // 限购数量
+  purchaseLimitQuantity: "",
+  // 适用范围类型:0-全场通用,1-部分不可用
+  applicableScopeType: 0,
+  // 适用范围(当applicableScopeType为1时使用)
+  applicableScope: "",
+  // 补充说明
+  additionalInstructions: ""
+});
+
+// ==================== 下拉选项数据 ====================
+
+// 有效期类型列表
+const validityPeriodList = ref([
+  { value: 0, label: "指定天数" },
+  { value: 1, label: "指定时间段内可用" }
+]);
+
+// 不可用日期类型列表
+const unavailableDateTypeList = ref([
+  { value: 0, label: "全部日期可用" },
+  { value: 1, label: "限制日期" },
+  { value: 2, label: "自定义不可用日期" }
+]);
+
+// 适用范围类型列表
+const applicableScopeList = ref([
+  { value: 0, label: "全场通用" },
+  { value: 1, label: "部分不可用" }
+]);
+
+// 自定义不可用日期列表
+const dates = ref([]);
+
+// 星期选项列表
+const weekdayList = ref([
+  { name: "周一", id: "0", oName: "星期一" },
+  { name: "周二", id: "1", oName: "星期二" },
+  { name: "周三", id: "2", oName: "星期三" },
+  { name: "周四", id: "3", oName: "星期四" },
+  { name: "周五", id: "4", oName: "星期五" },
+  { name: "周六", id: "5", oName: "星期六" },
+  { name: "周日", id: "6", oName: "星期日" }
+]);
+
+// 节日选项列表
+const holidayList: any = ref([]);
+
+// ==================== 监听器 ====================
+
+/**
+ * 监听不可用日期选项变化
+ * 当切换到自定义不可用日期时,确保至少有一个日期项
+ */
+watch(
+  () => voucherModel.value.unavailableDateType,
+  newVal => {
+    if (newVal === 2) {
+      if (!dates.value || dates.value.length === 0) {
+        dates.value = [null];
+      }
+    }
+  },
+  { immediate: true }
+);
+
+/**
+ * 监听开始售卖时间变化
+ * 当开始时间改变时,重新验证结束时间
+ */
+watch(
+  () => voucherModel.value.startSellingTime,
+  () => {
+    if (voucherModel.value.endSellingTime) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("endSellingTime");
+      });
+    }
+  }
+);
+
+/**
+ * 监听结束售卖时间变化
+ * 当结束时间改变时,重新验证开始时间
+ */
+watch(
+  () => voucherModel.value.endSellingTime,
+  () => {
+    if (voucherModel.value.startSellingTime) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("startSellingTime");
+      });
+    }
+  }
+);
+
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取页面类型和ID
+ */
+onMounted(async () => {
+  id.value = (route.query.id as string) || "";
+  type.value = (route.query.type as string) || "";
+
+  // 获取节日列表
+  let params = {
+    year: new Date().getFullYear(),
+    page: 1,
+    size: 500,
+    openFlag: 1,
+    holidayName: ""
+  };
+  let res = await getHolidayList(params);
+  if (res && res.code == 200) {
+    holidayList.value = res.data.records;
+  }
+
+  // 编辑模式下加载数据
+  if (type.value != "add") {
+    // TODO: 加载代金券详情数据
+    // let res: any = await getVoucherDetail({ id: id.value });
+    // voucherModel.value = res.data;
+  }
+});
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+/**
+ * 添加自定义不可用日期
+ */
+const addDate = () => {
+  dates.value.push(null);
+};
+
+/**
+ * 删除自定义不可用日期
+ * @param index 要删除的日期索引
+ */
+const removeDate = (index: number) => {
+  if (dates.value.length <= 1) {
+    ElMessage.warning("至少需要保留一个日期项");
+    return;
+  }
+  dates.value.splice(index, 1);
+};
+
+/**
+ * 切换星期选择
+ * @param value 星期值
+ */
+const toggleWeekday = (value: string) => {
+  if (!voucherModel.value.unavailableWeekdays) {
+    voucherModel.value.unavailableWeekdays = [];
+  }
+  const index = voucherModel.value.unavailableWeekdays.indexOf(value);
+  if (index > -1) {
+    voucherModel.value.unavailableWeekdays.splice(index, 1);
+  } else {
+    voucherModel.value.unavailableWeekdays.push(value);
+  }
+  // 触发表单验证
+  nextTick(() => {
+    ruleFormRef.value?.validateField("unavailableWeekdays");
+  });
+};
+
+/**
+ * 切换节日选择
+ * @param value 节日值
+ */
+const toggleHoliday = (value: string | number) => {
+  if (!voucherModel.value.unavailableHolidays) {
+    voucherModel.value.unavailableHolidays = [];
+  }
+  const index = voucherModel.value.unavailableHolidays.indexOf(value);
+  if (index > -1) {
+    voucherModel.value.unavailableHolidays.splice(index, 1);
+  } else {
+    voucherModel.value.unavailableHolidays.push(value);
+  }
+  // 触发表单验证
+  nextTick(() => {
+    ruleFormRef.value?.validateField("unavailableHolidays");
+  });
+};
+
+// ==================== 表单引用 ====================
+const ruleFormRef = ref<FormInstance>(); // 表单引用
+
+/**
+ * 提交数据(新增/编辑)
+ * 验证表单,通过后调用相应的API接口
+ */
+const handleSubmit = (submitType?: string) => {
+  // 验证表单
+  ruleFormRef.value!.validate(async (valid: boolean) => {
+    if (!valid) return;
+
+    // 组装提交参数
+    let params: any = { ...voucherModel.value };
+
+    // 处理有效期
+    if (params.validityPeriodType === 0) {
+      params.validityPeriodStr = `用户购买${params.validityDays}天内有效`;
+    } else {
+      params.validityPeriodStr = params.validityPeriod.join(",");
+    }
+
+    // 处理不可用日期
+    if (params.unavailableDateType === 1) {
+      params.unavailableDateValue = params.unavailableWeekdays.join(",") + ";" + params.unavailableHolidays.join(",");
+    } else if (params.unavailableDateType === 2) {
+      params.unavailableDateValue = dates.value.map((subArray: any) => subArray.join(",")).join(";");
+    }
+
+    // 处理使用时间
+    if (params.usageTime && params.usageTime.length === 2) {
+      params.usageStartTime = params.usageTime[0];
+      params.usageEndTime = params.usageTime[1];
+    }
+
+    console.log("提交参数:", params);
+
+    // TODO: 调用API保存数据
+    // if (submitType === 'draft') {
+    //   await saveVoucherDraft(params);
+    // } else {
+    //   await saveVoucher(params);
+    // }
+
+    if (submitType === "draft") {
+      ElMessage.success("草稿保存成功");
+    } else {
+      ElMessage.success("代金券创建成功");
+      router.go(-1);
+    }
+  });
+};
+
+// ==================== 工具函数 ====================
+
+/**
+ * 禁用开始售卖时间的日期
+ * 不能选择早于当前时间的日期
+ */
+const disabledStartDate = (time: Date) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  return time.getTime() < today.getTime();
+};
+
+/**
+ * 禁用结束售卖时间的日期
+ * 不能选择早于当前时间的日期,也不能选择早于或等于开始售卖时间的日期
+ */
+const disabledEndDate = (time: Date) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  if (time.getTime() < today.getTime()) {
+    return true;
+  }
+  if (voucherModel.value.startSellingTime) {
+    const startDate = new Date(voucherModel.value.startSellingTime);
+    startDate.setHours(0, 0, 0, 0);
+    return time.getTime() <= startDate.getTime();
+  }
+  return false;
+};
+
+/**
+ * 禁用使用时间的日期
+ */
+const disabledUsageDate = (time: Date) => {
+  return false; // 可以根据需要添加限制
+};
+
+/**
+ * 禁用有效期时间段的日期
+ */
+const disabledValidityDate = (time: Date) => {
+  return false; // 可以根据需要添加限制
+};
+
+/**
+ * 禁用自定义不可用日期的日期
+ */
+const disabledCustomUnavailableDate = (time: Date) => {
+  return false; // 可以根据需要添加限制
+};
+</script>
+
+<style scoped lang="scss">
+/* 页面容器 */
+.table-box {
+  display: flex;
+  flex-direction: column;
+  height: auto !important;
+  min-height: 100%;
+}
+
+/* 头部区域 */
+.header {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+  border-bottom: 1px solid #e4e7ed;
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 20px;
+  width: 98%;
+  margin: 20px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+  }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+  }
+}
+
+/* 模块容器 */
+.model {
+  margin-bottom: 50px;
+}
+
+/* 表单容器 */
+.formBox {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  min-height: 100%;
+}
+
+/* 底部按钮容器 - 居中显示 */
+.button-container {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  justify-content: center;
+  padding: 20px 0;
+  margin-top: 20px;
+}
+
+/* 日期选择器容器 */
+.date-picker-container {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  width: 100%;
+}
+
+/* 添加日期按钮 */
+.add-date-btn {
+  width: fit-content;
+  margin-bottom: 8px;
+}
+
+/* 日期项容器 */
+.date-item {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  padding: 8px;
+  border-radius: 4px;
+  transition: background-color 0.3s;
+  &:hover {
+    background-color: #ecf5ff;
+  }
+}
+
+/* 日期选择器 */
+.date-item .date-picker {
+  flex: 1;
+  max-width: 500px;
+}
+
+/* 删除按钮 */
+.date-item .delete-btn {
+  flex-shrink: 0;
+}
+
+/* 有效期天数容器 */
+.expiration-date-container {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  width: fit-content;
+  padding: 8px 12px;
+  border-radius: 4px;
+}
+
+/* 有效期标签文字 */
+.expiration-label {
+  font-size: 14px;
+  color: #606266;
+  white-space: nowrap;
+}
+
+/* 有效期输入框 */
+.expiration-input {
+  width: 150px;
+}
+
+/* 不可用日期容器 */
+.unavailable-dates-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  width: 100%;
+}
+
+/* 日期选择区块 */
+.date-select-section {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+/* 区块标题 */
+.section-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: #606266;
+}
+
+/* 按钮组 */
+.button-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+/* 日期选择按钮 */
+.date-select-btn {
+  min-width: 80px;
+  height: 36px;
+  padding: 8px 16px;
+  margin: 0;
+  font-size: 14px;
+  border-radius: 4px;
+  transition: all 0.3s;
+}
+</style>