Explorar el Código

红娘排行榜 数据同步

YH_0525 hace 1 mes
padre
commit
180af2c30b

+ 49 - 5
LiangZhiYUMao/pages/matchmaker-workbench/index.vue

@@ -21,16 +21,16 @@
 				<view class="welcome-text">
 					<text class="welcome-title">欢迎回来</text>
 					<text class="heart-icon">❤️</text>
-					<text class="matchmaker-name">{{}}</text>
+					<text class="matchmaker-name">{{ matchmakerInfo.realName || makerName }}</text>
 				</view>
-				<view class="avatar"></view>
+				<image class="avatar" :src="matchmakerInfo.avatarUrl || '/static/default-avatar.svg'" mode="aspectFill"></image>
 			</view>
 
 			<!-- 统计卡片 -->
 			<view class="stats-cards">
 				<view class="stats-card success">
 					<view class="stats-row">
-						<text class="stats-number">520</text>
+						<text class="stats-number">{{ matchmakerInfo.successCouples || 0 }}</text>
 						<text class="stats-tag orange">撮合达人</text>
 					</view>
 					<text class="stats-label">成功撮合</text>
@@ -38,7 +38,7 @@
 				</view>
 				<view class="stats-card points">
 					<view class="stats-row">
-						<text class="stats-number">23</text>
+						<text class="stats-number">{{ matchmakerInfo.points || 0 }}</text>
 						<text class="stats-tag purple">积分可兑礼</text>
 					</view>
 					<text class="stats-label">我的积分</text>
@@ -144,7 +144,13 @@ export default {
 				bestMatchmakers: [],
 				announcements: [],
 				currentAnnouncementIndex: 0,
-				makerName: uni.getStorageSync("userInfo").nickname
+				makerName: uni.getStorageSync("userInfo").nickname,
+				matchmakerInfo: {
+					realName: '',
+					avatarUrl: '',
+					successCouples: 0,
+					points: 0
+				}
 			}
 		},
 		computed: {
@@ -153,10 +159,48 @@ export default {
 			}
 		},
 		onLoad() {
+			this.loadMatchmakerInfo()
 			this.loadRankingData()
 			this.loadAnnouncements()
 		},
 		methods: {
+			// 加载当前登录红娘信息
+			async loadMatchmakerInfo() {
+				try {
+					const userInfo = uni.getStorageSync('userInfo')
+					if (!userInfo || !userInfo.userId) {
+						console.log('未登录,无法获取红娘信息')
+						return
+					}
+					
+					// 根据userId获取红娘信息
+					const res = await api.matchmaker.getByUserId(userInfo.userId)
+					console.log('红娘信息原始返回:', res)
+					
+					// 解析返回数据(可能被包装在data字段中)
+					let matchmaker = res
+					if (res && res.data) {
+						matchmaker = res.data
+					}
+					
+					console.log('解析后红娘信息:', matchmaker)
+					
+					if (matchmaker && (matchmaker.matchmakerId || matchmaker.matchmaker_id)) {
+						this.matchmakerInfo = {
+							realName: matchmaker.realName || matchmaker.real_name || userInfo.nickname,
+							avatarUrl: matchmaker.avatarUrl || matchmaker.avatar_url || userInfo.avatarUrl,
+							successCouples: matchmaker.successCouples || matchmaker.success_couples || 0,
+							points: matchmaker.points || 0
+						}
+						console.log('设置红娘信息:', this.matchmakerInfo)
+					} else {
+						console.log('未找到红娘信息,使用默认值')
+					}
+				} catch (e) {
+					console.error('加载红娘信息失败:', e)
+				}
+			},
+			
 			// 加载排行榜数据(取前3名)
 			async loadRankingData() {
 				try {

+ 24 - 37
LiangZhiYUMao/pages/matchmaker-workbench/product-detail.vue

@@ -28,21 +28,14 @@
       <view class="desc-content">{{ product.description || '暂无描述' }}</view>
     </view>
 
-    <!-- 收货信息 -->
-    <view class="receiver-section" v-if="product.category === 1">
-      <view class="section-title">收货信息</view>
-      <view class="form-item">
-        <text class="label">收货人</text>
-        <input class="input" v-model="receiverName" placeholder="请输入收货人姓名" />
-      </view>
+    <!-- 联系信息 -->
+    <view class="receiver-section">
+      <view class="section-title">联系信息</view>
       <view class="form-item">
         <text class="label">联系电话</text>
-        <input class="input" v-model="receiverPhone" placeholder="请输入联系电话" type="number" />
-      </view>
-      <view class="form-item">
-        <text class="label">收货地址</text>
-        <textarea class="textarea" v-model="receiverAddress" placeholder="请输入详细收货地址"></textarea>
+        <input class="input" v-model="contactPhone" placeholder="请输入联系电话" type="number" maxlength="11" />
       </view>
+      <view class="tips">提交后将由后台审核,审核通过后会与您联系</view>
     </view>
 
     <!-- 底部操作栏 -->
@@ -69,9 +62,7 @@ export default {
       product: {},
       userPoints: 0,
       makerId: null,
-      receiverName: '',
-      receiverPhone: '',
-      receiverAddress: '',
+      contactPhone: '',
       loading: false
     }
   },
@@ -81,11 +72,9 @@ export default {
       if (!this.product.pointsPrice) return false
       if (this.userPoints < this.product.pointsPrice) return false
       if (this.product.stock <= 0) return false
-      // 实物商品需要填写收货信息
-      if (this.product.category === 1) {
-        if (!this.receiverName || !this.receiverPhone || !this.receiverAddress) {
-          return false
-        }
+      // 需要填写联系电话
+      if (!this.contactPhone || this.contactPhone.length !== 11) {
+        return false
       }
       return true
     }
@@ -143,8 +132,8 @@ export default {
           uni.showToast({ title: '积分不足', icon: 'none' })
         } else if (this.product.stock <= 0) {
           uni.showToast({ title: '库存不足', icon: 'none' })
-        } else if (this.product.category === 1 && (!this.receiverName || !this.receiverPhone || !this.receiverAddress)) {
-          uni.showToast({ title: '请填写完整收货信息', icon: 'none' })
+        } else if (!this.contactPhone || this.contactPhone.length !== 11) {
+          uni.showToast({ title: '请输入正确的11位手机号', icon: 'none' })
         }
         return
       }
@@ -167,14 +156,12 @@ export default {
           makerId: this.makerId,
           productId: this.productId,
           quantity: 1,
-          receiverName: this.receiverName,
-          receiverPhone: this.receiverPhone,
-          receiverAddress: this.receiverAddress
+          contactPhone: this.contactPhone
         }
         
         await api.pointsMall.exchange(data)
         
-        uni.showToast({ title: '兑换成功', icon: 'success' })
+        uni.showToast({ title: '提交成功,等待审核', icon: 'success' })
         
         // 刷新积分余额
         await this.loadBalance()
@@ -315,14 +302,13 @@ export default {
   
   .form-item {
     display: flex;
-    align-items: flex-start;
+    align-items: center;
     margin-bottom: 20rpx;
     
     .label {
       width: 150rpx;
       font-size: 28rpx;
       color: #333;
-      padding-top: 10rpx;
     }
     
     .input {
@@ -333,15 +319,16 @@ export default {
       padding: 0 20rpx;
       font-size: 28rpx;
     }
-    
-    .textarea {
-      flex: 1;
-      height: 150rpx;
-      background: #F5F5F5;
-      border-radius: 10rpx;
-      padding: 20rpx;
-      font-size: 28rpx;
-    }
+  }
+  
+  .tips {
+    font-size: 24rpx;
+    color: #999;
+    margin-top: 10rpx;
+    padding: 15rpx;
+    background: #FFF8E1;
+    border-radius: 10rpx;
+    line-height: 1.5;
   }
 }
 

+ 97 - 22
LiangZhiYUMao/pages/matchmaker-workbench/ranking.vue

@@ -33,7 +33,9 @@
 				<image class="avatar-large" :src="topThree[1].avatar_url || defaultAvatar" mode="aspectFill"></image>
 				<text class="matchmaker-name">{{ topThree[1].real_name }}</text>
 				<text class="success-count">成功人数: {{ topThree[1].success_couples || 0 }}</text>
-				<view class="like-btn" @click="handleLike(topThree[1])">点赞</view>
+				<view class="like-btn" :class="{ liked: topThree[1].hasLiked }" @click="handleLike(topThree[1])">
+					{{ topThree[1].hasLiked ? '已点赞' : '点赞' }}
+				</view>
 			</view>
 
 			<view class="top-three-item first">
@@ -41,7 +43,9 @@
 				<image class="avatar-large" :src="topThree[0].avatar_url || defaultAvatar" mode="aspectFill"></image>
 				<text class="matchmaker-name">{{ topThree[0].real_name }}</text>
 				<text class="success-count">成功人数: {{ topThree[0].success_couples || 0 }}</text>
-				<view class="like-btn active" @click="handleLike(topThree[0])">点赞</view>
+				<view class="like-btn" :class="{ liked: topThree[0].hasLiked }" @click="handleLike(topThree[0])">
+					{{ topThree[0].hasLiked ? '已点赞' : '点赞' }}
+				</view>
 			</view>
 
 			<view class="top-three-item third">
@@ -49,7 +53,9 @@
 				<image class="avatar-large" :src="topThree[2].avatar_url || defaultAvatar" mode="aspectFill"></image>
 				<text class="matchmaker-name">{{ topThree[2].real_name }}</text>
 				<text class="success-count">成功人数: {{ topThree[2].success_couples || 0 }}</text>
-				<view class="like-btn" @click="handleLike(topThree[2])">点赞</view>
+				<view class="like-btn" :class="{ liked: topThree[2].hasLiked }" @click="handleLike(topThree[2])">
+					{{ topThree[2].hasLiked ? '已点赞' : '点赞' }}
+				</view>
 			</view>
 		</view>
 
@@ -60,10 +66,12 @@
 				<image class="avatar-small" :src="item.avatar_url || defaultAvatar" mode="aspectFill"></image>
 				<view class="matchmaker-info">
 					<text class="matchmaker-name-normal">{{ item.real_name }}</text>
-					<text class="success-count-normal">成功人数: {{ item.success_couples || 0 }} 人</text>
+					<text class="success-count-normal">成功: {{ item.success_couples || 0 }} | 点赞: {{ item.weeklyLikes || 0 }}</text>
 					<text class="user-rating">等级: {{ item.level_name || '青铜红娘' }}</text>
 				</view>
-				<view class="like-btn-small" @click="handleLike(item)">点赞</view>
+				<view class="like-btn-small" :class="{ liked: item.hasLiked }" @click="handleLike(item)">
+					{{ item.hasLiked ? '已赞' : '点赞' }}
+				</view>
 			</view>
 			<view v-if="loading" class="loading-tip">加载中...</view>
 			<view v-if="!loading && restList.length === 0" class="empty-tip">暂无更多红娘</view>
@@ -105,7 +113,8 @@ export default {
 			return {
 				loading: false,
 				rankingList: [],
-				defaultAvatar: '/static/default-avatar.svg'
+				defaultAvatar: '/static/default-avatar.svg',
+				userId: null
 			}
 		},
 		computed: {
@@ -119,6 +128,11 @@ export default {
 			}
 		},
 		onLoad() {
+			// 获取当前用户ID
+			const userInfo = uni.getStorageSync('userInfo')
+			if (userInfo && userInfo.userId) {
+				this.userId = userInfo.userId
+			}
 			// 加载排行榜数据
 			this.loadRankingData()
 		},
@@ -127,17 +141,21 @@ export default {
 			goBack() {
 				uni.navigateBack()
 			},
-			// 加载排行榜数据
+			// 加载排行榜数据(使用本周排行榜API)
 			async loadRankingData() {
 				this.loading = true
 				try {
-					const res = await api.matchmaker.getRankingData({ limit: 20 })
+					// 使用本周排行榜API,传入userId以获取点赞状态
+					const res = await api.matchmaker.getWeeklyRanking({ 
+						limit: 20,
+						userId: this.userId
+					})
 					if (res && Array.isArray(res)) {
-						this.rankingList = res
+						this.rankingList = res.map(item => this.formatMatchmaker(item))
 					} else if (res && res.data && Array.isArray(res.data)) {
-						this.rankingList = res.data
+						this.rankingList = res.data.map(item => this.formatMatchmaker(item))
 					}
-					console.log('排行榜数据:', this.rankingList)
+					console.log('本周排行榜数据:', this.rankingList)
 				} catch (e) {
 					console.error('加载排行榜数据失败:', e)
 					uni.showToast({
@@ -148,14 +166,66 @@ export default {
 					this.loading = false
 				}
 			},
+			// 格式化红娘数据(统一字段名)
+			formatMatchmaker(item) {
+				return {
+					...item,
+					matchmaker_id: item.matchmakerId || item.matchmaker_id,
+					real_name: item.realName || item.real_name,
+					avatar_url: item.avatarUrl || item.avatar_url,
+					success_couples: item.successCouples || item.success_couples,
+					level_name: item.levelName || item.level_name,
+					weeklyLikes: item.weeklyLikes || 0,
+					hasLiked: item.hasLiked || false
+				}
+			},
 			// 点赞
-			handleLike(matchmaker) {
-				// 实现点赞功能
-				console.log('点赞红娘:', matchmaker.realName)
-				uni.showToast({
-					title: '点赞成功',
-					icon: 'success'
-				})
+			async handleLike(matchmaker) {
+				// 检查是否已点赞
+				if (matchmaker.hasLiked) {
+					uni.showToast({
+						title: '本周已点赞过该红娘',
+						icon: 'none'
+					})
+					return
+				}
+				
+				// 检查是否登录
+				if (!this.userId) {
+					uni.showToast({
+						title: '请先登录',
+						icon: 'none'
+					})
+					return
+				}
+				
+				try {
+					const matchmakerId = matchmaker.matchmaker_id || matchmaker.matchmakerId
+					const res = await api.matchmaker.likeMatchmaker(this.userId, matchmakerId)
+					
+					if (res && (res.code === 200 || res.liked)) {
+						uni.showToast({
+							title: '点赞成功',
+							icon: 'success'
+						})
+						// 更新本地状态
+						matchmaker.hasLiked = true
+						matchmaker.weeklyLikes = (matchmaker.weeklyLikes || 0) + 1
+						// 刷新列表
+						this.loadRankingData()
+					} else {
+						uni.showToast({
+							title: res.message || '点赞失败',
+							icon: 'none'
+						})
+					}
+				} catch (e) {
+					console.error('点赞失败:', e)
+					uni.showToast({
+						title: e.message || '点赞失败',
+						icon: 'none'
+					})
+				}
 			},
 			// 导航到工作台
 			navigateToWorkbench() {
@@ -356,10 +426,10 @@ export default {
 				font-weight: bold;
 				border: 2rpx solid #FFFFFF;
 
-				&.active {
-					background: #E91E63;
-					color: #FFFFFF;
-					border-color: #E91E63;
+				&.liked {
+					background: rgba(255, 255, 255, 0.5);
+					color: rgba(255, 255, 255, 0.8);
+					border-color: rgba(255, 255, 255, 0.5);
 				}
 			}
 		}
@@ -427,6 +497,11 @@ export default {
 				border-radius: 25rpx;
 				font-size: 26rpx;
 				font-weight: bold;
+				
+				&.liked {
+					background: #CCCCCC;
+					color: #999999;
+				}
 			}
 		}
 

+ 20 - 1
LiangZhiYUMao/utils/api.js

@@ -300,13 +300,32 @@ export default {
             data: params 
         }),
         
-        // 获取排行榜数据
+        // 获取排行榜数据(总排行榜)
         getRankingData: (params) => request({ 
             url: '/matchmaker/ranking', 
             method: 'GET',
             data: params 
         }),
         
+        // 获取本周排行榜(按点赞数和成功人数平均数排名)
+        getWeeklyRanking: (params) => request({ 
+            url: '/matchmaker/weekly-ranking', 
+            method: 'GET',
+            data: params 
+        }),
+        
+        // 给红娘点赞(一周只能给同一红娘点赞一次)
+        likeMatchmaker: (userId, matchmakerId) => request({ 
+            url: `/matchmaker/like?userId=${userId}&matchmakerId=${matchmakerId}`, 
+            method: 'POST'
+        }),
+        
+        // 检查是否已点赞
+        checkLikeStatus: (userId, matchmakerId) => request({ 
+            url: `/matchmaker/check-like?userId=${userId}&matchmakerId=${matchmakerId}`, 
+            method: 'GET'
+        }),
+        
         // 签到相关
         checkinStatus: (makerId) => request({ 
             url: `/matchmaker/checkin/status?makerId=${makerId}` 

+ 8 - 6
service/admin/src/main/java/com/zhentao/controller/PointsMallController.java

@@ -133,7 +133,7 @@ public class PointsMallController {
     }
     
     /**
-     * 兑换商品
+     * 兑换商品(提交到后台审核)
      */
     @PostMapping("/exchange")
     public Result<PointsOrder> exchangeProduct(@RequestBody Map<String, Object> params) {
@@ -142,14 +142,16 @@ public class PointsMallController {
             Long productId = Long.valueOf(params.get("productId").toString());
             Integer quantity = params.get("quantity") != null ? 
                     Integer.valueOf(params.get("quantity").toString()) : 1;
-            String receiverName = (String) params.get("receiverName");
-            String receiverPhone = (String) params.get("receiverPhone");
-            String receiverAddress = (String) params.get("receiverAddress");
+            String contactPhone = (String) params.get("contactPhone");
+            
+            if (contactPhone == null || contactPhone.length() != 11) {
+                return Result.error("请输入正确的11位手机号");
+            }
             
             PointsOrder order = pointsMallService.exchangeProduct(
-                    makerId, productId, quantity, receiverName, receiverPhone, receiverAddress);
+                    makerId, productId, quantity, contactPhone);
             
-            return Result.success("兑换成功", order);
+            return Result.success("提交成功,等待审核", order);
         } catch (RuntimeException e) {
             return Result.error(e.getMessage());
         } catch (Exception e) {

+ 1 - 1
service/admin/src/main/java/com/zhentao/entity/PointsOrder.java

@@ -45,7 +45,7 @@ public class PointsOrder implements Serializable {
     /** 总积分 */
     private Integer totalPoints;
     
-    /** 状态: 0-待发货 1-已发货 2-已完成 3-已取消 */
+    /** 状态: 0-待审核 1-审核通过 2-已完成 3-已拒绝 */
     private Integer status;
     
     /** 收货人姓名 */

+ 3 - 6
service/admin/src/main/java/com/zhentao/service/PointsMallService.java

@@ -59,17 +59,14 @@ public interface PointsMallService {
     List<PointRule> getPointRules();
     
     /**
-     * 兑换商品
+     * 兑换商品(提交到后台审核)
      * @param makerId 红娘ID
      * @param productId 商品ID
      * @param quantity 数量
-     * @param receiverName 收货人姓名
-     * @param receiverPhone 收货人电话
-     * @param receiverAddress 收货地址
+     * @param contactPhone 联系电话
      * @return 订单信息
      */
-    PointsOrder exchangeProduct(Long makerId, Long productId, Integer quantity,
-                                 String receiverName, String receiverPhone, String receiverAddress);
+    PointsOrder exchangeProduct(Long makerId, Long productId, Integer quantity, String contactPhone);
     
     /**
      * 获取兑换订单列表

+ 4 - 7
service/admin/src/main/java/com/zhentao/service/impl/PointsMallServiceImpl.java

@@ -130,8 +130,7 @@ public class PointsMallServiceImpl implements PointsMallService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public PointsOrder exchangeProduct(Long makerId, Long productId, Integer quantity,
-                                       String receiverName, String receiverPhone, String receiverAddress) {
+    public PointsOrder exchangeProduct(Long makerId, Long productId, Integer quantity, String contactPhone) {
         // 1. 检查商品是否存在且上架
         PointsProduct product = pointsProductMapper.selectById(productId);
         if (product == null || product.getStatus() != 1) {
@@ -162,7 +161,7 @@ public class PointsMallServiceImpl implements PointsMallService {
             throw new RuntimeException("库存扣减失败,请重试");
         }
 
-        // 6. 创建订单
+        // 6. 创建订单(状态为待审核)
         PointsOrder order = new PointsOrder();
         order.setOrderNo(generateOrderNo());
         order.setMakerId(makerId);
@@ -172,10 +171,8 @@ public class PointsMallServiceImpl implements PointsMallService {
         order.setPointsPrice(product.getPointsPrice());
         order.setQuantity(quantity);
         order.setTotalPoints(totalPoints);
-        order.setStatus(0); // 待发货
-        order.setReceiverName(receiverName);
-        order.setReceiverPhone(receiverPhone);
-        order.setReceiverAddress(receiverAddress);
+        order.setStatus(0); // 0-待审核
+        order.setReceiverPhone(contactPhone); // 联系电话
         order.setCreateTime(LocalDateTime.now());
         order.setUpdateTime(LocalDateTime.now());
 

+ 89 - 1
service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java

@@ -277,7 +277,7 @@ public class MatchmakerController {
     }
     
     /**
-     * 获取红娘排行榜
+     * 获取红娘排行榜(总排行榜)
      * 
      * @param limit 返回数量限制,默认20
      * @return 红娘排行榜列表
@@ -294,6 +294,94 @@ public class MatchmakerController {
         }
     }
     
+    /**
+     * 获取本周红娘排行榜(按点赞数和成功人数的平均数排名)
+     * 
+     * @param limit 返回数量限制,默认20
+     * @param userId 当前用户ID(可选,用于判断是否已点赞)
+     * @return 红娘排行榜列表
+     */
+    @GetMapping("/weekly-ranking")
+    public Result<List<MatchmakerVO>> getWeeklyRankingList(
+            @RequestParam(defaultValue = "20") Integer limit,
+            @RequestParam(required = false) Long userId) {
+        try {
+            List<MatchmakerVO> rankingList = matchmakerService.getWeeklyRankingList(limit);
+            
+            // 如果传入了userId,标记用户是否已点赞
+            if (userId != null) {
+                for (MatchmakerVO vo : rankingList) {
+                    boolean hasLiked = matchmakerService.hasLikedThisWeek(userId, vo.getMatchmakerId().longValue());
+                    vo.setHasLiked(hasLiked);
+                }
+            }
+            
+            return Result.success(rankingList);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取本周排行榜失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 给红娘点赞(一周只能给同一红娘点赞一次)
+     * 
+     * @param userId 用户ID
+     * @param matchmakerId 红娘ID
+     * @return 点赞结果
+     */
+    @PostMapping("/like")
+    public Result<Map<String, Object>> likeMatchmaker(
+            @RequestParam Long userId,
+            @RequestParam Long matchmakerId) {
+        try {
+            // 检查是否给自己点赞
+            Matchmaker selfMatchmaker = matchmakerService.getMatchmakerByUserId(userId.intValue());
+            if (selfMatchmaker != null && selfMatchmaker.getMatchmakerId().equals(matchmakerId.intValue())) {
+                return Result.error("不能给自己点赞哦");
+            }
+            
+            // 检查是否已点赞
+            if (matchmakerService.hasLikedThisWeek(userId, matchmakerId)) {
+                return Result.error("本周已给该红娘点赞,下周再来吧");
+            }
+            
+            boolean success = matchmakerService.likeMatchmaker(userId, matchmakerId);
+            if (success) {
+                Map<String, Object> data = new HashMap<>();
+                data.put("liked", true);
+                return Result.success("点赞成功", data);
+            } else {
+                return Result.error("点赞失败,请稍后重试");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("点赞失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 检查用户是否已给某红娘点赞(本周)
+     * 
+     * @param userId 用户ID
+     * @param matchmakerId 红娘ID
+     * @return 是否已点赞
+     */
+    @GetMapping("/check-like")
+    public Result<Map<String, Object>> checkLikeStatus(
+            @RequestParam Long userId,
+            @RequestParam Long matchmakerId) {
+        try {
+            boolean hasLiked = matchmakerService.hasLikedThisWeek(userId, matchmakerId);
+            Map<String, Object> data = new HashMap<>();
+            data.put("hasLiked", hasLiked);
+            return Result.success(data);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("检查点赞状态失败:" + e.getMessage());
+        }
+    }
+    
     /**
      * 批量查询红娘信息
      * 

+ 35 - 0
service/homePage/src/main/java/com/zhentao/entity/MatchmakerLike.java

@@ -0,0 +1,35 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 红娘点赞记录实体类
+ * 用于记录用户对红娘的点赞,限制一周只能给同一红娘点赞一次
+ */
+@Data
+@TableName("matchmaker_likes")
+public class MatchmakerLike implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+    
+    /** 点赞用户ID */
+    private Long userId;
+    
+    /** 被点赞的红娘ID */
+    private Long matchmakerId;
+    
+    /** 点赞时间 */
+    private LocalDateTime likeTime;
+    
+    /** 点赞所属周(格式:yyyy-ww,如2025-50表示2025年第50周) */
+    private String weekKey;
+}

+ 37 - 0
service/homePage/src/main/java/com/zhentao/mapper/MatchmakerLikeMapper.java

@@ -0,0 +1,37 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.MatchmakerLike;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 红娘点赞记录Mapper接口
+ */
+@Mapper
+public interface MatchmakerLikeMapper extends BaseMapper<MatchmakerLike> {
+    
+    /**
+     * 查询用户本周是否已对某红娘点赞
+     * 
+     * @param userId 用户ID
+     * @param matchmakerId 红娘ID
+     * @param weekKey 周标识
+     * @return 点赞记录数
+     */
+    @Select("SELECT COUNT(*) FROM matchmaker_likes WHERE user_id = #{userId} AND matchmaker_id = #{matchmakerId} AND week_key = #{weekKey}")
+    int countByUserAndMatchmakerAndWeek(@Param("userId") Long userId, 
+                                         @Param("matchmakerId") Long matchmakerId, 
+                                         @Param("weekKey") String weekKey);
+    
+    /**
+     * 统计红娘本周获得的点赞数
+     * 
+     * @param matchmakerId 红娘ID
+     * @param weekKey 周标识
+     * @return 点赞数
+     */
+    @Select("SELECT COUNT(*) FROM matchmaker_likes WHERE matchmaker_id = #{matchmakerId} AND week_key = #{weekKey}")
+    int countWeeklyLikes(@Param("matchmakerId") Long matchmakerId, @Param("weekKey") String weekKey);
+}

+ 26 - 0
service/homePage/src/main/java/com/zhentao/service/MatchmakerService.java

@@ -72,5 +72,31 @@ public interface MatchmakerService extends IService<Matchmaker> {
      * @return 红娘VO列表
      */
     java.util.List<MatchmakerVO> batchGetMatchmakers(java.util.List<Integer> matchmakerIds);
+    
+    /**
+     * 用户给红娘点赞(一周只能给同一红娘点赞一次)
+     * 
+     * @param userId 用户ID
+     * @param matchmakerId 红娘ID
+     * @return 点赞结果
+     */
+    boolean likeMatchmaker(Long userId, Long matchmakerId);
+    
+    /**
+     * 检查用户本周是否已给某红娘点赞
+     * 
+     * @param userId 用户ID
+     * @param matchmakerId 红娘ID
+     * @return 是否已点赞
+     */
+    boolean hasLikedThisWeek(Long userId, Long matchmakerId);
+    
+    /**
+     * 获取本周红娘排行榜(按点赞数和成功人数的平均数排名)
+     * 
+     * @param limit 返回数量限制
+     * @return 红娘排行榜列表
+     */
+    java.util.List<MatchmakerVO> getWeeklyRankingList(Integer limit);
 }
 

+ 124 - 0
service/homePage/src/main/java/com/zhentao/service/impl/MatchmakerServiceImpl.java

@@ -5,18 +5,25 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zhentao.constant.RedisKeyConstants;
 import com.zhentao.dto.MatchmakerQueryDTO;
 import com.zhentao.entity.Matchmaker;
+import com.zhentao.entity.MatchmakerLike;
 import com.zhentao.mapper.MatchmakerMapper;
+import com.zhentao.mapper.MatchmakerLikeMapper;
 import com.zhentao.service.MatchmakerService;
 import com.zhentao.util.MinioUtil;
 import com.zhentao.vo.MatchmakerVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.Period;
+import java.time.temporal.WeekFields;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -30,6 +37,9 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
     @Autowired
     private MatchmakerMapper matchmakerMapper;
     
+    @Autowired
+    private MatchmakerLikeMapper matchmakerLikeMapper;
+    
     @Autowired
     private RedisTemplate<String, Object> redisTemplate;
     
@@ -454,4 +464,118 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
         
         return matchmakers;
     }
+    
+    /**
+     * 获取当前周的标识(格式:yyyy-ww)
+     */
+    private String getCurrentWeekKey() {
+        LocalDate now = LocalDate.now();
+        WeekFields weekFields = WeekFields.of(Locale.CHINA);
+        int weekNumber = now.get(weekFields.weekOfWeekBasedYear());
+        int year = now.get(weekFields.weekBasedYear());
+        return String.format("%d-%02d", year, weekNumber);
+    }
+    
+    @Override
+    public boolean hasLikedThisWeek(Long userId, Long matchmakerId) {
+        String weekKey = getCurrentWeekKey();
+        int count = matchmakerLikeMapper.countByUserAndMatchmakerAndWeek(userId, matchmakerId, weekKey);
+        return count > 0;
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean likeMatchmaker(Long userId, Long matchmakerId) {
+        // 1. 检查本周是否已点赞
+        if (hasLikedThisWeek(userId, matchmakerId)) {
+            return false; // 本周已点赞过
+        }
+        
+        // 2. 插入点赞记录
+        String weekKey = getCurrentWeekKey();
+        MatchmakerLike like = new MatchmakerLike();
+        like.setUserId(userId);
+        like.setMatchmakerId(matchmakerId);
+        like.setLikeTime(LocalDateTime.now());
+        like.setWeekKey(weekKey);
+        matchmakerLikeMapper.insert(like);
+        
+        // 3. 更新红娘总点赞数
+        Matchmaker matchmaker = this.getById(matchmakerId);
+        if (matchmaker != null) {
+            Integer currentLikes = matchmaker.getLikes() != null ? matchmaker.getLikes() : 0;
+            matchmaker.setLikes(currentLikes + 1);
+            this.updateById(matchmaker);
+        }
+        
+        // 4. 清除排行榜缓存
+        try {
+            Set<String> keys = redisTemplate.keys(RedisKeyConstants.MATCHMAKER_LIST + "ranking:*");
+            if (keys != null && !keys.isEmpty()) {
+                redisTemplate.delete(keys);
+            }
+            // 清除本周排行榜缓存
+            redisTemplate.delete(RedisKeyConstants.MATCHMAKER_LIST + "weekly_ranking:" + weekKey + ":*");
+        } catch (Exception e) {
+            System.err.println("⚠️ 清除排行榜缓存失败: " + e.getMessage());
+        }
+        
+        return true;
+    }
+    
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<MatchmakerVO> getWeeklyRankingList(Integer limit) {
+        if (limit == null || limit <= 0) {
+            limit = 20;
+        }
+        
+        String weekKey = getCurrentWeekKey();
+        String cacheKey = RedisKeyConstants.MATCHMAKER_LIST + "weekly_ranking:" + weekKey + ":" + limit;
+        
+        // 1. 尝试从缓存获取
+        try {
+            List<MatchmakerVO> cachedList = (List<MatchmakerVO>) redisTemplate.opsForValue().get(cacheKey);
+            if (cachedList != null) {
+                System.out.println("✅ 从Redis缓存获取本周红娘排行榜: " + cacheKey);
+                return cachedList;
+            }
+        } catch (Exception e) {
+            System.err.println("⚠️ Redis读取失败: " + e.getMessage());
+        }
+        
+        // 2. 从数据库查询所有活跃红娘
+        System.out.println("❌ 缓存未命中,从数据库查询本周排行榜");
+        List<MatchmakerVO> allMatchmakers = matchmakerMapper.selectRankingList(100); // 获取更多数据用于计算
+        
+        // 3. 计算本周点赞数并设置到VO
+        for (MatchmakerVO vo : allMatchmakers) {
+            int weeklyLikes = matchmakerLikeMapper.countWeeklyLikes(vo.getMatchmakerId().longValue(), weekKey);
+            vo.setWeeklyLikes(weeklyLikes);
+            
+            // 计算排名分数:(本周点赞数 + 成功人数) / 2
+            int successCouples = vo.getSuccessCouples() != null ? vo.getSuccessCouples() : 0;
+            double rankScore = (weeklyLikes + successCouples) / 2.0;
+            vo.setRankScore(rankScore);
+        }
+        
+        // 4. 按排名分数降序排序
+        List<MatchmakerVO> rankingList = allMatchmakers.stream()
+                .sorted(Comparator.comparingDouble(MatchmakerVO::getRankScore).reversed())
+                .limit(limit)
+                .collect(Collectors.toList());
+        
+        // 5. 处理数据
+        rankingList.forEach(this::processMatchmakerVO);
+        
+        // 6. 缓存结果(缓存10分钟)
+        try {
+            redisTemplate.opsForValue().set(cacheKey, rankingList, 600, TimeUnit.SECONDS);
+            System.out.println("💾 本周红娘排行榜已缓存到Redis: " + cacheKey);
+        } catch (Exception e) {
+            System.err.println("⚠️ Redis写入失败: " + e.getMessage());
+        }
+        
+        return rankingList;
+    }
 }

+ 15 - 0
service/homePage/src/main/java/com/zhentao/vo/MatchmakerVO.java

@@ -178,5 +178,20 @@ public class MatchmakerVO {
      * 等级进度百分比
      */
     private Integer levelProgress;
+    
+    /**
+     * 本周点赞数(用于本周排行榜)
+     */
+    private Integer weeklyLikes;
+    
+    /**
+     * 排名分数(点赞数和成功人数的平均值)
+     */
+    private Double rankScore;
+    
+    /**
+     * 当前用户是否已点赞(本周)
+     */
+    private Boolean hasLiked;
 }