sunshibo 3 дней назад
Родитель
Сommit
730897a18d

+ 5 - 0
src/api/modules/couponManagement.ts

@@ -16,6 +16,11 @@ export const getStoreAllCouponList = (params: {
   return httpApi.get<any>(`/alienStore/life-discount-coupon/getStoreAllCouponList`, params);
 };
 
+/** 删除优惠券 DELETE /alienStore/life-discount-coupon/deleteDiscountCoupon/{id} */
+export const deleteLifeDiscountCoupon = (id: string | number) => {
+  return httpApi.delete<any>(`/alienStore/life-discount-coupon/deleteDiscountCoupon/${id}`);
+};
+
 export const delCouponById = (params: { id: string }) => {
   return http.get(PORT_NONE + `/discountCouponPlatform/deleteDiscountCoupon`, params);
 };

+ 0 - 48
src/api/modules/friendRelation.ts

@@ -1,48 +0,0 @@
-import http from "@/api";
-
-/**
- * @name 好友关系管理模块
- */
-
-// 获取好友关系列表
-export const getFriendRelationList = (params: any) => {
-  return http.post(`/api/friendRelation/list`, params);
-};
-
-// 添加好友
-export const addFriendRelation = (params: {
-  friendName: string;
-  friendPhone: string;
-  remark?: string;
-  relationType: string | number;
-  couponList?: Array<{ couponId: string | number; quantity: number }>;
-}) => {
-  return http.post(`/api/friendRelation/add`, params);
-};
-
-// 编辑好友信息
-export const updateFriendRelation = (params: {
-  id: string | number;
-  friendName: string;
-  friendPhone: string;
-  remark?: string;
-  relationType: string | number;
-  couponList?: Array<{ couponId: string | number; quantity: number }>;
-}) => {
-  return http.post(`/api/friendRelation/update`, params);
-};
-
-// 删除好友
-export const deleteFriendRelation = (params: { id: string | number }) => {
-  return http.post(`/api/friendRelation/delete`, params);
-};
-
-// 同意好友申请
-export const approveFriend = (params: { id: string | number }) => {
-  return http.post(`/api/friendRelation/approve`, params);
-};
-
-// 拒绝好友申请
-export const rejectFriend = (params: { id: string | number; reason?: string }) => {
-  return http.post(`/api/friendRelation/reject`, params);
-};

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

@@ -138,7 +138,7 @@ export const setFriendCoupon = (params: any) => {
   return httpLogin.post(`/alienStore/life-discount-coupon-store-friend/setFriendCoupon`, params);
 };
 
-//好友关系管理列表
+// 好友赠券规则列表
 export const getRuleList = (params: any) => {
   return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getRuleList`, params);
 };
@@ -162,12 +162,12 @@ export const getRuleById = (params: any) => {
   return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getRuleById`, params);
 };
 
-/** 代金券列表(好友关系管理-折扣券用)与 group_merchant couponList 参数一致 */
+/** 代金券列表(好友赠券等场景)与 group_merchant couponList 参数一致 */
 export const getVoucherList = (params: { storeId: string; status?: number; size?: number; page?: number }) => {
   return httpLogin.get(`/alienStore/coupon/getCouponList`, params);
 };
 
-/** 满减券列表(好友关系管理-满减券用)与 group_merchant issueCouponList 参数一致 */
+/** 满减券列表(好友赠券等场景)与 group_merchant issueCouponList 参数一致 */
 export const getIssueCouponList = (params: {
   storeId: string;
   tab?: number;

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

@@ -769,20 +769,6 @@
           ]
         },
         {
-          "path": "/dynamicManagement/friendRelation",
-          "name": "friendRelation",
-          "component": "/dynamicManagement/friendRelation",
-          "meta": {
-            "icon": "User",
-            "title": "好友关系管理",
-            "isLink": "",
-            "isHide": false,
-            "isFull": false,
-            "isAffix": false,
-            "isKeepAlive": false
-          }
-        },
-        {
           "path": "/operationManagement/couponTemplate",
           "name": "operationManagementCouponTemplate",
           "component": "/operationManagement/couponTemplate",

+ 50 - 21
src/views/dynamicManagement/friendCoupon.vue

@@ -187,7 +187,7 @@ const giftRules = reactive<FormRules>({
   quantity: [{ required: true, message: "请输入赠送数量", trigger: "blur" }]
 });
 
-// 好友赠我表格列配置(与截图一致:店铺名称、类型、数量、结束时间、操作)
+// 好友赠我表格列配置
 const friendMessageColumns = reactive<ColumnProps<any>[]>([
   {
     prop: "storeName",
@@ -215,14 +215,27 @@ const friendMessageColumns = reactive<ColumnProps<any>[]>([
     }
   },
   {
+    prop: "couponName",
+    label: "优惠券名称",
+    render: (scope: any) => {
+      const r = scope.row;
+      return r.couponName ?? r.name ?? r.couponTitle ?? "--";
+    }
+  },
+  {
     prop: "couponNum",
     label: "数量"
   },
   {
-    prop: "endDate",
-    label: "结束时间",
+    prop: "longTermValid",
+    label: "有效期",
     render: (scope: any) => {
-      return scope.row.endDate?.replace(/-/g, "/") || "--";
+      const r = scope.row;
+      const lt = r.longTermValid;
+      if (lt === 1 || lt === "1" || lt === true) return "长期有效";
+      const ed = r.expirationDate;
+      if (ed === null || ed === undefined || ed === "") return "--";
+      return `${ed}天`;
     }
   },
   { prop: "operation", label: "操作", fixed: "right", width: 200 }
@@ -256,14 +269,27 @@ const myGiftColumns = reactive<ColumnProps<any>[]>([
     }
   },
   {
+    prop: "couponName",
+    label: "优惠券名称",
+    render: (scope: any) => {
+      const r = scope.row;
+      return r.couponName ?? r.name ?? r.couponTitle ?? "--";
+    }
+  },
+  {
     prop: "couponNum",
     label: "数量"
   },
   {
-    prop: "endDate",
-    label: "结束时间",
+    prop: "longTermValid",
+    label: "有效期",
     render: (scope: any) => {
-      return scope.row.endDate?.replace(/-/g, "/") || "--";
+      const r = scope.row;
+      const lt = r.longTermValid;
+      if (lt === 1 || lt === "1" || lt === true) return "长期有效";
+      const ed = r.expirationDate;
+      if (ed === null || ed === undefined || ed === "") return "--";
+      return `${ed}天`;
     }
   },
   { prop: "operation", label: "操作", fixed: "right", width: 200 }
@@ -377,24 +403,27 @@ const handleGiftSubmit = async () => {
   });
 };
 
-// 查看详情
+// 查看详情:整行写入 sessionStorage,详情页直接展示,不调接口
+const FRIEND_COUPON_DETAIL_ROW_KEY = "friendCoupon_detail_row";
+
 const viewDetail = (row: any) => {
-  let query = {};
-  if (row.voucherId) {
-    query = {
-      voucherId: row.voucherId,
-      type: activeName.value
-    };
-  } else {
-    query = {
-      couponId: row.couponId,
-      type: activeName.value,
-      storeId: row.storeId != null && row.storeId !== "" ? row.storeId : localGet("createdId")
-    };
+  try {
+    sessionStorage.setItem(FRIEND_COUPON_DETAIL_ROW_KEY, JSON.stringify(row ?? {}));
+  } catch (e) {
+    console.error(e);
+    ElMessage.warning("无法缓存行数据,请重试");
+    return;
+  }
+  const query: Record<string, string> = { type: String(activeName.value || "") };
+  if (row?.voucherId != null && row.voucherId !== "") {
+    query.voucherId = String(row.voucherId);
+  } else if (row?.couponId != null && row.couponId !== "") {
+    query.couponId = String(row.couponId);
   }
+  if (row?.storeId != null && row.storeId !== "") query.storeId = String(row.storeId);
   router.push({
     path: "/dynamicManagement/friendCouponDetail",
-    query: query
+    query
   });
 };
 

+ 39 - 181
src/views/dynamicManagement/friendCouponDetail.vue

@@ -1,5 +1,5 @@
 <template>
-  <!-- 好友优惠券 - 详情页面 -->
+  <!-- 好友优惠券 - 详情页面(数据由列表页 sessionStorage 传入,不调详情接口) -->
   <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
     <div class="header">
       <el-button @click="goBack"> 返回 </el-button>
@@ -8,7 +8,6 @@
     <div class="content">
       <!-- 左侧内容区域 -->
       <div class="contentLeft">
-        <!-- 基础信息模块(与截图一致:店铺名称、类型、优惠券名称、面值、数量、结束时间、有效期) -->
         <div class="model">
           <h3 style="font-weight: bold">基础信息:</h3>
           <!-- 店铺名称 -->
@@ -41,38 +40,25 @@
           </div>
           <div class="detail-item" v-else>
             <div class="detail-label">面值</div>
-            <div class="detail-value" v-if="couponId">
-              {{ formatCurrency(couponModel.nominalValue, 2, "¥") }}
-            </div>
-            <div class="detail-value" v-else>
+            <div class="detail-value">
               {{ formatCurrency(couponModel.nominalValue ?? couponModel.price, 2, "¥") }}
             </div>
           </div>
-          <!-- 数量:优惠券详情接口返回持有数量 ownedQuantity -->
+          <!-- 数量 -->
           <div class="detail-item">
             <div class="detail-label">数量</div>
             <div class="detail-value">
               {{ couponModel.ownedQuantity ?? couponModel.couponNum ?? couponModel.singleQty ?? "--" }}
             </div>
           </div>
-          <!-- 结束时间 -->
-          <div class="detail-item">
-            <div class="detail-label">结束时间</div>
-            <div class="detail-value" v-if="couponId">
-              {{ couponModel.endGetDate || "--" }}
-            </div>
-            <div class="detail-value" v-else>
-              {{ couponModel.endDate || "--" }}
-            </div>
-          </div>
-          <!-- 有效期:接口字段 expirationDate -->
+          <!-- 有效期:longTermValid=1 长期有效;否则展示 expirationDate(天) -->
           <div class="detail-item">
             <div class="detail-label">有效期</div>
             <div class="detail-value">
               {{ getValidityDisplay() }}
             </div>
           </div>
-          <!-- 最低消费金额:绑定接口字段 minimumSpendingAmount -->
+          <!-- 最低消费金额 -->
           <div class="detail-item">
             <div class="detail-label">最低消费金额</div>
             <div class="detail-value">
@@ -87,203 +73,97 @@
 
 <script setup lang="tsx" name="friendCouponDetail">
 /**
- * 好友优惠券 - 详情页面
- * 功能:显示好友优惠券的详细信息
+ * 好友优惠券 - 详情页:数据来自列表行(sessionStorage),不请求详情接口
  */
 import { ref, onMounted } from "vue";
-import { useRouter, useRoute } from "vue-router";
+import { useRouter } from "vue-router";
 import { ElMessage } from "element-plus";
-import { getFriendCouponDetail, getCouponDetail } from "@/api/modules/newLoginApi";
 import { formatCurrency } from "@/utils/formatCurrency";
-import { localGet } from "@/utils";
 
-// ==================== 响应式数据定义 ====================
+const FRIEND_COUPON_DETAIL_ROW_KEY = "friendCoupon_detail_row";
 
-// 路由相关
 const router = useRouter();
-const route = useRoute();
 
-// 页面ID参数
-const couponId = ref<string>("");
-const voucherId = ref<string>("");
-/** 详情接口 getCouponDetailWithOwnedQty 所需店铺 ID(路由 storeId,缺省时为当前登录店铺) */
-const detailStoreId = ref<string>("");
-
-// 优惠券类型(好友赠我 friendMessage / 我赠好友 myGift)
-const type = ref<string>("");
-
-// ==================== 优惠券信息数据模型 ====================
 const couponModel = ref<any>({
-  // 账户名称(赠送人/接收人)
   acName: "",
-  // 优惠券名称
   couponName: "",
-  // 优惠券数量
   couponNum: 0,
-  // 删除标志
   deleteFlag: 0,
-  // 结束日期
   endDate: "",
-  // 有效期(接口)
   expirationDate: "",
-  // ID
+  longTermValid: undefined as number | undefined,
   id: 0,
-  // 图片URL
   imgUrl: "",
-  // 详细列表
   lifeDiscountCouponFriendRuleDetailVos: [],
-  // 最低消费金额(接口字段 minimumSpendingAmount;不设默认数字,未加载时由 formatCurrency 显示为 --)
   minimumSpendingAmount: undefined,
-  // 金额上限
   moneyHigh: 0,
-  // 金额下限
   moneyLow: 0,
-  // 面值
   nominalValue: 0,
-  // 折扣率(折扣券 couponType=2,接口 discountRate)
   discountRate: undefined,
-  // 状态
   status: "",
-  // 店铺ID
   storeId: 0,
-  // 店铺名称
   storeName: ""
 });
 
-// ==================== 生命周期钩子 ====================
-
-/**
- * 组件挂载时初始化
- * 从路由参数中获取couponId并加载详情数据
- */
-onMounted(async () => {
-  if (route.query.voucherId) {
-    voucherId.value = (route.query.voucherId as string) || "";
-  } else if (route.query.couponId) {
-    couponId.value = (route.query.couponId as string) || "";
-  }
-  type.value = (route.query.type as string) || "";
-  detailStoreId.value =
-    (route.query.storeId != null && String(route.query.storeId) !== ""
-      ? String(route.query.storeId)
-      : String(localGet("createdId") ?? "")) || "";
-
-  if (voucherId.value) {
-    await loadDetailData();
-  } else if (couponId.value) {
-    if (!detailStoreId.value) {
-      ElMessage.warning("缺少店铺ID参数");
-      return;
-    }
-    await loadDetailData();
-  } else {
-    ElMessage.warning("缺少优惠券ID参数");
-  }
-});
-
-// ==================== 事件处理函数 ====================
-
-/**
- * 返回上一页
- */
 const goBack = () => {
   router.go(-1);
 };
 
-// ==================== 数据加载函数 ====================
-
-/**
- * 加载详情数据
- */
-const loadDetailData = async () => {
-  try {
-    if (voucherId.value) {
-      const res: any = await getCouponDetail({
-        id: voucherId.value
-      });
-      if (res.code === 200) {
-        couponModel.value = res.data;
-        console.log(couponModel.value, "couponModel.value");
-      } else {
-        ElMessage.error(res.msg);
-      }
-    } else if (couponId.value) {
-      const res: any = await getFriendCouponDetail({
-        couponId: couponId.value,
-        storeId: detailStoreId.value
-      });
+onMounted(() => {
+  const raw = sessionStorage.getItem(FRIEND_COUPON_DETAIL_ROW_KEY);
+  sessionStorage.removeItem(FRIEND_COUPON_DETAIL_ROW_KEY);
 
-      if (res.code === 200) {
-        couponModel.value = res.data;
-      } else {
-        ElMessage.error(res.msg);
-      }
-    }
-  } catch (error) {
-    ElMessage.error("加载详情数据出错");
+  if (!raw) {
+    ElMessage.warning("缺少列表数据,请从列表重新进入");
+    router.go(-1);
+    return;
   }
-};
 
-/** 有效期是否有值(0 / "0" 视为有效,仅 null、undefined、"" 视为缺失) */
-const hasValidityValue = (v: unknown) => v !== null && v !== undefined && v !== "";
+  try {
+    const row = JSON.parse(raw);
+    couponModel.value = { ...couponModel.value, ...row };
+  } catch {
+    ElMessage.error("数据解析失败");
+    router.go(-1);
+  }
+});
 
-/**
- * 有效期展示:优先 expirationDate,且为 0 时显示 0
- */
 const getValidityDisplay = () => {
   const m = couponModel.value;
-  if (hasValidityValue(m.expirationDate)) return m.expirationDate;
-  if (hasValidityValue(m.validityPeriod)) return m.validityPeriod;
-  if (hasValidityValue(m.endDate)) return m.endDate;
-  return "--";
+  const lt = m.longTermValid;
+  if (lt === 1 || lt === "1" || lt === true) return "长期有效";
+  const ed = m.expirationDate;
+  if (ed === null || ed === undefined || ed === "") return "--";
+  const s = String(ed).trim();
+  if (/^\d{4}-\d{2}-\d{2}/.test(s) || s.includes("T")) {
+    return s.includes("-") ? (s.replace(/-/g, "/").split(" ")[0] ?? s) : s;
+  }
+  return Number.isNaN(Number(s)) ? s : `${s}天`;
 };
 
-/**
- * 折扣率展示:绑定 discountRate;接口常见为整数 11–100 表示几点几折(与 newCoupon 说明一致),否则原样加「折」
- */
 const formatDiscountRateDisplay = (rate: unknown) => {
   if (rate === null || rate === undefined || rate === "") return "--";
   const n = Number(rate);
   if (isNaN(n)) return `${rate}折`;
-  if (n > 10 && n <= 100) {
-    const x = n / 10;
-    const s = Number.isInteger(x) ? String(x) : String(Number(x.toFixed(1)));
-    return `${s.replace(/\.0$/, "")}折`;
-  }
-  return `${n}折`;
+  // 与 newCoupon 一致:接口多为 1–100(如 85 表示 8.5 折),展示时除以 10
+  const x = n / 10;
+  const s = Number.isInteger(x) ? String(x) : String(Number(x.toFixed(1)));
+  return `${s.replace(/\.0$/, "")}折`;
 };
 
-/**
- * 获取优惠券类型文案
- */
-const getCouponTypeText = (type: number | string | undefined) => {
+const getCouponTypeText = (t: number | string | undefined) => {
   const typeMap: Record<string, string> = { "1": "满减券", "2": "折扣券" };
-  return type != null && type !== "" ? (typeMap[String(type)] ?? String(type)) : "--";
-};
-
-/**
- * 获取状态文本
- */
-const getStatusText = () => {
-  const statusMap: Record<string, string> = {
-    "0": "未使用",
-    "1": "已使用",
-    "2": "已过期"
-  };
-  return statusMap[couponModel.value.status] || "--";
+  return t != null && t !== "" ? (typeMap[String(t)] ?? String(t)) : "--";
 };
 </script>
 
 <style scoped lang="scss">
-/* 页面容器 */
 .table-box {
   display: flex;
   flex-direction: column;
   height: auto !important;
   min-height: 100%;
 }
-
-/* 头部区域 */
 .header {
   display: flex;
   align-items: center;
@@ -300,8 +180,6 @@ const getStatusText = () => {
   color: #303133;
   text-align: center;
 }
-
-/* 内容区域布局 */
 .content {
   display: flex;
   flex: 1;
@@ -309,21 +187,15 @@ const getStatusText = () => {
   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 {
@@ -333,8 +205,6 @@ const getStatusText = () => {
     color: #303133;
   }
 }
-
-/* 详情项样式 */
 .detail-item {
   display: flex;
   align-items: flex-start;
@@ -356,16 +226,4 @@ const getStatusText = () => {
   color: #303133;
   word-break: break-word;
 }
-.empty-text {
-  color: #909399;
-}
-
-/* 详情卡片样式 */
-.detail-card {
-  padding: 16px;
-  margin-bottom: 16px;
-  background-color: #f5f7fa;
-  border: 1px solid #e4e7ed;
-  border-radius: 4px;
-}
 </style>

+ 0 - 683
src/views/dynamicManagement/friendRelation.vue

@@ -1,683 +0,0 @@
-<template>
-  <div class="table-box button-table friend-relation-container">
-    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
-      <!-- 表格 header 按钮 -->
-      <template #tableHeader="scope">
-        <div class="header-button">
-          <el-button type="primary" @click="openAddDialog"> 添加活动 </el-button>
-        </div>
-      </template>
-
-      <!-- 状态列 -->
-      <template #status="scope">
-        <el-tag :type="getStatusType(scope.row.status)">
-          {{ getStatusText(scope.row.status) }}
-        </el-tag>
-      </template>
-
-      <!-- 表格操作 -->
-      <template #operation="scope">
-        <el-button link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
-        <el-button link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
-      </template>
-    </ProTable>
-
-    <!-- 添加/编辑活动对话框 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" @close="closeDialog">
-      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px">
-        <el-form-item label="活动名称" prop="acName">
-          <el-input v-model="formData.acName" placeholder="请输入" clearable />
-        </el-form-item>
-
-        <el-form-item label="赠送类型">
-          <el-radio-group v-model="formData.distributeType" @change="onDistributeTypeChange">
-            <el-radio :label="1"> 满减券 </el-radio>
-            <el-radio :label="2"> 折扣券 </el-radio>
-          </el-radio-group>
-        </el-form-item>
-
-        <el-form-item label="消费门槛金额(¥)">
-          <div style="display: flex; gap: 10px; align-items: center; width: 100%">
-            <el-form-item prop="moneyLow" style="flex: 1; margin-bottom: 0">
-              <el-input
-                v-model="formData.moneyLow"
-                type="number"
-                :min="0"
-                step="0.01"
-                placeholder="0.00"
-                clearable
-                @input="handleMoneyInput('moneyLow')"
-              >
-                <template #prefix> ¥ </template>
-              </el-input>
-            </el-form-item>
-            <span>~</span>
-            <el-form-item prop="moneyHigh" style="flex: 1; margin-bottom: 0">
-              <el-input
-                v-model="formData.moneyHigh"
-                type="number"
-                :min="0"
-                step="0.01"
-                placeholder="0.00"
-                clearable
-                @input="handleMoneyInput('moneyHigh')"
-              >
-                <template #prefix> ¥ </template>
-              </el-input>
-            </el-form-item>
-          </div>
-        </el-form-item>
-
-        <el-form-item label="来源商家及优惠券">
-          <div class="merchant-coupon-list">
-            <div v-for="(item, index) in formData.coupons" :key="index" class="merchant-coupon-item">
-              <el-select
-                v-model="item.merchant"
-                placeholder="选择商家"
-                style="width: 200px; margin-right: 10px"
-                filterable
-                @change="handleMerchantChange(index)"
-              >
-                <el-option
-                  v-for="(m, idx) in effectiveMerchantOptions"
-                  :key="m.label ? `${String(m.value)}_${m.label}_${idx}` : String(m.value)"
-                  :label="m.label"
-                  :value="m.value"
-                />
-              </el-select>
-              <el-select
-                v-model="item.coupon"
-                :placeholder="formData.distributeType === 2 ? '选择折扣券' : '选择满减券'"
-                style="width: 200px; margin-right: 10px"
-                filterable
-                @change="handleCouponChange(index)"
-              >
-                <el-option
-                  v-for="c in getCouponOptions(formData.distributeType)"
-                  :key="c.value"
-                  :label="c.label"
-                  :value="c.value"
-                />
-              </el-select>
-              <span v-if="item.coupon" class="remaining-text">剩余{{ item.remaining }}张</span>
-              <el-button type="danger" link @click="removeMerchantCoupon(index)" v-if="formData.coupons.length > 1">
-                删除
-              </el-button>
-            </div>
-            <el-button type="primary" link @click="addMerchantCoupon" style="margin-top: 10px">
-              <el-icon><Plus /></el-icon>
-              添加商家优惠券
-            </el-button>
-          </div>
-        </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>
-  </div>
-</template>
-
-<script setup lang="tsx" name="friendRelation">
-import { computed, onMounted, reactive, ref } from "vue";
-import type { FormInstance, FormRules } from "element-plus";
-import { ElMessage, ElMessageBox } from "element-plus";
-import { Plus } from "@element-plus/icons-vue";
-import ProTable from "@/components/ProTable/index.vue";
-import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
-import { localGet } from "@/utils";
-
-import {
-  getRuleList,
-  saveFriendCouponRule,
-  getMutualAttention,
-  delFriendCouponRule,
-  getRuleById,
-  getVoucherList,
-  getIssueCouponList
-} from "@/api/modules/newLoginApi";
-
-// ProTable 实例
-const proTable = ref<ProTableInstance>();
-
-// 对话框相关
-const dialogVisible = ref(false);
-const dialogTitle = computed(() => (isEdit.value ? "编辑活动" : "添加活动"));
-const isEdit = ref(false);
-const currentEditId = ref("");
-
-// 表单引用
-const formRef = ref<FormInstance>();
-
-// 商家列表(互相关注接口,仅显示 phoneId 带 store 的商家)
-const merchantList = ref<Array<{ value: string | number; label: string; raw?: any }>>([]);
-const merchantListRaw = ref<any[]>([]);
-
-// 优惠券/折扣券选项缓存 key: '1-couponList' | '2-couponList'
-const couponOptionsMap = ref<Record<string, Array<{ value: string; label: string }>>>({});
-const couponDataMap = ref<Record<string, any[]>>({});
-
-const normalizeCouponValue = (v: any) => (v != null && v !== "" ? String(v) : "");
-
-// 编辑时合并详情中的 storeName,使商家下拉能正确显示;按 label 去重,避免同一商家出现多次
-const effectiveMerchantOptions = computed(() => {
-  const base = merchantList.value || [];
-  const fromDetail = (formData.coupons || [])
-    .filter((c: any) => c.merchant != null && c.merchant !== "" && c.storeName)
-    .map((c: any) => ({ value: c.merchant, label: c.storeName }));
-  const seenValue = new Set(base.map((o: any) => o.value));
-  const extra = fromDetail.filter((o: any) => {
-    if (seenValue.has(o.value)) return false;
-    seenValue.add(o.value);
-    return true;
-  });
-  const merged = [...base, ...extra];
-  // 按 label(店名)去重:同一店名只保留一项,优先保留在 form 中已选中的 value,避免下拉重复显示
-  const usedValues = new Set((formData.coupons || []).map((c: any) => c.merchant).filter((v: any) => v != null && v !== ""));
-  const byLabel = new Map<string, { value: string | number; label: string }>();
-  for (const o of merged) {
-    const label = o.label || "";
-    const existing = byLabel.get(label);
-    if (!existing) {
-      byLabel.set(label, o);
-    } else if (usedValues.has(o.value) && !usedValues.has(existing.value)) {
-      byLabel.set(label, o);
-    }
-  }
-  return Array.from(byLabel.values());
-});
-
-const getCouponOptions = (distributeType: number) => {
-  const key = distributeType === 2 ? "2-couponList" : "1-couponList";
-  return couponOptionsMap.value[key] || [];
-};
-
-// 表单数据
-const formData = reactive({
-  acName: "",
-  distributeType: 1 as 1 | 2, // 1-满减券 2-折扣券
-  moneyLow: "" as string | number,
-  moneyHigh: "" as string | number,
-  coupons: [] as Array<{
-    merchant: string | number | null;
-    coupon: string | null;
-    remaining: number;
-    storeName?: string;
-  }>
-});
-
-// 金额验证器
-const validateMoneyLow = (rule: any, value: any, callback: any) => {
-  if (value === "" || value === null || value === undefined) {
-    callback(new Error("请输入最低消费金额"));
-    return;
-  }
-
-  const numValue = Number(value);
-  if (isNaN(numValue)) {
-    callback(new Error("请输入有效的金额"));
-    return;
-  }
-
-  if (numValue < 0) {
-    callback(new Error("金额不能为负数"));
-    return;
-  }
-
-  // 验证小数位数不超过2位
-  const decimalPart = String(value).split(".")[1];
-  if (decimalPart && decimalPart.length > 2) {
-    callback(new Error("最多支持2位小数"));
-    return;
-  }
-
-  // 如果最高金额已填写,验证最低金额不能大于最高金额
-  if (formData.moneyHigh !== "" && formData.moneyHigh !== null && formData.moneyHigh !== undefined) {
-    const highValue = Number(formData.moneyHigh);
-    if (!isNaN(highValue) && numValue > highValue) {
-      callback(new Error("最低金额不能大于最高金额"));
-      return;
-    }
-  }
-
-  callback();
-};
-
-const validateMoneyHigh = (rule: any, value: any, callback: any) => {
-  if (value === "" || value === null || value === undefined) {
-    callback(new Error("请输入最高消费金额"));
-    return;
-  }
-
-  const numValue = Number(value);
-  if (isNaN(numValue)) {
-    callback(new Error("请输入有效的金额"));
-    return;
-  }
-
-  if (numValue < 0) {
-    callback(new Error("金额不能为负数"));
-    return;
-  }
-
-  // 验证小数位数不超过2位
-  const decimalPart = String(value).split(".")[1];
-  if (decimalPart && decimalPart.length > 2) {
-    callback(new Error("最多支持2位小数"));
-    return;
-  }
-
-  // 如果最低金额已填写,验证最高金额不能小于最低金额
-  if (formData.moneyLow !== "" && formData.moneyLow !== null && formData.moneyLow !== undefined) {
-    const lowValue = Number(formData.moneyLow);
-    if (!isNaN(lowValue) && numValue < lowValue) {
-      callback(new Error("最高金额不能小于最低金额"));
-      return;
-    }
-  }
-
-  callback();
-};
-
-// 表单验证规则
-const formRules = reactive<FormRules>({
-  acName: [{ required: true, message: "请输入活动名称", trigger: "blur" }],
-  moneyLow: [{ required: true, validator: validateMoneyLow, trigger: "blur" }],
-  moneyHigh: [{ required: true, validator: validateMoneyHigh, trigger: "blur" }]
-});
-
-// 表格列配置
-const columns = reactive<ColumnProps<any>[]>([
-  {
-    prop: "acName",
-    label: "活动名称",
-    search: { el: "input", props: { placeholder: "请输入活动名称" } }
-  },
-  {
-    prop: "endDate",
-    label: "有效期至"
-  },
-  {
-    prop: "relationType",
-    label: "消费门槛",
-    render: (scope: any) => {
-      return scope.row.moneyLow + "元" + "~" + scope.row.moneyHigh + "元";
-    }
-  },
-  {
-    prop: "status",
-    label: "状态",
-    enum: [
-      { label: "启用", value: 0 },
-      { label: "禁用", value: 1 }
-    ],
-    search: { el: "select", props: { placeholder: "请选择状态" } },
-    fieldNames: { label: "label", value: "value" },
-    render: (scope: any) => {
-      return scope.row.status == 0 ? "启用" : "禁用";
-    }
-  },
-  { prop: "operation", label: "操作", fixed: "right", width: 250 }
-]);
-
-// 初始化请求参数
-const initParam = reactive({
-  storeId: localGet("createdId") || ""
-});
-
-// 数据回调处理:兼容接口返回数组或 { records/list, total } 格式,确保有数据时分页显示正确总数(iOS 上 total 需为明确数字)
-const dataCallback = (data: any) => {
-  const list = Array.isArray(data) ? data : (data?.records ?? data?.list ?? []);
-  const totalNum =
-    typeof data?.total === "number" && Number.isFinite(data.total) ? data.total : Array.isArray(list) ? list.length : 0;
-  const total = Math.max(0, Math.floor(Number(totalNum)) || 0);
-  return {
-    list,
-    total
-  };
-};
-
-// 获取表格列表
-const getTableList = (params: any) => {
-  return getRuleList(params);
-};
-
-// 获取状态文本
-const getStatusText = (status: number) => {
-  const statusMap: Record<number, string> = {
-    0: "待同意",
-    1: "已同意",
-    2: "已拒绝"
-  };
-  return statusMap[status] || "--";
-};
-
-// 获取状态类型
-const getStatusType = (status: number): "success" | "warning" | "info" | "danger" => {
-  const typeMap: Record<number, "success" | "warning" | "info" | "danger"> = {
-    0: "warning",
-    1: "success",
-    2: "info"
-  };
-  return typeMap[status] || "info";
-};
-
-// 获取商家列表(互相关注接口,仅显示 phoneId 带 store 的商家)- 与 group_merchant eachOtherInterest 参数一致
-const loadAddActivityMerchants = async () => {
-  const phone = localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
-  try {
-    const res: any = await getMutualAttention({
-      page: 1,
-      size: 1000,
-      fansId: `store_${phone}`
-    });
-    const data = res?.data || res;
-    const records = data?.records || data?.list || (Array.isArray(data) ? data : []);
-    const list = Array.isArray(records) ? records : [];
-    const merchantListFiltered = list.filter((item: any) => {
-      const pid = String(item.phoneId ?? item.id ?? "");
-      return pid.includes("store");
-    });
-    merchantListRaw.value = merchantListFiltered;
-    // 按 value(id/storeId/phoneId)去重,避免同一商家在接口中多次返回导致下拉重复
-    const seen = new Set<string | number>();
-    merchantList.value = merchantListFiltered
-      .map((item: any) => ({
-        value: item.id ?? item.storeId ?? item.phoneId,
-        label: item.storeName ?? item.name ?? "",
-        raw: item
-      }))
-      .filter((o: any) => {
-        if (seen.has(o.value)) return false;
-        seen.add(o.value);
-        return true;
-      });
-  } catch (error) {
-    console.error("获取商家列表失败", error);
-    merchantList.value = [];
-    merchantListRaw.value = [];
-  }
-};
-
-// 打开添加对话框
-const openAddDialog = async () => {
-  isEdit.value = false;
-  currentEditId.value = "";
-  await loadAddActivityMerchants();
-  couponOptionsMap.value = {};
-  couponDataMap.value = {};
-  formData.distributeType = 1;
-  formData.coupons = [{ merchant: null, coupon: null, remaining: 0 }];
-  formData.acName = "";
-  formData.moneyLow = "";
-  formData.moneyHigh = "";
-  dialogVisible.value = true;
-};
-
-// 处理金额输入
-const handleMoneyInput = (field: "moneyLow" | "moneyHigh") => {
-  // 当一个金额字段改变时,触发另一个字段的验证
-  if (formRef.value) {
-    const otherField = field === "moneyLow" ? "moneyHigh" : "moneyLow";
-    // 如果另一个字段有值,触发其验证
-    if (formData[otherField] !== "" && formData[otherField] !== null && formData[otherField] !== undefined) {
-      formRef.value.validateField(otherField, () => {});
-    }
-  }
-};
-
-// 关闭对话框
-const closeDialog = () => {
-  dialogVisible.value = false;
-  formRef.value?.resetFields();
-  Object.assign(formData, {
-    acName: "",
-    distributeType: 1,
-    moneyLow: "",
-    moneyHigh: "",
-    coupons: []
-  });
-  currentEditId.value = "";
-};
-
-// 添加商家优惠券
-const addMerchantCoupon = () => {
-  formData.coupons.push({
-    merchant: null,
-    coupon: null,
-    remaining: 0
-  });
-};
-
-// 移除商家优惠券
-const removeMerchantCoupon = (index: number) => {
-  formData.coupons.splice(index, 1);
-};
-
-// 赠送类型切换:清空选项缓存并重置行内选择(与 group_merchant setDistributeType 一致)
-const onDistributeTypeChange = () => {
-  couponOptionsMap.value = {};
-  couponDataMap.value = {};
-  formData.coupons = formData.coupons.map(() => ({
-    merchant: null,
-    coupon: null,
-    remaining: 0
-  }));
-  if (formData.coupons.length !== 1) formData.coupons = [{ merchant: null, coupon: null, remaining: 0 }];
-  loadAddActivityMerchants();
-};
-
-// 加载当前类型下的优惠券/折扣券选项
-const loadCouponOptionsByType = async (distributeType: number) => {
-  const key = distributeType === 2 ? "2-couponList" : "1-couponList";
-  if (couponOptionsMap.value[key]?.length) return;
-  const storeId = localGet("createdId") || "";
-  try {
-    const res: any = await getIssueCouponList({
-      storeId,
-      tab: 1,
-      size: 100,
-      page: 1,
-      couponName: "",
-      couponsFromType: 1,
-      couponType: formData.distributeType
-    });
-    const records = res?.data?.records ?? res?.data ?? [];
-    const list = Array.isArray(records) ? records : [];
-    couponDataMap.value[key] = list;
-    couponOptionsMap.value[key] = list.map((item: any) => ({
-      value: String(item.id ?? item.couponId ?? ""),
-      label: item.couponName ?? item.name ?? ""
-    }));
-  } catch (e) {
-    couponDataMap.value[key] = [];
-    couponOptionsMap.value[key] = [];
-  }
-};
-
-// 商家选择改变
-const handleMerchantChange = async (index: number) => {
-  const item = formData.coupons[index];
-  item.coupon = null;
-  item.remaining = 0;
-  if (item.merchant) {
-    const raw = merchantListRaw.value.find(
-      (m: any) => (m.id ?? m.friendStoreUserId) === item.merchant || (m.storeId ?? m.phoneId) === item.merchant
-    );
-    if (raw) item.remaining = raw.singleQty ?? raw.couponNum ?? 0;
-    await loadCouponOptionsByType(formData.distributeType);
-  }
-};
-
-// 优惠券/折扣券选择改变
-const handleCouponChange = (index: number) => {
-  const item = formData.coupons[index];
-  const key = formData.distributeType === 2 ? "2-couponList" : "1-couponList";
-  const list = couponDataMap.value[key] || [];
-  const idKey = formData.distributeType === 2 ? "voucherId" : "couponId";
-  if (item.coupon && list.length) {
-    const found = list.find((c: any) => String(c[idKey] ?? c.couponId ?? c.id ?? "") === String(item.coupon));
-    if (found) item.remaining = found.singleQty ?? found.couponNum ?? 0;
-  }
-};
-
-// 编辑行数据(与 group_merchant editActivity 逻辑及 getRuleById 回显一致)
-const editRow = async (row: any) => {
-  isEdit.value = true;
-  currentEditId.value = row.id;
-  const id = row.id;
-  if (!id) return;
-
-  await loadAddActivityMerchants();
-  const type = row.type === 2 ? 2 : 1;
-  formData.distributeType = type;
-  couponOptionsMap.value = {};
-  couponDataMap.value = {};
-
-  try {
-    const res: any = await getRuleById({ id });
-    if (res.code != 200 || !res.data) {
-      ElMessage.error(res.msg || "获取活动详情失败");
-      return;
-    }
-    const d = res.data;
-    formData.acName = d.acName;
-    formData.moneyLow = d.moneyLow;
-    formData.moneyHigh = d.moneyHigh;
-    formData.distributeType = d.couponType;
-    formData.coupons = (d.lifeDiscountCouponFriendRuleDetailVos || []).map((vo: any) => ({
-      merchant: vo.friendStoreUserId ?? null,
-      coupon: normalizeCouponValue(vo.couponId ?? vo.voucherId),
-      remaining: vo.singleQty ?? vo.couponNum ?? 0,
-      storeName: vo.storeName ?? ""
-    }));
-    if (formData.coupons.length === 0) {
-      formData.coupons = [{ merchant: null, coupon: null, remaining: 0 }];
-    }
-    await loadCouponOptionsByType(formData.distributeType);
-    // 编辑回显:从已加载的券列表中按券 id 同步剩余张数,避免详情接口未返回 singleQty/couponNum 时一直显示 0
-    const key = formData.distributeType === 2 ? "2-couponList" : "1-couponList";
-    const list = couponDataMap.value[key] || [];
-    const idKey = formData.distributeType === 2 ? "voucherId" : "couponId";
-    formData.coupons.forEach((row: any) => {
-      if (!row.coupon) return;
-      const found = list.find((c: any) => String(c[idKey] ?? c.couponId ?? c.id ?? "") === String(row.coupon));
-      if (found) row.remaining = found.singleQty ?? found.couponNum ?? 0;
-    });
-    dialogVisible.value = true;
-  } catch (error: any) {
-    ElMessage.error(error?.msg || "获取活动详情失败");
-  }
-};
-
-// 删除行数据
-const deleteRow = (row: any) => {
-  ElMessageBox.confirm("确定要删除这个活动吗?", "提示", {
-    confirmButtonText: "确定",
-    cancelButtonText: "取消",
-    type: "warning"
-  })
-    .then(async () => {
-      try {
-        const res: any = await delFriendCouponRule({ id: row.id });
-        if (res.code == 200) {
-          ElMessage.success("删除成功");
-          proTable.value?.getTableList();
-        } else {
-          ElMessage.error(res.msg || "删除失败");
-        }
-      } catch (error: any) {
-        ElMessage.error(error?.msg || "删除失败");
-      }
-    })
-    .catch(() => {
-      // 用户取消删除
-    });
-};
-
-// 提交表单(与 group_merchant saveActivity 请求参数一致)
-const handleSubmit = async () => {
-  if (!formRef.value) return;
-
-  await formRef.value.validate(async (valid: boolean) => {
-    if (!valid) return;
-
-    if (!formData.coupons.length) {
-      ElMessage.warning("请至少添加一个商家优惠券");
-      return;
-    }
-    const hasEmpty = formData.coupons.some((item: any) => !item.merchant || !item.coupon);
-    if (hasEmpty) {
-      ElMessage.warning("请完善所有商家优惠券信息");
-      return;
-    }
-
-    const isVoucher = formData.distributeType === 2;
-    const details = formData.coupons.map((item: any) =>
-      isVoucher
-        ? { voucherId: item.coupon, friendStoreUserId: item.merchant }
-        : { couponId: item.coupon, friendStoreUserId: item.merchant }
-    );
-
-    const requestData: any = {
-      storeId: localGet("createdId") || "",
-      acName: formData.acName.trim(),
-      couponType: formData.distributeType,
-      moneyHigh: Number(formData.moneyHigh),
-      moneyLow: Number(formData.moneyLow),
-      details
-    };
-    if (isEdit.value && currentEditId.value) {
-      requestData.id = currentEditId.value;
-    }
-
-    try {
-      const res: any = await saveFriendCouponRule(requestData);
-      if (res.code == 200) {
-        ElMessage.success(isEdit.value ? "编辑成功" : "添加成功");
-        closeDialog();
-        proTable.value?.getTableList();
-      } else {
-        ElMessage.error(res.msg || "操作失败");
-      }
-    } catch (error: any) {
-      ElMessage.error(error?.msg || (isEdit.value ? "编辑失败" : "添加失败"));
-    }
-  });
-};
-
-// 页面加载时触发查询
-onMounted(() => {
-  proTable.value?.getTableList();
-});
-</script>
-
-<style lang="scss" scoped>
-.friend-relation-container {
-  .header-button {
-    margin-bottom: 16px;
-  }
-  .merchant-coupon-list {
-    width: 100%;
-    .merchant-coupon-item {
-      display: flex;
-      align-items: center;
-      margin-bottom: 10px;
-    }
-    .remaining-text {
-      margin-right: 10px;
-      font-size: 13px;
-      color: var(--el-text-color-regular);
-    }
-  }
-  .dialog-footer {
-    display: flex;
-    gap: 10px;
-    justify-content: flex-end;
-  }
-}
-</style>

+ 46 - 3
src/views/dynamicManagement/myDynamic.vue

@@ -461,7 +461,13 @@
         <div ref="relationListScrollRef" class="relation-list" @scroll="onRelationListScroll">
           <div v-for="user in filteredRelationList" :key="user.id" class="relation-item">
             <div class="user-info-row">
-              <div class="user-avatar-small">
+              <div
+                class="user-avatar-small relation-avatar-link"
+                role="button"
+                tabindex="0"
+                @click.stop="handleRelationAvatarClick(user)"
+                @keydown.enter.prevent="handleRelationAvatarClick(user)"
+              >
                 <img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
                 <el-icon v-else :size="40">
                   <Avatar />
@@ -826,7 +832,10 @@ interface RelationUser {
   avatar: string;
   description: string;
   relationStatus: "following" | "mutual" | "none"; // following: 已关注, mutual: 互相关注, none: 未关注
-  phoneId?: string; // 用户的 phoneId,用于后续操作
+  phoneId?: string; // 用户的 phoneId,用于后续操作与跳转主页
+  /** 对应他人主页 query.userId(多为 storeUserId) */
+  storeUserId?: string | number;
+  phone?: string;
 }
 
 // 响应式数据
@@ -1112,6 +1121,27 @@ const handleVideoPlay = (index: number) => {
   });
 };
 
+// 关系弹窗(好友/关注/粉丝)内点击头像进入他人主页
+const handleRelationAvatarClick = (user: RelationUser) => {
+  const phoneId = String(user.phoneId || "").trim();
+  if (!phoneId) {
+    ElMessage.warning("无法跳转:缺少用户标识");
+    return;
+  }
+  const userId = String(user.storeUserId ?? user.id ?? "");
+  relationDialogVisible.value = false;
+  router.push({
+    path: "/dynamicManagement/userDynamic",
+    query: {
+      userId,
+      phoneId,
+      userName: user.name || "",
+      userAvatar: user.avatar || "",
+      phone: user.phone || ""
+    }
+  });
+};
+
 // 查看用户主页
 const handleViewUserProfile = () => {
   // ElMessage.info("查看用户主页功能开发中");
@@ -1800,7 +1830,9 @@ const mapRecordToRelationUser = (item: any, defaultStatus: "following" | "mutual
   avatar: item.image || item.userImage || item.avatar || item.headImg || "",
   description: item.blurb || item.accountBlurb || item.description || item.bio || item.signature || "",
   relationStatus: item.isFollowThis === 1 ? ("mutual" as const) : defaultStatus,
-  phoneId: item.phoneId || item.fansId || ""
+  phoneId: item.phoneId || item.fansId || "",
+  storeUserId: item.storeUserId ?? item.storeId ?? item.storesUserId ?? item.userId ?? item.id,
+  phone: item.phone || item.mobile || item.telephone || ""
 });
 
 const handleFriendList = async (append = false) => {
@@ -2955,6 +2987,17 @@ onMounted(() => {
                 height: 100%;
                 object-fit: cover;
               }
+              &.relation-avatar-link {
+                cursor: pointer;
+                outline: none;
+                transition: opacity 0.2s;
+                &:hover {
+                  opacity: 0.88;
+                }
+                &:focus-visible {
+                  box-shadow: 0 0 0 2px rgb(64 158 255 / 35%);
+                }
+              }
             }
             .user-details {
               flex: 1;

+ 23 - 6
src/views/dynamicManagement/userDynamic.vue

@@ -549,7 +549,9 @@
                 :max="getGiftRowMaxQuantity(row)"
                 @input="(val: string | number) => setGiftRowQuantity(row, val)"
               />
-              <span v-if="row.couponId" class="quantity-limit-hint"> 最多可赠 {{ getGiftRowMaxQuantity(row) }} 张 </span>
+              <span v-if="row.couponId && !isGiftRowUnlimitedQty(row)" class="quantity-limit-hint">
+                最多可赠 {{ getGiftRowMaxQuantity(row) }} 张
+              </span>
             </div>
             <el-button type="danger" link :icon="Delete" circle title="删除" @click="removeGiftCouponRow(index)" />
           </div>
@@ -1187,7 +1189,7 @@ const handleCommand = (command: string) => {
 const giftCouponDialogVisible = ref(false);
 const giftFriendList = ref<{ id: number | string; name: string; phoneId?: string }[]>([]);
 const giftFriendListLoading = ref(false);
-const giftCouponList = ref<{ id: number | string; name: string; singleQty?: number }[]>([]);
+const giftCouponList = ref<{ id: number | string; name: string; singleQty?: number; unlimitedQty?: number }[]>([]);
 const giftCouponListLoaded = ref(false);
 const giftCouponListLoading = ref(false);
 let giftCouponRowKey = 0;
@@ -1249,14 +1251,16 @@ const loadGiftCouponList = async (giftType: 1 | 2 = giftCouponFormData.giftType)
         giftCouponList.value = rawList.map((item: any) => ({
           id: item.couponId ?? item.id,
           name: item.name ?? item.couponName ?? "",
-          singleQty: item.singleQty != null ? Number(item.singleQty) : 100
+          singleQty: item.singleQty != null ? Number(item.singleQty) : 100,
+          unlimitedQty: item.unlimitedQty != null ? Number(item.unlimitedQty) : 0
         }));
       } else {
         // 折扣券:id 用 voucherId,提交时传 voucherId
         giftCouponList.value = rawList.map((item: any) => ({
           id: item.voucherId ?? item.id ?? item.couponId,
           name: item.name ?? item.couponName ?? "",
-          singleQty: item.singleQty != null ? Number(item.singleQty) : 100
+          singleQty: item.singleQty != null ? Number(item.singleQty) : 100,
+          unlimitedQty: item.unlimitedQty != null ? Number(item.unlimitedQty) : 0
         }));
       }
     } else {
@@ -1297,11 +1301,24 @@ const isGiftCouponSelectedInOtherRow = (couponId: string | number, currentRowInd
   );
 };
 
-// 根据所选券的 singleQty 限制该行数量上限
+/** 赠券:所选券为不限张数(unlimitedQty=1)时不展示「最多可赠」,数量上限 9999 */
+const GIFT_UNLIMITED_QTY_MAX = 9999;
+
+const isGiftRowUnlimitedQty = (row: { couponId: string | number; quantity: number }) => {
+  if (row.couponId === "" || row.couponId == null) return false;
+  const coupon = giftCouponList.value.find((c: any) => String(c.id) === String(row.couponId));
+  const uq = coupon?.unlimitedQty;
+  return uq === 1 || uq === "1" || uq === true;
+};
+
+// 根据所选券的 singleQty 限制该行数量上限;unlimitedQty=1 时不限张数,上限 9999
 const getGiftRowMaxQuantity = (row: { couponId: string | number; quantity: number }) => {
   if (row.couponId === "" || row.couponId == null) return 100;
   const coupon = giftCouponList.value.find((c: any) => String(c.id) === String(row.couponId));
-  const max = coupon?.singleQty != null ? Number(coupon.singleQty) : 100;
+  if (!coupon) return 100;
+  const uq = coupon.unlimitedQty;
+  if (uq === 1 || uq === "1" || uq === true) return GIFT_UNLIMITED_QTY_MAX;
+  const max = coupon.singleQty != null ? Number(coupon.singleQty) : 100;
   return Math.max(1, max);
 };
 

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

@@ -95,7 +95,7 @@
         <el-form-item label="套餐名">
           {{ formInventory.groupName }}
         </el-form-item>
-        <el-form-item label="剩余库存">
+        <el-form-item label="剩余数量">
           {{ formInventory.inventoryNum }}
         </el-form-item>
         <el-form-item label="修改库存" prop="newInventory">
@@ -275,7 +275,7 @@ const columns = reactive<ColumnProps<any>[]>([
   },
   {
     prop: "inventoryNum",
-    label: "剩余库存"
+    label: "剩余数量"
   },
   {
     prop: "goodsId",

+ 33 - 60
src/views/ticketManagement/couponDetail.vue

@@ -38,50 +38,18 @@
             <div class="detail-label">折扣率</div>
             <div class="detail-value">{{ couponModel.discountRate || "--" }}%</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}天` : "--" }}
+              {{ validityDisplay }}
             </div>
           </div>
-          <!-- 库存 -->
+          <!-- 数量 -->
           <div class="detail-item">
-            <div class="detail-label">库存</div>
+            <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 ? "否" : "--" }}
+              {{ quantityDisplay }}
             </div>
           </div>
         </div>
@@ -121,7 +89,7 @@
  * 优惠券管理 - 详情页面
  * 功能:显示优惠券的详细信息
  */
-import { ref, onMounted } from "vue";
+import { ref, onMounted, computed } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { ElMessage } from "element-plus";
 import { getCouponDetail } from "@/api/modules/couponManagement";
@@ -147,6 +115,9 @@ const couponModel = ref<any>({
   // 结束领取时间
   endGetDate: "",
   // 有效期
+  longTermValid: undefined as number | undefined,
+  expirationDate: "",
+  unlimitedQty: undefined as number | undefined,
   specifiedDay: "",
   // 库存
   singleQty: "",
@@ -162,6 +133,30 @@ const couponModel = ref<any>({
   supplementaryInstruction: ""
 });
 
+const validityDisplay = computed(() => {
+  const m = couponModel.value;
+  const lt = m.longTermValid;
+  if (lt === 1 || lt === "1" || lt === true) return "长期有效";
+  const days = m.expirationDate ?? m.specifiedDay;
+  const n = Number(days);
+  if (days === null || days === undefined || days === "" || Number.isNaN(n) || n === 0) {
+    const sd = m.specifiedDay;
+    const sn = Number(sd);
+    if (sd === null || sd === undefined || sd === "" || Number.isNaN(sn) || sn === 0) return "长期有效";
+    return `领取后${sd}天`;
+  }
+  return `领取后${days}天`;
+});
+
+const quantityDisplay = computed(() => {
+  const m = couponModel.value;
+  const uq = m.unlimitedQty;
+  if (uq === 1 || uq === "1" || uq === true) return "不限";
+  const sq = m.singleQty;
+  if (sq === null || sq === undefined || sq === "") return "--";
+  return `${sq}张`;
+});
+
 // ==================== 生命周期钩子 ====================
 
 /**
@@ -214,28 +209,6 @@ const loadDetailData = async () => {
 };
 
 // ==================== 工具函数 ====================
-
-/**
- * 格式化日期
- * @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">

+ 58 - 95
src/views/ticketManagement/index.vue

@@ -8,17 +8,6 @@
       :data-callback="dataCallback"
       :key="activeName"
     >
-      <!-- 表格 header 按钮 -->
-      <template #tableHeader="scope">
-        <div class="table-header-btn">
-          <div class="header-actions">
-            <!-- 优惠券:状态 Tab(全部、未开始、进行中、已下架、已结束、已清库,不含草稿) -->
-            <el-tabs v-if="activeName === '2'" v-model="couponStatusTab" class="status-tabs" @tab-click="onCouponStatusTabChange">
-              <el-tab-pane v-for="tab in couponStatusTabOptions" :key="tab.value" :label="tab.label" :name="tab.value" />
-            </el-tabs>
-          </div>
-        </div>
-      </template>
       <template #tableHeaderRight="scope">
         <el-button :icon="Plus" class="button" type="primary" @click="newCoupon" v-if="type"> 新建优惠券 </el-button>
         <!--        <div class="action-buttons">-->
@@ -34,10 +23,6 @@
           </p>
           <p style="margin: 0; line-height: 1.5" v-if="scope.row.dataType == 0">审核状态:{{ scope.row.reviewType || "--" }}</p>
         </template>
-        <!-- 优惠券:只显示状态一行 -->
-        <template v-else>
-          <span>{{ getStatusLabel(scope.row.status) }}</span>
-        </template>
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
@@ -47,26 +32,26 @@
         <el-button v-if="canShowAction(scope.row, '下架')" link type="primary" @click="changeTypes(scope.row, 6)">
           下架
         </el-button>
-        <el-button v-if="canShowAction(scope.row, '修改库存')" link type="primary" @click="changeInventory(scope.row)">
-          修改库存
+        <el-button v-if="canShowAction(scope.row, '修改数量')" link type="primary" @click="changeInventory(scope.row)">
+          修改数量
         </el-button>
         <el-button v-if="canShowAction(scope.row, '查看详情')" link type="primary" @click="toDetail(scope.row)">
           查看详情
         </el-button>
-        <el-button v-if="canShowAction(scope.row, '编辑')" link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
         <el-button v-if="canShowAction(scope.row, '删除')" link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+        <el-button v-if="canShowAction(scope.row, '编辑')" link type="primary" @click="editRow(scope.row)"> 编辑 </el-button>
         <el-button v-if="canShowAction(scope.row, '查看拒绝原因')" link type="primary" @click="viewRejectReason(scope.row)">
           查看拒绝原因
         </el-button>
       </template>
     </ProTable>
-    <el-dialog v-model="dialogFormVisible" title="修改库存" width="500" append-to-body class="inventory-dialog-ios-fix">
+    <el-dialog v-model="dialogFormVisible" title="修改数量" width="500" append-to-body class="inventory-dialog-ios-fix">
       <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-form-item label="剩余数量"> {{ formInventory.singleQty }}张 </el-form-item>
+        <el-form-item label="修改数量" prop="newInventory">
           <el-input
             v-model="formInventory.newInventory"
             placeholder="请输入"
@@ -118,7 +103,12 @@ import ProTable from "@/components/ProTable/index.vue";
 import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
 import { Plus } from "@element-plus/icons-vue";
 import { delThaliById, getThaliList, updateNum, updateStatus } from "@/api/modules/voucherManagement";
-import { delCouponById, getStoreAllCouponList, updateCouponSingleQty, updateCouponStatus } from "@/api/modules/couponManagement";
+import {
+  deleteLifeDiscountCoupon,
+  getStoreAllCouponList,
+  updateCouponSingleQty,
+  updateCouponStatus
+} from "@/api/modules/couponManagement";
 import { ElMessageBox } from "element-plus/es";
 import { localGet, usePermission } from "@/utils";
 import { formatCurrency } from "@/utils/formatCurrency";
@@ -160,10 +150,10 @@ const rules = reactive<FormRules<RuleForm>>({
             callback(new Error("库存不得大于10000"));
             return;
           }
-          // 验证新库存值不能小于剩余库存
+          // 验证新库存值不能小于剩余数量
           const currentQty = Number(formInventory.value.singleQty) || 0;
           if (numValue < currentQty) {
-            callback(new Error(`新库存值不能小于剩余库存(${currentQty})张`));
+            callback(new Error(`新库存值不能小于剩余数量(${currentQty})张`));
             return;
           }
         }
@@ -181,24 +171,6 @@ const statusEnum = [
   { label: "已售罄", value: "4" },
   { label: "已结束", value: "7" }
 ];
-const statusEnumY = [
-  { label: "未开始", value: "2" },
-  { label: "进行中", value: "0" },
-  { label: "已下架", value: "3" },
-  { label: "已结束", value: "1" },
-  { label: "已清库", value: "4" }
-];
-// 优惠券状态 Tab 选项(不含草稿)
-const couponStatusTabOptions = [
-  { label: "全部", value: "0" },
-  { label: "进行中", value: "1" },
-  // { label: "草稿", value: "3" },
-  { label: "已结束", value: "2" },
-  { label: "已下架", value: "5" },
-  { label: "未开始", value: "4" },
-  { label: "已清库", value: "6" }
-];
-const couponStatusTab = ref("0");
 // 优惠券类型枚举(满减券、折扣券)
 const couponTypeEnum = [
   { label: "满减券", value: "1" },
@@ -232,7 +204,7 @@ const voucherColumns = reactive<ColumnProps<any>[]>([
   },
   {
     prop: "singleQty",
-    label: "剩余库存",
+    label: "剩余数量",
     render: scope => {
       return scope.row.singleQty === null || scope.row.singleQty === undefined || scope.row.singleQty === ""
         ? 0
@@ -259,7 +231,7 @@ const voucherColumns = reactive<ColumnProps<any>[]>([
   { prop: "operation", label: "操作", fixed: "right", width: 330 }
 ]);
 
-// 优惠券表格列配置(参考商家端:优惠券名称、剩余库存、结束时间、状态、类型、操作
+// 优惠券表格列配置:名称、剩余数量、类型、操作(无结束时间、无状态列
 const couponColumns = reactive<ColumnProps<any>[]>([
   {
     prop: "name",
@@ -271,25 +243,39 @@ const couponColumns = reactive<ColumnProps<any>[]>([
   },
   {
     prop: "singleQty",
-    label: "剩余库存",
-    render: scope => {
-      return scope.row.singleQty === null || scope.row.singleQty === undefined || scope.row.singleQty === ""
-        ? 0
-        : scope.row.singleQty;
+    label: "剩余数量",
+    render: (scope: any) => {
+      const row = scope.row;
+      const uq = row.unlimitedQty;
+      if (uq === 1 || uq === "1" || uq === true) return "不限";
+      if (uq === 0 || uq === "0") {
+        const sq = row.singleQty;
+        if (sq === null || sq === undefined || sq === "") return "--";
+        return `${sq}张`;
+      }
+      const sq = row.singleQty;
+      if (sq === null || sq === undefined || sq === "") return "0";
+      if (Number(sq) === 0) return "不限";
+      return `${sq}张`;
     }
   },
   {
-    prop: "validDate",
-    label: "结束时间",
+    prop: "longTermValid",
+    label: "有效期",
     render: (scope: any) => {
-      return scope.row.validDate?.replace(/-/g, "/") || "--";
+      const row = scope.row;
+      const longTerm = row.longTermValid;
+      if (longTerm === 1 || longTerm === "1" || longTerm === true) {
+        return "长期有效";
+      }
+      const days = row.expirationDate;
+      if (days === null || days === undefined || days === "") {
+        return "--";
+      }
+      return `${days}天`;
     }
   },
   {
-    prop: "status",
-    label: "状态"
-  },
-  {
     prop: "couponType",
     label: "类型",
     search: {
@@ -300,7 +286,7 @@ const couponColumns = reactive<ColumnProps<any>[]>([
     fieldNames: { label: "label", value: "value" },
     render: (scope: any) => getCouponTypeLabel(scope.row.couponType)
   },
-  { prop: "operation", label: "操作", fixed: "right", width: 280 }
+  { prop: "operation", label: "操作", fixed: "right", width: 260 }
 ]);
 
 // 根据当前选中的tab动态返回列配置
@@ -338,24 +324,24 @@ const CO_STATUS = {
 // 代金券:各状态下的操作列表(草稿用 dataType==1 单独判断)
 const VOUCHER_ACTIONS_BY_STATUS: Record<number, string[]> = {
   [VO_STATUS.待审核]: ["查看详情", "查看拒绝原因"],
-  [VO_STATUS.未开始]: ["查看详情", "上架", "修改库存", "删除"],
+  [VO_STATUS.未开始]: ["查看详情", "上架", "修改数量", "删除"],
   [VO_STATUS.审核拒绝]: ["查看详情", "编辑", "删除", "查看拒绝原因"],
-  [VO_STATUS.进行中]: ["查看详情", "下架", "修改库存"],
-  [VO_STATUS.已售罄]: ["查看详情", "编辑", "修改库存", "删除"],
+  [VO_STATUS.进行中]: ["查看详情", "下架", "修改数量"],
+  [VO_STATUS.已售罄]: ["查看详情", "编辑", "修改数量", "删除"],
   [VO_STATUS.已下架]: ["查看详情", "上架", "编辑", "删除"],
   [VO_STATUS.已结束]: ["编辑", "删除"]
 };
 // 代金券草稿(dataType==1)仅显示:编辑、删除
 const VOUCHER_DRAFT_ACTIONS = ["编辑", "删除"];
 
-// 优惠券:各状态下的操作列表(完全按产品表
+// 优惠券:各状态下的操作列表(无下架、无修改数量
 const COUPON_ACTIONS_BY_STATUS: Record<number, string[]> = {
-  [CO_STATUS.草稿]: ["查看详情", "编辑", "删除"],
-  [CO_STATUS.未开始]: ["查看详情", "修改库存"],
-  [CO_STATUS.进行中]: ["下架", "查看详情", "修改库存"],
-  [CO_STATUS.已下架]: ["上架", "查看详情", "修改库存"],
+  [CO_STATUS.草稿]: ["查看详情", "删除", "编辑"],
+  [CO_STATUS.未开始]: ["查看详情", "删除"],
+  [CO_STATUS.进行中]: ["查看详情", "删除"],
+  [CO_STATUS.已下架]: ["上架", "查看详情", "删除"],
   [CO_STATUS.已结束]: ["查看详情", "删除"],
-  [CO_STATUS.已售罄]: ["查看详情", "编辑", "删除"]
+  [CO_STATUS.已售罄]: ["查看详情", "删除", "编辑"]
 };
 
 /** 判断当前行是否显示某操作按钮(代金券/优惠券通用) */
@@ -375,17 +361,6 @@ const isVoucher = computed(() => activeName.value === "1");
 // 判断是否为优惠券
 const isCoupon = computed(() => activeName.value === "2");
 
-// 获取状态标签
-const getStatusLabel = (status: any) => {
-  const statusEnumMap = CO_STATUS;
-  for (const [label, value] of Object.entries(statusEnumMap)) {
-    if (value === status) {
-      return label;
-    }
-  }
-  return "--";
-};
-
 // 优惠券类型展示(满减券/折扣券)
 const getCouponTypeLabel = (type: number | string) => {
   const t = type === undefined || type === null ? "" : String(type);
@@ -484,7 +459,7 @@ const getTableList = (params: any) => {
     // 优惠券:使用 getStoreAllCouponList,搜索项 name、couponType 需一并传入
     return getStoreAllCouponList({
       storeId: newParams.storeId || localGet("createdId") || "",
-      tab: couponStatusTab.value || "",
+      tab: "0",
       size: newParams.pageSize || 10,
       page: newParams.pageNum || 1,
       couponName: newParams.name ?? "",
@@ -531,12 +506,8 @@ const deleteRow = (row: any) => {
         };
         return delThaliById(params);
       } else {
-        // 优惠券删除逻辑
-        const params = {
-          id: row.id,
-          groupType: localGet("businessSection")
-        };
-        return delCouponById(params);
+        // 优惠券删除:life-discount-coupon 路径参数 id
+        return deleteLifeDiscountCoupon(row.id);
       }
     })
     .then(() => {
@@ -550,14 +521,6 @@ const deleteRow = (row: any) => {
 // Tab切换处理
 const handleClick = () => {};
 
-// 优惠券状态 Tab 切换
-// 优惠券状态 Tab 切换(tab-click 触发时 v-model 尚未更新,用 nextTick 等绑定更新后再请求)
-const onCouponStatusTabChange = () => {
-  nextTick(() => {
-    proTable.value?.getTableList();
-  });
-};
-
 // 修改状态(上架/下架)
 const changeTypes = async (row: any, status: number) => {
   if (isVoucher.value) {
@@ -576,7 +539,7 @@ const changeTypes = async (row: any, status: number) => {
     }
   }
 };
-// 修改库存
+// 修改数量
 const changeInventory = (row: any) => {
   formInventory.value = {
     id: row.id,
@@ -587,7 +550,7 @@ const changeInventory = (row: any) => {
   dialogFormVisible.value = true;
 };
 
-// 提交修改库存
+// 提交修改数量
 const handleSubmit = async () => {
   if (!ruleFormRef.value) return;
   await ruleFormRef.value.validate(async valid => {
@@ -650,7 +613,7 @@ const closeRejectReasonDialog = () => {
 </script>
 
 <style lang="scss" scoped>
-/* iOS 上修改库存弹窗:避免键盘弹起时遮挡底部,弹窗可滚动 */
+/* iOS 上修改数量弹窗:避免键盘弹起时遮挡底部,弹窗可滚动 */
 :deep(.inventory-dialog-ios-fix) {
   .el-dialog__body {
     max-height: 60vh;

+ 149 - 133
src/views/ticketManagement/newCoupon.vue

@@ -1,5 +1,5 @@
 <template>
-  <!-- 优惠券管理 - 新建/编辑页面(参考 IssueCoupons 与设计图) -->
+  <!-- 优惠券管理 - 新建/编辑页面(参考 IssueCoupons;左:类型/名称/折扣或面值/低消/有效期,右:数量/补充说明) -->
   <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
     <div class="header">
       <el-button @click="goBack"> 返回 </el-button>
@@ -9,7 +9,7 @@
     </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="couponType">
@@ -45,62 +45,47 @@
               <template #suffix> 元可用 </template>
             </el-input>
           </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"
-              style="width: 100%"
-            />
-          </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"
-              style="width: 100%"
-            />
+          <!-- 有效期:长期有效 / 自定义(领取后有效天数) -->
+          <el-form-item label="有效期" prop="validityMode">
+            <el-radio-group v-model="couponModel.validityMode" class="radio-group">
+              <el-radio :value="0"> 长期有效 </el-radio>
+              <el-radio :value="1"> 自定义 </el-radio>
+            </el-radio-group>
           </el-form-item>
-          <!-- 有效期 -->
-          <el-form-item label="有效期" prop="specifiedDay">
-            <el-input v-model="couponModel.specifiedDay" maxlength="5" placeholder="请输入" clearable>
+          <el-form-item v-if="couponModel.validityMode === 1" label="领取后有效期" prop="validDaysAfterReceive">
+            <el-input
+              v-model="couponModel.validDaysAfterReceive"
+              maxlength="5"
+              placeholder="请输入正整数"
+              clearable
+              inputmode="numeric"
+              @input="onValidDaysAfterReceiveInput"
+            >
               <template #suffix> 天 </template>
             </el-input>
           </el-form-item>
         </div>
-        <!-- 右侧:库存、用户领取规则、收藏店铺、补充说明 -->
+        <!-- 右侧:数量、补充说明 -->
         <div class="contentRight">
-          <!-- 库存 -->
-          <el-form-item label="库存" prop="singleQty">
-            <el-input v-model="couponModel.singleQty" maxlength="5" placeholder="请输入" clearable>
+          <!-- 数量:不限 / 自定义 -->
+          <el-form-item label="数量" prop="quantityMode">
+            <el-radio-group v-model="couponModel.quantityMode" class="radio-group">
+              <el-radio :value="0"> 不限 </el-radio>
+              <el-radio :value="1"> 自定义 </el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item v-if="couponModel.quantityMode === 1" label="请输入数量" prop="singleQty">
+            <el-input
+              v-model="couponModel.singleQty"
+              maxlength="5"
+              placeholder="请输入正整数"
+              clearable
+              inputmode="numeric"
+              @input="onSingleQtyInput"
+            >
               <template #suffix> 张 </template>
             </el-input>
           </el-form-item>
-          <!-- 用户领取规则 -->
-          <!-- <el-form-item label="用户领取规则" prop="claimRule" class="claim-rule-item">
-            <template #label>
-              <span>用户领取规则</span>
-              <span class="label-tip">(用户间隔多久可以领取一次)</span>
-            </template>
-            <el-radio-group v-model="couponModel.claimRule" class="radio-group block">
-              <el-radio value="day"> 每日一领 </el-radio>
-              <el-radio value="week"> 每周一领 </el-radio>
-              <el-radio value="month"> 每月一领 </el-radio>
-            </el-radio-group>
-          </el-form-item> -->
-          <!-- 用户是否需要收藏店铺领取 -->
-          <el-form-item label="用户是否需要收藏店铺领取" prop="attentionCanReceived">
-            <el-radio-group v-model="couponModel.attentionCanReceived" class="radio-group">
-              <el-radio :value="1"> 是 </el-radio>
-              <el-radio :value="0"> 否 </el-radio>
-            </el-radio-group>
-          </el-form-item>
           <!-- 补充说明 -->
           <el-form-item label="补充说明" prop="supplementaryInstruction">
             <el-input
@@ -128,14 +113,15 @@
 <script setup lang="tsx" name="newCoupon">
 /**
  * 优惠券管理 - 新建/编辑页面
- * 参考:group_merchant IssueCoupons、设计图布局(左:类型/名称/折扣或面值/低消/时间/有效期,右:库存/领取规则/收藏/补充说明)
+ * 接口入参:longTermValid(1 长期有效 / 0 自定义天数)+ expirationDate(天);
+ * unlimitedQty(1 不限 / 0 自定义张数)+ singleQty(张)
  */
 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, addDiscountCoupon, editDiscountCoupon } from "@/api/modules/couponManagement";
-import { validatePositiveNumber, validatePositiveInteger, validateDateRange, validatePriceFormat } from "@/utils/eleValidate";
+import { validatePositiveNumber, validatePositiveInteger, validatePriceFormat } from "@/utils/eleValidate";
 import { localGet } from "@/utils";
 
 const router = useRouter();
@@ -182,48 +168,40 @@ const rules = reactive({
     }
   ],
   discountRate: [{ validator: validateDiscountRate, trigger: "blur" }],
-  beginGetDate: [
-    { required: true, message: "请选择开始领取时间" },
-    {
-      validator: validateDateRange(
-        () => couponModel.value.beginGetDate,
-        () => couponModel.value.endGetDate,
-        "开始领取时间不能早于当前时间",
-        "结束领取时间不能早于当前时间",
-        "开始领取时间必须早于结束领取时间",
-        true,
-        true
-      ),
-      trigger: "change"
-    }
-  ],
-  endGetDate: [
-    { required: true, message: "请选择结束领取时间" },
+  validityMode: [{ required: true, message: "请选择有效期类型" }],
+  validDaysAfterReceive: [
     {
-      validator: validateDateRange(
-        () => couponModel.value.beginGetDate,
-        () => couponModel.value.endGetDate,
-        "开始领取时间不能早于当前时间",
-        "结束领取时间不能早于当前时间",
-        "开始领取时间必须早于结束领取时间",
-        true,
-        true
-      ),
-      trigger: "change"
-    }
-  ],
-  specifiedDay: [
-    { required: true, message: "请输入有效期" },
-    {
-      validator: validatePositiveInteger("有效期必须为正整数", { required: false }),
-      trigger: "blur"
+      validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
+        if (couponModel.value.validityMode !== 1) {
+          callback();
+          return;
+        }
+        if (value === null || value === undefined || value === "") {
+          callback(new Error("请输入领取后有效期"));
+          return;
+        }
+        const next = validatePositiveInteger("领取后有效期须为正整数", { required: false });
+        next(_rule, value, callback);
+      },
+      trigger: ["blur", "change"]
     }
   ],
+  quantityMode: [{ required: true, message: "请选择数量类型" }],
   singleQty: [
-    { required: true, message: "请输入库存" },
     {
-      validator: validatePositiveInteger("库存必须为正整数", { required: false }),
-      trigger: "blur"
+      validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
+        if (couponModel.value.quantityMode !== 1) {
+          callback();
+          return;
+        }
+        if (value === null || value === undefined || value === "") {
+          callback(new Error("请输入数量"));
+          return;
+        }
+        const next = validatePositiveInteger("数量须为正整数", { required: false });
+        next(_rule, value, callback);
+      },
+      trigger: ["blur", "change"]
     }
   ],
   minimumSpendingAmount: [
@@ -256,52 +234,58 @@ const rules = reactive({
   ]
 });
 
-const claimRuleOptions = [
-  { label: "每日一领", value: "day" },
-  { label: "每周一领", value: "week" },
-  { label: "每月一领", value: "month" }
-];
-
-const yesNoOptions = [
-  { label: "是", value: 1 },
-  { label: "否", value: 0 }
-];
-
 // 1-满减券 2-折扣券(与 ticketManagement index couponTypeEnum 一致)
+// validityMode:0 长期有效 → longTermValid=1;1 自定义 → longTermValid=0 + expirationDate=天
+// quantityMode:0 不限 → unlimitedQty=1;1 自定义 → unlimitedQty=0 + singleQty=张数
 const couponModel = ref<any>({
   name: "",
   couponType: 2,
   nominalValue: "",
   discountRate: "",
-  beginGetDate: "",
-  endGetDate: "",
-  specifiedDay: "",
+  validityMode: 0,
+  validDaysAfterReceive: "",
+  quantityMode: 0,
   singleQty: "",
   claimRule: "day",
-  attentionCanReceived: 1,
   hasMinimumSpend: 0,
   minimumSpendingAmount: "",
   supplementaryInstruction: ""
 });
 
+/** 仅允许数字,限制长度(正整数输入) */
+const sanitizeDigits = (raw: string, maxLen: number) =>
+  String(raw ?? "")
+    .replace(/\D/g, "")
+    .slice(0, maxLen);
+
+const onValidDaysAfterReceiveInput = (val: string) => {
+  couponModel.value.validDaysAfterReceive = sanitizeDigits(val, 5);
+};
+
+const onSingleQtyInput = (val: string) => {
+  couponModel.value.singleQty = sanitizeDigits(val, 5);
+};
+
 const isInitializing = ref(true);
 
 watch(
-  () => couponModel.value.beginGetDate,
-  () => {
+  () => couponModel.value.validityMode,
+  newVal => {
     if (isInitializing.value) return;
-    if (couponModel.value.endGetDate) {
-      nextTick(() => ruleFormRef.value?.validateField("endGetDate"));
+    if (newVal === 0) {
+      couponModel.value.validDaysAfterReceive = "";
+      nextTick(() => ruleFormRef.value?.clearValidate("validDaysAfterReceive"));
     }
   }
 );
 
 watch(
-  () => couponModel.value.endGetDate,
-  () => {
+  () => couponModel.value.quantityMode,
+  newVal => {
     if (isInitializing.value) return;
-    if (couponModel.value.beginGetDate) {
-      nextTick(() => ruleFormRef.value?.validateField("beginGetDate"));
+    if (newVal === 0) {
+      couponModel.value.singleQty = "";
+      nextTick(() => ruleFormRef.value?.clearValidate("singleQty"));
     }
   }
 );
@@ -338,15 +322,42 @@ onMounted(async () => {
   if (type.value !== "add" && id.value) {
     const res: any = await getCouponDetail({ counponId: id.value });
     const data = res.data || {};
+    const lt = data.longTermValid;
+    const isLongByFlag = lt === 1 || lt === "1" || lt === true;
+    const isLongLegacy =
+      lt === undefined &&
+      (() => {
+        const sd = data.specifiedDay;
+        const sdNum = Number(sd);
+        return sd === null || sd === undefined || sd === "" || Number.isNaN(sdNum) || sdNum === 0;
+      })();
+    const isLongValidity = isLongByFlag || isLongLegacy;
+    const expDays = data.expirationDate ?? data.specifiedDay;
+
+    const uq = data.unlimitedQty;
+    const isUnlimitedByFlag = uq === 1 || uq === "1" || uq === true;
+    const isUnlimitedLegacy =
+      uq === undefined &&
+      (data.singleQty === null || data.singleQty === undefined || data.singleQty === "" || Number(data.singleQty) === 0);
+    const isUnlimitedQty = isUnlimitedByFlag || isUnlimitedLegacy;
+
     couponModel.value = {
       ...couponModel.value,
       ...data,
       couponType: data.couponType ?? 1,
       discountRate: data.discountRate != null ? (Number(data.discountRate) / 10).toString() : "",
-      hasMinimumSpend: Number(data.minimumSpendingAmount) > 0 ? 1 : 0
+      hasMinimumSpend: Number(data.minimumSpendingAmount) > 0 ? 1 : 0,
+      validityMode: isLongValidity ? 0 : 1,
+      validDaysAfterReceive: isLongValidity ? "" : String(expDays ?? ""),
+      quantityMode: isUnlimitedQty ? 0 : 1,
+      singleQty: isUnlimitedQty ? "" : String(data.singleQty ?? "")
     };
   } else {
     couponModel.value.couponType = couponModel.value.couponType ?? 2;
+    couponModel.value.validityMode = 0;
+    couponModel.value.quantityMode = 0;
+    couponModel.value.validDaysAfterReceive = "";
+    couponModel.value.singleQty = "";
   }
   await nextTick();
   ruleFormRef.value?.clearValidate();
@@ -359,17 +370,40 @@ const goBack = () => {
 
 const ruleFormRef = ref<FormInstance>();
 
-const handleSubmit = async (submitType?: string) => {
+const buildSubmitParams = (submitType?: string) => {
+  const m = couponModel.value;
+  const longTermValid = m.validityMode === 0 ? 1 : 0;
+  const expirationDate = m.validityMode === 0 ? 0 : Number(m.validDaysAfterReceive) || 0;
+  const unlimitedQty = m.quantityMode === 0 ? 1 : 0;
+  const singleQty = m.quantityMode === 0 ? 0 : Number(m.singleQty) || 0;
   const params: any = {
-    ...couponModel.value,
+    name: m.name,
+    couponType: m.couponType,
+    nominalValue: m.nominalValue,
+    discountRate: m.discountRate,
+    longTermValid,
+    expirationDate,
+    unlimitedQty,
+    singleQty,
+    claimRule: m.claimRule || "day",
+    attentionCanReceived: 0,
+    hasMinimumSpend: m.hasMinimumSpend,
+    minimumSpendingAmount: m.minimumSpendingAmount,
+    supplementaryInstruction: m.supplementaryInstruction,
+    beginGetDate: "",
+    endGetDate: "",
     storeId: localGet("createdId"),
     couponId: type.value === "edit" ? id.value : "",
     couponStatus: submitType ? 0 : 1,
     restrictedQuantity: 0,
-    expirationDate: 0,
     getStatus: 1,
     type: 1
   };
+  return params;
+};
+
+const handleSubmit = async (submitType?: string) => {
+  const params: any = buildSubmitParams(submitType);
   // 折扣券:discountRate 后端多为 1-100(如 85 表示 8.5 折),此处按 0.1-9.9 输入则乘 10
   if (params.couponType === 2 && params.discountRate !== "" && params.discountRate != null) {
     const rate = Number(params.discountRate);
@@ -422,24 +456,6 @@ const handleSubmit = async (submitType?: string) => {
     }
   });
 };
-
-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">