فهرست منبع

Merge remote-tracking branch 'origin/test_dev' into yxy

yuxy 1 ماه پیش
والد
کامیت
13e685fd49
54فایلهای تغییر یافته به همراه3819 افزوده شده و 1436 حذف شده
  1. 7 0
      LiangZhiYUMao/pages.json
  2. 152 41
      LiangZhiYUMao/pages/courses/detail.vue
  3. 69 9
      LiangZhiYUMao/pages/matchmaker-workbench/earn-points.vue
  4. 66 5
      LiangZhiYUMao/pages/matchmaker-workbench/index.vue
  5. 1273 1083
      LiangZhiYUMao/pages/matchmaker-workbench/mine.vue
  6. 314 0
      LiangZhiYUMao/pages/matchmaker-workbench/my-activities.vue
  7. 23 1
      LiangZhiYUMao/pages/matchmaker-workbench/points-detail.vue
  8. 16 1
      LiangZhiYUMao/pages/matchmaker-workbench/points-mall.vue
  9. 40 38
      LiangZhiYUMao/pages/matchmaker-workbench/product-detail.vue
  10. 97 22
      LiangZhiYUMao/pages/matchmaker-workbench/ranking.vue
  11. 112 26
      LiangZhiYUMao/pages/matchmaker-workbench/sign-in.vue
  12. 55 2
      LiangZhiYUMao/utils/api.js
  13. 23 4
      gateway/src/main/resources/application.yml
  14. 10 0
      marriageAdmin-vue/src/config/api.js
  15. 12 2
      marriageAdmin-vue/src/layouts/MainLayout.vue
  16. 13 0
      marriageAdmin-vue/src/router/index.js
  17. 12 1
      marriageAdmin-vue/src/stores/user.js
  18. 15 1
      marriageAdmin-vue/src/views/banner/BannerList.vue
  19. 3 2
      marriageAdmin-vue/src/views/course/CourseList.vue
  20. 40 4
      marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue
  21. 181 34
      marriageAdmin-vue/src/views/report/ReportList.vue
  22. 3 2
      marriageAdmin-vue/src/views/user/UserVipList.vue
  23. 3 2
      marriageAdmin-vue/src/views/vip/VipPackageList.vue
  24. 12 0
      service/Essential/pom.xml
  25. 92 0
      service/Essential/src/main/java/com/zhentao/controller/CourseOrderController.java
  26. 30 0
      service/Essential/src/main/java/com/zhentao/dto/CoursePurchaseRequest.java
  27. 81 0
      service/Essential/src/main/java/com/zhentao/entity/CourseOrder.java
  28. 34 0
      service/Essential/src/main/java/com/zhentao/mapper/CourseOrderMapper.java
  29. 48 0
      service/Essential/src/main/java/com/zhentao/service/CourseOrderService.java
  30. 191 0
      service/Essential/src/main/java/com/zhentao/service/impl/CourseOrderServiceImpl.java
  31. 9 0
      service/Essential/src/main/resources/application.yml
  32. 10 0
      service/admin/pom.xml
  33. 22 3
      service/admin/src/main/java/com/zhentao/controller/AuthController.java
  34. 8 6
      service/admin/src/main/java/com/zhentao/controller/PointsMallController.java
  35. 92 33
      service/admin/src/main/java/com/zhentao/entity/AdminUser.java
  36. 1 1
      service/admin/src/main/java/com/zhentao/entity/PointsOrder.java
  37. 124 36
      service/admin/src/main/java/com/zhentao/entity/PointsProduct.java
  38. 10 5
      service/admin/src/main/java/com/zhentao/mapper/AdminUserMapper.java
  39. 11 5
      service/admin/src/main/java/com/zhentao/mapper/PointsProductMapper.java
  40. 3 6
      service/admin/src/main/java/com/zhentao/service/PointsMallService.java
  41. 37 12
      service/admin/src/main/java/com/zhentao/service/impl/AuthServiceImpl.java
  42. 4 7
      service/admin/src/main/java/com/zhentao/service/impl/PointsMallServiceImpl.java
  43. 5 0
      service/homePage/pom.xml
  44. 3 1
      service/homePage/src/main/java/com/zhentao/controller/ActivityController.java
  45. 13 3
      service/homePage/src/main/java/com/zhentao/controller/CourseController.java
  46. 115 3
      service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java
  47. 13 3
      service/homePage/src/main/java/com/zhentao/controller/SuccessCaseController.java
  48. 35 0
      service/homePage/src/main/java/com/zhentao/entity/MatchmakerLike.java
  49. 37 0
      service/homePage/src/main/java/com/zhentao/mapper/MatchmakerLikeMapper.java
  50. 26 0
      service/homePage/src/main/java/com/zhentao/service/MatchmakerService.java
  51. 55 28
      service/homePage/src/main/java/com/zhentao/service/impl/MatchmakerCheckinServiceImpl.java
  52. 138 4
      service/homePage/src/main/java/com/zhentao/service/impl/MatchmakerServiceImpl.java
  53. 15 0
      service/homePage/src/main/java/com/zhentao/vo/MatchmakerVO.java
  54. 6 0
      service/websocket/pom.xml

+ 7 - 0
LiangZhiYUMao/pages.json

@@ -133,6 +133,13 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/matchmaker-workbench/my-activities",
+			"style": {
+				"navigationBarTitleText": "我的活动",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/page3/page3",
 			"style": {

+ 152 - 41
LiangZhiYUMao/pages/courses/detail.vue

@@ -80,25 +80,35 @@
 <script>
 	import api from '@/utils/api.js'
 	import { DEFAULT_IMAGES } from '@/config/index.js'
+	import userAuth from '@/utils/userAuth.js'
 
 	export default {
 		data() {
 			return {
 				courseId: null,
 				course: {},
-				DEFAULT_IMAGES
+				DEFAULT_IMAGES,
+				gatewayURL: 'http://localhost:8083',
+				currentUserId: null,
+				isPurchased: false
 			}
 		},
 
 		onLoad(options) {
 			console.log('精品课程详情页面加载, 参数:', options)
 			
+			// 获取当前用户ID
+			this.currentUserId = userAuth.getUserId()
+			console.log('当前用户ID:', this.currentUserId)
+			
 			if (options.id) {
 				this.courseId = options.id
 				console.log('课程ID:', this.courseId)
 				// 根据ID设置对应的课程数据
 				this.setCourseDataById(this.courseId)
 				this.loadCourseDetail()
+				// 检查是否已购买
+				this.checkPurchaseStatus()
 			} else {
 				console.log('未传入课程ID,使用默认数据')
 			}
@@ -132,8 +142,11 @@
 					console.log('尝试从API加载课程详情:', this.courseId)
 					const data = await api.course.getDetail(this.courseId)
 					if (data) {
-						this.course = data
-						console.log('API课程详情加载成功')
+						this.course = {
+							...data,
+							purchase_status: 0  // 初始化为未购买,由 checkPurchaseStatus 判断
+						}
+						console.log('API课程详情加载成功, purchase_status:', this.course.purchase_status)
 					} else {
 						console.log('API返回空数据,使用默认课程详情')
 					}
@@ -148,10 +161,28 @@
 				}
 			},
 
+			// 检查购买状态
+			async checkPurchaseStatus() {
+				if (!this.currentUserId || !this.courseId) return
+				
+				try {
+					const purchased = await api.courseOrder.checkPurchased(this.currentUserId, this.courseId)
+					console.log('检查购买状态结果:', purchased, typeof purchased)
+					// 只有明确返回 true 时才设置为已购买
+					if (purchased === true) {
+						this.isPurchased = true
+						this.course.purchase_status = 1
+					}
+				} catch (error) {
+					console.log('检查购买状态失败:', error)
+					// 失败时不改变购买状态,默认为未购买
+				}
+			},
+			
 			// 处理购买
 			handlePurchase() {
 				// 检查是否已购买
-				if (this.course.purchase_status === 1) {
+				if (this.course.purchase_status === 1 || this.isPurchased) {
 					uni.showToast({
 						title: '您已购买过此课程',
 						icon: 'none'
@@ -159,49 +190,129 @@
 					return
 				}
 				
+				// 检查是否登录
+				if (!this.currentUserId) {
+					uni.showModal({
+						title: '提示',
+						content: '请先登录后再购买课程',
+						confirmText: '去登录',
+						success: (res) => {
+							if (res.confirm) {
+								uni.navigateTo({
+									url: '/pages/page3/page3'
+								})
+							}
+						}
+					})
+					return
+				}
+				
 				uni.showModal({
 					title: '确认购买',
 					content: `确认购买"${this.course.name}"课程吗?\n价格:¥${this.course.price}`,
-					success: async (res) => {
+					success: (res) => {
 						if (res.confirm) {
-							try {
-								console.log('尝试购买课程:', this.courseId)
-								// await api.course.purchase(this.courseId, {})
-								
-								// 模拟购买成功
-								uni.showToast({
-									title: '购买成功!',
-									icon: 'success'
-								})
-								
-								// 更新购买状态
-								this.course.purchase_status = 1
-								this.course.student_count = (this.course.student_count || 0) + 1
-								
-								setTimeout(() => {
-									uni.showModal({
-										title: '购买成功',
-										content: '您已成功购买此课程,现在可以开始学习了!',
-										showCancel: false,
-										confirmText: '开始学习',
-										success: (modalRes) => {
-											if (modalRes.confirm) {
-												// TODO: 跳转到课程学习页面
-												uni.showToast({
-													title: '学习功能开发中',
-													icon: 'none'
-												})
-											}
+							this.requestPay()
+						}
+					}
+				})
+			},
+			
+			// 请求支付参数并调起微信支付
+			requestPay() {
+				uni.showLoading({
+					title: '处理中...'
+				})
+				
+				uni.request({
+					url: this.gatewayURL + '/api/course-order/purchase',
+					method: 'POST',
+					data: {
+						userId: this.currentUserId,
+						courseId: parseInt(this.courseId),
+						courseName: this.course.name,
+						price: this.course.price
+					},
+					header: {
+						'content-type': 'application/json',
+						'token': uni.getStorageSync('token')
+					},
+					success: (res) => {
+						uni.hideLoading()
+						
+						if (res.data && res.data.code === 200) {
+							const payParams = res.data.data
+							this.callWxPay(payParams)
+						} else {
+							uni.showToast({
+								title: res.data?.message || res.data?.msg || '获取支付参数失败',
+								icon: 'none'
+							})
+						}
+					},
+					fail: (err) => {
+						uni.hideLoading()
+						console.error('请求支付参数失败:', err)
+						uni.showToast({
+							title: '网络请求失败',
+							icon: 'none'
+						})
+					}
+				})
+			},
+			
+			// 调起微信支付
+			callWxPay(payParams) {
+				uni.requestPayment({
+					provider: 'wxpay',
+					timeStamp: payParams.timeStamp,
+					nonceStr: payParams.nonceStr,
+					package: payParams.package,
+					signType: payParams.signType,
+					paySign: payParams.paySign,
+					success: (payResult) => {
+						if (payResult.errMsg === 'requestPayment:ok') {
+							uni.showToast({
+								title: '购买成功!',
+								icon: 'success',
+								duration: 2000
+							})
+							
+							// 更新购买状态
+							this.course.purchase_status = 1
+							this.isPurchased = true
+							this.course.student_count = (this.course.student_count || 0) + 1
+							
+							setTimeout(() => {
+								uni.showModal({
+									title: '购买成功',
+									content: '您已成功购买此课程,现在可以开始学习了!',
+									showCancel: false,
+									confirmText: '开始学习',
+									success: (modalRes) => {
+										if (modalRes.confirm) {
+											uni.showToast({
+												title: '学习功能开发中',
+												icon: 'none'
+											})
 										}
-									})
-								}, 1500)
-							} catch (error) {
-								console.error('购买失败:', error)
-								uni.showToast({
-									title: '购买失败,请重试',
-									icon: 'none'
+									}
 								})
-							}
+							}, 2000)
+						}
+					},
+					fail: (payError) => {
+						console.error('支付失败:', payError)
+						if (payError.errMsg.includes('cancel')) {
+							uni.showToast({
+								title: '已取消支付',
+								icon: 'none'
+							})
+						} else {
+							uni.showToast({
+								title: `支付失败:${payError.errMsg}`,
+								icon: 'none'
+							})
 						}
 					}
 				})

+ 69 - 9
LiangZhiYUMao/pages/matchmaker-workbench/earn-points.vue

@@ -25,8 +25,8 @@
             <view class="rule-desc">{{ getDesc(rule.ruleType) }}</view>
           </view>
           <view class="rule-points">+{{ rule.pointValue }}</view>
-          <view class="rule-action" @click="handleAction(rule)">
-            {{ getActionText(rule.ruleType) }}
+          <view class="rule-action" :class="{ disabled: rule.ruleType === 1 && isSignedToday }" @click="handleAction(rule)">
+            {{ rule.ruleType === 1 && isSignedToday ? '已签到' : getActionText(rule.ruleType) }}
           </view>
         </view>
       </view>
@@ -55,27 +55,67 @@ export default {
     return {
       balance: 0,
       rules: [],
-      makerId: null
+      makerId: null,
+      isSignedToday: false  // 今日是否已签到
     }
   },
   onLoad() {
     this.initData()
   },
+  onShow() {
+    // 每次显示页面时刷新数据,确保数据同步
+    if (this.makerId) {
+      this.loadBalance()
+      this.checkSignInStatus()
+    }
+  },
   methods: {
     async initData() {
       const userInfo = uni.getStorageSync('userInfo')
       if (userInfo && userInfo.matchmakerId) {
         this.makerId = userInfo.matchmakerId
       } else if (userInfo && userInfo.userId) {
-        this.makerId = userInfo.userId
+        // 如果没有matchmakerId,通过API获取
+        try {
+          const res = await api.matchmaker.getByUserId(userInfo.userId)
+          let matchmaker = res
+          if (res && res.data) {
+            matchmaker = res.data
+          }
+          if (matchmaker && (matchmaker.matchmakerId || matchmaker.matchmaker_id)) {
+            this.makerId = matchmaker.matchmakerId || matchmaker.matchmaker_id
+            // 保存到userInfo中
+            userInfo.matchmakerId = this.makerId
+            uni.setStorageSync('userInfo', userInfo)
+          }
+        } catch (e) {
+          console.error('获取红娘信息失败:', e)
+        }
       }
       
       await Promise.all([
         this.loadBalance(),
-        this.loadRules()
+        this.loadRules(),
+        this.checkSignInStatus()
       ])
     },
     
+    // 检查今日签到状态
+    async checkSignInStatus() {
+      if (!this.makerId) return
+      try {
+        const res = await api.matchmaker.checkinStatus(this.makerId)
+        // 解析返回数据
+        let data = res
+        if (res && res.data !== undefined) {
+          data = res.data
+        }
+        this.isSignedToday = data === true || data?.isCheckedIn === true || data?.checked === true
+      } catch (e) {
+        console.error('检查签到状态失败:', e)
+      }
+    },
+    
     async loadBalance() {
       if (!this.makerId) return
       try {
@@ -134,7 +174,11 @@ export default {
     
     async handleAction(rule) {
       if (rule.ruleType === 1) {
-        // 签到
+        // 签到 - 先检查是否已签到
+        if (this.isSignedToday) {
+          uni.showToast({ title: '今日已签到', icon: 'none' })
+          return
+        }
         await this.doSignIn(rule)
       } else if (rule.ruleType === 2) {
         // 上传线索
@@ -161,14 +205,25 @@ export default {
       }
       
       try {
-        const res = await api.pointsMall.addPoints(this.makerId, rule.ruleType, '每日签到')
+        // 使用红娘签到接口
+        const res = await api.matchmaker.doCheckin(this.makerId)
+        
+        // 签到成功后更新状态
+        this.isSignedToday = true
+        
+        // 刷新积分余额
+        await this.loadBalance()
+        
         uni.showToast({
-          title: `签到成功,+${res.addedPoints}积分`,
+          title: `签到成功,+${rule.pointValue}积分`,
           icon: 'success'
         })
-        this.balance = res.newBalance
       } catch (e) {
         console.error('签到失败:', e)
+        // 如果是已签到的错误,更新状态
+        if (e.message && e.message.includes('已签到')) {
+          this.isSignedToday = true
+        }
         uni.showToast({
           title: e.message || '签到失败',
           icon: 'none'
@@ -295,6 +350,11 @@ export default {
       color: #FFFFFF;
       font-size: 24rpx;
       border-radius: 30rpx;
+      
+      &.disabled {
+        background: #CCCCCC;
+        color: #FFFFFF;
+      }
     }
   }
 }

+ 66 - 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: {
@@ -157,10 +163,55 @@ 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)) {
+						// 获取红娘ID
+						const matchmakerId = 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
+						}
+						
+						// 将matchmakerId保存到userInfo中,供其他页面使用
+						userInfo.matchmakerId = matchmakerId
+						uni.setStorageSync('userInfo', userInfo)
+						console.log('设置红娘信息:', this.matchmakerInfo, '红娘ID:', matchmakerId)
+					} else {
+						console.log('未找到红娘信息,使用默认值')
+					}
+				} catch (e) {
+					console.error('加载红娘信息失败:', e)
+				}
+			},
+			
 			// 加载排行榜数据(取前3名)
 			async loadRankingData() {
 				try {
@@ -259,6 +310,16 @@ export default {
 				uni.navigateTo({
 					url: '/pages/matchmaker-workbench/mine'
 				})
+			},
+			// 返回用户端首页
+			openExitPopup() {
+				uni.navigateTo({
+					url: '/pages/index/index'
+				})
+			},
+			// 搜索
+			handleSearch() {
+				console.log('搜索')
 			}
 		}
 	}

+ 1273 - 1083
LiangZhiYUMao/pages/matchmaker-workbench/mine.vue

@@ -1,1096 +1,1286 @@
 <template>
-	<view class="matchmaker-mine">
-		<!-- 顶部导航栏 -->
-		<view class="header">
-			<view class="back-btn" @click="goBack"></view>
-			<text class="header-title">我的</text>
-			<view class="placeholder"></view>
-		</view>
-
-		<scroll-view scroll-y class="content">
-			<view class="content-container">
-				<!-- 个人信息卡片 -->
-				<view class="profile-card">
-					<view class="profile-header">
-						<view class="profile-avatar" :style="{ backgroundImage: `url(${profile.avatarUrl})` }"></view>
-						<view class="profile-info">
-							<text class="profile-name">{{ profile.realName }}</text>
-							<view class="profile-badge">{{ profile.badge }}</view>
-						</view>
-					</view>
-
-					<!-- 等级和积分 -->
-					<view class="level-points">
-						<view class="level-info">
-							<view class="level-icon"></view>
-							<text class="level-name">{{ profile.levelName }}</text>
-						</view>
-						<text class="points-value">{{ profile.points }}</text>
-						<text class="points-label">积分</text>
-					</view>
-
-					<!-- 等级进度 -->
-					<view class="level-progress">
-						<text class="progress-label">距离下一等级</text>
-						<view class="progress-bar">
-							<view class="progress-fill" :style="{ width: profile.levelProgress + '%' }"></view>
-						</view>
-						<view class="progress-values">
-							<text class="current-value">当前{{ profile.points }}</text>
-							<text class="target-value">目标{{ profile.nextLevelPoints }}</text>
-						</view>
-						<view class="upgrade-tip">再获得{{ profile.pointsToNextLevel }}积分即可晋升{{ profile.nextLevelName }}等级</view>
-					</view>
-				</view>
-
-				<!-- 功能菜单 -->
-				<view class="function-grid">
-					<view class="function-item" @click="showSignInPopup">
-						<view class="function-icon calendar">22</view>
-						<text class="function-text">签到</text>
-					</view>
-					<view class="function-item" @click="handleMyResources">
-						<view class="function-icon resources"></view>
-						<text class="function-text">我的资源</text>
-					</view>
-					<view class="function-item" @click="handleActivityCenter">
-						<view class="function-icon heart-book"></view>
-						<text class="function-text">我的活动</text>
-					</view>
-					<view class="function-item" @click="handlePointsMall">
-						<view class="function-icon money"></view>
-						<text class="function-text">我的积分</text>
-					</view>
-				</view>
-
-				<!-- 设置选项 -->
-				<view class="settings-section">
-					<view class="settings-item" @click="handleEditProfile">
-						<view class="settings-icon user"></view>
-						<text class="settings-text">编辑资料</text>
-						<view class="arrow-right"></view>
-					</view>
-
-					<view class="settings-item" @click="handleAccountSettings">
-						<view class="settings-icon gear"></view>
-						<text class="settings-text">账户设置</text>
-						<view class="arrow-right"></view>
-					</view>
-
-					<view class="settings-item" @click="handleLogout">
-						<view class="settings-icon logout"></view>
-						<text class="settings-text">退出登录</text>
-						<view class="arrow-right"></view>
-					</view>
-				</view>
-			</view>
-		</scroll-view>
-
-		<!-- 底部导航 -->
-		<view class="tabbar">
-			<view class="tabbar-item home" @click="navigateToWorkbench">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">工作台</text>
-			</view>
-			<view class="tabbar-item resources" @click="navigateToMyResources">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">我的资源</text>
-			</view>
-			<view class="tabbar-item trophy" @click="navigateToRanking">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">排行榜</text>
-			</view>
-			<view class="tabbar-item message" @click="navigateToMessage">
-				<view class="tabbar-icon">
-					<view v-if="unreadCount > 0" class="badge">{{ unreadCount }}</view>
-				</view>
-				<text class="tabbar-text">消息</text>
-			</view>
-			<view class="tabbar-item mine active" @click="navigateToMine">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">我的</text>
-			</view>
-		</view>
-
-		<!-- 签到弹框 -->
-		<uni-popup ref="signInPopup" type="center" :mask-click="false">
-			<view class="sign-in-popup">
-				<view class="popup-header">
-					<text class="popup-title">今日签到</text>
-					<view class="close-btn" @click="closeSignInPopup"></view>
-				</view>
-				<view class="popup-content">
-					<view class="calendar-container">
-						<view class="calendar-header">
-							<text class="calendar-title">{{ calendarTitle }}</text>
-							<view class="calendar-icon"></view>
-						</view>
-						<view class="calendar-week">
-							<text class="week-day">日</text>
-							<text class="week-day">一</text>
-							<text class="week-day">二</text>
-							<text class="week-day">三</text>
-							<text class="week-day">四</text>
-							<text class="week-day">五</text>
-							<text class="week-day">六</text>
-						</view>
-						<view class="calendar-days">
-							<view 
-								v-for="(day, index) in calendarDays" 
-								:key="index"
-								:class="[
+  <view class="matchmaker-mine">
+    <!-- 顶部导航栏 -->
+    <view class="header">
+      <view class="back-btn" @click="goBack"></view>
+      <text class="header-title">我的</text>
+      <view class="placeholder"></view>
+    </view>
+
+    <scroll-view scroll-y class="content">
+      <view class="content-container">
+        <!-- 个人信息卡片 -->
+        <view class="profile-card">
+          <view class="profile-header">
+            <view class="profile-avatar" :style="{ backgroundImage: `url(${profile.avatarUrl})` }"></view>
+            <view class="profile-info">
+              <text class="profile-name">{{ profile.realName }}</text>
+              <view class="profile-badge">{{ profile.badge }}</view>
+            </view>
+          </view>
+
+          <!-- 等级和积分 -->
+          <view class="level-points">
+            <view class="level-info">
+              <view class="level-icon"></view>
+              <text class="level-name">{{ profile.levelName }}</text>
+            </view>
+            <text class="points-value">{{ profile.points }}</text>
+            <text class="points-label">积分</text>
+          </view>
+
+          <!-- 等级进度 -->
+          <view class="level-progress">
+            <text class="progress-label">距离下一等级</text>
+            <view class="progress-bar">
+              <view class="progress-fill" :style="{ width: profile.levelProgress + '%' }"></view>
+            </view>
+            <view class="progress-values">
+              <text class="current-value">当前{{ profile.points }}</text>
+              <text class="target-value">目标{{ profile.nextLevelPoints }}</text>
+            </view>
+            <view class="upgrade-tip">再获得{{ profile.pointsToNextLevel }}积分即可晋升{{ profile.nextLevelName }}等级</view>
+          </view>
+        </view>
+
+        <!-- 功能菜单 -->
+        <view class="function-grid">
+          <view class="function-item" @click="showSignInPopup">
+            <view class="function-icon calendar">22</view>
+            <text class="function-text">签到</text>
+          </view>
+          <view class="function-item" @click="handleMyResources">
+            <view class="function-icon resources"></view>
+            <text class="function-text">我的资源</text>
+          </view>
+          <view class="function-item" @click="handleActivityCenter">
+            <view class="function-icon heart-book"></view>
+            <text class="function-text">我的活动</text>
+          </view>
+          <view class="function-item" @click="handlePointsMall">
+            <view class="function-icon money"></view>
+            <text class="function-text">我的积分</text>
+          </view>
+        </view>
+
+        <!-- 设置选项 -->
+        <view class="settings-section">
+          <view class="settings-item" @click="handleEditProfile">
+            <view class="settings-icon user"></view>
+            <text class="settings-text">编辑资料</text>
+            <view class="arrow-right"></view>
+          </view>
+
+          <view class="settings-item" @click="handleAccountSettings">
+            <view class="settings-icon info"></view>
+            <text class="settings-text">关于我们</text>
+            <view class="arrow-right"></view>
+          </view>
+
+          <view class="settings-item" @click="handleLogout">
+            <view class="settings-icon logout"></view>
+            <text class="settings-text">退出登录</text>
+            <view class="arrow-right"></view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 底部导航 -->
+    <view class="tabbar">
+      <view class="tabbar-item home" @click="navigateToWorkbench">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">工作台</text>
+      </view>
+      <view class="tabbar-item resources" @click="navigateToMyResources">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">我的资源</text>
+      </view>
+      <view class="tabbar-item trophy" @click="navigateToRanking">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">排行榜</text>
+      </view>
+      <view class="tabbar-item message" @click="navigateToMessage">
+        <view class="tabbar-icon">
+          <view v-if="unreadCount > 0" class="badge">{{ unreadCount }}</view>
+        </view>
+        <text class="tabbar-text">消息</text>
+      </view>
+      <view class="tabbar-item mine active" @click="navigateToMine">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">我的</text>
+      </view>
+    </view>
+
+    <!-- 签到弹框 -->
+    <uni-popup ref="signInPopup" type="center" :mask-click="false">
+      <view class="sign-in-popup">
+        <view class="popup-header">
+          <text class="popup-title">每日签到</text>
+          <view class="close-btn" @click="closeSignInPopup"></view>
+        </view>
+        <view class="popup-content">
+          <!-- 签到统计 -->
+          <view class="checkin-stats">
+            <view class="stats-item">
+              <text class="stats-label">已连续签到</text>
+              <text class="stats-value">{{ continuousDays }}</text>
+              <text class="stats-unit">天</text>
+            </view>
+            <view class="stats-item">
+              <text class="stats-label">累计签到</text>
+              <text class="stats-value">{{ totalDays }}</text>
+              <text class="stats-unit">天</text>
+            </view>
+          </view>
+          
+          <view class="calendar-container">
+            <view class="calendar-header">
+              <text class="calendar-title">{{ calendarTitle }}</text>
+            </view>
+            <view class="calendar-week">
+              <text class="week-day">日</text>
+              <text class="week-day">一</text>
+              <text class="week-day">二</text>
+              <text class="week-day">三</text>
+              <text class="week-day">四</text>
+              <text class="week-day">五</text>
+              <text class="week-day">六</text>
+            </view>
+            <view class="calendar-days">
+              <view
+                  v-for="(day, index) in calendarDays"
+                  :key="index"
+                  :class="[
 									'calendar-day',
 									{ 'other-month': !day.isCurrentMonth },
 									{ 'today': day.isToday },
-									{ 'checked': day.isChecked }
+									{ 'checked': day.isChecked },
+									{ 'today-checked': day.isToday && day.isChecked }
 								]"
-							>
-								<text class="day-text">{{ day.date }}</text>
-								<view v-if="day.isChecked" class="check-mark"></view>
-							</view>
-						</view>
-					</view>
-					<view class="sign-in-reward">
-						<text class="reward-title">签到奖励</text>
-						<text class="reward-points">+5积分</text>
-					</view>
-				</view>
-				<view class="popup-footer">
-					<button 
-						:class="['sign-in-btn', { 'signed': isSignedToday }]" 
-						@click="doSignIn"
-						:disabled="isSignedToday"
-					>
-						{{ isSignedToday ? '今日已签到' : '立即签到' }}
-					</button>
-				</view>
-			</view>
-		</uni-popup>
-	</view>
+              >
+                <text class="day-text">{{ day.date }}</text>
+                <view v-if="day.isChecked" class="check-mark">✓</view>
+              </view>
+            </view>
+          </view>
+          
+          <view class="sign-in-reward">
+            <text class="reward-title">签到奖励</text>
+            <text class="reward-points">+5积分</text>
+          </view>
+        </view>
+        <view class="popup-footer">
+          <button
+              :class="['sign-in-btn', { 'signed': isSignedToday }]"
+              @click="doSignIn"
+              :disabled="isSignedToday"
+          >
+            {{ isSignedToday ? '今日已签到' : '立即签到' }}
+          </button>
+        </view>
+      </view>
+    </uni-popup>
+  </view>
 
 </template>
 
 <script>
-	import api from '../../utils/api.js'
-	export default {
-		data() {
-			return {
-				profile: {
-					realName: '',
-					avatarUrl: '',
-					badge: '',
-					rating: 5.0,
-					level: '',
-					levelName: '',
-					nextLevelName: '',
-					points: 0,
-					currentLevelPoints: 0,
-					nextLevelPoints: 100,
-					pointsToNextLevel: 0,
-					levelProgress: 0
-				},
-				// 签到相关
-				isSignedToday: false,
-				continuousDays: 0,
-				totalDays: 0,
-				currentDate: new Date(),
-				calendarTitle: '',
-				calendarDays: []
-			}
-		},
-		onLoad() {
-			this.loadProfileData()
-			this.checkSignInStatus()
-			this.generateCalendar()
-		},
-		onShow() {
-			// 从编辑资料页返回时,自动刷新个人资料
-			this.loadProfileData()
-		},
-		computed: {
-			unreadCount() {
-				return this.$store.getters.getTotalUnread || 0
-			}
-		},
-		methods: {
-			// 生成日历数据
-			generateCalendar() {
-				const date = this.currentDate || new Date()
-				const year = date.getFullYear()
-				const month = date.getMonth()
-				// 设置日历标题
-				this.calendarTitle = `${year}年·${month + 1}月`
-				// 获取当月第一天
-				const firstDay = new Date(year, month, 1)
-				// 获取当月第一天是星期几(0-6,0表示周日)
-				const firstDayWeek = firstDay.getDay()
-				// 获取当月最后一天
-				const lastDay = new Date(year, month + 1, 0)
-				// 获取当月最后一天的日期
-				const lastDayDate = lastDay.getDate()
-				// 获取上个月最后一天的日期
-				const prevMonthLastDay = new Date(year, month, 0).getDate()
-				const days = []
-				// 添加上个月的日期
-				for (let i = firstDayWeek - 1; i >= 0; i--) {
-					days.push({
-						date: prevMonthLastDay - i,
-						isCurrentMonth: false,
-						isToday: false,
-						isChecked: false
-					})
-				}
-				// 添加当月的日期
-				const today = new Date()
-				const isCurrentMonth = today.getFullYear() === year && today.getMonth() === month
-				for (let i = 1; i <= lastDayDate; i++) {
-					const isToday = isCurrentMonth && today.getDate() === i
-					days.push({
-						date: i,
-						isCurrentMonth: true,
-						isToday: isToday,
-						isChecked: isToday && this.isSignedToday
-					})
-				}
-				// 添加下个月的日期,补满6行
-				const totalDays = 42 // 6行7列
-				const nextMonthDays = totalDays - days.length
-				for (let i = 1; i <= nextMonthDays; i++) {
-					days.push({
-						date: i,
-						isCurrentMonth: false,
-						isToday: false,
-						isChecked: false
-					})
-				}
-				this.calendarDays = days
-			},
-			// 加载个人资料数据
-			async loadProfileData() {
-				try {
-					const userInfo = uni.getStorageSync('userInfo')
-					console.log('从本地存储获取到的用户信息:', userInfo)
-					// 模拟用户信息,用于测试
-					const testUserId = 19
-					const userId = userInfo && userInfo.userId ? userInfo.userId : testUserId
-					console.log('使用的userId:', userId)
-					// 调用API获取红娘信息
-					console.log('开始调用API获取红娘信息...')
-					const matchmakerInfo = await api.matchmaker.getByUserId(userId)
-					console.log('API调用结果:', matchmakerInfo)
-					if (matchmakerInfo) {
-						const levelNames = ['', '青铜', '白银', '黄金', '铂金', '钻石']
-						const currentLevel = matchmakerInfo.level || 1
-						const nextLevel = currentLevel < 5 ? currentLevel + 1 : 5
-						const nextLevelName = levelNames[nextLevel]
-						const defaultAvatars = {
-							male: 'http://115.190.125.125:9000/dynamic-comments/dynamics/5c645152-9940-41d3-83a9-69ee6e0c0aaa.png',
-							female: 'http://115.190.125.125:9000/dynamic-comments/dynamics/c7fb04d7-ee4d-4b3d-bcef-f246da9c841f.png'
-						}
-						const avatarUrl = matchmakerInfo.gender === 1 ? defaultAvatars.male : defaultAvatars.female
-						console.log('获取到的红娘信息:', matchmakerInfo)
-						this.profile = {
-							realName: matchmakerInfo.real_name || '',
-							avatarUrl: avatarUrl,
-							badge: matchmakerInfo.type_name || '',
-							rating: matchmakerInfo.rating || 5.0,
-							level: currentLevel,
-							levelName: matchmakerInfo.level_name || levelNames[currentLevel],
-							nextLevelName: nextLevelName,
-							points: matchmakerInfo.points || 0,
-							currentLevelPoints: matchmakerInfo.current_level_points || 0,
-							nextLevelPoints: matchmakerInfo.next_level_points || 100,
-							pointsToNextLevel: matchmakerInfo.points_to_next_level || 0,
-							levelProgress: matchmakerInfo.level_progress || 0
-						}
-						console.log('更新后的profile数据:', this.profile)
-					} else {
-						console.log('matchmakerInfo为空,无法更新profile数据')
-					}
-				} catch (error) {
-					console.error('加载个人资料失败:', error)
-					uni.showToast({
-						title: '加载失败,请稍后重试: ' + error.message,
-						icon: 'none'
-					})
-				}
-			},
-			// 返回上一页
-			goBack() {
-				uni.navigateBack()
-			},
-			// 检查今日是否已签到
-			async checkSignInStatus() {
-				try {
-					const userInfo = uni.getStorageSync('userInfo')
-					const userId = userInfo && userInfo.userId ? userInfo.userId : null
-					if (userId) {
-						const res = await api.matchmaker.checkinStatus(userId)
-						console.log('检查签到状态返回结果:', res)
-						let signed = false
-						if (typeof res === 'boolean') {
-							signed = res
-						} else if (typeof res === 'string') {
-							signed = res === 'true'
-						} else if (typeof res === 'number') {
-							signed = res === 1
-						} else if (res && typeof res === 'object') {
-							if (typeof res.todaySigned === 'boolean') signed = res.todaySigned
-							else if (typeof res.signed === 'boolean') signed = res.signed
-							else if (typeof res.isSignedToday === 'boolean') signed = res.isSignedToday
-							else if (typeof res.data === 'object' && res.data) {
-								if (typeof res.data.todaySigned === 'boolean') signed = res.data.todaySigned
-								else if (typeof res.data.signed === 'boolean') signed = res.data.signed
-								else if (typeof res.data.isSignedToday === 'boolean') signed = res.data.isSignedToday
-							}
-						}
-						this.isSignedToday = !!signed
-						const stats = await api.matchmaker.checkinStats(userId)
-						console.log('获取签到统计返回结果:', stats)
-						if (stats) {
-							this.continuousDays = stats.continuousDays || 0
-							this.totalDays = stats.totalDays || 0
-						}
-					} else {
-						this.isSignedToday = false
-						this.continuousDays = 0
-						this.totalDays = 0
-						console.warn('没有有效的userId,无法检查签到状态')
-					}
-				} catch (error) {
-					console.error('检查签到状态失败:', error)
-					this.isSignedToday = false
-					this.continuousDays = 0
-					this.totalDays = 0
-				}
-			},
-			// 显示签到弹框
-			showSignInPopup() {
-				this.$refs.signInPopup.open()
-			},
-			// 关闭签到弹框
-			closeSignInPopup() {
-				this.$refs.signInPopup.close()
-			},
-			// 执行签到
-			async doSignIn() {
-				try {
-					const userInfo = uni.getStorageSync('userInfo')
-					const userId = userInfo && userInfo.userId ? userInfo.userId : null
-					if (userId) {
-						const res = await api.matchmaker.doCheckin(userId)
-						console.log('签到API返回结果:', res)
-						let success = false
-						let alreadySigned = false
-						if (typeof res === 'boolean') {
-							success = res
-						} else if (typeof res === 'string') {
-							success = res === 'true' || res === 'ok' || res === 'success'
-						} else if (res && typeof res === 'object') {
-							if (typeof res.success === 'boolean') success = res.success
-							if (typeof res.todaySigned === 'boolean') alreadySigned = res.todaySigned
-							else if (typeof res.signed === 'boolean') alreadySigned = res.signed
-							else if (typeof res.isSignedToday === 'boolean') alreadySigned = res.isSignedToday
-							if (res.data && typeof res.data === 'object') {
-								if (typeof res.data.success === 'boolean') success = res.data.success
-								if (typeof res.data.todaySigned === 'boolean') alreadySigned = res.data.todaySigned
-								else if (typeof res.data.signed === 'boolean') alreadySigned = res.data.signed
-								else if (typeof res.data.isSignedToday === 'boolean') alreadySigned = res.data.isSignedToday
-							}
-						}
-						if (success) {
-							uni.showToast({
-								title: '签到成功',
-								icon: 'success'
-							})
-							this.isSignedToday = true
-							this.generateCalendar()
-							this.loadProfileData()
-							this.closeSignInPopup()
-						} else if (alreadySigned) {
-							uni.showToast({
-								title: '今日已签到',
-								icon: 'none'
-							})
-						} else {
-							uni.showToast({
-								title: '签到失败,请稍后重试',
-								icon: 'none'
-							})
-						}
-					} else {
-						uni.showToast({
-							title: '请先登录',
-							icon: 'none'
-						})
-						setTimeout(() => {
-							uni.navigateTo({
-								url: '/pages/page3/page3'
-							})
-						}, 1000)
-					}
-				} catch (error) {
-					console.error('签到失败:', error)
-					uni.showToast({
-						title: error.msg || '签到失败,请稍后重试',
-						icon: 'none'
-					})
-				}
-			},
-			// 签到(旧方法,保持兼容)
-			handleSignIn() {
-				this.showSignInPopup()
-			},
-			// 我的资源
-			handleMyResources() {
-				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/my-resources'
-				})
-			},
-			// 活动中心
-			handleActivityCenter() {
-				uni.navigateTo({
-					url: '/pages/activities/list'
-				})
-			},
-			// 积分商城
-			handlePointsMall() {
-				console.log('积分商城')
-				uni.showToast({
-					title: '积分商城开发中',
-					icon: 'none'
-				})
-			},
-			// 编辑资料
-			handleEditProfile() {
-				console.log('编辑资料')
-				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/edit-profile'
-				})
-			},
-			// 账户设置
-			handleAccountSettings() {
-				console.log('账户设置')
-				uni.showToast({
-					title: '账户设置开发中',
-					icon: 'none'
-				})
-			},
-			// 退出登录
-			handleLogout() {
-				uni.showModal({
-					title: '退出登录',
-					content: '确定要退出登录吗?',
-					success: (res) => {
-						if (res.confirm) {
-							uni.removeStorageSync('token')
-							uni.removeStorageSync('userInfo')
-							uni.navigateTo({
-								url: '/pages/page3/page3'
-							})
-						}
-					}
-				})
-			},
-			// 导航到工作台
-			navigateToWorkbench() {
-				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/index'
-				})
-			},
-			// 导航到我的资源
-			navigateToMyResources() {
-				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/my-resources'
-				})
-			},
-			// 导航到排行榜
-			navigateToRanking() {
-				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/ranking'
-				})
-			},
-			// 导航到消息
-			navigateToMessage() {
-				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/message'
-				})
-			},
-			// 导航到我的
-			navigateToMine() {
-				// 已在我的页面,无需跳转
-			}
-		}
-	}
+import api from '../../utils/api.js'
+export default {
+  data() {
+    return {
+      profile: {
+        realName: '',
+        avatarUrl: '',
+        badge: '',
+        rating: 5.0,
+        level: '',
+        levelName: '',
+        nextLevelName: '',
+        points: 0,
+        currentLevelPoints: 0,
+        nextLevelPoints: 100,
+        pointsToNextLevel: 0,
+        levelProgress: 0
+      },
+      // 签到相关
+      isSignedToday: false,
+      continuousDays: 0,
+      totalDays: 0,
+      currentDate: new Date(),
+      calendarTitle: '',
+      calendarDays: [],
+      signedDays: [] // 存储本月已签到的日期
+    }
+  },
+  onLoad() {
+    this.loadProfileData()
+    this.checkSignInStatus()
+    this.generateCalendar()
+  },
+  onShow() {
+    // 从编辑资料页返回时,自动刷新个人资料
+    this.loadProfileData()
+  },
+  computed: {
+    unreadCount() {
+      return this.$store.getters.getTotalUnread || 0
+    }
+  },
+  methods: {
+    // 生成日历数据
+    generateCalendar() {
+      const date = this.currentDate || new Date()
+      const year = date.getFullYear()
+      const month = date.getMonth()
+      // 设置日历标题
+      this.calendarTitle = `${year}年·${month + 1}月`
+      // 获取当月第一天
+      const firstDay = new Date(year, month, 1)
+      // 获取当月第一天是星期几(0-6,0表示周日)
+      const firstDayWeek = firstDay.getDay()
+      // 获取当月最后一天
+      const lastDay = new Date(year, month + 1, 0)
+      // 获取当月最后一天的日期
+      const lastDayDate = lastDay.getDate()
+      // 获取上个月最后一天的日期
+      const prevMonthLastDay = new Date(year, month, 0).getDate()
+      const days = []
+      // 添加上个月的日期
+      for (let i = firstDayWeek - 1; i >= 0; i--) {
+        days.push({
+          date: prevMonthLastDay - i,
+          isCurrentMonth: false,
+          isToday: false,
+          isChecked: false
+        })
+      }
+      // 添加当月的日期
+      const today = new Date()
+      const isCurrentMonth = today.getFullYear() === year && today.getMonth() === month
+      for (let i = 1; i <= lastDayDate; i++) {
+        const isToday = isCurrentMonth && today.getDate() === i
+        // 检查该日期是否已签到
+        const isChecked = isToday && this.isSignedToday || this.signedDays.includes(i)
+        days.push({
+          date: i,
+          isCurrentMonth: true,
+          isToday: isToday,
+          isChecked: isChecked
+        })
+      }
+      // 添加下个月的日期,补满6行
+      const totalDays = 42 // 6行7列
+      const nextMonthDays = totalDays - days.length
+      for (let i = 1; i <= nextMonthDays; i++) {
+        days.push({
+          date: i,
+          isCurrentMonth: false,
+          isToday: false,
+          isChecked: false
+        })
+      }
+      this.calendarDays = days
+    },
+    // 加载个人资料数据
+    async loadProfileData() {
+      try {
+        const userInfo = uni.getStorageSync('userInfo')
+        console.log('从本地存储获取到的用户信息:', userInfo)
+        // 模拟用户信息,用于测试
+        const testUserId = 19
+        const userId = userInfo && userInfo.userId ? userInfo.userId : testUserId
+        console.log('使用的userId:', userId)
+        // 调用API获取红娘信息
+        console.log('开始调用API获取红娘信息...')
+        const matchmakerInfo = await api.matchmaker.getByUserId(userId)
+        console.log('API调用结果:', matchmakerInfo)
+        if (matchmakerInfo) {
+          const levelNames = ['', '青铜', '白银', '黄金', '铂金', '钻石']
+          const currentLevel = matchmakerInfo.level || 1
+          const nextLevel = currentLevel < 5 ? currentLevel + 1 : 5
+          const nextLevelName = levelNames[nextLevel]
+          const defaultAvatars = {
+            male: 'http://115.190.125.125:9000/dynamic-comments/dynamics/5c645152-9940-41d3-83a9-69ee6e0c0aaa.png',
+            female: 'http://115.190.125.125:9000/dynamic-comments/dynamics/c7fb04d7-ee4d-4b3d-bcef-f246da9c841f.png'
+          }
+          const avatarUrl = matchmakerInfo.avatarUrl || matchmakerInfo.avatar_url || (matchmakerInfo.gender === 1 ? defaultAvatars.male : defaultAvatars.female)
+          console.log('获取到的红娘信息:', matchmakerInfo)
+          this.profile = {
+            realName: matchmakerInfo.real_name || '',
+            avatarUrl: avatarUrl,
+            badge: matchmakerInfo.type_name || '',
+            rating: matchmakerInfo.rating || 5.0,
+            level: currentLevel,
+            levelName: matchmakerInfo.level_name || levelNames[currentLevel],
+            nextLevelName: nextLevelName,
+            points: matchmakerInfo.points || 0,
+            currentLevelPoints: matchmakerInfo.current_level_points || 0,
+            nextLevelPoints: matchmakerInfo.next_level_points || 100,
+            pointsToNextLevel: matchmakerInfo.points_to_next_level || 0,
+            levelProgress: matchmakerInfo.level_progress || 0
+          }
+          console.log('更新后的profile数据:', this.profile)
+        } else {
+          console.log('matchmakerInfo为空,无法更新profile数据')
+        }
+      } catch (error) {
+        console.error('加载个人资料失败:', error)
+        uni.showToast({
+          title: '加载失败,请稍后重试: ' + error.message,
+          icon: 'none'
+        })
+      }
+    },
+    // 返回上一页
+    goBack() {
+      uni.navigateBack()
+    },
+    // 检查今日是否已签到
+    async checkSignInStatus() {
+      try {
+        const userInfo = uni.getStorageSync('userInfo')
+        if (!userInfo || !userInfo.userId) {
+          this.isSignedToday = false
+          this.continuousDays = 0
+          this.totalDays = 0
+          this.signedDays = []
+          console.warn('没有有效的userId,无法检查签到状态')
+          return
+        }
+        
+        // 首先获取红娘信息,以获取makerId
+        const matchmakerInfo = await api.matchmaker.getByUserId(userInfo.userId)
+        if (!matchmakerInfo || !matchmakerInfo.matchmakerId && !matchmakerInfo.matchmaker_id) {
+          this.isSignedToday = false
+          this.continuousDays = 0
+          this.totalDays = 0
+          this.signedDays = []
+          console.warn('没有有效的makerId,无法检查签到状态')
+          return
+        }
+        
+        const makerId = matchmakerInfo.matchmakerId || matchmakerInfo.matchmaker_id
+        
+        // 检查今日是否已签到
+        const res = await api.matchmaker.checkinStatus(makerId)
+        console.log('检查签到状态返回结果:', res)
+        let signed = false
+        if (typeof res === 'boolean') {
+          signed = res
+        } else if (typeof res === 'string') {
+          signed = res === 'true'
+        } else if (typeof res === 'number') {
+          signed = res === 1
+        } else if (res && typeof res === 'object') {
+          if (typeof res.todaySigned === 'boolean') signed = res.todaySigned
+          else if (typeof res.signed === 'boolean') signed = res.signed
+          else if (typeof res.isSignedToday === 'boolean') signed = res.isSignedToday
+          else if (typeof res.data === 'object' && res.data) {
+            if (typeof res.data.todaySigned === 'boolean') signed = res.data.todaySigned
+            else if (typeof res.data.signed === 'boolean') signed = res.data.signed
+            else if (typeof res.data.isSignedToday === 'boolean') signed = res.data.isSignedToday
+          }
+        }
+        this.isSignedToday = !!signed
+        
+        // 获取签到统计
+        const stats = await api.matchmaker.checkinStats(makerId)
+        console.log('获取签到统计返回结果:', stats)
+        
+        // 初始化签到记录数组
+        this.signedDays = []
+        
+        if (stats) {
+          this.continuousDays = stats.continuousDays || 0
+          this.totalDays = stats.totalDays || 0
+          
+          // 检查stats中是否包含本月的签到记录
+          let signedDays = []
+          
+          // 情况1: stats直接包含signedDays数组
+          if (stats.signedDays && Array.isArray(stats.signedDays)) {
+            signedDays = stats.signedDays
+          }
+          // 情况2: stats.data包含signedDays数组
+          else if (stats.data && stats.data.signedDays && Array.isArray(stats.data.signedDays)) {
+            signedDays = stats.data.signedDays
+          }
+          // 情况3: stats是对象,直接包含日期属性
+          else if (typeof stats === 'object') {
+            // 处理stats对象
+            const processStats = (obj) => {
+              const days = []
+              // 遍历对象,寻找日期相关的属性
+              for (const key in obj) {
+                if (obj.hasOwnProperty(key)) {
+                  // 检查是否是日期相关的属性
+                  const value = obj[key]
+                  // 检查是否是日期格式
+                  if (typeof value === 'object' && value !== null && typeof value.isChecked === 'boolean') {
+                    // 如果是包含isChecked属性的对象,且isChecked为true
+                    if (value.isChecked && typeof value.date === 'number') {
+                      days.push(value.date)
+                    }
+                  }
+                }
+              }
+              return days
+            }
+            
+            // 先处理stats对象本身
+            signedDays = processStats(stats)
+            
+            // 如果stats.data存在,也处理一下
+            if (stats.data && typeof stats.data === 'object') {
+              signedDays = [...signedDays, ...processStats(stats.data)]
+            }
+          }
+          
+          // 检查签到状态响应中是否包含签到记录
+          if (signedDays.length === 0 && res && typeof res === 'object') {
+            const processRes = (obj) => {
+              const days = []
+              if (obj.signedDays && Array.isArray(obj.signedDays)) {
+                days.push(...obj.signedDays)
+              }
+              return days
+            }
+            
+            signedDays = processRes(res)
+            
+            if (res.data && typeof res.data === 'object') {
+              signedDays = [...signedDays, ...processRes(res.data)]
+            }
+          }
+          
+          // 检查今日是否已签到,如果已签到但不在signedDays中,添加进去
+          const today = new Date().getDate()
+          if (this.isSignedToday && !signedDays.includes(today)) {
+            signedDays.push(today)
+          }
+          
+          // 去重并排序
+          this.signedDays = [...new Set(signedDays)].sort((a, b) => a - b)
+        }
+        
+        console.log('最终的signedDays:', this.signedDays)
+      } catch (error) {
+        console.error('检查签到状态失败:', error)
+        this.isSignedToday = false
+        this.continuousDays = 0
+        this.totalDays = 0
+        this.signedDays = []
+      }
+    },
+    // 显示签到弹框
+    async showSignInPopup() {
+      // 显示弹窗前刷新签到状态和记录
+      await this.checkSignInStatus()
+      this.generateCalendar()
+      this.$refs.signInPopup.open()
+    },
+    // 关闭签到弹框
+    closeSignInPopup() {
+      this.$refs.signInPopup.close()
+    },
+    // 执行签到
+    async doSignIn() {
+      try {
+        const userInfo = uni.getStorageSync('userInfo')
+        if (!userInfo || !userInfo.userId) {
+          uni.showToast({
+            title: '请先登录',
+            icon: 'none'
+          })
+          setTimeout(() => {
+            uni.navigateTo({
+              url: '/pages/page3/page3'
+            })
+          }, 1000)
+          return
+        }
+        
+        // 首先获取红娘信息,以获取makerId
+        const matchmakerInfo = await api.matchmaker.getByUserId(userInfo.userId)
+        if (!matchmakerInfo || !matchmakerInfo.matchmakerId && !matchmakerInfo.matchmaker_id) {
+          uni.showToast({
+            title: '没有有效的makerId,无法签到',
+            icon: 'none'
+          })
+          return
+        }
+        
+        const makerId = matchmakerInfo.matchmakerId || matchmakerInfo.matchmaker_id
+        
+        // 执行签到
+        const res = await api.matchmaker.doCheckin(makerId)
+        console.log('签到API返回结果:', res)
+        let success = false
+        let alreadySigned = false
+        if (typeof res === 'boolean') {
+          success = res
+        } else if (typeof res === 'string') {
+          success = res === 'true' || res === 'ok' || res === 'success'
+        } else if (res && typeof res === 'object') {
+          if (typeof res.success === 'boolean') success = res.success
+          if (typeof res.todaySigned === 'boolean') alreadySigned = res.todaySigned
+          else if (typeof res.signed === 'boolean') alreadySigned = res.signed
+          else if (typeof res.isSignedToday === 'boolean') alreadySigned = res.isSignedToday
+          if (res.data && typeof res.data === 'object') {
+            if (typeof res.data.success === 'boolean') success = res.data.success
+            if (typeof res.data.todaySigned === 'boolean') alreadySigned = res.data.todaySigned
+            else if (typeof res.data.signed === 'boolean') alreadySigned = res.data.signed
+            else if (typeof res.data.isSignedToday === 'boolean') alreadySigned = res.data.isSignedToday
+          }
+        }
+        
+        if (success) {
+          uni.showToast({
+            title: '签到成功',
+            icon: 'success'
+          })
+          this.isSignedToday = true
+          // 更新签到记录
+          const today = new Date()
+          const todayDate = today.getDate()
+          if (!this.signedDays.includes(todayDate)) {
+            this.signedDays.push(todayDate)
+            this.signedDays.sort((a, b) => a - b)
+          }
+          this.generateCalendar()
+          this.loadProfileData()
+          this.closeSignInPopup()
+        } else if (alreadySigned) {
+          uni.showToast({
+            title: '今日已签到',
+            icon: 'none'
+          })
+        } else {
+          uni.showToast({
+            title: '签到失败,请稍后重试',
+            icon: 'none'
+          })
+        }
+      } catch (error) {
+        console.error('签到失败:', error)
+        uni.showToast({
+          title: error.msg || '签到失败,请稍后重试',
+          icon: 'none'
+        })
+      }
+    },
+    // 签到(旧方法,保持兼容)
+    handleSignIn() {
+      this.showSignInPopup()
+    },
+    // 我的资源
+    handleMyResources() {
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/my-resources'
+      })
+    },
+    // 我的活动
+    handleActivityCenter() {
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/my-activities'
+      })
+    },
+    // 积分商城
+    handlePointsMall() {
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/points-mall'
+      })
+    },
+    // 编辑资料
+    handleEditProfile() {
+      console.log('编辑资料')
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/edit-profile'
+      })
+    },
+    // 关于我们
+    handleAccountSettings() {
+      uni.navigateTo({
+        url: '/pages/settings/about'
+      })
+    },
+    // 退出登录
+    handleLogout() {
+      uni.showModal({
+        title: '退出登录',
+        content: '确定要退出登录吗?',
+        success: (res) => {
+          if (res.confirm) {
+            uni.removeStorageSync('token')
+            uni.removeStorageSync('userInfo')
+            uni.navigateTo({
+              url: '/pages/page3/page3'
+            })
+          }
+        }
+      })
+    },
+    // 导航到工作台
+    navigateToWorkbench() {
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/index'
+      })
+    },
+    // 导航到我的资源
+    navigateToMyResources() {
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/my-resources'
+      })
+    },
+    // 导航到排行榜
+    navigateToRanking() {
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/ranking'
+      })
+    },
+    // 导航到消息
+    navigateToMessage() {
+      uni.navigateTo({
+        url: '/pages/matchmaker-workbench/message'
+      })
+    },
+    // 导航到我的
+    navigateToMine() {
+      // 已在我的页面,无需跳转
+    }
+  }
+}
 </script>
 
 <style lang="scss" scoped>
-	.matchmaker-mine {
-		min-height: 100vh;
-		background: #FFF9F9;
-		display: flex;
-		flex-direction: column;
-	}
-
-	/* 顶部导航栏 */
-	.header {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		padding: 25rpx 30rpx;
-		padding-top: calc(25rpx + env(safe-area-inset-top));
-		background: #FFF9F9;
-		border-bottom: 1rpx solid #F0F0F0;
-
-		.back-btn {
-			width: 70rpx;
-			height: 70rpx;
-			display: flex;
-			align-items: center;
-			justify-content: center;
-			background: rgba(240, 240, 240, 0.5);
-			border-radius: 50%;
-			background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23333"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
-			background-size: 40rpx 40rpx;
-			background-repeat: no-repeat;
-			background-position: center;
-		}
-
-		.header-title {
-			font-size: 38rpx;
-			font-weight: bold;
-			color: #333;
-		}
-
-		.placeholder {
-			width: 70rpx;
-		}
-	}
-
-	.content {
-		flex: 1;
-		padding: 0;
-	}
-
-	.content-container {
-		padding: 20rpx 30rpx 120rpx;
-	}
-
-	/* 个人信息卡片 */
-	.profile-card {
-		background: linear-gradient(135deg, #FFEBEE 0%, #FFCDD2 100%);
-		border-radius: 25rpx;
-		padding: 35rpx;
-		margin-bottom: 25rpx;
-		box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
-	}
-
-	.profile-header {
-		display: flex;
-		align-items: center;
-		margin-bottom: 30rpx;
-
-		.profile-avatar {
-					width: 160rpx;
-					height: 160rpx;
-					border-radius: 50%;
-					background-size: cover;
-					background-repeat: no-repeat;
-					background-position: center;
-					margin-right: 25rpx;
-					border: 2rpx solid #FFF;
-					box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
-				}
-
-		.profile-info {
-			flex: 1;
-
-			.profile-name {
-				display: block;
-				font-size: 36rpx;
-				font-weight: bold;
-				color: #333;
-				margin-bottom: 10rpx;
-			}
-
-			.profile-badge {
-				display: inline-block;
-				background: #FFD700;
-				color: #FFFFFF;
-				font-size: 24rpx;
-				font-weight: bold;
-				padding: 6rpx 16rpx;
-				border-radius: 15rpx;
-				margin-bottom: 10rpx;
-			}
-
-			.profile-rating {
-				display: block;
-				font-size: 28rpx;
-				color: #666;
-			}
-		}
-	}
-
-	/* 等级和积分 */
-	.level-points {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		margin-bottom: 30rpx;
-
-		.level-info {
-			display: flex;
-			align-items: center;
-			gap: 10rpx;
-
-			.level-icon {
-				width: 44rpx;
-				height: 44rpx;
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23FFD700"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>');
-				background-size: contain;
-				background-repeat: no-repeat;
-				background-position: center;
-			}
-
-			.level-name {
-				font-size: 32rpx;
-				font-weight: bold;
-				color: #FF9800;
-			}
-		}
-
-		.points-value {
-			font-size: 48rpx;
-			font-weight: bold;
-			color: #E91E63;
-		}
-
-		.points-label {
-			font-size: 28rpx;
-			color: #666;
-		}
-	}
-
-	/* 等级进度 */
-	.level-progress {
-		.progress-label {
-			display: block;
-			font-size: 26rpx;
-			color: #333;
-			margin-bottom: 15rpx;
-		}
-
-		.progress-bar {
-			width: 100%;
-			height: 12rpx;
-			background: rgba(255, 255, 255, 0.5);
-			border-radius: 6rpx;
-			margin-bottom: 10rpx;
-
-			.progress-fill {
-				height: 100%;
-				background: #FFB74D;
-				border-radius: 6rpx;
-			}
-		}
-
-		.progress-values {
-			display: flex;
-			justify-content: space-between;
-			margin-bottom: 15rpx;
-
-			.current-value,
-			.target-value {
-				font-size: 24rpx;
-				color: #666;
-			}
-		}
-
-		.upgrade-tip {
-			display: block;
-			background: rgba(255, 255, 255, 0.3);
-			color: #E91E63;
-			font-size: 26rpx;
-			font-weight: bold;
-			padding: 15rpx;
-			border-radius: 15rpx;
-			text-align: center;
-		}
-	}
-
-	/* 功能菜单 */
-	.function-grid {
-		display: grid;
-		grid-template-columns: repeat(4, 1fr);
-		gap: 25rpx;
-		padding: 35rpx;
-		background: #FFFFFF;
-		border-radius: 20rpx;
-		margin-bottom: 20rpx;
-		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-
-		.function-item {
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			gap: 15rpx;
-
-			.function-icon {
-				width: 90rpx;
-				height: 90rpx;
-				border-radius: 50%;
-				background-size: 50rpx 50rpx;
-				background-repeat: no-repeat;
-				background-position: center;
-
-				&.calendar {
-					background-color: #FFF3E0;
-					background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23FF9800"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>');
-					display: flex;
-					align-items: center;
-					justify-content: center;
-					font-size: 32rpx;
-					font-weight: bold;
-					color: #FF9800;
-				}
-
-				&.resources {
-					background-color: #E8F5E9;
-					background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%234CAF50"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
-				}
-
-				&.heart-book {
-					background-color: #FFEBEE;
-					background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23F44336"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>');
-				}
-
-				&.money {
-					background-color: #F3E5F5;
-					background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg>');
-				}
-			}
-
-			.function-text {
-				font-size: 26rpx;
-				color: #333;
-			}
-		}
-	}
-
-	/* 设置选项 */
-	.settings-section {
-		background: #FFFFFF;
-		border-radius: 20rpx;
-		padding: 0;
-		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-
-		.settings-item {
-			display: flex;
-			align-items: center;
-			justify-content: space-between;
-			padding: 25rpx 35rpx;
-			margin-bottom: 0;
-			background: #FFFFFF;
-			border-radius: 20rpx;
-
-			&:last-child {
-				margin-bottom: 0;
-			}
-
-			.settings-icon {
-				width: 44rpx;
-				height: 44rpx;
-				background-size: contain;
-				background-repeat: no-repeat;
-				background-position: center;
-				margin-right: 20rpx;
-			}
-
-			.settings-icon.user {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
-			}
-
-			.settings-icon.gear {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>');
-			}
-
-			.settings-icon.logout {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>');
-			}
-
-			.settings-text {
-				flex: 1;
-				font-size: 30rpx;
-				color: #333;
-			}
-
-			.arrow-right {
-				width: 24rpx;
-				height: 24rpx;
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>');
-				background-size: contain;
-				background-repeat: no-repeat;
-				background-position: center;
-			}
-		}
-	}
-	
-	/* 底部导航 */
-	.tabbar {
-		position: fixed;
-		bottom: 0;
-		left: 0;
-		right: 0;
-		height: 100rpx;
-		background: #FFFFFF;
-		border-top: 1rpx solid #F0F0F0;
-		display: flex;
-		justify-content: space-around;
-		align-items: center;
-		padding-bottom: env(safe-area-inset-bottom);
-
-		.tabbar-item {
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			gap: 8rpx;
-			padding: 10rpx 0;
-
-			.tabbar-icon {
-				width: 44rpx;
-				height: 44rpx;
-				background-size: contain;
-				background-repeat: no-repeat;
-				background-position: center;
-				position: relative;
-
-				.badge {
-					position: absolute;
-					top: -8rpx;
-					right: -8rpx;
-					background: #FF4444;
-					color: #FFFFFF;
-					font-size: 20rpx;
-					font-weight: bold;
-					width: 32rpx;
-					height: 32rpx;
-					display: flex;
-					align-items: center;
-					justify-content: center;
-					border-radius: 16rpx;
-				}
-			}
-
-			.tabbar-text {
-				font-size: 20rpx;
-				color: #999;
-			}
-
-			&.active {
-				.tabbar-text {
-					color: #9C27B0;
-					font-weight: bold;
-				}
-			}
-
-			&.home .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
-			}
-
-			&.active.home .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
-			}
-
-			&.resources .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
-			}
-
-			&.active.resources .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
-			}
-
-			&.trophy .tabbar-icon {
-				background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
-			}
-
-			&.active.trophy .tabbar-icon {
-				background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
-			}
-
-			&.message .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
-			}
-
-			&.active.message .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
-			}
-
-			&.mine .tabbar-icon {
-				background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
-			}
-			&.mine.active .tabbar-icon {
-				background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
-			}
-		}
-	}
-
-	/* 签到弹框样式 */
-	.sign-in-popup {
-		width: 600rpx;
-		background: #FFFFFF;
-		border-radius: 20rpx;
-		overflow: hidden;
-
-		.popup-header {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			padding: 30rpx;
-			border-bottom: 1rpx solid #F0F0F0;
-
-			.popup-title {
-				font-size: 32rpx;
-				font-weight: bold;
-				color: #333;
-			}
-
-			.close-btn {
-				width: 40rpx;
-				height: 40rpx;
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
-				background-size: contain;
-				background-repeat: no-repeat;
-				background-position: center;
-			}
-		}
-
-		.popup-content {
-			padding: 30rpx;
-
-			.calendar-container {
-				background: #F8F9FA;
-				border-radius: 15rpx;
-				padding: 20rpx;
-				margin-bottom: 30rpx;
-
-				.calendar-header {
-					display: flex;
-					justify-content: space-between;
-					align-items: center;
-					margin-bottom: 20rpx;
-
-					.calendar-title {
-						font-size: 28rpx;
-						font-weight: bold;
-						color: #333;
-					}
-
-					.calendar-icon {
-						width: 40rpx;
-						height: 40rpx;
-						background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M3 17h18v-2H3v2zm0 4h18v-2H3v2zM3 3v2h18V3H3zm0 6h18V7H3v2z"/></svg>');
-						background-size: contain;
-						background-repeat: no-repeat;
-						background-position: center;
-					}
-				}
-
-				.calendar-week {
-					display: grid;
-					grid-template-columns: repeat(7, 1fr);
-					gap: 10rpx;
-					margin-bottom: 15rpx;
-
-					.week-day {
-						text-align: center;
-						font-size: 24rpx;
-						color: #999;
-						font-weight: bold;
-					}
-				}
-
-				.calendar-days {
-					display: grid;
-					grid-template-columns: repeat(7, 1fr);
-					gap: 10rpx;
-
-					.calendar-day {
-						text-align: center;
-						font-size: 26rpx;
-						color: #333;
-						background: #FFFFFF;
-						border-radius: 50%;
-						height: 60rpx;
-						display: flex;
-						align-items: center;
-						justify-content: center;
-
-						&.other-month {
-							color: #CCCCCC;
-						}
-
-						&.today {
-							background: #FFF3E0;
-							color: #FF9800;
-							font-weight: bold;
-						}
-
-						&.checked {
-							background: #E8F5E9;
-							color: #4CAF50;
-							font-weight: bold;
-						}
-					}
-				}
-			}
-
-			.sign-in-reward {
-				display: flex;
-				flex-direction: column;
-				align-items: center;
-				gap: 10rpx;
-
-				.reward-title {
-					font-size: 28rpx;
-					color: #666;
-				}
-
-				.reward-points {
-					font-size: 48rpx;
-					font-weight: bold;
-					color: #E91E63;
-				}
-			}
-		}
-
-		.popup-footer {
-				padding: 0 30rpx 30rpx;
-
-				.sign-in-btn {
-					width: 100%;
-					height: 80rpx;
-					background: #9C27B0;
-					color: #FFFFFF;
-					font-size: 32rpx;
-					border-radius: 40rpx;
-					border: none;
-					font-weight: bold;
-
-					&:hover {
-						background: #7B1FA2;
-					}
-
-					&.signed {
-						background: #E0E0E0;
-						color: #9E9E9E;
-						cursor: not-allowed;
-
-						&:hover {
-							background: #E0E0E0;
-						}
-					}
-				}
-			}
-	}
+.matchmaker-mine {
+  min-height: 100vh;
+  background: #FFF9F9;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 顶部导航栏 */
+.header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 25rpx 30rpx;
+  padding-top: calc(25rpx + env(safe-area-inset-top));
+  background: #FFF9F9;
+  border-bottom: 1rpx solid #F0F0F0;
+
+  .back-btn {
+    width: 70rpx;
+    height: 70rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(240, 240, 240, 0.5);
+    border-radius: 50%;
+    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23333"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
+    background-size: 40rpx 40rpx;
+    background-repeat: no-repeat;
+    background-position: center;
+  }
+
+  .header-title {
+    font-size: 38rpx;
+    font-weight: bold;
+    color: #333;
+  }
+
+  .placeholder {
+    width: 70rpx;
+  }
+}
+
+.content {
+  flex: 1;
+  padding: 0;
+}
+
+.content-container {
+  padding: 20rpx 30rpx 120rpx;
+}
+
+/* 个人信息卡片 */
+.profile-card {
+  background: linear-gradient(135deg, #FFEBEE 0%, #FFCDD2 100%);
+  border-radius: 25rpx;
+  padding: 35rpx;
+  margin-bottom: 25rpx;
+  box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
+}
+
+.profile-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 30rpx;
+
+  .profile-avatar {
+    width: 160rpx;
+    height: 160rpx;
+    border-radius: 50%;
+    background-size: cover;
+    background-repeat: no-repeat;
+    background-position: center;
+    margin-right: 25rpx;
+    border: 2rpx solid #FFF;
+    box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+  }
+
+  .profile-info {
+    flex: 1;
+
+    .profile-name {
+      display: block;
+      font-size: 36rpx;
+      font-weight: bold;
+      color: #333;
+      margin-bottom: 10rpx;
+    }
+
+    .profile-badge {
+      display: inline-block;
+      background: #FFD700;
+      color: #FFFFFF;
+      font-size: 24rpx;
+      font-weight: bold;
+      padding: 6rpx 16rpx;
+      border-radius: 15rpx;
+      margin-bottom: 10rpx;
+    }
+
+    .profile-rating {
+      display: block;
+      font-size: 28rpx;
+      color: #666;
+    }
+  }
+}
+
+/* 等级和积分 */
+.level-points {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 30rpx;
+
+  .level-info {
+    display: flex;
+    align-items: center;
+    gap: 10rpx;
+
+    .level-icon {
+      width: 44rpx;
+      height: 44rpx;
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23FFD700"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>');
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+    }
+
+    .level-name {
+      font-size: 32rpx;
+      font-weight: bold;
+      color: #FF9800;
+    }
+  }
+
+  .points-value {
+    font-size: 48rpx;
+    font-weight: bold;
+    color: #E91E63;
+  }
+
+  .points-label {
+    font-size: 28rpx;
+    color: #666;
+  }
+}
+
+/* 等级进度 */
+.level-progress {
+  .progress-label {
+    display: block;
+    font-size: 26rpx;
+    color: #333;
+    margin-bottom: 15rpx;
+  }
+
+  .progress-bar {
+    width: 100%;
+    height: 12rpx;
+    background: rgba(255, 255, 255, 0.5);
+    border-radius: 6rpx;
+    margin-bottom: 10rpx;
+
+    .progress-fill {
+      height: 100%;
+      background: #FFB74D;
+      border-radius: 6rpx;
+    }
+  }
+
+  .progress-values {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 15rpx;
+
+    .current-value,
+    .target-value {
+      font-size: 24rpx;
+      color: #666;
+    }
+  }
+
+  .upgrade-tip {
+    display: block;
+    background: rgba(255, 255, 255, 0.3);
+    color: #E91E63;
+    font-size: 26rpx;
+    font-weight: bold;
+    padding: 15rpx;
+    border-radius: 15rpx;
+    text-align: center;
+  }
+}
+
+/* 功能菜单 */
+.function-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 25rpx;
+  padding: 35rpx;
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+  .function-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 15rpx;
+
+    .function-icon {
+      width: 90rpx;
+      height: 90rpx;
+      border-radius: 50%;
+      background-size: 50rpx 50rpx;
+      background-repeat: no-repeat;
+      background-position: center;
+
+      &.calendar {
+        background-color: #FFF3E0;
+        background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23FF9800"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>');
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 32rpx;
+        font-weight: bold;
+        color: #FF9800;
+      }
+
+      &.resources {
+        background-color: #E8F5E9;
+        background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%234CAF50"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
+      }
+
+      &.heart-book {
+        background-color: #FFEBEE;
+        background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23F44336"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>');
+      }
+
+      &.money {
+        background-color: #F3E5F5;
+        background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg>');
+      }
+    }
+
+    .function-text {
+      font-size: 26rpx;
+      color: #333;
+    }
+  }
+}
+
+/* 设置选项 */
+.settings-section {
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 0;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+  .settings-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 25rpx 35rpx;
+    margin-bottom: 0;
+    background: #FFFFFF;
+    border-radius: 20rpx;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    .settings-icon {
+      width: 44rpx;
+      height: 44rpx;
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+      margin-right: 20rpx;
+    }
+
+    .settings-icon.user {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
+    }
+
+    .settings-icon.gear {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>');
+    }
+
+    .settings-icon.logout {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>');
+    }
+    
+    .settings-icon.info {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>');
+    }
+
+    .settings-text {
+      flex: 1;
+      font-size: 30rpx;
+      color: #333;
+    }
+
+    .arrow-right {
+      width: 24rpx;
+      height: 24rpx;
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>');
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+    }
+  }
+}
+
+/* 底部导航 */
+.tabbar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 100rpx;
+  background: #FFFFFF;
+  border-top: 1rpx solid #F0F0F0;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  padding-bottom: env(safe-area-inset-bottom);
+
+  .tabbar-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 8rpx;
+    padding: 10rpx 0;
+
+    .tabbar-icon {
+      width: 44rpx;
+      height: 44rpx;
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+      position: relative;
+
+      .badge {
+        position: absolute;
+        top: -8rpx;
+        right: -8rpx;
+        background: #FF4444;
+        color: #FFFFFF;
+        font-size: 20rpx;
+        font-weight: bold;
+        width: 32rpx;
+        height: 32rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-radius: 16rpx;
+      }
+    }
+
+    .tabbar-text {
+      font-size: 20rpx;
+      color: #999;
+    }
+
+    &.active {
+      .tabbar-text {
+        color: #9C27B0;
+        font-weight: bold;
+      }
+    }
+
+    &.home .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
+    }
+
+    &.active.home .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
+    }
+
+    &.resources .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
+    }
+
+    &.active.resources .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
+    }
+
+    &.trophy .tabbar-icon {
+      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
+    }
+
+    &.active.trophy .tabbar-icon {
+      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
+    }
+
+    &.message .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
+    }
+
+    &.active.message .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
+    }
+
+    &.mine .tabbar-icon {
+      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
+    }
+    &.mine.active .tabbar-icon {
+      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
+    }
+  }
+}
+
+/* 签到弹框样式 */
+.sign-in-popup {
+  width: 600rpx;
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  overflow: hidden;
+
+  .popup-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 30rpx;
+    background: #FFEBEE;
+    border-bottom: none;
+
+    .popup-title {
+      font-size: 36rpx;
+      font-weight: bold;
+      color: #E91E63;
+    }
+
+    .close-btn {
+      width: 40rpx;
+      height: 40rpx;
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23E91E63"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+    }
+  }
+
+  .popup-content {
+    padding: 20rpx 30rpx 30rpx;
+
+    /* 签到统计 */
+    .checkin-stats {
+      display: flex;
+      justify-content: space-around;
+      margin-bottom: 30rpx;
+      padding: 20rpx;
+      background: #FFF3E0;
+      border-radius: 15rpx;
+      
+      .stats-item {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 10rpx;
+        
+        .stats-label {
+          font-size: 24rpx;
+          color: #666;
+        }
+        
+        .stats-value {
+          font-size: 48rpx;
+          font-weight: bold;
+          color: #E91E63;
+        }
+        
+        .stats-unit {
+          font-size: 24rpx;
+          color: #666;
+        }
+      }
+    }
+    
+    .calendar-container {
+      background: #FFFFFF;
+      border-radius: 15rpx;
+      padding: 20rpx;
+      margin-bottom: 30rpx;
+      box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+      .calendar-header {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-bottom: 20rpx;
+
+        .calendar-title {
+          font-size: 28rpx;
+          font-weight: bold;
+          color: #333;
+        }
+      }
+
+      .calendar-week {
+        display: grid;
+        grid-template-columns: repeat(7, 1fr);
+        gap: 10rpx;
+        margin-bottom: 20rpx;
+
+        .week-day {
+          text-align: center;
+          font-size: 24rpx;
+          color: #999;
+          font-weight: bold;
+        }
+      }
+
+      .calendar-days {
+        display: grid;
+        grid-template-columns: repeat(7, 1fr);
+        gap: 15rpx;
+
+        .calendar-day {
+          text-align: center;
+          font-size: 28rpx;
+          color: #333;
+          background: #F5F5F5;
+          border-radius: 8rpx;
+          height: 70rpx;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          position: relative;
+
+          &.other-month {
+            color: #CCCCCC;
+            background: #F9F9F9;
+          }
+
+          &.today {
+            background: #FFF3E0;
+            color: #FF9800;
+            font-weight: bold;
+          }
+
+          &.checked {
+            background: #FFCDD2;
+            color: #E91E63;
+            font-weight: bold;
+          }
+          
+          &.today-checked {
+            background: #E91E63;
+            color: #FFFFFF;
+            font-weight: bold;
+          }
+          
+          .check-mark {
+            position: absolute;
+            bottom: 5rpx;
+            right: 5rpx;
+            font-size: 18rpx;
+            color: #FFFFFF;
+            background: #E91E63;
+            border-radius: 50%;
+            width: 24rpx;
+            height: 24rpx;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+          }
+          
+          .today-checked .check-mark {
+            background: #FFFFFF;
+            color: #E91E63;
+          }
+        }
+      }
+    }
+
+    .sign-in-reward {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 10rpx;
+
+      .reward-title {
+        font-size: 28rpx;
+        color: #666;
+      }
+
+      .reward-points {
+        font-size: 48rpx;
+        font-weight: bold;
+        color: #E91E63;
+      }
+    }
+  }
+
+  .popup-footer {
+    padding: 0 30rpx 30rpx;
+
+    .sign-in-btn {
+      width: 100%;
+      height: 80rpx;
+      background: #E91E63;
+      color: #FFFFFF;
+      font-size: 32rpx;
+      border-radius: 40rpx;
+      border: none;
+      font-weight: bold;
+      transition: all 0.3s;
+
+      &:hover {
+        background: #C2185B;
+      }
+
+      &.signed {
+        background: #E0E0E0;
+        color: #9E9E9E;
+        cursor: not-allowed;
+
+        &:hover {
+          background: #E0E0E0;
+        }
+      }
+    }
+  }
+}
 </style>

+ 314 - 0
LiangZhiYUMao/pages/matchmaker-workbench/my-activities.vue

@@ -0,0 +1,314 @@
+<template>
+  <view class="my-activities-page">
+    <!-- 自定义导航栏 -->
+    <view class="custom-navbar">
+      <view class="navbar-left" @click="goBack">
+        <text class="back-icon">←</text>
+      </view>
+      <view class="navbar-title">我的活动</view>
+      <view class="navbar-right"></view>
+    </view>
+
+    <!-- 活动列表 -->
+    <view class="activity-grid">
+      <view class="activity-card" v-for="(item, index) in activityList" :key="index"
+        @click="goToDetail(item.id)">
+        <image :src="item.cover_image" class="activity-image" mode="aspectFill"></image>
+        <view class="activity-info">
+          <view class="activity-name">{{ item.name }}</view>
+          <view class="activity-meta">
+            <text class="activity-location">📍 {{ item.location }}</text>
+          </view>
+          <view class="activity-footer">
+            <view class="activity-points">
+              <text class="points-symbol">💎</text>
+              <text class="points-value">{{ item.points }}</text>
+            </view>
+            <text class="activity-participants">{{ item.participants || 0 }}人参加</text>
+          </view>
+          <view class="activity-btn" @click.stop="goToDetail(item.id)">查看详情</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 空状态 -->
+    <view class="empty-state" v-if="activityList.length === 0 && !loading">
+      <image src="https://img.icons8.com/color/96/000000/calendar--v1.png" class="empty-icon"></image>
+      <text class="empty-text">暂无报名活动</text>
+      <text class="empty-subtext">快去报名参加更多精彩活动吧</text>
+    </view>
+
+    <!-- 加载更多 -->
+    <view class="load-more" v-if="hasMore">
+      <text class="load-text">{{ loading ? '加载中...' : '上拉加载更多' }}</text>
+    </view>
+    <view class="no-more" v-else-if="activityList.length > 0">
+      <text class="no-more-text">没有更多了</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import api from '../../utils/api.js'
+export default {
+  data() {
+    return {
+      activityList: [],
+      pageNum: 1,
+      pageSize: 10,
+      hasMore: true,
+      loading: false,
+      // 模拟数据,当API返回为空时使用
+      mockActivities: [
+        {
+          id: 1,
+          name: '姻缘一线牵·大型相亲会',
+          location: '雄安新区',
+          points: 299,
+          participants: 18,
+          cover_image: 'https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60'
+        },
+        {
+          id: 2,
+          name: '一期一会·红娘心得分享',
+          location: '北京',
+          points: 389,
+          participants: 54,
+          cover_image: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60'
+        }
+      ]
+    }
+  },
+
+  onLoad() {
+    this.loadMyActivityList()
+  },
+
+  onReachBottom() {
+    if (this.hasMore && !this.loading) {
+      this.pageNum++
+      this.loadMyActivityList()
+    }
+  },
+
+  methods: {
+    // 加载我的活动列表
+    async loadMyActivityList() {
+      if (this.loading) return
+      this.loading = true
+
+      try {
+        const userInfo = uni.getStorageSync('userInfo')
+        const testUserId = 19
+        const userId = userInfo && userInfo.userId ? userInfo.userId : testUserId
+        
+        const data = await api.activity.getMyActivities({
+          pageNum: this.pageNum,
+          pageSize: this.pageSize,
+          userId: userId
+        })
+
+        if (data && data.length > 0) {
+          this.activityList = [...this.activityList, ...data]
+          this.hasMore = data.length >= this.pageSize
+        } else {
+          // 如果API返回为空,使用模拟数据
+          this.hasMore = false
+          if (this.pageNum === 1) {
+            this.activityList = this.mockActivities
+          }
+        }
+      } catch (error) {
+        console.error('加载我的活动列表失败:', error)
+        // 当API调用失败时,使用模拟数据
+        if (this.pageNum === 1) {
+          this.activityList = this.mockActivities
+        }
+        this.hasMore = false
+      } finally {
+        this.loading = false
+      }
+    },
+
+    // 跳转到详情
+    goToDetail(id) {
+      uni.navigateTo({
+        url: `/pages/activities/detail?id=${id}`
+      })
+    },
+
+    // 返回
+    goBack() {
+      uni.navigateBack()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .my-activities-page {
+    min-height: 100vh;
+    background-color: #FFF9F9;
+    padding-top: 90rpx;
+  }
+
+  /* 自定义导航栏 */
+  .custom-navbar {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 90rpx;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 20rpx;
+    background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD0 100%);
+    z-index: 999;
+
+    .navbar-left,
+    .navbar-right {
+      width: 80rpx;
+    }
+
+    .back-icon {
+      font-size: 40rpx;
+      color: #333333;
+      font-weight: bold;
+    }
+
+    .navbar-title {
+      flex: 1;
+      text-align: center;
+      font-size: 32rpx;
+      font-weight: bold;
+      color: #333333;
+    }
+  }
+
+  /* 活动列表 - 两列布局 */
+  .activity-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 20rpx;
+    padding: 20rpx;
+
+    .activity-card {
+      position: relative;
+      background-color: #FFFFFF;
+      border-radius: 20rpx;
+      overflow: hidden;
+      box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
+
+      .activity-image {
+        width: 100%;
+        height: 240rpx;
+        background-color: #F5F5F5;
+      }
+
+      .activity-info {
+        padding: 20rpx;
+
+        .activity-name {
+          font-size: 28rpx;
+          font-weight: bold;
+          color: #333333;
+          margin-bottom: 10rpx;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 2;
+          overflow: hidden;
+        }
+
+        .activity-meta {
+          margin-bottom: 15rpx;
+
+          .activity-location {
+            font-size: 24rpx;
+            color: #666666;
+          }
+        }
+
+        .activity-footer {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          margin-bottom: 15rpx;
+
+          .activity-points {
+            display: flex;
+            align-items: center;
+
+            .points-symbol {
+              font-size: 24rpx;
+              margin-right: 5rpx;
+            }
+
+            .points-value {
+              font-size: 26rpx;
+              color: #FF6B8A;
+              font-weight: bold;
+            }
+          }
+
+          .activity-participants {
+            font-size: 22rpx;
+            color: #999999;
+          }
+        }
+
+        .activity-btn {
+          width: 100%;
+          height: 60rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          background-color: #9C27B0;
+          color: #FFFFFF;
+          border-radius: 30rpx;
+          font-size: 26rpx;
+        }
+      }
+    }
+  }
+
+  /* 空状态 */
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 100rpx 0;
+
+    .empty-icon {
+      width: 120rpx;
+      height: 120rpx;
+      opacity: 0.5;
+      margin-bottom: 20rpx;
+    }
+
+    .empty-text {
+      font-size: 32rpx;
+      color: #666666;
+      margin-bottom: 10rpx;
+    }
+
+    .empty-subtext {
+      font-size: 26rpx;
+      color: #999999;
+    }
+  }
+
+  /* 加载更多 */
+  .load-more,
+  .no-more {
+    padding: 30rpx 0;
+    text-align: center;
+
+    .load-text,
+    .no-more-text {
+      font-size: 24rpx;
+      color: #999999;
+    }
+  }
+</style>

+ 23 - 1
LiangZhiYUMao/pages/matchmaker-workbench/points-detail.vue

@@ -70,13 +70,35 @@ export default {
   onLoad() {
     this.initData()
   },
+  onShow() {
+    // 每次显示页面时刷新积分数据,确保数据同步
+    if (this.makerId) {
+      this.pageNum = 1
+      this.loadRecords()
+    }
+  },
   methods: {
     async initData() {
       const userInfo = uni.getStorageSync('userInfo')
       if (userInfo && userInfo.matchmakerId) {
         this.makerId = userInfo.matchmakerId
       } else if (userInfo && userInfo.userId) {
-        this.makerId = userInfo.userId
+        // 如果没有matchmakerId,通过API获取
+        try {
+          const res = await api.matchmaker.getByUserId(userInfo.userId)
+          let matchmaker = res
+          if (res && res.data) {
+            matchmaker = res.data
+          }
+          if (matchmaker && (matchmaker.matchmakerId || matchmaker.matchmaker_id)) {
+            this.makerId = matchmaker.matchmakerId || matchmaker.matchmaker_id
+            // 保存到userInfo中
+            userInfo.matchmakerId = this.makerId
+            uni.setStorageSync('userInfo', userInfo)
+          }
+        } catch (e) {
+          console.error('获取红娘信息失败:', e)
+        }
       }
       
       await this.loadRecords()

+ 16 - 1
LiangZhiYUMao/pages/matchmaker-workbench/points-mall.vue

@@ -122,7 +122,22 @@ export default {
       if (userInfo && userInfo.matchmakerId) {
         this.makerId = userInfo.matchmakerId
       } else if (userInfo && userInfo.userId) {
-        this.makerId = userInfo.userId
+        // 如果没有matchmakerId,通过API获取
+        try {
+          const res = await api.matchmaker.getByUserId(userInfo.userId)
+          let matchmaker = res
+          if (res && res.data) {
+            matchmaker = res.data
+          }
+          if (matchmaker && (matchmaker.matchmakerId || matchmaker.matchmaker_id)) {
+            this.makerId = matchmaker.matchmakerId || matchmaker.matchmaker_id
+            // 保存到userInfo中
+            userInfo.matchmakerId = this.makerId
+            uni.setStorageSync('userInfo', userInfo)
+          }
+        } catch (e) {
+          console.error('获取红娘信息失败:', e)
+        }
       }
       
       await Promise.all([

+ 40 - 38
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
     }
@@ -103,7 +92,22 @@ export default {
       if (userInfo && userInfo.matchmakerId) {
         this.makerId = userInfo.matchmakerId
       } else if (userInfo && userInfo.userId) {
-        this.makerId = userInfo.userId
+        // 如果没有matchmakerId,通过API获取
+        try {
+          const res = await api.matchmaker.getByUserId(userInfo.userId)
+          let matchmaker = res
+          if (res && res.data) {
+            matchmaker = res.data
+          }
+          if (matchmaker && (matchmaker.matchmakerId || matchmaker.matchmaker_id)) {
+            this.makerId = matchmaker.matchmakerId || matchmaker.matchmaker_id
+            // 保存到userInfo中
+            userInfo.matchmakerId = this.makerId
+            uni.setStorageSync('userInfo', userInfo)
+          }
+        } catch (e) {
+          console.error('获取红娘信息失败:', e)
+        }
       }
       
       await Promise.all([
@@ -143,8 +147,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 +171,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 +317,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 +334,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: {
@@ -123,6 +132,11 @@ export default {
 			}
 		},
 		onLoad() {
+			// 获取当前用户ID
+			const userInfo = uni.getStorageSync('userInfo')
+			if (userInfo && userInfo.userId) {
+				this.userId = userInfo.userId
+			}
 			// 加载排行榜数据
 			this.loadRankingData()
 		},
@@ -131,17 +145,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({
@@ -152,14 +170,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() {
@@ -360,10 +430,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);
 				}
 			}
 		}
@@ -431,6 +501,11 @@ export default {
 				border-radius: 25rpx;
 				font-size: 26rpx;
 				font-weight: bold;
+				
+				&.liked {
+					background: #CCCCCC;
+					color: #999999;
+				}
 			}
 		}
 

+ 112 - 26
LiangZhiYUMao/pages/matchmaker-workbench/sign-in.vue

@@ -90,14 +90,17 @@
 </template>
 
 <script>
+import api from '@/utils/api.js'
+
 export default {
 	data() {
 		return {
-			currentYear: 2025,
-			currentMonth: 11,
+			currentYear: new Date().getFullYear(),
+			currentMonth: new Date().getMonth() + 1,
 			totalSignDays: 0,
 			availablePoints: 0,
 			consecutiveDays: 0,
+			makerId: null,
 			isSigned: false,
 			calendarDays: [],
 			signedDates: [] // 已签到的日期
@@ -111,22 +114,80 @@ export default {
 		}
 	},
 	onLoad() {
-		this.loadSignInData()
-		this.generateCalendar()
+		this.initData()
+	},
+	onShow() {
+		// 每次显示页面时刷新数据
+		if (this.makerId) {
+			this.loadSignInData()
+		}
 	},
 	methods: {
 		goBack() {
 			uni.navigateBack()
 		},
+		async initData() {
+			// 获取红娘ID
+			const userInfo = uni.getStorageSync('userInfo')
+			if (userInfo && userInfo.matchmakerId) {
+				this.makerId = userInfo.matchmakerId
+			} else if (userInfo && userInfo.userId) {
+				// 如果没有matchmakerId,通过API获取
+				try {
+					const res = await api.matchmaker.getByUserId(userInfo.userId)
+					let matchmaker = res
+					if (res && res.data) {
+						matchmaker = res.data
+					}
+					if (matchmaker && (matchmaker.matchmakerId || matchmaker.matchmaker_id)) {
+						this.makerId = matchmaker.matchmakerId || matchmaker.matchmaker_id
+						userInfo.matchmakerId = this.makerId
+						uni.setStorageSync('userInfo', userInfo)
+					}
+				} catch (e) {
+					console.error('获取红娘信息失败:', e)
+				}
+			}
+			
+			await this.loadSignInData()
+			this.generateCalendar()
+		},
 		async loadSignInData() {
-			// 模拟加载签到数据
-			this.totalSignDays = 0
-			this.availablePoints = 0
-			this.consecutiveDays = 0
-			this.isSigned = false
+			if (!this.makerId) return
 			
-			// 模拟已签到日期
-			this.signedDates = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 30]
+			try {
+				// 获取签到统计数据
+				const statsRes = await api.matchmaker.checkinStats(this.makerId)
+				let stats = statsRes
+				if (statsRes && statsRes.data) {
+					stats = statsRes.data
+				}
+				
+				this.totalSignDays = stats.totalDays || 0
+				this.consecutiveDays = stats.continuousDays || 0
+				
+				// 获取积分余额
+				const balanceRes = await api.pointsMall.getBalance(this.makerId)
+				this.availablePoints = balanceRes.balance || 0
+				
+				// 检查今日签到状态
+				const statusRes = await api.matchmaker.checkinStatus(this.makerId)
+				let statusData = statusRes
+				if (statusRes && statusRes.data !== undefined) {
+					statusData = statusRes.data
+				}
+				this.isSigned = statusData === true || statusData?.isCheckedIn === true || statusData?.checked === true
+				
+				// 如果今日已签到,添加到已签到日期
+				if (this.isSigned) {
+					const today = new Date().getDate()
+					if (!this.signedDates.includes(today)) {
+						this.signedDates.push(today)
+					}
+				}
+			} catch (e) {
+				console.error('加载签到数据失败:', e)
+			}
 		},
 		generateCalendar() {
 			const year = this.currentYear
@@ -177,7 +238,7 @@ export default {
 			
 			this.calendarDays = days
 		},
-		handleSignIn() {
+		async handleSignIn() {
 			if (this.isSigned) {
 				uni.showToast({
 					title: '今日已签到',
@@ -186,21 +247,46 @@ export default {
 				return
 			}
 			
-			// 执行签到
-			this.isSigned = true
-			this.totalSignDays++
-			this.availablePoints += 10
-			this.consecutiveDays++
-			
-			// 更新今天的签到状态
-			const today = new Date().getDate()
-			this.signedDates.push(today)
-			this.generateCalendar()
+			if (!this.makerId) {
+				uni.showToast({ title: '请先登录', icon: 'none' })
+				return
+			}
 			
-			uni.showToast({
-				title: '签到成功 +10积分',
-				icon: 'success'
-			})
+			try {
+				// 调用后端签到接口
+				await api.matchmaker.doCheckin(this.makerId)
+				
+				// 签到成功后更新状态
+				this.isSigned = true
+				this.totalSignDays++
+				this.consecutiveDays++
+				
+				// 更新今天的签到状态
+				const today = new Date().getDate()
+				if (!this.signedDates.includes(today)) {
+					this.signedDates.push(today)
+				}
+				this.generateCalendar()
+				
+				// 刷新积分余额
+				const balanceRes = await api.pointsMall.getBalance(this.makerId)
+				this.availablePoints = balanceRes.balance || 0
+				
+				uni.showToast({
+					title: '签到成功 +5积分',
+					icon: 'success'
+				})
+			} catch (e) {
+				console.error('签到失败:', e)
+				// 如果是已签到的错误,更新状态
+				if (e.message && e.message.includes('已签到')) {
+					this.isSigned = true
+				}
+				uni.showToast({
+					title: e.message || '签到失败',
+					icon: 'none'
+				})
+			}
 		}
 	}
 }

+ 55 - 2
LiangZhiYUMao/utils/api.js

@@ -206,6 +206,13 @@ export default {
     cancelRegister: (activityId) => request({ 
       url: `/activity/cancel/${activityId}`, 
       method: 'POST' 
+    }),
+    
+    // 获取我的活动列表
+    getMyActivities: (params) => request({ 
+      url: '/activity/my',
+      method: 'GET',
+      data: params 
     })
   },
 
@@ -244,7 +251,7 @@ export default {
       method: 'GET'
     }),
     
-    // 购买课程
+    // 购买课程(旧接口-模拟)
     purchase: (courseId, data) => request({ 
       url: `/course/purchase/${courseId}`, 
       method: 'POST', 
@@ -252,6 +259,28 @@ export default {
     })
   },
 
+  // 课程订单相关(微信支付)
+  courseOrder: {
+    // 购买课程(获取微信支付参数)
+    purchase: (data) => request({
+      url: '/course-order/purchase',
+      method: 'POST',
+      data
+    }),
+    
+    // 检查是否已购买课程
+    checkPurchased: (userId, courseId) => request({
+      url: `/course-order/check?userId=${userId}&courseId=${courseId}`,
+      method: 'GET'
+    }),
+    
+    // 获取已购买的课程列表
+    getPurchasedCourses: (userId) => request({
+      url: `/course-order/purchased?userId=${userId}`,
+      method: 'GET'
+    })
+  },
+
   // 红娘相关
     matchmaker: {
         // 获取红娘列表
@@ -300,13 +329,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}` 
@@ -326,6 +374,11 @@ export default {
             url: `/matchmaker/update/${matchmakerId}`,
             method: 'PUT',
             data
+        }),
+        
+        // 获取本月签到记录
+        checkinList: (makerId, year, month) => request({
+            url: `/matchmaker/checkin/list?makerId=${makerId}&year=${year}&month=${month}`
         })
   },
 

+ 23 - 4
gateway/src/main/resources/application.yml

@@ -133,7 +133,7 @@ spring:
         - id: essential-route
           uri: http://localhost:1005
           predicates:
-            - Path=/api/user/**
+            - Path=/api/user/**, /api/checkin/**
           filters:
             - StripPrefix=0
         
@@ -161,13 +161,32 @@ spring:
           filters:
             - StripPrefix=1
         
-        # 课程服务路由(admin服务)
+        # 课程服务路由(homePage服务,端口8081)
+        # 注意:课程Controller在homePage服务中,路径为 /api/course/**
         - id: course-route
-          uri: http://localhost:8088
+          uri: http://localhost:8081
           predicates:
             - Path=/api/course/**
           filters:
-            - StripPrefix=1
+            - StripPrefix=0
+
+        # 推荐服务路由(Recommend服务,端口8089)
+        # 注意:必须在homepage-route之前,避免被兜底路由覆盖
+        - id: recommend-route
+          uri: http://localhost:8089
+          predicates:
+            - Path=/api/recommend/**
+          filters:
+            - StripPrefix=0
+            - StripPrefix=0
+
+        # 课程订单服务路由(Essential服务-微信支付)
+        - id: course-order-route
+          uri: http://localhost:1005
+          predicates:
+            - Path=/api/course-order/**
+          filters:
+            - StripPrefix=0
 
         # 首页服务路由(兜底路由)
         - id: homepage-route

+ 10 - 0
marriageAdmin-vue/src/config/api.js

@@ -13,6 +13,16 @@ export const API_ENDPOINTS = {
   LOGOUT: '/admin/auth/logout',
   GET_USER_INFO: '/admin/auth/userInfo',
   
+  // 管理员管理
+  ADMIN_USER_LIST: '/admin/admin-user/list',
+  ADMIN_USER_DETAIL: '/admin/admin-user/detail',
+  ADMIN_USER_REGISTER: '/admin/admin-user/register',
+  ADMIN_USER_UPDATE: '/admin/admin-user/update',
+  ADMIN_USER_DELETE: '/admin/admin-user/delete',
+  ADMIN_USER_DISABLE: '/admin/admin-user/disable',
+  ADMIN_USER_ENABLE: '/admin/admin-user/enable',
+  ADMIN_USER_ROLES: '/admin/admin-user/roles',
+  
   // 轮播图管理
   BANNER_LIST: '/admin/banner/list',
   BANNER_DETAIL: '/admin/banner/detail',

+ 12 - 2
marriageAdmin-vue/src/layouts/MainLayout.vue

@@ -19,7 +19,8 @@
         text-color="#cbd5e1"
         active-text-color="#ffffff"
       >
-        <el-menu-item index="/dashboard">
+        <!-- 数据面板 - 仅超级管理员 -->
+        <el-menu-item v-if="isSuperAdmin" index="/dashboard">
           <el-icon><DataBoard /></el-icon>
           <template #title>数据面板</template>
         </el-menu-item>
@@ -34,6 +35,12 @@
           <template #title>小喇叭公告</template>
         </el-menu-item>
         
+        <!-- 管理员管理 - 仅超级管理员 -->
+        <el-menu-item v-if="isSuperAdmin" index="/admin-user">
+          <el-icon><User /></el-icon>
+          <template #title>管理员管理</template>
+        </el-menu-item>
+        
         <el-sub-menu index="/activity">
           <template #title>
             <el-icon><Calendar /></el-icon>
@@ -51,6 +58,7 @@
           <el-menu-item index="/matchmaker/list">红娘列表</el-menu-item>
           <el-menu-item index="/matchmaker/audit">红娘审核</el-menu-item>
           <el-menu-item index="/matchmaker/create">添加红娘</el-menu-item>
+          <el-menu-item index="/matchmaker/points-product">积分商品</el-menu-item>
         </el-sub-menu>
         
         <el-sub-menu index="/course">
@@ -80,7 +88,8 @@
           <el-menu-item index="/user/vip">VIP用户</el-menu-item>
         </el-sub-menu>
         
-        <el-menu-item index="/vip-package">
+        <!-- VIP套餐 - 仅超级管理员 -->
+        <el-menu-item v-if="isSuperAdmin" index="/vip-package">
           <el-icon><Medal /></el-icon>
           <template #title>VIP套餐</template>
         </el-menu-item>
@@ -170,6 +179,7 @@ const isCollapse = ref(false)
 const userInfo = computed(() => userStore.userInfo)
 const currentRoute = computed(() => route)
 const activeMenu = computed(() => route.path)
+const isSuperAdmin = computed(() => userStore.isSuperAdmin)
 
 // 切换侧边栏折叠状态
 const toggleCollapse = () => {

+ 13 - 0
marriageAdmin-vue/src/router/index.js

@@ -39,6 +39,13 @@ const router = createRouter({
           component: () => import('@/views/announcement/AnnouncementList.vue'),
           meta: { title: '小喇叭公告', icon: 'Microphone' }
         },
+        // 管理员管理
+        {
+          path: 'admin-user',
+          name: 'AdminUser',
+          component: () => import('@/views/admin/AdminUserList.vue'),
+          meta: { title: '管理员管理', icon: 'User' }
+        },
         // 活动管理
         {
           path: 'activity',
@@ -102,6 +109,12 @@ const router = createRouter({
               name: 'MatchmakerEdit',
               component: () => import('@/views/matchmaker/MatchmakerForm.vue'),
               meta: { title: '编辑红娘' }
+            },
+            {
+              path: 'points-product',
+              name: 'PointsProduct',
+              component: () => import('@/views/points-product/PointsProductList.vue'),
+              meta: { title: '积分商品' }
             }
           ]
         },

+ 12 - 1
marriageAdmin-vue/src/stores/user.js

@@ -10,7 +10,8 @@ export const useUserStore = defineStore('user', () => {
   // 状态
   const token = ref(localStorage.getItem('admin_token') || '')
   const userInfo = ref(JSON.parse(localStorage.getItem('admin_user') || 'null'))
-  const permissions = ref([])
+  const permissions = ref(JSON.parse(localStorage.getItem('admin_permissions') || '[]'))
+  const isSuperAdmin = ref(localStorage.getItem('admin_isSuperAdmin') === 'true')
   
   // 登录
   const login = async (username, password) => {
@@ -24,10 +25,13 @@ export const useUserStore = defineStore('user', () => {
         token.value = response.data.token
         userInfo.value = response.data.userInfo
         permissions.value = response.data.permissions || []
+        isSuperAdmin.value = response.data.isSuperAdmin || false
         
         // 保存到 localStorage
         localStorage.setItem('admin_token', response.data.token)
         localStorage.setItem('admin_user', JSON.stringify(response.data.userInfo))
+        localStorage.setItem('admin_permissions', JSON.stringify(response.data.permissions || []))
+        localStorage.setItem('admin_isSuperAdmin', response.data.isSuperAdmin ? 'true' : 'false')
         
         return true
       }
@@ -48,8 +52,11 @@ export const useUserStore = defineStore('user', () => {
       token.value = ''
       userInfo.value = null
       permissions.value = []
+      isSuperAdmin.value = false
       localStorage.removeItem('admin_token')
       localStorage.removeItem('admin_user')
+      localStorage.removeItem('admin_permissions')
+      localStorage.removeItem('admin_isSuperAdmin')
     }
   }
   
@@ -60,7 +67,10 @@ export const useUserStore = defineStore('user', () => {
       if (response.code === 200) {
         userInfo.value = response.data.userInfo
         permissions.value = response.data.permissions || []
+        isSuperAdmin.value = response.data.isSuperAdmin || false
         localStorage.setItem('admin_user', JSON.stringify(response.data.userInfo))
+        localStorage.setItem('admin_permissions', JSON.stringify(response.data.permissions || []))
+        localStorage.setItem('admin_isSuperAdmin', response.data.isSuperAdmin ? 'true' : 'false')
         return true
       }
       return false
@@ -79,6 +89,7 @@ export const useUserStore = defineStore('user', () => {
     token,
     userInfo,
     permissions,
+    isSuperAdmin,
     login,
     logout,
     getUserInfo,

+ 15 - 1
marriageAdmin-vue/src/views/banner/BannerList.vue

@@ -299,7 +299,21 @@ const loadBannerList = async () => {
     if (response.code === 200) {
       // MyBatis-Plus的Page对象使用records字段
       bannerList.value = response.data.records || []
-      total.value = response.data.total || 0
+      // 确保正确读取total字段
+      // MyBatis-Plus的Page对象序列化后total字段可能是数字或字符串
+      const totalValue = response.data.total
+      if (totalValue !== undefined && totalValue !== null) {
+        total.value = Number(totalValue) || 0
+      } else {
+        // 如果total字段不存在,但有records数据,可能是后端分页插件未配置
+        // 对于分页数据,应该从后端获取正确的total,这里先使用records.length作为临时方案
+        // 注意:这只适用于当前页数据等于总数据的情况(即所有数据都在第一页)
+        if (bannerList.value.length > 0 && currentPage.value === 1 && pageSize.value >= bannerList.value.length) {
+          total.value = bannerList.value.length
+        } else {
+          total.value = 0
+        }
+      }
     }
   } catch (error) {
     console.error('加载轮播图列表失败:', error)

+ 3 - 2
marriageAdmin-vue/src/views/course/CourseList.vue

@@ -146,8 +146,9 @@ const loadList = async () => {
       params: { page: currentPage.value, pageSize: pageSize.value }
     })
     if (response.code === 200) {
-      list.value = response.data.list || response.data || []
-      total.value = response.data.total || list.value.length
+      // 后端返回格式: {list: [], total: xx}
+      list.value = response.data.list || []
+      total.value = response.data.total || 0
     }
   } catch (error) {
     console.error('加载课程列表失败:', error)

+ 40 - 4
marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue

@@ -163,14 +163,50 @@ const loadList = async () => {
       params.keyword = filters.keyword.trim()
     }
     
+    console.log('📤 请求参数:', params)
     const response = await request.post(API_ENDPOINTS.MATCHMAKER_LIST, params)
+    console.log('📥 响应数据:', response)
+    
     if (response.code === 200) {
-      list.value = response.data.records || response.data.list || response.data || []
-      total.value = response.data.total || list.value.length
+      // MyBatis-Plus的Page对象使用records字段
+      const pageData = response.data
+      console.log('📄 分页数据:', pageData)
+      
+      // 读取列表数据
+      list.value = pageData?.records || pageData?.list || (Array.isArray(pageData) ? pageData : []) || []
+      
+      // 读取总数 - 优先从total字段读取,支持多种可能的字段名
+      let totalValue = pageData?.total
+      if (totalValue === undefined || totalValue === null) {
+        totalValue = pageData?.totalCount || pageData?.totalElements
+      }
+      
+      console.log('📊 读取到的total值:', totalValue, '类型:', typeof totalValue)
+      
+      if (totalValue !== undefined && totalValue !== null) {
+        // 转换为数字,即使是字符串"0"也要正确处理
+        const numTotal = Number(totalValue)
+        total.value = isNaN(numTotal) ? 0 : numTotal
+      } else {
+        // 如果total字段不存在,尝试从其他字段获取
+        // 如果当前是第一页且返回的数据量等于pageSize,可能是所有数据都在第一页
+        // 但这种情况无法准确判断总数,所以设置为0
+        console.warn('⚠️ 未找到total字段,使用0作为默认值')
+        total.value = 0
+      }
+      
+      console.log('✅ 最终结果 - 列表数量:', list.value.length, '总数:', total.value)
+    } else {
+      console.error('❌ 请求失败:', response.message || response.msg)
+      ElMessage.error(response.message || response.msg || '加载失败')
+      list.value = []
+      total.value = 0
     }
   } catch (error) {
-    console.error('加载失败:', error)
-    ElMessage.error('加载失败')
+    console.error('❌ 加载失败:', error)
+    ElMessage.error('加载失败: ' + (error.message || '未知错误'))
+    list.value = []
+    total.value = 0
   } finally {
     loading.value = false
   }

+ 181 - 34
marriageAdmin-vue/src/views/report/ReportList.vue

@@ -40,7 +40,25 @@
         <el-table-column prop="description" label="描述" min-width="220" show-overflow-tooltip />
         <el-table-column label="截图" width="120">
           <template #default="{ row }">
-            <el-image v-if="parseScreens(row.screenshots).length" :src="formatMedia(parseScreens(row.screenshots)[0])" :preview-src-list="parseScreens(row.screenshots).map(formatMedia)" fit="cover" style="width:64px;height:64px;border-radius:6px;" />
+            <template v-if="getScreenshotUrls(row).length">
+              <el-image 
+                :src="getScreenshotUrls(row)[0]" 
+                :preview-src-list="getScreenshotUrls(row)" 
+                fit="cover" 
+                style="width:64px;height:64px;border-radius:6px;cursor:pointer;"
+                :lazy="true"
+                :hide-on-click-modal="true"
+                :data-report-id="row.reportId"
+                @error="(e) => handleImageError(e, row)"
+              >
+                <template #error>
+                  <div class="img-error" :title="`URL: ${getScreenshotUrls(row)[0]}`">
+                    <el-icon><Picture /></el-icon>
+                    <span>加载失败</span>
+                  </div>
+                </template>
+              </el-image>
+            </template>
             <div v-else class="img-placeholder">无</div>
           </template>
         </el-table-column>
@@ -80,7 +98,23 @@
       <div v-else>
         <div class="dynamic-content">{{ dynamicDialog.data?.content || '(无内容)' }}</div>
         <div class="media-list">
-          <el-image v-for="(m, idx) in dynamicDialog.medias" :key="idx" :src="formatMedia(m)" :preview-src-list="dynamicDialog.medias.map(formatMedia)" fit="cover" style="width:96px;height:96px;border-radius:6px;margin-right:8px;margin-bottom:8px;" />
+          <el-image 
+            v-for="(m, idx) in dynamicDialog.medias" 
+            :key="idx" 
+            :src="m" 
+            :preview-src-list="dynamicDialog.medias" 
+            fit="cover" 
+            style="width:96px;height:96px;border-radius:6px;margin-right:8px;margin-bottom:8px;cursor:pointer;"
+            :lazy="true"
+            @error="handleImageError"
+          >
+            <template #error>
+              <div class="img-error" style="width:96px;height:96px;">
+                <el-icon><Picture /></el-icon>
+                <span>加载失败</span>
+              </div>
+            </template>
+          </el-image>
         </div>
       </div>
     </el-dialog>
@@ -132,6 +166,7 @@
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
+import { Picture } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS, API_BASE_URL } from '@/config/api'
 
@@ -154,11 +189,42 @@ const statusType = (s) => ({0:'danger',1:'warning',2:'success',3:'info'}[s] || '
 const typeText = (t) => ({ spam:'垃圾广告', porn:'色情低俗', violence:'暴力违法', attack:'人身攻击', fake:'虚假信息', plagiarism:'抄袭侵权', other:'其他' }[t] || t || '其他')
 const typeTagType = (t) => ({ spam:'warning', porn:'danger', violence:'danger', attack:'danger', fake:'warning', plagiarism:'warning', other:'info' }[t] || 'info')
 
-const BASE = import.meta.env.DEV ? 'http://localhost:8083' : API_BASE_URL || ''
+// 获取基础URL(用于图片资源)
+const getBaseUrl = () => {
+  // 开发环境:图片资源通过8083端口访问
+  if (import.meta.env.DEV) {
+    return 'http://localhost:8083'
+  }
+  // 生产环境:使用配置的API基础URL
+  return API_BASE_URL || ''
+}
+
+// 格式化媒体URL(与DynamicDetail.vue保持一致)
 const formatMedia = (u) => {
   if (!u) return ''
-  const url = String(u).trim()
-  return /^https?:\/\//i.test(url) ? url : `${BASE}${url}`
+  
+  let url = String(u).trim()
+  if (!url) return ''
+  
+  // 去除引号
+  if ((url.startsWith('"') && url.endsWith('"')) || (url.startsWith("'") && url.endsWith("'"))) {
+    url = url.substring(1, url.length - 1)
+  }
+  
+  // 如果已经是完整的URL,直接返回
+  if (url.startsWith('http')) {
+    return url
+  }
+  
+  // 获取BASE URL并拼接
+  const baseUrl = getBaseUrl()
+  
+  // 确保url以/开头
+  if (!url.startsWith('/')) {
+    url = '/' + url
+  }
+  
+  return `${baseUrl}${url}`
 }
 
 // 从后端临时塞入的 handleResult 前缀里解析昵称(@nick:李娜 ...)
@@ -169,18 +235,86 @@ const extractNick = (row) => {
   return `用户${row?.reporterId ?? ''}`
 }
 
+// 解析截图数据(与DynamicDetail.vue保持一致)
 const parseScreens = (raw) => {
   if (!raw) return []
+  
+  let urls = []
+  const trimmed = String(raw).trim()
+  
+  // 尝试解析JSON数组格式
   try {
-    const v = typeof raw === 'string' ? raw.trim() : raw
-    if (typeof v === 'string' && v.startsWith('[')) {
-      return JSON.parse(v)
-    }
-    if (typeof v === 'string') {
-      return v.split(',').map(s => s.trim()).filter(Boolean)
+    if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
+      const arr = JSON.parse(trimmed)
+      if (Array.isArray(arr)) {
+        urls = arr
+      }
     }
-    return Array.isArray(v) ? v : []
-  } catch { return [] }
+  } catch (e) {
+    // JSON解析失败,忽略错误,继续尝试其他格式
+  }
+  
+  // 如果不是JSON数组,尝试按逗号分隔
+  if (!urls.length) {
+    urls = trimmed.split(',').map((item) => item.trim()).filter(Boolean)
+  }
+  
+  return urls
+}
+
+// 获取格式化后的截图URL列表
+const getScreenshotUrls = (row) => {
+  if (!row?.screenshots) {
+    return []
+  }
+  
+  const screens = parseScreens(row.screenshots)
+  if (!screens || screens.length === 0) {
+    return []
+  }
+  
+  const urls = screens.map(formatMedia).filter(Boolean)
+  
+  // 详细调试日志
+  if (urls.length > 0) {
+    console.log('📸 举报ID:', row.reportId, {
+      '原始数据': row.screenshots,
+      '解析后数组': screens,
+      '格式化后URLs': urls,
+      'BASE URL': getBaseUrl()
+    })
+  } else {
+    console.warn('⚠️ 举报ID:', row.reportId, '解析后无有效URL,原始数据:', row.screenshots)
+  }
+  
+  return urls
+}
+
+// 图片加载错误处理
+const handleImageError = (e, row) => {
+  const img = e.target || e
+  const src = img?.src || img?.currentSrc || 'unknown'
+  const reportId = row?.reportId || img?.dataset?.reportId || 'unknown'
+  const urls = row ? getScreenshotUrls(row) : []
+  
+  console.error('❌ 图片加载失败:', {
+    reportId,
+    src,
+    '原始screenshots': row?.screenshots,
+    '解析后URLs': urls,
+    'BASE URL': getBaseUrl(),
+    error: e
+  })
+  
+  // 检查URL格式
+  if (src && !src.startsWith('http')) {
+    console.warn('⚠️ 图片URL格式可能不正确:', src)
+  }
+  
+  // 检查URL是否可访问
+  if (src && src.startsWith('http')) {
+    console.warn('⚠️ 图片URL格式正确但加载失败,可能是文件不存在或CORS问题:', src)
+  }
 }
 
 const loadList = async () => {
@@ -192,6 +326,17 @@ const loadList = async () => {
     if (res.code === 200) {
       list.value = res.data.list || []
       total.value = res.data.total || list.value.length
+      
+      // 调试日志:检查截图数据
+      console.log('📋 举报列表加载成功,共', list.value.length, '条')
+      list.value.forEach((row, idx) => {
+        if (row.screenshots) {
+          const urls = getScreenshotUrls(row)
+          console.log(`  [${idx + 1}] 举报ID: ${row.reportId}, 原始screenshots:`, row.screenshots, '解析后URLs:', urls)
+        }
+      })
+    } else {
+      console.error('加载举报列表失败:', res.msg || '未知错误')
     }
   } catch (e) {
     console.error('加载举报失败', e)
@@ -219,30 +364,30 @@ const openDynamic = async (row) => {
   const dynamicId = row?.dynamicId
   if (!dynamicId) {
     dynamicDialog.data = { content: '未获取到动态ID' }
-    dynamicDialog.medias = parseScreens(row?.screenshots)
+    dynamicDialog.medias = getScreenshotUrls(row)
     dynamicDialog.visible = true
     return
   }
-  dynamicDialog.visible = true
-  dynamicDialog.loading = true
-  dynamicDialog.data = null
-  dynamicDialog.medias = []
-  try {
-    const res = await request.get(`${API_ENDPOINTS.DYNAMIC_DETAIL}/${dynamicId}`)
-    if (res.code === 200 && res.data) {
-      dynamicDialog.data = res.data
-      const medias = parseMediaUrls(res.data?.mediaUrls)
-      dynamicDialog.medias = medias.length ? medias : parseScreens(row?.screenshots)
-    } else {
-      dynamicDialog.data = { content: res.msg || '动态不存在或已被删除' }
-      dynamicDialog.medias = parseScreens(row?.screenshots)
-    }
-  } catch (e) {
-    console.error('获取动态详情失败', e)
-    dynamicDialog.data = { content: e?.message ? `加载失败:${e.message}` : '加载失败' }
-    dynamicDialog.medias = parseScreens(row?.screenshots)
-  }
-  finally { dynamicDialog.loading = false }
+      dynamicDialog.visible = true
+      dynamicDialog.loading = true
+      dynamicDialog.data = null
+      dynamicDialog.medias = []
+      try {
+        const res = await request.get(`${API_ENDPOINTS.DYNAMIC_DETAIL}/${dynamicId}`)
+        if (res.code === 200 && res.data) {
+          dynamicDialog.data = res.data
+          const medias = parseMediaUrls(res.data?.mediaUrls)
+          dynamicDialog.medias = medias.length ? medias.map(formatMedia) : getScreenshotUrls(row)
+        } else {
+          dynamicDialog.data = { content: res.msg || '动态不存在或已被删除' }
+          dynamicDialog.medias = getScreenshotUrls(row)
+        }
+      } catch (e) {
+        console.error('获取动态详情失败', e)
+        dynamicDialog.data = { content: e?.message ? `加载失败:${e.message}` : '加载失败' }
+        dynamicDialog.medias = getScreenshotUrls(row)
+      }
+      finally { dynamicDialog.loading = false }
 }
 
 // 处理举报
@@ -312,6 +457,8 @@ const openUnban = (row) => {
 .table-card { margin-top: 20px; }
 .pagination-container { display: flex; justify-content: flex-end; margin-top: 20px; }
 .img-placeholder { width:64px;height:64px;border-radius:6px;background:#f5f7fa;color:#909399;display:flex;align-items:center;justify-content:center;font-size:12px; }
+.img-error { width:64px;height:64px;border-radius:6px;background:#fef0f0;color:#f56c6c;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:10px; }
+.img-error .el-icon { font-size:20px;margin-bottom:2px; }
 .dynamic-content { margin-bottom: 10px; color:#333; }
 .media-list { display:flex; flex-wrap: wrap; }
 .loading { color:#909399; }

+ 3 - 2
marriageAdmin-vue/src/views/user/UserVipList.vue

@@ -70,7 +70,8 @@ const loadList = async () => {
       params: { page: currentPage.value, pageSize: pageSize.value }
     })
     if (response.code === 200) {
-      const dataList = response.data.list || response.data || []
+      // 后端返回格式: {list: [], total: xx}
+      const dataList = response.data.list || []
       // 兼容处理:统一字段格式为驼峰
       list.value = dataList.map(item => ({
         ...item,
@@ -81,7 +82,7 @@ const loadList = async () => {
         vipEndTime: item.vipEndTime || item.vip_end_time,
         remainingDays: item.remainingDays !== undefined ? item.remainingDays : (item.remaining_days !== undefined ? item.remaining_days : 0)
       }))
-      total.value = response.data.total || list.value.length
+      total.value = response.data.total || 0
       console.log('VIP用户列表数据:', list.value)
     }
   } catch (error) {

+ 3 - 2
marriageAdmin-vue/src/views/vip/VipPackageList.vue

@@ -224,8 +224,9 @@ const loadList = async () => {
     const response = await request.get('/admin/vip/package/list', { params })
     
     if (response.code === 200) {
-      list.value = response.data.records || response.data.list || response.data || []
-      total.value = response.data.total || list.value.length
+      // 后端返回格式: Result<Page<VipPackage>>,使用records字段
+      list.value = response.data.records || []
+      total.value = response.data.total || 0
     }
   } catch (error) {
     console.error('加载失败:', error)

+ 12 - 0
service/Essential/pom.xml

@@ -31,6 +31,12 @@
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
 
+        <!-- Spring Boot Starter Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
         <!-- Spring Boot Redis -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -94,5 +100,11 @@
             <artifactId>minio</artifactId>
             <version>8.5.2</version>
         </dependency>
+        <dependency>
+            <groupId>com.zhentao</groupId>
+            <artifactId>dynamic</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 </project>

+ 92 - 0
service/Essential/src/main/java/com/zhentao/controller/CourseOrderController.java

@@ -0,0 +1,92 @@
+package com.zhentao.controller;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.zhentao.common.Result;
+import com.zhentao.dto.CoursePurchaseRequest;
+import com.zhentao.service.CourseOrderService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 课程订单控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/course-order")
+public class CourseOrderController {
+
+    @Autowired
+    private CourseOrderService courseOrderService;
+
+    /**
+     * 购买课程(获取支付参数)
+     */
+    @PostMapping("/purchase")
+    public Result<Map<String, Object>> purchaseCourse(@RequestBody CoursePurchaseRequest request) {
+        try {
+            Map<String, Object> payParams = courseOrderService.purchaseCourse(
+                    request.getUserId(),
+                    request.getCourseId(),
+                    request.getCourseName(),
+                    request.getPrice()
+            );
+            return Result.success(payParams);
+        } catch (RuntimeException e) {
+            return Result.error(e.getMessage());
+        } catch (WxPayException e) {
+            log.error("微信支付下单失败", e);
+            return Result.error("支付下单失败:" + e.getMessage());
+        } catch (Exception e) {
+            log.error("课程购买失败", e);
+            return Result.error("课程购买失败");
+        }
+    }
+
+    /**
+     * 微信支付回调
+     */
+    @PostMapping("/notify")
+    public String handlePayNotify(@RequestBody String notifyData) {
+        log.info("收到课程支付回调:{}", notifyData);
+        try {
+            return courseOrderService.handlePayNotify(notifyData);
+        } catch (WxPayException e) {
+            log.error("处理课程支付回调失败", e);
+            return "处理支付回调失败";
+        }
+    }
+
+    /**
+     * 检查用户是否已购买课程
+     */
+    @GetMapping("/check")
+    public Result<Boolean> checkPurchased(
+            @RequestParam Long userId,
+            @RequestParam Integer courseId) {
+        try {
+            boolean purchased = courseOrderService.checkPurchased(userId, courseId);
+            return Result.success(purchased);
+        } catch (Exception e) {
+            log.error("检查购买状态失败", e);
+            return Result.error("检查购买状态失败");
+        }
+    }
+
+    /**
+     * 获取用户已购买的课程ID列表
+     */
+    @GetMapping("/purchased")
+    public Result<List<Integer>> getPurchasedCourses(@RequestParam Long userId) {
+        try {
+            List<Integer> courseIds = courseOrderService.getPurchasedCourseIds(userId);
+            return Result.success(courseIds);
+        } catch (Exception e) {
+            log.error("获取已购买课程失败", e);
+            return Result.error("获取已购买课程失败");
+        }
+    }
+}

+ 30 - 0
service/Essential/src/main/java/com/zhentao/dto/CoursePurchaseRequest.java

@@ -0,0 +1,30 @@
+package com.zhentao.dto;
+
+import lombok.Data;
+
+/**
+ * 课程购买请求DTO
+ */
+@Data
+public class CoursePurchaseRequest {
+    
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    
+    /**
+     * 课程ID
+     */
+    private Integer courseId;
+    
+    /**
+     * 课程名称
+     */
+    private String courseName;
+    
+    /**
+     * 课程价格
+     */
+    private Double price;
+}

+ 81 - 0
service/Essential/src/main/java/com/zhentao/entity/CourseOrder.java

@@ -0,0 +1,81 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 课程订单实体
+ */
+@Data
+@TableName("course_orders")
+public class CourseOrder implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 订单ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+    
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    
+    /**
+     * 课程ID
+     */
+    private Integer courseId;
+    
+    /**
+     * 课程名称
+     */
+    private String courseName;
+    
+    /**
+     * 订单号
+     */
+    private String orderNo;
+    
+    /**
+     * 支付金额
+     */
+    private BigDecimal paymentAmount;
+    
+    /**
+     * 支付方式
+     */
+    private String paymentMethod;
+    
+    /**
+     * 支付时间
+     */
+    private LocalDateTime paymentTime;
+    
+    /**
+     * 订单状态:0-待支付,1-已支付,2-已取消,3-已退款
+     */
+    private Integer status;
+    
+    /**
+     * 微信支付交易号
+     */
+    private String transactionId;
+    
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+    
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+}

+ 34 - 0
service/Essential/src/main/java/com/zhentao/mapper/CourseOrderMapper.java

@@ -0,0 +1,34 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.CourseOrder;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 课程订单Mapper
+ */
+@Mapper
+public interface CourseOrderMapper extends BaseMapper<CourseOrder> {
+    
+    /**
+     * 根据订单号查询订单
+     */
+    @Select("SELECT * FROM course_orders WHERE order_no = #{orderNo}")
+    CourseOrder selectByOrderNo(@Param("orderNo") String orderNo);
+    
+    /**
+     * 查询用户已购买的课程ID列表
+     */
+    @Select("SELECT course_id FROM course_orders WHERE user_id = #{userId} AND status = 1")
+    List<Integer> selectPurchasedCourseIds(@Param("userId") Long userId);
+    
+    /**
+     * 检查用户是否已购买某课程
+     */
+    @Select("SELECT COUNT(*) FROM course_orders WHERE user_id = #{userId} AND course_id = #{courseId} AND status = 1")
+    int checkPurchased(@Param("userId") Long userId, @Param("courseId") Integer courseId);
+}

+ 48 - 0
service/Essential/src/main/java/com/zhentao/service/CourseOrderService.java

@@ -0,0 +1,48 @@
+package com.zhentao.service;
+
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 课程订单服务接口
+ */
+public interface CourseOrderService {
+    
+    /**
+     * 购买课程(创建订单并获取支付参数)
+     * 
+     * @param userId 用户ID
+     * @param courseId 课程ID
+     * @param courseName 课程名称
+     * @param price 课程价格
+     * @return 微信支付参数
+     */
+    Map<String, Object> purchaseCourse(Long userId, Integer courseId, String courseName, Double price) throws WxPayException;
+    
+    /**
+     * 处理微信支付回调
+     * 
+     * @param notifyData 回调数据
+     * @return 处理结果
+     */
+    String handlePayNotify(String notifyData) throws WxPayException;
+    
+    /**
+     * 检查用户是否已购买课程
+     * 
+     * @param userId 用户ID
+     * @param courseId 课程ID
+     * @return 是否已购买
+     */
+    boolean checkPurchased(Long userId, Integer courseId);
+    
+    /**
+     * 获取用户已购买的课程ID列表
+     * 
+     * @param userId 用户ID
+     * @return 课程ID列表
+     */
+    List<Integer> getPurchasedCourseIds(Long userId);
+}

+ 191 - 0
service/Essential/src/main/java/com/zhentao/service/impl/CourseOrderServiceImpl.java

@@ -0,0 +1,191 @@
+package com.zhentao.service.impl;
+
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import com.github.binarywang.wxpay.util.SignUtils;
+import com.zhentao.entity.CourseOrder;
+import com.zhentao.entity.Wx;
+import com.zhentao.pojo.Users;
+import com.zhentao.mapper.CourseOrderMapper;
+import com.zhentao.service.CourseOrderService;
+import com.zhentao.service.UsersService;
+import com.zhentao.service.WxService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.RandomUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 课程订单服务实现
+ */
+@Slf4j
+@Service
+public class CourseOrderServiceImpl implements CourseOrderService {
+
+    @Autowired
+    private CourseOrderMapper courseOrderMapper;
+
+    @Autowired
+    private UsersService usersService;
+
+    @Autowired
+    private WxService wxService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> purchaseCourse(Long userId, Integer courseId, String courseName, Double price) throws WxPayException {
+        // 1. 检查是否已购买
+        if (checkPurchased(userId, courseId)) {
+            throw new RuntimeException("您已购买过此课程");
+        }
+
+        // 2. 生成订单号
+        String orderNo = generateOrderNo();
+        LocalDateTime now = LocalDateTime.now();
+
+        // 3. 创建待支付订单
+        CourseOrder order = new CourseOrder();
+        order.setUserId(userId);
+        order.setCourseId(courseId);
+        order.setCourseName(courseName);
+        order.setOrderNo(orderNo);
+        order.setPaymentAmount(BigDecimal.valueOf(price));
+        order.setPaymentMethod("微信支付");
+        order.setStatus(0); // 待支付
+        order.setCreateTime(now);
+        courseOrderMapper.insert(order);
+
+        // 4. 构建微信支付请求
+        WxPayUnifiedOrderRequest payRequest = buildWxPayRequest(orderNo, courseName, price, userId);
+        WxPayUnifiedOrderResult wxPayResult = createWxPayOrder(payRequest);
+
+        // 5. 生成前端调起支付的参数
+        Map<String, Object> payParams = generatePayParams(wxPayResult, price);
+        payParams.put("orderNo", orderNo);
+        return payParams;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String handlePayNotify(String notifyData) throws WxPayException {
+        // 1. 解析回调数据并验证签名
+        WxPayConfig payConfig = getWxPayConfig();
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+
+        WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(notifyData);
+        String orderNo = notifyResult.getOutTradeNo();
+        String transactionId = notifyResult.getTransactionId();
+
+        // 2. 查询待支付订单
+        CourseOrder order = courseOrderMapper.selectByOrderNo(orderNo);
+        if (order == null || order.getStatus() != 0) {
+            log.warn("课程订单不存在或已处理:{}", orderNo);
+            return WxPayNotifyResponse.fail("订单不存在或已处理");
+        }
+
+        // 3. 验证支付金额
+        int totalFee = order.getPaymentAmount().multiply(new BigDecimal("100")).intValue();
+        if (Integer.parseInt(String.valueOf(notifyResult.getTotalFee())) != totalFee) {
+            log.warn("课程订单金额不一致:{},实际支付:{}", orderNo, notifyResult.getTotalFee());
+            return WxPayNotifyResponse.fail("金额不一致");
+        }
+
+        // 4. 更新订单状态为已支付
+        order.setStatus(1);
+        order.setPaymentTime(LocalDateTime.now());
+        order.setTransactionId(transactionId);
+        order.setUpdateTime(LocalDateTime.now());
+        courseOrderMapper.updateById(order);
+
+        log.info("课程购买成功:用户{},课程{},订单{}", order.getUserId(), order.getCourseId(), orderNo);
+        return WxPayNotifyResponse.success("处理成功");
+    }
+
+    @Override
+    public boolean checkPurchased(Long userId, Integer courseId) {
+        return courseOrderMapper.checkPurchased(userId, courseId) > 0;
+    }
+
+    @Override
+    public List<Integer> getPurchasedCourseIds(Long userId) {
+        return courseOrderMapper.selectPurchasedCourseIds(userId);
+    }
+
+    private String generateOrderNo() {
+        return "COURSE" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
+    }
+
+    private WxPayUnifiedOrderRequest buildWxPayRequest(String orderNo, String courseName, Double price, Long userId) {
+        Users user = usersService.getById(userId);
+        if (user == null || user.getWechatOpenid() == null) {
+            throw new RuntimeException("用户微信信息未绑定");
+        }
+
+        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
+        request.setOutTradeNo(orderNo);
+        request.setBody("课程购买-" + courseName);
+        request.setTotalFee(BigDecimal.valueOf(price).multiply(new BigDecimal("100")).intValue());
+        request.setSpbillCreateIp("127.0.0.1");
+        request.setNotifyUrl("https://mini.workervip.com/api/course-order/notify");
+        request.setTradeType("JSAPI");
+        request.setOpenid(user.getWechatOpenid());
+        return request;
+    }
+
+    private WxPayUnifiedOrderResult createWxPayOrder(WxPayUnifiedOrderRequest request) throws WxPayException {
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(getWxPayConfig());
+        return wxPayService.unifiedOrder(request);
+    }
+
+    private Map<String, Object> generatePayParams(WxPayUnifiedOrderResult wxPayResult, Double price) {
+        Wx wxConfig = wxService.list().get(0);
+        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
+        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
+        String prepayId = wxPayResult.getPrepayId();
+
+        Map<String, String> signParams = new HashMap<>();
+        signParams.put("appId", wxConfig.getAppId());
+        signParams.put("timeStamp", timeStamp);
+        signParams.put("nonceStr", nonceStr);
+        signParams.put("package", "prepay_id=" + prepayId);
+        signParams.put("signType", "MD5");
+
+        String paySign = SignUtils.createSign(signParams, "7f633cbabd894b4d213bc6edffe3b119");
+
+        Map<String, Object> payParams = new HashMap<>();
+        payParams.put("appId", wxConfig.getAppId());
+        payParams.put("timeStamp", timeStamp);
+        payParams.put("nonceStr", nonceStr);
+        payParams.put("package", "prepay_id=" + prepayId);
+        payParams.put("signType", "MD5");
+        payParams.put("paySign", paySign);
+        payParams.put("totalFee", BigDecimal.valueOf(price).multiply(new BigDecimal("100")).intValue());
+        return payParams;
+    }
+
+    private WxPayConfig getWxPayConfig() {
+        Wx wxConfig = wxService.list().get(0);
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(wxConfig.getAppId());
+        payConfig.setMchId(wxConfig.getMchId());
+        payConfig.setMchKey("7f633cbabd894b4d213bc6edffe3b119");
+        payConfig.setNotifyUrl("https://mini.workervip.com/api/course-order/notify");
+        return payConfig;
+    }
+}

+ 9 - 0
service/Essential/src/main/resources/application.yml

@@ -70,6 +70,9 @@ minio:
   access-key: minioadmin
   secret-key: minioadmin
   bucket-name: user-feedback
+  accessKey: minioadmin
+  secretKey: minioadmin
+  bucketName: dynamic-comments
 
 # 头像本地存储配置
 avatar:
@@ -78,6 +81,12 @@ avatar:
   url:
     prefix: http://localhost:8083/avatars/  # 访问URL前缀(通过网关)
 
+# 动态媒体配置(dynamic模块依赖)
+dynamic:
+  media:
+    upload:
+      path: D:/dynamic-media/
+
 wx:
   pay:
     appId: wx3e90d662a801266e       # 替换为你的小程序AppID

+ 10 - 0
service/admin/pom.xml

@@ -76,5 +76,15 @@
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-crypto</artifactId>
         </dependency>
+        <!-- Spring Security -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>4.4.0</version>
+        </dependency>
     </dependencies>
 </project>

+ 22 - 3
service/admin/src/main/java/com/zhentao/controller/AuthController.java

@@ -3,10 +3,12 @@ package com.zhentao.controller;
 import com.zhentao.common.Result;
 import com.zhentao.entity.AdminUser;
 import com.zhentao.service.AuthService;
+import com.zhentao.service.AdminSecurityService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -20,6 +22,9 @@ public class AuthController {
     @Autowired
     private AuthService authService;
     
+    @Autowired
+    private AdminSecurityService adminSecurityService;
+    
     /**
      * 登录
      */
@@ -39,16 +44,23 @@ public class AuthController {
                 return Result.error("用户名或密码错误");
             }
             
-            // 生成token(这里简化处理,实际项目应使用JWT)
+            // 生成token
             String token = authService.generateToken(user);
             
+            // 获取用户权限
+            List<String> permissions = adminSecurityService.getUserPermissions(user.getId());
+            List<String> roles = adminSecurityService.getUserRoles(user.getId());
+            boolean isSuperAdmin = adminSecurityService.isSuperAdmin(user.getId());
+            
             Map<String, Object> data = new HashMap<>();
             data.put("token", token);
             
             // 返回用户信息(不包含密码)
             user.setPassword(null);
             data.put("userInfo", user);
-            data.put("permissions", new String[]{"*"});
+            data.put("permissions", permissions);
+            data.put("roles", roles);
+            data.put("isSuperAdmin", isSuperAdmin);
             
             return Result.success(data);
         } catch (Exception e) {
@@ -82,10 +94,17 @@ public class AuthController {
                 return Result.error(401, "token无效");
             }
             
+            // 获取用户权限
+            List<String> permissions = adminSecurityService.getUserPermissions(user.getId());
+            List<String> roles = adminSecurityService.getUserRoles(user.getId());
+            boolean isSuperAdmin = adminSecurityService.isSuperAdmin(user.getId());
+            
             Map<String, Object> data = new HashMap<>();
             user.setPassword(null);
             data.put("userInfo", user);
-            data.put("permissions", new String[]{"*"});
+            data.put("permissions", permissions);
+            data.put("roles", roles);
+            data.put("isSuperAdmin", isSuperAdmin);
             
             return Result.success(data);
         } catch (Exception e) {

+ 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) {

+ 92 - 33
service/admin/src/main/java/com/zhentao/entity/AdminUser.java

@@ -1,77 +1,136 @@
 package com.zhentao.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+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 java.util.Date;
 import lombok.Data;
 
-import java.io.Serializable;
-import java.time.LocalDateTime;
-
 /**
- * 管理员用户实体
+ * 管理员用户表
+ * @TableName admin_user
  */
+@TableName(value ="admin_user")
 @Data
-@TableName("admin_user")
-public class AdminUser implements Serializable {
-    
-    private static final long serialVersionUID = 1L;
-    
-    @TableId(value = "id", type = IdType.AUTO)
+public class AdminUser {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
     private Integer id;
-    
+
     /**
      * 用户名
      */
     private String username;
-    
+
     /**
-     * 密码(加密
+     * 密码(MD5加密)
      */
     private String password;
-    
+
+    /**
+     * 密码盐
+     */
+    private String salt;
+
     /**
      * 真实姓名
      */
     private String realName;
-    
+
     /**
      * 手机号
      */
     private String phone;
-    
+
     /**
      * 邮箱
      */
     private String email;
-    
-    /**
-     * 角色:1-超级管理员 2-普通管理员
-     */
-    private Integer role;
-    
+
     /**
      * 状态:0-禁用 1-启用
      */
     private Integer status;
-    
+
     /**
      * 创建时间
      */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime createTime;
-    
+    private Date createTime;
+
     /**
      * 更新时间
      */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime updateTime;
-    
+    private Date updateTime;
+
     /**
      * 最后登录时间
      */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime lastLoginTime;
-}
+    private Date lastLoginTime;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        AdminUser other = (AdminUser) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
+            && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword()))
+            && (this.getSalt() == null ? other.getSalt() == null : this.getSalt().equals(other.getSalt()))
+            && (this.getRealName() == null ? other.getRealName() == null : this.getRealName().equals(other.getRealName()))
+            && (this.getPhone() == null ? other.getPhone() == null : this.getPhone().equals(other.getPhone()))
+            && (this.getEmail() == null ? other.getEmail() == null : this.getEmail().equals(other.getEmail()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()))
+            && (this.getLastLoginTime() == null ? other.getLastLoginTime() == null : this.getLastLoginTime().equals(other.getLastLoginTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
+        result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode());
+        result = prime * result + ((getSalt() == null) ? 0 : getSalt().hashCode());
+        result = prime * result + ((getRealName() == null) ? 0 : getRealName().hashCode());
+        result = prime * result + ((getPhone() == null) ? 0 : getPhone().hashCode());
+        result = prime * result + ((getEmail() == null) ? 0 : getEmail().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        result = prime * result + ((getLastLoginTime() == null) ? 0 : getLastLoginTime().hashCode());
+        return result;
+    }
 
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", username=").append(username);
+        sb.append(", password=").append(password);
+        sb.append(", salt=").append(salt);
+        sb.append(", realName=").append(realName);
+        sb.append(", phone=").append(phone);
+        sb.append(", email=").append(email);
+        sb.append(", status=").append(status);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", lastLoginTime=").append(lastLoginTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 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;
     
     /** 收货人姓名 */

+ 124 - 36
service/admin/src/main/java/com/zhentao/entity/PointsProduct.java

@@ -1,56 +1,144 @@
 package com.zhentao.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+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 java.util.Date;
 import lombok.Data;
 
-import java.io.Serializable;
-import java.time.LocalDateTime;
-
 /**
- * 积分商品实体类
+ * 
+ * @TableName points_product
  */
+@TableName(value ="points_product")
 @Data
-@TableName("points_product")
-public class PointsProduct implements Serializable {
-    
-    private static final long serialVersionUID = 1L;
-    
-    @TableId(value = "id", type = IdType.AUTO)
+public class PointsProduct {
+    /**
+     * 
+     */
+    @TableId(type = IdType.AUTO)
     private Long id;
-    
-    /** 商品名称 */
+
+    /**
+     * 商品名称
+     */
     private String name;
-    
-    /** 商品描述 */
+
+    /**
+     * 商品描述
+     */
     private String description;
-    
-    /** 商品图片 */
+
+    /**
+     * 商品图片
+     */
     private String imageUrl;
-    
-    /** 积分价格 */
+
+    /**
+     * 积分价格
+     */
     private Integer pointsPrice;
-    
-    /** 库存数量 */
+
+    /**
+     * 库存
+     */
     private Integer stock;
-    
-    /** 分类: 1-实物商品 2-虚拟商品 */
+
+    /**
+     * 1-实物 2-虚拟
+     */
     private Integer category;
-    
-    /** 是否推荐: 0-否 1-是 */
+
+    /**
+     * 是否推荐
+     */
     private Integer isRecommend;
-    
-    /** 状态: 0-下架 1-上架 */
+
+    /**
+     * 0-下架 1-上架
+     */
     private Integer status;
-    
-    /** 排序 */
+
+    /**
+     * 
+     */
     private Integer sortOrder;
-    
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime createTime;
-    
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime updateTime;
-}
+
+    /**
+     * 
+     */
+    private Date createTime;
+
+    /**
+     * 
+     */
+    private Date updateTime;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        PointsProduct other = (PointsProduct) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
+            && (this.getDescription() == null ? other.getDescription() == null : this.getDescription().equals(other.getDescription()))
+            && (this.getImageUrl() == null ? other.getImageUrl() == null : this.getImageUrl().equals(other.getImageUrl()))
+            && (this.getPointsPrice() == null ? other.getPointsPrice() == null : this.getPointsPrice().equals(other.getPointsPrice()))
+            && (this.getStock() == null ? other.getStock() == null : this.getStock().equals(other.getStock()))
+            && (this.getCategory() == null ? other.getCategory() == null : this.getCategory().equals(other.getCategory()))
+            && (this.getIsRecommend() == null ? other.getIsRecommend() == null : this.getIsRecommend().equals(other.getIsRecommend()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getSortOrder() == null ? other.getSortOrder() == null : this.getSortOrder().equals(other.getSortOrder()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
+        result = prime * result + ((getDescription() == null) ? 0 : getDescription().hashCode());
+        result = prime * result + ((getImageUrl() == null) ? 0 : getImageUrl().hashCode());
+        result = prime * result + ((getPointsPrice() == null) ? 0 : getPointsPrice().hashCode());
+        result = prime * result + ((getStock() == null) ? 0 : getStock().hashCode());
+        result = prime * result + ((getCategory() == null) ? 0 : getCategory().hashCode());
+        result = prime * result + ((getIsRecommend() == null) ? 0 : getIsRecommend().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getSortOrder() == null) ? 0 : getSortOrder().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", name=").append(name);
+        sb.append(", description=").append(description);
+        sb.append(", imageUrl=").append(imageUrl);
+        sb.append(", pointsPrice=").append(pointsPrice);
+        sb.append(", stock=").append(stock);
+        sb.append(", category=").append(category);
+        sb.append(", isRecommend=").append(isRecommend);
+        sb.append(", status=").append(status);
+        sb.append(", sortOrder=").append(sortOrder);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 10 - 5
service/admin/src/main/java/com/zhentao/mapper/AdminUserMapper.java

@@ -1,13 +1,18 @@
 package com.zhentao.mapper;
 
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.zhentao.entity.AdminUser;
-import org.apache.ibatis.annotations.Mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 
 /**
- * 管理员用户Mapper
- */
-@Mapper
+* @author 联想
+* @description 针对表【admin_user(管理员用户表)】的数据库操作Mapper
+* @createDate 2025-12-11 16:08:57
+* @Entity com.zhentao.entity.AdminUser
+*/
 public interface AdminUserMapper extends BaseMapper<AdminUser> {
+
 }
 
+
+
+

+ 11 - 5
service/admin/src/main/java/com/zhentao/mapper/PointsProductMapper.java

@@ -1,12 +1,18 @@
 package com.zhentao.mapper;
 
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.zhentao.entity.PointsProduct;
-import org.apache.ibatis.annotations.Mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 
 /**
- * 积分商品Mapper
- */
-@Mapper
+* @author 联想
+* @description 针对表【points_product】的数据库操作Mapper
+* @createDate 2025-12-11 10:49:42
+* @Entity com.zhentao.entity.PointsProduct
+*/
 public interface PointsProductMapper extends BaseMapper<PointsProduct> {
+
 }
+
+
+
+

+ 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);
     
     /**
      * 获取兑换订单列表

+ 37 - 12
service/admin/src/main/java/com/zhentao/service/impl/AuthServiceImpl.java

@@ -10,7 +10,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.DigestUtils;
 
 import java.nio.charset.StandardCharsets;
-import java.time.LocalDateTime;
+import java.util.Date;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -40,17 +40,34 @@ public class AuthServiceImpl implements AuthService {
                 return null;
             }
             
-            // 验证密码(简化处理,实际项目应使用BCrypt)
-            String encryptedPassword = encryptPassword(password);
-            if (!user.getPassword().equals(encryptedPassword)) {
-                // 兼容明文密码(测试用)
-                if (!user.getPassword().equals(password)) {
+            // 验证密码:MD5(密码+盐)
+            if (user.getSalt() == null || user.getSalt().isEmpty()) {
+                // 兼容旧数据:如果没有盐,使用MD5(密码)验证
+                String md5Password = encryptPassword(password, "");
+                if (!user.getPassword().equals(md5Password)) {
+                    // 兼容明文密码(测试用)
+                    if (!user.getPassword().equals(password)) {
+                        return null;
+                    } else {
+                        // 如果是明文密码,更新为加密密码
+                        String salt = generateSalt();
+                        String encryptedPassword = encryptPassword(password, salt);
+                        user.setSalt(salt);
+                        user.setPassword(encryptedPassword);
+                        user.setUpdateTime(new Date());
+                        adminUserMapper.updateById(user);
+                    }
+                }
+            } else {
+                // 使用盐验证密码:MD5(密码+盐)
+                String encryptedPassword = encryptPassword(password, user.getSalt());
+                if (!user.getPassword().equals(encryptedPassword)) {
                     return null;
                 }
             }
             
             // 更新最后登录时间
-            user.setLastLoginTime(LocalDateTime.now());
+            user.setLastLoginTime(new Date());
             adminUserMapper.updateById(user);
             
             return user;
@@ -64,11 +81,11 @@ public class AuthServiceImpl implements AuthService {
     public String generateToken(AdminUser user) {
         String token = UUID.randomUUID().toString().replace("-", "");
         
-        // 将token存储到Redis(如果可用)
+        // 将token存储到Redis(如果可用),1小时过期
         if (redisTemplate != null) {
             try {
                 String key = "admin:token:" + token;
-                redisTemplate.opsForValue().set(key, String.valueOf(user.getId()), 24, TimeUnit.HOURS);
+                redisTemplate.opsForValue().set(key, String.valueOf(user.getId()), 1, TimeUnit.HOURS);
             } catch (Exception e) {
                 e.printStackTrace();
             }
@@ -113,10 +130,18 @@ public class AuthServiceImpl implements AuthService {
     }
     
     /**
-     * 密码加密(简化版,实际项目应使用BCrypt)
+     * 生成随机盐
+     */
+    private String generateSalt() {
+        return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
+    }
+    
+    /**
+     * 密码加密:MD5(密码+盐)
      */
-    private String encryptPassword(String password) {
-        return DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
+    private String encryptPassword(String password, String salt) {
+        String passwordWithSalt = password + salt;
+        return DigestUtils.md5DigestAsHex(passwordWithSalt.getBytes(StandardCharsets.UTF_8));
     }
 }
 

+ 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());
 

+ 5 - 0
service/homePage/pom.xml

@@ -53,6 +53,11 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
+        <!-- Spring Boot Starter Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.minio</groupId>
             <artifactId>minio</artifactId>

+ 3 - 1
service/homePage/src/main/java/com/zhentao/controller/ActivityController.java

@@ -14,6 +14,7 @@ import java.util.*;
  */
 @RestController
 @RequestMapping("/api/activity")
+@CrossOrigin(origins = "*")
 public class ActivityController {
     
     @Autowired
@@ -110,7 +111,8 @@ public class ActivityController {
             return Result.success(activityList);
         } catch (Exception e) {
             e.printStackTrace();
-            return Result.error("获取活动列表失败:" + e.getMessage());
+            System.err.println("❌ 获取活动列表异常: " + e.getClass().getName() + " - " + e.getMessage());
+            return Result.error("获取活动列表失败:" + (e.getMessage() != null ? e.getMessage() : "未知错误"));
         }
     }
     

+ 13 - 3
service/homePage/src/main/java/com/zhentao/controller/CourseController.java

@@ -17,6 +17,7 @@ import java.util.Map;
  */
 @RestController
 @RequestMapping("/api/course")
+@CrossOrigin(origins = "*")
 public class CourseController {
     
     @Autowired
@@ -51,10 +52,18 @@ public class CourseController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> getCourseList(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam(required = false, defaultValue = "1") Integer page,
+            @RequestParam(required = false, defaultValue = "10") Integer pageSize,
             @RequestParam(required = false) Integer status) {
         try {
+            // 参数校验和默认值设置
+            if (page == null || page < 1) {
+                page = 1;
+            }
+            if (pageSize == null || pageSize < 1) {
+                pageSize = 10;
+            }
+            
             Page<Course> pageObj = new Page<>(page, pageSize);
             LambdaQueryWrapper<Course> wrapper = new LambdaQueryWrapper<>();
             // 如果指定了 status 参数,则按状态过滤(用于小程序端)
@@ -90,7 +99,8 @@ public class CourseController {
             return Result.success(data);
         } catch (Exception e) {
             e.printStackTrace();
-            return Result.error("获取课程列表失败:" + e.getMessage());
+            System.err.println("❌ 获取课程列表异常: " + e.getClass().getName() + " - " + e.getMessage());
+            return Result.error("获取课程列表失败:" + (e.getMessage() != null ? e.getMessage() : "未知错误"));
         }
     }
     

+ 115 - 3
service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java

@@ -23,6 +23,7 @@ import java.util.Map;
  */
 @RestController
 @RequestMapping("/api/matchmaker")
+@CrossOrigin(origins = "*")
 public class MatchmakerController {
     
     @Autowired
@@ -40,8 +41,21 @@ public class MatchmakerController {
      * @return 分页数据
      */
     @PostMapping("/list")
-    public Result<Page<MatchmakerVO>> getMatchmakerList(@RequestBody MatchmakerQueryDTO queryDTO) {
+    public Result<Page<MatchmakerVO>> getMatchmakerList(@RequestBody(required = false) MatchmakerQueryDTO queryDTO) {
         try {
+            // 如果queryDTO为null,创建默认对象
+            if (queryDTO == null) {
+                queryDTO = new MatchmakerQueryDTO();
+            }
+            
+            // 设置默认值
+            if (queryDTO.getPageNum() == null || queryDTO.getPageNum() < 1) {
+                queryDTO.setPageNum(1);
+            }
+            if (queryDTO.getPageSize() == null || queryDTO.getPageSize() < 1) {
+                queryDTO.setPageSize(10);
+            }
+            
             // 调试日志:打印接收到的参数
             System.out.println("📥 接收到的查询参数: matchmakerType=" + queryDTO.getMatchmakerType() 
                     + ", level=" + queryDTO.getLevel() 
@@ -53,7 +67,9 @@ public class MatchmakerController {
             return Result.success(page);
         } catch (Exception e) {
             e.printStackTrace();
-            return Result.error("查询红娘列表失败:" + e.getMessage());
+            System.err.println("❌ 查询红娘列表异常: " + e.getClass().getName() + " - " + e.getMessage());
+            e.printStackTrace();
+            return Result.error("查询红娘列表失败:" + (e.getMessage() != null ? e.getMessage() : "未知错误"));
         }
     }
     
@@ -148,6 +164,14 @@ public class MatchmakerController {
             if (matchmaker.getStatus() == null) {
                 matchmaker.setStatus(1); // 1-正常
             }
+
+            if (matchmaker.getGender()== 1){
+                matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/5c645152-9940-41d3-83a9-69ee6e0c0aaa.png");
+            }
+
+            if (matchmaker.getGender()== 2){
+                matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/c7fb04d7-ee4d-4b3d-bcef-f246da9c841f.png");
+            }
             
             // 保存红娘到数据库
             boolean success = matchmakerService.save(matchmaker);
@@ -277,7 +301,7 @@ public class MatchmakerController {
     }
     
     /**
-     * 获取红娘排行榜
+     * 获取红娘排行榜(总排行榜)
      * 
      * @param limit 返回数量限制,默认20
      * @return 红娘排行榜列表
@@ -294,6 +318,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());
+        }
+    }
+    
     /**
      * 批量查询红娘信息
      * 

+ 13 - 3
service/homePage/src/main/java/com/zhentao/controller/SuccessCaseController.java

@@ -14,6 +14,7 @@ import java.util.Map;
  * 成功案例控制器
  */
 @RestController
+@CrossOrigin(origins = "*")
 public class SuccessCaseController {
     
     @Autowired
@@ -84,14 +85,23 @@ public class SuccessCaseController {
      */
     @GetMapping("/api/case/list")
     public Result<Map<String, Object>> getAdminCaseList(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize) {
+            @RequestParam(required = false, defaultValue = "1") Integer page,
+            @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         try {
+            // 参数校验和默认值设置
+            if (page == null || page < 1) {
+                page = 1;
+            }
+            if (pageSize == null || pageSize < 1) {
+                pageSize = 10;
+            }
+            
             Map<String, Object> result = successCaseService.getAdminCaseList(page, pageSize);
             return Result.success(result);
         } catch (Exception e) {
             e.printStackTrace();
-            return Result.error("获取案例列表失败:" + e.getMessage());
+            System.err.println("❌ 获取案例列表异常: " + e.getClass().getName() + " - " + e.getMessage());
+            return Result.error("获取案例列表失败:" + (e.getMessage() != null ? 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);
 }
 

+ 55 - 28
service/homePage/src/main/java/com/zhentao/service/impl/MatchmakerCheckinServiceImpl.java

@@ -1,7 +1,6 @@
 package com.zhentao.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.zhentao.entity.Matchmaker;
 import com.zhentao.entity.MatchmakerCheckin;
 import com.zhentao.mapper.MatchmakerCheckinMapper;
 import com.zhentao.service.MatchmakerCheckinService;
@@ -94,52 +93,80 @@ public class MatchmakerCheckinServiceImpl extends ServiceImpl<MatchmakerCheckinM
     }
 
     @Override
-    public boolean isCheckedIn(Long userId) {
-        // 根据 userId 查询 matchmaker 信息
-        MatchmakerVO matchmaker = matchmakerService.getMatchmakerByUserId(userId);
-        if (matchmaker == null) {
-            return false;
+    public boolean isCheckedIn(Long idParam) {
+        // idParam 可能是 userId 或 matchmakerId,需要兼容处理
+        LocalDate today = LocalDate.now();
+        
+        // 先尝试直接用 idParam 作为 matchmakerId 查询
+        MatchmakerCheckin checkin = matchmakerCheckinMapper.selectByMakerIdAndDate(idParam, today);
+        if (checkin != null) {
+            return true;
         }
-        Integer matchmakerId = matchmaker.getMatchmakerId();
         
-        LocalDate today = LocalDate.now();
-        MatchmakerCheckin checkin = matchmakerCheckinMapper.selectByMakerIdAndDate(matchmakerId.longValue(), today);
-        return checkin != null;
+        // 如果没找到,尝试把 idParam 当作 userId 来查询 matchmaker
+        MatchmakerVO matchmaker = matchmakerService.getMatchmakerByUserId(idParam);
+        if (matchmaker != null && matchmaker.getMatchmakerId() != null) {
+            Long matchmakerId = matchmaker.getMatchmakerId().longValue();
+            checkin = matchmakerCheckinMapper.selectByMakerIdAndDate(matchmakerId, today);
+            return checkin != null;
+        }
+        
+        return false;
     }
 
     @Override
-    public Integer getTotalDays(Long userId) {
-        // 根据 userId 查询 matchmaker 信息
-        MatchmakerVO matchmaker = matchmakerService.getMatchmakerByUserId(userId);
-        if (matchmaker == null) {
-            return 0;
+    public Integer getTotalDays(Long idParam) {
+        // idParam 可能是 userId 或 matchmakerId,需要兼容处理
+        Long matchmakerId = idParam;
+        
+        // 先尝试直接用 idParam 作为 matchmakerId 查询
+        Integer totalDays = matchmakerCheckinMapper.selectTotalDaysByMakerId(idParam);
+        if (totalDays != null && totalDays > 0) {
+            return totalDays;
         }
-        Integer matchmakerId = matchmaker.getMatchmakerId();
         
-        Integer totalDays = matchmakerCheckinMapper.selectTotalDaysByMakerId(matchmakerId.longValue());
-        return totalDays != null ? totalDays : 0;
+        // 如果没找到,尝试把 idParam 当作 userId 来查询 matchmaker
+        MatchmakerVO matchmaker = matchmakerService.getMatchmakerByUserId(idParam);
+        if (matchmaker != null && matchmaker.getMatchmakerId() != null) {
+            matchmakerId = matchmaker.getMatchmakerId().longValue();
+            totalDays = matchmakerCheckinMapper.selectTotalDaysByMakerId(matchmakerId);
+            return totalDays != null ? totalDays : 0;
+        }
+        
+        return 0;
     }
 
     @Override
-    public Integer getContinuousDays(Long userId) {
-        // 根据 userId 查询 matchmaker 信息
-        MatchmakerVO matchmaker = matchmakerService.getMatchmakerByUserId(userId);
-        if (matchmaker == null) {
-            return 0;
-        }
-        Integer matchmakerId = matchmaker.getMatchmakerId();
+    public Integer getContinuousDays(Long idParam) {
+        // idParam 可能是 userId 或 matchmakerId,需要兼容处理
+        Long matchmakerId = idParam;
         
-        MatchmakerCheckin lastCheckin = matchmakerCheckinMapper.selectLastByMakerId(matchmakerId.longValue());
+        // 先尝试直接用 idParam 作为 matchmakerId 查询
+        MatchmakerCheckin lastCheckin = matchmakerCheckinMapper.selectLastByMakerId(idParam);
         if (lastCheckin != null) {
-            // 检查是否是连续签到(今天或昨天)
             LocalDate today = LocalDate.now();
             LocalDate lastDate = lastCheckin.getCheckinDate();
             long daysBetween = ChronoUnit.DAYS.between(lastDate, today);
-            
             if (daysBetween <= 1) {
                 return lastCheckin.getContinuousDays();
             }
         }
+        
+        // 如果没找到,尝试把 idParam 当作 userId 来查询 matchmaker
+        MatchmakerVO matchmaker = matchmakerService.getMatchmakerByUserId(idParam);
+        if (matchmaker != null && matchmaker.getMatchmakerId() != null) {
+            matchmakerId = matchmaker.getMatchmakerId().longValue();
+            lastCheckin = matchmakerCheckinMapper.selectLastByMakerId(matchmakerId);
+            if (lastCheckin != null) {
+                LocalDate today = LocalDate.now();
+                LocalDate lastDate = lastCheckin.getCheckinDate();
+                long daysBetween = ChronoUnit.DAYS.between(lastDate, today);
+                if (daysBetween <= 1) {
+                    return lastCheckin.getContinuousDays();
+                }
+            }
+        }
+        
         return 0;
     }
 }

+ 138 - 4
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;
     
@@ -54,15 +64,22 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
         try {
             Page<MatchmakerVO> cachedPage = (Page<MatchmakerVO>) redisTemplate.opsForValue().get(cacheKey);
             if (cachedPage != null) {
-                System.out.println("✅ 从Redis缓存获取红娘列表: " + cacheKey);
-                return cachedPage;
+                // 检查缓存数据是否异常:如果total为0但records不为空,说明缓存数据有问题,需要清除缓存
+                if (cachedPage.getTotal() == 0 && cachedPage.getRecords() != null && !cachedPage.getRecords().isEmpty()) {
+                    System.out.println("⚠️ 检测到缓存数据异常(total=0但records不为空),清除缓存并重新查询: " + cacheKey);
+                    redisTemplate.delete(cacheKey);
+                    // 继续执行数据库查询
+                } else {
+                    System.out.println("✅ 从Redis缓存获取红娘列表: " + cacheKey + ", total=" + cachedPage.getTotal() + ", records=" + cachedPage.getRecords().size());
+                    return cachedPage;
+                }
             }
         } catch (Exception e) {
             System.err.println("⚠️ Redis读取失败,将直接查询数据库: " + e.getMessage());
         }
         
         // 3. 缓存未命中或Redis异常,从数据库查询
-        System.out.println("❌ 缓存未命中或Redis异常,从数据库查询: " + cacheKey);
+        System.out.println("📊 从数据库查询红娘列表: " + cacheKey);
         Page<MatchmakerVO> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
         
         Page<MatchmakerVO> result = matchmakerMapper.selectMatchmakerPage(
@@ -79,7 +96,10 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
         // 4. 处理数据
         result.getRecords().forEach(this::processMatchmakerVO);
         
-        // 5. 尝试存入Redis缓存(带异常处理)
+        // 5. 打印查询结果日志
+        System.out.println("📈 查询结果 - total: " + result.getTotal() + ", records: " + result.getRecords().size() + ", current: " + result.getCurrent() + ", size: " + result.getSize());
+        
+        // 6. 尝试存入Redis缓存(带异常处理)
         try {
             redisTemplate.opsForValue().set(
                     cacheKey, 
@@ -454,4 +474,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;
 }
 

+ 6 - 0
service/websocket/pom.xml

@@ -32,6 +32,12 @@
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
 
+        <!-- Spring Boot Starter Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
         <!-- Spring Boot WebSocket -->
         <dependency>
             <groupId>org.springframework.boot</groupId>