12 Revize c2cdabc6e9 ... f7bc35186a

Autor SHA1 Zpráva Datum
  YH_0525 f7bc35186a 星命测算的页面修改 před 3 týdny
  caojp 6413c89b51 Merge branch 'cjp' into test_dev před 3 týdny
  caojp fad324cd69 app动态追加分页 před 3 týdny
  YH_0525 cfe451187c 星命测算的页面修改 před 3 týdny
  李思佳 c55b03fd89 Merge branch 'test_dev' into lisijia před 3 týdny
  李思佳 89009b89cf fix(matchmaker-workbench): 调整签到积分奖励为1分并优化签到逻辑 před 3 týdny
  caojp ba20821c62 Merge branch 'cjp' into test_dev před 3 týdny
  caojp 7b624819fb 1.个人资料的功能进行实现 před 3 týdny
  李思佳 b78b22a1ae Merge branch 'test_dev' into lisijia před 3 týdny
  李思佳 0622ae54ee feat(ui): 更新用户界面图标和布局 před 3 týdny
  wangwenju 211ce33cb7 红娘以及用户端金刚区修改 před 3 týdny
  wangwenju 0d6cd1dca4 pom文件修改 před 3 týdny
58 změnil soubory, kde provedl 3245 přidání a 650 odebrání
  1. 253 186
      LiangZhiYUMao/pages/astrology/index.vue
  2. 23 23
      LiangZhiYUMao/pages/index/index.vue
  3. 1 1
      LiangZhiYUMao/pages/matchmaker-workbench/edit-profile.vue
  4. 182 104
      LiangZhiYUMao/pages/matchmaker-workbench/index.vue
  5. 2 2
      LiangZhiYUMao/pages/matchmaker-workbench/mine.vue
  6. 61 21
      LiangZhiYUMao/pages/matchmaker-workbench/sign-in.vue
  7. 83 43
      LiangZhiYUMao/pages/mine/index.vue
  8. 1 1
      LiangZhiYUMao/pages/mine/my-activities.vue
  9. 56 13
      LiangZhiYUMao/pages/mine/my-dynamics.vue
  10. 1 1
      LiangZhiYUMao/pages/settings/index.vue
  11. 3 0
      LiangZhiYUMao/utils/api.js
  12. 374 0
      LiangZhiYUMao/utils/fortune.js
  13. 2 2
      gateway/src/main/resources/application.yml
  14. 10 1
      marriageAdmin-vue/src/assets/list-common.css
  15. 8 1
      marriageAdmin-vue/src/config/api.js
  16. 2 6
      marriageAdmin-vue/src/layouts/MainLayout.vue
  17. 7 0
      marriageAdmin-vue/src/router/index.js
  18. 36 20
      marriageAdmin-vue/src/utils/request.js
  19. 370 0
      marriageAdmin-vue/src/views/Profile.vue
  20. 34 4
      marriageAdmin-vue/src/views/activity/ActivityForm.vue
  21. 91 8
      marriageAdmin-vue/src/views/activity/ActivityList.vue
  22. 150 33
      marriageAdmin-vue/src/views/activity/ActivityRegistrations.vue
  23. 3 2
      marriageAdmin-vue/src/views/banner/BannerList.vue
  24. 0 83
      marriageAdmin-vue/src/views/success-case/SuccessCaseList.vue
  25. 0 18
      marriageAdmin-vue/src/views/user/UserList.vue
  26. 161 3
      marriageAdmin-vue/src/views/user/UserVipList.vue
  27. 3 1
      service/Essential/src/main/java/com/zhentao/dto/ActivityCreateOrderDto.java
  28. 2 1
      service/Essential/src/main/java/com/zhentao/service/ActivityOrderService.java
  29. 6 6
      service/Essential/src/main/java/com/zhentao/service/impl/ActivityOrderServiceImpl.java
  30. 2 2
      service/Essential/src/main/java/com/zhentao/service/impl/VipServiceImpl.java
  31. 6 0
      service/admin/pom.xml
  32. 0 13
      service/admin/src/main/java/com/zhentao/controller/ActivityController.java
  33. 122 0
      service/admin/src/main/java/com/zhentao/controller/ActivityRegistrationController.java
  34. 154 0
      service/admin/src/main/java/com/zhentao/controller/AdminUserController.java
  35. 72 44
      service/admin/src/main/java/com/zhentao/controller/UserController.java
  36. 146 0
      service/admin/src/main/java/com/zhentao/entity/ActivityRegistration.java
  37. 4 0
      service/admin/src/main/java/com/zhentao/entity/AdminUser.java
  38. 144 0
      service/admin/src/main/java/com/zhentao/entity/HomeFunctionGrid.java
  39. 35 0
      service/admin/src/main/java/com/zhentao/mapper/ActivityRegistrationMapper.java
  40. 18 0
      service/admin/src/main/java/com/zhentao/mapper/HomeFunctionGridMapper.java
  41. 55 0
      service/admin/src/main/java/com/zhentao/service/ActivityRegistrationService.java
  42. 13 0
      service/admin/src/main/java/com/zhentao/service/HomeFunctionGridService.java
  43. 8 0
      service/admin/src/main/java/com/zhentao/service/SystemMessagesService.java
  44. 5 0
      service/admin/src/main/java/com/zhentao/service/UserService.java
  45. 121 0
      service/admin/src/main/java/com/zhentao/service/impl/ActivityRegistrationServiceImpl.java
  46. 22 0
      service/admin/src/main/java/com/zhentao/service/impl/HomeFunctionGridServiceImpl.java
  47. 16 0
      service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java
  48. 96 0
      service/admin/src/main/java/com/zhentao/service/impl/SystemMessagesServiceImpl.java
  49. 9 0
      service/admin/src/main/java/com/zhentao/service/impl/UserServiceImpl.java
  50. 100 0
      service/admin/src/main/java/com/zhentao/vo/ActivityRegistrationExcelVO.java
  51. 81 0
      service/admin/src/main/resources/com/zhentao/mapper/ActivityRegistrationMapper.xml
  52. 27 0
      service/admin/src/main/resources/com/zhentao/mapper/HomeFunctionGridMapper.xml
  53. 1 1
      service/homePage/pom.xml
  54. 17 0
      service/homePage/src/main/java/com/zhentao/controller/HomeController.java
  55. 15 0
      service/homePage/src/main/java/com/zhentao/entity/HomeFunctionGrid.java
  56. 7 0
      service/homePage/src/main/java/com/zhentao/service/HomeFunctionGridService.java
  57. 10 0
      service/homePage/src/main/java/com/zhentao/service/impl/HomeFunctionGridServiceImpl.java
  58. 14 6
      service/homePage/src/main/resources/sql/home_function_grid.sql

+ 253 - 186
LiangZhiYUMao/pages/astrology/index.vue

@@ -11,29 +11,46 @@
 			</view>
 		</view>
 
+		<!-- 用户资料提示 -->
+		<view class="profile-tip" v-if="!hasProfile" @click="goToProfile">
+			<view class="tip-content">
+				<text class="tip-icon">💫</text>
+				<view class="tip-text-group">
+					<text class="tip-title">完善资料,解锁专属运势</text>
+					<text class="tip-desc">填写出生日期,获取更精准的运势分析</text>
+				</view>
+				<text class="tip-arrow">→</text>
+			</view>
+		</view>
+		
+		<!-- 用户信息卡片 -->
+		<view class="user-profile-card" v-if="hasProfile" @click="goToProfile">
+			<view class="user-avatar-wrapper">
+				<image class="user-avatar" :src="userInfo.avatarUrl || userInfo.avatar || defaultAvatar" mode="aspectFill"></image>
+			</view>
+			<view class="user-info-content">
+				<text class="user-nickname">{{ userInfo.nickname || '用户' }}</text>
+				<view class="user-astro-tags">
+					<view class="astro-tag" v-if="userConstellation">
+						<text class="astro-icon">⭐</text>
+						<text class="astro-text">{{ userConstellation }}</text>
+					</view>
+					<view class="astro-tag" v-if="userZodiac">
+						<text class="astro-icon">🐲</text>
+						<text class="astro-text">属{{ userZodiac }}</text>
+					</view>
+				</view>
+			</view>
+			<view class="edit-icon">
+				<text>›</text>
+			</view>
+		</view>
+
 		<!-- 测算卡片区域 -->
 		<view class="cards-section">
 			<view class="card-item" v-for="(card, index) in cardList" :key="index" @click="handleCardClick(card)">
 				<view class="card-icon">{{ card.icon }}</view>
 				<view class="card-name">{{ card.name }}</view>
-				<view class="card-desc">{{ card.desc }}</view>
-			</view>
-		</view>
-
-		<!-- 星座配对测试 -->
-		<view class="match-test-banner" @click="handleConstellationMatch">
-			<view class="match-banner-content">
-				<view class="match-icon-group">
-					<text class="match-icon">💫</text>
-				</view>
-				<view class="match-text-group">
-					<view class="match-title">星座配对测试</view>
-					<view class="match-subtitle">测测你们的星座匹配度有多高</view>
-				</view>
-				<view class="match-arrow">→</view>
-			</view>
-			<view class="match-stats">
-				<text class="match-stats-text">💕 今日已有 {{ matchTestCount }} 对情侣测试</text>
 			</view>
 		</view>
 
@@ -101,172 +118,66 @@
 			</view>
 		</view>
 
-		<!-- 热门测试榜单 -->
-		<view class="hot-tests">
-			<view class="section-title">
-				<text class="title-text">🔥 热门测试榜单</text>
-				<text class="title-more">查看全部 ></text>
-			</view>
-			<view class="test-list">
-				<view class="test-item" v-for="(item, index) in hotTests" :key="index" @click="handleTestClick(item)">
-					<view class="test-rank" :class="{'top-rank': index < 3}">{{ index + 1 }}</view>
-					<view class="test-info">
-						<text class="test-name">{{ item.name }}</text>
-						<text class="test-count">{{ item.count }} 人已测</text>
-					</view>
-					<view class="test-tag" v-if="item.isNew">NEW</view>
-				</view>
-			</view>
-		</view>
-
 		<!-- 底部占位 -->
 		<view class="bottom-placeholder"></view>
 	</view>
 </template>
 
 <script>
+import fortuneUtil from '@/utils/fortune.js'
+import api from '@/utils/api.js'
+
 export default {
 	data() {
 		return {
 			// 今日日期
 			todayDate: '',
 			
+			// 用户信息
+			userInfo: {},
+			
+			// 是否有完整资料
+			hasProfile: false,
+			
+			// 用户星座和生肖
+			userConstellation: '',
+			userZodiac: '',
+			
+			// 默认头像
+			defaultAvatar: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI9FhqmIJtfQMh0LLicJxYS9xcd1u9YLnnvibALzycnOXLqm0JtmaNotzzSMyyibBtvBfzic6bg/0',
+			
 			// 测算卡片
 			cardList: [
-				{
-					id: 1,
-					name: '属相卡片',
-					icon: '🐷',
-					desc: '2.3万人测过',
-					type: 'zodiac'
-				},
-				{
-					id: 2,
-					name: '星座卡片',
-					icon: '♓',
-					desc: '2.3万人测过',
-					type: 'constellation'
-				},
-				{
-					id: 3,
-					name: '八字卡片',
-					icon: '☯',
-					desc: '2.3万人测过',
-					type: 'bazi'
-				},
-				{
-					id: 4,
-					name: 'MBTI卡片',
-					icon: '🧩',
-					desc: '2.3万人测过',
-					type: 'mbti'
-				}
+				{ id: 1, name: '属相卡片', icon: '🐷', type: 'zodiac' },
+				{ id: 2, name: '星座卡片', icon: '♓', type: 'constellation' },
+				{ id: 3, name: '八字卡片', icon: '☯', type: 'bazi' },
+				{ id: 4, name: 'MBTI卡片', icon: '🧩', type: 'mbti' }
 			],
 
-			// 缘分配对测试统计
-			matchTestCount: 1234,
-
-			// 我的爱情运势
-			loveFortune: [
-				{
-					label: '桃花运',
-					icon: '🌸',
-					score: 85,
-					color: 'linear-gradient(135deg, #FF6B9D 0%, #FFA5C6 100%)'
-				},
-				{
-					label: '爱情指数',
-					icon: '💕',
-					score: 78,
-					color: 'linear-gradient(135deg, #E91E63 0%, #FF6B9D 100%)'
-				},
-				{
-					label: '魅力值',
-					icon: '✨',
-					score: 92,
-					color: 'linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)'
-				}
-			],
+			// 我的爱情运势(动态计算)
+			loveFortune: [],
 
-			// 本周脱单指数
+			// 本周脱单指数(动态计算)
 			singleIndex: {
-				score: 88,
-				level: '极佳',
-				levelColor: 'linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%)',
-				tips: [
-					{ icon: '📅', text: '最佳约会日:周六' },
-					{ icon: '⏰', text: '最佳时间:下午2-5点' },
-					{ icon: '📍', text: '推荐地点:咖啡厅、公园' }
-				]
+				score: 75,
+				level: '良好',
+				levelColor: 'linear-gradient(135deg, #2196F3 0%, #03A9F4 100%)',
+				tips: []
 			},
 
-			// 每日幸运推荐
-			luckyRecommend: [
-				{
-					label: '幸运色',
-					icon: '🎨',
-					value: '粉红色',
-					bgColor: 'linear-gradient(135deg, #FFB6C1 0%, #FFE4E1 100%)'
-				},
-				{
-					label: '幸运数字',
-					icon: '🔢',
-					value: '7',
-					bgColor: 'linear-gradient(135deg, #9C27B0 0%, #CE93D8 100%)'
-				},
-				{
-					label: '约会建议',
-					icon: '💡',
-					value: '浪漫晚餐',
-					bgColor: 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)'
-				},
-				{
-					label: '穿搭风格',
-					icon: '👗',
-					value: '清新甜美',
-					bgColor: 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)'
-				}
-			],
-
-			// 热门测试榜单
-			hotTests: [
-				{
-					id: 1,
-					name: '恋爱性格测试',
-					count: '12.8万',
-					isNew: false
-				},
-				{
-					id: 2,
-					name: '理想型匹配度',
-					count: '10.5万',
-					isNew: true
-				},
-				{
-					id: 3,
-					name: '爱情观测试',
-					count: '9.2万',
-					isNew: false
-				},
-				{
-					id: 4,
-					name: '婚姻契合度',
-					count: '8.6万',
-					isNew: false
-				},
-				{
-					id: 5,
-					name: '恋爱EQ测试',
-					count: '7.3万',
-					isNew: true
-				}
-			]
+			// 每日幸运推荐(动态计算)
+			luckyRecommend: []
 		}
 	},
 
 	onLoad() {
 		this.initData()
 	},
+	
+	onShow() {
+		// 每次显示页面时重新加载用户信息和计算运势
+		this.loadUserInfo()
+	},
 
 	methods: {
 		// 初始化数据
@@ -278,6 +189,63 @@ export default {
 			const weekDays = ['日', '一', '二', '三', '四', '五', '六']
 			const weekDay = weekDays[date.getDay()]
 			this.todayDate = `${month}月${day}日 周${weekDay}`
+			
+			// 加载用户信息并计算运势
+			this.loadUserInfo()
+		},
+		
+		// 加载用户信息
+		async loadUserInfo() {
+			// 先从本地存储获取基础信息
+			let userInfo = uni.getStorageSync('userInfo') || {}
+			const userId = uni.getStorageSync('userId')
+			
+			// 如果有userId,从API获取最新用户信息
+			if (userId) {
+				try {
+					const data = await api.user.getDetailInfo(userId)
+					if (data) {
+						// 合并API返回的数据
+						userInfo = {
+							...userInfo,
+							...data,
+							userId: userId
+						}
+						// 更新本地存储
+						uni.setStorageSync('userInfo', userInfo)
+						console.log('✅ 从API获取用户信息成功:', userInfo.nickname)
+					}
+				} catch (error) {
+					console.error('❌ 获取用户信息失败:', error)
+				}
+			}
+			
+			this.userInfo = userInfo
+			
+			// 计算运势
+			this.calculateFortune()
+		},
+		
+		// 计算运势数据
+		calculateFortune() {
+			const fortuneData = fortuneUtil.getFullFortuneData(this.userInfo)
+			
+			this.hasProfile = fortuneData.hasProfile
+			this.userConstellation = fortuneData.constellation || ''
+			this.userZodiac = fortuneData.zodiac || ''
+			this.loveFortune = fortuneData.loveFortune
+			this.singleIndex = fortuneData.singleIndex
+			this.luckyRecommend = fortuneData.luckyRecommend
+			
+			// 如果没有完整资料,显示提示
+			if (!this.hasProfile) {
+				console.log('用户资料不完整,显示默认运势数据')
+			} else {
+				console.log('根据用户资料计算运势:', {
+					constellation: this.userConstellation,
+					zodiac: this.userZodiac
+				})
+			}
 		},
 
 		// 返回上一页
@@ -320,18 +288,10 @@ export default {
 			}
 		},
 
-		// 星座配对测试
-		handleConstellationMatch() {
+		// 跳转到个人资料页面
+		goToProfile() {
 			uni.navigateTo({
-				url: '/pages/astrology/constellation-match'
-			})
-		},
-
-		// 测试项点击
-		handleTestClick(test) {
-			uni.showToast({
-				title: `${test.name}功能开发中`,
-				icon: 'none'
+				url: '/pages/profile/index'
 			})
 		}
 	}
@@ -385,6 +345,117 @@ export default {
 	}
 }
 
+/* 用户资料提示 */
+.profile-tip {
+	margin: 20rpx 30rpx;
+	background: linear-gradient(135deg, #FFF3E0 0%, #FFE0B2 100%);
+	border-radius: 16rpx;
+	padding: 24rpx;
+	box-shadow: 0 4rpx 12rpx rgba(255, 152, 0, 0.15);
+	
+	.tip-content {
+		display: flex;
+		align-items: center;
+		
+		.tip-icon {
+			font-size: 40rpx;
+			margin-right: 20rpx;
+		}
+		
+		.tip-text-group {
+			flex: 1;
+			
+			.tip-title {
+				font-size: 28rpx;
+				font-weight: 600;
+				color: #E65100;
+				display: block;
+				margin-bottom: 6rpx;
+			}
+			
+			.tip-desc {
+				font-size: 24rpx;
+				color: #FF8F00;
+			}
+		}
+		
+		.tip-arrow {
+			font-size: 32rpx;
+			color: #E65100;
+			font-weight: bold;
+		}
+	}
+}
+
+/* 用户信息卡片 */
+.user-profile-card {
+	display: flex;
+	align-items: center;
+	margin: 20rpx 30rpx;
+	padding: 24rpx;
+	background: linear-gradient(135deg, #FFFFFF 0%, #FFF9FB 100%);
+	border-radius: 20rpx;
+	box-shadow: 0 4rpx 16rpx rgba(233, 30, 99, 0.1);
+	border: 1rpx solid rgba(233, 30, 99, 0.1);
+	
+	.user-avatar-wrapper {
+		width: 100rpx;
+		height: 100rpx;
+		border-radius: 50%;
+		overflow: hidden;
+		margin-right: 24rpx;
+		border: 4rpx solid #FFE4EC;
+		box-shadow: 0 4rpx 12rpx rgba(233, 30, 99, 0.15);
+		
+		.user-avatar {
+			width: 100%;
+			height: 100%;
+		}
+	}
+	
+	.user-info-content {
+		flex: 1;
+		
+		.user-nickname {
+			font-size: 32rpx;
+			font-weight: 600;
+			color: #333333;
+			display: block;
+			margin-bottom: 12rpx;
+		}
+		
+		.user-astro-tags {
+			display: flex;
+			gap: 16rpx;
+			
+			.astro-tag {
+				display: flex;
+				align-items: center;
+				background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD9 100%);
+				padding: 8rpx 16rpx;
+				border-radius: 20rpx;
+				
+				.astro-icon {
+					font-size: 22rpx;
+					margin-right: 6rpx;
+				}
+				
+				.astro-text {
+					font-size: 24rpx;
+					color: #E91E63;
+					font-weight: 500;
+				}
+			}
+		}
+	}
+	
+	.edit-icon {
+		font-size: 40rpx;
+		color: #CCCCCC;
+		font-weight: 300;
+	}
+}
+
 /* 测算卡片区域 */
 .cards-section {
 	display: flex;
@@ -420,12 +491,6 @@ export default {
 			font-size: 28rpx;
 			color: #333333;
 			font-weight: 500;
-			margin-bottom: 8rpx;
-		}
-
-		.card-desc {
-			font-size: 22rpx;
-			color: #999999;
 		}
 	}
 }
@@ -667,42 +732,44 @@ export default {
 
 	.lucky-grid {
 		display: grid;
-		grid-template-columns: repeat(2, 1fr);
-		gap: 20rpx;
+		grid-template-columns: repeat(4, 1fr);
+		gap: 16rpx;
 
 		.lucky-item {
 			display: flex;
 			flex-direction: column;
 			align-items: center;
 			background-color: #FFFFFF;
-			border-radius: 15rpx;
-			padding: 30rpx 20rpx;
+			border-radius: 12rpx;
+			padding: 20rpx 10rpx;
 			box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
 
 			.lucky-icon-wrapper {
-				width: 80rpx;
-				height: 80rpx;
+				width: 60rpx;
+				height: 60rpx;
 				border-radius: 50%;
 				display: flex;
 				align-items: center;
 				justify-content: center;
-				margin-bottom: 15rpx;
+				margin-bottom: 10rpx;
 
 				.lucky-icon {
-					font-size: 40rpx;
+					font-size: 32rpx;
 				}
 			}
 
 			.lucky-label {
-				font-size: 24rpx;
+				font-size: 22rpx;
 				color: #999999;
-				margin-bottom: 8rpx;
+				margin-bottom: 6rpx;
 			}
 
 			.lucky-value {
-				font-size: 28rpx;
+				font-size: 24rpx;
 				color: #333333;
 				font-weight: 600;
+				text-align: center;
+				word-break: break-all;
 			}
 		}
 	}

+ 23 - 23
LiangZhiYUMao/pages/index/index.vue

@@ -494,29 +494,29 @@
 			},
 			
 			// 加载金刚区功能列表
-			async loadFunctionGridData() {
-				try {
-					// 调用 API 获取金刚区功能数据
-					const data = await api.home.getFunctionGrid()
-					if (data && data.length > 0) {
-						// 处理金刚区数据,确保字段格式正确
-						this.functionList = data.map(item => ({
-							...item,
-							// 确保渐变背景色格式正确
-							gradient: item.gradient || `linear-gradient(135deg, ${item.bgColor} 0%, ${this.lightenColor(item.bgColor, 10)} 100%)`
-						}))
-					}
-				} catch (error) {
-					console.error('获取金刚区功能列表失败:', error)
-					// 使用默认数据作为降级方案
-					this.functionList = [
-						{ id: 1, name: '星命测算', icon: '💖', path: '/pages/astrology/index', bgColor: '#FF6B9D', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF6B9D 0%, #FF8EAB 100%)' },
-						{ id: 2, name: '红娘列表', icon: '👤', path: '/pages/matchmakers/list', bgColor: '#6BC5F8', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #6BC5F8 0%, #87CEEB 100%)' },
-						{ id: 4, name: '精品课程', icon: '📚', path: '/pages/courses/list', bgColor: '#FF8C42', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF8C42 0%, #FFA366 100%)' },
-						{ id: 5, name: '今日缘分', icon: '💝', path: '/pages/recommend/index', bgColor: '#FF69B4', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF69B4 0%, #FF8CC8 100%)', needLogin: true }
-					]
-				}
-			},
+async loadFunctionGridData() {
+	try {
+		// 调用 API 获取用户端金刚区功能数据
+		const data = await api.home.getFunctionGridByType('user')
+		if (data && data.length > 0) {
+			// 处理金刚区数据,确保字段格式正确
+			this.functionList = data.map(item => ({
+				...item,
+				// 确保渐变背景色格式正确
+				gradient: item.gradient || `linear-gradient(135deg, ${item.bgColor} 0%, ${this.lightenColor(item.bgColor, 10)} 100%)`
+			}))
+		}
+	} catch (error) {
+		console.error('获取金刚区功能列表失败:', error)
+		// 使用默认数据作为降级方案
+		this.functionList = [
+			{ id: 1, name: '星命测算', icon: '💖', path: '/pages/astrology/index', bgColor: '#FF6B9D', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF6B9D 0%, #FF8EAB 100%)' },
+			{ id: 2, name: '红娘列表', icon: '👤', path: '/pages/matchmakers/list', bgColor: '#6BC5F8', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #6BC5F8 0%, #87CEEB 100%)' },
+			{ id: 4, name: '精品课程', icon: '📚', path: '/pages/courses/list', bgColor: '#FF8C42', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF8C42 0%, #FFA366 100%)' },
+			{ id: 5, name: '今日缘分', icon: '💝', path: '/pages/recommend/index', bgColor: '#FF69B4', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF69B4 0%, #FF8CC8 100%)', needLogin: true }
+		]
+	}
+},
 			
 			// 颜色提亮函数(用于生成渐变背景色)
 			lightenColor(color, percent) {

+ 1 - 1
LiangZhiYUMao/pages/matchmaker-workbench/edit-profile.vue

@@ -51,7 +51,7 @@
         <!-- 出生日期 -->
         <view class="form-item">
           <view class="form-label">
-            <text class="label-icon"></text>
+            <text class="label-icon">📅</text>
             <text class="label-text">出生日期</text>
           </view>
           <picker class="form-picker" mode="date" :value="userInfo.birth_date" @change="handleBirthChange">

+ 182 - 104
LiangZhiYUMao/pages/matchmaker-workbench/index.vue

@@ -54,27 +54,16 @@
         <text class="announcement-content">暂无公告</text>
       </view>
 
-      <!-- 功能菜单 -->
-      <view class="menu-grid">
-        <view class="menu-item" @click="navigateToMyResources">
-          <view class="menu-icon resources"></view>
-          <text class="menu-text">我的资源</text>
-        </view>
-        <view class="menu-item" @click="navigateToQualityResources">
-          <view class="menu-icon quality"></view>
-          <text class="menu-text">优质资源</text>
-        </view>
-        <view class="menu-item" @click="navigateToCourses">
-          <view class="menu-icon courses"></view>
-          <text class="menu-text">课程培训</text>
-        </view>
-        <view class="menu-item" @click="navigateToPointsMall">
-          <view class="menu-icon mall"></view>
-          <text class="menu-text">积分商城</text>
-        </view>
-        <view class="menu-item" @click="navigateToActivityCenter">
-          <view class="menu-icon activity"></view>
-          <text class="menu-text">活动中心</text>
+      <!-- 核心功能入口 -->
+      <view class="function-grid">
+        <view class="grid-item" 
+              v-for="item in functionList" 
+              :key="item.id"
+              @click="navigateToFunction(item)">
+          <view class="grid-icon-wrapper" :style="{ background: item.gradient || item.bgColor || '#FFE5F1' }">
+            <text class="grid-icon" :style="{ color: item.iconColor || '#333333' }">{{ item.icon }}</text>
+          </view>
+          <text class="grid-text">{{ item.name }}</text>
         </view>
       </view>
 
@@ -139,18 +128,20 @@ import api from '@/utils/api.js'
 
 export default {
 		data() {
-			return {
-				bestMatchmakers: [],
-				announcements: [],
-				currentAnnouncementIndex: 0,
-				makerName: uni.getStorageSync("userInfo").nickname,
-				matchmakerInfo: {
-					realName: '',
-					avatarUrl: '',
-					successCouples: 0,
-					points: 0
+				return {
+					bestMatchmakers: [],
+					announcements: [],
+					currentAnnouncementIndex: 0,
+					makerName: uni.getStorageSync("userInfo").nickname,
+					matchmakerInfo: {
+						realName: '',
+						avatarUrl: '',
+						successCouples: 0,
+						points: 0
+					},
+					// 金刚区功能列表(从数据库获取)
+					functionList: []
 				}
-			}
 		},
 		computed: {
 			currentAnnouncement() {
@@ -162,10 +153,11 @@ export default {
 			}
 		},
 		onLoad() {
-			this.loadMatchmakerInfo()
-			this.loadRankingData()
-			this.loadAnnouncements()
-		},
+				this.loadMatchmakerInfo()
+				this.loadRankingData()
+				this.loadAnnouncements()
+				this.loadFunctionGridData()
+			},
 		methods: {
 			// 加载当前登录红娘信息
 			async loadMatchmakerInfo() {
@@ -228,22 +220,47 @@ export default {
 				}
 			},
 			// 加载公告数据
-			async loadAnnouncements() {
-				try {
-					const res = await api.home.getNotices()
-					if (res && Array.isArray(res)) {
-						this.announcements = res
-					} else if (res && res.data && Array.isArray(res.data)) {
-						this.announcements = res.data
-					}
-					// 如果有多条公告,自动轮播
-					if (this.announcements.length > 1) {
-						this.startAnnouncementCarousel()
+				async loadAnnouncements() {
+					try {
+						const res = await api.home.getNotices()
+						if (res && Array.isArray(res)) {
+							this.announcements = res
+						} else if (res && res.data && Array.isArray(res.data)) {
+							this.announcements = res.data
+						}
+						// 如果有多条公告,自动轮播
+						if (this.announcements.length > 1) {
+							this.startAnnouncementCarousel()
+						}
+					} catch (e) {
+						console.error('加载公告失败:', e)
 					}
-				} catch (e) {
-					console.error('加载公告失败:', e)
+				},
+					
+				// 加载金刚区功能数据
+				async loadFunctionGridData() {
+			try {
+				// 获取红娘端金刚区功能列表
+				console.log('开始请求金刚区数据...')
+				const res = await api.home.getFunctionGridByType('matchmaker')
+				console.log('金刚区API返回结果:', res)
+				
+				// 由于api.js中的request函数已经处理了响应,res直接是res.data.data或res.data
+				if (Array.isArray(res)) {
+					this.functionList = res
+					console.log('金刚区数据加载成功,共', this.functionList.length, '项:', this.functionList)
+				} else if (res && res.code === 200 && Array.isArray(res.data)) {
+					this.functionList = res.data
+					console.log('金刚区数据加载成功,共', this.functionList.length, '项:', this.functionList)
+				} else {
+					console.error('金刚区API返回数据格式不正确:', res)
+					this.functionList = []
 				}
-			},
+			} catch (e) {
+				console.error('加载金刚区功能失败:', e)
+				this.functionList = []
+			}
+		},
 			// 公告轮播
 			startAnnouncementCarousel() {
 				setInterval(() => {
@@ -283,11 +300,18 @@ export default {
 				})
 			},
 			// 导航到活动中心
-			navigateToActivityCenter() {
-				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/activities'
-				})
-			},
+				navigateToActivityCenter() {
+					uni.navigateTo({
+						url: '/pages/matchmaker-workbench/activities'
+					})
+				},
+					
+				// 通用功能导航方法
+				navigateToFunction(item) {
+					uni.navigateTo({
+						url: item.path
+					})
+				},
 			// 导航到排行榜
 			navigateToRanking() {
 				uni.navigateTo({
@@ -365,8 +389,8 @@ export default {
 
     .search-icon,
     .settings-icon {
-      width: 44rpx;
-      height: 44rpx;
+      width: 33rpx;
+      height: 33rpx;
       background-size: contain;
       background-repeat: no-repeat;
       background-position: center;
@@ -580,61 +604,115 @@ export default {
   }
 }
 
-/* 功能菜单 */
-.menu-grid {
-  display: grid;
-  grid-template-columns: repeat(5, 1fr);
-  gap: 25rpx;
-  padding: 35rpx 20rpx;
-  background: #FFFFFF;
+/* 核心功能入口 */
+.function-grid {
+  display: flex;
+  flex-wrap: nowrap;
+  gap: 20rpx;
+  margin: 20rpx 10rpx;
+  padding: 25rpx 15rpx;
+  background: #fefeff;
   border-radius: 20rpx;
-  margin-bottom: 20rpx;
-  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-
-  .menu-item {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    gap: 15rpx;
+  border: 2rpx solid #f0f0f5;
+  box-shadow: 0 4rpx 16rpx rgba(220, 220, 240, 0.2);
+  box-sizing: border-box;
+}
 
-    .menu-icon {
-      width: 90rpx;
-      height: 90rpx;
-      border-radius: 50%;
-      background-size: 50rpx 50rpx;
-      background-repeat: no-repeat;
-      background-position: center;
+.grid-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 12rpx;
+  padding: 15rpx 8rpx;
+  transition: all 0.3s ease;
+  border-radius: 16rpx;
+  background: linear-gradient(180deg, #fff 0%, #fdfdff 100%);
+  box-shadow: 0 2rpx 8rpx rgba(220, 220, 240, 0.15);
+  flex: 1;
+  min-width: 0;
+  box-sizing: border-box;
+
+  &:hover,
+  &:active {
+    transform: translateY(-4rpx) scale(0.98);
+    background: linear-gradient(180deg, #fff 0%, #f8f8ff 100%);
+    box-shadow: 0 6rpx 18rpx rgba(200, 200, 230, 0.25);
+  }
+}
 
-      &.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>');
-      }
+/* 为不同功能定制图标容器底色(贴合功能主题) */
+.grid-item:nth-child(1) .grid-icon-wrapper {
+  background: linear-gradient(135deg, #ff85a2 0%, #ff5d8f 100%);
+}
+.grid-item:nth-child(2) .grid-icon-wrapper {
+  background: linear-gradient(135deg, #85cfff 0%, #5daaff 100%);
+}
+.grid-item:nth-child(3) .grid-icon-wrapper {
+  background: linear-gradient(135deg, #ffc885 0%, #ffaa5d 100%);
+}
+.grid-item:nth-child(4) .grid-icon-wrapper {
+  background: linear-gradient(135deg, #ff85d2 0%, #ff5daa 100%);
+}
 
-      &.quality {
-        background-color: #FFF3E0;
-        background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23FF9800"><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>');
-      }
+.grid-icon-wrapper {
+  width: 120rpx;
+  height: 120rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 20rpx;
+  position: relative;
+  overflow: hidden;
+  box-shadow: inset 0 3rpx 10rpx rgba(255, 255, 255, 0.4),
+              0 8rpx 24rpx rgba(0, 0, 0, 0.12);
+  transition: all 0.3s ease;
+}
 
-      &.courses {
-        background-color: #E3F2FD;
-        background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%232196F3"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM5 19h14v-2H5v2zm0-4h14v-2H5v2zm0-4h14v-2H5v2z"/></svg>');
-      }
+.grid-icon-wrapper::before {
+  content: '';
+  position: absolute;
+  top: -50%;
+  left: -50%;
+  width: 200%;
+  height: 200%;
+  background: linear-gradient(135deg, 
+    rgba(255, 255, 255, 0.7) 0%, 
+    rgba(255, 255, 255, 0.3) 40%, 
+    transparent 80%);
+  transform: rotate(45deg);
+  pointer-events: none;
+  z-index: 1;
+}
 
-      &.mall {
-        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>');
-      }
+.grid-icon {
+  font-size: 80rpx;
+  color: #fff;
+  position: relative;
+  z-index: 2;
+  filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.15));
+}
 
-      &.activity {
-        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>');
-      }
-    }
+.grid-text {
+  font-size: 22rpx;
+  font-weight: 600;
+  color: #2d2d46;
+  text-align: center;
+  line-height: 1.2;
+  text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  max-width: 100%;
+}
 
-    .menu-text {
-      font-size: 26rpx;
-      color: #333;
-    }
+/* 响应式适配:小屏自动改为2列 */
+@media (max-width: 600rpx) {
+  .grid-item {
+    min-width: calc(50% - 15rpx);
+    max-width: calc(50% - 15rpx);
   }
 }
 

+ 2 - 2
LiangZhiYUMao/pages/matchmaker-workbench/mine.vue

@@ -173,7 +173,7 @@
 
           <view class="sign-in-reward">
             <text class="reward-title">签到奖励</text>
-            <text class="reward-points">+5积分</text>
+            <text class="reward-points">+1积分</text>
           </view>
         </view>
         <view class="popup-footer">
@@ -531,7 +531,7 @@ export default {
 
         if (success) {
           uni.showToast({
-            title: '签到成功,+5积分',
+            title: '签到成功,+1积分',
             icon: 'success'
           })
           this.isSignedToday = true

+ 61 - 21
LiangZhiYUMao/pages/matchmaker-workbench/sign-in.vue

@@ -103,7 +103,8 @@ export default {
 			makerId: null,
 			isSigned: false,
 			calendarDays: [],
-			signedDates: [] // 已签到的日期
+			signedDates: [], // 已签到的日期字符串数组,格式:YYYY-MM-DD
+			currentMonthText: '' // 当前月份显示文本
 		}
 	},
 	computed: {
@@ -170,6 +171,16 @@ export default {
 				const balanceRes = await api.pointsMall.getBalance(this.makerId)
 				this.availablePoints = balanceRes.balance || 0
 				
+				// 获取本月所有签到日期
+				const checkinInfoRes = await api.matchmaker.checkinInfo(this.makerId, this.currentYear, this.currentMonth)
+				let checkinInfo = checkinInfoRes
+				if (checkinInfoRes && checkinInfoRes.data) {
+					checkinInfo = checkinInfoRes.data
+				}
+				
+				// 获取已签到日期列表
+				this.signedDates = checkinInfo.checkedDates || []
+				
 				// 检查今日签到状态
 				const statusRes = await api.matchmaker.checkinStatus(this.makerId)
 				let statusData = statusRes
@@ -177,14 +188,6 @@ export default {
 					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)
 			}
@@ -193,6 +196,9 @@ export default {
 			const year = this.currentYear
 			const month = this.currentMonth
 			
+			// 更新月份显示文本
+			this.currentMonthText = `${year}年${month}月`
+			
 			// 获取当月第一天是星期几
 			const firstDay = new Date(year, month - 1, 1).getDay()
 			// 获取当月天数
@@ -200,6 +206,10 @@ export default {
 			// 获取上月天数
 			const prevMonthDays = new Date(year, month - 1, 0).getDate()
 			
+			// 获取今天的日期字符串
+			const today = new Date()
+			const todayStr = this.formatDate(today)
+			
 			const days = []
 			
 			// 添加上月日期
@@ -213,15 +223,16 @@ export default {
 			}
 			
 			// 添加当月日期
-			const today = new Date().getDate()
-			const currentMonthNow = new Date().getMonth() + 1
-			
 			for (let i = 1; i <= daysInMonth; i++) {
+				const date = new Date(year, month - 1, i)
+				const dateStr = this.formatDate(date)
+				const isToday = dateStr === todayStr
+				
 				days.push({
 					day: i,
 					isOtherMonth: false,
-					isSigned: this.signedDates.includes(i),
-					isToday: i === today && month === currentMonthNow
+					isSigned: this.signedDates.includes(dateStr),
+					isToday: isToday
 				})
 			}
 			
@@ -238,6 +249,13 @@ export default {
 			
 			this.calendarDays = days
 		},
+		// 格式化日期为YYYY-MM-DD
+		formatDate(date) {
+			const year = date.getFullYear()
+			const month = String(date.getMonth() + 1).padStart(2, '0')
+			const day = String(date.getDate()).padStart(2, '0')
+			return `${year}-${month}-${day}`
+		},
 		async handleSignIn() {
 			if (this.isSigned) {
 				uni.showToast({
@@ -254,7 +272,7 @@ export default {
 			
 			try {
 				// 调用后端签到接口
-				await api.matchmaker.doCheckin(this.makerId)
+				const signRes = await api.matchmaker.doCheckin(this.makerId)
 				
 				// 签到成功后更新状态
 				this.isSigned = true
@@ -262,9 +280,10 @@ export default {
 				this.consecutiveDays++
 				
 				// 更新今天的签到状态
-				const today = new Date().getDate()
-				if (!this.signedDates.includes(today)) {
-					this.signedDates.push(today)
+				const today = new Date()
+				const todayStr = this.formatDate(today)
+				if (!this.signedDates.includes(todayStr)) {
+					this.signedDates.push(todayStr)
 				}
 				this.generateCalendar()
 				
@@ -272,6 +291,10 @@ export default {
 				const balanceRes = await api.pointsMall.getBalance(this.makerId)
 				this.availablePoints = balanceRes.balance || 0
 				
+				// 重新加载签到信息,确保数据最新
+				await this.loadSignInData()
+				this.generateCalendar()
+				
 				uni.showToast({
 					title: '签到成功 +5积分',
 					icon: 'success'
@@ -427,16 +450,33 @@ export default {
 			}
 
 			&.signed {
-				background: linear-gradient(135deg, #CE93D8 0%, #BA68C8 100%);
+				background: #F3E5F5;
+				border: 2rpx solid #CE93D8;
 
 				.day-number {
-					color: #FFFFFF;
+					color: #7B1FA2;
 					font-weight: bold;
 				}
 			}
 
 			&.today {
-				border: 2rpx solid #9C27B0;
+				background: #9C27B0;
+				border: 2rpx solid #7B1FA2;
+
+				.day-number {
+					color: #FFFFFF;
+					font-weight: bold;
+				}
+			}
+
+			&.today.signed {
+				background: #9C27B0;
+				border: 2rpx solid #7B1FA2;
+
+				.day-number {
+					color: #FFFFFF;
+					font-weight: bold;
+				}
 			}
 
 			.day-number {

+ 83 - 43
LiangZhiYUMao/pages/mine/index.vue

@@ -9,7 +9,7 @@
             <text class="nickname">{{ userInfo.nickname }}</text>
             <!-- VIP标志 -->
             <view class="vip-badge" v-if="userInfo.isVip">
-              <text class="vip-badge-text">VIP</text>
+              <text class="vip-badge-text">👑</text>
             </view>
             <view class="verify-badge" @click="goVerify" v-if="!userInfo.isVerified">
               <text class="verify-text">未实名,立即认证</text>
@@ -61,17 +61,21 @@
 
     <!-- 快捷入口 -->
     <view class="quick-actions">
-      <view class="action-item" @click="showCheckinPopup">
-        <view class="action-icon calendar-icon">📅</view>
-        <text class="action-label">签到</text>
+      <view class="action-item" @click="goToPage('basicInfo')">
+        <view class="action-icon calendar-icon">🆔</view>
+        <text class="action-label">基本资料</text>
       </view>
-      <view class="action-item" @click="goToPage('myDynamics')">
-        <view class="action-icon heart-icon">📝</view>
-        <text class="action-label">我的动态</text>
+      <view class="action-item" @click="goToPage('partnerRequirement')">
+        <view class="action-icon heart-icon">💜</view>
+        <text class="action-label">对象要求</text>
       </view>
-      <view class="action-item" @click="goToPage('myActivity')">
-        <view class="action-icon phone-icon">📷</view>
-        <text class="action-label">我的活动</text>
+      <view class="action-item" @click="goToPage('verifyRealName')">
+        <view class="action-icon phone-icon">🪪</view>
+        <text class="action-label">实名认证</text>
+      </view>
+      <view class="action-item" @click="goToPage('verifyEducation')">
+        <view class="action-icon phone-icon">🎓</view>
+        <text class="action-label">学历认证</text>
       </view>
     </view>
 
@@ -154,31 +158,39 @@
 
     <!-- 功能菜单列表 -->
     <view class="menu-list">
-
-      <view class="menu-item" @click="goToPage('basicInfo')">
-        <view class="menu-left">
-          <text class="menu-icon">🆔</text>
-          <text class="menu-text">基本资料</text>
-        </view>
-        <text class="menu-arrow">›</text>
-      </view>
-
-      <view class="menu-item" @click="goToPage('partnerRequirement')">
-        <view class="menu-left">
-          <text class="menu-icon">💜</text>
-          <text class="menu-text">对象要求</text>
-        </view>
-        <text class="menu-arrow">›</text>
-      </view>
-
-      <view class="menu-item" @click="goToPage('partTimeMatchmaker')">
-        <view class="menu-left">
-          <text class="menu-icon">💎</text>
-          <text class="menu-text">兼职红娘</text>
-        </view>
-        <text class="menu-arrow">›</text>
-      </view>
-
+		
+		<view class="menu-item" @click="showCheckinPopup">
+		  <view class="menu-left">
+		    <text class="menu-icon">📅</text>
+		    <text class="menu-text">签到</text>
+		  </view>
+		  <text class="menu-arrow">›</text>
+		</view>
+		
+		<view class="menu-item" @click="goToPage('myDynamics')">
+		  <view class="menu-left">
+		    <text class="menu-icon">📝</text>
+		    <text class="menu-text">我的动态</text>
+		  </view>
+		  <text class="menu-arrow">›</text>
+		</view>
+		
+		<view class="menu-item" @click="goToPage('myActivity')">
+		  <view class="menu-left">
+		    <text class="menu-icon">📷</text>
+		    <text class="menu-text">我的活动</text>
+		  </view>
+		  <text class="menu-arrow">›</text>
+		</view>
+		
+		<view class="menu-item" @click="goToPage('partTimeMatchmaker')">
+		  <view class="menu-left">
+		    <text class="menu-icon">💎</text>
+		    <text class="menu-text">兼职红娘</text>
+		  </view>
+		  <text class="menu-arrow">›</text>
+		</view>
+		
       <view class="menu-item" @click="goToPage('feedback')">
         <view class="menu-left">
           <text class="menu-icon">💬</text>
@@ -872,6 +884,38 @@ export default {
               })
             }
           })
+        } else if (page === 'verifyRealName') {
+          console.log('✅ 跳转到实名认证页面')
+          // 跳转到实名认证页面
+          uni.navigateTo({
+            url: '/pages/settings/id-verification',
+            success: () => {
+              console.log('✅ 实名认证页面跳转成功')
+            },
+            fail: (err) => {
+              console.error('❌ 实名认证页面跳转失败:', err)
+              uni.showToast({
+                title: '页面跳转失败',
+                icon: 'none'
+              })
+            }
+          })
+        } else if (page === 'verifyEducation') {
+          console.log('✅ 跳转到学历认证页面')
+          // 跳转到实名认证页面
+          uni.navigateTo({
+            url: '/pages/settings/id-verification',
+            success: () => {
+              console.log('✅ 学历认证页面跳转成功')
+            },
+            fail: (err) => {
+              console.error('❌ 学历认证页面跳转失败:', err)
+              uni.showToast({
+                title: '页面跳转失败',
+                icon: 'none'
+              })
+            }
+          })
         } else if (page === 'partTimeMatchmaker') {
           console.log('✅ 跳转到兼职红娘页面')
           // 跳转到兼职红娘页面
@@ -1325,17 +1369,13 @@ export default {
 
         /* VIP标志样式 */
         .vip-badge {
-          background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
-          padding: 6rpx 16rpx;
-          border-radius: 6rpx;
           margin-right: 12rpx;
-          box-shadow: 0 2rpx 8rpx rgba(255, 215, 0, 0.3);
+          display: flex;
+          align-items: center;
 
           .vip-badge-text {
-            font-size: 20rpx;
-            color: #FFFFFF;
-            font-weight: 700;
-            letter-spacing: 1rpx;
+            font-size: 48rpx;
+            display: inline-block;
           }
         }
 

+ 1 - 1
LiangZhiYUMao/pages/mine/my-activities.vue

@@ -296,7 +296,7 @@ export default {
 		// 跳转到活动详情
 		goToActivityDetail(activity) {
 			uni.navigateTo({
-				url: `/pages/activities/detail?id=${activity.id}`
+				url: `/pages/activities/detail?id=${activity.activityId}`
 			})
 		},
 		

+ 56 - 13
LiangZhiYUMao/pages/mine/my-dynamics.vue

@@ -216,14 +216,30 @@ export default {
             // 浏览记录列表数据
             browseList: [],
             // 默认头像
-            defaultAvatar: 'https://via.placeholder.com/100x100.png?text=头像'
+            defaultAvatar: 'https://via.placeholder.com/100x100.png?text=头像',
+            // 我的动态分页相关
+            dynamicPageNum: 1,
+            dynamicPageSize: 10,
+            dynamicLoading: false,
+            dynamicNoMore: false
         }
     },
   onLoad() {
     // 页面加载时获取用户信息和动态数据
     this.loadUserInfo();
+    // 初始化分页状态并加载第一页数据
+    this.dynamicPageNum = 1;
+    this.dynamicNoMore = false;
+    this.dynamicList = [];
     this.loadDynamicData();
   },
+  // 页面触底加载更多
+  onReachBottom() {
+    // 仅在“动态”标签下并且还有更多数据时加载
+    if (this.activeTab === 'dynamic' && !this.dynamicNoMore && !this.dynamicLoading) {
+      this.loadDynamicData();
+    }
+  },
   methods: {
         // 返回上一页
         goBack() {
@@ -248,6 +264,11 @@ export default {
       this.activeTab = tab;
       // 根据标签页加载对应数据
       if (tab === 'dynamic') {
+        // 切换回“动态”时,如果还没有数据或之前已经加载完一部分,继续使用当前分页状态
+        if (this.dynamicList.length === 0) {
+          this.dynamicPageNum = 1;
+          this.dynamicNoMore = false;
+        }
         this.loadDynamicData();
       } else if (tab === 'interaction') {
         this.loadInteractionData();
@@ -295,20 +316,42 @@ export default {
       }
     },
     // 加载动态数据
-    loadDynamicData() {
+    async loadDynamicData() {
       const userInfo = uni.getStorageSync('userInfo');
-      if (userInfo && userInfo.userId) {
-        // 获取用户发布的动态列表
-        api.dynamic.getUserDynamics(userInfo.userId, {
-          pageNum: 1,
-          pageSize: 10
-        }).then(res => {
-          if (res && res.records) {
-            this.dynamicList = res.records;
-          }
-        }).catch(err => {
-          console.error('获取用户动态列表失败:', err);
+      if (!userInfo || !userInfo.userId) {
+        return;
+      }
+      if (this.dynamicLoading || this.dynamicNoMore) {
+        return;
+      }
+
+      this.dynamicLoading = true;
+      try {
+        // 获取用户发布的动态列表(分页)
+        const res = await api.dynamic.getUserDynamics(userInfo.userId, {
+          pageNum: this.dynamicPageNum,
+          pageSize: this.dynamicPageSize
         });
+
+        if (res && res.records) {
+          const records = res.records || [];
+          if (this.dynamicPageNum === 1) {
+            this.dynamicList = records;
+          } else {
+            this.dynamicList = [...this.dynamicList, ...records];
+          }
+
+          // 根据返回数量判断是否还有更多
+          if (!records || records.length < this.dynamicPageSize) {
+            this.dynamicNoMore = true;
+          } else {
+            this.dynamicPageNum += 1;
+          }
+        }
+      } catch (err) {
+        console.error('获取用户动态列表失败:', err);
+      } finally {
+        this.dynamicLoading = false;
       }
     },
     // 加载互动数据

+ 1 - 1
LiangZhiYUMao/pages/settings/index.vue

@@ -14,7 +14,7 @@
 	      scroll-y 
 	      class="content"
 	      scroll-with-animation  <!-- 滚动动画(可选) -->
-	    >
+	    
 	      <!-- 设置列表 -->
 	      <view class="settings-list">
 	        <view class="setting-item" @click="goToPage('bindPhone')">

+ 3 - 0
LiangZhiYUMao/utils/api.js

@@ -193,6 +193,9 @@ export default {
     // 获取首页金刚区功能列表
     getFunctionGrid: () => request({ url: '/home/function-grid' }),
     
+    // 根据类型获取金刚区功能列表
+    getFunctionGridByType: (type) => request({ url: `/home/function-grid/type?type=${type}` }),
+    
     // 获取未读消息数
     getUnreadCount: () => request({ url: '/home/unread-count' })
   },

+ 374 - 0
LiangZhiYUMao/utils/fortune.js

@@ -0,0 +1,374 @@
+/**
+ * 运势计算工具类
+ * 基于用户资料(生日、星座、生肖)计算婚恋运势、脱单指数、幸运推荐
+ */
+
+import { getZodiacByBirthday, getZodiacInfo } from './zodiac.js'
+
+// 星座数据
+const CONSTELLATIONS = {
+	'白羊座': { element: '火', startDate: '03-21', endDate: '04-19', luckyColors: ['红色', '橙色'], luckyNumbers: [1, 9], personality: '热情冲动' },
+	'金牛座': { element: '土', startDate: '04-20', endDate: '05-20', luckyColors: ['绿色', '粉色'], luckyNumbers: [2, 6], personality: '稳重踏实' },
+	'双子座': { element: '风', startDate: '05-21', endDate: '06-21', luckyColors: ['黄色', '蓝色'], luckyNumbers: [3, 5], personality: '机智多变' },
+	'巨蟹座': { element: '水', startDate: '06-22', endDate: '07-22', luckyColors: ['白色', '银色'], luckyNumbers: [2, 7], personality: '温柔体贴' },
+	'狮子座': { element: '火', startDate: '07-23', endDate: '08-22', luckyColors: ['金色', '橙色'], luckyNumbers: [1, 5], personality: '自信大方' },
+	'处女座': { element: '土', startDate: '08-23', endDate: '09-22', luckyColors: ['灰色', '米色'], luckyNumbers: [4, 8], personality: '细心完美' },
+	'天秤座': { element: '风', startDate: '09-23', endDate: '10-23', luckyColors: ['粉色', '蓝色'], luckyNumbers: [6, 9], personality: '优雅和谐' },
+	'天蝎座': { element: '水', startDate: '10-24', endDate: '11-22', luckyColors: ['深红', '黑色'], luckyNumbers: [3, 9], personality: '神秘深情' },
+	'射手座': { element: '火', startDate: '11-23', endDate: '12-21', luckyColors: ['紫色', '蓝色'], luckyNumbers: [3, 7], personality: '乐观自由' },
+	'摩羯座': { element: '土', startDate: '12-22', endDate: '01-19', luckyColors: ['黑色', '深蓝'], luckyNumbers: [4, 8], personality: '务实稳重' },
+	'水瓶座': { element: '风', startDate: '01-20', endDate: '02-18', luckyColors: ['蓝色', '银色'], luckyNumbers: [4, 7], personality: '独立创新' },
+	'双鱼座': { element: '水', startDate: '02-19', endDate: '03-20', luckyColors: ['海蓝', '紫色'], luckyNumbers: [3, 9], personality: '浪漫敏感' }
+}
+
+// 约会建议库
+const DATE_SUGGESTIONS = [
+	'浪漫晚餐', '咖啡约会', '电影之夜', '公园散步', '逛街购物',
+	'看展览', '户外野餐', '游乐园', '密室逃脱', '烘焙体验',
+	'书店约会', '音乐会', '运动健身', '美食探店', '手工DIY'
+]
+
+// 穿搭风格库
+const STYLE_SUGGESTIONS = [
+	'清新甜美', '优雅知性', '休闲运动', '简约大方', '时尚潮流',
+	'温柔淑女', '帅气中性', '复古文艺', '可爱减龄', '成熟稳重'
+]
+
+// 幸运色库
+const LUCKY_COLORS = [
+	'粉红色', '天蓝色', '薄荷绿', '珊瑚橙', '薰衣草紫',
+	'米白色', '浅黄色', '玫瑰红', '湖蓝色', '杏色'
+]
+
+/**
+ * 根据生日获取星座
+ * @param {String} birthday - 生日,格式:YYYY-MM-DD
+ * @returns {String} 星座名称
+ */
+export function getConstellationByBirthday(birthday) {
+	if (!birthday) return null
+	
+	const parts = birthday.split('-')
+	const month = parseInt(parts[1])
+	const day = parseInt(parts[2])
+	const mmdd = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
+	
+	for (const [name, info] of Object.entries(CONSTELLATIONS)) {
+		const start = info.startDate
+		const end = info.endDate
+		
+		// 处理跨年的星座(摩羯座)
+		if (start > end) {
+			if (mmdd >= start || mmdd <= end) return name
+		} else {
+			if (mmdd >= start && mmdd <= end) return name
+		}
+	}
+	return '未知'
+}
+
+
+/**
+ * 获取基于日期的种子值(确保同一天结果一致)
+ * @param {Date} date - 日期
+ * @param {String} salt - 盐值(用于区分不同计算)
+ * @returns {Number} 种子值
+ */
+function getDaySeed(date, salt = '') {
+	const dateStr = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${salt}`
+	let hash = 0
+	for (let i = 0; i < dateStr.length; i++) {
+		const char = dateStr.charCodeAt(i)
+		hash = ((hash << 5) - hash) + char
+		hash = hash & hash
+	}
+	return Math.abs(hash)
+}
+
+/**
+ * 基于种子的伪随机数生成器
+ * @param {Number} seed - 种子值
+ * @param {Number} min - 最小值
+ * @param {Number} max - 最大值
+ * @returns {Number} 随机数
+ */
+function seededRandom(seed, min = 0, max = 100) {
+	const x = Math.sin(seed) * 10000
+	const random = x - Math.floor(x)
+	return Math.floor(random * (max - min + 1)) + min
+}
+
+/**
+ * 计算用户的婚恋运势
+ * @param {Object} userInfo - 用户信息 { birthDate, star, animal, gender }
+ * @returns {Array} 运势数组
+ */
+export function calculateLoveFortune(userInfo) {
+	const today = new Date()
+	const birthDate = userInfo.birthDate || userInfo.birth_date
+	
+	// 获取星座和生肖
+	const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
+	const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
+	
+	// 基础分数
+	let baseScore = 70
+	
+	// 根据星座元素调整基础分
+	if (constellation && CONSTELLATIONS[constellation]) {
+		const element = CONSTELLATIONS[constellation].element
+		const dayOfWeek = today.getDay()
+		
+		// 不同元素在不同日子有不同运势
+		if (element === '火' && (dayOfWeek === 2 || dayOfWeek === 4)) baseScore += 5
+		if (element === '土' && (dayOfWeek === 0 || dayOfWeek === 6)) baseScore += 5
+		if (element === '风' && (dayOfWeek === 1 || dayOfWeek === 3)) baseScore += 5
+		if (element === '水' && (dayOfWeek === 5)) baseScore += 5
+	}
+	
+	// 使用日期种子确保同一天结果一致
+	const seed1 = getDaySeed(today, `${constellation}-peach`)
+	const seed2 = getDaySeed(today, `${zodiac}-love`)
+	const seed3 = getDaySeed(today, `${constellation}-charm`)
+	
+	// 计算三项运势分数
+	const peachScore = Math.min(95, Math.max(60, baseScore + seededRandom(seed1, -10, 20)))
+	const loveScore = Math.min(95, Math.max(55, baseScore + seededRandom(seed2, -15, 18)))
+	const charmScore = Math.min(98, Math.max(65, baseScore + seededRandom(seed3, -5, 25)))
+	
+	return [
+		{
+			label: '桃花运',
+			icon: '🌸',
+			score: peachScore,
+			color: 'linear-gradient(135deg, #FF6B9D 0%, #FFA5C6 100%)'
+		},
+		{
+			label: '爱情指数',
+			icon: '💕',
+			score: loveScore,
+			color: 'linear-gradient(135deg, #E91E63 0%, #FF6B9D 100%)'
+		},
+		{
+			label: '魅力值',
+			icon: '✨',
+			score: charmScore,
+			color: 'linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)'
+		}
+	]
+}
+
+/**
+ * 计算本周脱单指数
+ * @param {Object} userInfo - 用户信息
+ * @returns {Object} 脱单指数数据
+ */
+export function calculateSingleIndex(userInfo) {
+	const today = new Date()
+	const birthDate = userInfo.birthDate || userInfo.birth_date
+	
+	// 获取星座和生肖
+	const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
+	const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
+	
+	// 获取本周的周一作为种子基准
+	const weekStart = new Date(today)
+	weekStart.setDate(today.getDate() - today.getDay() + 1)
+	
+	const seed = getDaySeed(weekStart, `${constellation}-${zodiac}-single`)
+	
+	// 计算基础分数
+	let score = seededRandom(seed, 65, 95)
+	
+	// 根据生肖特性微调
+	if (zodiac) {
+		const zodiacInfo = getZodiacInfo(zodiac)
+		if (zodiacInfo) {
+			// 社交型生肖加分
+			if (['鼠', '马', '猴', '猪'].includes(zodiac)) score += 3
+			// 内敛型生肖略减
+			if (['牛', '蛇', '羊'].includes(zodiac)) score -= 2
+		}
+	}
+	
+	score = Math.min(98, Math.max(50, score))
+	
+	// 确定等级
+	let level, levelColor
+	if (score >= 90) {
+		level = '极佳'
+		levelColor = 'linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%)'
+	} else if (score >= 80) {
+		level = '很好'
+		levelColor = 'linear-gradient(135deg, #4CAF50 0%, #8BC34A 100%)'
+	} else if (score >= 70) {
+		level = '良好'
+		levelColor = 'linear-gradient(135deg, #2196F3 0%, #03A9F4 100%)'
+	} else if (score >= 60) {
+		level = '一般'
+		levelColor = 'linear-gradient(135deg, #FF9800 0%, #FFC107 100%)'
+	} else {
+		level = '需努力'
+		levelColor = 'linear-gradient(135deg, #9E9E9E 0%, #BDBDBD 100%)'
+	}
+	
+	// 计算最佳约会日
+	const bestDaySeed = getDaySeed(weekStart, `${constellation}-bestday`)
+	const bestDayIndex = seededRandom(bestDaySeed, 0, 6)
+	const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
+	const bestDay = weekDays[bestDayIndex]
+	
+	// 计算最佳时间段
+	const timeSeed = getDaySeed(weekStart, `${zodiac}-time`)
+	const timeSlots = ['上午10-12点', '下午2-5点', '傍晚5-7点', '晚上7-9点']
+	const bestTime = timeSlots[seededRandom(timeSeed, 0, 3)]
+	
+	// 推荐地点
+	const placeSeed = getDaySeed(weekStart, `${constellation}-place`)
+	const places = [
+		'咖啡厅、书店', '公园、广场', '商场、电影院', 
+		'餐厅、美食街', '展览馆、博物馆', '游乐园、景区'
+	]
+	const bestPlace = places[seededRandom(placeSeed, 0, 5)]
+	
+	return {
+		score,
+		level,
+		levelColor,
+		tips: [
+			{ icon: '📅', text: `最佳约会日:${bestDay}` },
+			{ icon: '⏰', text: `最佳时间:${bestTime}` },
+			{ icon: '📍', text: `推荐地点:${bestPlace}` }
+		]
+	}
+}
+
+
+/**
+ * 计算今日幸运推荐
+ * @param {Object} userInfo - 用户信息
+ * @returns {Array} 幸运推荐数组
+ */
+export function calculateLuckyRecommend(userInfo) {
+	const today = new Date()
+	const birthDate = userInfo.birthDate || userInfo.birth_date
+	
+	// 获取星座
+	const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
+	const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
+	
+	// 幸运色
+	let luckyColor = '粉红色'
+	if (constellation && CONSTELLATIONS[constellation]) {
+		const constellationColors = CONSTELLATIONS[constellation].luckyColors
+		const colorSeed = getDaySeed(today, `${constellation}-color`)
+		luckyColor = constellationColors[seededRandom(colorSeed, 0, constellationColors.length - 1)]
+	} else {
+		const colorSeed = getDaySeed(today, 'default-color')
+		luckyColor = LUCKY_COLORS[seededRandom(colorSeed, 0, LUCKY_COLORS.length - 1)]
+	}
+	
+	// 幸运数字
+	let luckyNumber = '7'
+	if (constellation && CONSTELLATIONS[constellation]) {
+		const constellationNumbers = CONSTELLATIONS[constellation].luckyNumbers
+		const numberSeed = getDaySeed(today, `${constellation}-number`)
+		luckyNumber = constellationNumbers[seededRandom(numberSeed, 0, constellationNumbers.length - 1)].toString()
+	} else {
+		const numberSeed = getDaySeed(today, 'default-number')
+		luckyNumber = seededRandom(numberSeed, 1, 9).toString()
+	}
+	
+	// 约会建议
+	const dateSeed = getDaySeed(today, `${zodiac}-date`)
+	const dateSuggestion = DATE_SUGGESTIONS[seededRandom(dateSeed, 0, DATE_SUGGESTIONS.length - 1)]
+	
+	// 穿搭风格
+	const styleSeed = getDaySeed(today, `${constellation}-style`)
+	const styleSuggestion = STYLE_SUGGESTIONS[seededRandom(styleSeed, 0, STYLE_SUGGESTIONS.length - 1)]
+	
+	return [
+		{
+			label: '幸运色',
+			icon: '🎨',
+			value: luckyColor,
+			bgColor: 'linear-gradient(135deg, #FFB6C1 0%, #FFE4E1 100%)'
+		},
+		{
+			label: '幸运数字',
+			icon: '🔢',
+			value: luckyNumber,
+			bgColor: 'linear-gradient(135deg, #9C27B0 0%, #CE93D8 100%)'
+		},
+		{
+			label: '约会建议',
+			icon: '💡',
+			value: dateSuggestion,
+			bgColor: 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)'
+		},
+		{
+			label: '穿搭风格',
+			icon: '👗',
+			value: styleSuggestion,
+			bgColor: 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)'
+		}
+	]
+}
+
+/**
+ * 获取用户完整的运势数据
+ * @param {Object} userInfo - 用户信息
+ * @returns {Object} 完整运势数据
+ */
+export function getFullFortuneData(userInfo) {
+	// 检查用户是否有足够的资料
+	const birthDate = userInfo.birthDate || userInfo.birth_date
+	const hasProfile = birthDate || userInfo.star || userInfo.animal
+	
+	if (!hasProfile) {
+		// 返回默认数据并提示完善资料
+		return {
+			hasProfile: false,
+			message: '完善个人资料后可查看专属运势',
+			loveFortune: [
+				{ label: '桃花运', icon: '🌸', score: 75, color: 'linear-gradient(135deg, #FF6B9D 0%, #FFA5C6 100%)' },
+				{ label: '爱情指数', icon: '💕', score: 70, color: 'linear-gradient(135deg, #E91E63 0%, #FF6B9D 100%)' },
+				{ label: '魅力值', icon: '✨', score: 80, color: 'linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)' }
+			],
+			singleIndex: {
+				score: 75,
+				level: '良好',
+				levelColor: 'linear-gradient(135deg, #2196F3 0%, #03A9F4 100%)',
+				tips: [
+					{ icon: '📅', text: '最佳约会日:周六' },
+					{ icon: '⏰', text: '最佳时间:下午2-5点' },
+					{ icon: '📍', text: '推荐地点:咖啡厅、公园' }
+				]
+			},
+			luckyRecommend: [
+				{ label: '幸运色', icon: '🎨', value: '粉红色', bgColor: 'linear-gradient(135deg, #FFB6C1 0%, #FFE4E1 100%)' },
+				{ label: '幸运数字', icon: '🔢', value: '7', bgColor: 'linear-gradient(135deg, #9C27B0 0%, #CE93D8 100%)' },
+				{ label: '约会建议', icon: '💡', value: '浪漫晚餐', bgColor: 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)' },
+				{ label: '穿搭风格', icon: '👗', value: '清新甜美', bgColor: 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)' }
+			]
+		}
+	}
+	
+	return {
+		hasProfile: true,
+		constellation: userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null),
+		zodiac: userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null),
+		loveFortune: calculateLoveFortune(userInfo),
+		singleIndex: calculateSingleIndex(userInfo),
+		luckyRecommend: calculateLuckyRecommend(userInfo)
+	}
+}
+
+export default {
+	getConstellationByBirthday,
+	calculateLoveFortune,
+	calculateSingleIndex,
+	calculateLuckyRecommend,
+	getFullFortuneData,
+	CONSTELLATIONS
+}

+ 2 - 2
gateway/src/main/resources/application.yml

@@ -93,7 +93,7 @@ spring:
         - id: announcement-route
           uri: http://localhost:8081
           predicates:
-            - Path=/api/announcement/**
+            - Path=/api/announcement/**, /api/home/**
           filters:
             - StripPrefix=0
         
@@ -133,7 +133,7 @@ spring:
         - id: essential-route
           uri: http://localhost:1005
           predicates:
-            - Path=/api/user/**, /api/checkin/**, /api/vip/**, /api/activity-order/**
+            - Path=/api/user/**, /api/checkin/**, /api/vip/**, /api/activity-order/**, /api/activity-registration/**
           filters:
             - StripPrefix=0
         

+ 10 - 1
marriageAdmin-vue/src/assets/list-common.css

@@ -64,6 +64,8 @@
 .toolbar-card .el-button {
   transition: all var(--transition-base);
   font-weight: var(--font-medium);
+  white-space: nowrap; /* 防止文字换行 */
+  padding: 8px 15px; /* 确保按钮有足够的padding */
 }
 
 .toolbar-card .el-button--primary {
@@ -73,8 +75,15 @@
   color: #ffffff !important;  /* 确保按钮文字为白色 */
 }
 
+.toolbar-card .el-button--primary,
+.toolbar-card .el-button--primary * {
+  color: #ffffff !important;  /* 确保按钮所有文字为白色 */
+}
+
 .toolbar-card .el-button--primary span {
-  color: #ffffff !important;  /* 确保按钮内部文字为白色 */
+  display: inline !important; /* 确保span元素正常显示 */
+  visibility: visible !important; /* 确保文字可见 */
+  opacity: 1 !important; /* 确保文字不透明 */
 }
 
 .toolbar-card .el-button--primary .el-icon {

+ 8 - 1
marriageAdmin-vue/src/config/api.js

@@ -22,6 +22,9 @@ export const API_ENDPOINTS = {
   ADMIN_USER_DISABLE: '/admin/admin-user/disable',
   ADMIN_USER_ENABLE: '/admin/admin-user/enable',
   ADMIN_USER_ROLES: '/admin/admin-user/roles',
+  ADMIN_USER_CURRENT_PROFILE: '/admin/admin-user/current-profile',
+  ADMIN_USER_UPDATE_CURRENT_PROFILE: '/admin/admin-user/current-profile',
+  ADMIN_USER_UPDATE_CURRENT_PASSWORD: '/admin/admin-user/current-password',
   
   // 轮播图管理
   BANNER_LIST: '/admin/banner/list',
@@ -37,7 +40,9 @@ export const API_ENDPOINTS = {
   ACTIVITY_CREATE: '/admin/activity/create',
   ACTIVITY_UPDATE: '/admin/activity/update',
   ACTIVITY_DELETE: '/admin/activity/delete',
-  ACTIVITY_REGISTRATIONS: '/admin/activity/registrations',
+  ACTIVITY_REGISTRATIONS: '/admin/activity/registration/list',
+  ACTIVITY_REGISTRATION_STATS: '/admin/activity/registration/stats',
+  ACTIVITY_REGISTRATION_EXPORT: '/admin/activity/registration/export',
   ACTIVITY_STATS: '/admin/activity/stats',
   
   // 红娘管理
@@ -88,6 +93,8 @@ export const API_ENDPOINTS = {
   USER_UPDATE: '/admin/user/update',
   USER_STATS: '/admin/user/stats',
   USER_VIP_LIST: '/admin/user/vip/list',
+  // VIP 与系统消息
+  USER_VIP_REMIND: '/admin/user/vip/remind',
   
   // 动态管理
   DYNAMIC_LIST: '/admin/dynamic/list',

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

@@ -129,11 +129,10 @@
         <div class="header-right">
           <span class="username">{{ userInfo?.username || '管理员' }}</span>
           <el-dropdown @command="handleCommand">
-            <el-avatar :size="35" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" />
+            <el-avatar :size="35" :src="userInfo?.avatar || 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'" />
             <template #dropdown>
               <el-dropdown-menu>
                 <el-dropdown-item command="profile">个人资料</el-dropdown-item>
-                <el-dropdown-item command="setting">系统设置</el-dropdown-item>
                 <el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
               </el-dropdown-menu>
             </template>
@@ -193,10 +192,7 @@ const toggleCollapse = () => {
 const handleCommand = async (command) => {
   switch (command) {
     case 'profile':
-      ElMessage.info('个人资料功能开发中')
-      break
-    case 'setting':
-      ElMessage.info('系统设置功能开发中')
+      router.push('/profile')
       break
     case 'logout':
       try {

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

@@ -244,6 +244,13 @@ const router = createRouter({
           name: 'Report',
           component: () => import('@/views/report/ReportList.vue'),
           meta: { title: '举报管理', icon: 'Warning' }
+        },
+        // 个人资料
+        {
+          path: 'profile',
+          name: 'Profile',
+          component: () => import('@/views/Profile.vue'),
+          meta: { title: '个人资料', hidden: true }
         }
       ]
     },

+ 36 - 20
marriageAdmin-vue/src/utils/request.js

@@ -19,6 +19,18 @@ const showErrorMessage = (message) => {
   }
 }
 
+// 清除 token 并跳转登录页(用于服务重启等情况)
+const clearTokenAndRedirect = (message = '服务已重启,请重新登录') => {
+  showErrorMessage(message)
+  localStorage.removeItem('admin_token')
+  localStorage.removeItem('admin_user')
+  // 使用 replace 避免返回时再次触发
+  router.replace('/login').catch(() => {
+    // 如果路由跳转失败,强制刷新页面到登录页
+    window.location.href = '/login'
+  })
+}
+
 // 创建 axios 实例
 const request = axios.create({
   baseURL: API_BASE_URL,
@@ -47,6 +59,11 @@ request.interceptors.request.use(
 // 响应拦截器
 request.interceptors.response.use(
   response => {
+    // 如果是blob响应(文件下载),直接返回
+    if (response.config.responseType === 'blob') {
+      return response.data
+    }
+    
     const res = response.data
     
     // 根据后端返回的结构判断
@@ -54,17 +71,11 @@ request.interceptors.response.use(
       return res
     } else if (res.code === 401) {
       // 未授权,跳转登录
-      showErrorMessage(res.msg || '未授权,请重新登录')
-      localStorage.removeItem('admin_token')
-      localStorage.removeItem('admin_user')
-      router.push('/login')
+      clearTokenAndRedirect(res.msg || '未授权,请重新登录')
       return Promise.reject(new Error(res.msg || '未授权'))
     } else if (res.code === 403) {
       // 拒绝访问,清除token并跳转登录
-      showErrorMessage(res.msg || '拒绝访问,请重新登录')
-      localStorage.removeItem('admin_token')
-      localStorage.removeItem('admin_user')
-      router.push('/login')
+      clearTokenAndRedirect(res.msg || '拒绝访问,请重新登录')
       return Promise.reject(new Error(res.msg || '拒绝访问'))
     } else {
       // 其他错误
@@ -89,31 +100,36 @@ request.interceptors.response.use(
       
       switch (status) {
         case 401:
-          showErrorMessage('未授权,请重新登录')
-          localStorage.removeItem('admin_token')
-          localStorage.removeItem('admin_user')
-          router.push('/login')
+          clearTokenAndRedirect('未授权,请重新登录')
           break
         case 403:
-          showErrorMessage('拒绝访问,请重新登录')
-          localStorage.removeItem('admin_token')
-          localStorage.removeItem('admin_user')
-          router.push('/login')
+          clearTokenAndRedirect('拒绝访问,请重新登录')
           break
         case 404:
           // 只对API请求显示404提示
           showErrorMessage('请求的资源不存在')
           break
         case 500:
-          showErrorMessage('服务器内部错误')
+          // 500错误可能是服务重启,清除token并跳转登录
+          clearTokenAndRedirect('服务器错误,请重新登录')
+          break
+        case 502:
+          // 502 Bad Gateway - 通常表示后端服务不可用(服务重启)
+          clearTokenAndRedirect('服务已重启,请重新登录')
+          break
+        case 503:
+          // 503 Service Unavailable - 服务不可用(服务重启)
+          clearTokenAndRedirect('服务已重启,请重新登录')
           break
         default:
           showErrorMessage(error.response.data?.msg || '请求失败')
       }
     } else if (error.code === 'ECONNABORTED') {
-      showErrorMessage('请求超时,请重试')
-    } else if (error.message === 'Network Error') {
-      showErrorMessage('网络连接失败')
+      // 连接超时,可能是服务重启
+      clearTokenAndRedirect('连接超时,服务可能已重启,请重新登录')
+    } else if (error.message === 'Network Error' || error.code === 'ERR_NETWORK') {
+      // 网络连接失败,可能是服务重启
+      clearTokenAndRedirect('网络连接失败,服务可能已重启,请重新登录')
     }
     
     return Promise.reject(error)

+ 370 - 0
marriageAdmin-vue/src/views/Profile.vue

@@ -0,0 +1,370 @@
+<template>
+  <div class="profile-container">
+    <el-card class="profile-card">
+      <template #header>
+        <div class="card-header">
+          <h2>个人资料</h2>
+        </div>
+      </template>
+      
+      <el-tabs v-model="activeTab" type="border-card">
+        <!-- 基本信息 -->
+        <el-tab-pane label="基本信息" name="basic">
+          <el-form
+            ref="profileFormRef"
+            :model="profileForm"
+            :rules="profileRules"
+            label-width="120px"
+            style="max-width: 600px"
+          >
+            <el-form-item label="头像">
+              <div class="avatar-upload">
+                <el-avatar
+                  :size="100"
+                  :src="profileForm.avatar || defaultAvatar"
+                  class="avatar-preview"
+                >
+                  <el-icon><User /></el-icon>
+                </el-avatar>
+                <el-upload
+                  class="avatar-uploader"
+                  action="#"
+                  :show-file-list="false"
+                  :before-upload="beforeAvatarUpload"
+                  :http-request="handleAvatarUpload"
+                >
+                  <el-button type="primary" size="small">上传头像</el-button>
+                </el-upload>
+                <div class="avatar-tip">支持 JPG、PNG 格式,大小不超过 2MB</div>
+              </div>
+            </el-form-item>
+            
+            <el-form-item label="用户名">
+              <el-input v-model="profileForm.username" disabled />
+            </el-form-item>
+            
+            <el-form-item label="真实姓名" prop="realName">
+              <el-input v-model="profileForm.realName" placeholder="请输入真实姓名" />
+            </el-form-item>
+            
+            <el-form-item label="手机号" prop="phone">
+              <el-input v-model="profileForm.phone" placeholder="请输入手机号" />
+            </el-form-item>
+            
+            <el-form-item label="邮箱" prop="email">
+              <el-input v-model="profileForm.email" placeholder="请输入邮箱" />
+            </el-form-item>
+            
+            <el-form-item>
+              <el-button type="primary" @click="handleSaveProfile" :loading="saving">
+                保存
+              </el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+        
+        <!-- 修改密码 -->
+        <el-tab-pane label="修改密码" name="password">
+          <el-form
+            ref="passwordFormRef"
+            :model="passwordForm"
+            :rules="passwordRules"
+            label-width="120px"
+            style="max-width: 600px"
+          >
+            <el-form-item label="原密码" prop="oldPassword">
+              <el-input
+                v-model="passwordForm.oldPassword"
+                type="password"
+                placeholder="请输入原密码"
+                show-password
+              />
+            </el-form-item>
+            
+            <el-form-item label="新密码" prop="newPassword">
+              <el-input
+                v-model="passwordForm.newPassword"
+                type="password"
+                placeholder="请输入新密码(至少6位)"
+                show-password
+              />
+            </el-form-item>
+            
+            <el-form-item label="确认新密码" prop="confirmPassword">
+              <el-input
+                v-model="passwordForm.confirmPassword"
+                type="password"
+                placeholder="请再次输入新密码"
+                show-password
+              />
+            </el-form-item>
+            
+            <el-form-item>
+              <el-button type="primary" @click="handleChangePassword" :loading="changingPassword">
+                修改密码
+              </el-button>
+              <el-button @click="handleResetPassword">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+      </el-tabs>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+import { User } from '@element-plus/icons-vue'
+import request from '@/utils/request'
+import { API_ENDPOINTS } from '@/config/api'
+import { useUserStore } from '@/stores/user'
+
+const userStore = useUserStore()
+
+const activeTab = ref('basic')
+const saving = ref(false)
+const changingPassword = ref(false)
+const profileFormRef = ref(null)
+const passwordFormRef = ref(null)
+
+const defaultAvatar = 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
+
+const profileForm = reactive({
+  id: null,
+  username: '',
+  realName: '',
+  phone: '',
+  email: '',
+  avatar: ''
+})
+
+const passwordForm = reactive({
+  oldPassword: '',
+  newPassword: '',
+  confirmPassword: ''
+})
+
+// 表单验证规则
+const profileRules = {
+  realName: [
+    { max: 50, message: '真实姓名长度不能超过50个字符', trigger: 'blur' }
+  ],
+  phone: [
+    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
+  ],
+  email: [
+    { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
+  ]
+}
+
+const passwordRules = {
+  oldPassword: [
+    { required: true, message: '请输入原密码', trigger: 'blur' }
+  ],
+  newPassword: [
+    { required: true, message: '请输入新密码', trigger: 'blur' },
+    { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
+  ],
+  confirmPassword: [
+    { required: true, message: '请确认新密码', trigger: 'blur' },
+    {
+      validator: (rule, value, callback) => {
+        if (value !== passwordForm.newPassword) {
+          callback(new Error('两次输入的密码不一致'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    }
+  ]
+}
+
+// 加载个人资料
+const loadProfile = async () => {
+  try {
+    const response = await request.get(API_ENDPOINTS.ADMIN_USER_CURRENT_PROFILE)
+    if (response.code === 200) {
+      const data = response.data
+      profileForm.id = data.id
+      profileForm.username = data.username || ''
+      profileForm.realName = data.realName || ''
+      profileForm.phone = data.phone || ''
+      profileForm.email = data.email || ''
+      profileForm.avatar = data.avatar || ''
+    }
+  } catch (error) {
+    console.error('加载个人资料失败:', error)
+    ElMessage.error('加载个人资料失败')
+  }
+}
+
+// 保存个人资料
+const handleSaveProfile = async () => {
+  try {
+    await profileFormRef.value.validate()
+    
+    saving.value = true
+    const response = await request.put(API_ENDPOINTS.ADMIN_USER_UPDATE_CURRENT_PROFILE, {
+      realName: profileForm.realName,
+      phone: profileForm.phone,
+      email: profileForm.email,
+      avatar: profileForm.avatar
+    })
+    
+    if (response.code === 200) {
+      ElMessage.success('保存成功')
+      // 更新用户信息
+      await userStore.getUserInfo()
+    } else {
+      ElMessage.error(response.message || '保存失败')
+    }
+  } catch (error) {
+    if (error !== false) { // validate 返回 false 时不显示错误
+      console.error('保存个人资料失败:', error)
+      ElMessage.error(error.message || '保存失败')
+    }
+  } finally {
+    saving.value = false
+  }
+}
+
+// 重置表单
+const handleReset = () => {
+  loadProfile()
+}
+
+// 修改密码
+const handleChangePassword = async () => {
+  try {
+    await passwordFormRef.value.validate()
+    
+    changingPassword.value = true
+    const response = await request.put(API_ENDPOINTS.ADMIN_USER_UPDATE_CURRENT_PASSWORD, {
+      oldPassword: passwordForm.oldPassword,
+      newPassword: passwordForm.newPassword
+    })
+    
+    if (response.code === 200) {
+      ElMessage.success('密码修改成功')
+      handleResetPassword()
+    } else {
+      ElMessage.error(response.message || '密码修改失败')
+    }
+  } catch (error) {
+    if (error !== false) { // validate 返回 false 时不显示错误
+      console.error('修改密码失败:', error)
+      ElMessage.error(error.message || '修改密码失败')
+    }
+  } finally {
+    changingPassword.value = false
+  }
+}
+
+// 重置密码表单
+const handleResetPassword = () => {
+  passwordForm.oldPassword = ''
+  passwordForm.newPassword = ''
+  passwordForm.confirmPassword = ''
+  passwordFormRef.value?.clearValidate()
+}
+
+// 头像上传前验证
+const beforeAvatarUpload = (file) => {
+  const isImage = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg'
+  const isLt2M = file.size / 1024 / 1024 < 2
+
+  if (!isImage) {
+    ElMessage.error('头像图片只能是 JPG/PNG 格式!')
+    return false
+  }
+  if (!isLt2M) {
+    ElMessage.error('头像图片大小不能超过 2MB!')
+    return false
+  }
+  return true
+}
+
+// 头像上传
+const handleAvatarUpload = async (options) => {
+  try {
+    const formData = new FormData()
+    formData.append('file', options.file)
+    
+    const response = await request.post(API_ENDPOINTS.UPLOAD_IMAGE, formData, {
+      headers: {
+        'Content-Type': 'multipart/form-data'
+      }
+    })
+    
+    if (response.code === 200 && response.data) {
+      profileForm.avatar = response.data.url || response.data
+      ElMessage.success('头像上传成功')
+    } else {
+      ElMessage.error(response.message || '头像上传失败')
+    }
+  } catch (error) {
+    console.error('头像上传失败:', error)
+    ElMessage.error('头像上传失败')
+  }
+}
+
+onMounted(() => {
+  loadProfile()
+})
+</script>
+
+<style scoped>
+.profile-container {
+  padding: 20px;
+}
+
+.profile-card {
+  max-width: 900px;
+  margin: 0 auto;
+}
+
+.card-header h2 {
+  margin: 0;
+  font-size: 20px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.avatar-upload {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: 12px;
+}
+
+.avatar-preview {
+  border: 2px solid #dcdfe6;
+  cursor: pointer;
+}
+
+.avatar-uploader {
+  margin-top: 8px;
+}
+
+.avatar-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: -4px;
+}
+
+:deep(.el-tabs__content) {
+  padding: 24px;
+}
+
+:deep(.el-form-item__label) {
+  font-weight: 500;
+}
+
+:deep(.el-input__inner) {
+  max-width: 400px;
+}
+</style>
+

+ 34 - 4
marriageAdmin-vue/src/views/activity/ActivityForm.vue

@@ -163,7 +163,7 @@ import { useRoute, useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'
 import { Plus } from '@element-plus/icons-vue'
 import request from '@/utils/request'
-import { API_ENDPOINTS } from '@/config/api'
+import { API_ENDPOINTS, API_BASE_URL } from '@/config/api'
 
 const route = useRoute()
 const router = useRouter()
@@ -310,15 +310,45 @@ const handleUpload = async (options) => {
   }
 }
 
+// 规范化图片地址,兼容相对路径和缺少协议的情况
+const resolveImageUrl = (url) => {
+  if (!url || typeof url !== 'string') return ''
+  const normalized = url.trim()
+  if (/^https?:\/\//i.test(normalized)) return normalized
+  if (normalized.startsWith('//')) return `${window.location.protocol}${normalized}`
+  const base = API_BASE_URL || window.location.origin
+  if (normalized.startsWith('/')) return `${base}${normalized}`
+  return `${base}/${normalized}`
+}
+
 // 加载活动详情
 const loadActivityDetail = async (id) => {
   try {
     const response = await request.get(`${API_ENDPOINTS.ACTIVITY_DETAIL}/${id}`)
     
     if (response.code === 200) {
-      Object.assign(activityForm, response.data)
-      if (response.data.startTime && response.data.endTime) {
-        timeRange.value = [response.data.startTime, response.data.endTime]
+      const data = response.data
+      // 映射字段,兼容不同的命名方式
+      Object.assign(activityForm, {
+        id: data.id,
+        name: data.name || '',
+        type: data.type || 2,
+        category: data.category || '',
+        maxParticipants: data.maxParticipants || data.max_participants || 50,
+        location: data.location || '',
+        startTime: data.startTime || data.start_time || null,
+        endTime: data.endTime || data.end_time || null,
+        coverImage: resolveImageUrl(data.coverImage || data.cover_image || data.cover || ''),
+        price: data.price || 0,
+        status: data.status || 1,
+        registrationEndTime: data.registrationEndTime || data.registration_end_time || null,
+        description: data.description || '',
+        notes: data.notes || '',
+        isHot: data.isHot === true || data.isHot === 1 || data.isHot === '1' || data.is_hot === true || data.is_hot === 1
+      })
+      
+      if (activityForm.startTime && activityForm.endTime) {
+        timeRange.value = [activityForm.startTime, activityForm.endTime]
       }
       
       // 等待 DOM 更新后清除表单验证错误

+ 91 - 8
marriageAdmin-vue/src/views/activity/ActivityList.vue

@@ -138,15 +138,34 @@
         <el-table-column prop="isHot" label="热门" width="80" align="center">
           <template #default="{ row }">
             <el-switch
-              v-model="row.isHot"
+              :model-value="Boolean(row.isHot)"
               active-color="var(--danger-color)"
               inactive-color="var(--border-dark)"
-              @change="handleHotChange(row)"
+              @change="(val) => handleHotChange(row, val)"
             />
           </template>
         </el-table-column>
         
-        <el-table-column prop="startTime" label="开始时间" width="180" />
+        <el-table-column prop="startTime" label="开始时间" width="180">
+          <template #default="{ row }">
+            <span v-if="row.startTime">{{ formatDateTime(row.startTime) }}</span>
+            <span v-else class="data-meta">-</span>
+          </template>
+        </el-table-column>
+        
+        <el-table-column prop="endTime" label="结束时间" width="180">
+          <template #default="{ row }">
+            <span v-if="row.endTime">{{ formatDateTime(row.endTime) }}</span>
+            <span v-else class="data-meta">-</span>
+          </template>
+        </el-table-column>
+        
+        <el-table-column prop="registrationEndTime" label="报名截止时间" width="180">
+          <template #default="{ row }">
+            <span v-if="row.registrationEndTime">{{ formatDateTime(row.registrationEndTime) }}</span>
+            <span v-else class="data-meta">-</span>
+          </template>
+        </el-table-column>
         
         <el-table-column label="操作" width="240" fixed="right">
           <template #default="{ row }">
@@ -255,7 +274,25 @@ const loadActivityList = async () => {
     })
     
     if (response.code === 200) {
-      activityList.value = response.data.list || response.data || []
+      const list = response.data.list || response.data || []
+      // 字段兼容与转换:确保 isHot 为布尔,并兼容不同时间字段命名
+      activityList.value = list.map(item => ({
+        ...item,
+        isHot:
+          item.isHot === true ||
+          item.isHot === 1 ||
+          item.isHot === '1' ||
+          item.is_hot === true ||
+          item.is_hot === 1,
+        // 兼容下划线命名与不同字段名
+        startTime: item.startTime || item.start_time || item.start_at || '',
+        endTime: item.endTime || item.end_time || item.end_at || '',
+        registrationEndTime:
+          item.registrationEndTime ||
+          item.registration_end_time ||
+          item.signup_end_time ||
+          ''
+      }))
       total.value = response.data.total || activityList.value.length
     }
   } catch (error) {
@@ -283,21 +320,67 @@ const handleViewRegistrations = (row) => {
   router.push(`/activity/registrations/${row.id}`)
 }
 
+// 格式化日期时间
+const formatDateTime = (dateTime) => {
+  if (!dateTime) return '-'
+  // 如果是字符串,兼容 '2025-01-01T12:00:00' 或 '2025-01-01 12:00:00'
+  if (typeof dateTime === 'string') {
+    const normalized = dateTime.replace('T', ' ')
+    return normalized.substring(0, 19)
+  }
+  // 如果是时间戳(秒或毫秒),转换为日期字符串
+  if (typeof dateTime === 'number') {
+    const ts = dateTime > 1e12 ? dateTime : dateTime * 1000
+    const d = new Date(ts)
+    const pad = (n) => (n < 10 ? `0${n}` : n)
+    const Y = d.getFullYear()
+    const M = pad(d.getMonth() + 1)
+    const D = pad(d.getDate())
+    const h = pad(d.getHours())
+    const m = pad(d.getMinutes())
+    const s = pad(d.getSeconds())
+    return `${Y}-${M}-${D} ${h}:${m}:${s}`
+  }
+  // 其它类型(如 Date 对象)直接格式化
+  if (dateTime instanceof Date) {
+    const d = dateTime
+    const pad = (n) => (n < 10 ? `0${n}` : n)
+    const Y = d.getFullYear()
+    const M = pad(d.getMonth() + 1)
+    const D = pad(d.getDate())
+    const h = pad(d.getHours())
+    const m = pad(d.getMinutes())
+    const s = pad(d.getSeconds())
+    return `${Y}-${M}-${D} ${h}:${m}:${s}`
+  }
+  return String(dateTime)
+}
+
 // 切换热门状态
-const handleHotChange = async (row) => {
+const handleHotChange = async (row, newValue) => {
+  const oldValue = row.isHot
+  // 立即更新UI
+  row.isHot = newValue
+  
   try {
     const response = await request.put(`${API_ENDPOINTS.ACTIVITY_UPDATE}/${row.id}`, {
-      isHot: row.isHot
+      isHot: newValue
     })
     
     if (response.code === 200) {
       ElMessage.success('状态更新成功')
+      // 重新加载列表以确保数据同步
+      loadActivityList()
     } else {
-      row.isHot = !row.isHot
+      // 恢复原值
+      row.isHot = oldValue
+      ElMessage.error(response.message || '状态更新失败')
     }
   } catch (error) {
     console.error('状态更新失败:', error)
-    row.isHot = !row.isHot
+    // 恢复原值
+    row.isHot = oldValue
+    ElMessage.error('状态更新失败,请重试')
   }
 }
 

+ 150 - 33
marriageAdmin-vue/src/views/activity/ActivityRegistrations.vue

@@ -34,17 +34,49 @@
       </el-col>
     </el-row>
     
-    <!-- 报名列表 -->
-    <el-card shadow="never" class="table-card">
-      <template #header>
-        <el-row justify="space-between">
-          <span>报名列表</span>
-          <el-space>
-            <el-button type="success" size="small" icon="Download" @click="handleExport">
+    <!-- 搜索栏 -->
+    <el-card shadow="never" class="toolbar-card">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-space wrap>
+            <el-select v-model="filters.checkInStatus" placeholder="签到状态" clearable style="width: 120px" @change="loadRegistrationList">
+              <el-option label="全部" :value="undefined" />
+              <el-option label="已报名" :value="1" />
+              <el-option label="已取消" :value="2" />
+              <el-option label="已签到" :value="3" />
+            </el-select>
+            
+            <el-select v-model="filters.paymentStatus" placeholder="支付状态" clearable style="width: 120px" @change="loadRegistrationList">
+              <el-option label="全部" :value="undefined" />
+              <el-option label="未支付" :value="0" />
+              <el-option label="已支付" :value="1" />
+            </el-select>
+            
+            <el-input
+              v-model="filters.keyword"
+              placeholder="搜索用户名或手机号"
+              clearable
+              style="width: 200px"
+              @clear="loadRegistrationList"
+              @keyup.enter="loadRegistrationList"
+            >
+              <template #append>
+                <el-button icon="Search" @click="loadRegistrationList" />
+              </template>
+            </el-input>
+            
+            <el-button type="success" icon="Download" @click="handleExport">
               导出数据
             </el-button>
           </el-space>
-        </el-row>
+        </el-col>
+      </el-row>
+    </el-card>
+    
+    <!-- 报名列表 -->
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <span>报名列表</span>
       </template>
       
       <el-table
@@ -69,30 +101,26 @@
         
         <el-table-column prop="registrationTime" label="报名时间" width="180" />
         
-        <el-table-column prop="payStatus" label="支付状态" width="100">
+        <el-table-column prop="paymentStatus" label="支付状态" width="100">
           <template #default="{ row }">
-            <el-tag :type="row.payStatus === 1 ? 'success' : 'warning'" size="small">
-              {{ row.payStatus === 1 ? '已支付' : '未支付' }}
+            <el-tag :type="row.paymentStatus === 1 ? 'success' : 'warning'" size="small">
+              {{ row.paymentStatus === 1 ? '已支付' : '未支付' }}
             </el-tag>
           </template>
         </el-table-column>
         
-        <el-table-column prop="checkInStatus" label="签到状态" width="100">
+        <el-table-column prop="status" label="签到状态" width="100">
           <template #default="{ row }">
-            <el-tag :type="row.checkInStatus === 1 ? 'success' : 'info'" size="small">
-              {{ row.checkInStatus === 1 ? '已签到' : '未签到' }}
+            <el-tag :type="getCheckInStatusType(row.status)" size="small">
+              {{ getCheckInStatusText(row.status) }}
             </el-tag>
           </template>
         </el-table-column>
         
-        <el-table-column prop="checkInTime" label="签到时间" width="180" />
-        
-        <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
-        
         <el-table-column label="操作" width="150" fixed="right">
           <template #default="{ row }">
             <el-button
-              v-if="row.checkInStatus !== 1"
+              v-if="row.status !== 3"
               type="primary"
               size="small"
               link
@@ -101,6 +129,7 @@
               签到
             </el-button>
             <el-button
+              v-if="row.status !== 2"
               type="danger"
               size="small"
               link
@@ -133,10 +162,10 @@ import { ref, reactive, onMounted } from 'vue'
 import { useRoute } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import request from '@/utils/request'
-import { API_ENDPOINTS } from '@/config/api'
+import { API_BASE_URL, API_ENDPOINTS } from '@/config/api'
 
 const route = useRoute()
-const activityId = ref(route.params.id)
+const activityId = ref(parseInt(route.params.id))
 const activityName = ref('')
 const loading = ref(false)
 const currentPage = ref(1)
@@ -144,32 +173,59 @@ const pageSize = ref(10)
 const total = ref(0)
 const registrationList = ref([])
 
+const filters = reactive({
+  checkInStatus: undefined,
+  paymentStatus: undefined,
+  keyword: ''
+})
+
 const stats = reactive({
   totalCount: 0,
   checkedInCount: 0,
   notCheckedInCount: 0
 })
 
+// 加载统计数据
+const loadStats = async () => {
+  try {
+    const response = await request.get(API_ENDPOINTS.ACTIVITY_REGISTRATION_STATS, {
+      params: {
+        activityId: activityId.value
+      }
+    })
+    
+    if (response.code === 200) {
+      stats.totalCount = response.data.totalCount || 0
+      stats.checkedInCount = response.data.checkedInCount || 0
+      stats.notCheckedInCount = response.data.notCheckedInCount || 0
+    }
+  } catch (error) {
+    console.error('加载统计数据失败:', error)
+  }
+}
+
 // 加载报名列表
 const loadRegistrationList = async () => {
   loading.value = true
   try {
-    const response = await request.get(`${API_ENDPOINTS.ACTIVITY_REGISTRATIONS}/${activityId.value}`, {
+    const response = await request.get(API_ENDPOINTS.ACTIVITY_REGISTRATIONS, {
       params: {
         page: currentPage.value,
-        pageSize: pageSize.value
+        pageSize: pageSize.value,
+        activityId: activityId.value,
+        checkInStatus: filters.checkInStatus,
+        paymentStatus: filters.paymentStatus,
+        keyword: filters.keyword || undefined
       }
     })
     
     if (response.code === 200) {
       registrationList.value = response.data.list || []
-      total.value = response.data.total || registrationList.value.length
+      total.value = response.data.total || 0
       activityName.value = response.data.activityName || ''
       
-      // 计算统计数据
-      stats.totalCount = total.value
-      stats.checkedInCount = registrationList.value.filter(r => r.checkInStatus === 1).length
-      stats.notCheckedInCount = stats.totalCount - stats.checkedInCount
+      // 加载统计数据
+      await loadStats()
     }
   } catch (error) {
     console.error('加载报名列表失败:', error)
@@ -179,6 +235,18 @@ const loadRegistrationList = async () => {
   }
 }
 
+// 获取签到状态文本
+const getCheckInStatusText = (status) => {
+  const texts = { 1: '已报名', 2: '已取消', 3: '已签到' }
+  return texts[status] || '未知'
+}
+
+// 获取签到状态类型
+const getCheckInStatusType = (status) => {
+  const types = { 1: 'info', 2: 'danger', 3: 'success' }
+  return types[status] || ''
+}
+
 // 签到
 const handleCheckIn = async (row) => {
   try {
@@ -189,7 +257,9 @@ const handleCheckIn = async (row) => {
     
     if (response.code === 200) {
       ElMessage.success('签到成功')
-      loadRegistrationList()
+      // 重新加载列表和统计数据
+      await loadRegistrationList()
+      await loadStats()
     }
   } catch (error) {
     console.error('签到失败:', error)
@@ -214,7 +284,9 @@ const handleCancelRegistration = async (row) => {
     
     if (response.code === 200) {
       ElMessage.success('取消报名成功')
-      loadRegistrationList()
+      // 重新加载列表和统计数据
+      await loadRegistrationList()
+      await loadStats()
     }
   } catch (error) {
     if (error !== 'cancel') {
@@ -224,9 +296,50 @@ const handleCancelRegistration = async (row) => {
 }
 
 // 导出数据
-const handleExport = () => {
-  ElMessage.info('导出功能开发中')
-  // TODO: 实现导出功能
+const handleExport = async () => {
+  try {
+    loading.value = true
+    const params = {
+      activityId: activityId.value,
+      checkInStatus: filters.checkInStatus,
+      paymentStatus: filters.paymentStatus,
+      keyword: filters.keyword || undefined
+    }
+    
+    // 使用axios下载文件
+    const blob = await request.get(API_ENDPOINTS.ACTIVITY_REGISTRATION_EXPORT, {
+      params: params,
+      responseType: 'blob'
+    })
+    
+    // 创建下载链接
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = `活动报名列表_${new Date().getTime()}.xlsx`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    window.URL.revokeObjectURL(url)
+    
+    ElMessage.success('导出成功')
+  } catch (error) {
+    console.error('导出失败:', error)
+    // 如果是blob响应但包含错误信息,尝试解析
+    if (error.response && error.response.data instanceof Blob) {
+      const text = await error.response.data.text()
+      try {
+        const errorData = JSON.parse(text)
+        ElMessage.error('导出失败: ' + (errorData.msg || '未知错误'))
+      } catch {
+        ElMessage.error('导出失败')
+      }
+    } else {
+      ElMessage.error('导出失败: ' + (error.message || '未知错误'))
+    }
+  } finally {
+    loading.value = false
+  }
 }
 
 onMounted(() => {
@@ -275,6 +388,10 @@ onMounted(() => {
   color: #e6a23c;
 }
 
+.toolbar-card {
+  margin-top: 20px;
+}
+
 .table-card {
   margin-top: 20px;
 }

+ 3 - 2
marriageAdmin-vue/src/views/banner/BannerList.vue

@@ -7,8 +7,9 @@
       <el-row :gutter="20">
         <el-col :span="18">
           <el-space wrap>
-            <el-button type="primary" icon="Plus" @click="handleCreate">
-              新增轮播图
+            <el-button type="primary" @click="handleCreate">
+              <el-icon><Plus /></el-icon>
+              <span>新增轮播图</span>
             </el-button>
             <el-button icon="Refresh" @click="loadBannerList">刷新</el-button>
           </el-space>

+ 0 - 83
marriageAdmin-vue/src/views/success-case/SuccessCaseList.vue

@@ -75,33 +75,6 @@
           </el-descriptions-item>
         </el-descriptions>
         
-        <!-- 案例图片 -->
-        <div v-if="detailData.images && detailData.images.length > 0" class="images-section">
-          <h3 class="section-title">案例图片</h3>
-          <div class="images-grid">
-            <div v-for="(img, index) in detailData.images" :key="img.imageId" class="image-item">
-              <el-image
-                :src="img.imageUrl"
-                fit="cover"
-                class="detail-image"
-                :preview-src-list="detailData.images.map(i => i.imageUrl)"
-                :initial-index="index"
-                preview-teleported
-                hide-on-click-modal
-              >
-                <template #error>
-                  <div class="image-error">
-                    <el-icon><Picture /></el-icon>
-                    <span>加载失败</span>
-                  </div>
-                </template>
-              </el-image>
-              <div v-if="img.imageDesc" class="image-desc">{{ img.imageDesc }}</div>
-            </div>
-          </div>
-        </div>
-        <el-empty v-else description="暂无图片" />
-        
         <!-- 时间线 -->
         <div v-if="detailData.timeline && detailData.timeline.length > 0" class="timeline-section">
           <h3 class="section-title">案例时间线</h3>
@@ -133,7 +106,6 @@
 import { ref, onMounted, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Picture } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 
@@ -361,61 +333,6 @@ onMounted(() => {
   border-bottom: 2px solid #409eff;
 }
 
-/* 图片网格 */
-.images-section {
-  margin-top: 20px;
-}
-
-.images-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
-  gap: 15px;
-  margin-top: 15px;
-}
-
-.image-item {
-  position: relative;
-  border-radius: 8px;
-  overflow: hidden;
-  border: 1px solid #eee;
-  transition: all 0.3s;
-}
-
-.image-item:hover {
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-  transform: translateY(-2px);
-}
-
-.detail-image {
-  width: 100%;
-  height: 200px;
-  cursor: pointer;
-}
-
-.image-desc {
-  padding: 8px;
-  background-color: #f5f7fa;
-  font-size: 13px;
-  color: #666;
-  text-align: center;
-  border-top: 1px solid #eee;
-}
-
-.image-error {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  height: 200px;
-  background-color: #f5f7fa;
-  color: #909399;
-}
-
-.image-error .el-icon {
-  font-size: 40px;
-  margin-bottom: 8px;
-}
-
 /* 时间线样式 */
 .timeline-section {
   margin-top: 20px;

+ 0 - 18
marriageAdmin-vue/src/views/user/UserList.vue

@@ -90,15 +90,6 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column prop="matchmakerName" label="绑定红娘" min-width="120">
-          <template #default="{ row }">
-            <div v-if="row.matchmakerName">
-              <div>{{ row.matchmakerName }}</div>
-              <div style="font-size: 12px; color: #999;">{{ row.matchmakerPhone }}</div>
-            </div>
-            <span v-else style="color: #999;">-</span>
-          </template>
-        </el-table-column>
         <el-table-column prop="isProfileComplete" label="资料完整" width="100" align="center">
           <template #default="{ row }">
             <el-tag 
@@ -221,15 +212,6 @@
         <el-descriptions-item label="VIP结束时间" :span="2" v-if="currentUser.isVip">
           {{ currentUser.vipEndTime || '-' }}
         </el-descriptions-item>
-        <el-descriptions-item label="绑定红娘" :span="2">
-          <div v-if="currentUser.matchmakerName">
-            {{ currentUser.matchmakerName }} ({{ currentUser.matchmakerPhone }})
-            <div style="font-size: 12px; color: #999; margin-top: 5px;">
-              绑定时间:{{ currentUser.boundAt }}
-            </div>
-          </div>
-          <span v-else>-</span>
-        </el-descriptions-item>
         <el-descriptions-item label="注册时间" :span="2">{{ currentUser.createdAt }}</el-descriptions-item>
         <el-descriptions-item label="最后更新" :span="2">{{ currentUser.updatedAt }}</el-descriptions-item>
         <el-descriptions-item label="审核时间" :span="2">

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

@@ -3,7 +3,32 @@
     <h2 class="page-title">VIP用户管理</h2>
     
     <el-card shadow="never" class="table-card">
-      <el-table v-loading="loading" :data="list" stripe>
+      <div style="margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;">
+        <div>
+          <el-button 
+            type="warning" 
+            size="small" 
+            :disabled="multipleSelection.length === 0"
+            @click="handleBatchRemind"
+          >
+            批量提醒
+          </el-button>
+          <span v-if="multipleSelection.length" style="margin-left: 10px; color: #909399;">
+            已选 {{ multipleSelection.length }} 条
+          </span>
+        </div>
+      </div>
+      <el-table
+        v-loading="loading"
+        :data="list"
+        stripe
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column
+          type="selection"
+          width="55"
+          :selectable="isRowSelectable"
+        />
         <el-table-column type="index" label="序号" width="60" />
         <el-table-column prop="nickname" label="昵称" width="120" />
         <el-table-column prop="vipLevel" label="VIP等级" width="100">
@@ -30,9 +55,10 @@
             </span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="150">
+        <el-table-column label="操作" width="220">
           <template #default="{ row }">
             <el-button type="primary" size="small" link @click="handleRenew(row)">续费</el-button>
+            <el-button type="warning" size="small" link @click="handleRemind(row)">提醒</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -53,7 +79,7 @@
 
 <script setup>
 import { ref, onMounted } from 'vue'
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 
@@ -62,6 +88,18 @@ const currentPage = ref(1)
 const pageSize = ref(10)
 const total = ref(0)
 const list = ref([])
+const multipleSelection = ref([])
+
+// 仅允许勾选剩余天数 >= 2 天的用户
+const isRowSelectable = (row) => {
+  const days =
+    row.remainingDays !== undefined
+      ? row.remainingDays
+      : row.remaining_days !== undefined
+        ? row.remaining_days
+        : 0
+  return days >= 2
+}
 
 const loadList = async () => {
   loading.value = true
@@ -97,6 +135,126 @@ const handleRenew = (row) => {
   ElMessage.info('续费功能开发中')
 }
 
+const handleSelectionChange = (val) => {
+  multipleSelection.value = (val || []).filter(isRowSelectable)
+}
+
+// 发送系统提醒
+const handleRemind = async (row) => {
+  const userId = row.userId || row.user_id
+  if (!userId) {
+    ElMessage.error('缺少用户ID,无法发送提醒')
+    return
+  }
+
+  const days =
+    row.remainingDays !== undefined
+      ? row.remainingDays
+      : row.remaining_days !== undefined
+        ? row.remaining_days
+        : 0
+
+  let content = ''
+  if (days <= 0) {
+    content = '尊敬的用户,您的会员已经到期,请及时充值'
+  } else if (days <= 2) {
+    content = '尊敬的用户,您的会员不足2天,请及时充值'
+  } else {
+    ElMessage.info('该用户会员剩余时间大于2天,无需提醒')
+    return
+  }
+
+  try {
+    const res = await request.post(API_ENDPOINTS.USER_VIP_REMIND, {
+      userId,
+      content
+    })
+    if (res.code === 200) {
+      ElMessage.success('提醒发送成功')
+    } else {
+      ElMessage.error(res.message || '提醒发送失败')
+    }
+  } catch (error) {
+    console.error('发送提醒失败:', error)
+    ElMessage.error('发送提醒失败,请稍后重试')
+  }
+}
+
+// 批量提醒
+const handleBatchRemind = async () => {
+  if (!multipleSelection.value.length) {
+    ElMessage.warning('请先选择需要提醒的用户')
+    return
+  }
+
+  try {
+    await ElMessageBox.confirm(
+      `确定要对选中的 ${multipleSelection.value.length} 位用户发送会员到期提醒吗?`,
+      '批量提醒',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+  } catch {
+    return
+  }
+
+  let successCount = 0
+  let skipCount = 0
+  let failCount = 0
+
+  for (const row of multipleSelection.value) {
+    const userId = row.userId || row.user_id
+    if (!userId) {
+      failCount++
+      continue
+    }
+
+    const days =
+      row.remainingDays !== undefined
+        ? row.remainingDays
+        : row.remaining_days !== undefined
+          ? row.remaining_days
+          : 0
+
+    let content = ''
+    if (days <= 0) {
+      content = '尊敬的用户,您的会员已经到期,请及时充值'
+    } else if (days <= 2) {
+      content = '尊敬的用户,您的会员不足2天,请及时充值'
+    } else {
+      skipCount++
+      continue
+    }
+
+    try {
+      const res = await request.post(API_ENDPOINTS.USER_VIP_REMIND, {
+        userId,
+        content
+      })
+      if (res.code === 200) {
+        successCount++
+      } else {
+        failCount++
+      }
+    } catch {
+      failCount++
+    }
+  }
+
+  let msg = `批量提醒完成:成功 ${successCount} 条`
+  if (skipCount) msg += `,已跳过(剩余时间>2天)${skipCount} 条`
+  if (failCount) msg += `,失败 ${failCount} 条`
+
+  if (failCount) {
+    ElMessage.warning(msg)
+  } else {
+    ElMessage.success(msg)
+  }
+}
+
 onMounted(() => loadList())
 </script>
 

+ 3 - 1
service/Essential/src/main/java/com/zhentao/dto/ActivityCreateOrderDto.java

@@ -3,10 +3,12 @@ package com.zhentao.dto;
 
 import lombok.Data;
 
+import java.math.BigDecimal;
+
 @Data
 public class ActivityCreateOrderDto {
     private Long userId;
     private Integer activityId;
     private String activityName;
-    private Double price;
+    private BigDecimal price;
 }

+ 2 - 1
service/Essential/src/main/java/com/zhentao/service/ActivityOrderService.java

@@ -3,6 +3,7 @@ package com.zhentao.service;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.zhentao.entity.ActivityOrder;
 
+import java.math.BigDecimal;
 import java.util.Map;
 
 /**
@@ -19,7 +20,7 @@ public interface ActivityOrderService {
      * @param price 活动价格
      * @return 微信支付参数
      */
-    Map<String, Object> createOrderAndGetPayParams(Long userId, Integer activityId, String activityName, Double price) throws WxPayException;
+    Map<String, Object> createOrderAndGetPayParams(Long userId, Integer activityId, String activityName, BigDecimal price) throws WxPayException;
     
     /**
      * 处理微信支付回调

+ 6 - 6
service/Essential/src/main/java/com/zhentao/service/impl/ActivityOrderServiceImpl.java

@@ -51,7 +51,7 @@ public class ActivityOrderServiceImpl implements ActivityOrderService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Map<String, Object> createOrderAndGetPayParams(Long userId, Integer activityId, String activityName, Double price) throws WxPayException {
+    public Map<String, Object> createOrderAndGetPayParams(Long userId, Integer activityId, String activityName, BigDecimal price) throws WxPayException {
         // 1. 生成订单号
         String orderNo = generateOrderNo();
         LocalDateTime now = LocalDateTime.now();
@@ -62,7 +62,7 @@ public class ActivityOrderServiceImpl implements ActivityOrderService {
         order.setActivityId(activityId);
         order.setActivityName(activityName);
         order.setOrderNo(orderNo);
-        order.setPaymentAmount(BigDecimal.valueOf(price));
+        order.setPaymentAmount(price);
         order.setPaymentMethod("微信支付");
         order.setStatus(0); // 待支付
         order.setCreateTime(now);
@@ -134,7 +134,7 @@ public class ActivityOrderServiceImpl implements ActivityOrderService {
         return "ACTIVITY" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
     }
 
-    private WxPayUnifiedOrderRequest buildWxPayRequest(String orderNo, String activityName, Double price, Long userId) {
+    private WxPayUnifiedOrderRequest buildWxPayRequest(String orderNo, String activityName, BigDecimal price, Long userId) {
         Users user = usersService.getById(userId);
         if (user == null || user.getWechatOpenid() == null) {
             throw new RuntimeException("用户微信信息未绑定");
@@ -143,7 +143,7 @@ public class ActivityOrderServiceImpl implements ActivityOrderService {
         WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
         request.setOutTradeNo(orderNo);
         request.setBody("活动报名-" + activityName);
-        request.setTotalFee(BigDecimal.valueOf(price).multiply(new BigDecimal("100")).intValue());
+        request.setTotalFee(price.multiply(new BigDecimal("100")).intValue());
         request.setSpbillCreateIp("127.0.0.1");
         request.setNotifyUrl("https://mini.workervip.com/api/activity-order/notify");
         request.setTradeType("JSAPI");
@@ -157,7 +157,7 @@ public class ActivityOrderServiceImpl implements ActivityOrderService {
         return wxPayService.unifiedOrder(request);
     }
 
-    private Map<String, Object> generatePayParams(WxPayUnifiedOrderResult wxPayResult, Double price) {
+    private Map<String, Object> generatePayParams(WxPayUnifiedOrderResult wxPayResult, BigDecimal price) {
         Wx wxConfig = wxService.list().get(0);
         String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
         String nonceStr = RandomStringUtils.randomAlphanumeric(32);
@@ -179,7 +179,7 @@ public class ActivityOrderServiceImpl implements ActivityOrderService {
         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());
+        payParams.put("totalFee", price.multiply(new BigDecimal("100")).intValue());
         return payParams;
     }
 

+ 2 - 2
service/Essential/src/main/java/com/zhentao/service/impl/VipServiceImpl.java

@@ -824,7 +824,7 @@ public class VipServiceImpl implements VipService {
         request.setBody(pkg.getPackageName());
         request.setTotalFee(pkg.getCurrentPrice().multiply(new BigDecimal("100")).intValue());
         request.setSpbillCreateIp("127.0.0.1");
-        request.setNotifyUrl("https://mini.workervip.com/pay/notify"); // 回调地址
+        request.setNotifyUrl("https://mini.workervip.com/api/vip/notify"); // 回调地址
         request.setTradeType("JSAPI");
         request.setOpenid(user.getWechatOpenid());
         return request;
@@ -871,7 +871,7 @@ public class VipServiceImpl implements VipService {
         payConfig.setAppId(wxConfig.getAppId());
         payConfig.setMchId(wxConfig.getMchId());
         payConfig.setMchKey("7f633cbabd894b4d213bc6edffe3b119");
-        payConfig.setNotifyUrl("https://mini.workervip.com/pay/notify");
+        payConfig.setNotifyUrl("https://mini.workervip.com/api/vip/notify");
         return payConfig;
     }
 

+ 6 - 0
service/admin/pom.xml

@@ -86,5 +86,11 @@
             <artifactId>java-jwt</artifactId>
             <version>4.4.0</version>
         </dependency>
+        <!-- EasyExcel -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>3.3.2</version>
+        </dependency>
     </dependencies>
 </project>

+ 0 - 13
service/admin/src/main/java/com/zhentao/controller/ActivityController.java

@@ -112,19 +112,6 @@ public class ActivityController {
         }
     }
     
-    /**
-     * 获取活动报名列表
-     */
-    @GetMapping("/registrations/{id}")
-    public Result<?> getRegistrations(@PathVariable Integer id) {
-        try {
-            // TODO: 实现获取报名列表的逻辑
-            return Result.success("功能开发中");
-        } catch (Exception e) {
-            e.printStackTrace();
-            return Result.error("获取报名列表失败:" + e.getMessage());
-        }
-    }
     
     /**
      * 获取活动统计

+ 122 - 0
service/admin/src/main/java/com/zhentao/controller/ActivityRegistrationController.java

@@ -0,0 +1,122 @@
+package com.zhentao.controller;
+
+import com.alibaba.excel.EasyExcel;
+import com.zhentao.common.Result;
+import com.zhentao.entity.Activity;
+import com.zhentao.entity.ActivityRegistration;
+import com.zhentao.mapper.ActivityMapper;
+import com.zhentao.service.ActivityRegistrationService;
+import com.zhentao.vo.ActivityRegistrationExcelVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 活动报名管理控制器
+ */
+@RestController
+@RequestMapping("/admin/activity/registration")
+@CrossOrigin(origins = "*")
+public class ActivityRegistrationController {
+    
+    @Autowired
+    private ActivityRegistrationService activityRegistrationService;
+    
+    @Autowired
+    private ActivityMapper activityMapper;
+    
+    /**
+     * 获取报名列表(分页)
+     */
+    @GetMapping("/list")
+    public Result<Map<String, Object>> getRegistrationList(
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "activityId", required = false) Integer activityId,
+            @RequestParam(value = "checkInStatus", required = false) Integer checkInStatus,
+            @RequestParam(value = "paymentStatus", required = false) Integer paymentStatus,
+            @RequestParam(value = "keyword", required = false) String keyword) {
+        try {
+            Map<String, Object> data = activityRegistrationService.getRegistrationList(
+                    page, pageSize, activityId, checkInStatus, paymentStatus, keyword);
+            
+            // 如果有活动ID,获取活动名称
+            if (activityId != null) {
+                Activity activity = activityMapper.selectById(activityId);
+                if (activity != null) {
+                    data.put("activityName", activity.getName());
+                }
+            }
+            
+            return Result.success(data);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取报名列表失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 获取报名统计数据
+     * 报名总数:签到状态为已报名(status=1)的记录数
+     * 已签到:签到状态为已签到(status=3)的记录数(从已报名中筛选)
+     * 未签到:签到状态为已报名(status=1)但未签到(status≠3)的记录数
+     */
+    @GetMapping("/stats")
+    public Result<Map<String, Object>> getRegistrationStats(
+            @RequestParam(value = "activityId", required = true) Integer activityId) {
+        try {
+            Map<String, Object> stats = activityRegistrationService.getRegistrationStats(activityId);
+            return Result.success(stats);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取统计数据失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 导出报名数据为Excel
+     */
+    @GetMapping("/export")
+    public void exportRegistrationList(
+            HttpServletResponse response,
+            @RequestParam(value = "activityId", required = false) Integer activityId,
+            @RequestParam(value = "checkInStatus", required = false) Integer checkInStatus,
+            @RequestParam(value = "paymentStatus", required = false) Integer paymentStatus,
+            @RequestParam(value = "keyword", required = false) String keyword) throws IOException {
+        try {
+            // 查询所有符合条件的记录
+            List<ActivityRegistration> registrations = activityRegistrationService.getAllRegistrations(
+                    activityId, checkInStatus, paymentStatus, keyword);
+            
+            // 转换为Excel VO
+            List<ActivityRegistrationExcelVO> excelData = registrations.stream()
+                    .map(ActivityRegistrationExcelVO::fromEntity)
+                    .collect(Collectors.toList());
+            
+            // 设置响应头
+            String fileName = "活动报名列表_" + System.currentTimeMillis() + ".xlsx";
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8"));
+            
+            // 写入Excel
+            EasyExcel.write(response.getOutputStream(), ActivityRegistrationExcelVO.class)
+                    .sheet("报名列表")
+                    .doWrite(excelData);
+            
+        } catch (Exception e) {
+            e.printStackTrace();
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+        }
+    }
+}
+

+ 154 - 0
service/admin/src/main/java/com/zhentao/controller/AdminUserController.java

@@ -3,12 +3,15 @@ package com.zhentao.controller;
 import com.zhentao.common.Result;
 import com.zhentao.entity.AdminUser;
 import com.zhentao.entity.Role;
+import com.zhentao.mapper.AdminUserMapper;
 import com.zhentao.service.AuthService;
 import com.zhentao.service.RoleService;
 import com.zhentao.service.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.DigestUtils;
 import org.springframework.web.bind.annotation.*;
 
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -31,6 +34,9 @@ public class AdminUserController {
     @Autowired
     private RoleService roleService;
     
+    @Autowired
+    private AdminUserMapper adminUserMapper;
+    
     /**
      * 注册新管理员(仅超级管理员可用)
      */
@@ -386,5 +392,153 @@ public class AdminUserController {
             return Result.error("获取角色列表失败:" + e.getMessage());
         }
     }
+    
+    /**
+     * 获取当前登录用户的个人资料
+     */
+    @GetMapping("/current-profile")
+    public Result<Map<String, Object>> getCurrentProfile(@RequestHeader(value = "Authorization", required = false) String token) {
+        try {
+            // 验证token
+            if (token == null || token.isEmpty()) {
+                return Result.error(401, "未授权,请先登录");
+            }
+            
+            AdminUser currentUser = authService.getUserByToken(token);
+            if (currentUser == null) {
+                return Result.error(401, "Token无效或已过期,请重新登录");
+            }
+            
+            // 获取完整用户信息(不包括密码)
+            AdminUser user = userService.getUserById(currentUser.getId());
+            if (user == null) {
+                return Result.error("用户不存在");
+            }
+            
+            Map<String, Object> data = new HashMap<>();
+            data.put("id", user.getId());
+            data.put("username", user.getUsername());
+            data.put("realName", user.getRealName());
+            data.put("phone", user.getPhone());
+            data.put("email", user.getEmail());
+            data.put("avatar", user.getAvatar());
+            data.put("status", user.getStatus());
+            data.put("createTime", user.getCreateTime());
+            data.put("updateTime", user.getUpdateTime());
+            data.put("lastLoginTime", user.getLastLoginTime());
+            
+            return Result.success(data);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取个人资料失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 更新当前登录用户的个人资料
+     */
+    @PutMapping("/current-profile")
+    public Result<String> updateCurrentProfile(@RequestBody Map<String, Object> params,
+                                               @RequestHeader(value = "Authorization", required = false) String token) {
+        try {
+            // 验证token
+            if (token == null || token.isEmpty()) {
+                return Result.error(401, "未授权,请先登录");
+            }
+            
+            AdminUser currentUser = authService.getUserByToken(token);
+            if (currentUser == null) {
+                return Result.error(401, "Token无效或已过期,请重新登录");
+            }
+            
+            // 获取当前用户信息
+            AdminUser user = userService.getUserById(currentUser.getId());
+            if (user == null) {
+                return Result.error("用户不存在");
+            }
+            
+            // 更新字段(不包括密码、用户名、状态等敏感字段)
+            if (params.containsKey("realName")) {
+                user.setRealName((String) params.get("realName"));
+            }
+            if (params.containsKey("phone")) {
+                user.setPhone((String) params.get("phone"));
+            }
+            if (params.containsKey("email")) {
+                user.setEmail((String) params.get("email"));
+            }
+            if (params.containsKey("avatar")) {
+                user.setAvatar((String) params.get("avatar"));
+            }
+            
+            boolean success = userService.updateUserProfile(user);
+            if (success) {
+                return Result.success("更新成功");
+            } else {
+                return Result.error("更新失败");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("更新失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 更新当前登录用户的密码
+     */
+    @PutMapping("/current-password")
+    public Result<String> updateCurrentPassword(@RequestBody Map<String, Object> params,
+                                                @RequestHeader(value = "Authorization", required = false) String token) {
+        try {
+            // 验证token
+            if (token == null || token.isEmpty()) {
+                return Result.error(401, "未授权,请先登录");
+            }
+            
+            AdminUser currentUser = authService.getUserByToken(token);
+            if (currentUser == null) {
+                return Result.error(401, "Token无效或已过期,请重新登录");
+            }
+            
+            // 获取参数
+            String oldPassword = (String) params.get("oldPassword");
+            String newPassword = (String) params.get("newPassword");
+            
+            if (oldPassword == null || oldPassword.isEmpty()) {
+                return Result.error("原密码不能为空");
+            }
+            if (newPassword == null || newPassword.isEmpty()) {
+                return Result.error("新密码不能为空");
+            }
+            if (newPassword.length() < 6) {
+                return Result.error("新密码长度不能少于6位");
+            }
+            
+            // 获取完整用户信息(包含密码和盐)
+            AdminUser user = adminUserMapper.selectById(currentUser.getId());
+            if (user == null) {
+                return Result.error("用户不存在");
+            }
+            
+            // 验证原密码
+            String passwordWithSalt = oldPassword + user.getSalt();
+            String encryptedOldPassword = DigestUtils.md5DigestAsHex(passwordWithSalt.getBytes(StandardCharsets.UTF_8));
+            if (!encryptedOldPassword.equals(user.getPassword())) {
+                return Result.error("原密码不正确");
+            }
+            
+            // 更新密码
+            user.setPassword(newPassword);
+            boolean success = userService.updateUser(user);
+            if (success) {
+                return Result.success("密码修改成功");
+            } else {
+                return Result.error("密码修改失败");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("密码修改失败:" + e.getMessage());
+        }
+    }
 }
 

+ 72 - 44
service/admin/src/main/java/com/zhentao/controller/UserController.java

@@ -17,6 +17,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Date;
 import java.util.stream.Collectors;
 /**
  * 用户管理控制器
@@ -31,15 +32,12 @@ public class UserController {
     
     @Autowired
     private UserVipMapper userVipMapper;
-    
-    @Autowired
-    private UserMatchmakerBindingMapper userMatchmakerBindingMapper;
-    
-    @Autowired
-    private MatchmakerMapper matchmakerMapper;
 
     @Autowired
     private VipPackageMapper vipPackageMapper;
+
+    @Autowired
+    private com.zhentao.mapper.SystemMessagesMapper systemMessagesMapper;
     
     /**
      * 用户列表(分页)
@@ -81,7 +79,7 @@ public class UserController {
             
             Page<Users> result = usersMapper.selectPage(pageInfo, queryWrapper);
             
-            // 转换为UserVO并填充VIP和红娘信息
+            // 转换为UserVO并填充VIP信息
             List<UserVO> userVOList = result.getRecords().stream()
                     .map(this::convertToUserVO)
                     .collect(Collectors.toList());
@@ -112,9 +110,6 @@ public class UserController {
         // 填充VIP信息
         fillVipInfo(vo, user.getUserId());
         
-        // 填充红娘信息
-        fillMatchmakerInfo(vo, user.getUserId());
-        
         return vo;
     }
     
@@ -177,10 +172,14 @@ public class UserController {
                 vo.setVipLevel("VIP会员");
                 vo.setVipStartTime(userVip.getStartTime());
                 vo.setVipEndTime(userVip.getEndTime());
-                
-                // 计算剩余天数
-                long days = Duration.between(LocalDateTime.now(), userVip.getEndTime()).toDays();
-                vo.setVipRemainingDays((int) days);
+
+                // 计算剩余天数:到期时间 - 开通时间
+                if (userVip.getStartTime() != null && userVip.getEndTime() != null) {
+                    long days = Duration.between(userVip.getStartTime(), userVip.getEndTime()).toDays();
+                    vo.setVipRemainingDays((int) Math.max(days, 0));
+                } else {
+                    vo.setVipRemainingDays(0);
+                }
             } else {
                 vo.setIsVip(false);
                 vo.setVipLevel("普通用户");
@@ -193,36 +192,6 @@ public class UserController {
         }
     }
     
-    /**
-     * 填充红娘信息
-     */
-    private void fillMatchmakerInfo(UserVO vo, Integer userId) {
-        try {
-            // 查询红娘绑定关系
-            QueryWrapper<UserMatchmakerBinding> bindingWrapper = new QueryWrapper<>();
-            bindingWrapper.eq("user_id", userId)
-                    .eq("status", 1)  // 状态:有效
-                    .orderByDesc("bound_at")
-                    .last("LIMIT 1");
-            
-            UserMatchmakerBinding binding = userMatchmakerBindingMapper.selectOne(bindingWrapper);
-            
-            if (binding != null) {
-                vo.setMatchmakerId(binding.getMatchmakerId());
-                vo.setBoundAt(binding.getBoundAt());
-                
-                // 查询红娘信息
-                Matchmaker matchmaker = matchmakerMapper.selectById(binding.getMatchmakerId());
-                if (matchmaker != null) {
-                    vo.setMatchmakerName(matchmaker.getRealName());
-                    vo.setMatchmakerPhone(matchmaker.getPhone());
-                }
-            }
-        } catch (Exception e) {
-            // 忽略异常,不影响主流程
-        }
-    }
-    
     /**
      * 认证用户列表(资料完整的用户)
      */
@@ -275,6 +244,7 @@ public class UserController {
                 vo.setVipStartTime(vip.getStartTime());
                 vo.setVipEndTime(vip.getEndTime());
 
+                // 计算剩余天数:到期时间 - 当前时间
                 if (vip.getEndTime() != null) {
                     long days = Duration.between(LocalDateTime.now(), vip.getEndTime()).toDays();
                     vo.setRemainingDays((int) Math.max(days, 0));
@@ -297,6 +267,64 @@ public class UserController {
             return Result.error("查询失败:" + e.getMessage());
         }
     }
+
+    /**
+     * VIP 会员到期系统提醒
+     */
+    @PostMapping("/vip/remind")
+    public Result<String> vipRemind(@RequestBody Map<String, Object> params) {
+        try {
+            if (params == null) {
+                return Result.error("参数不能为空");
+            }
+
+            Object userIdObj = params.get("userId");
+            if (userIdObj == null) {
+                return Result.error("userId 不能为空");
+            }
+
+            Integer userId;
+            if (userIdObj instanceof Integer) {
+                userId = (Integer) userIdObj;
+            } else if (userIdObj instanceof Number) {
+                userId = ((Number) userIdObj).intValue();
+            } else {
+                try {
+                    userId = Integer.parseInt(userIdObj.toString());
+                } catch (NumberFormatException e) {
+                    return Result.error("userId 格式不正确");
+                }
+            }
+
+            if (userId <= 0) {
+                return Result.error("userId 必须大于 0");
+            }
+
+            String content = params.get("content") != null ? params.get("content").toString() : null;
+            if (content == null || content.trim().isEmpty()) {
+                return Result.error("content 不能为空");
+            }
+
+            // 构造系统消息
+            com.zhentao.entity.SystemMessages message = new com.zhentao.entity.SystemMessages();
+            message.setUserId(userId);
+            message.setTitle("系统通知-会员到期提醒");
+            message.setType("vip_expire_remind");
+            message.setContent(content);
+            message.setIsRead(0);
+            message.setCreatedAt(new Date());
+
+            int insert = systemMessagesMapper.insert(message);
+            if (insert <= 0) {
+                return Result.error("保存系统提醒失败");
+            }
+
+            return Result.success("发送成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("发送失败:" + e.getMessage());
+        }
+    }
     
     /**
      * 用户详情

+ 146 - 0
service/admin/src/main/java/com/zhentao/entity/ActivityRegistration.java

@@ -0,0 +1,146 @@
+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 lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 活动报名记录表
+ * @TableName activity_registration
+ */
+@TableName(value ="activity_registration")
+@Data
+public class ActivityRegistration implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 报名记录ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 活动ID
+     */
+    private Integer activityId;
+
+    /**
+     * 用户ID
+     */
+    private Integer userId;
+
+    /**
+     * 报名时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime registrationTime;
+
+    /**
+     * 报名状态(1:已报名,2:已取消,3:已签到)
+     */
+    private Integer status;
+
+    /**
+     * 支付状态(0:未支付,1:已支付)- 如果数据库中没有此字段,查询时使用默认值1(已支付)
+     */
+    @TableField(value = "payment_status", exist = false)
+    private Integer paymentStatus;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updatedTime;
+    
+    // ========== 关联查询字段(非数据库字段) ==========
+    
+    /**
+     * 活动名称
+     */
+    @TableField(exist = false)
+    private String activityName;
+    
+    /**
+     * 用户名(昵称)
+     */
+    @TableField(exist = false)
+    private String userName;
+    
+    /**
+     * 用户手机号
+     */
+    @TableField(exist = false)
+    private String userPhone;
+    
+    /**
+     * 用户性别
+     */
+    @TableField(exist = false)
+    private Integer userGender;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        ActivityRegistration other = (ActivityRegistration) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getActivityId() == null ? other.getActivityId() == null : this.getActivityId().equals(other.getActivityId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getRegistrationTime() == null ? other.getRegistrationTime() == null : this.getRegistrationTime().equals(other.getRegistrationTime()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getCreatedTime() == null ? other.getCreatedTime() == null : this.getCreatedTime().equals(other.getCreatedTime()))
+            && (this.getUpdatedTime() == null ? other.getUpdatedTime() == null : this.getUpdatedTime().equals(other.getUpdatedTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getActivityId() == null) ? 0 : getActivityId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getRegistrationTime() == null) ? 0 : getRegistrationTime().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getCreatedTime() == null) ? 0 : getCreatedTime().hashCode());
+        result = prime * result + ((getUpdatedTime() == null) ? 0 : getUpdatedTime().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(", activityId=").append(activityId);
+        sb.append(", userId=").append(userId);
+        sb.append(", registrationTime=").append(registrationTime);
+        sb.append(", status=").append(status);
+        sb.append(", createdTime=").append(createdTime);
+        sb.append(", updatedTime=").append(updatedTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 4 - 0
service/admin/src/main/java/com/zhentao/entity/AdminUser.java

@@ -55,6 +55,10 @@ public class AdminUser {
      * 状态:0-禁用 1-启用
      */
     private Integer status;
+    /**
+     * 头像
+     * */
+    private String avatar;
 
     /**
      * 角色:admin-普通管理员 super-超级管理员

+ 144 - 0
service/admin/src/main/java/com/zhentao/entity/HomeFunctionGrid.java

@@ -0,0 +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 java.util.Date;
+import lombok.Data;
+
+/**
+ * 首页金刚区功能表
+ * @TableName home_function_grid
+ */
+@TableName(value ="home_function_grid")
+@Data
+public class HomeFunctionGrid {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 功能名称
+     */
+    private String name;
+
+    /**
+     * 图标(emoji)
+     */
+    private String icon;
+
+    /**
+     * 跳转路径
+     */
+    private String path;
+
+    /**
+     * 背景颜色
+     */
+    private String bgColor;
+
+    /**
+     * 图标颜色
+     */
+    private String iconColor;
+
+    /**
+     * 渐变背景
+     */
+    private String gradient;
+
+    /**
+     * 是否需要登录:0-不需要 1-需要
+     */
+    private Integer needLogin;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 状态:0-禁用 1-启用
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    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;
+        }
+        HomeFunctionGrid other = (HomeFunctionGrid) 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.getIcon() == null ? other.getIcon() == null : this.getIcon().equals(other.getIcon()))
+            && (this.getPath() == null ? other.getPath() == null : this.getPath().equals(other.getPath()))
+            && (this.getBgColor() == null ? other.getBgColor() == null : this.getBgColor().equals(other.getBgColor()))
+            && (this.getIconColor() == null ? other.getIconColor() == null : this.getIconColor().equals(other.getIconColor()))
+            && (this.getGradient() == null ? other.getGradient() == null : this.getGradient().equals(other.getGradient()))
+            && (this.getNeedLogin() == null ? other.getNeedLogin() == null : this.getNeedLogin().equals(other.getNeedLogin()))
+            && (this.getSort() == null ? other.getSort() == null : this.getSort().equals(other.getSort()))
+            && (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()));
+    }
+
+    @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 + ((getIcon() == null) ? 0 : getIcon().hashCode());
+        result = prime * result + ((getPath() == null) ? 0 : getPath().hashCode());
+        result = prime * result + ((getBgColor() == null) ? 0 : getBgColor().hashCode());
+        result = prime * result + ((getIconColor() == null) ? 0 : getIconColor().hashCode());
+        result = prime * result + ((getGradient() == null) ? 0 : getGradient().hashCode());
+        result = prime * result + ((getNeedLogin() == null) ? 0 : getNeedLogin().hashCode());
+        result = prime * result + ((getSort() == null) ? 0 : getSort().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());
+        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(", icon=").append(icon);
+        sb.append(", path=").append(path);
+        sb.append(", bgColor=").append(bgColor);
+        sb.append(", iconColor=").append(iconColor);
+        sb.append(", gradient=").append(gradient);
+        sb.append(", needLogin=").append(needLogin);
+        sb.append(", sort=").append(sort);
+        sb.append(", status=").append(status);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 35 - 0
service/admin/src/main/java/com/zhentao/mapper/ActivityRegistrationMapper.java

@@ -0,0 +1,35 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.ActivityRegistration;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+* @author 联想
+* @description 针对表【activity_registration(活动报名记录表)】的数据库操作Mapper
+* @createDate 2025-12-17 09:04:20
+* @Entity com.zhentao.entity.ActivityRegistration
+*/
+public interface ActivityRegistrationMapper extends BaseMapper<ActivityRegistration> {
+
+    /**
+     * 分页查询报名列表(关联查询活动和用户信息)
+     * @param activityId 活动ID
+     * @param checkInStatus 签到状态(1:已报名,2:已取消,3:已签到)
+     * @param paymentStatus 支付状态(0:未支付,1:已支付)
+     * @param keyword 搜索关键词(用户名或手机号)
+     * @return 报名列表
+     */
+    List<ActivityRegistration> selectRegistrationList(
+            @Param("activityId") Integer activityId,
+            @Param("checkInStatus") Integer checkInStatus,
+            @Param("paymentStatus") Integer paymentStatus,
+            @Param("keyword") String keyword
+    );
+}
+
+
+
+

+ 18 - 0
service/admin/src/main/java/com/zhentao/mapper/HomeFunctionGridMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.mapper;
+
+import com.zhentao.entity.HomeFunctionGrid;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 联想
+* @description 针对表【home_function_grid(首页金刚区功能表)】的数据库操作Mapper
+* @createDate 2025-12-17 10:56:17
+* @Entity com.zhentao.entity.HomeFunctionGrid
+*/
+public interface HomeFunctionGridMapper extends BaseMapper<HomeFunctionGrid> {
+
+}
+
+
+
+

+ 55 - 0
service/admin/src/main/java/com/zhentao/service/ActivityRegistrationService.java

@@ -0,0 +1,55 @@
+package com.zhentao.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zhentao.entity.ActivityRegistration;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+* @author 联想
+* @description 针对表【activity_registration(活动报名记录表)】的数据库操作Service
+* @createDate 2025-12-17 09:04:20
+*/
+public interface ActivityRegistrationService extends IService<ActivityRegistration> {
+
+    /**
+     * 分页查询报名列表(关联查询活动和用户信息)
+     * @param page 页码
+     * @param pageSize 每页大小
+     * @param activityId 活动ID(可选)
+     * @param checkInStatus 签到状态(可选,1:已报名,2:已取消,3:已签到)
+     * @param paymentStatus 支付状态(可选,0:未支付,1:已支付)
+     * @param keyword 搜索关键词(可选,用户名或手机号)
+     * @return 分页结果
+     */
+    Map<String, Object> getRegistrationList(Integer page, Integer pageSize, 
+                                             Integer activityId, 
+                                             Integer checkInStatus, 
+                                             Integer paymentStatus, 
+                                             String keyword);
+
+    /**
+     * 查询所有符合条件的报名记录(用于导出)
+     * @param activityId 活动ID(可选)
+     * @param checkInStatus 签到状态(可选)
+     * @param paymentStatus 支付状态(可选)
+     * @param keyword 搜索关键词(可选)
+     * @return 报名列表
+     */
+    List<ActivityRegistration> getAllRegistrations(Integer activityId, 
+                                                    Integer checkInStatus, 
+                                                    Integer paymentStatus, 
+                                                    String keyword);
+
+    /**
+     * 获取报名统计数据
+     * 报名总数:签到状态为已报名(status=1)的记录数
+     * 已签到:签到状态为已签到(status=3)的记录数
+     * 未签到:签到状态为已报名(status=1)但未签到(status≠3)的记录数
+     * @param activityId 活动ID(必填)
+     * @return 统计数据
+     */
+    Map<String, Object> getRegistrationStats(Integer activityId);
+}

+ 13 - 0
service/admin/src/main/java/com/zhentao/service/HomeFunctionGridService.java

@@ -0,0 +1,13 @@
+package com.zhentao.service;
+
+import com.zhentao.entity.HomeFunctionGrid;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 联想
+* @description 针对表【home_function_grid(首页金刚区功能表)】的数据库操作Service
+* @createDate 2025-12-17 10:56:17
+*/
+public interface HomeFunctionGridService extends IService<HomeFunctionGrid> {
+
+}

+ 8 - 0
service/admin/src/main/java/com/zhentao/service/SystemMessagesService.java

@@ -1,5 +1,6 @@
 package com.zhentao.service;
 
+import com.zhentao.entity.MarrApply;
 import com.zhentao.entity.PointsOrder;
 import com.zhentao.entity.SystemMessages;
 import com.baomidou.mybatisplus.extension.service.IService;
@@ -16,4 +17,11 @@ public interface SystemMessagesService extends IService<SystemMessages> {
      * @param pointsOrder 积分订单
      */
     void createPointsOrderReviewMessage(PointsOrder pointsOrder);
+    
+    /**
+     * 创建红娘申请审核系统消息
+     * @param marrApply 红娘申请
+     * @param approved 审核结果,true-通过,false-不通过
+     */
+    void createMatchmakerApplyReviewMessage(MarrApply marrApply, Boolean approved);
 }

+ 5 - 0
service/admin/src/main/java/com/zhentao/service/UserService.java

@@ -31,6 +31,11 @@ public interface UserService {
      */
     boolean updateUser(AdminUser user);
     
+    /**
+     * 更新用户个人资料(不包括密码)
+     */
+    boolean updateUserProfile(AdminUser user);
+    
     /**
      * 删除用户
      */

+ 121 - 0
service/admin/src/main/java/com/zhentao/service/impl/ActivityRegistrationServiceImpl.java

@@ -0,0 +1,121 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.ActivityRegistration;
+import com.zhentao.mapper.ActivityRegistrationMapper;
+import com.zhentao.service.ActivityRegistrationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+* @author 联想
+* @description 针对表【activity_registration(活动报名记录表)】的数据库操作Service实现
+* @createDate 2025-12-17 09:04:20
+*/
+@Service
+public class ActivityRegistrationServiceImpl extends ServiceImpl<ActivityRegistrationMapper, ActivityRegistration>
+    implements ActivityRegistrationService{
+
+    @Autowired
+    private ActivityRegistrationMapper activityRegistrationMapper;
+
+    @Override
+    public Map<String, Object> getRegistrationList(Integer page, Integer pageSize, 
+                                                    Integer activityId, 
+                                                    Integer checkInStatus, 
+                                                    Integer paymentStatus, 
+                                                    String keyword) {
+        // 查询所有符合条件的记录
+        List<ActivityRegistration> allList = activityRegistrationMapper.selectRegistrationList(
+                activityId, checkInStatus, paymentStatus, keyword);
+        
+        // 手动分页
+        int total = allList.size();
+        int start = (page - 1) * pageSize;
+        int end = Math.min(start + pageSize, total);
+        List<ActivityRegistration> pageList;
+        if (start < total) {
+            pageList = allList.subList(start, end);
+        } else {
+            pageList = new java.util.ArrayList<>();
+        }
+        
+        // 如果没有支付状态,默认设置为1(已支付)
+        for (ActivityRegistration registration : pageList) {
+            if (registration.getPaymentStatus() == null) {
+                registration.setPaymentStatus(1);
+            }
+        }
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("list", pageList);
+        result.put("total", total);
+        result.put("page", page);
+        result.put("pageSize", pageSize);
+        
+        return result;
+    }
+
+    @Override
+    public List<ActivityRegistration> getAllRegistrations(Integer activityId, 
+                                                           Integer checkInStatus, 
+                                                           Integer paymentStatus, 
+                                                           String keyword) {
+        List<ActivityRegistration> list = activityRegistrationMapper.selectRegistrationList(
+                activityId, checkInStatus, paymentStatus, keyword);
+        
+        // 如果没有支付状态,默认设置为1(已支付)
+        for (ActivityRegistration registration : list) {
+            if (registration.getPaymentStatus() == null) {
+                registration.setPaymentStatus(1);
+            }
+        }
+        
+        return list;
+    }
+
+    @Override
+    public Map<String, Object> getRegistrationStats(Integer activityId) {
+        Map<String, Object> stats = new HashMap<>();
+        
+        if (activityId == null) {
+            stats.put("totalCount", 0);
+            stats.put("checkedInCount", 0);
+            stats.put("notCheckedInCount", 0);
+            return stats;
+        }
+        
+        // 报名总数:签到状态为已报名(status=1)的记录数
+        List<ActivityRegistration> registeredList = activityRegistrationMapper.selectRegistrationList(
+                activityId, 1, null, null);
+        int totalCount = registeredList.size();
+        
+        // 已签到:查询status=3的记录数(这些记录之前是status=1,现在已签到)
+        // 注意:已签到的记录现在status=3,所以不会出现在status=1的查询结果中
+        List<ActivityRegistration> checkedInList = activityRegistrationMapper.selectRegistrationList(
+                activityId, 3, null, null);
+        int checkedInCount = checkedInList.size();
+        
+        // 未签到:签到状态为已报名(status=1)但未签到(status≠3)的记录数
+        // 根据用户需求,未签到也是根据签到状态是已报名进行显示的
+        // 所以未签到数 = 报名总数(status=1的记录数)
+        // 因为status=1的记录就是未签到的
+        int notCheckedInCount = totalCount;
+        
+        stats.put("totalCount", totalCount);
+        stats.put("checkedInCount", checkedInCount);
+        stats.put("notCheckedInCount", notCheckedInCount);
+        
+        return stats;
+    }
+}
+
+
+
+

+ 22 - 0
service/admin/src/main/java/com/zhentao/service/impl/HomeFunctionGridServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.HomeFunctionGrid;
+import com.zhentao.service.HomeFunctionGridService;
+import com.zhentao.mapper.HomeFunctionGridMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 联想
+* @description 针对表【home_function_grid(首页金刚区功能表)】的数据库操作Service实现
+* @createDate 2025-12-17 10:56:17
+*/
+@Service
+public class HomeFunctionGridServiceImpl extends ServiceImpl<HomeFunctionGridMapper, HomeFunctionGrid>
+    implements HomeFunctionGridService{
+
+}
+
+
+
+

+ 16 - 0
service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java

@@ -5,6 +5,7 @@ import com.zhentao.entity.MarrApply;
 import com.zhentao.entity.Matchmaker;
 import com.zhentao.entity.Users;
 import com.zhentao.service.MarrApplyService;
+import com.zhentao.service.SystemMessagesService;
 import com.zhentao.mapper.MarrApplyMapper;
 import com.zhentao.mapper.MatchmakerMapper;
 import com.zhentao.mapper.UsersMapper;
@@ -55,6 +56,9 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
     @Autowired
     private AreaMapper areaMapper;
     
+    @Autowired
+    private SystemMessagesService systemMessagesService;
+    
     private final RestTemplate restTemplate = new RestTemplate();
     
     // websocket 服务的 IM 接口地址
@@ -205,6 +209,18 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
         }
         
         this.updateById(apply);
+        
+        // 创建系统消息通知用户审核结果
+        try {
+            System.out.println("开始调用系统消息创建方法...");
+            systemMessagesService.createMatchmakerApplyReviewMessage(apply, approved);
+            System.out.println("系统消息创建方法调用完成");
+        } catch (Exception e) {
+            // 系统消息创建失败不影响审核结果,只记录日志
+            System.err.println("创建系统消息失败:" + e.getMessage());
+            e.printStackTrace();
+        }
+        
         System.out.println("========== 审核完成 ==========");
         return true;
     }

+ 96 - 0
service/admin/src/main/java/com/zhentao/service/impl/SystemMessagesServiceImpl.java

@@ -2,6 +2,7 @@ package com.zhentao.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.MarrApply;
 import com.zhentao.entity.PointsOrder;
 import com.zhentao.entity.SystemMessages;
 import com.zhentao.entity.Users;
@@ -135,6 +136,101 @@ public class SystemMessagesServiceImpl extends ServiceImpl<SystemMessagesMapper,
         
         System.out.println("========== 创建积分订单审核系统消息完成 ==========");
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void createMatchmakerApplyReviewMessage(MarrApply marrApply, Boolean approved) {
+        System.out.println("========== 开始创建红娘申请审核系统消息 ==========");
+        System.out.println("申请信息: " + (marrApply != null ? marrApply.toString() : "null"));
+        System.out.println("审核结果: " + (approved != null ? (approved ? "通过" : "不通过") : "null"));
+        
+        if (marrApply == null) {
+            System.err.println("错误:marrApply 为 null,无法创建系统消息");
+            return;
+        }
+        
+        if (marrApply.getUserId() == null) {
+            System.err.println("错误:红娘申请的用户ID为空,申请ID=" + marrApply.getApplyId());
+            return;
+        }
+
+        System.out.println("用户ID: " + marrApply.getUserId());
+        System.out.println("审核状态: " + (approved != null ? (approved ? "通过" : "不通过") : "null"));
+        
+        // 验证用户是否存在
+        Users user = usersMapper.selectById(marrApply.getUserId());
+        if (user == null) {
+            System.err.println("错误:未找到用户ID为 " + marrApply.getUserId() + " 的用户,无法创建系统消息");
+            return;
+        }
+        
+        if (user.getUserId() == null) {
+            System.err.println("错误:找到用户但 userId 为 null,用户ID=" + marrApply.getUserId());
+            return;
+        }
+
+        System.out.println("找到用户: userId=" + user.getUserId() + ", phone=" + user.getPhone());
+
+        // 创建系统消息
+        SystemMessages message = new SystemMessages();
+        message.setUserId(user.getUserId());
+        message.setTitle("系统通知-红娘申请审核通知");
+        message.setType("marr_apply_review"); // 设置消息类型为红娘申请审核
+        
+        // 根据审核状态设置内容
+        if (approved != null) {
+            if (approved) {
+                // 审核通过
+                String content = "经平台核实,已通过您的红娘申请,恭喜您成为青鸾之恋红娘大家族的一员!!!";
+                message.setContent(content);
+                System.out.println("审核通过,消息内容: " + content);
+            } else {
+                // 审核未通过
+                String reason = marrApply.getReason() != null && !marrApply.getReason().trim().isEmpty() 
+                    ? marrApply.getReason() : "未说明原因";
+                String content = "经平台核实,抱歉您的红娘申请未通过,原因是:" + reason;
+                message.setContent(content);
+                System.out.println("审核未通过,消息内容: " + content);
+            }
+        } else {
+            // 审核状态为空,不创建消息
+            System.err.println("错误:审核状态为 null,不创建消息");
+            return;
+        }
+
+        message.setIsRead(0); // 0-未读
+        message.setCreatedAt(new Date());
+        
+        System.out.println("准备保存系统消息: userId=" + message.getUserId() + ", title=" + message.getTitle() + ", content=" + message.getContent());
+        System.out.println("消息对象详情: " + message.toString());
+        
+        // 保存系统消息 - 使用 systemMessagesMapper.insert() 直接插入
+        try {
+            int insertResult = systemMessagesMapper.insert(message);
+            if (insertResult > 0) {
+                System.out.println("系统消息保存成功!插入行数=" + insertResult + ", 消息ID=" + (message.getId() != null ? message.getId() : "待生成"));
+                System.out.println("保存后的消息对象: " + message.toString());
+                
+                // 验证数据是否真的插入到数据库
+                if (message.getId() != null) {
+                    SystemMessages savedMessage = systemMessagesMapper.selectById(message.getId());
+                    if (savedMessage != null) {
+                        System.out.println("验证成功:从数据库查询到保存的消息,ID=" + savedMessage.getId());
+                    } else {
+                        System.err.println("警告:消息保存后无法从数据库查询到,可能存在事务问题");
+                    }
+                }
+            } else {
+                System.err.println("系统消息保存失败!insert() 方法返回 0,未插入任何数据");
+            }
+        } catch (Exception e) {
+            System.err.println("系统消息保存时发生异常:" + e.getMessage());
+            e.printStackTrace();
+            throw new RuntimeException("保存系统消息失败:" + e.getMessage(), e);
+        }
+        
+        System.out.println("========== 创建红娘申请审核系统消息完成 ==========");
+    }
 }
 
 

+ 9 - 0
service/admin/src/main/java/com/zhentao/service/impl/UserServiceImpl.java

@@ -139,6 +139,15 @@ public class UserServiceImpl implements UserService {
         return adminUserMapper.updateById(user) > 0;
     }
     
+    @Override
+    public boolean updateUserProfile(AdminUser user) {
+        user.setUpdateTime(new Date());
+        // 清除密码和盐字段,确保不会更新密码
+        user.setPassword(null);
+        user.setSalt(null);
+        return adminUserMapper.updateById(user) > 0;
+    }
+    
     @Override
     @Transactional
     public boolean deleteUser(Integer userId) {

+ 100 - 0
service/admin/src/main/java/com/zhentao/vo/ActivityRegistrationExcelVO.java

@@ -0,0 +1,100 @@
+package com.zhentao.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 活动报名Excel导出VO
+ */
+@Data
+@ColumnWidth(20)
+public class ActivityRegistrationExcelVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    
+    @ExcelProperty(value = "报名ID", index = 0)
+    private Long id;
+    
+    @ExcelProperty(value = "活动名称", index = 1)
+    private String activityName;
+    
+    @ExcelProperty(value = "用户ID", index = 2)
+    private Integer userId;
+    
+    @ExcelProperty(value = "用户名", index = 3)
+    private String userName;
+    
+    @ExcelProperty(value = "手机号", index = 4)
+    private String userPhone;
+    
+    @ExcelProperty(value = "性别", index = 5)
+    private String userGender;
+    
+    @ExcelProperty(value = "报名时间", index = 6)
+    private String registrationTime;
+    
+    @ExcelProperty(value = "签到状态", index = 7)
+    private String checkInStatus;
+    
+    @ExcelProperty(value = "支付状态", index = 8)
+    private String paymentStatus;
+    
+    @ExcelProperty(value = "创建时间", index = 9)
+    private String createdTime;
+    
+    /**
+     * 将实体类转换为ExcelVO
+     */
+    public static ActivityRegistrationExcelVO fromEntity(com.zhentao.entity.ActivityRegistration registration) {
+        ActivityRegistrationExcelVO vo = new ActivityRegistrationExcelVO();
+        vo.setId(registration.getId());
+        vo.setActivityName(registration.getActivityName());
+        vo.setUserId(registration.getUserId());
+        vo.setUserName(registration.getUserName());
+        vo.setUserPhone(registration.getUserPhone());
+        vo.setUserGender(registration.getUserGender() != null && registration.getUserGender() == 1 ? "男" : "女");
+        
+        if (registration.getRegistrationTime() != null) {
+            vo.setRegistrationTime(registration.getRegistrationTime().format(FORMATTER));
+        }
+        
+        // 签到状态:1-已报名,2-已取消,3-已签到
+        Integer status = registration.getStatus();
+        if (status != null) {
+            switch (status) {
+                case 1:
+                    vo.setCheckInStatus("已报名");
+                    break;
+                case 2:
+                    vo.setCheckInStatus("已取消");
+                    break;
+                case 3:
+                    vo.setCheckInStatus("已签到");
+                    break;
+                default:
+                    vo.setCheckInStatus("未知");
+            }
+        }
+        
+        // 支付状态:0-未支付,1-已支付
+        Integer paymentStatus = registration.getPaymentStatus();
+        if (paymentStatus != null) {
+            vo.setPaymentStatus(paymentStatus == 1 ? "已支付" : "未支付");
+        } else {
+            vo.setPaymentStatus("已支付"); // 默认已支付
+        }
+        
+        if (registration.getCreatedTime() != null) {
+            vo.setCreatedTime(registration.getCreatedTime().format(FORMATTER));
+        }
+        
+        return vo;
+    }
+}
+

+ 81 - 0
service/admin/src/main/resources/com/zhentao/mapper/ActivityRegistrationMapper.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.mapper.ActivityRegistrationMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.entity.ActivityRegistration">
+            <id property="id" column="id" />
+            <result property="activityId" column="activity_id" />
+            <result property="userId" column="user_id" />
+            <result property="registrationTime" column="registration_time" />
+            <result property="status" column="status" />
+            <result property="paymentStatus" column="payment_status" />
+            <result property="createdTime" column="created_time" />
+            <result property="updatedTime" column="updated_time" />
+            <!-- 关联查询字段 -->
+            <result property="activityName" column="activity_name" />
+            <result property="userName" column="user_name" />
+            <result property="userPhone" column="user_phone" />
+            <result property="userGender" column="user_gender" />
+    </resultMap>
+
+    <resultMap id="RegistrationWithDetailsMap" type="com.zhentao.entity.ActivityRegistration">
+            <id property="id" column="id" />
+            <result property="activityId" column="activity_id" />
+            <result property="userId" column="user_id" />
+            <result property="registrationTime" column="registration_time" />
+            <result property="status" column="status" />
+            <result property="paymentStatus" column="payment_status" />
+            <result property="createdTime" column="created_time" />
+            <result property="updatedTime" column="updated_time" />
+            <!-- 关联查询字段 -->
+            <result property="activityName" column="activity_name" />
+            <result property="userName" column="user_name" />
+            <result property="userPhone" column="user_phone" />
+            <result property="userGender" column="user_gender" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        ar.id,ar.activity_id,ar.user_id,ar.registration_time,ar.status,
+        1 as payment_status,
+        ar.created_time,ar.updated_time
+    </sql>
+
+    <!-- 关联查询字段 -->
+    <sql id="Join_Column_List">
+        <include refid="Base_Column_List"/>,
+        a.name as activity_name,
+        u.nickname as user_name,
+        u.phone as user_phone,
+        u.gender as user_gender
+    </sql>
+
+    <!-- 分页查询报名列表(关联查询活动和用户信息) -->
+    <select id="selectRegistrationList" resultMap="RegistrationWithDetailsMap">
+        SELECT
+        <include refid="Join_Column_List"/>
+        FROM activity_registration ar
+        LEFT JOIN activity a ON ar.activity_id = a.id
+        LEFT JOIN users u ON ar.user_id = u.user_id
+        <where>
+            <if test="activityId != null">
+                AND ar.activity_id = #{activityId}
+            </if>
+            <if test="checkInStatus != null">
+                AND ar.status = #{checkInStatus}
+            </if>
+            <!-- 支付状态筛选(如果数据库有payment_status字段,可以启用此条件) -->
+            <!-- 
+            <if test="paymentStatus != null">
+                AND IFNULL(ar.payment_status, 1) = #{paymentStatus}
+            </if>
+            -->
+            <if test="keyword != null and keyword != ''">
+                AND (u.nickname LIKE CONCAT('%', #{keyword}, '%')
+                OR u.phone LIKE CONCAT('%', #{keyword}, '%'))
+            </if>
+        </where>
+        ORDER BY ar.registration_time DESC
+    </select>
+</mapper>

+ 27 - 0
service/admin/src/main/resources/com/zhentao/mapper/HomeFunctionGridMapper.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.mapper.HomeFunctionGridMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.entity.HomeFunctionGrid">
+            <id property="id" column="id" />
+            <result property="name" column="name" />
+            <result property="icon" column="icon" />
+            <result property="path" column="path" />
+            <result property="bgColor" column="bg_color" />
+            <result property="iconColor" column="icon_color" />
+            <result property="gradient" column="gradient" />
+            <result property="needLogin" column="need_login" />
+            <result property="sort" column="sort" />
+            <result property="status" column="status" />
+            <result property="createTime" column="create_time" />
+            <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,name,icon,path,bg_color,icon_color,
+        gradient,need_login,sort,status,create_time,
+        update_time
+    </sql>
+</mapper>

+ 1 - 1
service/homePage/pom.xml

@@ -1,4 +1,4 @@
-2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>

+ 17 - 0
service/homePage/src/main/java/com/zhentao/controller/HomeController.java

@@ -81,6 +81,23 @@ public class HomeController {
             return Result.error("获取金刚区功能列表失败:" + e.getMessage());
         }
     }
+    
+    /**
+     * 根据类型获取金刚区功能列表
+     * 
+     * @param type 功能类型:user-用户端 matchmaker-红娘端
+     * @return 金刚区功能列表
+     */
+    @GetMapping("/function-grid/type")
+    public Result<List> getFunctionGridByType(@RequestParam String type) {
+        try {
+            // 从数据库获取指定类型的启用金刚区功能列表
+            return Result.success(homeFunctionGridService.getEnabledFunctionGridsByType(type));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取金刚区功能列表失败:" + e.getMessage());
+        }
+    }
 }
 
 

+ 15 - 0
service/homePage/src/main/java/com/zhentao/entity/HomeFunctionGrid.java

@@ -69,6 +69,12 @@ public class HomeFunctionGrid implements Serializable {
     @TableField("need_login")
     private Boolean needLogin;
 
+    /**
+     * 功能类型:user-用户端 matchmaker-红娘端
+     */
+    @TableField("type")
+    private String type;
+
     /**
      * 排序
      */
@@ -158,6 +164,14 @@ public class HomeFunctionGrid implements Serializable {
         this.needLogin = needLogin;
     }
 
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
     public Integer getSort() {
         return sort;
     }
@@ -201,6 +215,7 @@ public class HomeFunctionGrid implements Serializable {
                 ", iconColor='" + iconColor + '\'' +
                 ", gradient='" + gradient + '\'' +
                 ", needLogin=" + needLogin +
+                ", type='" + type + '\'' +
                 ", sort=" + sort +
                 ", status=" + status +
                 ", createTime=" + createTime +

+ 7 - 0
service/homePage/src/main/java/com/zhentao/service/HomeFunctionGridService.java

@@ -19,4 +19,11 @@ public interface HomeFunctionGridService extends IService<HomeFunctionGrid> {
      * @return 金刚区功能列表
      */
     List<HomeFunctionGrid> getEnabledFunctionGrids();
+    
+    /**
+     * 根据类型获取启用的金刚区功能列表,按排序排序
+     * @param type 功能类型:user-用户端 matchmaker-红娘端
+     * @return 金刚区功能列表
+     */
+    List<HomeFunctionGrid> getEnabledFunctionGridsByType(String type);
 }

+ 10 - 0
service/homePage/src/main/java/com/zhentao/service/impl/HomeFunctionGridServiceImpl.java

@@ -27,4 +27,14 @@ public class HomeFunctionGridServiceImpl extends ServiceImpl<HomeFunctionGridMap
                 .orderByAsc(HomeFunctionGrid::getSort)
         );
     }
+    
+    @Override
+    public List<HomeFunctionGrid> getEnabledFunctionGridsByType(String type) {
+        // 根据类型查询启用的金刚区功能,按排序字段升序排列
+        return baseMapper.selectList(new LambdaQueryWrapper<HomeFunctionGrid>()
+                .eq(HomeFunctionGrid::getStatus, true)
+                .eq(HomeFunctionGrid::getType, type)
+                .orderByAsc(HomeFunctionGrid::getSort)
+        );
+    }
 }

+ 14 - 6
service/homePage/src/main/resources/sql/home_function_grid.sql

@@ -8,17 +8,25 @@ CREATE TABLE IF NOT EXISTS `home_function_grid` (
   `icon_color` VARCHAR(50) NULL COMMENT '图标颜色',
   `gradient` VARCHAR(255) NULL COMMENT '渐变背景',
   `need_login` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否需要登录:0-不需要 1-需要',
+  `type` VARCHAR(20) NOT NULL DEFAULT 'user' COMMENT '功能类型:user-用户端 matchmaker-红娘端',
   `sort` INT NOT NULL DEFAULT 0 COMMENT '排序',
   `status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态:0-禁用 1-启用',
   `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   PRIMARY KEY (`id`),
-  KEY `idx_status_sort` (`status`, `sort`)
+  KEY `idx_status_sort_type` (`status`, `sort`, `type`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页金刚区功能表';
 
 -- 插入初始数据
-INSERT INTO `home_function_grid` (`name`, `icon`, `path`, `bg_color`, `icon_color`, `gradient`, `need_login`, `sort`, `status`) VALUES
-('星命测算', '💖', '/pages/astrology/index', '#FF6B9D', '#FFFFFF', 'linear-gradient(135deg, #FF6B9D 0%, #FF8EAB 100%)', 0, 1, 1),
-('红娘列表', '👤', '/pages/matchmakers/list', '#6BC5F8', '#FFFFFF', 'linear-gradient(135deg, #6BC5F8 0%, #87CEEB 100%)', 0, 2, 1),
-('精品课程', '📚', '/pages/courses/list', '#FF8C42', '#FFFFFF', 'linear-gradient(135deg, #FF8C42 0%, #FFA366 100%)', 0, 3, 1),
-('今日缘分', '💝', '/pages/recommend/index', '#FF69B4', '#FFFFFF', 'linear-gradient(135deg, #FF69B4 0%, #FF8CC8 100%)', 1, 4, 1);
+INSERT INTO `home_function_grid` (`name`, `icon`, `path`, `bg_color`, `icon_color`, `gradient`, `need_login`, `type`, `sort`, `status`) VALUES
+-- 用户端金刚区功能
+('星命测算', '💖', '/pages/astrology/index', '#FF6B9D', '#FFFFFF', 'linear-gradient(135deg, #FF6B9D 0%, #FF8EAB 100%)', 0, 'user', 1, 1),
+('红娘列表', '👤', '/pages/matchmakers/list', '#6BC5F8', '#FFFFFF', 'linear-gradient(135deg, #6BC5F8 0%, #87CEEB 100%)', 0, 'user', 2, 1),
+('精品课程', '📚', '/pages/courses/list', '#FF8C42', '#FFFFFF', 'linear-gradient(135deg, #FF8C42 0%, #FFA366 100%)', 0, 'user', 3, 1),
+('今日缘分', '💝', '/pages/recommend/index', '#FF69B4', '#FFFFFF', 'linear-gradient(135deg, #FF69B4 0%, #FF8CC8 100%)', 1, 'user', 4, 1),
+-- 红娘端金刚区功能
+('我的资源', '📋', '/pages/matchmaker-workbench/my-resources', '#4CAF50', '#FFFFFF', 'linear-gradient(135deg, #4CAF50 0%, #66BB6A 100%)', 1, 'matchmaker', 1, 1),
+('优质资源', '🌟', '/pages/matchmaker-workbench/quality-resources', '#2196F3', '#FFFFFF', 'linear-gradient(135deg, #2196F3 0%, #42A5F5 100%)', 1, 'matchmaker', 2, 1),
+('课程培训', '🎓', '/pages/courses/list', '#FF9800', '#FFFFFF', 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)', 0, 'matchmaker', 3, 1),
+('积分商城', '🎁', '/pages/matchmaker-workbench/points-mall', '#9C27B0', '#FFFFFF', 'linear-gradient(135deg, #9C27B0 0%, #AB47BC 100%)', 1, 'matchmaker', 4, 1),
+('活动中心', '🎉', '/pages/matchmaker-workbench/activities', '#F44336', '#FFFFFF', 'linear-gradient(135deg, #F44336 0%, #EF5350 100%)', 0, 'matchmaker', 5, 1);