Ver código fonte

feat(dynamicManagement): 新增好友优惠券管理功能

- 创建好友优惠券模块API接口文件
- 添加好友优惠券列表、赠送、删除等相关接口方法
- 在菜单配置中增加好友优惠券子菜单项
- 开发好友优惠券页面组件,支持查看和赠送优惠券
- 实现好友优惠券表格展示及操作功能
- 支持切换“好友赠我”和“我赠好友”两个标签页
- 添加赠送优惠券对话框及表单校验逻辑
- 使用模拟数据进行开发,预留真实接口调用位置
- 样式优化,适配不同屏幕尺寸显示效果
congxuesong 2 semanas atrás
pai
commit
2bc9ab3e23

+ 30 - 0
src/api/modules/friendCoupon.ts

@@ -0,0 +1,30 @@
+import http from "@/api";
+
+/**
+ * @name 好友优惠券管理模块
+ */
+
+// 获取好友优惠券列表
+export const getFriendCouponList = (params: any) => {
+  return http.post(`/api/friendCoupon/list`, params);
+};
+
+// 赠送优惠券给好友
+export const sendCouponToFriend = (params: { friendId: string | number; couponId: string | number; quantity: number }) => {
+  return http.post(`/api/friendCoupon/send`, params);
+};
+
+// 删除好友优惠券记录
+export const deleteFriendCoupon = (params: { id: string | number }) => {
+  return http.post(`/api/friendCoupon/delete`, params);
+};
+
+// 获取好友列表
+export const getFriendList = () => {
+  return http.get(`/api/friendCoupon/friendList`);
+};
+
+// 获取可赠送的优惠券列表
+export const getAvailableCouponList = () => {
+  return http.get(`/api/friendCoupon/availableCoupons`);
+};

+ 29 - 13
src/assets/json/authMenuList.json

@@ -672,20 +672,36 @@
         "isAffix": false,
         "isKeepAlive": false
       },
-      "children": [{
-        "path": "/dynamicManagement/index",
-        "name": "dynamicManagement",
-        "component": "/dynamicManagement/index",
-        "meta": {
-          "icon": "Setting",
-          "title": "动态广场",
-          "isLink": "",
-          "isHide": false,
-          "isFull": false,
-          "isAffix": false,
-          "isKeepAlive": false
+      "children": [
+        {
+          "path": "/dynamicManagement/index",
+          "name": "dynamicManagementIndex",
+          "component": "/dynamicManagement/index",
+          "meta": {
+            "icon": "Setting",
+            "title": "动态广场",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/dynamicManagement/friendCoupon",
+          "name": "friendCoupon",
+          "component": "/dynamicManagement/friendCoupon",
+          "meta": {
+            "icon": "Tickets",
+            "title": "好友优惠券",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
         }
-      }]
+      ]
     }
   ],
   "msg": "成功"

+ 382 - 0
src/views/dynamicManagement/friendCoupon.vue

@@ -0,0 +1,382 @@
+<template>
+  <div class="table-box button-table friend-coupon-container">
+    <ProTable
+      ref="proTable"
+      :columns="columns"
+      :request-api="getTableList"
+      :init-param="initParam"
+      :data-callback="dataCallback"
+      :key="activeName"
+    >
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <div class="table-header-content">
+          <div class="header-button">
+            <el-button type="primary" @click="openGiftDialog"> 赠送好友优惠券 </el-button>
+          </div>
+          <el-tabs v-model="activeName" class="header-tabs" @tab-click="handleTabClick">
+            <el-tab-pane label="好友赠我" name="friendMessage" />
+            <el-tab-pane label="我赠好友" name="myGift" />
+          </el-tabs>
+        </div>
+      </template>
+
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button link type="primary" @click="viewDetail(scope.row)"> 查看详情 </el-button>
+        <el-button v-if="activeName === 'myGift'" link type="primary" @click="deleteRow(scope.row)"> 删除 </el-button>
+      </template>
+    </ProTable>
+
+    <!-- 赠送好友优惠券对话框 -->
+    <el-dialog v-model="giftDialogVisible" title="赠送好友优惠券" width="600px" @close="closeGiftDialog">
+      <el-form ref="giftFormRef" :model="giftFormData" :rules="giftRules" label-width="120px">
+        <el-form-item label="选择好友" prop="friendId">
+          <el-select v-model="giftFormData.friendId" placeholder="请选择好友" style="width: 100%">
+            <el-option v-for="friend in friendList" :key="friend.id" :label="friend.name" :value="friend.id" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="选择优惠券" prop="couponId">
+          <el-select v-model="giftFormData.couponId" placeholder="请选择优惠券" style="width: 100%" @change="handleCouponChange">
+            <el-option v-for="coupon in couponList" :key="coupon.id" :label="coupon.name" :value="coupon.id" />
+          </el-select>
+          <div v-if="giftFormData.couponId" class="coupon-info">
+            <span>请输入赠送数量</span>
+          </div>
+        </el-form-item>
+
+        <el-form-item v-if="giftFormData.couponId" label="赠送数量" prop="quantity">
+          <el-input-number v-model="giftFormData.quantity" :min="1" :max="100" placeholder="请输入数量" style="width: 100%" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeGiftDialog"> 取消 </el-button>
+          <el-button type="primary" @click="handleGiftSubmit"> 确定 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="friendCoupon">
+import { computed, onMounted, reactive, ref } from "vue";
+import { useRouter } from "vue-router";
+import type { FormInstance, FormRules } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
+import ProTable from "@/components/ProTable/index.vue";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { localGet } from "@/utils";
+// import { getFriendCouponList, sendCouponToFriend, deleteFriendCoupon } from "@/api/modules/friendCoupon";
+
+const router = useRouter();
+
+// 当前激活的标签页
+const activeName = ref("friendMessage");
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>();
+
+// 赠送对话框
+const giftDialogVisible = ref(false);
+const giftFormRef = ref<FormInstance>();
+
+// 好友列表(模拟数据)
+const friendList = ref([
+  { id: 1, name: "张三" },
+  { id: 2, name: "李四" },
+  { id: 3, name: "王五" }
+]);
+
+// 优惠券列表(模拟数据)
+const couponList = ref([
+  { id: 1, name: "满100减20代金券", stock: 50 },
+  { id: 2, name: "8折优惠券", stock: 30 },
+  { id: 3, name: "满200减50代金券", stock: 20 }
+]);
+
+// 赠送表单数据
+const giftFormData = reactive({
+  friendId: "",
+  couponId: "",
+  quantity: 1
+});
+
+// 表单验证规则
+const giftRules = reactive<FormRules>({
+  friendId: [{ required: true, message: "请选择好友", trigger: "change" }],
+  couponId: [{ required: true, message: "请选择优惠券", trigger: "change" }],
+  quantity: [{ required: true, message: "请输入赠送数量", trigger: "blur" }]
+});
+
+// 好友留言表格列配置
+const friendMessageColumns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "storeName",
+    label: "店铺名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "couponAmount",
+    label: "优惠券额",
+    render: (scope: any) => {
+      return `¥${scope.row.couponAmount || 0}`;
+    }
+  },
+  {
+    prop: "validUntil",
+    label: "有效期至",
+    render: (scope: any) => {
+      return scope.row.validUntil?.replace(/-/g, "/") || "--";
+    }
+  },
+  {
+    prop: "senderName",
+    label: "赠送人"
+  },
+  {
+    prop: "receiveTime",
+    label: "接收时间",
+    render: (scope: any) => {
+      return scope.row.receiveTime?.replace(/-/g, "/") || "--";
+    }
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 200 }
+]);
+
+// 我感好友表格列配置
+const myGiftColumns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "storeName",
+    label: "店铺名称",
+    search: {
+      el: "input"
+    }
+  },
+  {
+    prop: "couponAmount",
+    label: "优惠券额",
+    render: (scope: any) => {
+      return `¥${scope.row.couponAmount || 0}`;
+    }
+  },
+  {
+    prop: "validUntil",
+    label: "有效期至",
+    render: (scope: any) => {
+      return scope.row.validUntil?.replace(/-/g, "/") || "--";
+    }
+  },
+  {
+    prop: "receiverName",
+    label: "接收人"
+  },
+  {
+    prop: "status",
+    label: "状态",
+    render: (scope: any) => {
+      const statusMap: Record<number, string> = {
+        0: "未使用",
+        1: "已使用",
+        2: "已过期"
+      };
+      return statusMap[scope.row.status] || "--";
+    }
+  },
+  {
+    prop: "sendTime",
+    label: "赠送时间",
+    render: (scope: any) => {
+      return scope.row.sendTime?.replace(/-/g, "/") || "--";
+    }
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 200 }
+]);
+
+// 根据当前选中的tab动态返回列配置
+const columns = computed(() => {
+  return activeName.value === "friendMessage" ? friendMessageColumns : myGiftColumns;
+});
+
+// 初始化请求参数
+const initParam = reactive({
+  storeId: localGet("createdId") || "",
+  type: activeName.value
+});
+
+// Tab切换处理
+const handleTabClick = () => {
+  initParam.type = activeName.value;
+  proTable.value?.getTableList();
+};
+
+// dataCallback 是对于返回的表格数据做处理
+const dataCallback = (data: any) => {
+  return {
+    list: data?.records || [],
+    total: data?.total || 0
+  };
+};
+
+// 获取表格列表
+const getTableList = (params: any) => {
+  const newParams = {
+    ...params,
+    type: activeName.value
+  };
+
+  // TODO: 集成真实接口时,取消下面的注释
+  // return getFriendCouponList(newParams);
+
+  // 临时方案:返回模拟数据
+  return new Promise(resolve => {
+    setTimeout(() => {
+      const mockData = activeName.value === "friendMessage" ? generateMockFriendMessages() : generateMockMyGifts();
+      resolve({
+        code: 200,
+        data: {
+          records: mockData,
+          total: mockData.length
+        }
+      });
+    }, 500);
+  });
+};
+
+// 生成模拟的好友留言数据
+const generateMockFriendMessages = () => {
+  return Array.from({ length: 2 }, (_, i) => ({
+    id: i + 1,
+    storeName: i === 0 ? "甜客优越" : "洗浴优越铺",
+    couponAmount: 200,
+    validUntil: "2025-05-01",
+    senderName: "张三",
+    receiveTime: "2025-03-01"
+  }));
+};
+
+// 生成模拟的我感好友数据
+const generateMockMyGifts = () => {
+  return Array.from({ length: 3 }, (_, i) => ({
+    id: i + 1,
+    storeName: `店铺${i + 1}`,
+    couponAmount: 200,
+    validUntil: "2025-05-01",
+    receiverName: `好友${i + 1}`,
+    status: i % 3,
+    sendTime: "2025-03-01"
+  }));
+};
+
+// 打开赠送对话框
+const openGiftDialog = () => {
+  giftDialogVisible.value = true;
+};
+
+// 关闭赠送对话框
+const closeGiftDialog = () => {
+  giftDialogVisible.value = false;
+  giftFormRef.value?.resetFields();
+  Object.assign(giftFormData, {
+    friendId: "",
+    couponId: "",
+    quantity: 1
+  });
+};
+
+// 优惠券改变时
+const handleCouponChange = (val: string) => {
+  giftFormData.quantity = 1;
+};
+
+// 提交赠送
+const handleGiftSubmit = async () => {
+  if (!giftFormRef.value) return;
+
+  await giftFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        // TODO: 集成真实接口时,取消下面的注释
+        // await sendCouponToFriend({
+        //   friendId: giftFormData.friendId,
+        //   couponId: giftFormData.couponId,
+        //   quantity: giftFormData.quantity
+        // });
+
+        ElMessage.success("赠送成功");
+        closeGiftDialog();
+        proTable.value?.getTableList();
+      } catch (error) {
+        ElMessage.error("赠送失败");
+      }
+    }
+  });
+};
+
+// 查看详情
+const viewDetail = (row: any) => {
+  ElMessage.info(`查看详情: ${row.storeName}`);
+  // TODO: 跳转到详情页或显示详情对话框
+};
+
+// 删除行数据
+const deleteRow = (row: any) => {
+  ElMessageBox.confirm("确定要删除这条赠送记录吗?", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(async () => {
+      try {
+        // TODO: 集成真实接口时,取消下面的注释
+        // await deleteFriendCoupon({ id: row.id });
+
+        ElMessage.success("删除成功");
+        proTable.value?.getTableList();
+      } catch (error) {
+        ElMessage.error("删除失败");
+      }
+    })
+    .catch(() => {
+      // 用户取消删除
+    });
+};
+
+// 页面加载时触发查询
+onMounted(() => {
+  proTable.value?.getTableList();
+});
+</script>
+
+<style lang="scss" scoped>
+.friend-coupon-container {
+  .table-header-content {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    .header-tabs {
+      :deep(.el-tabs__nav-wrap::after) {
+        height: 0;
+      }
+    }
+    .header-button {
+      display: flex;
+      justify-content: flex-start;
+    }
+  }
+  .coupon-info {
+    margin-top: 8px;
+    font-size: 12px;
+    color: #909399;
+  }
+  .dialog-footer {
+    display: flex;
+    gap: 10px;
+    justify-content: flex-end;
+  }
+}
+</style>