Kaynağa Gözat

Merge remote-tracking branch 'origin/development' into development

spy 4 hafta önce
ebeveyn
işleme
b6c2031c7e
34 değiştirilmiş dosya ile 6532 ekleme ve 941 silme
  1. 30 1
      src/api/modules/homeEntry.ts
  2. 7 13
      src/api/modules/voucherManagement.ts
  3. BIN
      src/assets/financial/financeBg.png
  4. BIN
      src/assets/financial/gold.png
  5. 61 0
      src/assets/json/authMenuList.json
  6. 94 36
      src/layouts/components/Header/components/InfoDialog.vue
  7. 40 1
      src/layouts/components/Header/components/Message.vue
  8. 284 5
      src/layouts/components/Header/components/PasswordDialog.vue
  9. 63 0
      src/utils/eleValidate.ts
  10. 3 4
      src/views/couponManagement/index.vue
  11. 0 93
      src/views/financialManagement/detail.vue
  12. 162 173
      src/views/financialManagement/index.vue
  13. 497 0
      src/views/financialManagement/realName.vue
  14. 20 26
      src/views/groupPackageManagement/detail.vue
  15. 7 8
      src/views/groupPackageManagement/index.vue
  16. 164 24
      src/views/groupPackageManagement/newGroup.vue
  17. 34 7
      src/views/home/components/go-examine.vue
  18. 302 89
      src/views/home/components/go-flow.vue
  19. 54 9
      src/views/home/index.vue
  20. 124 20
      src/views/home/notice.vue
  21. 5 2
      src/views/home/userInfo.vue
  22. 9 8
      src/views/licenseManagement/businessLicense.vue
  23. 9 8
      src/views/licenseManagement/foodBusinessLicense.vue
  24. 7 0
      src/views/login/index.vue
  25. 241 108
      src/views/orderManagement/detail.vue
  26. 15 15
      src/views/orderManagement/index.vue
  27. 333 0
      src/views/ticketManagement/couponDetail.vue
  28. 524 0
      src/views/ticketManagement/detail.vue
  29. 640 0
      src/views/ticketManagement/index.vue
  30. 426 0
      src/views/ticketManagement/newCoupon.vue
  31. 1232 0
      src/views/ticketManagement/newVoucher.vue
  32. 498 65
      src/views/voucherManagement/detail.vue
  33. 148 47
      src/views/voucherManagement/index.vue
  34. 499 179
      src/views/voucherManagement/newVoucher.vue

+ 30 - 1
src/api/modules/homeEntry.ts

@@ -12,7 +12,7 @@ export const applyStore = params => {
   return http.post(PORT_NONE + `/storeManage/applyStore`, params);
 };
 
-//用户信息
+//用户信息查询
 export const getMerchantByPhone = params => {
   return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/merchantUser/getMerchantByPhone`, params);
 };
@@ -21,6 +21,35 @@ export const getMerchantByPhone = params => {
 export const getNoticeList = params => {
   return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/notice/getNoticeList`, params);
 };
+//通知列表  一键已读
+export const markAllNoticesAsRead = params => {
+  return http.post(PORT_NONE + `/notice/markAllNoticesAsRead`, params);
+};
+//今日订单量
+export const getTodayOrderCount = params => {
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/storeManage/getTodayOrderCount`, params);
+};
+//今日预计收益
+export const getTodayIncome = params => {
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/storeManage/getTodayIncome`, params);
+};
+
+//验券
+export const verifyOrder = params => {
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/couponManage/verifyOrder`, params);
+};
+export const verifyCoupon = params => {
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/couponManage/verifyCoupon`, params);
+};
+//保存用户信息
+export const updateMerchantUserInfo = params => {
+  return http.post(PORT_NONE + `/merchantUser/updateMerchantUserInfo`, params);
+};
+
+//财务   可提现金额
+export const getAccountBalance = params => {
+  return http.post(PORT_NONE + `/incomeManage/getAccountBalance`, params);
+};
 export const getUserByPhone = params => {
   return http.get(PORT_NONE + `/merchantUser/getMerchantByPhone`, params);
 };

+ 7 - 13
src/api/modules/voucherManagement.ts

@@ -3,29 +3,23 @@ import { PORT_NONE } from "@/api/config/servicePort";
 import http from "@/api";
 
 export const getThaliList = params => {
-  return http.get<ResPage<StoreUser.ResStoreUserList>>(PORT_NONE + `/PcGroupBuy/getThaliList`, params);
+  return http.get<ResPage<StoreUser.ResStoreUserList>>(PORT_NONE + `/couponPlatform/getCouponList`, params);
 };
 export const delThaliById = (params: { id: string }) => {
-  return http.get(PORT_NONE + `/PcGroupBuy/deleteThali`, params);
+  return http.get(PORT_NONE + `/couponPlatform/deleteCoupon`, params);
 };
 export const updateStatus = params => {
-  return http.get(PORT_NONE + `/PcGroupBuy/updateStatus1`, params);
+  return http.get(PORT_NONE + `/couponPlatform/updateCoupon`, params);
 };
 export const updateNum = params => {
-  return http.get(PORT_NONE + `/PcGroupBuy/updateNum11`, params);
+  return http.get(PORT_NONE + `/couponPlatform/updateCouponSingleQty`, 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);
-};
-export const getMenuByStoreId = params => {
-  return http.get(PORT_NONE + `/menuPlatform/getMenuByStoreId`, params);
+  return http.get(PORT_NONE + `/couponPlatform/getCouponDetail`, params);
 };
 export const getHolidayList = (params: any) => {
   return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/couponPlatform/getHolidayList`, params);
 };
-export const getStoreDetail = (params: StoreUser.ReqUserParams) => {
-  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/store/info/getStoreDetail`, params);
-};

BIN
src/assets/financial/financeBg.png


BIN
src/assets/financial/gold.png


+ 61 - 0
src/assets/json/authMenuList.json

@@ -122,6 +122,67 @@
       ]
     },
     {
+      "path": "/ticketManagement",
+      "name": "ticketManagement",
+      "component": "/ticketManagement/index",
+      "meta": {
+        "icon": "Money",
+        "title": "券管理",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": false
+      },
+      "children": [
+        {
+          "path": "/ticketManagement/newVoucher",
+          "name": "ticketManagementNewVoucher",
+          "component": "/ticketManagement/newVoucher",
+          "meta": {
+            "icon": "Menu",
+            "title": "新建代金券",
+            "activeMenu": "/ticketManagement",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/ticketManagement/newCoupon",
+          "name": "ticketManagementNewCoupon",
+          "component": "/ticketManagement/newCoupon",
+          "meta": {
+            "icon": "Menu",
+            "title": "新建优惠券",
+            "activeMenu": "/ticketManagement",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/ticketManagement/detail",
+          "name": "ticketManagementDetail",
+          "component": "/ticketManagement/detail",
+          "meta": {
+            "icon": "Menu",
+            "title": "券管理详情",
+            "activeMenu": "/ticketManagement",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        }
+      ]
+    },
+    {
       "path": "/voucherManagement",
       "name": "voucherManagement",
       "component": "/voucherManagement/index",

+ 94 - 36
src/layouts/components/Header/components/InfoDialog.vue

@@ -1,25 +1,24 @@
 <template>
   <el-dialog v-model="dialogVisible" title="个人信息" width="500px" draggable>
     <div class="form-wrap">
-      <el-form :model="form" :rules="rules" ref="formRef" label-width="72px">
+      <el-form :model="form" label-width="72px">
         <el-form-item label="头像">
           <el-upload
-            class="avatar-uploader"
-            action="#"
+            v-model:file-list="form.avatarList"
+            :http-request="handleHttpUpload"
             list-type="picture-card"
             :limit="1"
             :on-exceed="onExceed"
-            v-model:file-list="form.avatarList"
           >
             <el-icon><Plus /></el-icon>
           </el-upload>
         </el-form-item>
 
-        <el-form-item label="昵称" prop="nickname">
+        <el-form-item label="昵称">
           <el-input v-model="form.nickname" placeholder="请输入昵称" />
         </el-form-item>
 
-        <el-form-item label="简介" prop="intro">
+        <el-form-item label="简介">
           <el-input v-model="form.intro" type="textarea" :rows="6" placeholder="请输入简介" />
         </el-form-item>
       </el-form>
@@ -27,53 +26,112 @@
     <template #footer>
       <span class="dialog-footer">
         <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="dialogVisible = false">确认</el-button>
+        <el-button type="primary" @click="getSaveUserInfo()">确认</el-button>
       </span>
     </template>
   </el-dialog>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from "vue";
-import { ElMessage, type FormInstance, type FormRules } from "element-plus";
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, type FormInstance, type UploadUserFile, UploadRequestOptions } from "element-plus";
 import { Plus } from "@element-plus/icons-vue";
-import { getMerchantByPhone } from "@/api/modules/homeEntry.ts";
+import { getMerchantByPhone, updateMerchantUserInfo } from "@/api/modules/homeEntry.ts";
+import { uploadImg } from "@/api/modules/newLoginApi.ts";
+import { localGet } from "@/utils/index";
+import defaultAvatar from "@/assets/images/avatar.gif";
+import { head } from "lodash";
 
+const userInfo = localGet("geeker-user")?.userInfo || {};
 const dialogVisible = ref(false);
-const openDialog = async () => {
-  const res = await getMerchantByPhone({ phone: "15242687180" });
-  console.log(res);
-  dialogVisible.value = true;
-};
 
-defineExpose({ openDialog });
-
-const formRef = ref<FormInstance>();
+let headImg1 = ref("");
+const userId = ref<string | number>(""); // 保存用户ID
 const form = reactive({
-  avatarList: [] as any[],
-  nickname: "重庆老火锅",
-  intro: "这——家优秀的火锅店"
+  avatarList: [] as UploadUserFile[],
+  nickname: "",
+  intro: ""
 });
 
-const rules: FormRules = {
-  nickname: [{ required: true, message: "请输入昵称", trigger: "blur" }],
-  intro: [{ required: true, message: "请输入简介", trigger: "blur" }]
+const getUserInfo = async () => {
+  const res: any = await getMerchantByPhone({ phone: userInfo.phone });
+  if (res.code == 200) {
+    // 保存用户ID
+    userId.value = res.data.id || res.data.userId || "";
+    form.nickname = res.data.nickName || "";
+    form.intro = res.data.accountBlurb || "";
+    // 设置头像
+    const headImg = res.data.headImg;
+    if (headImg && headImg !== null && headImg !== "") {
+      // 如果有头像,使用用户头像
+      form.avatarList = [
+        {
+          name: "avatar",
+          url: headImg,
+          uid: Date.now()
+        } as UploadUserFile
+      ];
+    } else {
+      // 如果 headImg 为 null 或空,使用默认头像
+      form.avatarList = [
+        {
+          name: "avatar",
+          url: defaultAvatar,
+          uid: Date.now()
+        } as UploadUserFile
+      ];
+    }
+  }
 };
-
-const onExceed = () => {
-  ElMessage.warning("仅允许上传一张头像");
+const openDialog = async () => {
+  dialogVisible.value = true;
+  getUserInfo();
+};
+//文件上传
+const handleHttpUpload = async (options: UploadRequestOptions) => {
+  let formData = new FormData();
+  formData.append("file", options.file);
+  try {
+    const res: any = await uploadImg(formData);
+    // 上传成功,将URL保存到文件对象中
+    const fileUrl = res?.data?.fileUrl || res?.data?.[0] || res?.fileUrl;
+    if (fileUrl) {
+      headImg1.value = fileUrl;
+      // 调用 onSuccess 回调,传入响应数据
+      options.onSuccess({ fileUrl });
+    } else {
+      throw new Error("上传失败:未获取到文件URL");
+    }
+  } catch (error) {
+    options.onError(error as any);
+    ElMessage.error("文件上传失败,请重试");
+  }
 };
+const getSaveUserInfo = async () => {
+  try {
+    const res: any = await updateMerchantUserInfo({
+      id: userId.value,
+      nickName: form.nickname,
+      headImg: headImg1.value ? headImg1.value : "",
+      accountBlurb: form.intro
+    });
 
-const handleBack = () => {
-  history.back();
+    if (res.code == 200) {
+      ElMessage.success(res.msg || "保存成功");
+      dialogVisible.value = false;
+      // 可以刷新用户信息
+      getUserInfo();
+    } else {
+      ElMessage.error(res.msg || "保存失败,请重试");
+    }
+  } catch (error: any) {
+    ElMessage.error(error?.msg || error?.message || "保存失败,请重试");
+  }
 };
 
-const handleSave = async () => {
-  if (!formRef.value) return;
-  await formRef.value.validate(valid => {
-    if (!valid) return;
-    // TODO: 接口保存
-    ElMessage.success("保存成功");
-  });
+defineExpose({ openDialog });
+
+const onExceed = () => {
+  ElMessage.warning("仅允许上传一张头像");
 };
 </script>

+ 40 - 1
src/layouts/components/Header/components/Message.vue

@@ -1,14 +1,53 @@
 <template>
   <div class="message">
-    <el-badge :value="0" class="item">
+    <el-badge :value="unreadCount > 0 ? unreadCount : undefined" class="item">
       <i :class="'iconfont icon-xiaoxi'" class="toolBar-icon" @click="getMessage" />
     </el-badge>
   </div>
 </template>
 <script setup lang="ts">
+import { ref, onMounted, onBeforeUnmount } from "vue";
 import { useRouter } from "vue-router";
+import { getNoticeList } from "@/api/modules/homeEntry";
+import { localGet } from "@/utils/index";
+import mittBus from "@/utils/mittBus";
+
 const router = useRouter();
+const unreadCount = ref<number>(0);
+
+// 获取未读消息数量
+const getUnreadCount = async () => {
+  try {
+    const receiverId = localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
+    if (!receiverId) return;
+
+    const res: any = await getNoticeList({ pageNum: 1, pageSize: 100, receiverId });
+    if (res.code == "200") {
+      const noticeList = res.data?.records || res.data?.list || [];
+      // 计算未读消息数量
+      unreadCount.value = noticeList.filter((item: any) => !item.isRead).length;
+    }
+  } catch (error) {
+    console.error("获取消息列表失败:", error);
+  }
+};
+
 const getMessage = () => {
   router.push(`/home/notice`);
 };
+
+onMounted(() => {
+  // 初始化获取未读数量
+  getUnreadCount();
+
+  // 监听消息更新事件,当消息页面有操作时(标记已读等)更新未读数量
+  mittBus.on("updateUnreadCount", () => {
+    getUnreadCount();
+  });
+});
+
+onBeforeUnmount(() => {
+  // 移除事件监听
+  mittBus.off("updateUnreadCount");
+});
 </script>

+ 284 - 5
src/layouts/components/Header/components/PasswordDialog.vue

@@ -1,22 +1,301 @@
 <template>
-  <el-dialog v-model="dialogVisible" title="修改密码" width="500px" draggable>
-    <span>This is Password</span>
+  <!-- 修改密码对话框 -->
+  <el-dialog v-model="dialogVisible" title="修改密码" width="500px" draggable :close-on-click-modal="false">
+    <el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-width="100px" label-position="left">
+      <el-form-item label="输入原密码" prop="oldPassword">
+        <el-input v-model="passwordForm.oldPassword" type="password" placeholder="请输入原密码" show-password clearable />
+      </el-form-item>
+
+      <el-form-item label="输入新密码" prop="newPassword">
+        <el-input
+          v-model="passwordForm.newPassword"
+          type="password"
+          placeholder="请输入6-16位的密码,包含字母和数字"
+          show-password
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="确认密码" prop="confirmPassword">
+        <div style="display: flex; align-items: center; width: 100%">
+          <el-input
+            v-model="passwordForm.confirmPassword"
+            type="password"
+            placeholder="请确认密码"
+            show-password
+            clearable
+            style="flex: 1"
+          />
+          <el-link type="primary" :underline="false" style="margin-left: 12px" @click="handleForgotPassword"> 忘记密码 </el-link>
+        </div>
+      </el-form-item>
+    </el-form>
+
     <template #footer>
       <span class="dialog-footer">
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="dialogVisible = false">确认</el-button>
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" :loading="loading" @click="handleConfirm">确定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+
+  <!-- 忘记密码对话框 -->
+  <el-dialog v-model="forgotPasswordVisible" title="忘记密码" width="500px" draggable :close-on-click-modal="false">
+    <el-form
+      ref="forgotPasswordFormRef"
+      :model="forgotPasswordForm"
+      :rules="forgotPasswordRules"
+      label-width="100px"
+      label-position="left"
+    >
+      <el-form-item label="手机号码">
+        <span>{{ maskedPhone }}</span>
+      </el-form-item>
+
+      <el-form-item label="" prop="verificationCode">
+        <div style="display: flex; gap: 12px; width: 100%">
+          <el-input v-model="forgotPasswordForm.verificationCode" placeholder="请输入" clearable style="flex: 1" />
+          <el-button type="primary" :disabled="codeCountdown > 0" @click="sendLoginCode">
+            {{ codeCountdown > 0 ? `${codeCountdown}秒后重新获取` : "获取验证码" }}
+          </el-button>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="输入新密码" prop="newPassword">
+        <el-input
+          v-model="forgotPasswordForm.newPassword"
+          type="password"
+          placeholder="请输入6-16位的密码,包含字母和数字"
+          show-password
+          clearable
+        />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="handleForgotPasswordCancel">取消</el-button>
+        <el-button type="primary" :loading="forgotPasswordLoading" @click="handleForgotPasswordConfirm">确定</el-button>
       </span>
     </template>
   </el-dialog>
 </template>
 
 <script setup lang="ts">
-import { ref } from "vue";
+import { ref, reactive, computed, onBeforeUnmount } from "vue";
+import { ElMessage, type FormInstance, type FormRules } from "element-plus";
+import { validatePassword, validateConfirmPassword } from "@/utils/eleValidate";
+import { forgetPassword, getPhoneCode } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils/index";
 
 const dialogVisible = ref(false);
+const loading = ref(false);
+const passwordFormRef = ref<FormInstance>();
+
+const passwordForm = reactive({
+  oldPassword: "",
+  newPassword: "",
+  confirmPassword: ""
+});
+
+// 忘记密码对话框
+const forgotPasswordVisible = ref(false);
+const forgotPasswordLoading = ref(false);
+const forgotPasswordFormRef = ref<FormInstance>();
+const codeCountdown = ref(0);
+let countdownTimer: NodeJS.Timeout | null = null;
+
+// 获取用户手机号
+const userPhone = computed(() => {
+  return localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
+});
+// 掩码显示手机号
+const maskedPhone = computed(() => {
+  if (!userPhone.value || userPhone.value.length !== 11) {
+    return "";
+  }
+  return userPhone.value.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
+});
+
+const forgotPasswordForm = reactive({
+  verificationCode: "",
+  newPassword: ""
+});
+
+// 表单验证规则
+const passwordRules: FormRules = {
+  oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
+  newPassword: [{ required: true, validator: validatePassword, trigger: "blur" }],
+  confirmPassword: [
+    {
+      required: true,
+      validator: validateConfirmPassword(() => passwordForm.newPassword),
+      trigger: "blur"
+    }
+  ]
+};
+
+// 忘记密码表单验证规则
+const forgotPasswordRules: FormRules = {
+  verificationCode: [{ required: true, message: "请输入验证码", trigger: "blur" }],
+  newPassword: [{ required: true, validator: validatePassword, trigger: "blur" }]
+};
+
+// 打开对话框
 const openDialog = () => {
   dialogVisible.value = true;
+  // 重置表单
+  if (passwordFormRef.value) {
+    passwordFormRef.value.resetFields();
+  }
 };
 
+// 取消
+const handleCancel = () => {
+  dialogVisible.value = false;
+  if (passwordFormRef.value) {
+    passwordFormRef.value.resetFields();
+  }
+};
+
+// 确定
+const handleConfirm = async () => {
+  if (!passwordFormRef.value) return;
+
+  await passwordFormRef.value.validate(async valid => {
+    if (!valid) return;
+
+    loading.value = true;
+    try {
+      const res: any = await forgetPassword({
+        type: 1,
+        phone: userPhone.value,
+        verificationCode: "",
+        oldPassword: passwordForm.oldPassword,
+        newPassword: passwordForm.newPassword,
+        confirmNewPassword: passwordForm.newPassword
+      });
+
+      if (res.code === 200) {
+        ElMessage.success(res.msg || "密码修改成功");
+        dialogVisible.value = false;
+        if (passwordFormRef.value) {
+          passwordFormRef.value.resetFields();
+        }
+      } else {
+        ElMessage.error(res.msg || "密码修改失败,请重试");
+      }
+    } catch (error: any) {
+      ElMessage.error(error?.msg || error?.message || "密码修改失败,请重试");
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+const handleForgotPassword = () => {
+  dialogVisible.value = false;
+  forgotPasswordVisible.value = true;
+  // 重置表单
+  if (forgotPasswordFormRef.value) {
+    forgotPasswordFormRef.value.resetFields();
+  }
+  // 重置倒计时
+  codeCountdown.value = 0;
+  if (countdownTimer) {
+    clearInterval(countdownTimer);
+    countdownTimer = null;
+  }
+};
+// ----------------------忘记密码-------------------------
+// 发送短信验证码
+const sendLoginCode = async () => {
+  let phoneCode: any = await getPhoneCode({ phone: userPhone.value, appType: "2", businessType: "0" });
+  if (phoneCode.code === 200) {
+    ElMessage.success("验证码已发送");
+    // 开始倒计时
+    codeCountdown.value = 60;
+    countdownTimer = setInterval(() => {
+      codeCountdown.value--;
+      if (codeCountdown.value <= 0) {
+        if (countdownTimer) {
+          clearInterval(countdownTimer);
+          countdownTimer = null;
+        }
+      }
+    }, 1000);
+  }
+};
+
+// 忘记密码取消
+const handleForgotPasswordCancel = () => {
+  forgotPasswordVisible.value = false;
+  if (forgotPasswordFormRef.value) {
+    forgotPasswordFormRef.value.resetFields();
+  }
+  // 清除倒计时
+  if (countdownTimer) {
+    clearInterval(countdownTimer);
+    countdownTimer = null;
+  }
+  codeCountdown.value = 0;
+};
+
+// 忘记密码确定
+const handleForgotPasswordConfirm = async () => {
+  if (!forgotPasswordFormRef.value) return;
+
+  await forgotPasswordFormRef.value.validate(async valid => {
+    if (!valid) return;
+
+    forgotPasswordLoading.value = true;
+
+    try {
+      const res: any = await forgetPassword({
+        phone: userPhone.value,
+        verificationCode: forgotPasswordForm.verificationCode,
+        type: "0",
+        newPassword: forgotPasswordForm.newPassword
+      });
+
+      if (res.code === 200) {
+        ElMessage.success(res.msg || "密码修改成功");
+        forgotPasswordVisible.value = false;
+        if (forgotPasswordFormRef.value) {
+          forgotPasswordFormRef.value.resetFields();
+        }
+        // 清除倒计时
+        if (countdownTimer) {
+          clearInterval(countdownTimer);
+          countdownTimer = null;
+        }
+        codeCountdown.value = 0;
+      } else {
+        ElMessage.error(res.msg || "密码修改失败,请重试");
+      }
+    } catch (error: any) {
+      ElMessage.error(error?.msg || error?.message || "密码修改失败,请重试");
+    } finally {
+      forgotPasswordLoading.value = false;
+    }
+  });
+};
+
+// 组件卸载时清除定时器
+onBeforeUnmount(() => {
+  if (countdownTimer) {
+    clearInterval(countdownTimer);
+    countdownTimer = null;
+  }
+});
+
 defineExpose({ openDialog });
 </script>
+
+<style scoped lang="scss">
+.dialog-footer {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+}
+</style>

+ 63 - 0
src/utils/eleValidate.ts

@@ -441,3 +441,66 @@ export function validateDateListArray(
 export function validatePositiveIntegerForNumber(errorMessage: string = "必须为正整数") {
   return validatePositiveInteger(errorMessage, { required: false, checkLeadingZero: false });
 }
+
+/**
+ * 验证价格格式(整数部分最多6位,小数部分最多2位)
+ * @param errorMessage - 错误提示信息,默认为"整数部分最多6位,小数部分最多2位"
+ * @returns 验证函数
+ */
+export function validatePriceFormat(errorMessage: string = "整数部分最多6位,小数部分最多2位") {
+  return (rule: any, value: any, callback: any) => {
+    if (!value || value.toString().trim() === "") {
+      callback();
+      return;
+    }
+    const strValue = value.toString().trim();
+    // 验证格式:整数部分最多6位,小数部分最多2位
+    const regex = /^\d{1,6}(\.\d{1,2})?$/;
+    if (!regex.test(strValue)) {
+      callback(new Error(errorMessage));
+      return;
+    }
+    callback();
+  };
+}
+
+/**
+ * 验证价格比较(第一个价格必须大于等于第二个价格)
+ * @param getFirstPrice - 获取第一个价格的函数(通常是抵扣价格)
+ * @param getSecondPrice - 获取第二个价格的函数(通常是售卖价格)
+ * @param errorMessage - 错误提示信息,默认为"抵扣价格不能低于售卖价格"
+ * @returns 验证函数
+ */
+export function validatePriceComparison(
+  getFirstPrice: () => any,
+  getSecondPrice: () => any,
+  errorMessage: string = "抵扣价格不能低于售卖价格"
+) {
+  return (rule: any, value: any, callback: any) => {
+    const firstPrice = getFirstPrice();
+    const secondPrice = getSecondPrice();
+
+    // 如果任一价格为空,不进行验证
+    if (!firstPrice || !secondPrice || firstPrice.toString().trim() === "" || secondPrice.toString().trim() === "") {
+      callback();
+      return;
+    }
+
+    const firstNum = Number(firstPrice);
+    const secondNum = Number(secondPrice);
+
+    // 如果转换失败,不进行验证(由其他验证规则处理)
+    if (isNaN(firstNum) || isNaN(secondNum)) {
+      callback();
+      return;
+    }
+
+    // 验证第一个价格必须大于等于第二个价格
+    if (firstNum < secondNum) {
+      callback(new Error(errorMessage));
+      return;
+    }
+
+    callback();
+  };
+}

+ 3 - 4
src/views/couponManagement/index.vue

@@ -211,18 +211,17 @@ const currentAuditStatus = computed(() => {
 // 控制 el-tabs 是否显示:当审核状态为空或审核通过时显示
 const showTabs = computed(() => {
   const status = currentAuditStatus.value;
-  return !status || status === "-2";
+  return !status || status === "1";
 });
 
 // 根据审核状态过滤 tabOptions:如果审核状态为空,只显示草稿;审核通过时,显示除草稿外的所有标签页
 const filteredTabOptions = computed(() => {
   const status = currentAuditStatus.value;
   if (!status) {
-    // 审核状态为空时,只显示草稿
     return allTabOptions;
-  } else if (status === "-2") {
+  } else if (status == "1") {
     // 审核通过时,显示除草稿外的所有标签页
-    return allTabOptions.filter(tab => tab.name !== "1");
+    return allTabOptions.filter(tab => tab.name != "0");
   }
   return [];
 });

+ 0 - 93
src/views/financialManagement/detail.vue

@@ -1,93 +0,0 @@
-<template>
-  <div class="card content-box">
-    <el-form :model="formData" label-width="140px">
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="店铺名称 :">
-            <span>{{ formData.storeName }}</span>
-          </el-form-item>
-          <el-form-item label="名称 :">
-            <span>{{ formData.name }}</span>
-          </el-form-item>
-          <el-form-item label="描述 :">
-            <span>{{ formData.description }}</span>
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="状态:">
-            <span>{{ getStatusName(formData.status) }}</span>
-          </el-form-item>
-          <el-form-item label="拒绝原因:" v-if="formData.status === '2'">
-            <span>{{ formData.rejectionReason }}</span>
-          </el-form-item>
-        </el-col>
-      </el-row>
-      <el-row class="text-center" style="margin-top: 20px">
-        <el-col :span="24">
-          <el-button type="primary" @click="goBack"> 确定 </el-button>
-        </el-col>
-      </el-row>
-    </el-form>
-  </div>
-</template>
-
-<script setup lang="tsx" name="financialManagementDetail">
-import { ref, onMounted } from "vue";
-import { useRoute, useRouter } from "vue-router";
-import { ElMessage } from "element-plus";
-import { getStaffConfigDeatail } from "@/api/modules/staffConfig";
-
-const route = useRoute();
-const router = useRouter();
-
-const formData = ref({});
-
-const id = ref((route.query.id as string) || "");
-
-const getStatusName = (status: string) => {
-  switch (status) {
-    case "0":
-      return "待审核";
-    case "1":
-      return "审核通过";
-    case "2":
-      return "审核拒绝";
-    default:
-      return "未知状态";
-  }
-};
-
-onMounted(async () => {
-  await initData();
-});
-
-const initData = async () => {
-  if (id.value) {
-    try {
-      const response = await getStaffConfigDeatail({ id: id.value });
-      if (response.code === 200) {
-        formData.value = response.data;
-      }
-    } catch (error) {
-      ElMessage.error("获取详情失败");
-    }
-  }
-};
-
-const goBack = () => {
-  router.go(-1);
-};
-</script>
-
-<style lang="scss" scoped>
-.el-form {
-  width: 100%;
-  .text-center {
-    text-align: center;
-  }
-}
-.el-col {
-  box-sizing: border-box;
-  padding-right: 10px;
-}
-</style>

+ 162 - 173
src/views/financialManagement/index.vue

@@ -1,207 +1,196 @@
 <template>
-  <div class="financial-dashboard">
-    <h3 class="title"><el-image :src="homeIcon" class="homeIcon" />账户总览</h3>
-    <el-row :gutter="16">
-      <el-col v-for="card in cards" :key="card.key" :span="5">
-        <div class="summary-card" :class="{ 'is-disabled': card.disabled }">
-          <div class="card-header">
-            <div class="header-left">
-              <span class="card-title">{{ card.title }}</span>
-              <el-tooltip v-if="card.tooltip" :content="card.tooltip" placement="top">
-                <el-icon class="info-icon">
-                  <QuestionFilled />
-                </el-icon>
-              </el-tooltip>
-            </div>
-            <el-icon v-if="card.showArrow" class="arrow-icon" @click="handleAction(card)">
-              <ArrowRight />
+  <div class="finance-page">
+    <!-- 今日收益 -->
+    <el-card class="summary-card" shadow="hover">
+      <div class="card-img">
+        <div class="summary-header">
+          <span class="title">今日收益1</span>
+          <el-tooltip content="今日收益为今天入账的金额" placement="top">
+            <el-icon class="question-icon">
+              <QuestionFilled />
             </el-icon>
+          </el-tooltip>
+        </div>
+        <div class="summary-body">
+          <el-image :src="gold" class="gold" />
+          <div class="summary-amount">
+            <span class="currency">¥</span>
+            <span class="amount">{{ formatCurrency(summaryData.todayIncome) }}</span>
+            <span class="unit">元</span>
           </div>
-          <div class="card-amount">¥{{ formatAmount(card.amount) }}</div>
-          <div class="card-button-wrapper">
-            <el-button
-              v-if="card.buttonText"
-              class="card-button"
-              size="large"
-              :type="card.disabled ? 'default' : 'primary'"
-              :plain="card.disabled"
-              :disabled="card.disabled"
-              @click="handleAction(card)"
-            >
-              {{ card.buttonText }}
-            </el-button>
-          </div>
         </div>
-      </el-col>
-    </el-row>
+      </div>
+    </el-card>
+
+    <!-- 统计信息 -->
+    <div class="stat-cards">
+      <el-card v-for="stat in stats" :key="stat.key" class="stat-card" shadow="hover">
+        <div class="stat-header">
+          <span class="stat-title">{{ stat.title }}</span>
+          <el-tooltip :content="stat.tooltip" placement="top" v-if="stat.tooltip">
+            <el-icon class="question-icon">
+              <QuestionFilled />
+            </el-icon>
+          </el-tooltip>
+        </div>
+        <div class="stat-amount">
+          {{ formatCurrency(stat.amount) }}
+        </div>
+        <el-button type="primary" class="stat-btn" @click="handleAction(stat.key)">
+          {{ stat.buttonText }}
+        </el-button>
+      </el-card>
+    </div>
   </div>
 </template>
 
-<script setup lang="ts" name="financialManagement">
-import homeIcon from "../../assets/images/home-icon.png";
-
-import { computed } from "vue";
+<script setup lang="ts">
+import { ref, reactive, watch, onMounted } from "vue";
+import { QuestionFilled } from "@element-plus/icons-vue";
+import gold from "../../assets/financial/gold.png";
+import { getAccountBalance } from "@/api/modules/homeEntry";
+import { localGet } from "@/utils/index";
 import { useRouter } from "vue-router";
-import { ArrowRight, QuestionFilled } from "@element-plus/icons-vue";
-
-type FinancialCard = {
-  key: string;
-  title: string;
-  amount: number;
-  tooltip?: string;
-  buttonText?: string;
-  disabled?: boolean;
-  showArrow?: boolean;
-  route?: string;
-  action?: () => void;
-};
-
 const router = useRouter();
-
-const cards = computed<FinancialCard[]>(() => [
-  {
-    key: "todayIncome",
-    title: "今日收益",
-    amount: 0,
-    tooltip: "今日收益=今日已核销到账金额-技术服务费",
-    showArrow: true,
-    route: "/financialManagement/detail"
-  },
-  {
-    key: "withdrawable",
-    title: "可提现金额",
-    amount: 0,
-    tooltip: "收益产生后3天可提现,28天自动",
-    buttonText: "提现",
-    disabled: true
-  },
-  {
-    key: "arrivedAmount",
-    title: "已到账期金额",
-    amount: 0,
-    tooltip: "已完成入账的金额总计",
-    buttonText: "查看",
-    route: "/financialManagement/detail"
-  },
-  {
-    key: "pendingAmount",
-    title: "未到账期金额",
-    amount: 0,
-    tooltip: "正在处理中的预计入账金额",
-    buttonText: "查看",
-    route: "/financialManagement/detail"
+onMounted(() => {
+  fetchAccountBalance();
+});
+//可提现金额
+const fetchAccountBalance = async () => {
+  let param = {
+    storeId: 138
+  };
+  const res: any = await getAccountBalance(param as any);
+  if (res.code == 200) {
   }
-]);
+};
+const summaryData = {
+  todayIncome: 65411
+};
 
-const handleAction = (card: FinancialCard) => {
-  if (card.disabled) return;
-  if (card.action) {
-    card.action();
-    return;
-  }
-  if (card.route) {
-    router.push(card.route);
-  }
+const stats = [
+  { key: "withdraw", title: "可提现金额", amount: 932110, buttonText: "提现", tooltip: "可用于提现的金额" },
+  { key: "settled", title: "已到账期金额", amount: 932110, buttonText: "查看明细", tooltip: "已结算到账的金额" },
+  { key: "pending", title: "未到账期金额", amount: 932110, buttonText: "查看明细", tooltip: "正在结算中的金额" }
+];
+
+const formatCurrency = (value: number) => {
+  return value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
 };
 
-const formatAmount = (value: number) => {
-  return value.toLocaleString("zh-CN", { minimumFractionDigits: 0, maximumFractionDigits: 2 });
+const handleAction = (key: string) => {
+  switch (key) {
+    case "withdraw":
+      router.push("/financialManagement/realName");
+      break;
+    default:
+      console.log("查看明细");
+  }
 };
 </script>
 
 <style scoped lang="scss">
-.financial-dashboard {
-  box-sizing: border-box;
+.finance-page {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  min-height: calc(100vh - 100px);
+}
+.summary-card {
+  position: relative;
   width: 100%;
-  height: calc(100vh - 105px);
   overflow: hidden;
-  background: url("../../assets/images/home-bg.png") center center no-repeat;
-  background-size: cover;
-  .title {
+  border: none;
+  border-radius: 16px;
+  .card-img {
+    width: 100%;
+    height: 214px;
+    background: url("../../assets/financial/financeBg.png") no-repeat;
+    background-position: right;
+    background-size: contain;
+  }
+  .summary-header {
     display: flex;
+    gap: 8px;
     align-items: center;
-    height: 105px;
-    font-size: 20px;
-    font-weight: 600;
-    color: #ffffff;
-    background: linear-gradient(90deg, #6c8ff8 0%, rgb(255 255 255 / 0%) 100%);
-    .homeIcon {
-      width: 76px;
-      height: 76px;
-      margin-right: 8px;
-      margin-left: 25px;
+    margin-bottom: 12px;
+    font-size: 16px;
+    color: #333333;
+    .question-icon {
+      color: #9aa4c2;
+      cursor: pointer;
     }
-
-    border-radius: 10px;
   }
-  :deep(.el-row) {
+  .summary-body {
     display: flex;
-    margin: 0 !important;
+    gap: 24px;
+    align-items: center;
+    justify-content: center;
+    width: 757px;
+    height: 140px;
+    padding: 12px 0;
+    margin: auto;
+    font-weight: 600;
+    background: #6c8ff8;
+    border-radius: 74px;
+    box-shadow: 0 5px 0 0 #355ac8;
   }
-  :deep(.el-col) {
+  .summary-amount {
     display: flex;
-  }
-}
-.summary-card {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-  justify-content: space-between;
-  height: 100%;
-  min-height: 148px;
-  padding: 20px 90px;
-  background-color: #ffffff;
-  border: 1px solid #e5e6eb;
-  border-radius: 8px;
-  transition: box-shadow 0.2s;
-  &:hover {
-    box-shadow: 0 6px 12px rgb(31 41 55 / 10%);
-  }
-  &.is-disabled {
-    .card-button {
-      cursor: not-allowed;
+    gap: 8px;
+    align-items: baseline;
+    font-weight: 600;
+    .currency {
+      padding: 8px 16px;
+      font-size: 80px;
+      color: #ffffff;
+      border-radius: 999px 0 0 999px;
+    }
+    .amount {
+      font-size: 80px;
+      color: #ffffff;
+    }
+    .unit {
+      font-size: 16px;
+      color: #ffffff;
     }
   }
 }
-.card-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  .header-left {
-    display: inline-flex;
-    gap: 6px;
-    align-items: center;
+.stat-cards {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 16px;
+  .stat-card {
+    padding: 16px;
+    text-align: center;
+    border: none;
+    border-radius: 12px;
+    .stat-header {
+      display: inline-flex;
+      gap: 6px;
+      align-items: center;
+      margin-bottom: 16px;
+      font-size: 16px;
+      color: #222222;
+    }
+    .stat-amount {
+      margin-bottom: 20px;
+      font-size: 32px;
+      font-weight: 600;
+      color: #222222;
+    }
+    .stat-btn {
+      width: 120px;
+      background: #7f9dff;
+      border: none;
+      border-radius: 6px;
+    }
   }
 }
-.card-title {
-  font-size: 16px;
-  font-weight: 500;
-  color: #1d2129;
-}
-.card-amount {
-  font-size: 28px;
-  font-weight: 600;
-  line-height: 1.2;
-  color: #1d2129;
-}
-.card-button-wrapper {
-  display: flex;
-  align-items: flex-end;
-  min-height: 40px;
-}
-.card-button {
-  width: 100%;
-}
-.info-icon {
-  font-size: 16px;
-  color: #86909c;
-}
-.arrow-icon {
-  font-size: 18px;
-  color: #c9ccd3;
-  cursor: pointer;
-  &:hover {
-    color: #165dff;
+
+@media (width <= 1024px) {
+  .stat-cards {
+    grid-template-columns: repeat(1, 1fr);
   }
 }
 </style>

+ 497 - 0
src/views/financialManagement/realName.vue

@@ -0,0 +1,497 @@
+<template>
+  <div class="real-name-page">
+    <!-- 头部区域 -->
+    <div class="header-section">
+      <div class="header-content">
+        <div class="shop-info">
+          <el-image :src="homeIcon" class="homeIcon" />
+          <span class="shop-name">每天汪汪的店铺</span>
+        </div>
+        <div class="auth-status">
+          <span class="status-badge">未认证</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- 标签导航栏 -->
+    <div class="tabs-section">
+      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+        <el-tab-pane label="实名认证" name="realName" />
+        <el-tab-pane label="提现密码" name="withdrawPassword" />
+        <el-tab-pane label="收款账户" name="receivingAccount" />
+      </el-tabs>
+    </div>
+
+    <!-- 实名认证 -->
+    <div class="content-section" v-if="activeTab === 'realName'">
+      <div class="content-wrapper">
+        <el-button type="primary" class="auth-button" @click="handleGoAuth"> 去实名认证 </el-button>
+      </div>
+    </div>
+
+    <!-- 提现密码 -->
+    <div class="content-section" v-if="activeTab === 'withdrawPassword'">
+      <div class="content-wrapper">
+        <!-- <div class="password-form-wrapper">
+          <el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-width="120px" label-position="left">
+            <el-form-item label="设置提现密码" prop="password">
+              <el-input
+                v-model="passwordForm.password"
+                type="password"
+                placeholder="请输入6位提现密码"
+                clearable
+                maxlength="6"
+                show-password
+                class="password-input"
+              />
+            </el-form-item>
+            <el-form-item label="请确认密码" prop="confirmPassword">
+              <el-input
+                v-model="passwordForm.confirmPassword"
+                type="password"
+                placeholder="请输入6位提现密码"
+                clearable
+                maxlength="6"
+                show-password
+                class="password-input"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" class="submit-button" :loading="passwordLoading" @click="handlePasswordSubmit">
+                确认
+              </el-button>
+            </el-form-item>
+          </el-form>
+        </div> -->
+        <div class="password-form-wrapper-step">
+          <el-steps :active="currentStep" style="max-width: 600px">
+            <el-step v-for="(item, index) in passList" :key="index">
+              <template #title>
+                <div class="step-title-wrapper">
+                  <span class="step-title">{{ item.title }}</span>
+                </div>
+              </template>
+            </el-step>
+          </el-steps>
+          <el-form
+            ref="passwordFormRef"
+            :model="passwordForm"
+            :rules="passwordRules"
+            label-width="100px"
+            label-position="left"
+            class="form-wrapper"
+          >
+            <el-form-item label="验证原密码" prop="password">
+              <el-input
+                v-model="passwordForm.password"
+                type="password"
+                placeholder="请输入6位提现密码"
+                clearable
+                maxlength="6"
+                show-password
+                class="password-input"
+              />
+            </el-form-item>
+          </el-form>
+          <div class="forget-password">忘记密码</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 忘记密码对话框 -->
+    <el-dialog v-model="forgotPasswordVisible" title="忘记密码" width="500px" draggable :close-on-click-modal="false">
+      <el-form
+        ref="forgotPasswordFormRef"
+        :model="forgotPasswordForm"
+        :rules="forgotPasswordRules"
+        label-width="100px"
+        label-position="left"
+      >
+        <el-form-item label="手机号码">
+          <span>{{ maskedPhone }}</span>
+        </el-form-item>
+
+        <el-form-item label="" prop="verificationCode">
+          <div style="display: flex; gap: 12px; width: 100%">
+            <el-input v-model="forgotPasswordForm.verificationCode" placeholder="请输入" clearable style="flex: 1" />
+            <el-button type="primary" :disabled="codeCountdown > 0" @click="sendLoginCode">
+              {{ codeCountdown > 0 ? `${codeCountdown}秒后重新获取` : "获取验证码" }}
+            </el-button>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="输入新密码" prop="newPassword">
+          <el-input
+            v-model="forgotPasswordForm.newPassword"
+            type="password"
+            placeholder="请输入6-16位的密码,包含字母和数字"
+            show-password
+            clearable
+          />
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+
+    <!-- 实名认证对话框 -->
+    <el-dialog v-model="dialogVisible" title="实名认证" width="500px" :close-on-click-modal="false">
+      <el-form ref="authFormRef" :model="authForm" :rules="authRules" label-width="100px" label-position="left">
+        <el-form-item label="姓名" prop="name">
+          <el-input v-model="authForm.name" placeholder="请输入姓名" clearable />
+        </el-form-item>
+        <el-form-item label="身份证号码" prop="idCard">
+          <el-input v-model="authForm.idCard" placeholder="请输入身份证号码" clearable maxlength="18" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="handleCancel">取消</el-button>
+          <el-button type="primary" :loading="loading" @click="handleConfirm">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from "vue";
+import { useRouter } from "vue-router";
+import { ElMessage, type FormInstance, type FormRules } from "element-plus";
+import { localGet } from "@/utils/index";
+import homeIcon from "../../assets/images/home-icon.png";
+
+const router = useRouter();
+const activeTab = ref("realName");
+const dialogVisible = ref(false);
+const loading = ref(false);
+const authFormRef = ref<FormInstance>();
+const passwordFormRef = ref<FormInstance>();
+const passwordLoading = ref(false);
+const currentStep = ref(0);
+const forgotPasswordVisible = ref(false);
+const forgotPasswordFormRef = ref<FormInstance>();
+const forgotPasswordForm = reactive({
+  verificationCode: "",
+  newPassword: ""
+});
+// 忘记密码表单验证规则
+const forgotPasswordRules: FormRules = {
+  verificationCode: [{ required: true, message: "请输入验证码", trigger: "blur" }]
+};
+const passList = [
+  {
+    title: "验证原密码"
+  },
+  {
+    title: "设置新密码"
+  }
+];
+// 获取用户手机号
+const userPhone = computed(() => {
+  return localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
+});
+// 掩码显示手机号
+const maskedPhone = computed(() => {
+  if (!userPhone.value || userPhone.value.length !== 11) {
+    return "";
+  }
+  return userPhone.value.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
+});
+// 实名认证表单数据
+const authForm = reactive({
+  name: "",
+  idCard: ""
+});
+
+// 提现密码表单数据
+const passwordForm = reactive({
+  password: "",
+  confirmPassword: ""
+});
+
+// 实名认证表单验证规则
+const authRules = reactive<FormRules>({
+  name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
+  idCard: [
+    { required: true, message: "请输入身份证号码", trigger: "blur" },
+    {
+      pattern: /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/,
+      message: "请输入正确的身份证号码",
+      trigger: "blur"
+    }
+  ]
+});
+
+// 提现密码表单验证规则
+const passwordRules = reactive<FormRules>({
+  password: [
+    { required: true, message: "请输入6位提现密码", trigger: "blur" },
+    {
+      pattern: /^\d{6}$/,
+      message: "请输入6位数字密码",
+      trigger: "blur"
+    }
+  ],
+  confirmPassword: [
+    { required: true, message: "请确认密码", trigger: "blur" },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (value !== passwordForm.password) {
+          callback(new Error("两次输入的密码不一致"));
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur"
+    }
+  ]
+});
+
+const handleTabClick = (tab: any) => {
+  // 根据不同的标签页可以跳转到不同的页面或显示不同的内容
+  console.log("切换到标签:", tab.props.name);
+  // 这里可以根据需要实现路由跳转或内容切换
+};
+
+const handleGoAuth = () => {
+  // 打开实名认证对话框
+  dialogVisible.value = true;
+  // 重置表单
+  if (authFormRef.value) {
+    authFormRef.value.resetFields();
+  }
+};
+
+const handleCancel = () => {
+  dialogVisible.value = false;
+  // 重置表单
+  if (authFormRef.value) {
+    authFormRef.value.resetFields();
+  }
+};
+
+const handleConfirm = async () => {
+  if (!authFormRef.value) return;
+
+  // 表单验证
+  await authFormRef.value.validate(valid => {
+    if (valid) {
+      loading.value = true;
+      // 这里可以调用API提交实名认证信息
+      // 模拟API调用
+      setTimeout(() => {
+        loading.value = false;
+        ElMessage.success("实名认证提交成功");
+        dialogVisible.value = false;
+        // 重置表单
+        authFormRef.value?.resetFields();
+        // 这里可以更新认证状态或刷新页面数据
+      }, 1000);
+    }
+  });
+};
+
+const handlePasswordSubmit = async () => {
+  if (!passwordFormRef.value) return;
+
+  // 表单验证
+  await passwordFormRef.value.validate(valid => {
+    if (valid) {
+      passwordLoading.value = true;
+      // 这里可以调用API设置提现密码
+      // 模拟API调用
+      setTimeout(() => {
+        passwordLoading.value = false;
+        ElMessage.success("提现密码设置成功");
+        // 重置表单
+        passwordFormRef.value?.resetFields();
+      }, 1000);
+    }
+  });
+};
+</script>
+
+<style scoped lang="scss">
+.real-name-page {
+  display: flex;
+  flex-direction: column;
+  min-height: calc(100vh - 100px);
+  background-color: #f5f7fa;
+}
+
+// 头部区域
+.header-section {
+  padding: 16px 24px;
+  background: linear-gradient(90deg, #6c8ff8 0%, rgb(255 255 255 / 0%) 100%);
+  border-bottom: 1px solid #e4e7ed;
+  border-radius: 10px;
+  .header-content {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    max-width: 100%;
+    margin: 0 auto;
+    .shop-info {
+      display: flex;
+      gap: 12px;
+      align-items: center;
+      .homeIcon {
+        width: 76px;
+        height: 76px;
+        margin-right: 8px;
+        margin-left: 25px;
+      }
+      .shop-icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 32px;
+        height: 32px;
+        background-color: #ffffff;
+        border-radius: 6px;
+        box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
+      }
+      .shop-name {
+        font-size: 16px;
+        font-weight: 500;
+        color: #ffffff;
+      }
+    }
+    .auth-status {
+      .status-badge {
+        display: inline-block;
+        padding: 3px 20px;
+        font-size: 14px;
+        font-weight: 500;
+        color: #ffffff;
+        background: #f81515;
+        border-radius: 39px;
+      }
+    }
+  }
+}
+
+// 标签导航栏
+.tabs-section {
+  padding: 0 24px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  :deep(.el-tabs) {
+    max-width: 100%;
+    margin: 0 auto;
+    .el-tabs__header {
+      margin: 0;
+      border-bottom: none;
+    }
+    .el-tabs__nav-wrap::after {
+      height: 1px;
+      background-color: #e4e7ed;
+    }
+    .el-tabs__item {
+      height: 48px;
+      padding: 0 20px;
+      font-size: 15px;
+      font-weight: 400;
+      line-height: 48px;
+      color: #606266;
+      &.is-active {
+        font-weight: 500;
+        color: #409eff;
+        &::after {
+          position: absolute;
+          bottom: 0;
+          left: 50%;
+          width: 40px;
+          height: 3px;
+          content: "";
+          background-color: #409eff;
+          border-radius: 2px 2px 0 0;
+          transform: translateX(-50%);
+        }
+      }
+      &:hover {
+        color: #409eff;
+      }
+    }
+  }
+}
+
+// 主内容区
+.content-section {
+  display: flex;
+  flex: 1;
+
+  // align-items: center;
+  justify-content: center;
+  min-height: 400px;
+  background-color: #ffffff;
+  .content-wrapper {
+    display: flex;
+
+    // align-items: center;
+    justify-content: center;
+    width: 100%;
+    padding: 40px 24px;
+    .auth-button {
+      padding: 22px 35px;
+      font-size: 16px;
+      font-weight: 500;
+      background-color: #6c8ff8;
+      border: none;
+      border-radius: 6px;
+      transition: all 0.3s;
+    }
+  }
+  .password-form-wrapper {
+    width: 100%;
+    max-width: 500px;
+    padding: 40px;
+    background-color: #ffffff;
+    border: 1px solid red;
+    border-radius: 8px;
+    .password-input {
+      width: 100%;
+    }
+    .submit-button {
+      width: 100%;
+      padding: 12px;
+      margin-top: 20px;
+      font-size: 16px;
+      font-weight: 500;
+      background-color: #6c8ff8;
+      border: none;
+      border-radius: 6px;
+    }
+    :deep(.el-form-item__label) {
+      font-weight: 500;
+      color: #303133;
+    }
+  }
+  .password-form-wrapper-step {
+    width: 600px;
+    .form-wrapper {
+      width: 600px;
+      margin-top: 40px;
+    }
+    .forget-password {
+      font-size: 14px;
+      color: #6c8ff8;
+      text-align: right;
+      cursor: pointer;
+    }
+    :deep(.el-step__head.is-process .el-step__icon) {
+      color: #909399;
+      border-color: #909399 !important; /* 设置圆圈边框为灰色 */
+    }
+    :deep(.el-steps) {
+      .el-step__head {
+        .el-step__icon {
+          width: 30px;
+          height: 30px;
+          font-size: 16px;
+          font-weight: 600;
+        }
+      }
+    }
+  }
+}
+</style>

+ 20 - 26
src/views/groupPackageManagement/detail.vue

@@ -33,7 +33,7 @@
           <div class="detail-item">
             <div class="detail-label">团购名称</div>
             <div class="detail-value">
-              {{ storeInfoModel.groupName || "-" }}
+              {{ storeInfoModel.groupName || "--" }}
             </div>
           </div>
           <!-- 开始售卖时间 -->
@@ -47,14 +47,14 @@
           <div class="detail-item">
             <div class="detail-label">结束时间</div>
             <div class="detail-value">
-              {{ storeInfoModel.endTime ? formatDate(storeInfoModel.endTime) : "-" }}
+              {{ storeInfoModel.endTime ? formatDate(storeInfoModel.endTime) : "--" }}
             </div>
           </div>
           <!-- 库存数量 -->
           <div class="detail-item">
             <div class="detail-label">库存数量</div>
             <div class="detail-value">
-              {{ storeInfoModel.inventoryNum || "-" }}
+              {{ storeInfoModel.inventoryNum || "--" }}
             </div>
           </div>
           <!-- 每人限购 -->
@@ -78,7 +78,7 @@
                   </div>
                 </div>
               </div>
-              <span v-else class="empty-text">-</span>
+              <span v-else class="empty-text">--</span>
             </div>
           </div>
         </div>
@@ -89,14 +89,14 @@
           <div class="detail-item">
             <div class="detail-label">原价(¥)</div>
             <div class="detail-value">
-              {{ storeInfoModel.originalPrice || "-" }}
+              {{ storeInfoModel.originalPrice || "--" }}
             </div>
           </div>
           <!-- 优惠价 -->
           <div class="detail-item">
             <div class="detail-label">优惠价(¥)</div>
             <div class="detail-value">
-              {{ storeInfoModel.preferentialPrice || "-" }}
+              {{ storeInfoModel.preferentialPrice || "--" }}
             </div>
           </div>
         </div>
@@ -128,30 +128,24 @@
           <!-- 预约规则 -->
           <div class="detail-item">
             <div class="detail-label">预约规则</div>
-            <div class="detail-value">
-              {{ storeInfoModel.reservationRules || "详情请咨询商家" }}
-            </div>
+            <div class="detail-value" v-html="storeInfoModel.reservationRules || '详情请咨询商家'" />
           </div>
           <!-- 使用规则 -->
           <div class="detail-item">
             <div class="detail-label">使用规则</div>
-            <div class="detail-value">
-              {{ storeInfoModel.useRules || "详情请咨询商家" }}
-            </div>
+            <div class="detail-value" v-html="storeInfoModel.useRules || '详情请咨询商家'" />
           </div>
           <!-- 适用人数 -->
           <div class="detail-item">
             <div class="detail-label">适用人数</div>
             <div class="detail-value">
-              {{ storeInfoModel.applicableNum ? storeInfoModel.applicableNum + "人" : "-" }}
+              {{ storeInfoModel.applicableNum ? storeInfoModel.applicableNum + "人" : "--" }}
             </div>
           </div>
           <!-- 其他规则 -->
           <div class="detail-item">
             <div class="detail-label">其他规则</div>
-            <div class="detail-value">
-              {{ storeInfoModel.otherRules || "详情请咨询商家" }}
-            </div>
+            <div class="detail-value" v-html="storeInfoModel.otherRules || '详情请咨询商家'" />
           </div>
           <!-- 发票信息 -->
           <div class="detail-item">
@@ -385,7 +379,7 @@ const getStartTimeText = () => {
   } else if (storeInfoModel.value.startTimeType === 1 && storeInfoModel.value.startTimeValue) {
     return formatDate(storeInfoModel.value.startTimeValue);
   }
-  return "-";
+  return "--";
 };
 
 /**
@@ -397,7 +391,7 @@ const getQuotaText = () => {
   } else if (storeInfoModel.value.quotaType === 1) {
     return storeInfoModel.value.quotaValue ? `不限量/${storeInfoModel.value.quotaValue}` : "不限量";
   }
-  return "-";
+  return "--";
 };
 
 /**
@@ -408,14 +402,14 @@ const getEffectiveDateText = () => {
     if (storeInfoModel.value.expirationDate) {
       return `用户购买后${storeInfoModel.value.expirationDate}天内有效`;
     }
-    return "-";
+    return "--";
   } else if (storeInfoModel.value.effectiveDateType === 1) {
     if (storeInfoModel.value.expirationDateList && storeInfoModel.value.expirationDateList.length === 2) {
       return `${formatDate(storeInfoModel.value.expirationDateList[0])}-${formatDate(storeInfoModel.value.expirationDateList[1])}`;
     }
-    return "-";
+    return "--";
   }
-  return "-";
+  return "--";
 };
 
 /**
@@ -456,17 +450,17 @@ const getDisableDateText = () => {
       parts.push(holidays.join("、"));
     }
 
-    return parts.length > 0 ? parts.join("、") : "-";
+    return parts.length > 0 ? parts.join("、") : "--";
   } else if (storeInfoModel.value.disableDateType === 2) {
     if (storeInfoModel.value.disableDateList && storeInfoModel.value.disableDateList.length > 0) {
       const dateStrings = storeInfoModel.value.disableDateList
         .filter((date: any) => date && date.length === 2)
         .map((date: any) => `${formatDate(date[0])}-${formatDate(date[1])}`);
-      return dateStrings.length > 0 ? dateStrings.join("、") : "-";
+      return dateStrings.length > 0 ? dateStrings.join("、") : "--";
     }
-    return "-";
+    return "--";
   }
-  return "-";
+  return "--";
 };
 
 /**
@@ -475,7 +469,7 @@ const getDisableDateText = () => {
  * @returns 格式化后的日期字符串 (YYYY/MM/DD)
  */
 const formatDate = (date: string) => {
-  if (!date) return "-";
+  if (!date) return "--";
   return date.replace(/-/g, "/");
 };
 

+ 7 - 8
src/views/groupPackageManagement/index.vue

@@ -329,18 +329,17 @@ const currentAuditStatus = computed(() => {
 // 控制 el-tabs 是否显示:当审核状态为空或审核通过时显示
 const showTabs = computed(() => {
   const status = currentAuditStatus.value;
-  return !status || status === "-2";
+  return !status || status === "1";
 });
 
 // 根据审核状态过滤 tabOptions:如果审核状态为空,只显示草稿;审核通过时,显示除草稿外的所有标签页
 const filteredTabOptions = computed(() => {
   const status = currentAuditStatus.value;
   if (!status) {
-    // 审核状态为空时,只显示草稿
     return allTabOptions;
-  } else if (status === "-2") {
+  } else if (status == "1") {
     // 审核通过时,显示除草稿外的所有标签页
-    return allTabOptions.filter(tab => tab.name !== "1");
+    return allTabOptions.filter(tab => tab.name != "0");
   }
   return [];
 });
@@ -358,8 +357,8 @@ watch(
 );
 // 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
 const initParam = reactive({
-  storeId: localGet("createdId") || "",
-  groupType: localGet("businessSection") || "1",
+  storeId: localGet("createdId"),
+  groupType: localGet("businessSection"),
   status: activeName
 });
 const type = ref(false);
@@ -408,7 +407,7 @@ const deleteRow = row => {
   }).then(() => {
     const params = {
       id: row.id,
-      groupType: localGet("businessSection") || "1"
+      groupType: localGet("businessSection")
     };
     delThaliById(params).then(() => {
       ElMessage.success("删除成功");
@@ -439,7 +438,7 @@ const handleSubmit = async () => {
     if (valid) {
       let res = await updateNum({ id: formInventory.value.id, num: formInventory.value.newInventory });
       if (res && res.code == 200) {
-        ElMessage.success("库存修改成功");
+        ElMessage.success("修改成功");
         dialogFormVisible.value = false;
         proTable.value?.getTableList();
       }

+ 164 - 24
src/views/groupPackageManagement/newGroup.vue

@@ -44,7 +44,7 @@
             </el-form-item>
             <!-- 团购名称 -->
             <el-form-item label="团购名称" prop="groupName">
-              <el-input maxlength="50" v-model="storeInfoModel.groupName" placeholder="请填写团购名称" clearable />
+              <el-input maxlength="30" v-model="storeInfoModel.groupName" placeholder="请填写团购名称" clearable />
             </el-form-item>
             <!-- 开始售卖时间设置方式 -->
             <el-form-item label="开始售卖时间" prop="startTimeType">
@@ -145,7 +145,7 @@
                         >
                           <div class="category-row">
                             <span class="label">类别</span>
-                            <el-input v-model="item.group.groupName" placeholder="请输入" clearable />
+                            <el-input v-model="item.group.groupName" placeholder="请输入" maxlength="10" clearable />
                           </div>
                         </el-form-item>
                         <!-- 第一行菜品,始终显示 -->
@@ -322,7 +322,15 @@
             </template>
             <el-form-item label="" prop="customUnavailableDates" v-else-if="storeInfoModel.disableDateType == 2">
               <div class="date-picker-container">
-                <el-button :icon="Plus" class="add-date-btn" type="primary" @click="addDate"> 添加日期 </el-button>
+                <el-button
+                  :icon="Plus"
+                  class="add-date-btn"
+                  type="primary"
+                  :disabled="storeInfoModel.disableDateList.length >= 10"
+                  @click="addDate"
+                >
+                  添加日期
+                </el-button>
                 <div v-for="(item, index) in storeInfoModel.disableDateList" :key="index" class="date-item">
                   <el-date-picker
                     v-model="storeInfoModel.disableDateList[index]"
@@ -354,6 +362,7 @@
                 :rows="4"
                 type="textarea"
                 placeholder="请输入预约规则"
+                show-word-limit
               />
             </el-form-item>
             <!-- 使用规则  prop="useRules"-->
@@ -364,6 +373,7 @@
                 :rows="4"
                 type="textarea"
                 placeholder="请输入使用规则"
+                show-word-limit
               />
             </el-form-item>
             <!-- 适用人数 -->
@@ -378,6 +388,7 @@
                 :rows="4"
                 type="textarea"
                 placeholder="请输入其他规则"
+                show-word-limit
               />
             </el-form-item>
             <!-- 发票信息  prop="invoiceInformation"-->
@@ -471,7 +482,8 @@ import {
   validatePositiveInteger,
   validateDateRangeArray,
   validateConditionalRequired,
-  validateDateListArray
+  validateDateListArray,
+  validatePriceFormat
 } from "@/utils/eleValidate";
 
 // ==================== 响应式数据定义 ====================
@@ -574,7 +586,24 @@ const rules = reactive({
   inventoryNum: [
     { required: true, message: "请填写库存数量" },
     {
-      validator: validatePositiveInteger("库存数量必须为正整数", { required: false }),
+      validator: (rule: any, value: any, callback: any) => {
+        // 先验证是否为正整数
+        validatePositiveInteger("库存数量必须为正整数", { required: false })(rule, value, (error: any) => {
+          if (error) {
+            callback(error);
+            return;
+          }
+          // 验证最大值
+          if (value) {
+            const numValue = Number(value);
+            if (!isNaN(numValue) && numValue > 99999) {
+              callback(new Error("库存数量不得大于99999"));
+              return;
+            }
+          }
+          callback();
+        });
+      },
       trigger: "blur"
     }
   ],
@@ -617,9 +646,9 @@ const rules = reactive({
     }
   ],
   originalPrice: [
-    { required: true, message: "请输入原价" },
     {
       validator: (rule: any, value: any, callback: any) => {
+        // 非必填,如果为空则直接通过
         if (!value || value.toString().trim() === "") {
           callback();
           return;
@@ -632,6 +661,10 @@ const rules = reactive({
         callback();
       },
       trigger: "blur"
+    },
+    {
+      validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
+      trigger: "blur"
     }
   ],
   preferentialPrice: [
@@ -650,6 +683,10 @@ const rules = reactive({
         callback();
       },
       trigger: "blur"
+    },
+    {
+      validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
+      trigger: "blur"
     }
   ],
   effectiveDateType: [{ required: true, message: "请选择有效期" }],
@@ -715,12 +752,62 @@ const rules = reactive({
             callback(new Error("至少需要添加一个自定义不可用日期"));
             return;
           }
-          validateDateListArray(
-            () => storeInfoModel.value.disableDateList,
-            "日期项未完整填写",
-            "开始时间必须早于结束时间",
-            true
-          )(rule, value, callback);
+
+          const dateList = storeInfoModel.value.disableDateList;
+          const today = new Date();
+          today.setHours(0, 0, 0, 0);
+
+          // 用于存储所有日期范围,用于检查重复
+          const dateRanges: Array<{ start: Date; end: Date; index: number }> = [];
+
+          for (let i = 0; i < dateList.length; i++) {
+            const dateItem = dateList[i];
+            if (!dateItem || !Array.isArray(dateItem) || dateItem.length !== 2) {
+              callback(new Error(`第${i + 1}个日期项未完整填写`));
+              return;
+            }
+
+            const startDate = new Date(dateItem[0]);
+            const endDate = new Date(dateItem[1]);
+            startDate.setHours(0, 0, 0, 0);
+            endDate.setHours(0, 0, 0, 0);
+
+            // 验证不能早于今天
+            if (startDate < today) {
+              callback(new Error(`第${i + 1}个日期项的开始时间不能早于当前时间`));
+              return;
+            }
+            if (endDate < today) {
+              callback(new Error(`第${i + 1}个日期项的结束时间不能早于当前时间`));
+              return;
+            }
+
+            // 验证开始时间不能晚于结束时间(允许相等,即可以选择单天)
+            if (startDate > endDate) {
+              callback(new Error(`第${i + 1}个日期项的开始时间不能晚于结束时间`));
+              return;
+            }
+
+            // 存储日期范围用于重复检查
+            dateRanges.push({ start: startDate, end: endDate, index: i });
+          }
+
+          // 检查日期范围是否重复
+          for (let i = 0; i < dateRanges.length; i++) {
+            for (let j = i + 1; j < dateRanges.length; j++) {
+              const range1 = dateRanges[i];
+              const range2 = dateRanges[j];
+
+              // 检查两个日期范围是否有重叠
+              // 重叠条件:range1的开始时间 <= range2的结束时间 && range1的结束时间 >= range2的开始时间
+              if (range1.start.getTime() <= range2.end.getTime() && range1.end.getTime() >= range2.start.getTime()) {
+                callback(new Error(`第${range1.index + 1}个日期项与第${range2.index + 1}个日期项存在重复,请重新选择`));
+                return;
+              }
+            }
+          }
+
+          callback();
         } else {
           callback();
         }
@@ -731,9 +818,34 @@ const rules = reactive({
   reservationRules: [{ required: true, message: "请输入预约规则" }],
   useRules: [{ required: true, message: "请输入使用规则" }],
   applicableNum: [
-    { required: true, message: "请输入适用人数" },
     {
-      validator: validatePositiveInteger("适用人数必须为正整数", { required: false }),
+      validator: (rule: any, value: any, callback: any) => {
+        // 非必填,如果为空则直接通过
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        // 先验证是否为正整数
+        validatePositiveInteger("适用人数必须为正整数", { required: false })(rule, value, (error: any) => {
+          if (error) {
+            callback(error);
+            return;
+          }
+          // 验证范围:1-99999
+          const numValue = Number(value);
+          if (!isNaN(numValue)) {
+            if (numValue < 1) {
+              callback(new Error("适用人数不能小于1"));
+              return;
+            }
+            if (numValue > 99999) {
+              callback(new Error("适用人数不能大于99999"));
+              return;
+            }
+          }
+          callback();
+        });
+      },
       trigger: "blur"
     }
   ],
@@ -986,7 +1098,7 @@ onMounted(async () => {
   if (type.value != "add") {
     const params = {
       id: id.value,
-      userId: localGet("userId") || "104"
+      userId: localGet("userId")
     };
     let res: any = await getThaliById(params);
     storeInfoModel.value = { ...storeInfoModel.value, ...res.data.lifeGroupBuyMain };
@@ -1059,6 +1171,7 @@ onMounted(async () => {
 const goBack = () => {
   router.go(-1);
 };
+
 const beforeAvatarUpload = (file: any) => {
   console.log(file);
   return false;
@@ -1215,7 +1328,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();
 
@@ -1317,6 +1430,11 @@ const handlePictureCardPreview = (file: any) => {
  * 添加自定义不可用日期
  */
 const addDate = () => {
+  // 检查最大数量限制
+  if (storeInfoModel.value.disableDateList.length >= 10) {
+    ElMessage.warning("最多只能添加10个自定义不可用日期");
+    return;
+  }
   storeInfoModel.value.disableDateList.push(null);
 };
 
@@ -1673,7 +1791,7 @@ const openDishDialog = async (groupIndex: number, dishIndex: number) => {
   currentDishIndex.value = dishIndex;
   dishSearchKeyword.value = "";
   const params = {
-    storeId: localGet("createdId") || "104",
+    storeId: localGet("createdId"),
     phoneId: 18641153170,
     dishType: 0
   };
@@ -1791,6 +1909,11 @@ const packageFormRules = computed(() => {
             return;
           }
           const trimmedValue = value.toString().trim();
+          // 检查长度(不得大于10字)
+          if (trimmedValue.length > 10) {
+            callback(new Error("类别不得大于10字"));
+            return;
+          }
           // 检查是否与其他分组的类别重复
           for (let i = 0; i < lifeGroupBuyThalis.value.length; i++) {
             // 跳过当前分组
@@ -1830,9 +1953,26 @@ const packageFormRules = computed(() => {
             trigger: "blur"
           },
           {
-            validator: validatePositiveInteger(`第${groupIndex + 1}个分组的第${dishIndex + 1}个菜品数量必须为正整数`, {
-              required: false
-            }),
+            validator: (rule: any, value: any, callback: any) => {
+              // 先验证是否为正整数
+              validatePositiveInteger(`第${groupIndex + 1}个分组的第${dishIndex + 1}个菜品数量必须为正整数`, {
+                required: false
+              })(rule, value, (error: any) => {
+                if (error) {
+                  callback(error);
+                  return;
+                }
+                // 验证最大值
+                if (value) {
+                  const numValue = Number(value);
+                  if (!isNaN(numValue) && numValue > 99999) {
+                    callback(new Error("数量不得大于99999"));
+                    return;
+                  }
+                }
+                callback();
+              });
+            },
             trigger: "blur"
           }
         ];
@@ -1861,17 +2001,17 @@ const handleSubmit = async (type?) => {
   }
   if (params.disableDateType == 1) {
     params.disableDateValue = params.unavailableWeekdays.join(",") + ";" + params.unavailableHolidays.join(",");
-    delete params.unavailableWeekdays;
-    delete params.unavailableHolidays;
   }
   params.invoiceType = params.invoiceInformation.join(",");
+  delete params.unavailableWeekdays;
+  delete params.unavailableHolidays;
   const paramsObj: any = {
     lifeGroupBuyMain: params,
     lifeGroupBuyThalis: lifeGroupBuyThalis.value
   };
   paramsObj.lifeGroupBuyMain.status = type ? "0" : "1";
-  paramsObj.lifeGroupBuyMain.groupType = localGet("businessSection") || "1";
-  paramsObj.lifeGroupBuyMain.storeId = localGet("createdId") || "104";
+  paramsObj.lifeGroupBuyMain.groupType = localGet("businessSection");
+  paramsObj.lifeGroupBuyMain.storeId = localGet("createdId");
   if (id.value) {
     paramsObj.lifeGroupBuyMain.id = id.value;
   }

+ 34 - 7
src/views/home/components/go-examine.vue

@@ -18,7 +18,7 @@
         <div class="stat-card">
           <div class="stat-title">店铺钱包(元)</div>
           <div class="stat-value">
-            {{ walletAmount.toFixed(2) }}
+            {{ walletAmount }}
           </div>
         </div>
         <el-image :src="homeWallet" class="walletImg" />
@@ -36,7 +36,7 @@
         <div class="stat-card">
           <div class="stat-title">今日收益(元)</div>
           <div class="stat-value">
-            {{ todayRevenue.toFixed(2) }}
+            {{ todayRevenue }}
           </div>
         </div>
         <el-image :src="homeIncome" class="walletImg" />
@@ -46,12 +46,14 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from "vue";
+import { onMounted, ref } from "vue";
+import { ElMessage } from "element-plus";
 import homeIcon from "../../../assets/images/home-icon.png";
 import homeWallet from "../../../assets/images/home-wallet.png";
 import homeOrder from "../../../assets/images/home-order.png";
 import homeIncome from "../../../assets/images/home-income.png";
-
+import { verifyOrder, verifyCoupon, getTodayOrderCount, getTodayIncome } from "@/api/modules/homeEntry.ts";
+import { localGet } from "@/utils/index";
 // 数据(可根据实际接口替换)
 const storeName = ref("时间图书馆");
 const expireDate = ref("2027/10/29");
@@ -60,11 +62,36 @@ const voucherCode = ref("");
 const walletAmount = ref(0);
 const todayOrders = ref(0);
 const todayRevenue = ref(0);
-
-const handleVerify = () => {
+onMounted(() => {
+  getOrder();
+  getInCome();
+});
+const getOrder = async () => {
+  const res: any = await getTodayOrderCount({ storeId: localGet("geeker-user").storeId });
+  if (res.code == 200) {
+    todayOrders.value = res.data;
+  }
+};
+const getInCome = async () => {
+  const res: any = await getTodayIncome({ storeId: localGet("geeker-user").storeId });
+  if (res.success) {
+    todayRevenue.value = res.data;
+  }
+};
+const handleVerify = async () => {
   // 调用验券接口
   if (!voucherCode.value) return;
-  // TODO: 接入后端验券逻辑
+  const res: any = await verifyOrder({ orderCode: voucherCode.value });
+  if (res.code == 200) {
+    let i = {
+      quanCode: voucherCode.value,
+      storeId: 120
+    };
+    const res1 = await verifyCoupon(i);
+    if (res && res.data.code === "true") {
+      ElMessage.success(res.msg);
+    }
+  }
   console.log("verify voucher:", voucherCode.value);
 };
 </script>

+ 302 - 89
src/views/home/components/go-flow.vue

@@ -65,16 +65,16 @@
                 <el-cascader :props="areaProps" v-model="step2Form.region" style="width: 100%" />
               </el-form-item>
 
-              <el-form-item label="详细地址" prop="storeAddress">
-                <el-input v-model="step2Form.storeAddress" type="textarea" :rows="3" placeholder="请输入" />
+              <el-form-item label="详细地址" prop="storeDetailAddress">
+                <el-input v-model="step2Form.storeDetailAddress" type="textarea" :rows="3" placeholder="请输入" />
               </el-form-item>
 
               <el-form-item label="门店简介" prop="storeBlurb">
                 <el-input v-model="step2Form.storeBlurb" type="textarea" :rows="3" placeholder="请输入" />
               </el-form-item>
 
-              <el-form-item label="经营板块" prop="businessSector">
-                <el-radio-group v-model="step2Form.businessSector" @change="changeBusinessSector">
+              <el-form-item label="经营板块" prop="businessSection">
+                <el-radio-group v-model="step2Form.businessSection" @change="changeBusinessSector">
                   <el-radio
                     v-for="businessSection in businessSectionList"
                     :value="businessSection.value"
@@ -85,8 +85,8 @@
                 </el-radio-group>
               </el-form-item>
 
-              <el-form-item label="经营种类" prop="businessType">
-                <el-checkbox-group v-model="step2Form.businessType">
+              <el-form-item label="经营种类" prop="businessTypes">
+                <el-checkbox-group v-model="step2Form.businessTypes">
                   <el-checkbox
                     v-for="businessType in businessTypeList"
                     :key="businessType"
@@ -106,15 +106,15 @@
                   <el-radio label="筹建中"> 筹建中 </el-radio>
                 </el-radio-group>
               </el-form-item>
-              <el-form-item label="经度" prop="storePositionLongitude">
+              <el-form-item label="经度" prop="storePositionLongitude" v-show="latShow">
                 <el-input disabled v-model="step2Form.storePositionLongitude" placeholder="请填写经度" clearable />
               </el-form-item>
-              <el-form-item label="纬度" prop="storePositionLatitude">
+              <el-form-item label="纬度" prop="storePositionLatitude" v-show="latShow">
                 <el-input disabled v-model="step2Form.storePositionLatitude" placeholder="请填写纬度" clearable />
               </el-form-item>
-              <el-form-item label="经纬度查询" prop="addressName">
+              <el-form-item label="经纬度查询" prop="address">
                 <el-select
-                  v-model="step2Form.addressName"
+                  v-model="step2Form.address"
                   filterable
                   placeholder="请输入地址进行查询"
                   remote
@@ -129,47 +129,50 @@
                 </el-select>
               </el-form-item>
 
-              <el-form-item label="营业执照" prop="businessLicenseFiles">
+              <el-form-item label="营业执照" prop="businessLicenseAddress">
                 <el-upload
-                  v-model:file-list="step2Form.businessLicenseFiles"
+                  v-model:file-list="step2Form.businessLicenseAddress"
                   :http-request="handleHttpUpload"
                   list-type="picture-card"
                   :limit="1"
                   :on-exceed="handleExceed"
+                  :on-success="handleUploadSuccess"
                 >
                   <el-icon><Plus /></el-icon>
                   <template #tip>
-                    <div class="el-upload__tip">(0/1)</div>
+                    <div class="el-upload__tip">({{ step2Form.businessLicenseAddress.length }}/1)</div>
                   </template>
                 </el-upload>
               </el-form-item>
 
-              <el-form-item label="合同图片" prop="contractImageFiles">
+              <el-form-item label="合同图片" prop="contractImageList">
                 <el-upload
-                  v-model:file-list="step2Form.contractImageFiles"
+                  v-model:file-list="step2Form.contractImageList"
                   :http-request="handleHttpUpload"
                   list-type="picture-card"
                   :limit="20"
                   :on-exceed="handleExceed"
+                  :on-success="handleUploadSuccess"
                 >
                   <el-icon><Plus /></el-icon>
                   <template #tip>
-                    <div class="el-upload__tip">(0/20)</div>
+                    <div class="el-upload__tip">({{ step2Form.contractImageList.length }}/20)</div>
                   </template>
                 </el-upload>
               </el-form-item>
 
-              <el-form-item label="食品经营许可证" prop="foodLicenseFiles ">
+              <el-form-item label="食品经营许可证" prop="foodLicenceImgList">
                 <el-upload
-                  v-model:file-list="step2Form.foodLicenseFiles"
+                  v-model:file-list="step2Form.foodLicenceImgList"
                   :http-request="handleHttpUpload"
                   list-type="picture-card"
                   :limit="1"
                   :on-exceed="handleExceed"
+                  :on-success="handleUploadSuccess"
                 >
                   <el-icon><Plus /></el-icon>
                   <template #tip>
-                    <div class="el-upload__tip">(0/1)</div>
+                    <div class="el-upload__tip">({{ step2Form.foodLicenceImgList.length }}/1)</div>
                   </template>
                 </el-upload>
               </el-form-item>
@@ -180,33 +183,43 @@
 
       <!-- 按钮 -->
       <div class="form-actions">
-        <el-button size="large" @click="handlePrevStep"> 上一步 </el-button>
         <el-button type="primary" size="large" @click="handleSubmit"> 提交 </el-button>
       </div>
     </div>
     <!-- 第三步: 等待审核-->
     <div v-if="currentStep === 3">
       <div class="button-container">
-        <el-button type="primary" size="large" class="register-btn"> 等待审核 </el-button>
+        <el-button type="danger" size="large" class="register-btn-red" @click="changeRefuse" v-if="storeApplicationStatus == 2">
+          审核拒绝,重新入驻
+        </el-button>
+        <el-button type="primary" size="large" class="register-btn" v-else> 等待审核 </el-button>
       </div>
     </div>
   </div>
 </template>
 <script setup lang="ts">
 import { ref, reactive, watch, onMounted } from "vue";
-import { ElMessage, ElMessageBox, type FormInstance, type FormRules, UploadProps, UploadRequestOptions } from "element-plus";
-import { Plus } from "@element-plus/icons-vue";
-
-import { verifyIdInfo, applyStore } from "@/api/modules/homeEntry.ts";
 import {
-  getBusinessSection,
-  getBusinessSectionTypes,
-  getInputPrompt,
-  getDistrict,
-  uploadImg
-} from "@/api/modules/newLoginApi.ts";
-import { add } from "lodash";
+  ElMessage,
+  ElMessageBox,
+  type FormInstance,
+  type FormRules,
+  UploadProps,
+  UploadUserFile,
+  UploadRequestOptions
+} from "element-plus";
+import { Plus } from "@element-plus/icons-vue";
 
+import { verifyIdInfo, applyStore } from "@/api/modules/homeEntry";
+import { getBusinessSection, getBusinessSectionTypes, getInputPrompt, getDistrict, uploadImg } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils/index";
+const userInfo = localGet("geeker-user")?.userInfo || {};
+const latShow = ref(false);
+onMounted(() => {
+  getBusinessSectionList();
+  getBusinessTypes(null);
+  callGetUserInfo();
+});
 const entryList = ref([
   {
     title: "个人实名"
@@ -222,6 +235,19 @@ const entryList = ref([
   }
 ]);
 
+const step2Rules: FormRules = {
+  storeName: [{ required: true, message: "请输入店铺名称", trigger: "blur" }],
+  storeArea: [{ required: true, message: "请选择门店面积", trigger: "change" }],
+  storeBlurb: [{ required: true, message: "请输入门店简介", trigger: "change" }],
+  storeIntro: [{ required: true, message: "请输入门店简介", trigger: "blur" }],
+  businessSection: [{ required: true, message: "请选择经营板块", trigger: "change" }],
+  businessTypes: [{ required: true, message: "请选择经营种类", trigger: "change" }],
+  address: [{ required: true, message: "请输入经纬度", trigger: "blur" }],
+  businessLicenseAddress: [{ required: true, message: "请上传营业执照", trigger: "change" }],
+  contractImageList: [{ required: true, message: "请上传合同图片", trigger: "change" }],
+  foodLicenceImgList: [{ required: true, message: "请上传食品经营许可证", trigger: "change" }]
+};
+
 //地址集合
 const addressList = ref<any[]>([]);
 //查询地址名称
@@ -231,20 +257,38 @@ const props = defineProps({
   currentStep: {
     type: Number,
     value: 0
+  },
+  storeApplicationStatus: {
+    type: Number,
+    value: 0
   }
 });
+const emit = defineEmits(["update:currentStep", "update:get-user-info"]);
 
-const emit = defineEmits(["update:currentStep"]);
+// 调用父组件的 getUserInfo 方法
+const callGetUserInfo = () => {
+  emit("update:get-user-info");
+};
 
 // 内部步骤状态,和父组件同步
 const currentStep = ref<number>(props.currentStep || 0);
+const storeApplicationStatus = ref<number>(props.storeApplicationStatus || 0);
+console.log(storeApplicationStatus);
 watch(
   () => props.currentStep,
   val => {
     if (typeof val === "number") currentStep.value = val;
   }
 );
-
+watch(
+  () => props.storeApplicationStatus,
+  val => {
+    if (typeof val === "number") storeApplicationStatus.value = val;
+  }
+);
+const changeRefuse = () => {
+  currentStep.value = 2;
+};
 const setStep = (val: number) => {
   currentStep.value = val;
   emit("update:currentStep", val);
@@ -253,8 +297,8 @@ const setStep = (val: number) => {
 // 第一步表单
 const step1FormRef = ref<FormInstance>();
 const step1Form = reactive({
-  name: "朱丽",
-  idNumber: "231084199304282927"
+  name: "",
+  idNumber: ""
 });
 
 const step1Rules: FormRules = {
@@ -272,40 +316,32 @@ const step1Rules: FormRules = {
 // 第二步表单
 const step2FormRef = ref<FormInstance>();
 const step2Form = reactive({
+  storeName: "", //门店名称
+  storeCapacity: 1, //容纳人数
+  storeArea: "1", //门店面积
+  isChain: 0, //是否连锁
+  storeDetailAddress: "", //详细地址
   region: [],
-  addressName: "",
-  storePositionLongitude: "",
-  storePositionLatitude: "",
-  storeName: "",
-  storeCapacity: 1,
-  storePhone: "",
-  storeArea: "小于20平米",
-  province: "",
-  city: "",
-  district: "",
-  storeAddress: "",
-  storeBlurb: "",
-  businessSector: "1",
-  businessType: "正常营业",
-  coordinates: "",
-  businessLicenseFiles: [],
-  contractImageFiles: [],
-  foodLicenseFiles: []
+  administrativeRegionProvinceAdcode: "", //省
+  administrativeRegionCityAdcode: "", //市
+  administrativeRegionDistrictAdcode: "", //区
+  storeAddress: "", //门店地址(完整地址)
+  storeBlurb: "", //门店简介
+  businessSection: "", //经营板块
+  businessSectionName: "KTV",
+  businessTypes: [], //经营种类
+  businessTypesList: [], //经营种类集合
+  businessStatus: 0, //营业状态
+  storeStatus: 1, //门店状态
+  businessType: "正常营业", //门店营业状态(用于表单显示)
+  storePositionLongitude: "", //经度
+  storePositionLatitude: "", //纬度
+  businessLicenseAddress: [] as UploadUserFile[], //营业执照地址
+  contractImageList: [] as UploadUserFile[], //合同图片集合
+  foodLicenceImgList: [] as UploadUserFile[], //食品经营许可证
+  address: ""
 });
 
-const step2Rules: FormRules = {
-  storeName: [{ required: true, message: "请输入店铺名称", trigger: "blur" }],
-  storePhone: [{ required: true, message: "请输入门店电话", trigger: "blur" }],
-  storeArea: [{ required: true, message: "请选择门店面积", trigger: "change" }],
-  storeIntro: [{ required: true, message: "请输入门店简介", trigger: "blur" }],
-  businessSector: [{ required: true, message: "请选择经营板块", trigger: "change" }],
-  businessType: [{ required: true, message: "请选择经营种类", trigger: "change" }],
-  coordinates: [{ required: true, message: "请输入经纬度", trigger: "blur" }],
-  businessLicense: [{ required: true, message: "请上传营业执照", trigger: "change" }],
-  contractImages: [{ required: true, message: "请上传合同图片", trigger: "change" }],
-  foodLicense: [{ required: true, message: "请上传食品经营许可证", trigger: "change" }]
-};
-
 // 返回按钮
 const handleBack = () => {
   if (currentStep.value === 1) {
@@ -332,15 +368,25 @@ const areaProps: any = {
         label: item.name,
         leaf: level >= 2 // 假设最多三级,可以根据实际需求调整
       }));
-
+      console.log(nodes);
       // 返回数据
       resolve(nodes);
     } catch (error) {
-      console.error("获取区域数据失败:", error);
       resolve([]);
     }
   }
 };
+watch(
+  () => step2Form.region,
+  (newVal: any[]) => {
+    if (newVal.length > 0) {
+      step2Form.administrativeRegionProvinceAdcode = newVal[0];
+      step2Form.administrativeRegionCityAdcode = newVal[1];
+      step2Form.administrativeRegionDistrictAdcode = newVal[2];
+    }
+  }
+);
+
 //经营板块
 const businessSectionList = ref<any[]>([]);
 const getBusinessSectionList = async () => {
@@ -350,15 +396,35 @@ const getBusinessSectionList = async () => {
     addData.push({ value: element.dictId, label: element.dictDetail, parentId: element.parentId });
   });
   businessSectionList.value = addData;
+  // 如果businessSection有默认值,自动设置对应的id和name
+  if (step2Form.businessSection) {
+    const selectedSection = addData.find((item: any) => item.value === step2Form.businessSection);
+    if (selectedSection) {
+      step2Form.businessSection = selectedSection.value;
+      step2Form.businessSectionName = selectedSection.label;
+    }
+  }
 };
 const changeBusinessSector = async (val: any) => {
+  // 根据选中的value从businessSectionList中查找对应的项
+  const selectedSection = businessSectionList.value.find((item: any) => item.value === val);
+  if (selectedSection) {
+    step2Form.businessSection = selectedSection.value; // dictId
+    step2Form.businessSectionName = selectedSection.label; // dictDetail
+  } else {
+    step2Form.businessSection = "";
+    step2Form.businessSectionName = "";
+  }
+  // 清空之前选中的经营种类
+  step2Form.businessTypesList = [];
+  step2Form.businessTypes = [];
   getBusinessTypes(val);
 };
 
 //经营种类
 const businessTypeList = ref<any[]>([]);
 const getBusinessTypes = async (val: any) => {
-  let res: any = await getBusinessSectionTypes({ parentId: val ? val : step2Form.businessSector });
+  let res: any = await getBusinessSectionTypes({ parentId: val ? val : step2Form.businessSection });
   let addData1: any[] = [];
   (res?.data || []).forEach((element: any) => {
     addData1.push({ value: element.dictId, label: element.dictDetail });
@@ -385,14 +451,15 @@ const getLonAndLat = async (keyword: string) => {
   }
 };
 const selectAddress = async (param: any) => {
-  let locationList = step2Form.addressName.split(",");
+  let locationList = step2Form.address.split(",");
   addressList.value.forEach((item: any) => {
-    if (item.location == step2Form.addressName) {
+    if (item.location == step2Form.address) {
       queryAddress.value = item.name;
     }
   });
   step2Form.storePositionLongitude = locationList[0];
   step2Form.storePositionLatitude = locationList[1];
+  latShow.value = true;
 };
 //文件上传
 const handleHttpUpload = async (options: UploadRequestOptions) => {
@@ -400,9 +467,25 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
   formData.append("file", options.file);
   try {
     const res: any = await uploadImg(formData);
-    console.log(res);
+    // 上传成功,将URL保存到文件对象中
+    const fileUrl = res?.data?.fileUrl || res?.data?.[0] || res?.fileUrl;
+    if (fileUrl) {
+      // 调用 onSuccess 回调,传入响应数据
+      options.onSuccess({ fileUrl });
+    } else {
+      throw new Error("上传失败:未获取到文件URL");
+    }
   } catch (error) {
     options.onError(error as any);
+    ElMessage.error("文件上传失败,请重试");
+  }
+};
+
+// 文件上传成功回调
+const handleUploadSuccess = (response: any, uploadFile: UploadUserFile) => {
+  // 将URL保存到文件对象中
+  if (response?.fileUrl) {
+    uploadFile.url = response.fileUrl;
   }
 };
 // 下一步
@@ -416,12 +499,11 @@ const handleNextStep = async () => {
         idCard: step1Form.idNumber,
         appType: 1
       };
-      const res = await verifyIdInfo(params);
-      if (res.code == "200") {
+      const res: any = await verifyIdInfo(params);
+      if (res && res.code == 200) {
         ElMessage.success(res.msg);
+
         setStep(2);
-        getBusinessSectionList();
-        getBusinessTypes();
       }
     } else {
       ElMessage.error("请完善表单信息");
@@ -429,30 +511,152 @@ const handleNextStep = async () => {
   });
 };
 
-// 上一步
-const handlePrevStep = () => {
-  setStep(1);
+// 提取文件列表中的URL
+const getFileUrls = (fileList: UploadUserFile[]): string[] => {
+  return fileList
+    .map((file: UploadUserFile) => {
+      // 优先使用 url,如果没有则使用 response 中的 fileUrl
+      const response = file.response as any;
+      return file.url || response?.fileUrl || "";
+    })
+    .filter((url: string) => url); // 过滤掉空字符串
+};
+
+// 根据adcode获取地区详细信息
+const getDistrictInfo = async (adcode: string) => {
+  try {
+    const response: any = await getDistrict({ adCode: adcode } as any);
+    const district = response?.data?.districts?.[0];
+    if (district) {
+      return {
+        citycode: district.citycode ? [district.citycode] : [],
+        adcode: district.adcode,
+        level: district.level,
+        center: district.center,
+        name: district.name,
+        districts: []
+      };
+    }
+  } catch (error) {
+    console.error("获取地区信息失败:", error);
+  }
+  return null;
+};
+
+// 构建whereAddress数组
+const buildWhereAddress = async (regionCodes: string[]) => {
+  const whereAddress: any[] = [];
+  if (regionCodes && regionCodes.length > 0) {
+    for (const code of regionCodes) {
+      const districtInfo = await getDistrictInfo(code);
+      if (districtInfo) {
+        whereAddress.push(districtInfo);
+      }
+    }
+  }
+  return whereAddress;
 };
 
 // 提交
 const handleSubmit = async () => {
-  console.log(step2Form);
-  return;
-  let res = await applyStore(param);
-  return;
   if (!step2FormRef.value) return;
 
-  await step2FormRef.value.validate(valid => {
+  await step2FormRef.value.validate(async valid => {
     if (valid) {
+      // 提取上传图片的URL
+      const businessLicenseUrls = getFileUrls(step2Form.businessLicenseAddress);
+      const contractImageUrls = getFileUrls(step2Form.contractImageList);
+      const foodLicenceUrls = getFileUrls(step2Form.foodLicenceImgList);
+
+      // 构建whereAddress数组
+      const whereAddress = await buildWhereAddress(step2Form.region);
+
+      // 处理营业状态映射
+      let storeStatus = 1; // 默认值
+      if (step2Form.businessType === "正常营业") {
+        storeStatus = 1;
+      } else if (step2Form.businessType === "暂停营业") {
+        storeStatus = 0;
+      } else if (step2Form.businessType === "筹建中") {
+        storeStatus = 2;
+      }
+
+      // 处理门店面积:转换为数字
+      const storeAreaNum = typeof step2Form.storeArea === "string" ? parseInt(step2Form.storeArea) : step2Form.storeArea;
+
+      // 构建地址对象
+      const addressObj = {
+        address: queryAddress.value || "",
+        longitude: parseFloat(step2Form.storePositionLongitude) || 0,
+        latitude: parseFloat(step2Form.storePositionLatitude) || 0
+      };
+
+      // 构建门店位置字符串
+      const storePosition =
+        step2Form.storePositionLongitude && step2Form.storePositionLatitude
+          ? `${step2Form.storePositionLongitude},${step2Form.storePositionLatitude}`
+          : "";
+
+      // 构建完整地址(省市区+详细地址)
+      let fullStoreAddress = "";
+      if (whereAddress.length > 0) {
+        const provinceName = whereAddress[0]?.name || "";
+        const cityName = whereAddress[1]?.name || "";
+        const districtName = whereAddress[2]?.name || "";
+        fullStoreAddress = `${provinceName}${cityName}${districtName}`;
+      }
+
+      const params = {
+        storeTel: userInfo.phone,
+        storeName: step2Form.storeName,
+        storeCapacity: step2Form.storeCapacity,
+        storeArea: storeAreaNum,
+        isChain: step2Form.isChain,
+        storeDetailAddress: step2Form.storeDetailAddress,
+        storeBlurb: step2Form.storeBlurb,
+        businessSection: step2Form.businessSection,
+        businessTypesList: step2Form.businessTypes.length > 0 ? step2Form.businessTypes : step2Form.businessTypesList,
+        storeStatus: storeStatus,
+        businessStatus: step2Form.businessStatus,
+        address: addressObj,
+        businessLicenseAddress: businessLicenseUrls,
+        contractImageList: contractImageUrls,
+        foodLicenceImgList: foodLicenceUrls,
+        storeAddress: fullStoreAddress,
+        whereAddress: whereAddress,
+        updatedTime: null,
+        queryAddress: queryAddress.value,
+        storePosition: storePosition,
+        storePositionLatitude: parseFloat(step2Form.storePositionLatitude) || 0,
+        storePositionLongitude: parseFloat(step2Form.storePositionLongitude) || 0,
+        businessSectionName: step2Form.businessSectionName,
+        businessTypes: step2Form.businessTypes.length > 0 ? step2Form.businessTypes : step2Form.businessTypesList,
+        foodLicenceUrl: foodLicenceUrls.length > 0 ? foodLicenceUrls[0] : "",
+        userAccount: userInfo.id, // 需要从用户信息中获取
+        administrativeRegionProvinceAdcode: step2Form.administrativeRegionProvinceAdcode,
+        administrativeRegionCityAdcode: step2Form.administrativeRegionCityAdcode,
+        administrativeRegionDistrictAdcode: step2Form.administrativeRegionDistrictAdcode,
+        storeContact: step1Form.name,
+        idCard: step1Form.idNumber
+      };
+
       ElMessageBox.confirm("确认提交入驻申请吗?", "提示", {
         confirmButtonText: "确定",
         cancelButtonText: "取消",
         type: "warning"
       })
-        .then(() => {
-          // 这里可以调用API提交数据
-          ElMessage.success("提交成功,等待审核");
-          setStep(3); // 跳转到等待审核步骤
+        .then(async () => {
+          try {
+            const res: any = await applyStore(params);
+            if (res && res.code == 200) {
+              setStep(3); // 跳转到等待审核步骤
+              ElMessage.success(res.msg);
+            } else {
+              ElMessage.error(res.msg || "提交失败");
+            }
+          } catch (error) {
+            ElMessage.error("提交失败,请重试");
+          }
         })
         .catch(() => {
           // 取消提交
@@ -559,5 +763,14 @@ const handleExceed = () => {
     border-radius: 4px;
     outline: none;
   }
+  .register-btn-red {
+    width: 200px;
+    height: 44px;
+    font-size: 16px;
+    font-weight: 500;
+    border: 0;
+    border-radius: 4px;
+    outline: none;
+  }
 }
 </style>

+ 54 - 9
src/views/home/index.vue

@@ -1,31 +1,76 @@
 <template>
   <div id="home">
-    <!-- 第一步  未入驻 -->
-    <go-enter :current-step="currentStep" @update:current-step="handleUpdateCurrentStep" v-if="userInfo == 0" />
-
     <!--已入驻-->
-    <go-examine v-if="userInfo == 1" />
+    <go-examine v-if="isExaime" />
+    <!-- 第一步  未入驻 -->
+    <go-enter :current-step="currentStep" @update:current-step="handleUpdateCurrentStep" v-if="isEntry" />
 
     <!-- 第二步 -->
-    <go-flow :current-step="currentStep" @update:current-step="handleUpdateCurrentStep" />
+    <go-flow
+      :current-step="currentStep"
+      @update:current-step="handleUpdateCurrentStep"
+      @update:get-user-info="getUserInfo"
+      :store-application-status="storeApplicationStatus"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from "vue";
-
+import { onMounted, ref } from "vue";
+import { localGet } from "@/utils/index";
 import goEnter from "./components/go-enter.vue";
 import goFlow from "./components/go-flow.vue";
 import goExamine from "./components/go-examine.vue";
-const userInfo = ref(0);
+import { getMerchantByPhone, getDetail } from "@/api/modules/homeEntry";
+const isEntry = ref<boolean>(false);
+const isExaime = ref<boolean>(false);
+
+onMounted(() => {
+  getUserInfo();
+});
 
 // 当前步骤:0-首页,1-第一步,2-第二步
 const currentStep = ref(0);
-
+const storeId = ref<Number | undefined>(undefined);
 // 处理更新currentStep事件
 const handleUpdateCurrentStep = (step: number) => {
   currentStep.value = step;
 };
+let storeApplicationStatus = ref<number | undefined>(undefined);
+const getUserInfo = async () => {
+  try {
+    const geekerUser = localGet("geeker-user");
+    if (!geekerUser || !geekerUser.userInfo || !geekerUser.userInfo.phone) {
+      console.error("用户信息不存在");
+      return;
+    }
+    let param = {
+      phone: geekerUser.userInfo.phone
+    };
+    const res: any = await getMerchantByPhone(param);
+    storeId.value = res.data.storeId;
+    if (res && res.code == 200 && res.data && res.data.storeId) {
+      let param1 = {
+        id: res.data.storeId
+      };
+      const resStore: any = await getDetail(param1);
+      if (resStore && resStore.code == 200 && resStore.data) {
+        storeApplicationStatus.value = resStore.data.storeApplicationStatus;
+        if (storeApplicationStatus.value == 2) {
+          currentStep.value = 3;
+        }
+
+        if (resStore.data.storeApplicationStatus == 2 && res.data.storeId != null) {
+          isEntry.value = true;
+        } else {
+          isExaime.value = true;
+        }
+      }
+    }
+  } catch (error) {
+    console.error(error);
+  }
+};
 </script>
 
 <style scoped lang="scss"></style>

+ 124 - 20
src/views/home/notice.vue

@@ -2,6 +2,7 @@
   <div class="notice-page">
     <!-- 头部 -->
     <div class="header">
+      <el-button class="back-btn" @click="handleBack"> 返回 </el-button>
       <el-button class="mark-all-read-btn" v-if="noticeList.length != 0" @click="handleMarkAllRead">
         <el-icon><CircleCheck /></el-icon>
         一键已读
@@ -10,29 +11,46 @@
 
     <!-- 通知列表 -->
     <div class="notice-list">
+      <!-- 空状态 -->
+      <div v-if="total === 0" class="empty-state">
+        <img src="@/assets/images/notData.png" alt="暂无数据" class="empty-image" />
+        <p class="empty-text">暂无数据</p>
+      </div>
+      <!-- 通知卡片列表 -->
       <div v-for="(item, index) in noticeList" :key="index" class="notice-card" :class="{ unread: !item.isRead }">
         <div class="card-header">
           <h3 class="card-title">
             {{ item.title }}
           </h3>
           <div class="card-time-wrapper">
-            <span class="card-time">{{ item.time }}</span>
+            <span class="card-time">{{ item.createdTime }}</span>
             <span v-if="!item.isRead" class="unread-dot" />
           </div>
         </div>
         <div class="card-content">
-          {{ item.content }}
-        </div>
-        <div class="card-footer">
+          <div>{{ parseContext(item.context) }}</div>
           <el-button class="view-detail-btn" @click="handleViewDetail(item)"> 查看详情 </el-button>
         </div>
       </div>
     </div>
 
+    <!-- 分页 -->
+    <div v-if="total > 0" class="pagination-section">
+      <el-pagination
+        v-model:current-page="pageNum"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
     <!-- 详情对话框 -->
     <el-dialog v-model="dialogVisible" :title="currentNotice?.title" width="500px" :close-on-click-modal="false">
       <div class="dialog-content">
-        {{ currentNotice?.content }}
+        {{ parseContext(currentNotice?.context || currentNotice?.content) }}
       </div>
     </el-dialog>
   </div>
@@ -42,44 +60,104 @@
 import { ref, onMounted } from "vue";
 import { ElMessage } from "element-plus";
 import { CircleCheck } from "@element-plus/icons-vue";
-import { getNoticeList } from "@/api/modules/homeEntry.ts";
+import { getNoticeList, markAllNoticesAsRead } from "@/api/modules/homeEntry";
+import { useRouter } from "vue-router";
+const router = useRouter();
+import mittBus from "@/utils/mittBus";
+import { localGet } from "@/utils/index";
+import { get } from "lodash";
 
 interface NoticeItem {
   id: number;
   title: string;
   time: string;
   content: string;
+  context?: string;
   isRead: boolean;
 }
 
-onMounted(() => {
-  getList();
-});
+onMounted(() => {});
+// 返回按钮
+const handleBack = () => {
+  router.back();
+};
 
 const noticeList = ref<NoticeItem[]>([]);
+const total = ref<number>(0);
+const pageNum = ref<number>(1);
+const pageSize = ref<number>(10);
+
+// 获取用户手机号
+const getReceiverId = () => {
+  return localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
+};
+
 const getList = async () => {
-  let res = await getNoticeList({ pageNum: 1, pageSize: 10, receiverId: "15242687180" });
-  console.log(res);
-  if (res.code == "200") {
-    noticeList.value = res.data.records;
+  let res: any = await getNoticeList({
+    pageNum: pageNum.value,
+    pageSize: pageSize.value,
+    receiverId: "store_" + getReceiverId(),
+    noticeType: 1
+  });
+  if (res.code == 200) {
+    noticeList.value = res.data?.records || res.data?.list || [];
+    total.value = res.data?.total || 0;
   }
 };
+getList();
+// 每页条数改变
+const handleSizeChange = (size: number) => {
+  pageSize.value = size;
+  pageNum.value = 1; // 重置到第一页
+  getList();
+};
+
+// 当前页改变
+const handleCurrentChange = (current: number) => {
+  pageNum.value = current;
+  getList();
+};
 
 const dialogVisible = ref(false);
 const currentNotice = ref<NoticeItem | null>(null);
 
 // 一键已读
-const handleMarkAllRead = () => {
-  noticeList.value.forEach(item => {
-    item.isRead = true;
-  });
-  ElMessage.success("已全部标记为已读");
+const handleMarkAllRead = async () => {
+  let res: any = await markAllNoticesAsRead({ receiverId: "store_" + getReceiverId(), noticeType: 1 });
+  if (res.code == 200) {
+    ElMessage.success("操作成功");
+    // 重置到第一页并刷新列表
+    pageNum.value = 1;
+    getList();
+    // 通知 Message 组件更新未读数量
+    mittBus.emit("updateUnreadCount");
+  } else {
+    ElMessage.error(res.message);
+  }
+};
+
+// 解析 context JSON 字符串,提取 message 字段
+const parseContext = (context: string | undefined): string => {
+  if (!context) return "";
+  try {
+    // 如果 context 是 JSON 字符串,先解析
+    const parsed = typeof context === "string" ? JSON.parse(context) : context;
+    // 提取 message 字段
+    return parsed?.message || context;
+  } catch (error) {
+    // 如果不是有效的 JSON,直接返回原字符串
+    return context;
+  }
 };
 
 // 查看详情
 const handleViewDetail = (item: NoticeItem) => {
   // 标记为已读
-  item.isRead = true;
+  if (!item.isRead) {
+    item.isRead = true;
+    // 通知 Message 组件更新未读数量
+    mittBus.emit("updateUnreadCount");
+  }
   // 显示详情对话框
   currentNotice.value = item;
   dialogVisible.value = true;
@@ -115,6 +193,24 @@ const handleViewDetail = (item: NoticeItem) => {
     display: flex;
     flex-direction: column;
     gap: 16px;
+    margin-bottom: 20px;
+    .empty-state {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 80px 20px;
+      .empty-image {
+        width: 360px;
+        height: 360px;
+        margin-bottom: 20px;
+      }
+      .empty-text {
+        margin: 0;
+        font-size: 16px;
+        color: #909399;
+      }
+    }
     .notice-card {
       padding: 20px;
       background: #ffffff;
@@ -154,7 +250,8 @@ const handleViewDetail = (item: NoticeItem) => {
         }
       }
       .card-content {
-        margin-bottom: 16px;
+        display: flex;
+        justify-content: space-between;
         font-size: 14px;
         line-height: 1.6;
         color: #606266;
@@ -172,6 +269,13 @@ const handleViewDetail = (item: NoticeItem) => {
       }
     }
   }
+  .pagination-section {
+    display: flex;
+    justify-content: flex-end;
+    padding-top: 20px;
+    margin-top: 20px;
+    border-top: 1px solid #e4e7ed;
+  }
 }
 .dialog-content {
   padding: 10px 0;

+ 5 - 2
src/views/home/userInfo.vue

@@ -2,7 +2,7 @@
   <div class="user-info-page">
     <div class="top-bar">
       <el-button class="back-btn" @click="handleBack"> 返回 </el-button>
-      <div class="title">个人信息</div>
+      <div class="title">个人信息111</div>
     </div>
     <div class="form-wrap">
       <el-form :model="form" :rules="rules" ref="formRef" label-width="72px">
@@ -35,7 +35,10 @@
 import { ref, reactive } from "vue";
 import { ElMessage, type FormInstance, type FormRules } from "element-plus";
 import { Plus } from "@element-plus/icons-vue";
-
+import { localGet } from "@/utils/index";
+console.log(localGet);
+const info = localGet("geeker-user");
+console.log(1111111, info);
 const formRef = ref<FormInstance>();
 const form = reactive({
   avatarList: [] as any[],

+ 9 - 8
src/views/licenseManagement/businessLicense.vue

@@ -70,8 +70,8 @@ const initData = async () => {
   color: var(--el-text-color-regular);
 }
 .license-container {
-  width: 100%;
-  min-height: 500px;
+  // width: 100%;
+  // min-height: 500px;
   padding: 20px;
   background-color: var(--el-bg-color-page);
   border-radius: 8px;
@@ -80,12 +80,13 @@ const initData = async () => {
   display: flex;
   align-items: center;
   justify-content: center;
-  width: 100%;
-  min-height: 500px;
+
+  // width: 100%;
+  // min-height: 500px;
 }
 .license-image {
-  max-width: 100%;
-  max-height: 600px;
+  // max-width: 100%;
+  // max-height: 600px;
   border-radius: 8px;
   box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
 }
@@ -93,8 +94,8 @@ const initData = async () => {
   display: flex;
   align-items: center;
   justify-content: center;
-  width: 100%;
-  height: 500px;
+  width: 300px;
+  height: 200px;
   background-color: var(--el-fill-color-lighter);
   border-radius: 8px;
   .empty-icon {

+ 9 - 8
src/views/licenseManagement/foodBusinessLicense.vue

@@ -271,8 +271,8 @@ const getStatusText = (status: string) => {
   gap: 10px;
 }
 .license-container {
-  width: 100%;
-  min-height: 500px;
+  // width: 100%;
+  // min-height: 500px;
   padding: 20px;
   background-color: var(--el-bg-color-page);
   border-radius: 8px;
@@ -281,12 +281,13 @@ const getStatusText = (status: string) => {
   display: flex;
   align-items: center;
   justify-content: center;
-  width: 100%;
-  min-height: 500px;
+
+  // width: 100%;
+  // min-height: 500px;
 }
 .license-image {
-  max-width: 100%;
-  max-height: 600px;
+  // max-width: 100%;
+  // max-height: 600px;
   border-radius: 8px;
   box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
 }
@@ -294,8 +295,8 @@ const getStatusText = (status: string) => {
   display: flex;
   align-items: center;
   justify-content: center;
-  width: 100%;
-  height: 500px;
+  width: 300px;
+  height: 200px;
   background-color: var(--el-fill-color-lighter);
   border-radius: 8px;
   .empty-icon {

+ 7 - 0
src/views/login/index.vue

@@ -600,6 +600,7 @@ import {
   registerAccount,
   registerCheck
 } from "@/api/modules/newLoginApi";
+import { localGet, localSet } from "@/utils";
 
 const router = useRouter();
 const route = useRoute();
@@ -852,6 +853,12 @@ const handleLogin = async () => {
         console.log(res.data, "res.data");
         userStore.setToken(res.data.token);
         userStore.setUserInfo(res.data);
+        if (localGet("geeker-user").userInfo.storeId) {
+          localSet("createdId", localGet("geeker-user").userInfo.storeId);
+        }
+        if (localGet("geeker-user").userInfo.phone) {
+          localSet("iphone", localGet("geeker-user").userInfo.phone);
+        }
         await initDynamicRouter();
 
         // 3.清空 tabs、keepAlive 数据

+ 241 - 108
src/views/orderManagement/detail.vue

@@ -1,90 +1,162 @@
 <template>
-  <div class="order-detail-box">
-    <div class="detail-header">
+  <!-- 订单管理 - 详情页面 -->
+  <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
+    <div class="header">
       <el-button @click="goBack"> 返回 </el-button>
-      <h2 class="detail-title">订单详情</h2>
+      <h2 class="title">订单详情</h2>
     </div>
-    <div class="detail-content">
-      <div class="product-title">
-        {{ formData.productName || "--" }}
-      </div>
-      <el-row :gutter="40" class="detail-row">
-        <el-col :span="12">
-          <div class="detail-section">
-            <el-form :model="formData" label-width="120px">
-              <el-form-item label="订单编号:">
-                <span>{{ formData.orderNo || "--" }}</span>
-              </el-form-item>
-              <el-form-item label="下单时间:">
-                <span>{{ formData.orderTime || "--" }}</span>
-              </el-form-item>
-              <el-form-item label="原价:">
-                <span>¥{{ formData.originalPrice || 0 }}</span>
-              </el-form-item>
-              <el-form-item label="优惠价:">
-                <span>¥{{ formData.discountedPrice || 0 }}</span>
-              </el-form-item>
-              <el-form-item label="数量:">
-                <span>{{ formData.quantity || 0 }}</span>
-              </el-form-item>
-              <el-form-item label="优惠券减免:">
-                <span>¥{{ formData.couponDiscount || 0 }}</span>
-              </el-form-item>
-              <el-form-item label="优惠券类型:">
-                <span>{{ formData.couponType || "--" }}</span>
-              </el-form-item>
-              <el-form-item label="预留手机号:">
-                <span>{{ formData.phone || "--" }}</span>
-              </el-form-item>
-              <el-form-item label="预计收入:">
-                <span>¥{{ formData.estimatedIncome || 0 }}</span>
-              </el-form-item>
-            </el-form>
+    <div class="content">
+      <!-- 左侧内容区域 -->
+      <div class="contentLeft">
+        <!-- 基础信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">基础信息:</h3>
+          <!-- 订单名称 -->
+          <div class="detail-item">
+            <div class="detail-label">订单名称</div>
+            <div class="detail-value">
+              {{ formData.productName || "--" }}
+            </div>
+          </div>
+          <!-- 订单编号 -->
+          <div class="detail-item">
+            <div class="detail-label">订单编号</div>
+            <div class="detail-value">
+              {{ formData.orderNo || "--" }}
+            </div>
+          </div>
+          <!-- 下单时间 -->
+          <div class="detail-item">
+            <div class="detail-label">下单时间</div>
+            <div class="detail-value">
+              {{ formData.orderTime || "--" }}
+            </div>
+          </div>
+          <!-- 原价 -->
+          <div class="detail-item">
+            <div class="detail-label">原价</div>
+            <div class="detail-value">
+              {{ formData.originalPrice ? `¥${formData.originalPrice}` : "--" }}
+            </div>
+          </div>
+          <!-- 优惠价 -->
+          <div class="detail-item">
+            <div class="detail-label">优惠价</div>
+            <div class="detail-value">
+              {{ formData.discountedPrice ? `¥${formData.discountedPrice}` : "--" }}
+            </div>
+          </div>
+          <!-- 数量 -->
+          <div class="detail-item">
+            <div class="detail-label">数量</div>
+            <div class="detail-value">
+              {{ formData.quantity || "--" }}
+            </div>
+          </div>
+          <!-- 优惠券减免 -->
+          <div class="detail-item">
+            <div class="detail-label">优惠券减免</div>
+            <div class="detail-value">
+              {{ formData.couponDiscount ? `¥${formData.couponDiscount}` : "--" }}
+            </div>
+          </div>
+          <!-- 优惠券类型 -->
+          <div class="detail-item">
+            <div class="detail-label">优惠券类型</div>
+            <div class="detail-value">
+              {{ formData.couponType || "--" }}
+            </div>
           </div>
-        </el-col>
-        <el-col :span="12">
-          <div class="detail-section">
-            <div class="coupon-list">
-              <div v-for="(coupon, index) in couponList" :key="index" class="coupon-item">
-                <el-form :model="coupon" label-width="100px">
-                  <el-form-item label="状态:">
-                    <span>{{ getCouponStatusName(coupon.status) }}</span>
-                  </el-form-item>
-                  <el-form-item label="券码:">
-                    <span>{{ coupon.code || "--" }}</span>
-                  </el-form-item>
-                  <el-form-item v-if="coupon.status === '2'" label="核销时间:">
-                    <span>{{ coupon.verifyTime || "--" }}</span>
-                  </el-form-item>
-                  <el-form-item v-if="coupon.status === '3'" label="退款时间:">
-                    <span>{{ coupon.refundTime || "--" }}</span>
-                  </el-form-item>
-                </el-form>
-                <el-divider v-if="index < couponList.length - 1" />
+          <!-- 预留手机号 -->
+          <div class="detail-item">
+            <div class="detail-label">预留手机号</div>
+            <div class="detail-value">
+              {{ formData.phone || "--" }}
+            </div>
+          </div>
+          <!-- 预计收入 -->
+          <div class="detail-item">
+            <div class="detail-label">预计收入</div>
+            <div class="detail-value">
+              {{ formData.estimatedIncome ? `¥${formData.estimatedIncome}` : "--" }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 右侧内容区域 -->
+      <div class="contentRight">
+        <!-- 券码信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">券码信息:</h3>
+          <div v-if="couponList && couponList.length > 0" class="coupon-list">
+            <div v-for="(coupon, index) in couponList" :key="index" class="coupon-item">
+              <!-- 状态 -->
+              <div class="detail-item">
+                <div class="detail-label">状态</div>
+                <div class="detail-value">
+                  {{ getCouponStatusName(coupon.status) }}
+                </div>
+              </div>
+              <!-- 券码 -->
+              <div class="detail-item">
+                <div class="detail-label">券码</div>
+                <div class="detail-value">
+                  {{ coupon.code || "--" }}
+                </div>
+              </div>
+              <!-- 核销时间 -->
+              <div v-if="coupon.status === '2'" class="detail-item">
+                <div class="detail-label">核销时间</div>
+                <div class="detail-value">
+                  {{ coupon.verifyTime || "--" }}
+                </div>
+              </div>
+              <!-- 退款时间 -->
+              <div v-if="coupon.status === '3'" class="detail-item">
+                <div class="detail-label">退款时间</div>
+                <div class="detail-value">
+                  {{ coupon.refundTime || "--" }}
+                </div>
               </div>
+              <!-- 分隔线 -->
+              <el-divider v-if="index < couponList.length - 1" style="margin: 20px 0" />
             </div>
           </div>
-        </el-col>
-      </el-row>
+          <div v-else class="empty-text">--</div>
+        </div>
+      </div>
     </div>
   </div>
 </template>
 
 <script setup lang="tsx" name="orderManagementDetail">
+/**
+ * 订单管理 - 详情页面
+ * 功能:显示订单的详细信息
+ */
 import { ref, onMounted } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { ElMessage } from "element-plus";
 import { getStaffConfigDeatail } from "@/api/modules/staffConfig";
 
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
 const route = useRoute();
 const router = useRouter();
 
+// 页面ID参数
+const id = ref((route.query.id as string) || "");
+
+// 订单数据
 const formData = ref<any>({});
 const couponList = ref<any[]>([]);
 
-const id = ref((route.query.id as string) || "");
+// ==================== 工具函数 ====================
 
-// 券码状态映射
+/**
+ * 获取券码状态名称
+ */
 const getCouponStatusName = (status: string) => {
   const statusMap: Record<string, string> = {
     "1": "未核销",
@@ -94,10 +166,30 @@ const getCouponStatusName = (status: string) => {
   return statusMap[status] || "--";
 };
 
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取ID并加载详情数据
+ */
 onMounted(async () => {
   await initData();
 });
 
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+// ==================== 数据加载函数 ====================
+
+/**
+ * 初始化数据
+ */
 const initData = async () => {
   if (id.value) {
     try {
@@ -143,61 +235,102 @@ const initData = async () => {
     }
   }
 };
-
-const goBack = () => {
-  router.go(-1);
-};
 </script>
 
-<style lang="scss" scoped>
-.order-detail-box {
-  padding: 20px;
-  background: #ffffff;
+<style scoped lang="scss">
+/* 页面容器 */
+.table-box {
+  display: flex;
+  flex-direction: column;
+  height: auto !important;
+  min-height: 100%;
 }
-.detail-header {
+
+/* 头部区域 */
+.header {
   display: flex;
   align-items: center;
-  margin-bottom: 20px;
-  .detail-title {
-    margin: 0 auto;
-    font-size: 20px;
-    font-weight: 500;
-  }
+  padding: 20px 24px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  box-shadow: 0 2px 4px rgb(0 0 0 / 2%);
 }
-.detail-content {
-  .product-title {
-    padding-bottom: 15px;
-    margin-bottom: 30px;
-    font-size: 18px;
-    font-weight: 500;
-    border-bottom: 1px solid #e4e7ed;
-  }
-  .detail-row {
-    margin-top: 20px;
-  }
-  .detail-section {
-    min-height: 400px;
-    padding: 20px;
-    background: #fafafa;
-    border-radius: 4px;
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 24px;
+  width: 98%;
+  padding: 0 12px;
+  margin: 24px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+    padding-right: 12px;
   }
-  .coupon-list {
-    .coupon-item {
-      margin-bottom: 20px;
-      &:last-child {
-        margin-bottom: 0;
-      }
-    }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+    padding-left: 12px;
   }
 }
-:deep(.el-form-item) {
-  margin-bottom: 20px;
-  .el-form-item__label {
-    font-weight: normal;
-    color: #606266;
-  }
-  span {
+
+/* 模块容器 */
+.model {
+  margin-bottom: 50px;
+  h3 {
+    padding-bottom: 12px;
+    margin: 0 0 20px;
+    font-size: 16px;
     color: #303133;
+    border-bottom: 2px solid #e4e7ed;
+  }
+}
+
+/* 详情项样式 */
+.detail-item {
+  display: flex;
+  align-items: flex-start;
+  min-height: 32px;
+  margin-bottom: 24px;
+}
+.detail-label {
+  flex-shrink: 0;
+  min-width: 120px;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 32px;
+  color: #606266;
+}
+.detail-value {
+  flex: 1;
+  font-size: 14px;
+  line-height: 32px;
+  color: #303133;
+  word-break: break-word;
+}
+.empty-text {
+  color: #909399;
+}
+
+/* 券码列表样式 */
+.coupon-list {
+  .coupon-item {
+    margin-bottom: 0;
+    &:last-child {
+      margin-bottom: 0;
+    }
   }
 }
 </style>

+ 15 - 15
src/views/orderManagement/index.vue

@@ -11,7 +11,7 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button type="primary" link @click="showDishDialog(scope.row)"> 查看菜品 </el-button>
+        <!--        <el-button type="primary" link @click="showDishDialog(scope.row)"> 查看菜品 </el-button>-->
         <el-button link type="primary" @click="toDetail(scope.row)"> 订单详情 </el-button>
       </template>
     </ProTable>
@@ -93,20 +93,20 @@ const columns = reactive<ColumnProps<any>[]>([
       props: { placeholder: "商品名称" }
     }
   },
-  {
-    prop: "productType",
-    label: "商品类型",
-    render: (scope: any) => {
-      const type = statusEnum.find(item => item.value === scope.row.productType);
-      return type ? type.label : "--";
-    },
-    search: {
-      el: "select",
-      props: { placeholder: "请选择" }
-    },
-    enum: statusEnum,
-    fieldNames: { label: "label", value: "value" }
-  },
+  // {
+  //   prop: "productType",
+  //   label: "商品类型",
+  //   render: (scope: any) => {
+  //     const type = statusEnum.find(item => item.value === scope.row.productType);
+  //     return type ? type.label : "--";
+  //   },
+  //   search: {
+  //     el: "select",
+  //     props: { placeholder: "请选择" }
+  //   },
+  //   enum: statusEnum,
+  //   fieldNames: { label: "label", value: "value" }
+  // },
   {
     prop: "quantity",
     label: "数量"

+ 333 - 0
src/views/ticketManagement/couponDetail.vue

@@ -0,0 +1,333 @@
+<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>
+    <div class="content">
+      <!-- 左侧内容区域 -->
+      <div class="contentLeft">
+        <!-- 基础信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">基础信息:</h3>
+          <!-- 优惠券名称 -->
+          <div class="detail-item">
+            <div class="detail-label">优惠券名称</div>
+            <div class="detail-value">
+              {{ couponModel.name || "--" }}
+            </div>
+          </div>
+          <!-- 面值 -->
+          <div class="detail-item">
+            <div class="detail-label">面值(元)</div>
+            <div class="detail-value">
+              {{ couponModel.nominalValue ? `¥${couponModel.nominalValue}` : "--" }}
+            </div>
+          </div>
+          <!-- 开始领取时间 -->
+          <div class="detail-item">
+            <div class="detail-label">开始领取时间</div>
+            <div class="detail-value">
+              {{ couponModel.beginGetDate ? formatDate(couponModel.beginGetDate) : "--" }}
+            </div>
+          </div>
+          <!-- 结束领取时间 -->
+          <div class="detail-item">
+            <div class="detail-label">结束领取时间</div>
+            <div class="detail-value">
+              {{ couponModel.endGetDate ? formatDate(couponModel.endGetDate) : "--" }}
+            </div>
+          </div>
+          <!-- 有效期 -->
+          <div class="detail-item">
+            <div class="detail-label">有效期</div>
+            <div class="detail-value">
+              {{ couponModel.specifiedDay ? `购买后${couponModel.specifiedDay}天` : "--" }}
+            </div>
+          </div>
+          <!-- 库存 -->
+          <div class="detail-item">
+            <div class="detail-label">库存</div>
+            <div class="detail-value">
+              {{ couponModel.singleQty || "--" }}
+            </div>
+          </div>
+        </div>
+        <!-- 领取规则模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">领取规则:</h3>
+          <!-- 用户领取规则 -->
+          <div class="detail-item">
+            <div class="detail-label">用户领取规则</div>
+            <div class="detail-value">
+              {{ getClaimRuleText() }}
+            </div>
+          </div>
+          <!-- 用户是否需要收藏店铺领取 -->
+          <div class="detail-item">
+            <div class="detail-label">用户是否需要收藏店铺领取</div>
+            <div class="detail-value">
+              {{ couponModel.attentionCanReceived === 1 ? "是" : couponModel.attentionCanReceived === 0 ? "否" : "--" }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 右侧内容区域 -->
+      <div class="contentRight">
+        <!-- 使用规则模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">使用规则:</h3>
+          <!-- 是否有低消 -->
+          <div class="detail-item">
+            <div class="detail-label">是否有低消</div>
+            <div class="detail-value">
+              {{ couponModel.hasMinimumSpend === 1 ? "是" : couponModel.hasMinimumSpend === 0 ? "否" : "--" }}
+            </div>
+          </div>
+          <!-- 最低消费金额 -->
+          <div class="detail-item" v-if="couponModel.hasMinimumSpend === 1">
+            <div class="detail-label">最低消费金额</div>
+            <div class="detail-value">
+              {{ couponModel.minimumSpendingAmount ? `¥${couponModel.minimumSpendingAmount}` : "--" }}
+            </div>
+          </div>
+          <!-- 补充说明 -->
+          <div class="detail-item">
+            <div class="detail-label">补充说明</div>
+            <div class="detail-value" v-html="couponModel.supplementaryInstruction || '--'" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="couponManagementDetail">
+/**
+ * 优惠券管理 - 详情页面
+ * 功能:显示优惠券的详细信息
+ */
+import { ref, onMounted } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
+import { getCouponDetail } from "@/api/modules/couponManagement";
+
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+const route = useRoute();
+
+// 页面ID参数
+const id = ref<string>("");
+
+// ==================== 优惠券信息数据模型 ====================
+const couponModel = ref<any>({
+  // 优惠券名称
+  name: "",
+  // 面值(元)
+  nominalValue: "",
+  // 开始领取时间
+  beginGetDate: "",
+  // 结束领取时间
+  endGetDate: "",
+  // 有效期
+  specifiedDay: "",
+  // 库存
+  singleQty: "",
+  // 用户领取规则:day-每日一领,week-每周一领,month-每月一领
+  claimRule: "day",
+  // 用户是否需要收藏店铺领取:1-是,0-否
+  attentionCanReceived: 0,
+  // 是否有低消:1-是,0-否
+  hasMinimumSpend: 0,
+  // 最低消费金额
+  minimumSpendingAmount: "",
+  // 补充说明
+  supplementaryInstruction: ""
+});
+
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取ID并加载详情数据
+ */
+onMounted(async () => {
+  id.value = (route.query.id as string) || "";
+  if (id.value) {
+    await loadDetailData();
+  } else {
+    // 如果没有ID,使用假数据展示
+    loadMockData();
+  }
+});
+
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+// ==================== 数据加载函数 ====================
+
+/**
+ * 加载详情数据
+ */
+const loadDetailData = async () => {
+  try {
+    const params: any = {
+      id: id.value
+    };
+    const res: any = await getCouponDetail(params);
+    if (res && res.code == 200) {
+      // 合并主数据
+      couponModel.value = { ...couponModel.value, ...res.data };
+    } else {
+      ElMessage.error("加载详情数据失败");
+      // 加载失败时使用假数据
+      loadMockData();
+    }
+  } catch (error) {
+    console.error("加载详情数据出错:", error);
+    ElMessage.error("加载详情数据出错");
+    // 加载失败时使用假数据
+    loadMockData();
+  }
+};
+
+/**
+ * 加载假数据(用于展示)
+ */
+const loadMockData = () => {
+  couponModel.value = {
+    name: "5元通用优惠券",
+    nominalValue: "5",
+    beginGetDate: "2025-01-01",
+    endGetDate: "2025-04-20",
+    specifiedDay: "30",
+    singleQty: "2000",
+    claimRule: "day",
+    attentionCanReceived: 0,
+    hasMinimumSpend: 1,
+    minimumSpendingAmount: "100",
+    supplementaryInstruction: "本优惠券适用于全场商品,不可与其他优惠叠加使用。"
+  };
+};
+
+// ==================== 工具函数 ====================
+
+/**
+ * 格式化日期
+ * @param date 日期字符串 (YYYY-MM-DD)
+ * @returns 格式化后的日期字符串 (YYYY.MM.DD)
+ */
+const formatDate = (date: string) => {
+  if (!date) return "--";
+  return date.replace(/-/g, ".");
+};
+
+/**
+ * 获取用户领取规则文本
+ */
+const getClaimRuleText = () => {
+  const ruleMap: Record<string, string> = {
+    day: "每日一领",
+    week: "每周一领",
+    month: "每月一领"
+  };
+  return ruleMap[couponModel.value.claimRule] || "--";
+};
+</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 24px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  box-shadow: 0 2px 4px rgb(0 0 0 / 2%);
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 24px;
+  width: 98%;
+  padding: 0 12px;
+  margin: 24px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+    padding-right: 12px;
+  }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+    padding-left: 12px;
+  }
+}
+
+/* 模块容器 */
+.model {
+  margin-bottom: 50px;
+  h3 {
+    padding-bottom: 12px;
+    margin: 0 0 20px;
+    font-size: 16px;
+    color: #303133;
+    border-bottom: 2px solid #e4e7ed;
+  }
+}
+
+/* 详情项样式 */
+.detail-item {
+  display: flex;
+  align-items: flex-start;
+  min-height: 32px;
+  margin-bottom: 24px;
+}
+.detail-label {
+  flex-shrink: 0;
+  min-width: 120px;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 32px;
+  color: #606266;
+}
+.detail-value {
+  flex: 1;
+  font-size: 14px;
+  line-height: 32px;
+  color: #303133;
+  word-break: break-word;
+}
+.empty-text {
+  color: #909399;
+}
+</style>

+ 524 - 0
src/views/ticketManagement/detail.vue

@@ -0,0 +1,524 @@
+<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>
+    <div class="content">
+      <!-- 左侧内容区域 -->
+      <div class="contentLeft">
+        <!-- 基础信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">基础信息:</h3>
+          <!-- 代金券名称 -->
+          <div class="detail-item">
+            <div class="detail-label">代金券名称</div>
+            <div class="detail-value">
+              {{ voucherModel.name || "--" }}
+            </div>
+          </div>
+          <!-- 抵扣价格 -->
+          <div class="detail-item">
+            <div class="detail-label">抵扣价格</div>
+            <div class="detail-value">
+              {{ voucherModel.offprice ? `¥${voucherModel.offprice}` : "--" }}
+            </div>
+          </div>
+          <!-- 售卖价格 -->
+          <div class="detail-item">
+            <div class="detail-label">售卖价格</div>
+            <div class="detail-value">
+              {{ voucherModel.price ? `¥${voucherModel.price}` : "--" }}
+            </div>
+          </div>
+          <!-- 开始售卖时间 -->
+          <div class="detail-item">
+            <div class="detail-label">开始售卖时间</div>
+            <div class="detail-value">
+              {{ voucherModel.startDate ? formatDate(voucherModel.startDate) : "--" }}
+            </div>
+          </div>
+          <!-- 结束售卖时间 -->
+          <div class="detail-item">
+            <div class="detail-label">结束售卖时间</div>
+            <div class="detail-value">
+              {{ voucherModel.endDate ? formatDate(voucherModel.endDate) : "--" }}
+            </div>
+          </div>
+        </div>
+        <!-- 购买须知模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">购买须知:</h3>
+          <!-- 使用时间 -->
+          <div class="detail-item">
+            <div class="detail-label">使用时间</div>
+            <div class="detail-value">
+              {{ getUsageTimeText() }}
+            </div>
+          </div>
+          <!-- 有效期 -->
+          <div class="detail-item">
+            <div class="detail-label">有效期</div>
+            <div class="detail-value">
+              {{ getExpirationText() }}
+            </div>
+          </div>
+          <!-- 不可用日期 -->
+          <div class="detail-item">
+            <div class="detail-label">不可用日期</div>
+            <div class="detail-value">
+              {{ getUnavailableDateText() }}
+            </div>
+          </div>
+          <!-- 库存 -->
+          <div class="detail-item">
+            <div class="detail-label">库存</div>
+            <div class="detail-value">
+              {{ voucherModel.singleQty || "--" }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 右侧内容区域 -->
+      <div class="contentRight">
+        <!-- 使用规则模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">使用规则:</h3>
+          <!-- 单次可用数量 -->
+          <div class="detail-item">
+            <div class="detail-label">单次可用数量</div>
+            <div class="detail-value">
+              {{ voucherModel.singleCanUse || "--" }}
+            </div>
+          </div>
+          <!-- 限购数量 -->
+          <div class="detail-item">
+            <div class="detail-label">限购数量</div>
+            <div class="detail-value">
+              {{ voucherModel.purchaseLimitCode || "--" }}
+            </div>
+          </div>
+          <!-- 适用范围 -->
+          <div class="detail-item">
+            <div class="detail-label">适用范围</div>
+            <div class="detail-value">
+              {{ getApplyScopeText() }}
+            </div>
+          </div>
+          <!-- 补充说明 -->
+          <div class="detail-item">
+            <div class="detail-label">补充说明</div>
+            <div class="detail-value" v-html="voucherModel.supplement || '--'" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="voucherManagementDetail">
+/**
+ * 代金券管理 - 详情页面
+ * 功能:显示代金券的详细信息
+ */
+import { ref, onMounted } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
+import { getVoucherDetail, getHolidayList } from "@/api/modules/voucherManagement";
+
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+const route = useRoute();
+
+// 页面ID参数
+const id = ref<string>("");
+
+// ==================== 代金券信息数据模型 ====================
+const voucherModel = ref<any>({
+  // 代金券名称
+  name: "",
+  // 抵扣价格
+  offprice: "",
+  // 售卖价格
+  price: "",
+  // 开始售卖时间
+  startDate: "",
+  // 结束售卖时间
+  endDate: "",
+  // 使用时间 - 开始时间
+  buyUseStartTime: "",
+  // 使用时间 - 结束时间
+  buyUseEndTime: "",
+  // 有效期类型:0-指定天数,1-指定时间段内可用
+  expirationType: "0",
+  // 有效期天数(当expirationType为0时使用)
+  expirationDate: 0,
+  // 有效期时间段(当expirationType为1时使用)
+  validityPeriod: [],
+  // 不可用日期类型:0-全部日期可用,1-限制日期
+  unusedType: "0",
+  // 限制日期 - 星期选择(数组,存储选中的星期值)
+  unavailableWeekdays: [],
+  // 限制日期 - 节日选择(数组,存储选中的节日值)
+  unavailableHolidays: [],
+  // 自定义不可用日期列表(数组,每个元素是一个日期范围 [startDate, endDate])
+  disableDateList: [],
+  // 库存
+  singleQty: "",
+  // 单日可用数量
+  singleCanUse: "",
+  // 限购数量
+  purchaseLimitCode: "",
+  // 适用范围类型:0-全场通用,1-部分不可用
+  applyType: "1",
+  // 适用范围(当applyType为1时使用)
+  applyDesc: "",
+  // 补充说明
+  supplement: ""
+});
+
+// 星期选项列表
+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([]);
+
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取ID并加载详情数据
+ */
+onMounted(async () => {
+  id.value = (route.query.id as string) || "";
+  // 加载节日列表
+  await loadHolidayList();
+  if (id.value) {
+    await loadDetailData();
+  } else {
+    ElMessage.warning("缺少代金券ID参数");
+  }
+});
+
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+// ==================== 数据加载函数 ====================
+
+/**
+ * 加载节日列表
+ */
+const loadHolidayList = async () => {
+  try {
+    let params = {
+      year: new Date().getFullYear(),
+      page: 1,
+      size: 500,
+      openFlag: 1,
+      holidayName: ""
+    };
+    let res: any = await getHolidayList(params);
+    if (res && res.code == 200) {
+      holidayList.value = res.data.records || [];
+    }
+  } catch (error) {
+    console.error("加载节日列表出错:", error);
+  }
+};
+
+/**
+ * 加载详情数据
+ */
+const loadDetailData = async () => {
+  try {
+    const params = {
+      id: id.value
+    };
+    const res: any = await getVoucherDetail(params);
+    if (res && res.code == 200) {
+      // 合并主数据
+      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) => {
+            const timestamp = Number(item.trim());
+            if (!isNaN(timestamp)) {
+              // 将时间戳转换为日期字符串 (YYYY-MM-DD)
+              return new Date(timestamp).toISOString().split("T")[0];
+            }
+            return null;
+          })
+          .filter((item: any) => item !== null);
+      } 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) : [];
+      } else if (voucherModel.value.unusedType === 2) {
+        // 自定义不可用日期类型:格式为 "开始日期,结束日期;开始日期,结束日期"
+        if (voucherModel.value.unusedDate) {
+          const dateRanges = voucherModel.value.unusedDate.split(";");
+          voucherModel.value.disableDateList = dateRanges
+            .map((range: string) => {
+              const dates = range.split(",");
+              if (dates.length === 2 && dates[0] && dates[1]) {
+                return [dates[0].trim(), dates[1].trim()];
+              }
+              return null;
+            })
+            .filter((item: any) => item !== null);
+        } else {
+          voucherModel.value.disableDateList = [];
+        }
+      }
+    } else {
+      ElMessage.error("加载详情数据失败");
+    }
+  } catch (error) {
+    console.error("加载详情数据出错:", error);
+    ElMessage.error("加载详情数据出错");
+  }
+};
+
+// ==================== 工具函数 ====================
+
+/**
+ * 格式化日期
+ * @param date 日期字符串 (YYYY-MM-DD)
+ * @returns 格式化后的日期字符串 (YYYY.MM.DD)
+ */
+const formatDate = (date: string) => {
+  if (!date) return "--";
+  return date.replace(/-/g, ".");
+};
+
+/**
+ * 获取使用时间文本
+ */
+const getUsageTimeText = () => {
+  if (voucherModel.value.buyUseStartTime && voucherModel.value.buyUseEndTime) {
+    const startHour = formatHour(voucherModel.value.buyUseStartTime);
+    const endHour = formatHour(voucherModel.value.buyUseEndTime);
+    return `${startHour}-${endHour}`;
+  }
+  return "--";
+};
+
+/**
+ * 格式化小时
+ * @param hour 小时值(字符串或数字)
+ * @returns 格式化后的小时文本(如:7:00)
+ */
+const formatHour = (hour: string | number) => {
+  if (!hour && hour !== 0) return "";
+  const hourNum = typeof hour === "string" ? parseInt(hour) : hour;
+  return `${hourNum}:00`;
+};
+
+/**
+ * 获取有效期文本
+ */
+const getExpirationText = () => {
+  if (voucherModel.value.expirationType === "0" || voucherModel.value.expirationType == 0) {
+    if (voucherModel.value.expirationDate) {
+      return `购买后${voucherModel.value.expirationDate}天`;
+    }
+    return "--";
+  } else if (voucherModel.value.expirationType === "1" || voucherModel.value.expirationType == 1) {
+    if (
+      voucherModel.value.validityPeriod &&
+      Array.isArray(voucherModel.value.validityPeriod) &&
+      voucherModel.value.validityPeriod.length === 2
+    ) {
+      const startDate = formatDate(voucherModel.value.validityPeriod[0]);
+      const endDate = formatDate(voucherModel.value.validityPeriod[1]);
+      return `${startDate}-${endDate}`;
+    }
+    return "--";
+  }
+  return "--";
+};
+
+/**
+ * 获取不可用日期文本
+ */
+const getUnavailableDateText = () => {
+  if (voucherModel.value.unusedType === "0" || voucherModel.value.unusedType == 0) {
+    return "全部日期可用";
+  } else if (voucherModel.value.unusedType === "1" || voucherModel.value.unusedType == 1) {
+    const weekdays: string[] = [];
+    const holidays: string[] = [];
+
+    // 处理星期
+    if (voucherModel.value.unavailableWeekdays && voucherModel.value.unavailableWeekdays.length > 0) {
+      voucherModel.value.unavailableWeekdays.forEach((day: string) => {
+        const weekday = weekdayList.value.find(w => w.oName === day);
+        if (weekday) {
+          weekdays.push(weekday.name);
+        }
+      });
+    }
+
+    // 处理节日
+    if (voucherModel.value.unavailableHolidays && voucherModel.value.unavailableHolidays.length > 0) {
+      voucherModel.value.unavailableHolidays.forEach((holidayId: string) => {
+        const holiday = holidayList.value.find((h: any) => h.id === holidayId || String(h.id) === String(holidayId));
+        if (holiday) {
+          holidays.push(holiday.festivalName);
+        }
+      });
+    }
+
+    const parts: string[] = [];
+    if (weekdays.length > 0) {
+      parts.push(weekdays.join("、"));
+    }
+    if (holidays.length > 0) {
+      parts.push(holidays.join("、"));
+    }
+
+    return parts.length > 0 ? parts.join("、") : "--";
+  } else if (voucherModel.value.unusedType === "2" || voucherModel.value.unusedType == 2) {
+    if (voucherModel.value.disableDateList && voucherModel.value.disableDateList.length > 0) {
+      const dateStrings = voucherModel.value.disableDateList
+        .filter((date: any) => date && Array.isArray(date) && date.length === 2)
+        .map((date: any) => {
+          const startDate = formatDate(date[0]);
+          const endDate = formatDate(date[1]);
+          return startDate === endDate ? startDate : `${startDate}-${endDate}`;
+        });
+      return dateStrings.length > 0 ? dateStrings.join("、") : "--";
+    }
+    return "--";
+  }
+  return "--";
+};
+
+/**
+ * 获取适用范围文本
+ */
+const getApplyScopeText = () => {
+  if (voucherModel.value.applyType === "1" || voucherModel.value.applyType == 1) {
+    return "全场通用";
+  } else if (voucherModel.value.applyType === "2" || voucherModel.value.applyType == 2) {
+    if (voucherModel.value.applyDesc) {
+      return `除${voucherModel.value.applyDesc}外全场通用`;
+    }
+    return "全场通用";
+  }
+  return "--";
+};
+</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 24px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  box-shadow: 0 2px 4px rgb(0 0 0 / 2%);
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 24px;
+  width: 98%;
+  padding: 0 12px;
+  margin: 24px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+    padding-right: 12px;
+  }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+    padding-left: 12px;
+  }
+}
+
+/* 模块容器 */
+.model {
+  margin-bottom: 50px;
+  h3 {
+    padding-bottom: 12px;
+    margin: 0 0 20px;
+    font-size: 16px;
+    color: #303133;
+    border-bottom: 2px solid #e4e7ed;
+  }
+}
+
+/* 详情项样式 */
+.detail-item {
+  display: flex;
+  align-items: flex-start;
+  min-height: 32px;
+  margin-bottom: 24px;
+}
+.detail-label {
+  flex-shrink: 0;
+  min-width: 120px;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 32px;
+  color: #606266;
+}
+.detail-value {
+  flex: 1;
+  font-size: 14px;
+  line-height: 32px;
+  color: #303133;
+  word-break: break-word;
+}
+.empty-text {
+  color: #909399;
+}
+</style>

+ 640 - 0
src/views/ticketManagement/index.vue

@@ -0,0 +1,640 @@
+<template>
+  <div class="table-box">
+    <ProTable
+      ref="proTable"
+      :columns="columns"
+      :request-api="getTableList"
+      :init-param="initParam"
+      :data-callback="dataCallback"
+      :key="activeName"
+    >
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <div class="table-header-btn">
+          <div class="header-actions">
+            <el-tabs v-model="activeName" class="tabs" @tab-click="handleClick">
+              <el-tab-pane v-for="tab in allTabOptions" :key="tab.name" :label="tab.label" :name="tab.name" />
+            </el-tabs>
+            <div class="action-buttons">
+              <el-button :icon="Plus" class="button" type="primary" @click="newGroupBuying" v-if="type"> 新建代金券 </el-button>
+              <el-button :icon="Plus" class="button" type="primary" @click="newCoupon" v-if="typeCoupon"> 新建优惠券 </el-button>
+            </div>
+          </div>
+        </div>
+      </template>
+      <template #status="scope">
+        <!-- 代金券:显示状态和审核状态两行 -->
+        <template v-if="activeName === '1'">
+          <p style="margin: 0; line-height: 1.5">状态:{{ getStatusLabel(scope.row.status) || "--" }}</p>
+          <p style="margin: 0; line-height: 1.5">审核状态:{{ getReviewStatusLabel(scope.row.reviewType) || "--" }}</p>
+        </template>
+        <!-- 优惠券:只显示状态一行 -->
+        <template v-else>
+          <span>{{ getStatusLabel(scope.row.status) }}</span>
+        </template>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <!-- 上架按钮 -->
+        <el-button
+          v-if="canShowButton(scope.row.status, currentOperationPermissions.上架)"
+          link
+          type="primary"
+          @click="changeTypes(scope.row, isVoucher ? 1 : 5)"
+        >
+          上架
+        </el-button>
+        <!-- 下架按钮 -->
+        <el-button
+          v-if="canShowButton(scope.row.status, currentOperationPermissions.下架)"
+          link
+          type="primary"
+          @click="changeTypes(scope.row, 6)"
+        >
+          下架
+        </el-button>
+        <!-- 修改库存按钮 -->
+        <el-button
+          v-if="canShowButton(scope.row.status, currentOperationPermissions.修改库存)"
+          link
+          type="primary"
+          @click="changeInventory(scope.row)"
+        >
+          修改库存
+        </el-button>
+        <!-- 查看拒绝原因按钮(仅代金券) -->
+        <el-button
+          v-if="isVoucher && canShowButton(scope.row.status, VOUCHER_OPERATION_PERMISSIONS.查看拒绝原因)"
+          link
+          type="primary"
+          @click="viewRejectReason(scope.row)"
+        >
+          查看拒绝原因
+        </el-button>
+        <!-- 查看详情按钮 -->
+        <el-button
+          v-if="canShowButton(scope.row.status, currentOperationPermissions.查看详情) && (isCoupon || scope.row.dataType != 1)"
+          link
+          type="primary"
+          @click="toDetail(scope.row)"
+        >
+          查看详情
+        </el-button>
+        <!-- 编辑按钮 -->
+        <el-button
+          v-if="canShowButton(scope.row.status, currentOperationPermissions.编辑) || (isVoucher && scope.row.dataType == 1)"
+          link
+          type="primary"
+          @click="editRow(scope.row)"
+        >
+          编辑
+        </el-button>
+        <!-- 删除按钮 -->
+        <el-button
+          v-if="canShowButton(scope.row.status, currentOperationPermissions.删除) || (isVoucher && scope.row.dataType == 1)"
+          link
+          type="primary"
+          @click="deleteRow(scope.row)"
+        >
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <el-dialog v-model="dialogFormVisible" title="修改库存" width="500">
+      <el-form ref="ruleFormRef" :model="formInventory" :rules="rules" @submit.prevent>
+        <el-form-item label="套餐名">
+          {{ formInventory.name }}
+        </el-form-item>
+        <el-form-item label="剩余库存">
+          {{ formInventory.singleQty }}
+        </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>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 查看拒绝原因弹窗 -->
+    <el-dialog v-model="rejectReasonDialogVisible" title="查看拒绝原因" width="600px">
+      <div class="reject-reason-content">
+        <div class="reject-reason-item">
+          <div class="reject-reason-label">代金券名称:</div>
+          <div class="reject-reason-value">
+            {{ rejectReasonData.name || "--" }}
+          </div>
+        </div>
+        <div class="reject-reason-item">
+          <div class="reject-reason-label">拒绝原因:</div>
+          <div class="reject-reason-value reject-reason-text">
+            {{ rejectReasonData.approvalComments || "暂无拒绝原因" }}
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="closeRejectReasonDialog"> 确定 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="voucherManagement">
+import { computed, onActivated, onMounted, reactive, ref } 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 { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { Plus } from "@element-plus/icons-vue";
+import {
+  delThaliById as delVoucherById,
+  getThaliList,
+  updateNum,
+  updateStatus as updateVoucherStatus
+} from "@/api/modules/voucherManagement";
+import { delThaliById as delCouponById, updateStatus as updateCouponStatus } from "@/api/modules/couponManagement";
+import { ElMessageBox } from "element-plus/es";
+import { localGet, usePermission } from "@/utils";
+
+const router = useRouter();
+const dialogFormVisible = ref(false);
+const formInventory: any = ref({
+  id: "",
+  name: "",
+  singleQty: "",
+  newInventory: ""
+});
+const activeName = ref("1");
+// 查看拒绝原因弹窗相关
+const rejectReasonDialogVisible = ref(false);
+const rejectReasonData = ref<any>({
+  name: "",
+  approvalComments: ""
+});
+// 定义表单类型
+interface RuleForm {
+  newInventory: string;
+}
+const ruleFormRef = ref<FormInstance>();
+const rules = reactive<FormRules<RuleForm>>({
+  newInventory: [
+    { required: true, message: "请输入库存数量", trigger: "blur" },
+    {
+      pattern: /^(0|[1-9][0-9]*)$/,
+      message: "请输入整数,不允许输入小数,负数",
+      trigger: "blur"
+    },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (value) {
+          const numValue = Number(value);
+          if (!isNaN(numValue) && numValue > 10000) {
+            callback(new Error("库存不得大于10000"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ]
+});
+const statusEnum = [
+  { label: "全部", value: "" },
+  { label: "草稿", value: "0" },
+  { label: "进行中", value: "5" },
+  { label: "未开始", value: "2" },
+  { label: "已下架", value: "6" },
+  { label: "已售罄", value: "4" },
+  { label: "已结束", value: "7" }
+];
+// ProTable 实例(需要在使用它的地方之前定义)
+const proTable = ref<ProTableInstance>();
+
+// 代金券表格列配置
+const voucherColumns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "name",
+    label: "券名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "price",
+    label: "价格",
+    render: (scope: any) => {
+      return scope.row.price ? `¥${scope.row.price}` : "--";
+    }
+  },
+  {
+    prop: "saleNum",
+    label: "已售",
+    render: scope => {
+      return scope.row.saleNum === null || scope.row.saleNum === undefined ? 0 : scope.row.saleNum;
+    }
+  },
+  {
+    prop: "singleQty",
+    label: "剩余库存",
+    render: scope => {
+      return scope.row.singleQty === null || scope.row.singleQty === undefined ? 0 : scope.row.singleQty;
+    }
+  },
+  {
+    prop: "endDate",
+    label: "结束时间"
+  },
+  {
+    prop: "status",
+    label: "状态",
+    search: {
+      el: "select",
+      props: { placeholder: "请选择" }
+    },
+    enum: statusEnum,
+    fieldNames: { label: "label", value: "value" }
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 330 }
+]);
+
+// 优惠券表格列配置
+const couponColumns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "name",
+    label: "券名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "saleNum",
+    label: "已领",
+    render: scope => {
+      return scope.row.saleNum === null || scope.row.saleNum === undefined ? 0 : scope.row.saleNum;
+    }
+  },
+  {
+    prop: "singleQty",
+    label: "剩余库存",
+    render: scope => {
+      return scope.row.singleQty === null || scope.row.singleQty === undefined ? 0 : scope.row.singleQty;
+    }
+  },
+  {
+    prop: "endDate",
+    label: "结束时间"
+  },
+  {
+    prop: "status",
+    label: "状态",
+    search: {
+      el: "select",
+      props: { placeholder: "请选择" }
+    },
+    enum: statusEnum,
+    fieldNames: { label: "label", value: "value" }
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 330 }
+]);
+
+// 根据当前选中的tab动态返回列配置
+const columns = computed(() => {
+  return activeName.value === "1" ? voucherColumns : couponColumns;
+});
+
+const allTabOptions = [
+  { label: "代金券", name: "1" },
+  { label: "优惠券", name: "2" }
+];
+
+// 状态枚举:0草稿 1待审核 2未开始 3审核拒绝 4已售罄 5进行中 6已下架 7已结束
+const STATUS = {
+  草稿: 0,
+  待审核: 1,
+  未开始: 2,
+  审核拒绝: 3,
+  已售罄: 4,
+  进行中: 5,
+  已下架: 6,
+  已结束: 7
+} as const;
+
+// 代金券操作按钮权限配置:定义每个操作按钮在哪些状态下显示
+const VOUCHER_OPERATION_PERMISSIONS = {
+  // 查看详情:待审核、未开始、审核拒绝、进行中、已售罄、已下架
+  查看详情: [STATUS.待审核, STATUS.未开始, STATUS.审核拒绝, STATUS.进行中, STATUS.已售罄, STATUS.已下架],
+  // 上架:未开始、已下架
+  上架: [STATUS.未开始, STATUS.已下架],
+  // 下架:进行中
+  下架: [STATUS.进行中],
+  // 修改库存:未开始、进行中、已售罄
+  修改库存: [STATUS.未开始, STATUS.进行中, STATUS.已售罄],
+  // 编辑:草稿、审核拒绝、已售罄、已下架、已结束
+  编辑: [STATUS.草稿, STATUS.审核拒绝, STATUS.已售罄, STATUS.已下架, STATUS.已结束],
+  // 删除:草稿、未开始、审核拒绝、已售罄、已结束
+  删除: [STATUS.草稿, STATUS.未开始, STATUS.审核拒绝, STATUS.已售罄, STATUS.已结束],
+  // 查看拒绝原因:审核拒绝
+  查看拒绝原因: [STATUS.审核拒绝]
+} as const;
+
+// 优惠券操作按钮权限配置
+const COUPON_OPERATION_PERMISSIONS = {
+  // 查看详情:草稿、未开始、进行中、已下架、已结束、已售罄
+  查看详情: [STATUS.草稿, STATUS.未开始, STATUS.进行中, STATUS.已下架, STATUS.已结束, STATUS.已售罄],
+  // 上架:未开始、已下架
+  上架: [STATUS.未开始, STATUS.已下架],
+  // 下架:进行中
+  下架: [STATUS.进行中],
+  // 修改库存:未开始、进行中、已售罄
+  修改库存: [STATUS.未开始, STATUS.进行中, STATUS.已售罄],
+  // 编辑:草稿、未开始、进行中、已下架、已结束、已售罄
+  编辑: [STATUS.草稿, STATUS.未开始, STATUS.进行中, STATUS.已下架, STATUS.已结束, STATUS.已售罄],
+  // 删除:草稿、已结束、已售罄
+  删除: [STATUS.草稿, STATUS.已结束, STATUS.已售罄]
+} as const;
+
+// 判断按钮是否显示的工具函数
+const canShowButton = (status: number, allowedStatuses: readonly number[]) => {
+  return allowedStatuses.includes(status);
+};
+
+// 根据当前tab获取操作权限配置
+const currentOperationPermissions = computed(() => {
+  return activeName.value === "1" ? VOUCHER_OPERATION_PERMISSIONS : COUPON_OPERATION_PERMISSIONS;
+});
+
+// 判断是否为代金券
+const isVoucher = computed(() => activeName.value === "1");
+
+// 判断是否为优惠券
+const isCoupon = computed(() => activeName.value === "2");
+
+// 获取状态标签
+const getStatusLabel = (status: number) => {
+  const statusMap: Record<number, string> = {
+    0: "草稿",
+    1: "待审核",
+    2: "未开始",
+    3: "审核拒绝",
+    4: "已售罄",
+    5: "进行中",
+    6: "已下架",
+    7: "已结束"
+  };
+  return statusMap[status] || "--";
+};
+
+// 获取审核状态标签
+const getReviewStatusLabel = (reviewType: string | number) => {
+  if (reviewType === null || reviewType === undefined || reviewType === "") {
+    return "";
+  }
+  const reviewStatusMap: Record<string, string> = {
+    "0": "待审核",
+    "1": "审核通过",
+    "2": "审核驳回"
+  };
+  return reviewStatusMap[String(reviewType)] || "";
+};
+
+// 数据类型:1-草稿,0-已发布,2-全部
+const dataType = computed(() => {
+  if (!activeName.value) return 2;
+  return activeName.value === "0" ? 1 : 0;
+});
+
+// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
+const initParam = reactive({
+  storeId: localGet("createdId"),
+  groupType: localGet("businessSection"),
+  status: activeName,
+  dataType: dataType
+});
+const type = ref(false);
+const typeCoupon = ref(false);
+// 页面加载时触发查询
+onMounted(async () => {
+  type.value = await usePermission("新建代金券");
+  typeCoupon.value = await usePermission("新建优惠券");
+  proTable.value?.getTableList();
+});
+
+// 从其他页面返回时触发查询
+onActivated(() => {
+  proTable.value?.getTableList();
+});
+
+// dataCallback 是对于返回的表格数据做处理,如果你后台返回的数据不是 list && total 这些字段,可以在这里进行处理成这些字段
+// 或者直接去 hooks/useTable.ts 文件中把字段改为你后端对应的就行
+const dataCallback = (data: any) => {
+  return {
+    list: data.records,
+    total: data.total
+  };
+};
+
+// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
+// 默认不做操作就直接在 ProTable 组件上绑定	:requestApi="getUserList"
+const getTableList = (params: any) => {
+  let newParams = JSON.parse(JSON.stringify(params));
+  return getThaliList(newParams);
+};
+const newGroupBuying = () => {
+  router.push(`/ticketManagement/newVoucher?type=add`);
+};
+const newCoupon = () => {
+  router.push(`/ticketManagement/newCoupon?type=add`);
+};
+// 跳转详情页 - 根据类型跳转不同页面
+const toDetail = (row: any) => {
+  const path = isVoucher.value ? `/ticketManagement/detail?id=${row.id}` : `/ticketManagement/couponDetail?id=${row.id}`;
+  router.push(path);
+};
+// 编辑行数据
+const editRow = (row: any) => {
+  const path = isVoucher.value
+    ? `/ticketManagement/newVoucher?id=${row.id}&type=edit`
+    : `/ticketManagement/newCoupon?id=${row.id}&type=edit`;
+  router.push(path);
+};
+// 删除行数据
+const deleteRow = (row: any) => {
+  ElMessageBox.confirm("确定要删除吗?", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(() => {
+      if (isVoucher.value) {
+        // 代金券删除逻辑
+        const params = {
+          id: row.id,
+          groupType: localGet("businessSection")
+        };
+        return delVoucherById(params);
+      } else {
+        // 优惠券删除逻辑
+        const params = {
+          id: row.id,
+          groupType: localGet("businessSection")
+        };
+        return delCouponById(params);
+      }
+    })
+    .then(() => {
+      ElMessage.success("删除成功");
+      proTable.value?.getTableList();
+    })
+    .catch(() => {
+      // 用户取消删除,不做任何操作
+    });
+};
+// Tab切换处理
+const handleClick = () => {
+  proTable.value?.getTableList();
+};
+
+// 修改状态(上架/下架)
+const changeTypes = async (row: any, status: number) => {
+  if (isVoucher.value) {
+    // 代金券上架/下架逻辑
+    const res = await updateVoucherStatus({ id: row.id, status: status, approvalComments: "" });
+    if (res && res.code == 200) {
+      ElMessage.success("操作成功");
+      proTable.value?.getTableList();
+    }
+  } else {
+    // 优惠券上架/下架逻辑
+    const res = await updateCouponStatus({ id: row.id, status: status, approvalComments: "" });
+    if (res && res.code == 200) {
+      ElMessage.success("操作成功");
+      proTable.value?.getTableList();
+    }
+  }
+};
+// 修改库存
+const changeInventory = (row: any) => {
+  formInventory.value = {
+    id: row.id,
+    name: row.name,
+    singleQty: row.singleQty,
+    newInventory: ""
+  };
+  dialogFormVisible.value = true;
+};
+
+// 提交修改库存
+const handleSubmit = async () => {
+  if (!ruleFormRef.value) return;
+  await ruleFormRef.value.validate(async valid => {
+    if (valid) {
+      const res = await updateNum({
+        id: formInventory.value.id,
+        singleQty: formInventory.value.newInventory
+      });
+      if (res && res.code == 200) {
+        ElMessage.success("修改成功");
+        closeDialog();
+        proTable.value?.getTableList();
+      }
+    }
+  });
+};
+
+// 关闭弹窗
+const closeDialog = () => {
+  dialogFormVisible.value = false;
+  formInventory.value = {
+    id: "",
+    name: "",
+    singleQty: "",
+    newInventory: ""
+  };
+};
+// 查看拒绝原因
+const viewRejectReason = (row: any) => {
+  rejectReasonData.value = {
+    name: row.name || "--",
+    approvalComments: row.approvalComments || "--"
+  };
+  rejectReasonDialogVisible.value = true;
+};
+// 关闭拒绝原因弹窗
+const closeRejectReasonDialog = () => {
+  rejectReasonDialogVisible.value = false;
+  rejectReasonData.value = {
+    name: "",
+    approvalComments: ""
+  };
+};
+</script>
+
+<style lang="scss" scoped>
+// 在组件样式中添加
+.date-range {
+  display: block; // 确保换行生效
+  padding: 0 8px; // 可选:增加内边距
+  word-wrap: break-word; // 长单词内换行
+  white-space: normal; // 允许自然换行
+}
+.table-header-btn {
+  width: 160vh;
+  .header-actions {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    .tabs {
+      flex: 0 0 auto;
+      :deep(.el-tabs__nav-wrap::after) {
+        height: 0;
+      }
+    }
+    .action-buttons {
+      display: flex;
+      flex: 0 0 auto;
+      gap: 10px;
+      .button {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+.reject-reason-content {
+  padding: 20px 0;
+  .reject-reason-item {
+    display: flex;
+    margin-bottom: 20px;
+    &:last-child {
+      margin-bottom: 0;
+    }
+    .reject-reason-label {
+      flex-shrink: 0;
+      min-width: 100px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #606266;
+    }
+    .reject-reason-value {
+      flex: 1;
+      font-size: 14px;
+      color: #303133;
+      word-break: break-word;
+      &.reject-reason-text {
+        min-height: 80px;
+        padding: 12px;
+        line-height: 1.6;
+        white-space: pre-wrap;
+        background-color: #f5f7fa;
+        border-radius: 4px;
+      }
+    }
+  }
+}
+</style>

+ 426 - 0
src/views/ticketManagement/newCoupon.vue

@@ -0,0 +1,426 @@
+<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="name">
+            <el-input maxlength="50" v-model="couponModel.name" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 面值 -->
+          <el-form-item label="面值(元)" prop="nominalValue">
+            <el-input v-model="couponModel.nominalValue" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 开始领取时间 -->
+          <el-form-item label="开始领取时间" prop="beginGetDate">
+            <el-date-picker
+              v-model="couponModel.beginGetDate"
+              format="YYYY/MM/DD"
+              value-format="YYYY-MM-DD"
+              placeholder="请选择开始领取时间"
+              :disabled-date="disabledStartDate"
+            />
+          </el-form-item>
+          <!-- 结束领取时间 -->
+          <el-form-item label="结束领取时间" prop="endGetDate">
+            <el-date-picker
+              v-model="couponModel.endGetDate"
+              format="YYYY/MM/DD"
+              value-format="YYYY-MM-DD"
+              placeholder="请选择结束领取时间"
+              :disabled-date="disabledEndDate"
+            />
+          </el-form-item>
+          <!-- 有效期 -->
+          <el-form-item label="有效期" prop="specifiedDay">
+            <el-input v-model="couponModel.specifiedDay" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 库存 -->
+          <el-form-item label="库存" prop="singleQty">
+            <el-input v-model="couponModel.singleQty" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 用户领取规则 -->
+          <el-form-item label="用户领取规则" prop="claimRule">
+            <el-radio-group v-model="couponModel.claimRule" class="ml-4">
+              <el-radio v-for="item in claimRuleOptions" :key="item.value" :value="item.value">
+                {{ item.label }}
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <!-- 用户是否需要收藏店铺领取 -->
+          <el-form-item label="用户是否需要收藏店铺领取" prop="attentionCanReceived">
+            <el-radio-group v-model="couponModel.attentionCanReceived" class="ml-4">
+              <el-radio v-for="item in yesNoOptions" :key="item.value" :value="item.value">
+                {{ item.label }}
+              </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 v-for="item in yesNoOptions" :key="item.value" :value="item.value">
+                {{ item.label }}
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <!-- 最低消费金额 -->
+          <el-form-item label="最低消费金额" prop="minimumSpendingAmount" v-if="couponModel.hasMinimumSpend === 1">
+            <el-input v-model="couponModel.minimumSpendingAmount" maxlength="15" placeholder="请输入" clearable />
+          </el-form-item>
+          <!-- 补充说明 -->
+          <el-form-item label="补充说明" prop="supplementaryInstruction">
+            <el-input
+              maxlength="300"
+              v-model="couponModel.supplementaryInstruction"
+              :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, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+import { useRoute, useRouter } from "vue-router";
+import type { FormInstance } from "element-plus";
+import { getCouponDetail } from "@/api/modules/couponManagement";
+import { validatePositiveNumber, validatePositiveInteger, validateDateRange } from "@/utils/eleValidate";
+import { localGet } from "@/utils";
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+const route = useRoute();
+
+// 页面状态
+const type = ref<string>(""); // 页面类型:add-新增, edit-编辑
+const id = ref<string>(""); // 页面ID参数
+// ==================== 表单验证规则 ====================
+const rules = reactive({
+  name: [{ required: true, message: "请输入优惠券名称" }],
+  nominalValue: [
+    { required: true, message: "请输入面值" },
+    {
+      validator: validatePositiveNumber("面值必须为正数"),
+      trigger: "blur"
+    }
+  ],
+  beginGetDate: [
+    { required: true, message: "请选择开始领取时间" },
+    {
+      validator: validateDateRange(
+        () => couponModel.value.beginGetDate,
+        () => couponModel.value.endGetDate,
+        "开始领取时间不能早于当前时间",
+        "结束领取时间不能早于当前时间",
+        "开始领取时间必须早于结束领取时间",
+        true
+      ),
+      trigger: "change"
+    }
+  ],
+  endGetDate: [
+    { required: true, message: "请选择结束领取时间" },
+    {
+      validator: validateDateRange(
+        () => couponModel.value.beginGetDate,
+        () => couponModel.value.endGetDate,
+        "开始领取时间不能早于当前时间",
+        "结束领取时间不能早于当前时间",
+        "开始领取时间必须早于结束领取时间",
+        true
+      ),
+      trigger: "change"
+    }
+  ],
+  specifiedDay: [
+    { required: true, message: "请输入有效期" },
+    {
+      validator: validatePositiveInteger("有效期必须为正整数", { required: false }),
+      trigger: "blur"
+    }
+  ],
+  singleQty: [
+    { required: true, message: "请输入库存" },
+    {
+      validator: validatePositiveInteger("库存必须为正整数", { required: false }),
+      trigger: "blur"
+    }
+  ],
+  minimumSpendingAmount: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (couponModel.value.hasMinimumSpend === 1) {
+          if (!value || value.toString().trim() === "") {
+            callback(new Error("请输入最低消费金额"));
+            return;
+          }
+          validatePositiveNumber("最低消费金额必须为正数")(rule, value, callback);
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur"
+    }
+  ]
+});
+
+// ==================== 选项配置 ====================
+// 用户领取规则选项
+const claimRuleOptions = [
+  { label: "每日一领", value: "day" },
+  { label: "每周一领", value: "week" },
+  { label: "每月一领", value: "month" }
+];
+
+// 是/否选项
+const yesNoOptions = [
+  { label: "是", value: 1 },
+  { label: "否", value: 0 }
+];
+
+// ==================== 优惠券信息数据模型 ====================
+const couponModel = ref<any>({
+  // 优惠券名称
+  name: "",
+  // 面值(元)
+  nominalValue: "",
+  // 开始领取时间
+  beginGetDate: "",
+  // 结束领取时间
+  endGetDate: "",
+  // 有效期
+  specifiedDay: "",
+  // 库存
+  singleQty: "",
+  // 用户领取规则:day-每日一领,week-每周一领,month-每月一领
+  claimRule: "day",
+  // 用户是否需要收藏店铺领取:1-是,0-否
+  attentionCanReceived: 1,
+  // 是否有低消:1-是,0-否
+  hasMinimumSpend: 1,
+  // 最低消费金额
+  minimumSpendingAmount: "",
+  // 补充说明
+  supplementaryInstruction: ""
+});
+
+// ==================== 监听器 ====================
+
+/**
+ * 监听开始领取时间变化
+ * 当开始时间改变时,重新验证结束时间
+ */
+watch(
+  () => couponModel.value.beginGetDate,
+  () => {
+    if (couponModel.value.endGetDate) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("endGetDate");
+      });
+    }
+  }
+);
+
+/**
+ * 监听结束领取时间变化
+ * 当结束时间改变时,重新验证开始时间
+ */
+watch(
+  () => couponModel.value.endGetDate,
+  () => {
+    if (couponModel.value.beginGetDate) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("beginGetDate");
+      });
+    }
+  }
+);
+
+/**
+ * 监听是否有低消变化
+ * 当选择"否"时,清空最低消费金额
+ */
+watch(
+  () => couponModel.value.hasMinimumSpend,
+  newVal => {
+    if (newVal === 0) {
+      couponModel.value.minimumSpendingAmount = "";
+      nextTick(() => {
+        ruleFormRef.value?.clearValidate("minimumSpendingAmount");
+      });
+    }
+  }
+);
+
+// ==================== 事件处理函数 ====================
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取页面类型和ID
+ */
+onMounted(async () => {
+  id.value = (route.query.id as string) || "";
+  type.value = (route.query.type as string) || "";
+  if (type.value != "add") {
+    // TODO: 加载优惠券详情数据
+    // let res: any = await getCouponDetail({ id: id.value });
+  }
+});
+/**
+ * 返回上一页
+ */
+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 };
+    params.storeId = localGet("createdId");
+    params.status = 3;
+    params.couponId = "couponId";
+    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.beginGetDate) {
+    const startDate = new Date(couponModel.value.beginGetDate);
+    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>

+ 1232 - 0
src/views/ticketManagement/newVoucher.vue

@@ -0,0 +1,1232 @@
+<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="name">
+              <el-input maxlength="20" v-model="voucherModel.name" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 抵扣价格 -->
+            <el-form-item label="抵扣价格(¥)" prop="offprice">
+              <el-input v-model="voucherModel.offprice" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 售卖价格 -->
+            <el-form-item label="售卖价格(¥)" prop="price">
+              <el-input v-model="voucherModel.price" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 开始售卖时间 -->
+            <el-form-item label="开始售卖时间" prop="startDate">
+              <el-date-picker
+                v-model="voucherModel.startDate"
+                format="YYYY/MM/DD"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择开始售卖时间"
+                :disabled-date="disabledStartDate"
+              />
+            </el-form-item>
+            <!-- 结束售卖时间 -->
+            <el-form-item label="结束售卖时间" prop="endDate">
+              <el-date-picker
+                v-model="voucherModel.endDate"
+                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">
+              <div class="time-range-container">
+                <el-select v-model="voucherModel.buyUseStartTime" placeholder="开始时间" class="time-picker">
+                  <el-option v-for="hour in hourOptions" :key="hour.value" :label="hour.label" :value="hour.value" />
+                </el-select>
+                <span class="time-separator">至</span>
+                <el-select v-model="voucherModel.buyUseEndTime" placeholder="结束时间" class="time-picker">
+                  <el-option v-for="hour in hourOptions" :key="hour.value" :label="hour.label" :value="hour.value" />
+                </el-select>
+              </div>
+            </el-form-item>
+            <!-- 有效期 -->
+            <el-form-item label="有效期" prop="expirationType">
+              <el-radio-group v-model="voucherModel.expirationType" 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="expirationDate" v-if="voucherModel.expirationType == 0">
+              <div class="expiration-date-container">
+                <span class="expiration-label">用户购买</span>
+                <el-input-number
+                  v-model="voucherModel.expirationDate"
+                  placeholder="请输入"
+                  :min="0"
+                  :max="10000"
+                  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="x"
+                range-separator="-"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                :disabled-date="disabledValidityDate"
+              />
+            </el-form-item>
+            <!-- 不可用日期 -->
+            <el-form-item label="不可用日期" prop="unusedType">
+              <el-radio-group v-model="voucherModel.unusedType" 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.unusedType == 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(String(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.unusedType == 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 voucherModel.disableDateList" :key="index" class="date-item">
+                  <el-date-picker
+                    v-model="voucherModel.disableDateList[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="voucherModel.disableDateList.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="singleQty">
+              <el-input v-model="voucherModel.singleQty" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+          </div>
+          <!-- 使用规则模块 -->
+          <div class="model">
+            <h3 style="font-weight: bold">使用规则:</h3>
+            <!-- 单次可用数量 -->
+            <el-form-item label="单次可用数量" prop="singleCanUse">
+              <el-input v-model="voucherModel.singleCanUse" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 限购数量 -->
+            <el-form-item label="限购数量" prop="purchaseLimitCode">
+              <el-input v-model="voucherModel.purchaseLimitCode" maxlength="15" placeholder="请输入" clearable />
+            </el-form-item>
+            <!-- 适用范围 -->
+            <el-form-item label="适用范围" prop="applyType">
+              <el-radio-group v-model="voucherModel.applyType" 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="applyDesc" v-if="voucherModel.applyType == 2">
+              <el-input
+                maxlength="50"
+                v-model="voucherModel.applyDesc"
+                :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="supplement">
+              <el-input
+                maxlength="300"
+                v-model="voucherModel.supplement"
+                :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, watch, nextTick, onMounted } from "vue";
+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, addOrUpdateCoupon } from "@/api/modules/voucherManagement";
+import {
+  validatePositiveNumber,
+  validatePositiveInteger,
+  validateDateRange,
+  validateDateRangeArray,
+  validateConditionalRequired,
+  validateArrayMinLength,
+  validateDateListArray,
+  validatePriceFormat,
+  validatePriceComparison
+} from "@/utils/eleValidate";
+import { localGet } from "@/utils";
+
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+const route = useRoute();
+
+// 页面状态
+const type = ref<string>(""); // 页面类型:add-新增, edit-编辑
+const id = ref<string>(""); // 页面ID参数
+
+// ==================== 表单验证规则 ====================
+const rules = reactive({
+  name: [{ required: true, message: "请输入代金券名称" }],
+  offprice: [
+    { required: true, message: "请输入抵扣价格" },
+    {
+      validator: validatePositiveNumber("抵扣价格必须为正数"),
+      trigger: "blur"
+    },
+    {
+      validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
+      trigger: "blur"
+    },
+    {
+      validator: validatePriceComparison(
+        () => voucherModel.value.offprice,
+        () => voucherModel.value.price,
+        "抵扣价格不能低于售卖价格"
+      ),
+      trigger: "blur"
+    }
+  ],
+  price: [
+    { required: true, message: "请输入售卖价格" },
+    {
+      validator: validatePositiveNumber("售卖价格必须为正数"),
+      trigger: "blur"
+    },
+    {
+      validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
+      trigger: "blur"
+    },
+    {
+      validator: validatePriceComparison(
+        () => voucherModel.value.offprice,
+        () => voucherModel.value.price,
+        "抵扣价格不能低于售卖价格"
+      ),
+      trigger: "blur"
+    }
+  ],
+  startDate: [
+    { 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;
+        }
+        // 验证开始时间必须早于结束时间
+        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: (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"
+    }
+  ],
+  usageTime: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (!voucherModel.value.buyUseStartTime || !voucherModel.value.buyUseEndTime) {
+          callback(new Error("请选择使用时间"));
+          return;
+        }
+        callback();
+      },
+      trigger: []
+    }
+  ],
+  expirationType: [{ required: true, message: "请选择有效期类型" }],
+  expirationDate: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.expirationType == 0) {
+          if (value === null || value === undefined || value === "") {
+            callback(new Error("请输入用户购买天数"));
+            return;
+          }
+          // 先验证是否为正整数
+          validatePositiveInteger("用户购买天数必须为正整数", { required: false, checkLeadingZero: false })(
+            rule,
+            value,
+            (error: any) => {
+              if (error) {
+                callback(error);
+                return;
+              }
+              // 验证最大值
+              const numValue = Number(value);
+              if (!isNaN(numValue) && numValue > 10000) {
+                callback(new Error("有效期不得大于10000"));
+                return;
+              }
+              callback();
+            }
+          );
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur"
+    }
+  ],
+  validityPeriod: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.expirationType == 1) {
+          if (!value || value.length !== 2) {
+            callback(new Error("请选择指定时间段"));
+            return;
+          }
+          validateDateRangeArray("开始时间必须早于结束时间", true, "时间不能早于当前时间")(rule, value, callback);
+        } else {
+          callback();
+        }
+      },
+      trigger: "change"
+    }
+  ],
+  unusedType: [{ required: true, message: "请选择不可用日期类型" }],
+  unavailableWeekdays: [
+    {
+      validator: validateConditionalRequired(() => voucherModel.value.unusedType == 1, "至少需要选择一个星期"),
+      trigger: "change"
+    }
+  ],
+  unavailableHolidays: [
+    {
+      validator: validateConditionalRequired(() => voucherModel.value.unusedType == 1, "至少需要选择一个节日"),
+      trigger: "change"
+    }
+  ],
+  customUnavailableDates: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.unusedType == 2) {
+          if (!voucherModel.value.disableDateList || voucherModel.value.disableDateList.length === 0) {
+            callback(new Error("至少需要添加一个自定义不可用日期"));
+            return;
+          }
+          validateDateListArray(
+            () => voucherModel.value.disableDateList,
+            "日期项未完整填写",
+            "开始时间必须早于结束时间",
+            true
+          )(rule, value, callback);
+        } else {
+          callback();
+        }
+      },
+      trigger: "change"
+    }
+  ],
+  singleQty: [
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        // 非必填,如果为空则直接通过
+        if (!value || value.toString().trim() === "") {
+          callback();
+          return;
+        }
+        // 先验证是否为正整数
+        validatePositiveInteger("库存必须为正整数", { required: false })(rule, value, (error: any) => {
+          if (error) {
+            callback(error);
+            return;
+          }
+          // 验证最大值
+          if (value) {
+            const numValue = Number(value);
+            if (!isNaN(numValue) && numValue > 10000) {
+              callback(new Error("库存不得大于10000"));
+              return;
+            }
+          }
+          callback();
+        });
+      },
+      trigger: "blur"
+    }
+  ],
+  singleCanUse: [
+    {
+      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: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (voucherModel.value.applyType == 2) {
+          if (!value || value.toString().trim() === "") {
+            callback(new Error("请输入适用范围"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
+    }
+  ]
+});
+
+// ==================== 代金券信息数据模型 ====================
+const voucherModel = ref<any>({
+  // 代金券名称
+  name: "",
+  // 抵扣价格
+  offprice: "",
+  // 售卖价格
+  price: "",
+  // 开始售卖时间
+  startDate: "",
+  // 结束售卖时间
+  endDate: "",
+  // 使用时间 - 开始时间
+  buyUseStartTime: "",
+  // 使用时间 - 结束时间
+  buyUseEndTime: "",
+  // 使用时间(虚拟字段,用于表单验证)
+  usageTime: null,
+  // 有效期类型:0-指定天数,1-指定时间段内可用
+  expirationType: "0",
+  // 有效期天数(当expirationType为0时使用)
+  expirationDate: 0,
+  // 有效期时间段(当expirationType为1时使用)
+  validityPeriod: [],
+  // 不可用日期类型:0-全部日期可用,1-限制日期
+  unusedType: "0",
+  // 限制日期 - 星期选择(数组,存储选中的星期值)
+  unavailableWeekdays: [],
+  // 限制日期 - 节日选择(数组,存储选中的节日值)
+  unavailableHolidays: [],
+  // 自定义不可用日期列表(数组,每个元素是一个日期范围 [startDate, endDate])
+  disableDateList: [],
+  // 库存
+  singleQty: "",
+  // 单日可用数量
+  singleCanUse: "",
+  // 限购数量
+  purchaseLimitCode: "",
+  // 适用范围类型:0-全场通用,1-部分不可用
+  applyType: "1",
+  // 适用范围(当applyType为1时使用)
+  applyDesc: "",
+  // 补充说明
+  supplement: ""
+});
+
+// ==================== 下拉选项数据 ====================
+
+// 小时选项列表(0-23点)
+const hourOptions = ref(
+  Array.from({ length: 24 }, (_, i) => ({
+    value: String(i),
+    label: `${i}点`
+  }))
+);
+
+// 有效期类型列表
+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: "1", label: "全场通用" },
+  { value: "2", label: "部分不可用" }
+]);
+
+// 星期选项列表
+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.startDate,
+  () => {
+    if (voucherModel.value.endDate) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("endDate");
+      });
+    }
+  }
+);
+
+/**
+ * 监听结束售卖时间变化
+ * 当结束时间改变时,重新验证开始时间
+ */
+watch(
+  () => voucherModel.value.endDate,
+  () => {
+    if (voucherModel.value.startDate) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("startDate");
+      });
+    }
+  }
+);
+
+/**
+ * 监听抵扣价格变化
+ * 当抵扣价格改变时,重新验证售卖价格
+ */
+watch(
+  () => voucherModel.value.offprice,
+  () => {
+    if (voucherModel.value.price) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("price");
+      });
+    }
+  }
+);
+
+/**
+ * 监听售卖价格变化
+ * 当售卖价格改变时,重新验证抵扣价格
+ */
+watch(
+  () => voucherModel.value.price,
+  () => {
+    if (voucherModel.value.offprice) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("offprice");
+      });
+    }
+  }
+);
+
+/**
+ * 监听库存变化
+ * 当库存改变时,重新验证单日可用数量和限购数量
+ */
+watch(
+  () => voucherModel.value.singleQty,
+  () => {
+    if (voucherModel.value.singleCanUse) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("singleCanUse");
+      });
+    }
+    if (voucherModel.value.purchaseLimitCode) {
+      nextTick(() => {
+        ruleFormRef.value?.validateField("purchaseLimitCode");
+      });
+    }
+  }
+);
+
+/**
+ * 监听使用开始时间变化
+ * 更新虚拟字段以支持表单验证
+ */
+watch(
+  () => voucherModel.value.buyUseStartTime,
+  () => {
+    // 更新虚拟字段
+    voucherModel.value.usageTime =
+      voucherModel.value.buyUseStartTime && voucherModel.value.buyUseEndTime
+        ? [voucherModel.value.buyUseStartTime, voucherModel.value.buyUseEndTime]
+        : null;
+  }
+);
+
+/**
+ * 监听使用结束时间变化
+ * 更新虚拟字段以支持表单验证
+ */
+watch(
+  () => voucherModel.value.buyUseEndTime,
+  () => {
+    // 更新虚拟字段
+    voucherModel.value.usageTime =
+      voucherModel.value.buyUseStartTime && voucherModel.value.buyUseEndTime
+        ? [voucherModel.value.buyUseStartTime, voucherModel.value.buyUseEndTime]
+        : null;
+  }
+);
+
+/**
+ * 监听不可用日期类型变化
+ * 当切换到自定义不可用日期时,确保至少有一个日期项
+ */
+watch(
+  () => voucherModel.value.unusedType,
+  newVal => {
+    if (newVal == 2) {
+      // 切换到自定义不可用日期时,如果disableDateList为空,则添加一个默认项
+      if (!voucherModel.value.disableDateList || voucherModel.value.disableDateList.length === 0) {
+        voucherModel.value.disableDateList = [null];
+      }
+    }
+  },
+  { immediate: true }
+);
+
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取页面类型和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: any = await getHolidayList(params);
+  if (res && res.code == 200) {
+    holidayList.value = res.data.records;
+  }
+
+  // 编辑模式下加载数据
+  if (type.value != "add") {
+    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);
+  }
+});
+
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-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 valueStr = String(value);
+  const index = voucherModel.value.unavailableHolidays.findIndex((item: any) => String(item) === valueStr);
+  if (index > -1) {
+    voucherModel.value.unavailableHolidays.splice(index, 1);
+  } else {
+    voucherModel.value.unavailableHolidays.push(valueStr);
+  }
+  // 触发表单验证
+  nextTick(() => {
+    ruleFormRef.value?.validateField("unavailableHolidays");
+  });
+};
+
+/**
+ * 添加自定义不可用日期
+ */
+const addDate = () => {
+  if (!voucherModel.value.disableDateList) {
+    voucherModel.value.disableDateList = [];
+  }
+  voucherModel.value.disableDateList.push(null);
+};
+
+/**
+ * 删除自定义不可用日期
+ * @param index 要删除的日期索引
+ */
+const removeDate = (index: number) => {
+  if (voucherModel.value.disableDateList.length <= 1) {
+    ElMessage.warning("至少需要保留一个日期项");
+    return;
+  }
+  voucherModel.value.disableDateList.splice(index, 1);
+  // 删除日期项后,重新验证表单以清除旧的验证错误
+  nextTick(() => {
+    ruleFormRef.value?.validateField("customUnavailableDates");
+  });
+};
+
+// ==================== 表单引用 ====================
+const ruleFormRef = ref<FormInstance>(); // 表单引用
+
+/**
+ * 提交数据(新增/编辑)
+ * 验证表单,通过后调用相应的API接口
+ */
+const handleSubmit = async (submitType?: string) => {
+  // 组装提交参数
+  let params: any = { ...voucherModel.value };
+  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) {
+    params.unusedDate = params.unavailableWeekdays.join(",") + ";" + params.unavailableHolidays.join(",");
+  } else if (params.unusedType == 2) {
+    // 处理自定义不可用日期
+    if (params.disableDateList && params.disableDateList.length > 0) {
+      params.unusedDate = params.disableDateList
+        .map((dateRange: any) => (dateRange && dateRange.length === 2 ? dateRange.join(",") : ""))
+        .filter((item: string) => item)
+        .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;
+    let res: any = await addOrUpdateCoupon(params);
+    if (res && res.code == 200) {
+      ElMessage.success("创建成功,请耐心等待审核");
+      goBack();
+    }
+  });
+};
+
+// ==================== 工具函数 ====================
+
+/**
+ * 禁用开始售卖时间的日期
+ * 不能选择早于当前时间的日期
+ */
+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.startDate) {
+    const startDate = new Date(voucherModel.value.startDate);
+    startDate.setHours(0, 0, 0, 0);
+    return time.getTime() <= startDate.getTime();
+  }
+  return false;
+};
+
+/**
+ * 禁用有效期时间段的日期
+ * 不能选择早于当前时间的日期
+ * @param time 日期对象
+ * @returns 是否禁用该日期
+ */
+const disabledValidityDate = (time: Date) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  return time.getTime() < today.getTime();
+};
+
+/**
+ * 禁用自定义不可用日期的日期
+ * 不能选择早于当前时间的日期
+ * @param time 日期对象
+ * @returns 是否禁用该日期
+ */
+const disabledCustomUnavailableDate = (time: Date) => {
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  return time.getTime() < today.getTime();
+};
+</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;
+}
+
+/* 有效期天数容器 */
+.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.1s;
+}
+
+/* 日期选择器容器 */
+.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.1s;
+  &:hover {
+    background-color: #ecf5ff;
+  }
+}
+
+/* 日期选择器 */
+.date-item .date-picker {
+  flex: 1;
+  max-width: 500px;
+}
+
+/* 删除按钮 */
+.date-item .delete-btn {
+  flex-shrink: 0;
+}
+
+/* 时间范围容器 */
+.time-range-container {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  width: 100%;
+}
+
+/* 时间选择器 */
+.time-picker {
+  flex: 1;
+  max-width: 200px;
+}
+
+/* 时间分隔符 */
+.time-separator {
+  font-size: 14px;
+  color: #606266;
+  white-space: nowrap;
+}
+</style>

+ 498 - 65
src/views/voucherManagement/detail.vue

@@ -1,93 +1,526 @@
 <template>
-  <div class="card content-box">
-    <el-form :model="formData" label-width="140px">
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="店铺名称 :">
-            <span>{{ formData.storeName }}</span>
-          </el-form-item>
-          <el-form-item label="名称 :">
-            <span>{{ formData.name }}</span>
-          </el-form-item>
-          <el-form-item label="描述 :">
-            <span>{{ formData.description }}</span>
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="状态:">
-            <span>{{ getStatusName(formData.status) }}</span>
-          </el-form-item>
-          <el-form-item label="拒绝原因:" v-if="formData.status === '2'">
-            <span>{{ formData.rejectionReason }}</span>
-          </el-form-item>
-        </el-col>
-      </el-row>
-      <el-row class="text-center" style="margin-top: 20px">
-        <el-col :span="24">
-          <el-button type="primary" @click="goBack"> 确定 </el-button>
-        </el-col>
-      </el-row>
-    </el-form>
+  <!-- 代金券管理 - 详情页面 -->
+  <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>
+    <div class="content">
+      <!-- 左侧内容区域 -->
+      <div class="contentLeft">
+        <!-- 基础信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">基础信息:</h3>
+          <!-- 代金券名称 -->
+          <div class="detail-item">
+            <div class="detail-label">代金券名称</div>
+            <div class="detail-value">
+              {{ voucherModel.name || "--" }}
+            </div>
+          </div>
+          <!-- 抵扣价格 -->
+          <div class="detail-item">
+            <div class="detail-label">抵扣价格</div>
+            <div class="detail-value">
+              {{ voucherModel.offprice ? `¥${voucherModel.offprice}` : "--" }}
+            </div>
+          </div>
+          <!-- 售卖价格 -->
+          <div class="detail-item">
+            <div class="detail-label">售卖价格</div>
+            <div class="detail-value">
+              {{ voucherModel.price ? `¥${voucherModel.price}` : "--" }}
+            </div>
+          </div>
+          <!-- 开始售卖时间 -->
+          <div class="detail-item">
+            <div class="detail-label">开始售卖时间</div>
+            <div class="detail-value">
+              {{ voucherModel.startDate ? formatDate(voucherModel.startDate) : "--" }}
+            </div>
+          </div>
+          <!-- 结束售卖时间 -->
+          <div class="detail-item">
+            <div class="detail-label">结束售卖时间</div>
+            <div class="detail-value">
+              {{ voucherModel.endDate ? formatDate(voucherModel.endDate) : "--" }}
+            </div>
+          </div>
+        </div>
+        <!-- 购买须知模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">购买须知:</h3>
+          <!-- 使用时间 -->
+          <div class="detail-item">
+            <div class="detail-label">使用时间</div>
+            <div class="detail-value">
+              {{ getUsageTimeText() }}
+            </div>
+          </div>
+          <!-- 有效期 -->
+          <div class="detail-item">
+            <div class="detail-label">有效期</div>
+            <div class="detail-value">
+              {{ getExpirationText() }}
+            </div>
+          </div>
+          <!-- 不可用日期 -->
+          <div class="detail-item">
+            <div class="detail-label">不可用日期</div>
+            <div class="detail-value">
+              {{ getUnavailableDateText() }}
+            </div>
+          </div>
+          <!-- 库存 -->
+          <div class="detail-item">
+            <div class="detail-label">库存</div>
+            <div class="detail-value">
+              {{ voucherModel.singleQty || "--" }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 右侧内容区域 -->
+      <div class="contentRight">
+        <!-- 使用规则模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">使用规则:</h3>
+          <!-- 单次可用数量 -->
+          <div class="detail-item">
+            <div class="detail-label">单次可用数量</div>
+            <div class="detail-value">
+              {{ voucherModel.singleCanUse || "--" }}
+            </div>
+          </div>
+          <!-- 限购数量 -->
+          <div class="detail-item">
+            <div class="detail-label">限购数量</div>
+            <div class="detail-value">
+              {{ voucherModel.purchaseLimitCode || "--" }}
+            </div>
+          </div>
+          <!-- 适用范围 -->
+          <div class="detail-item">
+            <div class="detail-label">适用范围</div>
+            <div class="detail-value">
+              {{ getApplyScopeText() }}
+            </div>
+          </div>
+          <!-- 补充说明 -->
+          <div class="detail-item">
+            <div class="detail-label">补充说明</div>
+            <div class="detail-value">
+              {{ voucherModel.supplement || "--" }}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup lang="tsx" name="voucherManagementDetail">
+/**
+ * 代金券管理 - 详情页面
+ * 功能:显示代金券的详细信息
+ */
 import { ref, onMounted } from "vue";
-import { useRoute, useRouter } from "vue-router";
+import { useRouter, useRoute } from "vue-router";
 import { ElMessage } from "element-plus";
-import { getStaffConfigDeatail } from "@/api/modules/staffConfig";
+import { getVoucherDetail, getHolidayList } from "@/api/modules/voucherManagement";
 
-const route = useRoute();
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
 const router = useRouter();
+const route = useRoute();
 
-const formData = ref({});
+// 页面ID参数
+const id = ref<string>("");
 
-const id = ref((route.query.id as string) || "");
+// ==================== 代金券信息数据模型 ====================
+const voucherModel = ref<any>({
+  // 代金券名称
+  name: "",
+  // 抵扣价格
+  offprice: "",
+  // 售卖价格
+  price: "",
+  // 开始售卖时间
+  startDate: "",
+  // 结束售卖时间
+  endDate: "",
+  // 使用时间 - 开始时间
+  buyUseStartTime: "",
+  // 使用时间 - 结束时间
+  buyUseEndTime: "",
+  // 有效期类型:0-指定天数,1-指定时间段内可用
+  expirationType: "0",
+  // 有效期天数(当expirationType为0时使用)
+  expirationDate: 0,
+  // 有效期时间段(当expirationType为1时使用)
+  validityPeriod: [],
+  // 不可用日期类型:0-全部日期可用,1-限制日期
+  unusedType: "0",
+  // 限制日期 - 星期选择(数组,存储选中的星期值)
+  unavailableWeekdays: [],
+  // 限制日期 - 节日选择(数组,存储选中的节日值)
+  unavailableHolidays: [],
+  // 自定义不可用日期列表(数组,每个元素是一个日期范围 [startDate, endDate])
+  disableDateList: [],
+  // 库存
+  singleQty: "",
+  // 单日可用数量
+  singleCanUse: "",
+  // 限购数量
+  purchaseLimitCode: "",
+  // 适用范围类型:0-全场通用,1-部分不可用
+  applyType: "1",
+  // 适用范围(当applyType为1时使用)
+  applyDesc: "",
+  // 补充说明
+  supplement: ""
+});
 
-const getStatusName = (status: string) => {
-  switch (status) {
-    case "0":
-      return "待审核";
-    case "1":
-      return "审核通过";
-    case "2":
-      return "审核拒绝";
-    default:
-      return "未知状态";
-  }
-};
+// 星期选项列表
+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([]);
 
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取ID并加载详情数据
+ */
 onMounted(async () => {
-  await initData();
+  id.value = (route.query.id as string) || "";
+  // 加载节日列表
+  await loadHolidayList();
+  if (id.value) {
+    await loadDetailData();
+  } else {
+    ElMessage.warning("缺少代金券ID参数");
+  }
 });
 
-const initData = async () => {
-  if (id.value) {
-    try {
-      const response = await getStaffConfigDeatail({ id: id.value });
-      if (response.code === 200) {
-        formData.value = response.data;
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+// ==================== 数据加载函数 ====================
+
+/**
+ * 加载节日列表
+ */
+const loadHolidayList = async () => {
+  try {
+    let params = {
+      year: new Date().getFullYear(),
+      page: 1,
+      size: 500,
+      openFlag: 1,
+      holidayName: ""
+    };
+    let res: any = await getHolidayList(params);
+    if (res && res.code == 200) {
+      holidayList.value = res.data.records || [];
+    }
+  } catch (error) {
+    console.error("加载节日列表出错:", error);
+  }
+};
+
+/**
+ * 加载详情数据
+ */
+const loadDetailData = async () => {
+  try {
+    const params = {
+      id: id.value
+    };
+    const res: any = await getVoucherDetail(params);
+    if (res && res.code == 200) {
+      // 合并主数据
+      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) => {
+            const timestamp = Number(item.trim());
+            if (!isNaN(timestamp)) {
+              // 将时间戳转换为日期字符串 (YYYY-MM-DD)
+              return new Date(timestamp).toISOString().split("T")[0];
+            }
+            return null;
+          })
+          .filter((item: any) => item !== null);
+      } else {
+        voucherModel.value.validityPeriod = [];
       }
-    } catch (error) {
-      ElMessage.error("获取详情失败");
+
+      // 处理不可用日期
+      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) : [];
+      } else if (voucherModel.value.unusedType === 2) {
+        // 自定义不可用日期类型:格式为 "开始日期,结束日期;开始日期,结束日期"
+        if (voucherModel.value.unusedDate) {
+          const dateRanges = voucherModel.value.unusedDate.split(";");
+          voucherModel.value.disableDateList = dateRanges
+            .map((range: string) => {
+              const dates = range.split(",");
+              if (dates.length === 2 && dates[0] && dates[1]) {
+                return [dates[0].trim(), dates[1].trim()];
+              }
+              return null;
+            })
+            .filter((item: any) => item !== null);
+        } else {
+          voucherModel.value.disableDateList = [];
+        }
+      }
+    } else {
+      ElMessage.error("加载详情数据失败");
     }
+  } catch (error) {
+    console.error("加载详情数据出错:", error);
+    ElMessage.error("加载详情数据出错");
   }
 };
 
-const goBack = () => {
-  router.go(-1);
+// ==================== 工具函数 ====================
+
+/**
+ * 格式化日期
+ * @param date 日期字符串 (YYYY-MM-DD)
+ * @returns 格式化后的日期字符串 (YYYY.MM.DD)
+ */
+const formatDate = (date: string) => {
+  if (!date) return "--";
+  return date.replace(/-/g, ".");
+};
+
+/**
+ * 获取使用时间文本
+ */
+const getUsageTimeText = () => {
+  if (voucherModel.value.buyUseStartTime && voucherModel.value.buyUseEndTime) {
+    const startHour = formatHour(voucherModel.value.buyUseStartTime);
+    const endHour = formatHour(voucherModel.value.buyUseEndTime);
+    return `${startHour}-${endHour}`;
+  }
+  return "--";
+};
+
+/**
+ * 格式化小时
+ * @param hour 小时值(字符串或数字)
+ * @returns 格式化后的小时文本(如:7:00)
+ */
+const formatHour = (hour: string | number) => {
+  if (!hour && hour !== 0) return "";
+  const hourNum = typeof hour === "string" ? parseInt(hour) : hour;
+  return `${hourNum}:00`;
+};
+
+/**
+ * 获取有效期文本
+ */
+const getExpirationText = () => {
+  if (voucherModel.value.expirationType === "0" || voucherModel.value.expirationType == 0) {
+    if (voucherModel.value.expirationDate) {
+      return `购买后${voucherModel.value.expirationDate}天`;
+    }
+    return "--";
+  } else if (voucherModel.value.expirationType === "1" || voucherModel.value.expirationType == 1) {
+    if (
+      voucherModel.value.validityPeriod &&
+      Array.isArray(voucherModel.value.validityPeriod) &&
+      voucherModel.value.validityPeriod.length === 2
+    ) {
+      const startDate = formatDate(voucherModel.value.validityPeriod[0]);
+      const endDate = formatDate(voucherModel.value.validityPeriod[1]);
+      return `${startDate}-${endDate}`;
+    }
+    return "--";
+  }
+  return "--";
+};
+
+/**
+ * 获取不可用日期文本
+ */
+const getUnavailableDateText = () => {
+  if (voucherModel.value.unusedType === "0" || voucherModel.value.unusedType == 0) {
+    return "全部日期可用";
+  } else if (voucherModel.value.unusedType === "1" || voucherModel.value.unusedType == 1) {
+    const weekdays: string[] = [];
+    const holidays: string[] = [];
+
+    // 处理星期
+    if (voucherModel.value.unavailableWeekdays && voucherModel.value.unavailableWeekdays.length > 0) {
+      voucherModel.value.unavailableWeekdays.forEach((day: string) => {
+        const weekday = weekdayList.value.find(w => w.oName === day);
+        if (weekday) {
+          weekdays.push(weekday.name);
+        }
+      });
+    }
+
+    // 处理节日
+    if (voucherModel.value.unavailableHolidays && voucherModel.value.unavailableHolidays.length > 0) {
+      voucherModel.value.unavailableHolidays.forEach((holidayId: string) => {
+        const holiday = holidayList.value.find((h: any) => h.id === holidayId || String(h.id) === String(holidayId));
+        if (holiday) {
+          holidays.push(holiday.festivalName);
+        }
+      });
+    }
+
+    const parts: string[] = [];
+    if (weekdays.length > 0) {
+      parts.push(weekdays.join("、"));
+    }
+    if (holidays.length > 0) {
+      parts.push(holidays.join("、"));
+    }
+
+    return parts.length > 0 ? parts.join("、") : "--";
+  } else if (voucherModel.value.unusedType === "2" || voucherModel.value.unusedType == 2) {
+    if (voucherModel.value.disableDateList && voucherModel.value.disableDateList.length > 0) {
+      const dateStrings = voucherModel.value.disableDateList
+        .filter((date: any) => date && Array.isArray(date) && date.length === 2)
+        .map((date: any) => {
+          const startDate = formatDate(date[0]);
+          const endDate = formatDate(date[1]);
+          return startDate === endDate ? startDate : `${startDate}-${endDate}`;
+        });
+      return dateStrings.length > 0 ? dateStrings.join("、") : "--";
+    }
+    return "--";
+  }
+  return "--";
+};
+
+/**
+ * 获取适用范围文本
+ */
+const getApplyScopeText = () => {
+  if (voucherModel.value.applyType === "1" || voucherModel.value.applyType == 1) {
+    return "全场通用";
+  } else if (voucherModel.value.applyType === "2" || voucherModel.value.applyType == 2) {
+    if (voucherModel.value.applyDesc) {
+      return `除${voucherModel.value.applyDesc}外全场通用`;
+    }
+    return "全场通用";
+  }
+  return "--";
 };
 </script>
 
-<style lang="scss" scoped>
-.el-form {
-  width: 100%;
-  .text-center {
-    text-align: center;
+<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 24px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  box-shadow: 0 2px 4px rgb(0 0 0 / 2%);
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 24px;
+  width: 98%;
+  padding: 0 12px;
+  margin: 24px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+    padding-right: 12px;
   }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+    padding-left: 12px;
+  }
+}
+
+/* 模块容器 */
+.model {
+  margin-bottom: 50px;
+  h3 {
+    padding-bottom: 12px;
+    margin: 0 0 20px;
+    font-size: 16px;
+    color: #303133;
+    border-bottom: 2px solid #e4e7ed;
+  }
+}
+
+/* 详情项样式 */
+.detail-item {
+  display: flex;
+  align-items: flex-start;
+  min-height: 32px;
+  margin-bottom: 24px;
+}
+.detail-label {
+  flex-shrink: 0;
+  min-width: 120px;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 32px;
+  color: #606266;
+}
+.detail-value {
+  flex: 1;
+  font-size: 14px;
+  line-height: 32px;
+  color: #303133;
+  word-break: break-word;
 }
-.el-col {
-  box-sizing: border-box;
-  padding-right: 10px;
+.empty-text {
+  color: #909399;
 }
 </style>

+ 148 - 47
src/views/voucherManagement/index.vue

@@ -11,15 +11,15 @@
         </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
+        <p>团购状态:{{ allTabOptions.find(item => item.name == scope.row.groupType)?.label ?? "--" }}</p>
+        <p>审核状态:{{ scope.row.reviewType ?? "--" }}</p> </template
       >:
       <!-- 表格操作 -->
       <template #operation="scope">
         <el-button
           link
           type="primary"
-          @click="changeTypes(scope.row, 5)"
+          @click="changeTypes(scope.row, 1)"
           v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.上架)"
         >
           上架
@@ -41,26 +41,27 @@
           修改库存
         </el-button>
         <el-button
-          type="primary"
           link
-          @click="toDetail(scope.row)"
-          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看详情)"
+          type="primary"
+          @click="viewRejectReason(scope.row)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.查看拒绝原因)"
         >
-          查看详情
+          查看拒绝原因
         </el-button>
         <el-button
-          link
           type="primary"
-          @click="editRow(scope.row)"
-          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.编辑)"
+          link
+          @click="toDetail(scope.row)"
+          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="scope.row.dataType == 1"> 编辑 </el-button>
         <el-button
           link
           type="primary"
           @click="deleteRow(scope.row)"
-          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.删除)"
+          v-if="canShowButton(scope.row.status, OPERATION_PERMISSIONS.删除) && scope.row.dataType == 1"
         >
           删除
         </el-button>
@@ -69,10 +70,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="请输入" />
@@ -85,10 +86,32 @@
         </div>
       </template>
     </el-dialog>
+    <!-- 查看拒绝原因弹窗 -->
+    <el-dialog v-model="rejectReasonDialogVisible" title="查看拒绝原因" width="600px">
+      <div class="reject-reason-content">
+        <div class="reject-reason-item">
+          <div class="reject-reason-label">代金券名称:</div>
+          <div class="reject-reason-value">
+            {{ rejectReasonData.name || "--" }}
+          </div>
+        </div>
+        <div class="reject-reason-item">
+          <div class="reject-reason-label">拒绝原因:</div>
+          <div class="reject-reason-value reject-reason-text">
+            {{ rejectReasonData.approvalComments || "暂无拒绝原因" }}
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="closeRejectReasonDialog"> 确定 </el-button>
+        </div>
+      </template>
+    </el-dialog>
   </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";
@@ -96,7 +119,7 @@ import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
 import { Plus } from "@element-plus/icons-vue";
-import { delThaliById, getThaliList, updateStatus, updateNum } from "@/api/modules/voucherManagement";
+import { delThaliById, getThaliList, updateNum, updateStatus } from "@/api/modules/voucherManagement";
 import { ElMessageBox } from "element-plus/es";
 import { localGet, usePermission } from "@/utils";
 
@@ -104,12 +127,18 @@ const router = useRouter();
 const dialogFormVisible = ref(false);
 const formInventory: any = ref({
   id: "",
-  packageName: "",
-  inventoryNum: "",
+  name: "",
+  singleQty: "",
   newInventory: ""
 });
 const rowData = ref<any>();
 const activeName = ref("");
+// 查看拒绝原因弹窗相关
+const rejectReasonDialogVisible = ref(false);
+const rejectReasonData = ref<any>({
+  name: "",
+  approvalComments: ""
+});
 // 定义表单类型
 interface RuleForm {
   newInventory: string;
@@ -122,15 +151,27 @@ const rules = reactive<FormRules<RuleForm>>({
       pattern: /^(0|[1-9][0-9]*)$/,
       message: "请输入整数,不允许输入小数,负数",
       trigger: "blur"
+    },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (value) {
+          const numValue = Number(value);
+          if (!isNaN(numValue) && numValue > 10000) {
+            callback(new Error("库存不得大于10000"));
+            return;
+          }
+        }
+        callback();
+      },
+      trigger: "blur"
     }
   ]
 });
 const statusEnum = [
-  { value: "-1", label: "待审核" },
-  { value: "-2", label: "审核通过" },
-  { value: "0", label: "审核驳回" }
+  { value: "0", label: "待审核" },
+  { value: "1", label: "审核通过" },
+  { value: "2", label: "审核驳回" }
 ];
-
 // ProTable 实例(需要在使用它的地方之前定义)
 const proTable = ref<ProTableInstance>();
 
@@ -145,29 +186,35 @@ const columns = reactive<ColumnProps<any>[]>([
     }
   },
   {
-    prop: "groupName",
+    prop: "name",
     label: "代金券名称",
     search: {
       el: "input"
     }
   },
   {
-    prop: "preferentialPrice",
+    prop: "price",
     label: "价格",
     render: (scope: any) => {
-      return scope.row.preferentialPrice ? `¥${scope.row.preferentialPrice}` : "--";
+      return scope.row.price ? `¥${scope.row.price}` : "--";
     }
   },
   {
-    prop: "goodsId",
-    label: "已售"
+    prop: "saleNum",
+    label: "已售",
+    render: scope => {
+      return scope.row.saleNum === null || !scope.row.saleNum ? "--" : scope.row.saleNum;
+    }
   },
   {
-    prop: "inventoryNum",
-    label: "剩余库存"
+    prop: "singleQty",
+    label: "剩余库存",
+    render: scope => {
+      return scope.row.singleQty === null ? "--" : scope.row.singleQty;
+    }
   },
   {
-    prop: "goodsId",
+    prop: "endDate",
     label: "结束时间"
   },
   {
@@ -226,7 +273,9 @@ const OPERATION_PERMISSIONS = {
   // 编辑:草稿、审核拒绝、已售罄、已下架、已结束
   编辑: [STATUS.草稿, STATUS.审核拒绝, STATUS.已售罄, STATUS.已下架, STATUS.已结束],
   // 删除:草稿、未开始、审核拒绝、已售罄、已结束
-  删除: [STATUS.草稿, STATUS.未开始, STATUS.审核拒绝, STATUS.已售罄, STATUS.已结束]
+  删除: [STATUS.草稿, STATUS.未开始, STATUS.审核拒绝, STATUS.已售罄, STATUS.已结束],
+  // 查看拒绝原因:审核拒绝
+  查看拒绝原因: [STATUS.审核拒绝]
 } as const;
 
 // 判断按钮是否显示的工具函数
@@ -243,18 +292,17 @@ const currentAuditStatus = computed(() => {
 // 控制 el-tabs 是否显示:当审核状态为空或审核通过时显示
 const showTabs = computed(() => {
   const status = currentAuditStatus.value;
-  return !status || status === "-2";
+  return !status || status === "1";
 });
 
 // 根据审核状态过滤 tabOptions:如果审核状态为空,只显示草稿;审核通过时,显示除草稿外的所有标签页
 const filteredTabOptions = computed(() => {
   const status = currentAuditStatus.value;
   if (!status) {
-    // 审核状态为空时,只显示草稿
     return allTabOptions;
-  } else if (status === "-2") {
+  } else if (status == "1") {
     // 审核通过时,显示除草稿外的所有标签页
-    return allTabOptions.filter(tab => tab.name !== "1");
+    return allTabOptions.filter(tab => tab.name != "0");
   }
   return [];
 });
@@ -264,17 +312,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
+  storeId: localGet("createdId"),
+  groupType: localGet("businessSection"),
+  status: activeName,
+  dataType: dataType
 });
 const type = ref(false);
 // 页面加载时触发查询
@@ -321,7 +375,7 @@ const deleteRow = row => {
   }).then(() => {
     const params = {
       id: row.id,
-      groupType: localGet("businessSection") || "1"
+      groupType: localGet("businessSection")
     };
     delThaliById(params).then(() => {
       ElMessage.success("删除成功");
@@ -330,7 +384,7 @@ const deleteRow = row => {
   });
 };
 const handleClick = () => {};
-const changeTypes = async (row: any, status: string) => {
+const changeTypes = async (row, status) => {
   rowData.value = row;
   let res = await updateStatus({ id: row.id, status: status, approvalComments: "" });
   if (res && res.code == 200) {
@@ -340,8 +394,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;
 };
@@ -350,9 +404,9 @@ const handleSubmit = async () => {
   if (!ruleFormRef.value) return;
   await ruleFormRef.value.validate(async (valid, fields) => {
     if (valid) {
-      let res = await updateNum({ id: formInventory.value.id, num: formInventory.value.newInventory });
+      let res = await updateNum({ id: formInventory.value.id, singleQty: formInventory.value.newInventory });
       if (res && res.code == 200) {
-        ElMessage.success("库存修改成功");
+        ElMessage.success("修改成功");
         dialogFormVisible.value = false;
         proTable.value?.getTableList();
       }
@@ -364,11 +418,27 @@ const closeDialog = () => {
   dialogFormVisible.value = false;
   formInventory.value = {
     id: "",
-    packageName: "",
-    inventoryNum: "",
+    name: "",
+    singleQty: "",
     newInventory: ""
   };
 };
+// 查看拒绝原因
+const viewRejectReason = (row: any) => {
+  rejectReasonData.value = {
+    name: row.name || "--",
+    approvalComments: row.approvalComments || "--"
+  };
+  rejectReasonDialogVisible.value = true;
+};
+// 关闭拒绝原因弹窗
+const closeRejectReasonDialog = () => {
+  rejectReasonDialogVisible.value = false;
+  rejectReasonData.value = {
+    name: "",
+    approvalComments: ""
+  };
+};
 </script>
 
 <style lang="scss" scoped>
@@ -389,4 +459,35 @@ const closeDialog = () => {
     }
   }
 }
+.reject-reason-content {
+  padding: 20px 0;
+  .reject-reason-item {
+    display: flex;
+    margin-bottom: 20px;
+    &:last-child {
+      margin-bottom: 0;
+    }
+    .reject-reason-label {
+      flex-shrink: 0;
+      min-width: 100px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #606266;
+    }
+    .reject-reason-value {
+      flex: 1;
+      font-size: 14px;
+      color: #303133;
+      word-break: break-word;
+      &.reject-reason-text {
+        min-height: 80px;
+        padding: 12px;
+        line-height: 1.6;
+        white-space: pre-wrap;
+        background-color: #f5f7fa;
+        border-radius: 4px;
+      }
+    }
+  }
+}
 </style>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 499 - 179
src/views/voucherManagement/newVoucher.vue


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor