Răsfoiți Sursa

优惠券调试

skyline 1 lună în urmă
părinte
comite
a34383956c

+ 1 - 1
haha-admin-web/src/api/coupon.ts

@@ -45,8 +45,8 @@ export const deleteCoupon = (id: number) => {
 
 export const distributeCoupon = (data: {
   templateId: number;
+  targetType: number; // 1-全部用户,3-指定用户
   userIds?: number[];
-  distributeType?: string;
 }) => {
   return http.request<Result>("post", "/marketing/coupon/distribute", { data });
 };

+ 28 - 0
haha-admin-web/src/api/user-coupon.ts

@@ -0,0 +1,28 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getUserCouponList = (params: {
+  page?: number;
+  pageSize?: number;
+  userId?: number;
+  couponCode?: string;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("get", "/marketing/coupon/user/list", { params });
+};

+ 9 - 0
haha-admin-web/src/router/modules/marketing.ts

@@ -37,6 +37,15 @@ export default {
         title: "优惠券管理"
       }
     },
+    {
+      path: "/marketing/user-coupon",
+      name: "UserCouponList",
+      component: () => import("@/views/marketing/user-coupon/index.vue"),
+      meta: {
+        icon: "ri:ticket-line",
+        title: "用户优惠券"
+      }
+    },
     {
       path: "/marketing/timed-discount",
       name: "TimedDiscount",

+ 102 - 84
haha-admin-web/src/views/marketing/coupon/utils/hook.tsx

@@ -30,12 +30,12 @@ import {
   ElRadioButton,
   ElAlert
 } from "element-plus";
-import { 
-  initPagination, 
-  handlePageSizeChange, 
-  handleCurrentPageChange, 
+import {
+  initPagination,
+  handlePageSizeChange,
+  handleCurrentPageChange,
   resetPagination,
-  updatePaginationData 
+  updatePaginationData
 } from "@/utils/paginationHelper";
 
 interface SearchFormProps {
@@ -91,11 +91,11 @@ export function useCoupon() {
     {
       label: "优惠券ID",
       prop: "id",
-      width: 90
+      width: 200
     },
     {
       label: "优惠券名称",
-      prop: "name",
+      prop: "couponName",
       minWidth: 150
     },
     {
@@ -116,7 +116,7 @@ export function useCoupon() {
     {
       label: "发放类型",
       prop: "receiveType",
-      minWidth: 110,
+      minWidth: 80,
       cellRenderer: ({ row }) => {
         if (row.receiveType === 'Collect') {
           return <ElTag type="success">主动领取</ElTag>;
@@ -126,19 +126,19 @@ export function useCoupon() {
     },
     {
       label: "优惠值",
-      prop: "value",
-      minWidth: 100,
+      prop: "discountValue",
+      minWidth: 80,
       cellRenderer: ({ row }) => {
-        if (row.type === 2) {
-          return `${row.value}折`;
+        if (row.couponType === 2) {
+          return `${row.discountValue}折`;
         }
-        return `¥${row.value}`;
+        return `¥${row.discountValue}`;
       }
     },
     {
       label: "发放总量",
       prop: "totalCount",
-      minWidth: 100
+      minWidth: 90
     },
     {
       label: "已领取",
@@ -183,12 +183,12 @@ export function useCoupon() {
         page: pagination.currentPage,
         pageSize: pagination.pageSize
       };
-      
+
       if (form.name) searchParams.name = form.name;
       if (form.type !== undefined) searchParams.type = form.type;
       if (form.status !== undefined) searchParams.status = form.status;
       if (form.receiveType) searchParams.receiveType = form.receiveType;
-      
+
       const { data } = await getCouponList(searchParams);
       if (data) {
         dataList.value = data.list || [];
@@ -225,7 +225,8 @@ export function useCoupon() {
   function handleDistribute(row) {
     const distributeForm = reactive({
       targetType: "all", // all-全部用户, specific-指定用户
-      userIds: []
+      userIds: [],
+      distributeCount: 1 // 发放数量
     });
 
     // 用户列表状态
@@ -282,9 +283,9 @@ export function useCoupon() {
             <ElInput value={`${row.receivedCount || 0} / ${row.totalCount || 0}`} disabled />
           </ElFormItem>
           <ElFormItem label="目标用户" required>
-            <ElSelect 
-              v-model={distributeForm.targetType} 
-              placeholder="请选择目标用户" 
+            <ElSelect
+              v-model={distributeForm.targetType}
+              placeholder="请选择目标用户"
               class="w-full"
               onChange={() => {
                 // 切换时清空用户选择
@@ -297,11 +298,11 @@ export function useCoupon() {
               <ElOption label="指定用户" value="specific" />
             </ElSelect>
           </ElFormItem>
-          
+
           {distributeForm.targetType === 'specific' && (
             <ElFormItem label="选择用户" required>
-              <ElSelect 
-                v-model={distributeForm.userIds} 
+              <ElSelect
+                v-model={distributeForm.userIds}
                 class="w-full"
                 multiple
                 filterable
@@ -313,10 +314,10 @@ export function useCoupon() {
                 placeholder="请输入用户名或手机号搜索"
               >
                 {userList.value.map((user: any) => (
-                  <ElOption 
-                    key={user.id} 
-                    label={`${user.nickname || user.phone || '未知用户'} (${user.phone || '无手机号'})`} 
-                    value={user.id} 
+                  <ElOption
+                    key={user.id}
+                    label={`${user.nickname || user.phone || '未知用户'} (${user.phone || '无手机号'})`}
+                    value={user.id}
                   />
                 ))}
               </ElSelect>
@@ -325,38 +326,55 @@ export function useCoupon() {
               </div>
             </ElFormItem>
           )}
-          
+
           <ElFormItem label="发放数量" required>
-            <ElInputNumber 
-              placeholder="请输入发放数量" 
-              class="w-full" 
-              min={1} 
-              max={row.totalCount - (row.receivedCount || 0)} 
+            <ElInputNumber
+              v-model={distributeForm.distributeCount}
+              placeholder="发放数量"
+              class="w-full"
+              min={1}
+              max={row.totalCount - (row.receivedCount || 0)}
             />
           </ElFormItem>
-          
+
           <ElAlert
             type="info"
             closable={false}
             showIcon={true}
             style={{marginTop: '8px'}}
           >
-            {distributeForm.targetType === 'all' 
-              ? '将向平台所有用户发放该优惠券' 
+            {distributeForm.targetType === 'all'
+              ? '将向平台所有用户发放该优惠券'
               : `将向已选择的 ${distributeForm.userIds.length} 个用户发放该优惠券`}
           </ElAlert>
         </ElForm>
       ),
-      beforeSure: done => {
+      beforeSure: async done => {
         // 验证
         if (distributeForm.targetType === 'specific' && distributeForm.userIds.length === 0) {
           message("请选择至少一个用户", { type: "warning" });
           return;
         }
-        
-        message("发放成功", { type: "success" });
-        done();
-        onSearch();
+
+        try {
+          // 调用后端发放接口
+          const { code, message: errorMsg } = await distributeCoupon({
+            templateId: row.id,  // 现在后端返回的是字符串,无需转换
+            targetType: distributeForm.targetType === 'all' ? 1 : 3, // 1-全部用户,3-指定用户
+            userIds: distributeForm.targetType === 'specific' ? distributeForm.userIds : undefined
+          });
+
+          if (code === 200) {
+            message("发放成功", { type: "success" });
+            done();
+            onSearch();
+          } else {
+            message(errorMsg || "发放失败", { type: "error" });
+          }
+        } catch (error) {
+          const errorMsg = error?.response?.data?.message || "发放失败,请重试";
+          message(errorMsg, { type: "error" });
+        }
       }
     });
   }
@@ -385,11 +403,11 @@ export function useCoupon() {
       padding: 0;
       font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
     }
-    
+
     .form-block {
       margin-bottom: 20px;
     }
-    
+
     .block-title {
       font-size: 14px;
       font-weight: 600;
@@ -398,26 +416,26 @@ export function useCoupon() {
       padding-bottom: 8px;
       border-bottom: 1px solid #e5e7eb;
     }
-    
+
     .form-grid-2 {
       display: grid;
       grid-template-columns: repeat(2, 1fr);
       gap: 20px 24px;
     }
-    
+
     .form-grid-3 {
       display: grid;
       grid-template-columns: repeat(3, 1fr);
       gap: 20px 24px;
     }
-    
+
     .type-selector {
       display: grid;
       grid-template-columns: repeat(4, 1fr);
       gap: 12px;
       margin-bottom: 18px;
     }
-    
+
     .type-btn {
       padding: 12px 16px;
       border: 1px solid #e5e7eb;
@@ -427,87 +445,87 @@ export function useCoupon() {
       transition: all 0.15s ease;
       text-align: center;
     }
-    
+
     .type-btn:hover {
       border-color: #3b82f6;
       background: #f8fafc;
     }
-    
+
     .type-btn.active {
       border-color: #3b82f6;
       background: #eff6ff;
     }
-    
+
     .type-btn .name {
       font-size: 14px;
       font-weight: 600;
       color: #374151;
       margin-bottom: 4px;
     }
-    
+
     .type-btn .hint {
       font-size: 12px;
       color: #9ca3af;
     }
-    
+
     .type-btn.active .name {
       color: #3b82f6;
     }
-    
+
     .scope-grid {
       display: grid;
       grid-template-columns: repeat(3, 1fr);
       gap: 16px;
     }
-    
+
     .scope-card {
       background: #f9fafb;
       border: 1px solid #e5e7eb;
       border-radius: 8px;
       padding: 14px 16px;
     }
-    
+
     .scope-card .card-header {
       display: flex;
       align-items: center;
       justify-content: space-between;
       margin-bottom: 12px;
     }
-    
+
     .scope-card .card-label {
       font-size: 13px;
       font-weight: 500;
       color: #374151;
     }
-    
+
     .scope-card .card-body {
       min-height: 32px;
     }
-    
+
     .coupon-form .el-form-item {
       margin-bottom: 0;
       align-items: center;
     }
-    
+
     .coupon-form .el-form-item__label {
       font-size: 13px;
       color: #606266;
       padding-right: 8px;
     }
-    
+
     .coupon-form .el-input__wrapper,
     .coupon-form .el-textarea__inner {
       font-size: 13px;
     }
-    
+
     .coupon-form .form-label-90 .el-form-item__label {
       width: 90px !important;
     }
-    
+
     .coupon-form .form-label-100 .el-form-item__label {
       width: 100px !important;
     }
-    
+
     .preview-box {
       display: flex;
       align-items: center;
@@ -520,11 +538,11 @@ export function useCoupon() {
       font-size: 14px;
       color: #374151;
     }
-    
+
     .preview-box .value {
       font-weight: 600;
     }
-    
+
     @media (max-width: 1200px) {
       .form-grid-3,
       .scope-grid {
@@ -534,7 +552,7 @@ export function useCoupon() {
         grid-template-columns: repeat(2, 1fr);
       }
     }
-    
+
     @media (max-width: 768px) {
       .form-grid-2,
       .form-grid-3,
@@ -551,7 +569,7 @@ export function useCoupon() {
     return (
       <div class="coupon-form">
         <style>{formStyles}</style>
-        
+
         <div class="form-block">
           <div class="block-title">基本信息</div>
           <ElForm label-width="90px" size="default">
@@ -580,8 +598,8 @@ export function useCoupon() {
               showIcon={true}
               style={{marginTop: '12px'}}
             >
-              {formData.receiveType === 'Collect' 
-                ? '主动领取:将显示在用户端小程序领券中心,用户可自行领取' 
+              {formData.receiveType === 'Collect'
+                ? '主动领取:将显示在用户端小程序领券中心,用户可自行领取'
                 : '系统发放:创建后可在列表中点击发放,选择指定用户或全部用户'}
             </ElAlert>
           </ElForm>
@@ -591,7 +609,7 @@ export function useCoupon() {
           <div class="block-title">优惠券类型</div>
           <div class="type-selector">
             {[1, 2, 3, 4].map(type => (
-              <div 
+              <div
                 class={`type-btn ${formData.type === type ? 'active' : ''}`}
                 onClick={() => { formData.type = type; }}
               >
@@ -605,26 +623,26 @@ export function useCoupon() {
               </div>
             ))}
           </div>
-          
+
           {formData.type && (
             <ElForm label-width="90px" size="default">
               <div class="form-grid-3">
                 <ElFormItem label="优惠值" required>
-                  <ElInputNumber 
-                    v-model={formData.value} 
-                    min={0} 
-                    precision={2} 
-                    class="w-full!" 
-                    placeholder={formData.type === 2 ? "折扣值如8表示8折" : "优惠金额"} 
-                    prefix={formData.type !== 2 ? "¥" : ""} 
+                  <ElInputNumber
+                    v-model={formData.value}
+                    min={0}
+                    precision={2}
+                    class="w-full!"
+                    placeholder={formData.type === 2 ? "折扣值如8表示8折" : "优惠金额"}
+                    prefix={formData.type !== 2 ? "¥" : ""}
                   />
                 </ElFormItem>
                 <ElFormItem label="发放总量" required>
-                  <ElInputNumber 
-                    v-model={formData.totalCount} 
-                    min={1} 
-                    class="w-full!" 
-                    placeholder="发放总数量" 
+                  <ElInputNumber
+                    v-model={formData.totalCount}
+                    min={1}
+                    class="w-full!"
+                    placeholder="发放总数量"
                   />
                 </ElFormItem>
                 <ElFormItem label="有效期" required>
@@ -761,7 +779,7 @@ export function useCoupon() {
             couponDesc: formData.description,
             receiveLimit: 1
           };
-          
+
           const { code, message: errorMsg } = await createCoupon(submitData);
           if (code === 200) {
             message("新增成功", { type: "success" });

+ 117 - 0
haha-admin-web/src/views/marketing/user-coupon/index.vue

@@ -0,0 +1,117 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import { useUserCoupon } from "./utils/hook";
+import { PureTableBar } from "@/components/RePureTableBar";
+import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+
+import Refresh from "~icons/ep/refresh";
+
+defineOptions({
+  name: "UserCouponList"
+});
+
+const formRef = ref();
+
+const {
+  form,
+  loading,
+  columns,
+  dataList,
+  pagination,
+  onSearch,
+  resetForm,
+  handleSizeChange,
+  handleCurrentChange
+} = useUserCoupon();
+</script>
+
+<template>
+  <div class="main">
+    <el-form
+      ref="formRef"
+      :inline="true"
+      :model="form"
+      class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
+    >
+      <el-form-item label="用户ID:" prop="userId">
+        <el-input
+          v-model="form.userId"
+          placeholder="请输入用户ID"
+          clearable
+          class="w-[180px]!"
+        />
+      </el-form-item>
+      <el-form-item label="优惠券码:" prop="couponCode">
+        <el-input
+          v-model="form.couponCode"
+          placeholder="请输入优惠券码"
+          clearable
+          class="w-[180px]!"
+        />
+      </el-form-item>
+      <el-form-item label="状态:" prop="status">
+        <el-select
+          v-model="form.status"
+          placeholder="请选择"
+          clearable
+          class="w-[180px]!"
+        >
+          <el-option label="未使用" :value="0" />
+          <el-option label="已使用" :value="1" />
+          <el-option label="已过期" :value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button
+          type="primary"
+          :icon="useRenderIcon('ri/search-line')"
+          :loading="loading"
+          @click="onSearch"
+        >
+          搜索
+        </el-button>
+        <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
+          重置
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <PureTableBar title="用户优惠券列表" :columns="columns">
+      <template #buttons>
+        <el-button
+          type="primary"
+          :icon="useRenderIcon(Refresh)"
+          @click="onSearch"
+        >
+          刷新
+        </el-button>
+      </template>
+      <template v-slot="{ size, dynamicColumns }">
+        <pure-table
+          ref="tableRef"
+          align-whole="center"
+          showOverflowTooltip
+          table-layout="auto"
+          :loading="loading"
+          :size="size"
+          adaptive
+          :data="dataList"
+          :columns="dynamicColumns"
+          :pagination="pagination"
+          :page-size="pagination.pageSize"
+          :currentPage="pagination.currentPage"
+          @page-size-change="handleSizeChange"
+          @page-current-change="handleCurrentChange"
+        />
+      </template>
+    </PureTableBar>
+  </div>
+</template>
+
+<style scoped>
+.search-form {
+  background: #fff;
+  border-radius: 4px;
+  margin-bottom: 16px;
+}
+</style>

+ 162 - 0
haha-admin-web/src/views/marketing/user-coupon/utils/hook.tsx

@@ -0,0 +1,162 @@
+import { ref, reactive, onMounted } from "vue";
+import { getUserCouponList } from "@/api/user-coupon";
+import dayjs from "dayjs";
+import { ElTag } from "element-plus";
+
+interface SearchFormProps {
+  userId: string;
+  couponCode: string;
+  status: number | undefined;
+}
+
+interface PaginationProps {
+  total: number;
+  pageSize: number;
+  currentPage: number;
+  layout: string;
+}
+
+export function useUserCoupon() {
+  const form = reactive<SearchFormProps>({
+    userId: "",
+    couponCode: "",
+    status: undefined
+  });
+
+  const loading = ref(false);
+  const dataList = ref([]);
+  const pagination = reactive<PaginationProps>({
+    total: 0,
+    pageSize: 10,
+    currentPage: 1,
+    layout: "total, sizes, prev, pager, next, jumper"
+  });
+
+  const statusMap: Record<number, { text: string; type: string }> = {
+    0: { text: "未使用", type: "success" },
+    1: { text: "已使用", type: "info" },
+    2: { text: "已过期", type: "danger" }
+  };
+
+  const typeMap: Record<number, { text: string; type: string }> = {
+    1: { text: "满减券", type: "primary" },
+    2: { text: "折扣券", type: "success" },
+    3: { text: "立减券", type: "warning" },
+    4: { text: "商品券", type: "info" }
+  };
+
+  const columns = ref([
+    {
+      label: "优惠券码",
+      prop: "couponCode",
+      minWidth: 180
+    },
+    {
+      label: "用户ID",
+      prop: "userId",
+      minWidth: 120
+    },
+    {
+      label: "优惠券名称",
+      prop: "couponName",
+      minWidth: 150
+    },
+    {
+      label: "类型",
+      prop: "couponType",
+      minWidth: 100,
+      cellRenderer: ({ row }) => {
+        const item = typeMap[row.couponType] || { text: "未知", type: "info" };
+        return <ElTag type={item.type}>{item.text}</ElTag>;
+      }
+    },
+    {
+      label: "状态",
+      prop: "status",
+      minWidth: 100,
+      cellRenderer: ({ row }) => {
+        const item = statusMap[row.status] || { text: "未知", type: "info" };
+        return <ElTag type={item.type}>{item.text}</ElTag>;
+      }
+    },
+    {
+      label: "领取时间",
+      prop: "receiveTime",
+      minWidth: 160,
+      formatter: ({ receiveTime }) => receiveTime ? dayjs(receiveTime).format("YYYY-MM-DD HH:mm:ss") : "-"
+    },
+    {
+      label: "有效期",
+      minWidth: 200,
+      cellRenderer: ({ row }) => {
+        const start = row.validStartTime ? dayjs(row.validStartTime).format("YYYY-MM-DD") : "";
+        const end = row.validEndTime ? dayjs(row.validEndTime).format("YYYY-MM-DD") : "";
+        return `${start} ~ ${end}`;
+      }
+    },
+    {
+      label: "使用时间",
+      prop: "useTime",
+      minWidth: 160,
+      formatter: ({ useTime }) => useTime ? dayjs(useTime).format("YYYY-MM-DD HH:mm:ss") : "-"
+    }
+  ]);
+
+  async function onSearch() {
+    loading.value = true;
+    try {
+      const params: any = {
+        page: pagination.currentPage,
+        pageSize: pagination.pageSize
+      };
+
+      if (form.userId) params.userId = Number(form.userId);
+      if (form.couponCode) params.couponCode = form.couponCode;
+      if (form.status !== undefined) params.status = form.status;
+
+      const { data } = await getUserCouponList(params);
+      if (data) {
+        dataList.value = data.list || [];
+        pagination.total = data.total || 0;
+      }
+    } catch (error) {
+      console.error("查询用户优惠券失败:", error);
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  function resetForm(formRef: any) {
+    if (!formRef) return;
+    formRef.resetFields();
+    pagination.currentPage = 1;
+    onSearch();
+  }
+
+  function handleSizeChange(val: number) {
+    pagination.pageSize = val;
+    pagination.currentPage = 1;
+    onSearch();
+  }
+
+  function handleCurrentChange(val: number) {
+    pagination.currentPage = val;
+    onSearch();
+  }
+
+  onMounted(() => {
+    onSearch();
+  });
+
+  return {
+    form,
+    loading,
+    columns,
+    dataList,
+    pagination,
+    onSearch,
+    resetForm,
+    handleSizeChange,
+    handleCurrentChange
+  };
+}

+ 16 - 0
haha-admin/src/main/java/com/haha/admin/controller/CouponController.java

@@ -7,6 +7,7 @@ import com.haha.common.enums.OperationType;
 import com.haha.common.vo.PageResult;
 import com.haha.common.vo.Result;
 import com.haha.entity.CouponTemplate;
+import com.haha.entity.UserCoupon;
 import com.haha.entity.dto.CouponCreateDTO;
 import com.haha.entity.dto.CouponQueryDTO;
 import com.haha.entity.dto.CouponDistributeDTO;
@@ -83,6 +84,9 @@ public class CouponController {
     @Log(module = "营销管理", operation = OperationType.INSERT, summary = "发放优惠券")
     @PostMapping("/distribute")
     public Result<String> distribute(@RequestBody CouponDistributeDTO dto) {
+        log.info("发放优惠券请求: templateId={}, targetType={}, userIds={}", 
+                dto.getTemplateId(), dto.getTargetType(), dto.getUserIds());
+        
         Long operatorId = StpUtil.getLoginIdAsLong();
         String operatorName = (String) StpUtil.getSession().get("name");
 
@@ -102,4 +106,16 @@ public class CouponController {
         List<CouponTemplate> templates = templateService.getAvailableTemplates();
         return Result.success("查询成功", templates);
     }
+
+    @RequirePermission("marketing:coupon:read")
+    @GetMapping("/user/list")
+    public Result<PageResult<UserCoupon>> getUserCouponList(
+            @RequestParam(required = false) Long userId,
+            @RequestParam(required = false) String couponCode,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        IPage<UserCoupon> pageResult = userCouponService.getUserCouponList(userId, couponCode, status, page, pageSize);
+        return Result.success("查询成功", PageResult.of(pageResult));
+    }
 }

+ 3 - 0
haha-entity/src/main/java/com/haha/entity/CouponTemplate.java

@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -17,6 +19,7 @@ public class CouponTemplate implements Serializable {
     private static final long serialVersionUID = 1L;
 
     @TableId(type = IdType.ASSIGN_ID)
+    @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
 
     private String couponName;

+ 5 - 0
haha-entity/src/main/java/com/haha/entity/UserCoupon.java

@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -17,12 +19,15 @@ public class UserCoupon implements Serializable {
     private static final long serialVersionUID = 1L;
 
     @TableId(type = IdType.ASSIGN_ID)
+    @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
 
     private String couponCode;
 
+    @JsonSerialize(using = ToStringSerializer.class)
     private Long templateId;
 
+    @JsonSerialize(using = ToStringSerializer.class)
     private Long userId;
 
     private Long orderId;

+ 2 - 0
haha-service/src/main/java/com/haha/service/UserCouponService.java

@@ -28,4 +28,6 @@ public interface UserCouponService extends IService<UserCoupon> {
     int countAvailableCoupons(Long userId);
 
     Map<String, Object> getCouponStatistics(Long templateId);
+
+    IPage<UserCoupon> getUserCouponList(Long userId, String couponCode, Integer status, int page, int pageSize);
 }

+ 39 - 0
haha-service/src/main/java/com/haha/service/impl/UserCouponServiceImpl.java

@@ -147,8 +147,16 @@ public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCou
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void distributeCoupon(CouponDistributeDTO dto, Long operatorId, String operatorName) {
+        log.info("[优惠券发放] 开始发放 - templateId={}, targetType={}, userIds={}", 
+                dto.getTemplateId(), dto.getTargetType(), dto.getUserIds());
+        
         CouponTemplate template = templateMapper.selectById(dto.getTemplateId());
+        log.info("[优惠券发放] 查询模板结果 - templateId={}, template={}", 
+                dto.getTemplateId(), template != null ? "存在" : "不存在");
+        
         if (template == null || template.getDeleted() == 1) {
+            log.error("[优惠券发放] 模板不存在或已删除 - templateId={}, deleted={}", 
+                    dto.getTemplateId(), template != null ? template.getDeleted() : "N/A");
             throw new BusinessException(404, "优惠券模板不存在");
         }
 
@@ -296,4 +304,35 @@ public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCou
         userCoupon.setStatusLabel(label.getLabel());
         userCoupon.setStatusColor(label.getColor());
     }
+
+    @Override
+    public IPage<UserCoupon> getUserCouponList(Long userId, String couponCode, Integer status, int page, int pageSize) {
+        log.debug("[优惠券服务] 查询用户优惠券列表 - userId: {}, couponCode: {}, status: {}, page: {}, pageSize: {}",
+                userId, couponCode, status, page, pageSize);
+
+        Page<UserCoupon> pageParam = new Page<>(page, pageSize);
+
+        LambdaQueryWrapper<UserCoupon> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(userId != null, UserCoupon::getUserId, userId);
+        wrapper.eq(couponCode != null && !couponCode.isEmpty(), UserCoupon::getCouponCode, couponCode);
+        wrapper.eq(status != null, UserCoupon::getStatus, status);
+        wrapper.orderByDesc(UserCoupon::getCreateTime);
+
+        IPage<UserCoupon> result = this.page(pageParam, wrapper);
+
+        // 填充优惠券模板信息
+        result.getRecords().forEach(userCoupon -> {
+            CouponTemplate template = templateMapper.selectById(userCoupon.getTemplateId());
+            if (template != null) {
+                userCoupon.setCouponName(template.getCouponName());
+                userCoupon.setCouponType(template.getCouponType());
+                userCoupon.setMinAmount(template.getMinAmount());
+            }
+            fillCouponLabels(userCoupon);
+        });
+
+        log.debug("[优惠券服务] 查询结果 - 总数: {}, 当前页数量: {}", result.getTotal(), result.getRecords().size());
+
+        return result;
+    }
 }