Переглянути джерело

feat: 实现红包记录查询页面,支持发放记录列表及领取明细查看

urban 3 тижнів тому
батько
коміт
1dd5c78767

+ 38 - 0
magic_admin/model/biz_modules/app/tg_red_packet.go

@@ -0,0 +1,38 @@
+package app
+
+import "time"
+
+// TgRedPacket 红包主记录
+type TgRedPacket struct {
+	Id            int64     `json:"id" gorm:"column:id;type:bigint;comment:id;primarykey;NOT NULL"`
+	CreatedAt     int64     `json:"createdAt" gorm:"column:created_at;type:bigint;comment:创建时间"`
+	UpdatedAt     int64     `json:"updatedAt" gorm:"column:updated_at;type:bigint;comment:更新时间"`
+	PacketNo      string    `json:"packetNo" gorm:"column:packet_no;type:varchar(64);uniqueIndex;comment:红包编号;NOT NULL"`
+	UserId        int64     `json:"userId" gorm:"column:user_id;type:bigint;index;comment:发送者用户ID;NOT NULL"`
+	GroupId       string    `json:"groupId" gorm:"column:group_id;type:varchar(128);index;comment:群组ID;NOT NULL"`
+	PacketType    int8      `json:"packetType" gorm:"column:packet_type;type:tinyint;comment:红包类型:1=普通红包,2=手气红包;NOT NULL"`
+	TotalAmount   float64   `json:"totalAmount" gorm:"column:total_amount;type:decimal(25,8);comment:红包总金额;NOT NULL"`
+	TotalCount    int64     `json:"totalCount" gorm:"column:total_count;type:bigint;comment:红包总个数;NOT NULL"`
+	GrabbedCount  int64     `json:"grabbedCount" gorm:"column:grabbed_count;type:bigint;default:0;comment:已抢个数"`
+	GrabbedAmount float64   `json:"grabbedAmount" gorm:"column:grabbed_amount;type:decimal(25,8);default:0;comment:已抢金额"`
+	RemainCount   int64     `json:"remainCount" gorm:"column:remain_count;type:bigint;comment:剩余个数;NOT NULL"`
+	RemainAmount  float64   `json:"remainAmount" gorm:"column:remain_amount;type:decimal(25,8);comment:剩余金额;NOT NULL"`
+	Symbol        string    `json:"symbol" gorm:"column:symbol;type:varchar(20);default:VND;comment:币种"`
+	MessageId     int64     `json:"messageId" gorm:"column:message_id;type:bigint;comment:Telegram消息ID"`
+	BlessingWords string    `json:"blessingWords" gorm:"column:blessing_words;type:varchar(255);comment:祝福语"`
+	Status        int8      `json:"status" gorm:"column:status;type:tinyint;default:1;index;comment:状态:1=进行中,2=已抢完,3=已过期"`
+	ExpireAt      time.Time `json:"expireAt" gorm:"column:expire_at;type:timestamp;comment:过期时间"`
+	CompletedAt   time.Time `json:"completedAt" gorm:"column:completed_at;type:timestamp;comment:完成时间"`
+}
+
+func (*TgRedPacket) TableName() string {
+	return "magic_tg_red_packet"
+}
+
+func NewTgRedPacket() *TgRedPacket {
+	return &TgRedPacket{}
+}
+
+func (*TgRedPacket) Comment() string {
+	return "TG红包记录表"
+}

+ 1 - 0
magic_admin/router/app/tg_red_packet_record.go

@@ -16,4 +16,5 @@ var tgRedPacketRecordService = service.RealizationLayer.AppServiceGroup.TgRedPac
 func (h TgRedPacketRecordRouter) Register(group *gin.RouterGroup) {
 	group.GET("find", tgRedPacketRecordService.Find)
 	group.GET("get", tgRedPacketRecordService.Get)
+	group.GET("findGrabRecords", tgRedPacketRecordService.FindGrabRecords)
 }

+ 49 - 8
magic_admin/service/app/tg_red_packet_record.go

@@ -11,16 +11,16 @@ type TgRedPacketRecordService struct {
 	base.BizCommonService
 }
 
-// Find 查询红包记录列表
+// Find 查询红包发放记录列表(主表)
 func (s *TgRedPacketRecordService) Find(c *gin.Context) {
 	s.SetDbAlias("app")
 	type request[T any] struct {
 		base.ListRequest[T]
 		PacketNo string `form:"packet_no" json:"packet_no"`
-		ChatId   int64  `form:"chat_id" json:"chat_id"`
+		GroupId  string `form:"group_id" json:"group_id"`
 		Status   *int   `form:"status" json:"status"`
 	}
-	req := new(request[app.TgRedPacketRecord])
+	req := new(request[app.TgRedPacket])
 	if err := c.BindQuery(req); err != nil {
 		response.Resp(c, err.Error())
 		return
@@ -32,8 +32,8 @@ func (s *TgRedPacketRecordService) Find(c *gin.Context) {
 	if req.PacketNo != "" {
 		db = db.Where("packet_no = ?", req.PacketNo)
 	}
-	if req.ChatId != 0 {
-		db = db.Where("chat_id = ?", req.ChatId)
+	if req.GroupId != "" {
+		db = db.Where("group_id = ?", req.GroupId)
 	}
 	if req.Status != nil {
 		db = db.Where("status = ?", *req.Status)
@@ -42,7 +42,7 @@ func (s *TgRedPacketRecordService) Find(c *gin.Context) {
 	// 按创建时间倒序
 	db = db.Order("created_at DESC")
 
-	resp, err := base.NewQueryBaseHandler(app.NewTgRedPacketRecord()).List(db, req)
+	resp, err := base.NewQueryBaseHandler(app.NewTgRedPacket()).List(db, req)
 	if err != nil {
 		response.Resp(c, err.Error())
 		return
@@ -50,8 +50,49 @@ func (s *TgRedPacketRecordService) Find(c *gin.Context) {
 	response.Resp(c, resp)
 }
 
-// Get 获取红包记录详情
+// Get 获取红包详情
 func (s *TgRedPacketRecordService) Get(c *gin.Context) {
 	s.SetDbAlias("app")
-	base.NewBaseHandler(app.NewTgRedPacketRecord()).Get(c, s.DB())
+	base.NewBaseHandler(app.NewTgRedPacket()).Get(c, s.DB())
+}
+
+// FindGrabRecords 查询抢红包记录列表
+func (s *TgRedPacketRecordService) FindGrabRecords(c *gin.Context) {
+	s.SetDbAlias("app")
+	type request[T any] struct {
+		base.ListRequest[T]
+		PacketNo         string `form:"packet_no" json:"packet_no"`
+		PacketId         int64  `form:"packet_id" json:"packet_id"`
+		TelegramUsername string `form:"telegram_username" json:"telegram_username"`
+		UserId           int64  `form:"user_id" json:"user_id"`
+	}
+	req := new(request[app.TgRedPacketRecord])
+	if err := c.BindQuery(req); err != nil {
+		response.Resp(c, err.Error())
+		return
+	}
+
+	db := s.DB()
+
+	if req.PacketNo != "" {
+		db = db.Where("packet_no = ?", req.PacketNo)
+	}
+	if req.PacketId != 0 {
+		db = db.Where("packet_id = ?", req.PacketId)
+	}
+	if req.TelegramUsername != "" {
+		db = db.Where("telegram_username = ?", req.TelegramUsername)
+	}
+	if req.UserId != 0 {
+		db = db.Where("user_id = ?", req.UserId)
+	}
+
+	db = db.Order("created_at DESC")
+
+	resp, err := base.NewQueryBaseHandler(app.NewTgRedPacketRecord()).List(db, req)
+	if err != nil {
+		response.Resp(c, err.Error())
+		return
+	}
+	response.Resp(c, resp)
 }

+ 2 - 2
magic_admin_web/src/api/modules/redpacket.js

@@ -96,11 +96,11 @@ export const getRedPacketRecordList = params => {
 };
 
 /**
- * 获取抢红包记录列表
+ * 获取抢红包记录列表(领取明细)
  * @param {Object} params - 查询参数
  */
 export const getGrabRecordList = params => {
-  return http.get(`/admin/api/app/tg_red_packet_record/find`, params);
+  return http.get(`/admin/api/app/tg_red_packet/findGrabRecords`, params);
 };
 
 // ==================== 群组管理 ====================

+ 194 - 20
magic_admin_web/src/views/redpacket/record/index.vue

@@ -1,36 +1,210 @@
 <template>
   <div class="red-packet-record">
-    <el-card class="box-card">
-      <template #header>
-        <div class="card-header">
-          <span>红包记录查询</span>
-        </div>
+    <ProTable
+      ref="proTableRef"
+      :columns="columns"
+      :request-api="getRedPacketRecordList"
+      :search-columns="searchColumns"
+      :show-pagination="true"
+    >
+      <!-- 红包类型列 -->
+      <template #packetType="{ row }">
+        <el-tag :type="row.packetType === 1 ? '' : 'warning'">
+          {{ row.packetType === 1 ? '普通红包' : '手气红包' }}
+        </el-tag>
       </template>
 
-      <div class="placeholder">
-        <el-empty description="功能开发中,敬请期待..." />
+      <!-- 状态列 -->
+      <template #status="{ row }">
+        <el-tag :type="getStatusType(row.status)">
+          {{ getStatusText(row.status) }}
+        </el-tag>
+      </template>
+
+      <!-- 进度列 -->
+      <template #progress="{ row }">
+        <span>{{ row.grabbedCount }} / {{ row.totalCount }}</span>
+      </template>
+
+      <!-- 金额列 -->
+      <template #totalAmount="{ row }">
+        {{ row.totalAmount }} {{ row.symbol }}
+      </template>
+
+      <!-- 已抢金额列 -->
+      <template #grabbedAmount="{ row }">
+        {{ row.grabbedAmount }} {{ row.symbol }}
+      </template>
+
+      <!-- 创建时间列 -->
+      <template #createdAt="{ row }">
+        {{ formatTimestamp(row.createdAt) }}
+      </template>
+
+      <!-- 操作列 -->
+      <template #actions="{ row }">
+        <el-button link type="primary" @click="handleView(row)">查看</el-button>
+        <el-button link type="primary" @click="handleViewGrabRecords(row)">领取明细</el-button>
+      </template>
+    </ProTable>
+
+    <!-- 查看红包详情对话框 -->
+    <el-dialog v-model="viewDialogVisible" title="红包详情" width="650px">
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="ID">{{ viewData.id }}</el-descriptions-item>
+        <el-descriptions-item label="红包编号">{{ viewData.packetNo }}</el-descriptions-item>
+        <el-descriptions-item label="群组ID">{{ viewData.groupId }}</el-descriptions-item>
+        <el-descriptions-item label="红包类型">
+          <el-tag :type="viewData.packetType === 1 ? '' : 'warning'">
+            {{ viewData.packetType === 1 ? '普通红包' : '手气红包' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="总金额">{{ viewData.totalAmount }} {{ viewData.symbol }}</el-descriptions-item>
+        <el-descriptions-item label="总个数">{{ viewData.totalCount }}</el-descriptions-item>
+        <el-descriptions-item label="已抢金额">{{ viewData.grabbedAmount }} {{ viewData.symbol }}</el-descriptions-item>
+        <el-descriptions-item label="已抢个数">{{ viewData.grabbedCount }}</el-descriptions-item>
+        <el-descriptions-item label="剩余金额">{{ viewData.remainAmount }} {{ viewData.symbol }}</el-descriptions-item>
+        <el-descriptions-item label="剩余个数">{{ viewData.remainCount }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="getStatusType(viewData.status)">{{ getStatusText(viewData.status) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="消息ID">{{ viewData.messageId || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="祝福语" :span="2">{{ viewData.blessingWords || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间" :span="2">{{ formatTimestamp(viewData.createdAt) }}</el-descriptions-item>
+        <el-descriptions-item label="过期时间">{{ formatTime(viewData.expireAt) }}</el-descriptions-item>
+        <el-descriptions-item label="完成时间">{{ formatTime(viewData.completedAt) }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+
+    <!-- 领取明细对话框 -->
+    <el-dialog v-model="grabDialogVisible" title="领取明细" width="800px">
+      <div style="margin-bottom: 10px; color: #909399;">
+        红包编号: {{ currentPacketNo }} | 总额: {{ currentPacketAmount }} {{ currentPacketSymbol }}
       </div>
-    </el-card>
+      <el-table :data="grabRecords" border style="width: 100%" v-loading="grabLoading">
+        <el-table-column prop="sequence" label="序号" width="60" />
+        <el-table-column prop="telegramUsername" label="Telegram用户名" min-width="140">
+          <template #default="{ row }">
+            {{ row.telegramUsername || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="userId" label="平台用户ID" width="110" />
+        <el-table-column prop="amount" label="抢到金额" width="150">
+          <template #default="{ row }">
+            <span :style="{ color: row.isBest === 1 ? '#E6A23C' : '' , fontWeight: row.isBest === 1 ? 'bold' : '' }">
+              {{ row.amount }}
+            </span>
+            <el-tag v-if="row.isBest === 1" type="warning" size="small" style="margin-left: 4px;">手气最佳</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="grabbedAt" label="抢红包时间" width="180">
+          <template #default="{ row }">
+            {{ formatTime(row.grabbedAt) }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-// 红包记录查询页面 - 待开发
-</script>
+import { ref } from 'vue';
+import ProTable from '@/components/ProTable/index.vue';
+import { getRedPacketRecordList, getGrabRecordList } from '@/api/modules/redpacket';
 
-<style scoped lang="scss">
-.red-packet-record {
-  height: 100%;
-  padding: 20px;
+const proTableRef = ref();
+const viewDialogVisible = ref(false);
+const grabDialogVisible = ref(false);
+const grabLoading = ref(false);
+const viewData = ref({});
+const grabRecords = ref([]);
+const currentPacketNo = ref('');
+const currentPacketAmount = ref('');
+const currentPacketSymbol = ref('');
+
+// 表格列配置
+const columns = [
+  { prop: 'id', label: 'ID', width: 70 },
+  { prop: 'packetNo', label: '红包编号', minWidth: 160 },
+  { prop: 'groupId', label: '群组ID', width: 140 },
+  { prop: 'packetType', label: '红包类型', width: 100, slot: 'packetType' },
+  { prop: 'totalAmount', label: '总金额', width: 150, slot: 'totalAmount' },
+  { prop: 'progress', label: '领取进度', width: 110, slot: 'progress' },
+  { prop: 'grabbedAmount', label: '已抢金额', width: 150, slot: 'grabbedAmount' },
+  { prop: 'status', label: '状态', width: 90, slot: 'status' },
+  { prop: 'createdAt', label: '创建时间', width: 180, slot: 'createdAt' }
+];
 
-  .box-card {
-    .card-header {
-      font-weight: bold;
-    }
+// 搜索列配置
+const searchColumns = [
+  { prop: 'packet_no', label: '红包编号', type: 'input' },
+  { prop: 'group_id', label: '群组ID', type: 'input' },
+  {
+    prop: 'status',
+    label: '状态',
+    type: 'select',
+    options: [
+      { label: '进行中', value: 1 },
+      { label: '已抢完', value: 2 },
+      { label: '已过期', value: 3 }
+    ]
   }
+];
+
+// 状态文本
+const getStatusText = status => {
+  const map = { 1: '进行中', 2: '已抢完', 3: '已过期' };
+  return map[status] || '未知';
+};
+
+// 状态类型
+const getStatusType = status => {
+  const map = { 1: 'success', 2: 'info', 3: 'danger' };
+  return map[status] || '';
+};
+
+// 格式化时间戳(秒级)
+const formatTimestamp = ts => {
+  if (!ts) return '-';
+  return new Date(ts * 1000).toLocaleString('zh-CN');
+};
+
+// 格式化时间(ISO 字符串)
+const formatTime = t => {
+  if (!t) return '-';
+  const d = new Date(t);
+  if (isNaN(d.getTime())) return '-';
+  return d.toLocaleString('zh-CN');
+};
+
+// 查看详情
+function handleView(row) {
+  viewData.value = { ...row };
+  viewDialogVisible.value = true;
+}
 
-  .placeholder {
-    padding: 60px 0;
+// 查看领取明细
+async function handleViewGrabRecords(row) {
+  currentPacketNo.value = row.packetNo;
+  currentPacketAmount.value = row.totalAmount;
+  currentPacketSymbol.value = row.symbol;
+  grabRecords.value = [];
+  grabDialogVisible.value = true;
+  grabLoading.value = true;
+
+  try {
+    const res = await getGrabRecordList({ packet_id: row.id, pageSize: 100 });
+    grabRecords.value = res.data?.list || res.list || res.data || [];
+  } catch (e) {
+    console.error('查询领取明细失败', e);
+  } finally {
+    grabLoading.value = false;
   }
 }
+</script>
+
+<style scoped lang="scss">
+.red-packet-record {
+  padding: 20px;
+}
 </style>