9 Revize 4d436fc13d ... bac0f7b3eb

Autor SHA1 Zpráva Datum
  李思佳 bac0f7b3eb Merge branch 'lisijia' into test_dev před 3 týdny
  李思佳 649ed1338d style(index): 优化红娘工作台切换按钮样式 před 3 týdny
  wangwenju bf855e778e Merge branch 'test_dev' into wwj před 3 týdny
  wangwenju a3d818e4f9 首页金刚区样式修改 před 3 týdny
  mazhenhang 7caddd81a8 Merge branch 'mzh' into test_dev před 3 týdny
  wangwenju e56c4bcd66 Merge remote-tracking branch 'origin/test_dev' into test_dev před 3 týdny
  wangwenju 37d984c103 首页金刚区交由数据库管理 před 3 týdny
  caojp 2ff54a90f0 Merge branch 'cjp' into test_dev před 3 týdny
  caojp 9b7b02e772 管理端功能增加 před 3 týdny
62 změnil soubory, kde provedl 5422 přidání a 1415 odebrání
  1. 187 185
      LiangZhiYUMao/pages/index/index.vue
  2. 647 655
      LiangZhiYUMao/pages/matchmaker-workbench/index.vue
  3. 6 44
      LiangZhiYUMao/pages/message/chat.vue
  4. 3 0
      LiangZhiYUMao/utils/api.js
  5. 0 0
      common/src/main/java/com/zhentao/constant/RedisKeyConstants.java
  6. 1 1
      common/src/main/java/com/zhentao/pojo/MatchmakerApply.java
  7. 3 2
      common/src/main/java/com/zhentao/service/impl/MatchmakerApplyServiceImpl.java
  8. binární
      marriageAdmin-vue/src/assets/qingluan.png
  9. 2 0
      marriageAdmin-vue/src/config/api.js
  10. 2 2
      marriageAdmin-vue/src/layouts/MainLayout.vue
  11. 168 57
      marriageAdmin-vue/src/views/Login.vue
  12. 33 22
      marriageAdmin-vue/src/views/course/CourseForm.vue
  13. 406 46
      marriageAdmin-vue/src/views/matchmaker/CaseAudit.vue
  14. 516 33
      marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue
  15. 241 2
      marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue
  16. 120 2
      marriageAdmin-vue/src/views/matchmaker/PointsOrderList.vue
  17. 294 39
      marriageAdmin-vue/src/views/matchmaker/ResourceList.vue
  18. 188 23
      marriageAdmin-vue/src/views/points-product/PointsProductList.vue
  19. 272 59
      marriageAdmin-vue/src/views/report/ReportList.vue
  20. 1 0
      pom.xml
  21. 6 0
      service/Recommend/pom.xml
  22. 33 0
      service/Recommend/src/main/java/com/zhentao/config/SecurityConfig.java
  23. 14 14
      service/Recommend/src/main/java/com/zhentao/controller/RecommendController.java
  24. 3 3
      service/admin/src/main/java/com/zhentao/controller/AdminUserController.java
  25. 3 3
      service/admin/src/main/java/com/zhentao/controller/AnnouncementController.java
  26. 3 3
      service/admin/src/main/java/com/zhentao/controller/BannerController.java
  27. 3 3
      service/admin/src/main/java/com/zhentao/controller/DashboardController.java
  28. 22 4
      service/admin/src/main/java/com/zhentao/controller/DynamicController.java
  29. 20 4
      service/admin/src/main/java/com/zhentao/controller/DynamicReportController.java
  30. 68 10
      service/admin/src/main/java/com/zhentao/controller/MarrApplyControllor.java
  31. 33 6
      service/admin/src/main/java/com/zhentao/controller/MatchmakerSuccessCaseUploadController.java
  32. 1 1
      service/admin/src/main/java/com/zhentao/controller/MatchmakerWorkbenchController.java
  33. 16 15
      service/admin/src/main/java/com/zhentao/controller/MyResourceController.java
  34. 11 11
      service/admin/src/main/java/com/zhentao/controller/PointsMallController.java
  35. 8 8
      service/admin/src/main/java/com/zhentao/controller/PointsOrderController.java
  36. 5 5
      service/admin/src/main/java/com/zhentao/controller/PointsProductController.java
  37. 7 7
      service/admin/src/main/java/com/zhentao/controller/UserController.java
  38. 4 4
      service/admin/src/main/java/com/zhentao/controller/VipPackageController.java
  39. 8 3
      service/admin/src/main/java/com/zhentao/entity/Dynamic.java
  40. 3 0
      service/admin/src/main/java/com/zhentao/entity/DynamicReport.java
  41. 5 1
      service/admin/src/main/java/com/zhentao/entity/MarrApply.java
  42. 3 0
      service/admin/src/main/java/com/zhentao/entity/PointsOrder.java
  43. 24 0
      service/admin/src/main/java/com/zhentao/mapper/DynamicReportMapper.java
  44. 14 2
      service/admin/src/main/java/com/zhentao/service/MarrApplyService.java
  45. 162 109
      service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java
  46. 4 4
      service/admin/src/main/java/com/zhentao/vo/MatchmakerSuccessCaseUploadVO.java
  47. 2 1
      service/admin/src/main/resources/com/zhentao/mapper/MarrApplyMapper.xml
  48. 3 3
      service/homePage/src/main/java/com/zhentao/controller/CourseController.java
  49. 21 0
      service/homePage/src/main/java/com/zhentao/controller/HomeController.java
  50. 5 5
      service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java
  51. 1 1
      service/homePage/src/main/java/com/zhentao/controller/MatchmakerCourseController.java
  52. 11 11
      service/homePage/src/main/java/com/zhentao/controller/MyResourceController.java
  53. 2 2
      service/homePage/src/main/java/com/zhentao/controller/SuccessCaseController.java
  54. 210 0
      service/homePage/src/main/java/com/zhentao/entity/HomeFunctionGrid.java
  55. 18 0
      service/homePage/src/main/java/com/zhentao/mapper/HomeFunctionGridMapper.java
  56. 22 0
      service/homePage/src/main/java/com/zhentao/service/HomeFunctionGridService.java
  57. 30 0
      service/homePage/src/main/java/com/zhentao/service/impl/HomeFunctionGridServiceImpl.java
  58. 24 0
      service/homePage/src/main/resources/sql/home_function_grid.sql
  59. 491 0
      代码结构详解.md
  60. 101 0
      修复说明-参数绑定错误.md
  61. 739 0
      项目全面分析报告.md
  62. 169 0
      项目快速参考手册.md

+ 187 - 185
LiangZhiYUMao/pages/index/index.vue

@@ -7,7 +7,7 @@
 				<!-- 红娘工作台切换按钮 - 根据数据库查询结果动态显示 -->
 				<view v-if="showMatchmakerButton"
 					class="matchmaker-btn" @click="openMatchmakerPopup">
-					<text class="switch-icon">🔄</text>
+<!--					<text class="switch-icon">🔄</text>-->
 					<text class="switch-text">切换工作台</text>
 				</view>
 				<!-- 消息通知图标 -->
@@ -262,15 +262,7 @@
 				noticeList: [],
 
 				// 功能入口
-				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: 3, name: '加入红娘', icon: '💕', path: '/pages/part-time-matchmaker/index', bgColor: '#9B7EDE', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #9B7EDE 0%, #B19CD9 100%)' },
-					// { id: 3, name: '加入红娘', icon: '💕', path: '/pages/part-time-matchmaker/index', bgColor: '#9B7EDE', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #9B7EDE 0%, #B19CD9 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 },
-					// { id: 6, name: '专属定制', icon: '🎁', path: '/pages/customize/index', bgColor: '#FFA500', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FFA500 0%, #FFB84D 100%)' }
-				],
+			functionList: [],
 
 				// 热门活动
 				hotActivities: [],
@@ -322,6 +314,7 @@
 			this.loadUserInfo()
 			this.loadBannerData()
 			this.loadNoticeData()
+			this.loadFunctionGridData()
 			// this.loadTodayRecommend()
 			this.loadCharmIndex()
 			this.loadHotActivities()
@@ -499,6 +492,47 @@
 					// 使用默认数据
 				}
 			},
+			
+			// 加载金刚区功能列表
+			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 }
+					]
+				}
+			},
+			
+			// 颜色提亮函数(用于生成渐变背景色)
+			lightenColor(color, percent) {
+				// 移除#号
+				color = color.replace('#', '')
+				// 转换为RGB
+				let r = parseInt(color.substring(0, 2), 16)
+				let g = parseInt(color.substring(2, 4), 16)
+				let b = parseInt(color.substring(4, 6), 16)
+				// 提亮颜色
+				r = Math.min(255, Math.round(r + (255 - r) * percent / 100))
+				g = Math.min(255, Math.round(g + (255 - g) * percent / 100))
+				b = Math.min(255, Math.round(b + (255 - b) * percent / 100))
+				// 转换回十六进制
+				return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')
+			},
 
 			// 加载公告数据
 			async loadNoticeData() {
@@ -622,46 +656,10 @@
 							endTime: activity.endTime || activity.end_time || activity.endDate || '',
 							status: activity.status || 0
 						}})
-					} else {
-						// 使用默认活动数据
-						this.hotActivities = [
-						]
-					}
+					} 
 				} catch (error) {
 					console.error('获取热门活动失败:', error)
-					// 使用默认数据
-					this.hotActivities = [
-						{
-							id: 1,
-							name: '七夕单身派对',
-							description: '浪漫七夕夜,遇见更好的ta',
-							coverImage: DEFAULT_IMAGES.activity || 'https://images.unsplash.com/photo-1511632765486-a01980e01a18?w=750&h=400&fit=crop',
-							startTime: '2024-02-14 19:00:00',
-							location: '杭州西湖文化广场',
-							price: 99,
-							status: 1
-						},
-						{
-							id: 2,
-							name: '情人节主题聚会',
-							description: '高端单身聚会,品味生活',
-							coverImage: DEFAULT_IMAGES.banner || 'https://images.unsplash.com/photo-1522673607200-164d1b6ce486?w=750&h=360&fit=crop',
-							startTime: '2024-02-14 20:00:00',
-							location: '上海外滩茶餐厅',
-							price: 128,
-							status: 1
-						},
-						{
-							id: 3,
-							name: '春日户外踏青',
-							description: '拥抱春天,邂逅美好',
-							coverImage: DEFAULT_IMAGES.couple || 'https://images.unsplash.com/photo-1516589178581-6cd7833ae3b2?w=520&h=360&fit=crop',
-							startTime: '2024-03-20 14:00:00',
-							location: '苏州园林博物馆',
-							price: 68,
-							status: 1
-						}
-					]
+
 				}
 			},
 
@@ -717,25 +715,7 @@
 					}
 				} catch (error) {
 					console.error('获取成功案例失败:', error)
-					// 使用默认数据
-					this.successCases = [
-						{
-							caseNo: 'CASE001',
-							maleUserNickname: '张先生',
-							femaleUserNickname: '李女士',
-							imageUrl: DEFAULT_IMAGES.couple,
-							quote: '从第一次咖啡约会到领证,只用了90天',
-							marriageDate: '2024-08-20'
-						},
-						{
-							caseNo: 'CASE002',
-							maleUserNickname: '王先生',
-							femaleUserNickname: '赵女士',
-							imageUrl: DEFAULT_IMAGES.couple,
-							quote: '感谢红娘的专业服务,让我们相遇相知',
-							marriageDate: '2024-09-15'
-						}
-					]
+					
 				}
 			},
 
@@ -1051,35 +1031,26 @@
 
 		.matchmaker-btn {
 			position: relative;
-			width: auto;
-			height: auto;
 			display: flex;
 			align-items: center;
 			justify-content: center;
-			background: transparent;
-			border-radius: 0;
+			padding: 16rpx 32rpx;
+			background: linear-gradient(135deg, #9B7EDE 0%, #B19CD9 100%);
+			border-radius: 30rpx;
+			box-shadow: 0 8rpx 20rpx rgba(155, 126, 222, 0.3);
 			transition: all 0.3s ease;
-			padding: 10rpx 20rpx;
-			color: #E91E63;
-			font-weight: 600;
-			border: 2rpx solid #E91E63;
-			border-radius: 25rpx;
+			border: none;
 			
 			&:active {
-				opacity: 0.8;
-				transform: scale(0.98);
-				background: rgba(233, 30, 99, 0.1);
-			}
-
-			.switch-icon {
-				font-size: 28rpx;
-				margin-right: 8rpx;
+				transform: scale(0.95);
+				box-shadow: 0 4rpx 12rpx rgba(155, 126, 222, 0.4);
 			}
 			
 			.switch-text {
 				font-size: 26rpx;
-				color: #E91E63;
-				font-weight: 600;
+				color: #FFFFFF;
+				font-weight: 700;
+				letter-spacing: 0.5rpx;
 			}
 		}
 
@@ -1249,105 +1220,136 @@
 
 	/* 功能入口 */
 	.function-grid {
-		display: grid;
-		grid-template-columns: repeat(4, 1fr);
-		gap: 24rpx;
-		margin: 20rpx 1rpx;
-		padding: 30rpx;
-		background: #FFFFFF;
-		border-radius: 16rpx;
-		border: 2rpx solid #E0E0E0;
-
-		.grid-item {
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			gap: 14rpx;
-			padding: 20rpx 12rpx;
-			transition: all 0.3s ease;
-			border-radius: 12rpx;
-
-			&:active {
-				transform: scale(0.95);
-				opacity: 0.9;
-			}
-
-			.grid-icon-wrapper {
-				width: 140rpx;
-				height: 140rpx;
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				border-radius: 20rpx;
-				position: relative;
-				overflow: hidden;
-				/* 外阴影 + 内阴影 - 增强立体感和深度感 */
-				box-shadow: inset 0 2rpx 8rpx rgba(255, 255, 255, 0.3),
-							0 10rpx 30rpx rgba(0, 0, 0, 0.2),
-							0 4rpx 12rpx rgba(0, 0, 0, 0.15);
-				transition: all 0.3s ease;
-
-				/* 光泽效果 - 左上角强烈高光,模拟图片中的光泽 */
-				&::before {
-					content: '';
-					position: absolute;
-					top: -60%;
-					left: -60%;
-					width: 220%;
-					height: 220%;
-					background: linear-gradient(135deg, 
-						rgba(255, 255, 255, 0.6) 0%, 
-						rgba(255, 255, 255, 0.4) 15%, 
-						rgba(255, 255, 255, 0.2) 30%, 
-						rgba(255, 255, 255, 0.05) 50%, 
-						transparent 70%);
-					transform: rotate(45deg);
-					pointer-events: none;
-					z-index: 1;
-					border-radius: 50%;
-				}
-
-				/* 底部深度阴影 - 增强立体感 */
-				&::after {
-					content: '';
-					position: absolute;
-					bottom: 0;
-					left: 0;
-					right: 0;
-					height: 40%;
-					background: linear-gradient(to top, 
-						rgba(0, 0, 0, 0.15) 0%, 
-						rgba(0, 0, 0, 0.08) 50%, 
-						transparent 100%);
-					pointer-events: none;
-					z-index: 1;
-					border-radius: 0 0 20rpx 20rpx;
-				}
-
-				&:active {
-					transform: scale(0.92);
-					box-shadow: inset 0 2rpx 8rpx rgba(255, 255, 255, 0.2),
-								0 6rpx 20rpx rgba(0, 0, 0, 0.25);
-				}
-
-				.grid-icon {
-					font-size: 70rpx;
-					position: relative;
-					z-index: 2;
-					/* 图标阴影 - 让图标更突出 */
-					filter: drop-shadow(0 3rpx 8rpx rgba(0, 0, 0, 0.2));
-					text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
-				}
-			}
-
-			.grid-text {
-				font-size: 24rpx;
-				font-weight: 600;
-				color: #333333;
-				text-align: center;
-				line-height: 1.2;
-			}
-		}
+	  display: flex; /* 核心:改为flex布局 */
+	  flex-wrap: wrap; /* 自动换行 */
+	  gap: 25rpx; /* 保留间距(注意:低版本浏览器可能不兼容flex gap,需用margin替代) */
+	  margin: 20rpx 1rpx;
+	  padding: 30rpx 20rpx;
+	  background: #fefeff;
+	  border-radius: 20rpx;
+	  border: 2rpx solid #f0f0f5;
+	  box-shadow: 0 4rpx 16rpx rgba(220, 220, 240, 0.2);
+	  /* 兼容低版本浏览器(无flex gap时):给子元素加margin,替代gap */
+	  // > .grid-item {
+	  //   margin: 0 15rpx 30rpx 15rpx;
+	  // }
+	  // > .grid-item:nth-child(4n+1) {
+	  //   margin-left: 0;
+	  // }
+	  // > .grid-item:nth-child(n+5) {
+	  //   margin-top: 30rpx;
+	  // }
+	}
+	
+	.grid-item {
+	  display: flex;
+	  flex-direction: column;
+	  align-items: center;
+	  justify-content: center;
+	  gap: 16rpx;
+	  padding: 24rpx 12rpx;
+	  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);
+	  /* 核心:保证4列均分,每列最小宽度限制 */
+	  flex: 1;
+	  min-width: calc(25% - 22.5rpx); /* 25% - 30rpx/2(gap的一半),适配4列 */
+	  max-width: calc(25% - 22.5rpx);
+	  box-sizing: border-box; /* 避免padding撑大宽度 */
+	
+	  &: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);
+	  }
+	}
+	
+	/* 为不同功能定制图标容器底色(贴合功能主题) */
+	.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%);
+	}
+	
+	.grid-icon-wrapper {
+	  width: 150rpx;
+	  height: 150rpx;
+	  display: flex;
+	  align-items: center;
+	  justify-content: center;
+	  border-radius: 24rpx;
+	  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;
+	}
+	
+	.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;
+	}
+	
+	.grid-icon {
+	  font-size: 80rpx;
+	  color: #fff;
+	  position: relative;
+	  z-index: 2;
+	  filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.15));
+	}
+	
+	/* 功能图标装饰 */
+	.grid-item:nth-child(1) .grid-icon::after {
+	  content: '⭐';
+	  position: absolute;
+	  top: 10rpx;
+	  right: 10rpx;
+	  font-size: 30rpx;
+	  transform: rotate(15deg);
+	}
+	.grid-item:nth-child(4) .grid-icon::after {
+	  content: '🎀';
+	  position: absolute;
+	  bottom: 10rpx;
+	  right: 10rpx;
+	  font-size: 30rpx;
+	}
+	
+	.grid-text {
+	  font-size: 26rpx;
+	  font-weight: 600;
+	  color: #2d2d46;
+	  text-align: center;
+	  line-height: 1.3;
+	  text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.05);
+	}
+	
+	/* 响应式适配:小屏自动改为2列 */
+	@media (max-width: 600rpx) {
+	  .grid-item {
+	    min-width: calc(50% - 15rpx);
+	    max-width: calc(50% - 15rpx);
+	  }
 	}
 
 	/* 区块样式 */

+ 647 - 655
LiangZhiYUMao/pages/matchmaker-workbench/index.vue

@@ -1,143 +1,142 @@
 <template>
-	<view class="matchmaker-workbench">
-		<!-- 顶部导航栏 -->
-		<view class="header">
-			<text class="header-title">红娘工作台</text>
-			<view class="header-right">
-				<view class="search-icon" @click="handleSearch"></view>
-				<!-- 退出红娘工作台按钮 -->
-				<view class="exit-workbench-btn" @click="openExitPopup">
-					<view class="arrow-container">
-						<view class="arrow arrow-top"></view>
-						<view class="arrow arrow-bottom"></view>
-					</view>
-				</view>
-			</view>
-		</view>
-
-		<scroll-view scroll-y class="content">
-			<!-- 欢迎卡片 -->
-			<view class="welcome-card">
-				<view class="welcome-text">
-					<text class="welcome-title">欢迎回来</text>
-					<text class="heart-icon">❤️</text>
-					<text class="matchmaker-name">{{ matchmakerInfo.realName || makerName }}</text>
-				</view>
-				<image class="avatar" :src="matchmakerInfo.avatarUrl || '/static/default-avatar.svg'" mode="aspectFill"></image>
-			</view>
-
-			<!-- 统计卡片 -->
-			<view class="stats-cards">
-				<view class="stats-card success">
-					<view class="stats-row">
-						<text class="stats-number">{{ matchmakerInfo.successCouples || 0 }}</text>
-						<text class="stats-tag orange">撮合达人</text>
-					</view>
-					<text class="stats-label">成功撮合</text>
-					<text class="stats-subtitle">匹配数</text>
-				</view>
-				<view class="stats-card points">
-					<view class="stats-row">
-						<text class="stats-number">{{ matchmakerInfo.points || 0 }}</text>
-						<text class="stats-tag purple">积分可兑礼</text>
-					</view>
-					<text class="stats-label">我的积分</text>
-					<text class="stats-subtitle">可兑换</text>
-				</view>
-			</view>
-
-			<!-- 公告卡片 -->
-			<view class="announcement-card" @click="handleAnnouncement" v-if="currentAnnouncement">
-				<text class="announcement-tag">公告</text>
-				<text class="announcement-content">{{ currentAnnouncement.content }}</text>
-				<view class="arrow-right"></view>
-			</view>
-			<view class="announcement-card" v-else>
-				<text class="announcement-tag">公告</text>
-				<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>
-			</view>
-
-			<!-- 本周最佳红娘 -->
-			<view class="best-matchmaker-section">
-				<view class="section-header">
-					<text class="section-title">
-						<text class="crown-icon">👑</text>
-						本周最佳红娘
-					</text>
-					<text class="section-more" @click="navigateToRanking">排行榜 ></text>
-				</view>
-				<view class="best-matchmaker-list">
-					<view class="best-matchmaker-item" v-for="(item, index) in bestMatchmakers" :key="item.matchmaker_id || index" @click="goToMatchmakerDetail(item)">
-						<text class="rank-number" :class="'rank-' + (index + 1)">{{ index + 1 }}</text>
-						<image class="avatar-small" :src="item.avatar_url || '/static/default-avatar.svg'" mode="aspectFill"></image>
-						<view class="matchmaker-info">
-							<text class="matchmaker-name-small">{{ item.real_name || '红娘' }}</text>
-							<text class="success-count">成功人数: {{ item.success_couples || 0 }}</text>
-						</view>
-						<view class="likes-info">
-							<text class="heart-small">❤️</text>
-							<text class="like-count">{{ item.likes || 0 }}</text>
-						</view>
-					</view>
-				</view>
-			</view>
-		</scroll-view>
-
-		<!-- 底部导航 -->
-		<view class="tabbar">
-			<view class="tabbar-item active home" @click="navigateToWorkbench">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">工作台</text>
-			</view>
-			<view class="tabbar-item resources" @click="navigateToMyResources">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">我的资源</text>
-			</view>
-			<view class="tabbar-item trophy" @click="navigateToRanking">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">排行榜</text>
-			</view>
-			<view class="tabbar-item message" @click="navigateToMessage">
-				<view class="tabbar-icon">
-					<view v-if="unreadCount > 0" class="badge">{{ unreadCount }}</view>
-				</view>
-				<text class="tabbar-text">消息</text>
-			</view>
-			<view class="tabbar-item mine" @click="navigateToMine">
-				<view class="tabbar-icon"></view>
-				<text class="tabbar-text">我的</text>
-			</view>
-		</view>
-	</view>
+  <view class="matchmaker-workbench">
+    <!-- 顶部导航栏 -->
+    <view class="header">
+      <text class="header-title">红娘工作台</text>
+      <view class="header-right">
+        <view class="search-icon" @click="handleSearch"></view>
+        <!-- 返回用户端按钮 - 修改为与"前往红娘工作台"一致的样式 -->
+        <view class="back-to-user-btn" @click="openExitPopup">
+          <text class="btn-text">返回用户端</text>
+        </view>
+      </view>
+    </view>
+
+    <scroll-view scroll-y class="content">
+      <!-- 欢迎卡片 -->
+      <view class="welcome-card">
+        <view class="welcome-text">
+          <text class="welcome-title">欢迎回来</text>
+          <text class="heart-icon">❤️</text>
+          <text class="matchmaker-name">{{ matchmakerInfo.realName || makerName }}</text>
+        </view>
+        <image class="avatar" :src="matchmakerInfo.avatarUrl || '/static/default-avatar.svg'" mode="aspectFill"></image>
+      </view>
+
+      <!-- 统计卡片 -->
+      <view class="stats-cards">
+        <view class="stats-card success">
+          <view class="stats-row">
+            <text class="stats-number">{{ matchmakerInfo.successCouples || 0 }}</text>
+            <text class="stats-tag orange">撮合达人</text>
+          </view>
+          <text class="stats-label">成功撮合</text>
+          <text class="stats-subtitle">匹配数</text>
+        </view>
+        <view class="stats-card points">
+          <view class="stats-row">
+            <text class="stats-number">{{ matchmakerInfo.points || 0 }}</text>
+            <text class="stats-tag purple">积分可兑礼</text>
+          </view>
+          <text class="stats-label">我的积分</text>
+          <text class="stats-subtitle">可兑换</text>
+        </view>
+      </view>
+
+      <!-- 公告卡片 -->
+      <view class="announcement-card" @click="handleAnnouncement" v-if="currentAnnouncement">
+        <text class="announcement-tag">公告</text>
+        <text class="announcement-content">{{ currentAnnouncement.content }}</text>
+        <view class="arrow-right"></view>
+      </view>
+      <view class="announcement-card" v-else>
+        <text class="announcement-tag">公告</text>
+        <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>
+      </view>
+
+      <!-- 本周最佳红娘 -->
+      <view class="best-matchmaker-section">
+        <view class="section-header">
+          <text class="section-title">
+            <text class="crown-icon">👑</text>
+            本周最佳红娘
+          </text>
+          <text class="section-more" @click="navigateToRanking">排行榜 ></text>
+        </view>
+        <view class="best-matchmaker-list">
+          <view class="best-matchmaker-item" v-for="(item, index) in bestMatchmakers" :key="item.matchmaker_id || index"
+                @click="goToMatchmakerDetail(item)">
+            <text class="rank-number" :class="'rank-' + (index + 1)">{{ index + 1 }}</text>
+            <image class="avatar-small" :src="item.avatar_url || '/static/default-avatar.svg'"
+                   mode="aspectFill"></image>
+            <view class="matchmaker-info">
+              <text class="matchmaker-name-small">{{ item.real_name || '红娘' }}</text>
+              <text class="success-count">成功人数: {{ item.success_couples || 0 }}</text>
+            </view>
+            <view class="likes-info">
+              <text class="heart-small">❤️</text>
+              <text class="like-count">{{ item.likes || 0 }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 底部导航 -->
+    <view class="tabbar">
+      <view class="tabbar-item active home" @click="navigateToWorkbench">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">工作台</text>
+      </view>
+      <view class="tabbar-item resources" @click="navigateToMyResources">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">我的资源</text>
+      </view>
+      <view class="tabbar-item trophy" @click="navigateToRanking">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">排行榜</text>
+      </view>
+      <view class="tabbar-item message" @click="navigateToMessage">
+        <view class="tabbar-icon">
+          <view v-if="unreadCount > 0" class="badge">{{ unreadCount }}</view>
+        </view>
+        <text class="tabbar-text">消息</text>
+      </view>
+      <view class="tabbar-item mine" @click="navigateToMine">
+        <view class="tabbar-icon"></view>
+        <text class="tabbar-text">我的</text>
+      </view>
+    </view>
+  </view>
 </template>
 
 <script>
-	import api from '@/utils/api.js'
-	
+import api from '@/utils/api.js'
+
 export default {
 		data() {
 			return {
@@ -343,523 +342,516 @@ export default {
 		flex-direction: column;
 	}
 
-	/* 顶部导航栏 */
-	.header {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		padding: 25rpx 30rpx;
-		padding-top: calc(25rpx + env(safe-area-inset-top));
-		background: #FFF9F9;
-		border-bottom: 1rpx solid #F0F0F0;
-
-		.header-title {
-			font-size: 38rpx;
-			font-weight: bold;
-			color: #9C27B0;
-		}
-
-		.header-right {
-			display: flex;
-			align-items: center;
-			gap: 20rpx;
-
-			.search-icon,
-			.settings-icon {
-				width: 44rpx;
-				height: 44rpx;
-				background-size: contain;
-				background-repeat: no-repeat;
-				background-position: center;
-			}
-
-			.search-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>');
-			}
-
-			.settings-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>');
-			}
-
-			/* 切换工作台按钮 */
-			.exit-workbench-btn {
-				width: 60rpx;
-				height: 44rpx;
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				background: linear-gradient(135deg, #9C27B0 0%, #E91E63 100%);
-				border-radius: 22rpx;
-				padding: 8rpx 12rpx;
-
-				.arrow-container {
-					display: flex;
-					flex-direction: column;
-					align-items: center;
-					gap: 4rpx;
-
-					.arrow {
-						width: 0;
-						height: 0;
-						border-left: 8rpx solid transparent;
-						border-right: 8rpx solid transparent;
-
-						&.arrow-top {
-							border-bottom: 8rpx solid #FFFFFF;
-						}
-
-						&.arrow-bottom {
-							border-top: 8rpx solid #FFFFFF;
-						}
-					}
-				}
-			}
-		}
-	}
-
-	.content {
-		flex: 1;
-		padding: 20rpx 20rpx 120rpx;
-		display: flex;
-		flex-direction: column;
-	}
-
-	/* 欢迎卡片 + 统计卡片合并 */
-	.welcome-card {
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-		padding: 30rpx;
-		background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD0 100%);
-		border-radius: 20rpx;
-		margin-bottom: 20rpx;
-
-		.welcome-text {
-			display: flex;
-			align-items: center;
-			gap: 10rpx;
-
-			.welcome-title {
-				font-size: 38rpx;
-				color: #333;
-			}
-
-			.heart-icon {
-				font-size: 32rpx;
-			}
-
-			.matchmaker-name {
-				font-size: 40rpx;
-				font-weight: bold;
-				color: #333;
-			}
-		}
-
-		.avatar {
-			width: 100rpx;
-			height: 100rpx;
-			border-radius: 50%;
-			background: rgba(255, 255, 255, 0.3);
-		}
-	}
-
-	/* 统计卡片 */
-	.stats-cards {
-		display: flex;
-		gap: 20rpx;
-		margin-bottom: 20rpx;
-
-		.stats-card {
-			flex: 1;
-			padding: 25rpx;
-			border-radius: 15rpx;
-			background: #FFFFFF;
-			box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-
-			.stats-row {
-				display: flex;
-				align-items: center;
-				gap: 10rpx;
-				margin-bottom: 8rpx;
-			}
-
-			.stats-number {
-				font-size: 48rpx;
-				font-weight: bold;
-			}
-
-			.stats-tag {
-				font-size: 20rpx;
-				padding: 4rpx 12rpx;
-				border-radius: 6rpx;
-				font-weight: 500;
-
-				&.orange {
-					color: #FF6B8A;
-					border: 1rpx solid #FF6B8A;
-					background: rgba(255, 107, 138, 0.1);
-				}
-
-				&.purple {
-					color: #9C27B0;
-					border: 1rpx solid #9C27B0;
-					background: rgba(156, 39, 176, 0.1);
-				}
-			}
-
-			.stats-label {
-				display: block;
-				font-size: 28rpx;
-				margin-bottom: 8rpx;
-			}
-
-			.stats-subtitle {
-				display: block;
-				font-size: 24rpx;
-				color: #999;
-			}
-
-			.stats-extra {
-				display: block;
-				font-size: 22rpx;
-				color: #999;
-				margin-top: 8rpx;
-			}
-
-			&.success {
-				.stats-number {
-					color: #FF6B8A;
-				}
-				.stats-label {
-					color: #333;
-				}
-			}
-
-			&.points {
-				.stats-number {
-					color: #9C27B0;
-				}
-				.stats-label {
-					color: #333;
-				}
-			}
-		}
-	}
-
-	/* 公告卡片 */
-	.announcement-card {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		padding: 35rpx 30rpx;
-		min-height: 80rpx;
-		background: #FFFFFF;
-		border-radius: 20rpx;
-		margin-bottom: 20rpx;
-		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-		position: relative;
-
-		.announcement-tag {
-			display: inline-block;
-			background: #FF9800;
-			color: #FFFFFF;
-			font-size: 22rpx;
-			padding: 6rpx 16rpx;
-			border-radius: 12rpx;
-			margin-right: 15rpx;
-			font-weight: bold;
-		}
-
-		.announcement-content {
-			flex: 1;
-			font-size: 26rpx;
-			color: #333;
-			line-height: 1.4;
-		}
-
-		.arrow-right {
-			width: 24rpx;
-			height: 24rpx;
-			background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>');
-			background-size: contain;
-			background-repeat: no-repeat;
-			background-position: center;
-			margin-left: 15rpx;
-		}
-	}
-
-	/* 功能菜单 */
-	.menu-grid {
-		display: grid;
-		grid-template-columns: repeat(5, 1fr);
-		gap: 25rpx;
-		padding: 35rpx 20rpx;
-		background: #FFFFFF;
-		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;
-
-			.menu-icon {
-				width: 90rpx;
-				height: 90rpx;
-				border-radius: 50%;
-				background-size: 50rpx 50rpx;
-				background-repeat: no-repeat;
-				background-position: center;
-
-				&.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>');
-				}
-
-				&.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>');
-				}
-
-				&.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>');
-				}
-
-				&.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>');
-				}
-
-				&.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>');
-				}
-			}
-
-			.menu-text {
-				font-size: 26rpx;
-				color: #333;
-			}
-		}
-	}
-
-	/* 本周最佳红娘 */
-	.best-matchmaker-section {
-		flex: 1;
-		min-height: 400rpx;
-		padding: 30rpx;
-		background: #FFFFFF;
-		border-radius: 20rpx;
-		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-		display: flex;
-		flex-direction: column;
-		margin-bottom: 20rpx;
-
-		.section-header {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			margin-bottom: 20rpx;
-
-			.section-title {
-				display: flex;
-				align-items: center;
-				font-size: 30rpx;
-				font-weight: bold;
-				color: red;
-
-				.crown-icon {
-					font-size: 32rpx;
-					margin-right: 10rpx;
-				}
-			}
-
-			.section-more {
-				font-size: 24rpx;
-				color: #9C27B0;
-			}
-		}
-
-		.best-matchmaker-list {
-			flex: 1;
-			display: flex;
-			flex-direction: column;
-			gap: 20rpx;
-
-			.best-matchmaker-item {
-				display: flex;
-				align-items: center;
-				gap: 20rpx;
-				padding: 30rpx 25rpx;
-				background: #FAFAFA;
-				border-radius: 15rpx;
-				min-height: 100rpx;
-
-				.rank-number {
-					width: 45rpx;
-					height: 45rpx;
-					display: flex;
-					align-items: center;
-					justify-content: center;
-					font-size: 28rpx;
-					font-weight: bold;
-					color: #FFF;
-					border-radius: 50%;
-					background: #FFD700;
-
-					&.rank-1 {
-						background: #FFD700;
-					}
-
-					&.rank-2 {
-						background: #C0C0C0;
-					}
-
-					&.rank-3 {
-						background: #CD7F32;
-					}
-				}
-
-				.avatar-small {
-					width: 80rpx;
-					height: 80rpx;
-					border-radius: 50%;
-					background: #F0F0F0;
-				}
-
-				.matchmaker-info {
-					flex: 1;
-
-					.matchmaker-name-small {
-						display: block;
-						font-size: 32rpx;
-						font-weight: bold;
-						color: #333;
-						margin-bottom: 8rpx;
-					}
-
-					.success-count {
-						display: block;
-						font-size: 26rpx;
-						color: #999;
-					}
-				}
-
-				.likes-info {
-					display: flex;
-					align-items: center;
-					gap: 10rpx;
-
-					.heart-small {
-						font-size: 28rpx;
-					}
-
-					.like-count {
-						font-size: 28rpx;
-						color: #E91E63;
-						font-weight: bold;
-					}
-				}
-			}
-		}
-	}
-
-	/* 底部导航 */
-	.tabbar {
-		position: fixed;
-		bottom: 0;
-		left: 0;
-		right: 0;
-		height: 100rpx;
-		background: #FFFFFF;
-		border-top: 1rpx solid #F0F0F0;
-		display: flex;
-		justify-content: space-around;
-		align-items: center;
-		padding-bottom: env(safe-area-inset-bottom);
-
-		.tabbar-item {
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			gap: 8rpx;
-			padding: 10rpx 0;
-
-			.tabbar-icon {
-				width: 44rpx;
-				height: 44rpx;
-				background-size: contain;
-				background-repeat: no-repeat;
-				background-position: center;
-				position: relative;
-
-				.badge {
-					position: absolute;
-					top: -8rpx;
-					right: -8rpx;
-					background: #FF4444;
-					color: #FFFFFF;
-					font-size: 20rpx;
-					font-weight: bold;
-					width: 32rpx;
-					height: 32rpx;
-					display: flex;
-					align-items: center;
-					justify-content: center;
-					border-radius: 16rpx;
-				}
-			}
-
-			.tabbar-text {
-				font-size: 20rpx;
-				color: #999;
-			}
-
-			&.active {
-				.tabbar-text {
-					color: #9C27B0;
-					font-weight: bold;
-				}
-			}
-
-			&.home .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
-			}
-
-			&.resources .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
-			}
-
-			&.active.resources .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
-			}
-
-			&.trophy .tabbar-icon {
-				background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
-			}
-
-			&.active.trophy .tabbar-icon {
-				background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
-			}
-
-			&.message .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
-			}
-
-			&.active.message .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
-			}
-
-			&.mine .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
-			}
-
-			&.active.mine .tabbar-icon {
-				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
-			}
-		}
-	}
+/* 顶部导航栏 */
+.header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 25rpx 30rpx;
+  padding-top: calc(25rpx + env(safe-area-inset-top));
+  background: #FFF9F9;
+  border-bottom: 1rpx solid #F0F0F0;
+
+  .header-title {
+    font-size: 38rpx;
+    font-weight: bold;
+    color: #9C27B0;
+  }
+
+  .header-right {
+    display: flex;
+    align-items: center;
+    gap: 20rpx;
+
+    .search-icon,
+    .settings-icon {
+      width: 44rpx;
+      height: 44rpx;
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+    }
+
+    .search-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>');
+    }
+
+    .settings-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>');
+    }
+
+    /* 返回用户端按钮 - 与"前往红娘工作台"按钮样式一致 */
+    .back-to-user-btn {
+      height: 60rpx;
+      padding: 0 24rpx;
+      background: linear-gradient(135deg, #FF8A9B 0%, #FF6B8A 100%);
+      border-radius: 30rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      box-shadow: 0 4rpx 12rpx rgba(255, 107, 138, 0.3);
+      transition: all 0.3s ease;
+
+      &:active {
+        transform: scale(0.95);
+        box-shadow: 0 2rpx 8rpx rgba(255, 107, 138, 0.3);
+      }
+
+      .btn-text {
+        font-size: 26rpx;
+        color: #FFFFFF;
+        font-weight: bold;
+        white-space: nowrap;
+      }
+    }
+  }
+}
+
+.content {
+  flex: 1;
+  padding: 20rpx 20rpx 120rpx;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 欢迎卡片 + 统计卡片合并 */
+.welcome-card {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 30rpx;
+  background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD0 100%);
+  border-radius: 20rpx;
+  margin-bottom: 20rpx;
+
+  .welcome-text {
+    display: flex;
+    align-items: center;
+    gap: 10rpx;
+
+    .welcome-title {
+      font-size: 38rpx;
+      color: #333;
+    }
+
+    .heart-icon {
+      font-size: 32rpx;
+    }
+
+    .matchmaker-name {
+      font-size: 40rpx;
+      font-weight: bold;
+      color: #333;
+    }
+  }
+
+  .avatar {
+    width: 100rpx;
+    height: 100rpx;
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.3);
+  }
+}
+
+/* 统计卡片 */
+.stats-cards {
+  display: flex;
+  gap: 20rpx;
+  margin-bottom: 20rpx;
+
+  .stats-card {
+    flex: 1;
+    padding: 25rpx;
+    border-radius: 15rpx;
+    background: #FFFFFF;
+    box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+    .stats-row {
+      display: flex;
+      align-items: center;
+      gap: 10rpx;
+      margin-bottom: 8rpx;
+    }
+
+    .stats-number {
+      font-size: 48rpx;
+      font-weight: bold;
+    }
+
+    .stats-tag {
+      font-size: 20rpx;
+      padding: 4rpx 12rpx;
+      border-radius: 6rpx;
+      font-weight: 500;
+
+      &.orange {
+        color: #FF6B8A;
+        border: 1rpx solid #FF6B8A;
+        background: rgba(255, 107, 138, 0.1);
+      }
+
+      &.purple {
+        color: #9C27B0;
+        border: 1rpx solid #9C27B0;
+        background: rgba(156, 39, 176, 0.1);
+      }
+    }
+
+    .stats-label {
+      display: block;
+      font-size: 28rpx;
+      margin-bottom: 8rpx;
+    }
+
+    .stats-subtitle {
+      display: block;
+      font-size: 24rpx;
+      color: #999;
+    }
+
+    .stats-extra {
+      display: block;
+      font-size: 22rpx;
+      color: #999;
+      margin-top: 8rpx;
+    }
+
+    &.success {
+      .stats-number {
+        color: #FF6B8A;
+      }
+
+      .stats-label {
+        color: #333;
+      }
+    }
+
+    &.points {
+      .stats-number {
+        color: #9C27B0;
+      }
+
+      .stats-label {
+        color: #333;
+      }
+    }
+  }
+}
+
+/* 公告卡片 */
+.announcement-card {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 35rpx 30rpx;
+  min-height: 80rpx;
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+  position: relative;
+
+  .announcement-tag {
+    display: inline-block;
+    background: #FF9800;
+    color: #FFFFFF;
+    font-size: 22rpx;
+    padding: 6rpx 16rpx;
+    border-radius: 12rpx;
+    margin-right: 15rpx;
+    font-weight: bold;
+  }
+
+  .announcement-content {
+    flex: 1;
+    font-size: 26rpx;
+    color: #333;
+    line-height: 1.4;
+  }
+
+  .arrow-right {
+    width: 24rpx;
+    height: 24rpx;
+    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>');
+    background-size: contain;
+    background-repeat: no-repeat;
+    background-position: center;
+    margin-left: 15rpx;
+  }
+}
+
+/* 功能菜单 */
+.menu-grid {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 25rpx;
+  padding: 35rpx 20rpx;
+  background: #FFFFFF;
+  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;
+
+    .menu-icon {
+      width: 90rpx;
+      height: 90rpx;
+      border-radius: 50%;
+      background-size: 50rpx 50rpx;
+      background-repeat: no-repeat;
+      background-position: center;
+
+      &.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>');
+      }
+
+      &.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>');
+      }
+
+      &.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>');
+      }
+
+      &.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>');
+      }
+
+      &.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>');
+      }
+    }
+
+    .menu-text {
+      font-size: 26rpx;
+      color: #333;
+    }
+  }
+}
+
+/* 本周最佳红娘 */
+.best-matchmaker-section {
+  flex: 1;
+  min-height: 400rpx;
+  padding: 30rpx;
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 20rpx;
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20rpx;
+
+    .section-title {
+      display: flex;
+      align-items: center;
+      font-size: 30rpx;
+      font-weight: bold;
+      color: red;
+
+      .crown-icon {
+        font-size: 32rpx;
+        margin-right: 10rpx;
+      }
+    }
+
+    .section-more {
+      font-size: 24rpx;
+      color: #9C27B0;
+    }
+  }
+
+  .best-matchmaker-list {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: 20rpx;
+
+    .best-matchmaker-item {
+      display: flex;
+      align-items: center;
+      gap: 20rpx;
+      padding: 30rpx 25rpx;
+      background: #FAFAFA;
+      border-radius: 15rpx;
+      min-height: 100rpx;
+
+      .rank-number {
+        width: 45rpx;
+        height: 45rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 28rpx;
+        font-weight: bold;
+        color: #FFF;
+        border-radius: 50%;
+        background: #FFD700;
+
+        &.rank-1 {
+          background: #FFD700;
+        }
+
+        &.rank-2 {
+          background: #C0C0C0;
+        }
+
+        &.rank-3 {
+          background: #CD7F32;
+        }
+      }
+
+      .avatar-small {
+        width: 80rpx;
+        height: 80rpx;
+        border-radius: 50%;
+        background: #F0F0F0;
+      }
+
+      .matchmaker-info {
+        flex: 1;
+
+        .matchmaker-name-small {
+          display: block;
+          font-size: 32rpx;
+          font-weight: bold;
+          color: #333;
+          margin-bottom: 8rpx;
+        }
+
+        .success-count {
+          display: block;
+          font-size: 26rpx;
+          color: #999;
+        }
+      }
+
+      .likes-info {
+        display: flex;
+        align-items: center;
+        gap: 10rpx;
+
+        .heart-small {
+          font-size: 28rpx;
+        }
+
+        .like-count {
+          font-size: 28rpx;
+          color: #E91E63;
+          font-weight: bold;
+        }
+      }
+    }
+  }
+}
+
+/* 底部导航 */
+.tabbar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 100rpx;
+  background: #FFFFFF;
+  border-top: 1rpx solid #F0F0F0;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  padding-bottom: env(safe-area-inset-bottom);
+
+  .tabbar-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 8rpx;
+    padding: 10rpx 0;
+
+    .tabbar-icon {
+      width: 44rpx;
+      height: 44rpx;
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+      position: relative;
+
+      .badge {
+        position: absolute;
+        top: -8rpx;
+        right: -8rpx;
+        background: #FF4444;
+        color: #FFFFFF;
+        font-size: 20rpx;
+        font-weight: bold;
+        width: 32rpx;
+        height: 32rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-radius: 16rpx;
+      }
+    }
+
+    .tabbar-text {
+      font-size: 20rpx;
+      color: #999;
+    }
+
+    &.active {
+      .tabbar-text {
+        color: #9C27B0;
+        font-weight: bold;
+      }
+    }
+
+    &.home .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
+    }
+
+    &.resources .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
+    }
+
+    &.active.resources .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
+    }
+
+    &.trophy .tabbar-icon {
+      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
+    }
+
+    &.active.trophy .tabbar-icon {
+      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
+    }
+
+    &.message .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
+    }
+
+    &.active.message .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
+    }
+
+    &.mine .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
+    }
+
+    &.active.mine .tabbar-icon {
+      background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
+    }
+  }
+}
 </style>

+ 6 - 44
LiangZhiYUMao/pages/message/chat.vue

@@ -487,23 +487,6 @@ export default {
   },
 
   methods: {
-    /**
-     * 根据语音时长计算语音气泡宽度
-     */
-    getVoiceWidth(duration) {
-      // 基础宽度 + 时长比例计算的宽度
-      // 例如:60px 基础宽度 + 每秒钟 2px 的宽度增量
-      const baseWidth = 60;
-      const perSecondWidth = 2;
-      const maxWidth = 180;
-      
-      // 确保时长是数字类型
-      const durationNum = parseInt(duration) || 0;
-      
-      // 计算宽度并限制最大宽度
-      const width = Math.min(baseWidth + durationNum * perSecondWidth, maxWidth);
-      return width + 'px';
-    },
     /**
      * 红娘入口:根据当前登录用户ID获取红娘资料并设置头像
      */
@@ -1326,11 +1309,11 @@ export default {
       try {
         console.log('🔄 同步消息到MySQL...', timMessage.ID);
         
-        // 构建同步参数(V2版本,支持红娘消息)
+        // 构建同步参数
         const syncData = {
           messageId: timMessage.ID,
-          fromTimUserId: String(timMessage.from),  // 保持字符串格式,支持 m_xxx
-          toTimUserId: String(timMessage.to),      // 保持字符串格式,支持 m_xxx
+          fromUserId: timMessage.from,
+          toUserId: timMessage.to,
           messageType: this.getMessageType(timMessage),
           content: this.getMessageContent(timMessage),
           sendTime: timMessage.time  // TIM返回的是秒级时间戳
@@ -1364,9 +1347,9 @@ export default {
           }
         }
         
-        // 调用后端同步接口(V2版本,支持红娘消息)
+        // 调用后端同步接口
         const res = await uni.request({
-          url: 'http://localhost:8083/api/chat/syncTIMMessageV2',
+          url: 'http://localhost:8083/api/chat/syncTIMMessage',
           method: 'POST',
           data: syncData,
           header: {
@@ -1375,8 +1358,7 @@ export default {
         });
         
         if (res[1].data.code === 200) {
-          const msgType = res[1].data.type === 'matchmaker' ? '红娘消息' : '用户消息';
-          console.log(`✅ 消息已同步到MySQL (${msgType}):`, timMessage.ID);
+          console.log('✅ 消息已同步到MySQL:', timMessage.ID);
         } else {
           console.warn('⚠️ 消息同步失败:', res[1].data.message);
         }
@@ -1444,26 +1426,6 @@ export default {
       }
     },
     
-    /**
-     * 根据语音时长计算语音消息气泡宽度
-     * @param {number} duration 语音时长(秒)
-     * @returns {string} 宽度样式值
-     */
-    getVoiceWidth(duration) {
-      // 基础宽度 120rpx,每秒增加 10rpx,最大 300rpx
-      const baseWidth = 120;
-      const perSecondWidth = 10;
-      const maxWidth = 300;
-      const minWidth = 120;
-      
-      const seconds = parseInt(duration) || 0;
-      let width = baseWidth + seconds * perSecondWidth;
-      
-      // 限制在最小和最大范围内
-      width = Math.max(minWidth, Math.min(maxWidth, width));
-      
-      return width + 'rpx';
-    },
     
     /**
      * 滚动到底部

+ 3 - 0
LiangZhiYUMao/utils/api.js

@@ -190,6 +190,9 @@ export default {
     // 获取公告列表
     getNotices: () => request({ url: '/announcement/active' }),
     
+    // 获取首页金刚区功能列表
+    getFunctionGrid: () => request({ url: '/home/function-grid' }),
+    
     // 获取未读消息数
     getUnreadCount: () => request({ url: '/home/unread-count' })
   },

+ 0 - 0
service/homePage/src/main/java/com/zhentao/constant/RedisKeyConstants.java → common/src/main/java/com/zhentao/constant/RedisKeyConstants.java


+ 1 - 1
common/src/main/java/com/zhentao/pojo/MatchmakerApply.java

@@ -49,5 +49,5 @@ public class MatchmakerApply {
     
     private String updateMan;
     
-    private Integer status; // 0-正常, 1-禁止, 默认为0
+    private Integer status; // 0-同意, 1-不同意, 2-待审核, 默认为2
 }

+ 3 - 2
common/src/main/java/com/zhentao/service/impl/MatchmakerApplyServiceImpl.java

@@ -25,11 +25,12 @@ public class MatchmakerApplyServiceImpl extends ServiceImpl<MatchmakerApplyMappe
         // 更新人创建人
         matchmakerApply.setCreateMan("系统管理员");
         matchmakerApply.setUpdateMan("系统管理员");
-
+        
+        // 设置默认状态为待审核(2)
         if (matchmakerApply.getStatus() == null) {
             matchmakerApply.setStatus(2);
         }
-
+        
         // 保存申请信息
         return this.save(matchmakerApply);
     }

binární
marriageAdmin-vue/src/assets/qingluan.png


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

@@ -49,8 +49,10 @@ export const API_ENDPOINTS = {
   MATCHMAKER_STATS: '/api/matchmaker/stats',
   MATCHMAKER_AUDIT_LIST: '/admin/marr-apply/list',
   MATCHMAKER_AUDIT_APPROVE: '/admin/marr-apply/approve',
+  MATCHMAKER_AUDIT_BATCH_APPROVE: '/admin/marr-apply/batch-approve',
   MATCHMAKER_AUDIT_DELETE: '/admin/marr-apply/delete',
   SUCCESS_CASE_UPLOAD_LIST: '/admin/success-case-upload/list',
+  SUCCESS_CASE_UPLOAD_DETAIL: '/admin/success-case-upload',
   SUCCESS_CASE_UPLOAD_APPROVE: '/admin/success-case-upload/approve',
   SUCCESS_CASE_UPLOAD_REJECT: '/admin/success-case-upload/reject',
   

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

@@ -3,9 +3,9 @@
     <!-- 侧边栏 -->
     <el-aside :width="isCollapse ? '64px' : '240px'" class="layout-aside">
       <div class="logo-container">
-        <img src="@/assets/logo.svg" alt="Logo" class="logo" />
+        <img src="@/assets/qingluan.png" alt="Logo" class="logo" />
         <transition name="fade">
-          <span v-if="!isCollapse" class="logo-text">婚恋管理系统</span>
+          <span v-if="!isCollapse" class="logo-text">青鸾之恋</span>
         </transition>
       </div>
       

+ 168 - 57
marriageAdmin-vue/src/views/Login.vue

@@ -2,7 +2,9 @@
   <div class="login-container">
     <div class="login-box">
       <div class="login-header">
-        <img src="@/assets/logo.svg" alt="Logo" class="login-logo" />
+        <div class="logo-wrapper">
+          <img src="@/assets/qingluan.png" alt="Logo" class="login-logo" />
+        </div>
         <h2 class="login-title">婚恋管理系统</h2>
         <p class="login-subtitle">Marriage Management System</p>
       </div>
@@ -48,10 +50,6 @@
           </el-button>
         </el-form-item>
       </el-form>
-      
-      <div class="login-footer">
-        <p>测试账号:admin / admin123</p>
-      </div>
     </div>
   </div>
 </template>
@@ -114,12 +112,26 @@ const handleLogin = async () => {
   justify-content: center;
   align-items: center;
   min-height: 100vh;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #a8c0ff 0%, #3f2b96 50%, #a8c0ff 100%);
+  background-size: 200% 200%;
+  animation: gradientShift 15s ease infinite;
   padding: var(--spacing-xl);
   position: relative;
   overflow: hidden;
 }
 
+@keyframes gradientShift {
+  0% {
+    background-position: 0% 50%;
+  }
+  50% {
+    background-position: 100% 50%;
+  }
+  100% {
+    background-position: 0% 50%;
+  }
+}
+
 /* 背景动画装饰 */
 .login-container::before {
   content: '';
@@ -146,15 +158,38 @@ const handleLogin = async () => {
 /* 登录框 */
 .login-box {
   width: 100%;
-  max-width: 440px;
-  background: rgba(255, 255, 255, 0.98);
-  border-radius: var(--radius-xl);
-  box-shadow: var(--shadow-2xl);
-  padding: var(--spacing-3xl);
-  backdrop-filter: blur(10px);
+  max-width: 480px;
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 24px;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15),
+              0 0 0 1px rgba(255, 255, 255, 0.5) inset;
+  padding: 48px 40px;
+  backdrop-filter: blur(20px);
   animation: slideInUp 0.6s ease-out;
   position: relative;
   z-index: 1;
+  overflow: hidden;
+}
+
+.login-box::after {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 4px;
+  background: linear-gradient(90deg, #a8c0ff, #3f2b96, #a8c0ff);
+  background-size: 200% 100%;
+  animation: shimmer 3s linear infinite;
+}
+
+@keyframes shimmer {
+  0% {
+    background-position: -200% 0;
+  }
+  100% {
+    background-position: 200% 0;
+  }
 }
 
 @keyframes slideInUp {
@@ -168,6 +203,28 @@ const handleLogin = async () => {
   }
 }
 
+@keyframes fadeInDown {
+  from {
+    opacity: 0;
+    transform: translateY(-20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes fadeInUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
 /* 登录头部 */
 .login-header {
   text-align: center;
@@ -175,12 +232,61 @@ const handleLogin = async () => {
   animation: fadeInDown 0.8s ease-out;
 }
 
+.logo-wrapper {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 120px;
+  height: 120px;
+  margin: 0 auto var(--spacing-lg);
+  background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.05) 100%);
+  border-radius: 50%;
+  padding: 20px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1),
+              inset 0 2px 8px rgba(255, 255, 255, 0.3);
+  backdrop-filter: blur(10px);
+  border: 2px solid rgba(255, 255, 255, 0.2);
+  position: relative;
+  overflow: hidden;
+}
+
+.logo-wrapper::before {
+  content: '';
+  position: absolute;
+  top: -50%;
+  left: -50%;
+  width: 200%;
+  height: 200%;
+  background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%);
+  animation: rotate 8s linear infinite;
+}
+
+@keyframes rotate {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
 .login-logo {
-  width: 72px;
-  height: 72px;
-  margin-bottom: var(--spacing-lg);
-  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
-  animation: pulse 2s ease-in-out infinite;
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+  filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15));
+  animation: float 3s ease-in-out infinite;
+  position: relative;
+  z-index: 1;
+}
+
+@keyframes float {
+  0%, 100% {
+    transform: translateY(0px) scale(1);
+  }
+  50% {
+    transform: translateY(-8px) scale(1.02);
+  }
 }
 
 @keyframes pulse {
@@ -193,14 +299,26 @@ const handleLogin = async () => {
 }
 
 .login-title {
-  font-size: var(--font-3xl);
-  font-weight: var(--font-bold);
+  font-size: 32px;
+  font-weight: 700;
   color: var(--text-primary);
   margin: 0 0 var(--spacing-sm) 0;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #3f2b96 0%, #a8c0ff 50%, #3f2b96 100%);
+  background-size: 200% 100%;
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;
   background-clip: text;
+  animation: textShimmer 3s ease-in-out infinite;
+  letter-spacing: 1px;
+}
+
+@keyframes textShimmer {
+  0%, 100% {
+    background-position: 0% 50%;
+  }
+  50% {
+    background-position: 100% 50%;
+  }
 }
 
 .login-subtitle {
@@ -224,16 +342,23 @@ const handleLogin = async () => {
 
 .login-form :deep(.el-input__wrapper) {
   padding: var(--spacing-base) var(--spacing-lg);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
-  transition: all var(--transition-base);
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  border: 1px solid rgba(168, 192, 255, 0.3);
+  border-radius: 12px;
+  background: rgba(255, 255, 255, 0.9);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 }
 
 .login-form :deep(.el-input__wrapper:hover) {
-  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
+  box-shadow: 0 4px 20px rgba(63, 43, 150, 0.2);
+  border-color: rgba(168, 192, 255, 0.6);
+  transform: translateY(-2px);
 }
 
 .login-form :deep(.el-input__wrapper.is-focus) {
-  box-shadow: 0 4px 16px rgba(102, 126, 234, 0.25);
+  box-shadow: 0 6px 24px rgba(63, 43, 150, 0.3);
+  border-color: rgba(168, 192, 255, 0.8);
+  background: rgba(255, 255, 255, 1);
 }
 
 .login-form :deep(.el-input__inner) {
@@ -249,16 +374,18 @@ const handleLogin = async () => {
 /* 登录按钮 */
 .login-button {
   width: 100%;
-  height: 48px;
-  font-size: var(--font-md);
-  font-weight: var(--font-semibold);
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  height: 52px;
+  font-size: 16px;
+  font-weight: 600;
+  background: linear-gradient(135deg, #3f2b96 0%, #a8c0ff 100%);
   border: none;
-  border-radius: var(--radius-base);
-  box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
-  transition: all var(--transition-base);
+  border-radius: 12px;
+  box-shadow: 0 6px 20px rgba(63, 43, 150, 0.4),
+              0 0 0 0 rgba(168, 192, 255, 0.4);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
   position: relative;
   overflow: hidden;
+  letter-spacing: 1px;
 }
 
 .login-button::before {
@@ -277,32 +404,15 @@ const handleLogin = async () => {
 }
 
 .login-button:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
+  transform: translateY(-3px);
+  box-shadow: 0 8px 28px rgba(63, 43, 150, 0.5),
+              0 0 0 4px rgba(168, 192, 255, 0.2);
+  background: linear-gradient(135deg, #4a3ba8 0%, #b8d0ff 100%);
 }
 
 .login-button:active {
-  transform: translateY(0);
-  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
-}
-
-/* 登录页脚 */
-.login-footer {
-  text-align: center;
-  margin-top: var(--spacing-xl);
-  padding-top: var(--spacing-lg);
-  border-top: 1px solid var(--border-light);
-  animation: fadeIn 1.2s ease-out;
-}
-
-.login-footer p {
-  font-size: var(--font-xs);
-  color: var(--text-tertiary);
-  margin: 0;
-  padding: var(--spacing-sm);
-  background-color: var(--bg-secondary);
-  border-radius: var(--radius-base);
-  display: inline-block;
+  transform: translateY(-1px);
+  box-shadow: 0 4px 16px rgba(63, 43, 150, 0.4);
 }
 
 /* ==================== 响应式设计 ==================== */
@@ -318,9 +428,10 @@ const handleLogin = async () => {
     padding: var(--spacing-2xl);
   }
 
-  .login-logo {
-    width: 60px;
-    height: 60px;
+  .logo-wrapper {
+    width: 100px;
+    height: 100px;
+    padding: 15px;
   }
 
   .login-title {

+ 33 - 22
marriageAdmin-vue/src/views/course/CourseForm.vue

@@ -66,10 +66,8 @@
           <div class="cover-upload-wrapper">
             <el-upload
               class="cover-uploader"
-              :action="uploadUrl"
+              :http-request="handleUpload"
               :show-file-list="false"
-              :on-success="handleUploadSuccess"
-              :on-error="handleUploadError"
               :before-upload="beforeUpload"
               accept="image/*"
             >
@@ -115,7 +113,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, API_BASE_URL } from '@/config/api'
+import { API_ENDPOINTS } from '@/config/api'
 
 const route = useRoute()
 const router = useRouter()
@@ -123,11 +121,6 @@ const isEdit = ref(false)
 const submitLoading = ref(false)
 const formRef = ref(null)
 
-// 上传地址
-const uploadUrl = computed(() => {
-  return API_BASE_URL + API_ENDPOINTS.UPLOAD_IMAGE
-})
-
 const form = reactive({
   id: null,
   name: '',
@@ -167,22 +160,40 @@ const beforeUpload = (file) => {
   return true
 }
 
-// 上传成功
-const handleUploadSuccess = (response) => {
-  if (response.code === 200) {
-    form.coverImage = response.data.url || response.data
-    ElMessage.success('封面上传成功')
-  } else {
-    ElMessage.error(response.message || '封面上传失败')
+// 上传图片
+const handleUpload = async (options) => {
+  const formData = new FormData()
+  formData.append('file', options.file)
+  
+  try {
+    const response = await request.post(API_ENDPOINTS.UPLOAD_IMAGE, formData, {
+      headers: { 'Content-Type': 'multipart/form-data' }
+    })
+    
+    if (response.code === 200) {
+      // 处理不同的返回格式
+      let imageUrl = ''
+      if (typeof response.data === 'string') {
+        imageUrl = response.data
+      } else if (response.data && response.data.url) {
+        imageUrl = response.data.url
+      } else if (response.data && response.data.path) {
+        imageUrl = response.data.path
+      } else {
+        imageUrl = String(response.data || '')
+      }
+      
+      form.coverImage = imageUrl
+      ElMessage.success('封面上传成功')
+    } else {
+      ElMessage.error(response.message || '封面上传失败')
+    }
+  } catch (error) {
+    console.error('上传异常:', error)
+    ElMessage.error('封面上传失败,请重试')
   }
 }
 
-// 上传失败
-const handleUploadError = (error) => {
-  console.error('上传失败:', error)
-  ElMessage.error('封面上传失败,请重试')
-}
-
 // 删除封面
 const removeCover = () => {
   form.coverImage = ''

+ 406 - 46
marriageAdmin-vue/src/views/matchmaker/CaseAudit.vue

@@ -37,12 +37,25 @@
           <el-option label="审核通过" :value="1" />
           <el-option label="审核失败" :value="2" />
         </el-select>
+        <el-button 
+          type="primary" 
+          :disabled="selectedRows.length === 0"
+          @click="handleBatchAudit"
+          style="margin-left: 10px"
+        >
+          批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
+        </el-button>
         <el-button icon="Refresh" @click="resetFilters">重置</el-button>
       </el-space>
     </el-card>
 
     <el-card shadow="never" class="table-card">
-      <el-table v-loading="loading" :data="list" stripe>
+      <el-table 
+        v-loading="loading" 
+        :data="list" 
+        stripe
+        @selection-change="handleSelectionChange"
+      >
         <template #empty>
           <div class="custom-empty-state">
             <el-icon class="empty-icon"><Document /></el-icon>
@@ -51,6 +64,7 @@
           </div>
         </template>
 
+        <el-table-column type="selection" width="55" :selectable="row => !isAudited(row)" />
         <el-table-column type="index" label="序号" width="60" />
         <el-table-column prop="maleRealName" label="男方姓名" min-width="120">
           <template #default="{ row }">
@@ -111,8 +125,15 @@
             <span v-else style="color: #999;">-</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="120" fixed="right">
+        <el-table-column label="操作" width="180" fixed="right">
           <template #default="{ row }">
+            <el-button
+              type="info"
+              size="small"
+              @click="handleDetail(row)"
+            >
+              详情
+            </el-button>
             <el-button
               type="primary"
               size="small"
@@ -138,51 +159,130 @@
       </div>
     </el-card>
 
+    <!-- 详情弹框 -->
+    <el-dialog 
+      v-model="detailDialogVisible" 
+      title="案例详情" 
+      width="900px"
+      :close-on-click-modal="false"
+    >
+      <el-descriptions :column="2" border v-if="detailData">
+        <el-descriptions-item label="案例ID">{{ detailData.id }}</el-descriptions-item>
+        <el-descriptions-item label="红娘ID">{{ detailData.matchmakerId || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="男方用户ID">{{ detailData.maleUserId || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="女方用户ID">{{ detailData.femaleUserId || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="男方姓名">{{ detailData.maleRealName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="女方姓名">{{ detailData.femaleRealName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="案例类型">
+          <el-tag size="small" :type="detailData.caseType === 1 ? 'success' : 'warning'">
+            {{ detailData.caseType === 1 ? '订婚' : detailData.caseType === 2 ? '领证结婚' : '-' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="成功日期">{{ formatDate(detailData.caseDate) }}</el-descriptions-item>
+        <el-descriptions-item label="审核状态">
+          <el-tag
+            size="small"
+            :type="getAuditStatusType(detailData.auditStatus)"
+            effect="light"
+          >
+            {{ getAuditStatusText(detailData.auditStatus) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="审核备注" :span="2" v-if="detailData.auditRemark">
+          {{ detailData.auditRemark }}
+        </el-descriptions-item>
+        <el-descriptions-item label="积分奖励">{{ detailData.pointsReward || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="现金奖励">
+          {{ detailData.cashReward ? '¥' + detailData.cashReward : '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="奖励状态">
+          {{ detailData.rewardStatus === 1 ? '已发放' : detailData.rewardStatus === 0 ? '未发放' : '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="是否发布">
+          {{ detailData.isPublished === 1 ? '是' : detailData.isPublished === 0 ? '否' : '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="关联案例ID">{{ detailData.publishedCaseId || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="审核耗时">{{ detailData.auditDuration || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="成功凭证图片" :span="2" v-if="proofImageList.length > 0">
+          <div class="proof-images">
+            <el-image
+              v-for="(img, index) in proofImageList"
+              :key="index"
+              :src="img"
+              :preview-src-list="proofImageList"
+              :initial-index="index"
+              fit="cover"
+              class="proof-image-item"
+              :preview-teleported="true"
+            />
+          </div>
+        </el-descriptions-item>
+        <el-descriptions-item label="上传时间">{{ formatDateTime(detailData.createdAt) }}</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ formatDateTime(detailData.updatedAt) }}</el-descriptions-item>
+        <el-descriptions-item label="审核时间">{{ formatDateTime(detailData.auditedAt) }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="detailDialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
     <!-- 审核弹框 -->
     <el-dialog
       v-model="auditDialogVisible"
-      title="案例审核"
-      width="500px"
+      :title="auditDialogTitle"
+      width="600px"
       :close-on-click-modal="false"
     >
-      <el-form :model="auditForm" label-width="100px">
-        <el-form-item label="男方姓名">
+      <!-- 批量审核进度显示 -->
+      <div v-if="isBatchAudit && batchAuditList.length > 0" class="batch-audit-progress">
+        进度:{{ currentBatchIndex }} / {{ batchAuditList.length }}
+      </div>
+      
+      <!-- 当前审核数据信息 -->
+      <div v-if="isBatchAudit && currentBatchRow" class="current-audit-info">
+        <el-descriptions :column="2" border size="small">
+          <el-descriptions-item label="男方姓名">{{ currentBatchRow.maleRealName || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="女方姓名">{{ currentBatchRow.femaleRealName || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="案例类型">
+            {{ currentBatchRow.caseType === 1 ? '订婚' : currentBatchRow.caseType === 2 ? '领证结婚' : '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="成功日期">{{ formatDate(currentBatchRow.caseDate) }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+      
+      <el-form :model="auditForm" :rules="auditRules" ref="auditFormRef" label-width="100px" style="margin-top: 20px">
+        <el-form-item v-if="!isBatchAudit" label="男方姓名">
           <span>{{ currentRow?.maleRealName || '-' }}</span>
         </el-form-item>
-        <el-form-item label="女方姓名">
+        <el-form-item v-if="!isBatchAudit" label="女方姓名">
           <span>{{ currentRow?.femaleRealName || '-' }}</span>
         </el-form-item>
-        <el-form-item label="审核操作" required>
+        <el-form-item label="审核操作" prop="action">
           <el-radio-group v-model="auditForm.action">
             <el-radio :label="1">审核通过</el-radio>
             <el-radio :label="2">审核不通过</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item
-          v-if="auditForm.action === 2"
-          label="审核备注"
-          required
-          :rules="[{ required: true, message: '请输入审核备注', trigger: 'blur' }]"
-        >
+        <el-form-item label="审核备注" prop="auditRemark">
           <el-input
             v-model="auditForm.auditRemark"
             type="textarea"
             :rows="4"
-            placeholder="请输入审核不通过的原因(失败原因)"
+            :placeholder="auditForm.action === 1 ? '请输入审核备注(可选)' : '请输入审核不通过的原因(必填)'"
             maxlength="500"
             show-word-limit
           />
         </el-form-item>
       </el-form>
       <template #footer>
-        <el-button @click="auditDialogVisible = false">取消</el-button>
+        <el-button @click="handleCancelBatchAudit">取消</el-button>
         <el-button
           type="primary"
           @click="submitAudit"
-          :loading="auditing"
+          :loading="auditSubmitting"
           :disabled="auditForm.action === 2 && !auditForm.auditRemark?.trim()"
         >
-          确定
+          {{ isBatchAudit && currentBatchIndex < batchAuditList.length ? '提交并继续' : '确定' }}
         </el-button>
       </template>
     </el-dialog>
@@ -202,8 +302,20 @@ const total = ref(0)
 const currentPage = ref(1)
 const pageSize = ref(10)
 const auditing = ref(false)
+const auditSubmitting = ref(false)
 const auditDialogVisible = ref(false)
 const currentRow = ref(null)
+const selectedRows = ref([])
+const auditFormRef = ref(null)
+const isBatchAudit = ref(false)
+const batchAuditList = ref([]) // 批量审核的列表
+const currentBatchIndex = ref(1) // 当前审核的索引(从1开始)
+const currentBatchRow = ref(null) // 当前审核的行数据
+const auditDialogTitle = ref('案例审核') // 弹框标题
+
+const detailDialogVisible = ref(false)
+const detailData = ref(null)
+const proofImageList = ref([])
 
 const filters = reactive({
   maleRealName: '',
@@ -216,6 +328,25 @@ const auditForm = reactive({
   auditRemark: ''
 })
 
+const auditRules = {
+  action: [
+    { required: true, message: '请选择审核操作', trigger: 'change' }
+  ],
+  auditRemark: [
+    { 
+      validator: (rule, value, callback) => {
+        if (auditForm.action === 2 && (!value || !value.trim())) {
+          callback(new Error('审核不通过时,必须填写审核备注'))
+        } else {
+          callback()
+        }
+      }, 
+      trigger: 'blur' 
+    },
+    { max: 500, message: '审核备注长度不能超过 500 个字符', trigger: 'blur' }
+  ]
+}
+
 const loadList = async () => {
   loading.value = true
   try {
@@ -290,59 +421,255 @@ const formatDateTime = (date) => {
   })
 }
 
+// 判断是否已审核(auditStatus=1或2都表示已审核)
+const isAudited = (row) => {
+  return row.auditStatus === 1 || row.auditStatus === 2
+}
+
+// 表格选择变化
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection
+}
+
+const handleDetail = async (row) => {
+  try {
+    const response = await request.get(`${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_DETAIL}/${row.id}`)
+    if (response.code === 200) {
+      detailData.value = response.data
+      
+      // 解析成功凭证图片(JSON数组格式)
+      proofImageList.value = []
+      if (response.data.proofImages) {
+        try {
+          const images = JSON.parse(response.data.proofImages)
+          if (Array.isArray(images)) {
+            proofImageList.value = images
+          }
+        } catch (e) {
+          console.error('解析图片JSON失败:', e)
+        }
+      }
+      
+      detailDialogVisible.value = true
+    } else {
+      ElMessage.error(response.message || response.msg || '获取详情失败')
+    }
+  } catch (error) {
+    console.error('获取详情失败:', error)
+    ElMessage.error('获取详情失败: ' + (error.message || '未知错误'))
+  }
+}
+
 const handleAudit = (row) => {
   if (row.auditStatus === 1 || row.auditStatus === 2) {
     ElMessage.warning('该案例已审核,无需重复操作')
     return
   }
   currentRow.value = row
+  isBatchAudit.value = false
   auditForm.action = 1
   auditForm.auditRemark = ''
+  auditDialogTitle.value = `案例审核 - ${row.maleRealName || ''} & ${row.femaleRealName || ''}`
   auditDialogVisible.value = true
 }
 
-const submitAudit = async () => {
-  if (!currentRow.value) {
-    ElMessage.error('请选择要审核的案例')
+// 批量审核
+const handleBatchAudit = () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请至少选择一条记录')
     return
   }
-
-  if (auditForm.action === 2 && !auditForm.auditRemark?.trim()) {
-    ElMessage.warning('审核不通过时,必须填写审核备注')
+  
+  // 过滤掉已审核的记录
+  const unapprovedRows = selectedRows.value.filter(row => !isAudited(row))
+  if (unapprovedRows.length === 0) {
+    ElMessage.warning('所选记录均已审核,无需重复操作')
     return
   }
+  
+  // 初始化批量审核列表
+  batchAuditList.value = unapprovedRows
+  currentBatchIndex.value = 1
+  currentRow.value = null
+  isBatchAudit.value = true
+  auditForm.action = 1
+  auditForm.auditRemark = ''
+  
+  // 设置标题
+  const count = unapprovedRows.length
+  auditDialogTitle.value = count > 0 ? `批量审核 (${count}条)` : '批量审核'
+  
+  // 设置当前审核的行数据
+  updateCurrentBatchRow()
+  
+  auditDialogVisible.value = true
+}
 
-  try {
-    auditing.value = true
-    let res
-
-    if (auditForm.action === 1) {
-      // 审核通过
-      res = await request.post(
-        `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_APPROVE}/${currentRow.value.id}`
-      )
+// 更新当前批量审核的行数据
+const updateCurrentBatchRow = () => {
+  if (isBatchAudit.value && batchAuditList.value.length > 0) {
+    const index = currentBatchIndex.value - 1
+    if (index >= 0 && index < batchAuditList.value.length) {
+      currentBatchRow.value = batchAuditList.value[index]
     } else {
-      // 审核不通过
-      res = await request.post(
-        `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_REJECT}/${currentRow.value.id}`,
-        {
-          auditRemark: auditForm.auditRemark.trim()
-        }
-      )
+      currentBatchRow.value = null
     }
+  }
+}
 
-    if (res.code === 200) {
-      ElMessage.success(auditForm.action === 1 ? '审核通过成功' : '审核不通过操作成功')
+// 取消批量审核
+const handleCancelBatchAudit = () => {
+  if (isBatchAudit.value && batchAuditList.value.length > 0) {
+    ElMessageBox.confirm(
+      `还有 ${batchAuditList.value.length - currentBatchIndex.value + 1} 条记录未审核,确定要取消吗?`,
+      '确认取消',
+      {
+        confirmButtonText: '确定取消',
+        cancelButtonText: '继续审核',
+        type: 'warning'
+      }
+    ).then(() => {
       auditDialogVisible.value = false
-      loadList()
+      resetBatchAudit()
+    }).catch(() => {
+      // 用户选择继续审核,不做任何操作
+    })
+  } else {
+    auditDialogVisible.value = false
+    resetBatchAudit()
+  }
+}
+
+// 重置批量审核状态
+const resetBatchAudit = () => {
+  batchAuditList.value = []
+  currentBatchIndex.value = 1
+  currentBatchRow.value = null
+  isBatchAudit.value = false
+  auditDialogTitle.value = '案例审核'
+  auditForm.action = 1
+  auditForm.auditRemark = ''
+}
+
+const submitAudit = async () => {
+  if (!auditFormRef.value) return
+  
+  try {
+    await auditFormRef.value.validate()
+    
+    auditing.value = true
+    auditSubmitting.value = true
+    
+    if (isBatchAudit.value) {
+      // 批量审核 - 逐条审核
+      const row = currentBatchRow.value
+      if (!row) {
+        ElMessage.error('当前审核数据不存在')
+        return
+      }
+      
+      let res
+      if (auditForm.action === 1) {
+        // 审核通过
+        const requestBody = auditForm.auditRemark?.trim() 
+          ? { auditRemark: auditForm.auditRemark.trim() } 
+          : {}
+        res = await request.post(
+          `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_APPROVE}/${row.id}`,
+          Object.keys(requestBody).length > 0 ? requestBody : null
+        )
+      } else {
+        // 审核不通过
+        res = await request.post(
+          `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_REJECT}/${row.id}`,
+          {
+            auditRemark: auditForm.auditRemark.trim()
+          }
+        )
+      }
+      
+      if (res.code === 200) {
+        ElMessage.success(
+          `${row.maleRealName || ''} & ${row.femaleRealName || ''} ${auditForm.action === 1 ? '审核通过' : '审核不通过'}成功`
+        )
+        
+        // 从批量审核列表中移除已审核的项
+        const index = currentBatchIndex.value - 1
+        batchAuditList.value.splice(index, 1)
+        
+        // 如果还有未审核的记录,自动跳转到下一条
+        if (batchAuditList.value.length > 0) {
+          // 删除后,如果当前索引超出范围(说明删除的是最后一条),则跳转到新的最后一条
+          // 否则保持当前索引不变,因为后面的记录会前移,当前索引对应的就是下一条
+          if (currentBatchIndex.value > batchAuditList.value.length) {
+            currentBatchIndex.value = batchAuditList.value.length
+          }
+          // 更新标题
+          const count = batchAuditList.value.length
+          auditDialogTitle.value = count > 0 ? `批量审核 (${count}条)` : '批量审核'
+          updateCurrentBatchRow()
+          // 重置表单
+          auditForm.action = 1
+          auditForm.auditRemark = ''
+          // 清除表单验证状态
+          if (auditFormRef.value) {
+            auditFormRef.value.clearValidate()
+          }
+        } else {
+          // 所有记录都已审核完成
+          ElMessage.success('批量审核完成')
+          auditDialogVisible.value = false
+          selectedRows.value = []
+          resetBatchAudit()
+          loadList()
+        }
+      } else {
+        ElMessage.error(res.msg || '审核失败')
+        // 审核失败时,不自动跳转,让用户可以修改后重试
+      }
     } else {
-      ElMessage.error(res.msg || '审核操作失败')
+      // 单个审核
+      if (!currentRow.value) {
+        ElMessage.error('请选择要审核的案例')
+        return
+      }
+      
+      let res
+      if (auditForm.action === 1) {
+        // 审核通过
+        const requestBody = auditForm.auditRemark?.trim() 
+          ? { auditRemark: auditForm.auditRemark.trim() } 
+          : {}
+        res = await request.post(
+          `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_APPROVE}/${currentRow.value.id}`,
+          Object.keys(requestBody).length > 0 ? requestBody : null
+        )
+      } else {
+        // 审核不通过
+        res = await request.post(
+          `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_REJECT}/${currentRow.value.id}`,
+          {
+            auditRemark: auditForm.auditRemark.trim()
+          }
+        )
+      }
+      
+      if (res.code === 200) {
+        ElMessage.success(auditForm.action === 1 ? '审核通过成功' : '审核不通过操作成功')
+        auditDialogVisible.value = false
+        loadList()
+      } else {
+        ElMessage.error(res.msg || '审核操作失败')
+      }
     }
   } catch (error) {
-    console.error('审核操作失败:', error)
-    ElMessage.error(error.response?.data?.msg || error.message || '审核操作失败')
+    if (error !== false) { // validate 失败会返回 false
+      console.error('审核操作失败:', error)
+      ElMessage.error(error.response?.data?.msg || error.message || '审核操作失败')
+    }
   } finally {
     auditing.value = false
+    auditSubmitting.value = false
   }
 }
 
@@ -355,5 +682,38 @@ onMounted(loadList)
 .case-audit-container {
   padding: 0;
 }
+
+.proof-images {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.proof-image-item {
+  width: 100px;
+  height: 100px;
+  border-radius: 4px;
+  cursor: pointer;
+  border: 1px solid #dcdfe6;
+}
+
+.batch-audit-progress {
+  font-size: 14px;
+  color: #606266;
+  font-weight: 500;
+  margin-bottom: 20px;
+  padding: 10px 15px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  text-align: center;
+}
+
+.current-audit-info {
+  margin-bottom: 20px;
+}
+
+.current-audit-info :deep(.el-descriptions__label) {
+  font-weight: 500;
+}
 </style>
 

+ 516 - 33
marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue

@@ -26,12 +26,25 @@
             <el-button icon="Search" @click="loadList" />
           </template>
         </el-input>
+        <el-button 
+          type="primary" 
+          :disabled="selectedRows.length === 0"
+          @click="handleBatchAudit"
+          style="margin-left: 10px"
+        >
+          批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
+        </el-button>
         <el-button icon="Refresh" @click="resetFilters">重置</el-button>
       </el-space>
     </el-card>
 
     <el-card shadow="never" class="table-card">
-      <el-table v-loading="loading" :data="list" stripe>
+      <el-table 
+        v-loading="loading" 
+        :data="list" 
+        stripe
+        @selection-change="handleSelectionChange"
+      >
         <template #empty>
           <div class="custom-empty-state">
             <el-icon class="empty-icon"><User /></el-icon>
@@ -40,6 +53,7 @@
           </div>
         </template>
 
+        <el-table-column type="selection" width="55" :selectable="row => !isAudited(row)" />
         <el-table-column type="index" label="序号" width="60" />
         <el-table-column prop="name" label="姓名" min-width="100">
           <template #default="{ row }">
@@ -63,20 +77,50 @@
             <span class="text-muted">{{ row.createTime || row.create_time || '-' }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="180" fixed="right">
+        <el-table-column label="审核原因" min-width="150" show-overflow-tooltip>
+          <template #default="{ row }">
+            <span class="text-muted">{{ row.reason || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="250" fixed="right">
           <template #default="{ row }">
             <el-button
+              type="info"
+              size="small"
+              link
+              @click="handleViewDetail(row)"
+            >
+              详情
+            </el-button>
+            <el-button
+              v-if="!isAudited(row)"
               type="primary"
               size="small"
-              @click="handleApprove(row)"
+              @click="handleAudit(row)"
               :loading="approvingId === row.applyId"
-              :disabled="isApproved(row)"
             >
-              {{ isApproved(row) ? '审核成功' : '审核通过' }}
+              审核
             </el-button>
+            <el-tag
+              v-else-if="row.status === 0"
+              type="success"
+              size="small"
+              effect="light"
+            >
+              审核通过
+            </el-tag>
+            <el-tag
+              v-else-if="row.status === 1"
+              class="audit-rejected-tag"
+              size="small"
+              effect="light"
+            >
+              审核未通过
+            </el-tag>
             <el-button
               type="danger"
               size="small"
+              link
               @click="handleDelete(row)"
               :loading="deletingId === row.applyId"
             >
@@ -98,13 +142,186 @@
         />
       </div>
     </el-card>
+
+    <!-- 详情对话框 -->
+    <el-dialog
+      v-model="detailDialogVisible"
+      title="红娘申请详情"
+      width="800px"
+      :close-on-click-modal="false"
+    >
+      <div v-if="detailData" class="detail-content">
+        <!-- 基本信息 -->
+        <el-card shadow="never" class="detail-section">
+          <template #header>
+            <div class="section-header">
+              <el-icon><User /></el-icon>
+              <span>基本信息</span>
+            </div>
+          </template>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="姓名">
+              {{ detailData.name || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="手机号">
+              {{ detailData.phone || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="邮箱">
+              {{ detailData.email || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="年龄">
+              {{ detailData.age || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="性别">
+              <el-tag v-if="detailData.gender === 1" type="primary" size="small">男</el-tag>
+              <el-tag v-else-if="detailData.gender === 2" type="danger" size="small">女</el-tag>
+              <span v-else>-</span>
+            </el-descriptions-item>
+            <el-descriptions-item label="所在地区">
+              {{ detailData.area || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="用户ID">
+              {{ detailData.userId || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="申请ID">
+              {{ detailData.applyId || '-' }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+
+        <!-- 红娘信息 -->
+        <el-card shadow="never" class="detail-section">
+          <template #header>
+            <div class="section-header">
+              <el-icon><Star /></el-icon>
+              <span>红娘信息</span>
+            </div>
+          </template>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="婚姻介绍经验" :span="2">
+              {{ detailData.experience || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="可服务时间" :span="2">
+              {{ detailData.serverTime || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="个人简介" :span="2">
+              <div class="introduction-content">
+                {{ detailData.introduction || '-' }}
+              </div>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+
+        <!-- 审核信息 -->
+        <el-card shadow="never" class="detail-section" v-if="detailData.status !== null && detailData.status !== undefined">
+          <template #header>
+            <div class="section-header">
+              <el-icon><Document /></el-icon>
+              <span>审核信息</span>
+            </div>
+          </template>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="审核状态">
+              <el-tag
+                v-if="detailData.status === 0"
+                type="success"
+                size="small"
+              >
+                审核通过
+              </el-tag>
+              <el-tag
+                v-else-if="detailData.status === 1"
+                class="audit-rejected-tag"
+                size="small"
+              >
+                审核未通过
+              </el-tag>
+              <el-tag
+                v-else
+                type="info"
+                size="small"
+              >
+                待审核
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="审核原因">
+              {{ detailData.reason || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="申请时间">
+              {{ detailData.createTime || detailData.create_time || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="更新时间">
+              {{ detailData.updateTime || detailData.update_time || '-' }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+      </div>
+      <template #footer>
+        <el-button @click="detailDialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 审核弹框 -->
+    <el-dialog
+      v-model="auditDialogVisible"
+      :title="auditDialogTitle"
+      width="600px"
+      :close-on-click-modal="false"
+    >
+      <!-- 批量审核进度显示 -->
+      <div v-if="isBatchAudit && batchAuditList.length > 0" class="batch-audit-progress">
+        进度:{{ currentBatchIndex }} / {{ batchAuditList.length }}
+      </div>
+      
+      <!-- 当前审核数据信息 -->
+      <div v-if="isBatchAudit && currentBatchRow" class="current-audit-info">
+        <el-descriptions :column="2" border size="small">
+          <el-descriptions-item label="姓名">{{ currentBatchRow.name || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="手机号">{{ currentBatchRow.phone || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="年龄">{{ currentBatchRow.age || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="性别">
+            {{ currentBatchRow.gender === 1 ? '男' : currentBatchRow.gender === 2 ? '女' : '未知' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="地区" :span="2">{{ currentBatchRow.area || '-' }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+      
+      <el-form :model="auditForm" :rules="auditRules" ref="auditFormRef" label-width="100px" style="margin-top: 20px">
+        <el-form-item label="审核结果" prop="approved">
+          <el-radio-group v-model="auditForm.approved">
+            <el-radio :label="true">同意</el-radio>
+            <el-radio :label="false">不同意</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="审核原因" prop="reason">
+          <el-input
+            v-model="auditForm.reason"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入审核原因(必填)"
+            maxlength="500"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="handleCancelBatchAudit">取消</el-button>
+        <el-button 
+          type="primary" 
+          @click="submitAudit" 
+          :loading="auditSubmitting"
+        >
+          {{ isBatchAudit && currentBatchIndex < batchAuditList.length ? '提交并继续' : '确定' }}
+        </el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, reactive, onMounted } from 'vue'
+import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { User } from '@element-plus/icons-vue'
+import { User, Star, Document } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 
@@ -115,12 +332,41 @@ const currentPage = ref(1)
 const pageSize = ref(10)
 const approvingId = ref(null)
 const deletingId = ref(null)
+const selectedRows = ref([])
+const auditDialogVisible = ref(false)
+const auditSubmitting = ref(false)
+const auditFormRef = ref(null)
+const currentAuditRow = ref(null)
+const isBatchAudit = ref(false)
+const batchAuditList = ref([]) // 批量审核的列表
+const currentBatchIndex = ref(1) // 当前审核的索引(从1开始)
+const currentBatchRow = ref(null) // 当前审核的行数据
+const auditDialogTitle = ref('审核') // 弹框标题
+
+// 详情对话框相关
+const detailDialogVisible = ref(false)
+const detailData = ref(null)
 
 const filters = reactive({
   name: '',
   phone: ''
 })
 
+const auditForm = reactive({
+  approved: true,
+  reason: ''
+})
+
+const auditRules = {
+  approved: [
+    { required: true, message: '请选择审核结果', trigger: 'change' }
+  ],
+  reason: [
+    { required: true, message: '请输入审核原因', trigger: 'blur' },
+    { min: 1, max: 500, message: '审核原因长度在 1 到 500 个字符', trigger: 'blur' }
+  ]
+}
+
 const loadList = async () => {
   loading.value = true
   try {
@@ -160,53 +406,240 @@ const isApproved = (row) => {
   return row.status === 0
 }
 
-// 审核通过
-const handleApprove = async (row) => {
+// 判断是否已审核(status=0或1都表示已审核)
+const isAudited = (row) => {
+  return row.status === 0 || row.status === 1
+}
+
+// 表格选择变化
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection
+}
+
+// 打开审核弹框(单个)
+const handleAudit = (row) => {
   if (!row.userId) {
     ElMessage.error('用户ID不存在,无法审核')
     return
   }
   
-  if (row.status === 0) {
-    ElMessage.warning('该申请已审核通过,无需重复操作')
+  if (isAudited(row)) {
+    ElMessage.warning('该申请已审核,无需重复操作')
     return
   }
   
-  try {
-    await ElMessageBox.confirm(
-      `确定要通过 ${row.name || '该用户'} 的红娘申请吗?\n通过后该用户将成为红娘,此操作将更新审核状态。`,
-      '确认审核通过',
+  currentAuditRow.value = row
+  isBatchAudit.value = false
+  auditForm.approved = true
+  auditForm.reason = ''
+  auditDialogTitle.value = `审核 - ${row.name || '该用户'}`
+  auditDialogVisible.value = true
+}
+
+// 批量审核
+const handleBatchAudit = () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请至少选择一条记录')
+    return
+  }
+  
+  // 过滤掉已审核的记录
+  const unapprovedRows = selectedRows.value.filter(row => !isAudited(row))
+  if (unapprovedRows.length === 0) {
+    ElMessage.warning('所选记录均已审核,无需重复操作')
+    return
+  }
+  
+  // 初始化批量审核列表
+  batchAuditList.value = unapprovedRows
+  currentBatchIndex.value = 1
+  currentAuditRow.value = null
+  isBatchAudit.value = true
+  auditForm.approved = true
+  auditForm.reason = ''
+  
+  // 设置标题
+  const count = unapprovedRows.length
+  auditDialogTitle.value = count > 0 ? `批量审核 (${count}条)` : '批量审核'
+  
+  // 设置当前审核的行数据
+  updateCurrentBatchRow()
+  
+  auditDialogVisible.value = true
+}
+
+// 更新当前批量审核的行数据
+const updateCurrentBatchRow = () => {
+  if (isBatchAudit.value && batchAuditList.value.length > 0) {
+    const index = currentBatchIndex.value - 1
+    if (index >= 0 && index < batchAuditList.value.length) {
+      currentBatchRow.value = batchAuditList.value[index]
+    } else {
+      currentBatchRow.value = null
+    }
+  }
+}
+
+// 批量审核分页变化
+const handleBatchPageChange = (page) => {
+  currentBatchIndex.value = page
+  updateCurrentBatchRow()
+  // 重置表单
+  auditForm.approved = true
+  auditForm.reason = ''
+  // 清除表单验证状态
+  if (auditFormRef.value) {
+    auditFormRef.value.clearValidate()
+  }
+}
+
+// 取消批量审核
+const handleCancelBatchAudit = () => {
+  if (isBatchAudit.value && batchAuditList.value.length > 0) {
+    ElMessageBox.confirm(
+      `还有 ${batchAuditList.value.length - currentBatchIndex.value + 1} 条记录未审核,确定要取消吗?`,
+      '确认取消',
       {
-        confirmButtonText: '确定通过',
-        cancelButtonText: '取消',
-        type: 'warning',
-        dangerouslyUseHTMLString: false
+        confirmButtonText: '确定取消',
+        cancelButtonText: '继续审核',
+        type: 'warning'
       }
-    )
+    ).then(() => {
+      auditDialogVisible.value = false
+      resetBatchAudit()
+    }).catch(() => {
+      // 用户选择继续审核,不做任何操作
+    })
+  } else {
+    auditDialogVisible.value = false
+    resetBatchAudit()
+  }
+}
+
+// 重置批量审核状态
+const resetBatchAudit = () => {
+  batchAuditList.value = []
+  currentBatchIndex.value = 1
+  currentBatchRow.value = null
+  isBatchAudit.value = false
+  auditDialogTitle.value = '审核'
+  auditForm.approved = true
+  auditForm.reason = ''
+}
+
+// 提交审核
+const submitAudit = async () => {
+  if (!auditFormRef.value) return
+  
+  try {
+    await auditFormRef.value.validate()
     
-    approvingId.value = row.applyId
-    const res = await request.post(
-      `${API_ENDPOINTS.MATCHMAKER_AUDIT_APPROVE}/${row.applyId}`,
-      null,
-      { params: { userId: row.userId } }
-    )
+    auditSubmitting.value = true
     
-    if (res.code === 200) {
-      ElMessage.success('审核通过成功')
-      loadList()
+    if (isBatchAudit.value) {
+      // 批量审核 - 逐条审核
+      const row = currentBatchRow.value
+      if (!row) {
+        ElMessage.error('当前审核数据不存在')
+        return
+      }
+      
+      approvingId.value = row.applyId
+      
+      // 使用单条审核接口
+      const res = await request.post(
+        `${API_ENDPOINTS.MATCHMAKER_AUDIT_APPROVE}/${row.applyId}`,
+        null,
+        {
+          params: {
+            userId: row.userId,
+            approved: auditForm.approved,
+            reason: auditForm.reason
+          }
+        }
+      )
+      
+      if (res.code === 200) {
+        ElMessage.success(
+          `${row.name || '该用户'} ${auditForm.approved ? '审核通过' : '审核不通过'}成功`
+        )
+        
+        // 从批量审核列表中移除已审核的项
+        const index = currentBatchIndex.value - 1
+        batchAuditList.value.splice(index, 1)
+        
+        // 如果还有未审核的记录,自动跳转到下一条
+        if (batchAuditList.value.length > 0) {
+          // 删除后,如果当前索引超出范围(说明删除的是最后一条),则跳转到新的最后一条
+          // 否则保持当前索引不变,因为后面的记录会前移,当前索引对应的就是下一条
+          if (currentBatchIndex.value > batchAuditList.value.length) {
+            currentBatchIndex.value = batchAuditList.value.length
+          }
+          // 更新标题
+          const count = batchAuditList.value.length
+          auditDialogTitle.value = count > 0 ? `批量审核 (${count}条)` : '批量审核'
+          updateCurrentBatchRow()
+          // 重置表单
+          auditForm.approved = true
+          auditForm.reason = ''
+          // 清除表单验证状态
+          if (auditFormRef.value) {
+            auditFormRef.value.clearValidate()
+          }
+        } else {
+          // 所有记录都已审核完成
+          ElMessage.success('批量审核完成')
+          auditDialogVisible.value = false
+          selectedRows.value = []
+          resetBatchAudit()
+          loadList()
+        }
+      } else {
+        ElMessage.error(res.msg || '审核失败')
+      }
     } else {
-      ElMessage.error(res.msg || '审核通过失败')
+      // 单个审核
+      const row = currentAuditRow.value
+      approvingId.value = row.applyId
+      
+      const res = await request.post(
+        `${API_ENDPOINTS.MATCHMAKER_AUDIT_APPROVE}/${row.applyId}`,
+        null,
+        {
+          params: {
+            userId: row.userId,
+            approved: auditForm.approved,
+            reason: auditForm.reason
+          }
+        }
+      )
+      
+      if (res.code === 200) {
+        ElMessage.success(res.msg || (auditForm.approved ? '审核通过成功' : '审核不通过成功'))
+        auditDialogVisible.value = false
+        loadList()
+      } else {
+        ElMessage.error(res.msg || '审核失败')
+      }
     }
   } catch (error) {
-    if (error !== 'cancel') {
-      console.error('审核通过失败:', error)
-      ElMessage.error(error.response?.data?.msg || error.message || '审核通过失败')
+    if (error !== false) { // validate 失败会返回 false
+      console.error('审核失败:', error)
+      ElMessage.error(error.response?.data?.msg || error.message || '审核失败')
     }
   } finally {
+    auditSubmitting.value = false
     approvingId.value = null
   }
 }
 
+
+// 查看详情
+const handleViewDetail = (row) => {
+  detailData.value = { ...row }
+  detailDialogVisible.value = true
+}
+
 // 删除申请
 const handleDelete = async (row) => {
   try {
@@ -251,5 +684,55 @@ onMounted(loadList)
 .matchmaker-audit-container {
   padding: 0;
 }
+
+.batch-audit-progress {
+  font-size: 14px;
+  color: #606266;
+  font-weight: 500;
+  margin-bottom: 20px;
+  padding: 10px 15px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  text-align: center;
+}
+
+.current-audit-info {
+  margin-bottom: 20px;
+}
+
+.current-audit-info :deep(.el-descriptions__label) {
+  font-weight: 500;
+}
+
+.audit-rejected-tag {
+  background-color: #f4f4f5;
+  border-color: #d3d4d6;
+  color: #909399;
+}
+
+.detail-content {
+  min-height: 200px;
+}
+
+.detail-section {
+  margin-bottom: 20px;
+}
+
+.detail-section:last-child {
+  margin-bottom: 0;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 500;
+}
+
+.introduction-content {
+  white-space: pre-wrap;
+  word-break: break-word;
+  line-height: 1.6;
+}
 </style>
 

+ 241 - 2
marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue

@@ -93,8 +93,11 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="150" fixed="right">
+        <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
+            <el-button type="info" size="small" link @click="handleViewDetail(row)">
+              详情
+            </el-button>
             <el-button type="primary" size="small" link @click="$router.push(`/matchmaker/edit/${row.matchmaker_id}`)">
               编辑
             </el-button>
@@ -117,13 +120,140 @@
         />
       </div>
     </el-card>
+
+    <!-- 详情对话框 -->
+    <el-dialog
+      v-model="detailDialogVisible"
+      title="红娘详情"
+      width="800px"
+      :close-on-click-modal="false"
+    >
+      <div v-loading="detailLoading" class="detail-content">
+        <div v-if="detailData" class="detail-info">
+          <!-- 基本信息 -->
+          <el-card shadow="never" class="detail-section">
+            <template #header>
+              <div class="section-header">
+                <el-icon><User /></el-icon>
+                <span>基本信息</span>
+              </div>
+            </template>
+            <el-descriptions :column="2" border>
+              <el-descriptions-item label="头像" :span="2">
+                <el-avatar v-if="detailData.avatarUrl || detailData.avatar_url" :src="detailData.avatarUrl || detailData.avatar_url" :size="80" />
+                <el-avatar v-else :size="80">
+                  <el-icon><User /></el-icon>
+                </el-avatar>
+              </el-descriptions-item>
+              <el-descriptions-item label="姓名">
+                {{ detailData.realName || detailData.real_name || '-' }}
+              </el-descriptions-item>
+              <el-descriptions-item label="用户名">
+                {{ detailData.username || '-' }}
+              </el-descriptions-item>
+              <el-descriptions-item label="手机号">
+                {{ detailData.phone || '-' }}
+              </el-descriptions-item>
+              <el-descriptions-item label="邮箱">
+                {{ detailData.email || detailData.Email || '-' }}
+              </el-descriptions-item>
+              <el-descriptions-item label="性别">
+                <el-tag v-if="detailData.gender === 1" type="primary" size="small">男</el-tag>
+                <el-tag v-else-if="detailData.gender === 2" type="danger" size="small">女</el-tag>
+                <span v-else>-</span>
+              </el-descriptions-item>
+              <el-descriptions-item label="年龄">
+                {{ detailData.age || '-' }}
+              </el-descriptions-item>
+            </el-descriptions>
+          </el-card>
+
+          <!-- 红娘信息 -->
+          <el-card shadow="never" class="detail-section">
+            <template #header>
+              <div class="section-header">
+                <el-icon><Star /></el-icon>
+                <span>红娘信息</span>
+              </div>
+            </template>
+            <el-descriptions :column="2" border>
+              <el-descriptions-item label="红娘类型">
+                <el-tag :type="detailData.matchmakerType === 2 ? 'success' : 'info'" size="small">
+                  {{ detailData.matchmakerType === 2 ? '全职' : '兼职' }}
+                </el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="等级">
+                <el-tag :type="getLevelColor(detailData.level)" size="small">
+                  {{ getLevelText(detailData.level) }}
+                </el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="成功对数">
+                <span class="number-emphasis">{{ detailData.success_couples || detailData.successCouples || 0 }}</span>
+              </el-descriptions-item>
+              <el-descriptions-item label="积分">
+                <span class="number-emphasis">{{ detailData.points || 0 }}</span>
+              </el-descriptions-item>
+              <el-descriptions-item label="点赞数">
+                <span class="number-emphasis">{{ detailData.likes || 0 }}</span>
+              </el-descriptions-item>
+              <el-descriptions-item label="状态">
+                <el-tag 
+                  :class="{
+                    'status-success': detailData.status === 1,
+                    'status-danger': detailData.status === 0,
+                    'status-disabled': detailData.status === 2
+                  }"
+                  size="small"
+                >
+                  {{ detailData.status === 1 ? '正常' : detailData.status === 0 ? '禁用' : '离职' }}
+                </el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="创建时间">
+                {{ detailData.createdAt || detailData.createTime || detailData.created_at || '-' }}
+              </el-descriptions-item>
+            </el-descriptions>
+          </el-card>
+
+          <!-- 地址信息 -->
+          <el-card shadow="never" class="detail-section" v-if="detailData.full_address || detailData.fullAddress">
+            <template #header>
+              <div class="section-header">
+                <el-icon><Location /></el-icon>
+                <span>地址信息</span>
+              </div>
+            </template>
+            <el-descriptions :column="1" border>
+              <el-descriptions-item label="完整地址">
+                {{ detailData.full_address || detailData.fullAddress || '-' }}
+              </el-descriptions-item>
+            </el-descriptions>
+          </el-card>
+
+          <!-- 个人简介 -->
+          <el-card v-if="detailData.profile" shadow="never" class="detail-section">
+            <template #header>
+              <div class="section-header">
+                <el-icon><Document /></el-icon>
+                <span>个人简介</span>
+              </div>
+            </template>
+            <div class="profile-content">
+              {{ detailData.profile }}
+            </div>
+          </el-card>
+        </div>
+      </div>
+      <template #footer>
+        <el-button @click="detailDialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { User } from '@element-plus/icons-vue'
+import { User, Star, Location, Document } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 
@@ -133,6 +263,11 @@ const pageSize = ref(10)
 const total = ref(0)
 const list = ref([])
 
+// 详情对话框相关
+const detailDialogVisible = ref(false)
+const detailLoading = ref(false)
+const detailData = ref(null)
+
 const filters = reactive({
   matchmakerType: null,
   level: null,
@@ -222,6 +357,61 @@ const getLevelColor = (level) => {
   return colors[level] || ''
 }
 
+// 获取地址字段(支持area对象和直接字段两种方式)
+const getAreaField = (type) => {
+  if (!detailData.value) return ''
+  
+  // 如果存在area对象,从area对象中获取
+  if (detailData.value.area) {
+    const area = detailData.value.area
+    if (type === 'province') {
+      return area.provinceName || area.province_name || area.province || ''
+    } else if (type === 'city') {
+      return area.cityName || area.city_name || area.city || ''
+    } else if (type === 'area') {
+      return area.areaName || area.area_name || area.area || area.name || ''
+    }
+  }
+  
+  // 否则从直接字段获取
+  if (type === 'province') {
+    return detailData.value.provinceName || detailData.value.province_name || ''
+  } else if (type === 'city') {
+    return detailData.value.cityName || detailData.value.city_name || ''
+  } else if (type === 'area') {
+    return detailData.value.areaName || detailData.value.area_name || ''
+  }
+  
+  return ''
+}
+
+const handleViewDetail = async (row) => {
+  detailDialogVisible.value = true
+  detailData.value = null
+  detailLoading.value = true
+  
+  try {
+    const response = await request.get(`${API_ENDPOINTS.MATCHMAKER_DETAIL}/${row.matchmaker_id}`)
+    if (response.code === 200) {
+      detailData.value = response.data
+      // 调试:打印返回的数据结构
+      console.log('红娘详情数据:', detailData.value)
+      console.log('邮箱字段:', detailData.value.email, detailData.value.Email)
+      console.log('成功对数字段:', detailData.value.success_couples, detailData.value.successCouples)
+      console.log('地址字段:', detailData.value.full_address, detailData.value.fullAddress)
+    } else {
+      ElMessage.error(response.message || response.msg || '获取详情失败')
+      detailDialogVisible.value = false
+    }
+  } catch (error) {
+    console.error('获取详情失败:', error)
+    ElMessage.error('获取详情失败: ' + (error.message || '未知错误'))
+    detailDialogVisible.value = false
+  } finally {
+    detailLoading.value = false
+  }
+}
+
 const handleDelete = async (row) => {
   try {
     await ElMessageBox.confirm('确定要删除这个红娘吗?', '提示', {
@@ -248,5 +438,54 @@ onMounted(() => loadList())
 .matchmaker-list-container {
   padding: 0;
 }
+
+.detail-content {
+  min-height: 200px;
+}
+
+.detail-info {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.detail-section {
+  margin-bottom: 0;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 600;
+  font-size: 16px;
+}
+
+.section-header .el-icon {
+  font-size: 18px;
+  color: var(--el-color-primary);
+}
+
+.profile-content {
+  line-height: 1.8;
+  color: #606266;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+
+:deep(.el-descriptions__label) {
+  font-weight: 600;
+  width: 120px;
+}
+
+:deep(.el-descriptions__content) {
+  color: #606266;
+}
+
+.number-emphasis {
+  color: var(--el-color-primary);
+  font-weight: 600;
+  font-size: 16px;
+}
 </style>
 

+ 120 - 2
marriageAdmin-vue/src/views/matchmaker/PointsOrderList.vue

@@ -95,8 +95,16 @@
           </template>
         </el-table-column>
         <el-table-column prop="createTime" label="创建时间" width="180" />
-        <el-table-column label="操作" width="150" fixed="right">
+        <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
+            <el-button 
+              type="info" 
+              size="small" 
+              link 
+              @click="handleDetail(row)"
+            >
+              详情
+            </el-button>
             <el-button 
               type="primary" 
               size="small" 
@@ -131,6 +139,82 @@
       </div>
     </el-card>
 
+    <!-- 订单详情弹框 -->
+    <el-dialog 
+      v-model="detailDialogVisible" 
+      title="订单详情" 
+      width="800px"
+      :close-on-click-modal="false"
+    >
+      <el-descriptions v-if="orderDetail" :column="2" border>
+        <el-descriptions-item label="订单号" :span="2">
+          {{ orderDetail.orderNo }}
+        </el-descriptions-item>
+        <el-descriptions-item label="产品名称" :span="2">
+          {{ orderDetail.productName }}
+        </el-descriptions-item>
+        <el-descriptions-item label="产品图片" :span="2">
+          <el-image 
+            v-if="orderDetail.productImage" 
+            :src="orderDetail.productImage" 
+            style="width: 120px; height: 120px"
+            fit="cover"
+          />
+          <span v-else>-</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="积分单价">
+          {{ orderDetail.pointsPrice }} 积分
+        </el-descriptions-item>
+        <el-descriptions-item label="数量">
+          {{ orderDetail.quantity }}
+        </el-descriptions-item>
+        <el-descriptions-item label="总积分" :span="2">
+          <span style="color: #f56c6c; font-weight: bold;">{{ orderDetail.totalPoints }} 积分</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="审核状态" :span="2">
+          <el-tag :type="getReviewType(orderDetail.review)" size="small">
+            {{ getReviewText(orderDetail.review) }}
+          </el-tag>
+          <span v-if="orderDetail.review === 2 && orderDetail.reviewFail" style="margin-left: 10px; color: #f56c6c;">
+            ({{ orderDetail.reviewFail }})
+          </span>
+        </el-descriptions-item>
+        <el-descriptions-item label="发货状态" :span="2">
+          <el-tag :type="getStatusType(orderDetail.status)" size="small">
+            {{ getStatusText(orderDetail.status) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="收件人姓名">
+          {{ orderDetail.receiverName || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="收件人电话">
+          {{ orderDetail.receiverPhone || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="收件地址" :span="2">
+          {{ orderDetail.receiverAddress || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="快递公司" :span="2">
+          {{ orderDetail.expressCompany || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="快递单号" :span="2">
+          {{ orderDetail.expressNo || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间" :span="2">
+          {{ orderDetail.createTime || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="更新时间" :span="2">
+          {{ orderDetail.updateTime || '-' }}
+        </el-descriptions-item>
+      </el-descriptions>
+      <div v-else class="loading-container">
+        <el-icon class="is-loading"><Loading /></el-icon>
+        <span style="margin-left: 10px;">加载中...</span>
+      </div>
+      <template #footer>
+        <el-button @click="detailDialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
     <!-- 审核弹框 -->
     <el-dialog 
       v-model="reviewDialogVisible" 
@@ -189,7 +273,7 @@
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Document } from '@element-plus/icons-vue'
+import { Document, Loading } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 
@@ -206,6 +290,10 @@ const filters = reactive({
   status: null
 })
 
+const detailDialogVisible = ref(false)
+const detailLoading = ref(false)
+const orderDetail = ref(null)
+
 const reviewDialogVisible = ref(false)
 const reviewLoading = ref(false)
 const reviewForm = reactive({
@@ -341,6 +429,29 @@ const submitReview = async () => {
   }
 }
 
+const handleDetail = async (row) => {
+  detailDialogVisible.value = true
+  detailLoading.value = true
+  orderDetail.value = null
+  
+  try {
+    const response = await request.get(`${API_ENDPOINTS.POINTS_ORDER_DETAIL}/${row.id}`)
+    
+    if (response.code === 200) {
+      orderDetail.value = response.data
+    } else {
+      ElMessage.error(response.message || response.msg || '获取订单详情失败')
+      detailDialogVisible.value = false
+    }
+  } catch (error) {
+    console.error('获取订单详情失败:', error)
+    ElMessage.error(error.response?.data?.message || error.message || '获取订单详情失败')
+    detailDialogVisible.value = false
+  } finally {
+    detailLoading.value = false
+  }
+}
+
 const handleDelete = async (row) => {
   try {
     await ElMessageBox.confirm('确定要删除这个订单吗?删除后订单将不再显示。', '提示', {
@@ -374,5 +485,12 @@ onMounted(() => loadList())
 .points-order-list-container {
   padding: 0;
 }
+
+.loading-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 40px 0;
+}
 </style>
 

+ 294 - 39
marriageAdmin-vue/src/views/matchmaker/ResourceList.vue

@@ -57,6 +57,14 @@
               <el-option label="离异" :value="1" />
               <el-option label="丧偶" :value="2" />
             </el-select>
+            <el-button 
+              type="primary" 
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchAudit"
+              style="margin-left: 10px"
+            >
+              批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
+            </el-button>
             <el-button type="primary" icon="Search" @click="loadList">查询</el-button>
             <el-button icon="Refresh" @click="resetFilters">重置</el-button>
           </el-space>
@@ -66,13 +74,23 @@
     
     <!-- 列表 -->
     <el-card shadow="never" class="table-card">
-      <el-table v-loading="loading" :data="list" stripe>
+      <el-table 
+        v-loading="loading" 
+        :data="list" 
+        stripe
+        @selection-change="handleSelectionChange"
+      >
         <template #empty>
           <div class="custom-empty-state">
             <el-icon class="empty-icon"><Document /></el-icon>
             <div class="empty-text">暂无线索数据</div>
           </div>
         </template>
+        <el-table-column 
+          type="selection" 
+          width="55" 
+          :selectable="row => row.status === 0"
+        />
         <el-table-column type="index" label="序号" width="60" />
         <el-table-column prop="name" label="名称" width="120" />
         <el-table-column prop="phone" label="手机号" width="130" />
@@ -111,6 +129,7 @@
               详情
             </el-button>
             <el-button 
+              v-if="row.status === 0"
               type="success" 
               size="small" 
               link 
@@ -118,6 +137,22 @@
             >
               审核
             </el-button>
+            <el-tag
+              v-else-if="row.status === 1"
+              type="success"
+              size="small"
+              effect="light"
+            >
+              审核通过
+            </el-tag>
+            <el-tag
+              v-else-if="row.status === 2"
+              type="danger"
+              size="small"
+              effect="light"
+            >
+              审核失败
+            </el-tag>
           </template>
         </el-table-column>
       </el-table>
@@ -138,40 +173,68 @@
     <!-- 审核弹框 -->
     <el-dialog 
       v-model="auditDialogVisible" 
-      title="审核线索" 
-      width="500px"
+      :title="auditDialogTitle"
+      width="600px"
       :close-on-click-modal="false"
     >
-      <div v-if="auditData" style="margin-bottom: 20px;">
+      <!-- 批量审核进度显示 -->
+      <div v-if="isBatchAudit && batchAuditList.length > 0" class="batch-audit-progress">
+        进度:{{ currentBatchIndex }} / {{ batchAuditList.length }}
+      </div>
+      
+      <!-- 当前审核数据信息 -->
+      <div v-if="isBatchAudit && currentBatchRow" class="current-audit-info">
+        <el-descriptions :column="2" border size="small">
+          <el-descriptions-item label="名称">{{ currentBatchRow.name || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="手机号">{{ currentBatchRow.phone || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="年龄">{{ currentBatchRow.age || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="性别">
+            {{ currentBatchRow.gender === 1 ? '男' : currentBatchRow.gender === 2 ? '女' : '未知' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="住址" :span="2">{{ currentBatchRow.address || '-' }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+      
+      <div v-else-if="auditData && !isBatchAudit" style="margin-bottom: 20px;">
         <el-descriptions :column="1" border>
           <el-descriptions-item label="名称">{{ auditData.name }}</el-descriptions-item>
           <el-descriptions-item label="手机号">{{ auditData.phone }}</el-descriptions-item>
         </el-descriptions>
       </div>
-      <el-form :model="auditForm" label-width="100px">
-        <el-form-item label="审核结果" required>
+      
+      <el-form 
+        :model="auditForm" 
+        :rules="auditRules"
+        ref="auditFormRef"
+        label-width="100px"
+        style="margin-top: 20px"
+      >
+        <el-form-item label="审核结果" prop="status">
           <el-radio-group v-model="auditForm.status">
             <el-radio :label="1">审核通过</el-radio>
             <el-radio :label="2">审核失败</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item 
-          label="失败原因" 
-          v-if="auditForm.status === 2"
-        >
+        <el-form-item label="审核原因" prop="rejectReason">
           <el-input 
             v-model="auditForm.rejectReason" 
             type="textarea" 
             :rows="4"
-            placeholder="请输入审核失败原因"
-            maxlength="200"
+            placeholder="请输入审核原因(必填)"
+            maxlength="500"
             show-word-limit
           />
         </el-form-item>
       </el-form>
       <template #footer>
-        <el-button @click="auditDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="submitAudit" :loading="auditLoading">确定</el-button>
+        <el-button @click="handleCancelBatchAudit">取消</el-button>
+        <el-button 
+          type="primary" 
+          @click="submitAudit" 
+          :loading="auditLoading"
+        >
+          {{ isBatchAudit && currentBatchIndex < batchAuditList.length ? '提交并继续' : '确定' }}
+        </el-button>
       </template>
     </el-dialog>
 
@@ -212,7 +275,7 @@
             {{ getStatusText(detailData.status) }}
           </el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="审核失败原因" :span="2" v-if="detailData.rejectReason">
+        <el-descriptions-item label="审核原因" :span="2" v-if="detailData.rejectReason">
           {{ detailData.rejectReason }}
         </el-descriptions-item>
         <el-descriptions-item label="择偶标准" :span="2">
@@ -230,7 +293,7 @@
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { Document } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
@@ -240,6 +303,7 @@ const currentPage = ref(1)
 const pageSize = ref(10)
 const total = ref(0)
 const list = ref([])
+const selectedRows = ref([])
 
 const filters = reactive({
   name: '',
@@ -255,11 +319,28 @@ const detailData = ref(null)
 const auditDialogVisible = ref(false)
 const auditData = ref(null)
 const auditLoading = ref(false)
+const auditFormRef = ref(null)
+const isBatchAudit = ref(false)
+const batchAuditList = ref([])
+const currentBatchIndex = ref(1)
+const currentBatchRow = ref(null)
+const auditDialogTitle = ref('审核线索')
+
 const auditForm = reactive({
   status: 1,
   rejectReason: ''
 })
 
+const auditRules = {
+  status: [
+    { required: true, message: '请选择审核结果', trigger: 'change' }
+  ],
+  rejectReason: [
+    { required: true, message: '请输入审核原因', trigger: 'blur' },
+    { min: 1, max: 500, message: '审核原因长度在 1 到 500 个字符', trigger: 'blur' }
+  ]
+}
+
 const loadList = async () => {
   loading.value = true
   try {
@@ -345,48 +426,203 @@ const handleDetail = async (row) => {
   }
 }
 
+// 表格选择变化
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection
+}
+
+// 判断是否已审核
+const isAudited = (row) => {
+  return row.status === 1 || row.status === 2
+}
+
 const handleAudit = (row) => {
+  if (isAudited(row)) {
+    ElMessage.warning('该线索已审核,无需重复操作')
+    return
+  }
+  
   auditData.value = row
+  isBatchAudit.value = false
   auditForm.status = 1
   auditForm.rejectReason = ''
+  auditDialogTitle.value = `审核线索 - ${row.name || '该线索'}`
   auditDialogVisible.value = true
 }
 
-const submitAudit = async () => {
-  if (!auditData.value) {
+// 批量审核
+const handleBatchAudit = () => {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning('请至少选择一条记录')
     return
   }
   
-  if (auditForm.status === 2 && !auditForm.rejectReason.trim()) {
-    ElMessage.warning('请填写审核失败原因')
+  // 过滤掉已审核的记录
+  const unapprovedRows = selectedRows.value.filter(row => !isAudited(row))
+  if (unapprovedRows.length === 0) {
+    ElMessage.warning('所选记录均已审核,无需重复操作')
     return
   }
   
-  auditLoading.value = true
-  try {
-    const params = {
-      status: auditForm.status
-    }
-    if (auditForm.status === 2 && auditForm.rejectReason.trim()) {
-      params.rejectReason = auditForm.rejectReason.trim()
+  // 初始化批量审核列表
+  batchAuditList.value = unapprovedRows
+  currentBatchIndex.value = 1
+  auditData.value = null
+  isBatchAudit.value = true
+  auditForm.status = 1
+  auditForm.rejectReason = ''
+  
+  // 设置标题
+  const count = unapprovedRows.length
+  auditDialogTitle.value = count > 0 ? `批量审核 (${count}条)` : '批量审核'
+  
+  // 设置当前审核的行数据
+  updateCurrentBatchRow()
+  
+  auditDialogVisible.value = true
+}
+
+// 更新当前批量审核的行数据
+const updateCurrentBatchRow = () => {
+  if (isBatchAudit.value && batchAuditList.value.length > 0) {
+    const index = currentBatchIndex.value - 1
+    if (index >= 0 && index < batchAuditList.value.length) {
+      currentBatchRow.value = batchAuditList.value[index]
+    } else {
+      currentBatchRow.value = null
     }
+  }
+}
+
+// 取消批量审核
+const handleCancelBatchAudit = () => {
+  if (isBatchAudit.value && batchAuditList.value.length > 0) {
+    ElMessageBox.confirm(
+      `还有 ${batchAuditList.value.length - currentBatchIndex.value + 1} 条记录未审核,确定要取消吗?`,
+      '确认取消',
+      {
+        confirmButtonText: '确定取消',
+        cancelButtonText: '继续审核',
+        type: 'warning'
+      }
+    ).then(() => {
+      auditDialogVisible.value = false
+      resetBatchAudit()
+    }).catch(() => {
+      // 用户选择继续审核,不做任何操作
+    })
+  } else {
+    auditDialogVisible.value = false
+    resetBatchAudit()
+  }
+}
+
+// 重置批量审核状态
+const resetBatchAudit = () => {
+  batchAuditList.value = []
+  currentBatchIndex.value = 1
+  currentBatchRow.value = null
+  isBatchAudit.value = false
+  auditDialogTitle.value = '审核线索'
+  auditForm.status = 1
+  auditForm.rejectReason = ''
+}
+
+const submitAudit = async () => {
+  if (!auditFormRef.value) return
+  
+  try {
+    await auditFormRef.value.validate()
     
-    const response = await request.put(
-      `${API_ENDPOINTS.MY_RESOURCE_AUDIT}/${auditData.value.resourceId}/audit`,
-      null,
-      { params }
-    )
+    auditLoading.value = true
     
-    if (response.code === 200) {
-      ElMessage.success(response.message || response.msg || '审核成功')
-      auditDialogVisible.value = false
-      loadList()
+    if (isBatchAudit.value) {
+      // 批量审核 - 逐条审核
+      const row = currentBatchRow.value
+      if (!row) {
+        ElMessage.error('当前审核数据不存在')
+        return
+      }
+      
+      const params = {
+        status: auditForm.status,
+        rejectReason: auditForm.rejectReason.trim()
+      }
+      
+      const response = await request.put(
+        `${API_ENDPOINTS.MY_RESOURCE_AUDIT}/${row.resourceId}/audit`,
+        null,
+        { params }
+      )
+      
+      if (response.code === 200) {
+        ElMessage.success(
+          `${row.name || '该线索'} ${auditForm.status === 1 ? '审核通过' : '审核失败'}成功`
+        )
+        
+        // 从批量审核列表中移除已审核的项
+        const index = currentBatchIndex.value - 1
+        batchAuditList.value.splice(index, 1)
+        
+        // 如果还有未审核的记录,自动跳转到下一条
+        if (batchAuditList.value.length > 0) {
+          // 删除后,如果当前索引超出范围(说明删除的是最后一条),则跳转到新的最后一条
+          // 否则保持当前索引不变,因为后面的记录会前移,当前索引对应的就是下一条
+          if (currentBatchIndex.value > batchAuditList.value.length) {
+            currentBatchIndex.value = batchAuditList.value.length
+          }
+          // 更新标题
+          const count = batchAuditList.value.length
+          auditDialogTitle.value = count > 0 ? `批量审核 (${count}条)` : '批量审核'
+          updateCurrentBatchRow()
+          // 重置表单
+          auditForm.status = 1
+          auditForm.rejectReason = ''
+          // 清除表单验证状态
+          if (auditFormRef.value) {
+            auditFormRef.value.clearValidate()
+          }
+        } else {
+          // 所有记录都已审核完成
+          ElMessage.success('批量审核完成')
+          auditDialogVisible.value = false
+          selectedRows.value = []
+          resetBatchAudit()
+          loadList()
+        }
+      } else {
+        ElMessage.error(response.message || response.msg || '审核失败')
+      }
     } else {
-      ElMessage.error(response.message || response.msg || '审核失败')
+      // 单个审核
+      if (!auditData.value) {
+        return
+      }
+      
+      const params = {
+        status: auditForm.status,
+        rejectReason: auditForm.rejectReason.trim()
+      }
+      
+      const response = await request.put(
+        `${API_ENDPOINTS.MY_RESOURCE_AUDIT}/${auditData.value.resourceId}/audit`,
+        null,
+        { params }
+      )
+      
+      if (response.code === 200) {
+        ElMessage.success(response.message || response.msg || '审核成功')
+        auditDialogVisible.value = false
+        loadList()
+      } else {
+        ElMessage.error(response.message || response.msg || '审核失败')
+      }
     }
   } catch (error) {
-    console.error('审核失败:', error)
-    ElMessage.error('审核失败: ' + (error.message || '未知错误'))
+    if (error !== false) { // validate 失败会返回 false
+      console.error('审核失败:', error)
+      ElMessage.error(error.response?.data?.message || error.message || '审核失败')
+    }
   } finally {
     auditLoading.value = false
   }
@@ -401,5 +637,24 @@ onMounted(() => loadList())
 .resource-list-container {
   padding: 0;
 }
+
+.batch-audit-progress {
+  font-size: 14px;
+  color: #606266;
+  font-weight: 500;
+  margin-bottom: 20px;
+  padding: 10px 15px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  text-align: center;
+}
+
+.current-audit-info {
+  margin-bottom: 20px;
+}
+
+.current-audit-info :deep(.el-descriptions__label) {
+  font-weight: 500;
+}
 </style>
 

+ 188 - 23
marriageAdmin-vue/src/views/points-product/PointsProductList.vue

@@ -103,10 +103,19 @@
         
         <el-table-column prop="sortOrder" label="排序" width="80" align="center" />
         
-        <el-table-column label="操作" width="220" fixed="right">
+        <el-table-column label="操作" width="280" fixed="right">
           <template #default="{ row }">
             <el-space>
-              <el-button type="primary" size="small" @click="showEditDialog(row)">
+              <el-button
+                type="info"
+                size="small"
+                link
+                @click="handleViewDetail(row)"
+              >
+                <el-icon><View /></el-icon>
+                详情
+              </el-button>
+              <el-button type="primary" size="small" link @click="showEditDialog(row)">
                 <el-icon><Edit /></el-icon>
                 编辑
               </el-button>
@@ -114,19 +123,23 @@
                 v-if="row.status === 0"
                 type="success"
                 size="small"
+                link
                 @click="handleOnShelf(row)"
               >
+                <el-icon><Top /></el-icon>
                 上架
               </el-button>
               <el-button
                 v-if="row.status === 1"
                 type="warning"
                 size="small"
+                link
                 @click="handleOffShelf(row)"
               >
+                <el-icon><Bottom /></el-icon>
                 下架
               </el-button>
-              <el-button type="danger" size="small" @click="handleDelete(row)">
+              <el-button type="danger" size="small" link @click="handleDelete(row)">
                 <el-icon><Delete /></el-icon>
                 删除
               </el-button>
@@ -168,12 +181,10 @@
         <el-form-item label="商品图片" prop="imageUrl">
           <el-upload
             class="image-uploader"
-            :action="uploadAction"
-            :headers="uploadHeaders"
+            action="#"
             :show-file-list="false"
-            :on-success="handleImageSuccess"
-            :before-upload="beforeImageUpload"
-            accept="image/*"
+            :before-upload="handleBeforeUpload"
+            :http-request="handleUpload"
           >
             <el-image
               v-if="form.imageUrl"
@@ -234,14 +245,109 @@
         <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
       </template>
     </el-dialog>
+    
+    <!-- 详情对话框 -->
+    <el-dialog
+      v-model="detailDialogVisible"
+      title="商品详情"
+      width="800px"
+      :close-on-click-modal="false"
+    >
+      <div v-if="detailData" class="detail-content">
+        <!-- 基本信息 -->
+        <el-card shadow="never" class="detail-section">
+          <template #header>
+            <div class="section-header">
+              <el-icon><InfoFilled /></el-icon>
+              <span>基本信息</span>
+            </div>
+          </template>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="商品ID">
+              {{ detailData.id || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="商品名称">
+              {{ detailData.name || '-' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="商品描述" :span="2">
+              <div class="description-content">
+                {{ detailData.description || '-' }}
+              </div>
+            </el-descriptions-item>
+            <el-descriptions-item label="商品图片" :span="2">
+              <el-image
+                v-if="detailData.imageUrl"
+                :src="detailData.imageUrl"
+                fit="cover"
+                style="width: 200px; height: 200px; border-radius: 6px;"
+                :preview-src-list="[detailData.imageUrl]"
+              />
+              <span v-else style="color: #999;">暂无图片</span>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+
+        <!-- 价格与库存 -->
+        <el-card shadow="never" class="detail-section">
+          <template #header>
+            <div class="section-header">
+              <el-icon><Money /></el-icon>
+              <span>价格与库存</span>
+            </div>
+          </template>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="积分价格">
+              <span style="color: #E91E63; font-weight: bold; font-size: 16px;">
+                {{ detailData.pointsPrice || 0 }}
+              </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="库存">
+              <el-tag :type="detailData.stock > 0 ? 'success' : 'danger'" size="small">
+                {{ detailData.stock || 0 }}
+              </el-tag>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+
+        <!-- 商品属性 -->
+        <el-card shadow="never" class="detail-section">
+          <template #header>
+            <div class="section-header">
+              <el-icon><Setting /></el-icon>
+              <span>商品属性</span>
+            </div>
+          </template>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="商品类型">
+              <el-tag :type="detailData.category === 1 ? 'primary' : 'warning'" size="small">
+                {{ detailData.category === 1 ? '实物' : '虚拟' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="是否推荐">
+              <el-tag v-if="detailData.isRecommend === 1" type="danger" size="small">推荐</el-tag>
+              <span v-else style="color: #999;">否</span>
+            </el-descriptions-item>
+            <el-descriptions-item label="状态">
+              <el-tag :type="detailData.status === 1 ? 'success' : 'info'" size="small">
+                {{ detailData.status === 1 ? '上架' : '下架' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="排序">
+              {{ detailData.sortOrder || 0 }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Plus, Search, Refresh, Edit, Delete } from '@element-plus/icons-vue'
+import { Plus, Search, Refresh, Edit, Delete, InfoFilled, Money, Setting, View, Top, Bottom } from '@element-plus/icons-vue'
 import request from '@/utils/request'
+import { API_ENDPOINTS } from '@/config/api'
 
 const loading = ref(false)
 const dialogVisible = ref(false)
@@ -252,6 +358,8 @@ const currentPage = ref(1)
 const pageSize = ref(10)
 const total = ref(0)
 const list = ref([])
+const detailDialogVisible = ref(false)
+const detailData = ref(null)
 
 const filters = reactive({
   status: null,
@@ -278,13 +386,6 @@ const rules = {
   category: [{ required: true, message: '请选择商品类型', trigger: 'change' }]
 }
 
-// 上传配置
-const uploadAction = ref('/admin/upload/image')
-const uploadHeaders = ref({
-  // 如果需要token,可以从localStorage获取
-  // Authorization: 'Bearer ' + localStorage.getItem('token')
-})
-
 const loadList = async () => {
   loading.value = true
   try {
@@ -407,6 +508,12 @@ const handleOffShelf = async (row) => {
   }
 }
 
+// 查看详情
+const handleViewDetail = (row) => {
+  detailData.value = { ...row }
+  detailDialogVisible.value = true
+}
+
 const handleDelete = async (row) => {
   try {
     await ElMessageBox.confirm('确定要删除这个商品吗?', '提示', {
@@ -430,7 +537,7 @@ const handleDelete = async (row) => {
 }
 
 // 图片上传相关
-const beforeImageUpload = (file) => {
+const handleBeforeUpload = (file) => {
   const isImage = file.type.startsWith('image/')
   const isLt10M = file.size / 1024 / 1024 < 10
 
@@ -445,12 +552,37 @@ const beforeImageUpload = (file) => {
   return true
 }
 
-const handleImageSuccess = (response) => {
-  if (response.code === 200 && response.data && response.data.url) {
-    form.imageUrl = response.data.url
-    ElMessage.success('图片上传成功')
-  } else {
-    ElMessage.error('图片上传失败')
+// 上传图片
+const handleUpload = async (options) => {
+  const formData = new FormData()
+  formData.append('file', options.file)
+  
+  try {
+    const response = await request.post(API_ENDPOINTS.UPLOAD_IMAGE, formData, {
+      headers: { 'Content-Type': 'multipart/form-data' }
+    })
+    
+    if (response.code === 200) {
+      // 处理不同的返回格式
+      let imageUrl = ''
+      if (typeof response.data === 'string') {
+        imageUrl = response.data
+      } else if (response.data && response.data.url) {
+        imageUrl = response.data.url
+      } else if (response.data && response.data.path) {
+        imageUrl = response.data.path
+      } else {
+        imageUrl = String(response.data || '')
+      }
+      
+      form.imageUrl = imageUrl
+      ElMessage.success('图片上传成功')
+    } else {
+      ElMessage.error(response.message || '图片上传失败')
+    }
+  } catch (error) {
+    console.error('上传异常:', error)
+    ElMessage.error('图片上传失败,请重试')
   }
 }
 
@@ -521,5 +653,38 @@ onMounted(() => loadList())
   color: #999;
   margin-top: 8px;
 }
+
+/* 详情对话框样式 */
+.detail-content {
+  padding: 0;
+}
+
+.detail-section {
+  margin-bottom: 20px;
+}
+
+.detail-section:last-child {
+  margin-bottom: 0;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 600;
+  color: #333;
+}
+
+.section-header .el-icon {
+  font-size: 18px;
+  color: #409eff;
+}
+
+.description-content {
+  white-space: pre-wrap;
+  word-break: break-word;
+  line-height: 1.6;
+  color: #666;
+}
 </style>
 

+ 272 - 59
marriageAdmin-vue/src/views/report/ReportList.vue

@@ -93,30 +93,51 @@
     </el-card>
 
     <!-- 动态详情 -->
-    <el-dialog v-model="dynamicDialog.visible" title="动态详情" width="720px">
+    <el-dialog v-model="dynamicDialog.visible" title="动态详情" width="800px">
       <div v-if="dynamicDialog.loading" class="loading">加载中...</div>
-      <div v-else>
-        <div class="dynamic-content">{{ dynamicDialog.data?.content || '(无内容)' }}</div>
-        <div class="media-list">
-          <el-image 
-            v-for="(m, idx) in dynamicDialog.medias" 
-            :key="idx" 
-            :src="m" 
-            :preview-src-list="dynamicDialog.medias" 
-            fit="cover" 
-            style="width:96px;height:96px;border-radius:6px;margin-right:8px;margin-bottom:8px;cursor:pointer;"
-            :lazy="true"
-            @error="handleImageError"
-          >
-            <template #error>
-              <div class="img-error" style="width:96px;height:96px;">
-                <el-icon><Picture /></el-icon>
-                <span>加载失败</span>
-              </div>
-            </template>
-          </el-image>
+      <div v-else-if="dynamicDialog.data">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="动态ID">{{ dynamicDialog.data.dynamicId || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="作者昵称">
+            {{ dynamicDialog.data.userNickname || `用户${dynamicDialog.data.userId || ''}` }}
+          </el-descriptions-item>
+          <el-descriptions-item label="作者ID">{{ dynamicDialog.data.userId || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ dynamicDialog.data.createTime || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="点赞数">{{ dynamicDialog.data.likeCount || 0 }}</el-descriptions-item>
+          <el-descriptions-item label="评论数">{{ dynamicDialog.data.commentCount || 0 }}</el-descriptions-item>
+          <el-descriptions-item label="审核状态" :span="2">
+            <el-tag :type="getAuditStatusType(dynamicDialog.data.auditStatus)" size="small">
+              {{ getAuditStatusText(dynamicDialog.data.auditStatus) }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="动态内容" :span="2">
+            <div class="dynamic-content-text">{{ dynamicDialog.data.content || '(无内容)' }}</div>
+          </el-descriptions-item>
+        </el-descriptions>
+        <div v-if="dynamicDialog.medias && dynamicDialog.medias.length > 0" class="media-section">
+          <div class="media-title">媒体文件({{ dynamicDialog.medias.length }}张)</div>
+          <div class="media-list">
+            <el-image 
+              v-for="(m, idx) in dynamicDialog.medias" 
+              :key="idx" 
+              :src="m" 
+              :preview-src-list="dynamicDialog.medias" 
+              fit="cover" 
+              style="width:96px;height:96px;border-radius:6px;margin-right:8px;margin-bottom:8px;cursor:pointer;"
+              :lazy="true"
+              @error="handleImageError"
+            >
+              <template #error>
+                <div class="img-error" style="width:96px;height:96px;">
+                  <el-icon><Picture /></el-icon>
+                  <span>加载失败</span>
+                </div>
+              </template>
+            </el-image>
+          </div>
         </div>
       </div>
+      <div v-else class="error-message">动态不存在或已被删除</div>
     </el-dialog>
 
     <!-- 处理举报 -->
@@ -145,18 +166,51 @@
     </el-dialog>
 
     <!-- 管理操作(封禁/删除) -->
-    <el-dialog v-model="moderateDialog.visible" :title="moderateDialog.action===2?'封禁动态':'删除动态'" width="520px">
-      <el-form :model="moderateDialog.form" label-width="88px">
-        <el-form-item label="动态ID">
-          <span>{{ moderateDialog.form.dynamicId }}</span>
-        </el-form-item>
-        <el-form-item label="处理原因" required>
-          <el-input v-model="moderateDialog.form.reason" type="textarea" :rows="4" placeholder="请填写具体原因" />
-        </el-form-item>
-      </el-form>
+    <el-dialog v-model="moderateDialog.visible" :title="moderateDialog.action===2?'封禁动态':moderateDialog.action===3?'删除动态':'取消封禁'" width="720px">
+      <div v-if="moderateDialog.loading" class="loading">加载中...</div>
+      <div v-else>
+        <el-descriptions :column="2" border style="margin-bottom: 20px;">
+          <el-descriptions-item label="动态ID">{{ moderateDialog.form.dynamicId || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="作者昵称">
+            {{ moderateDialog.form.userNickname || `用户${moderateDialog.form.userId || ''}` }}
+          </el-descriptions-item>
+          <el-descriptions-item label="作者ID">{{ moderateDialog.form.userId || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ moderateDialog.form.createTime || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="点赞数">{{ moderateDialog.form.likeCount || 0 }}</el-descriptions-item>
+          <el-descriptions-item label="评论数">{{ moderateDialog.form.commentCount || 0 }}</el-descriptions-item>
+          <el-descriptions-item label="审核状态" :span="2">
+            <el-tag :type="getAuditStatusType(moderateDialog.form.auditStatus)" size="small">
+              {{ getAuditStatusText(moderateDialog.form.auditStatus) }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="动态内容" :span="2">
+            <div class="dynamic-content-text">{{ moderateDialog.form.content || '(无内容)' }}</div>
+          </el-descriptions-item>
+        </el-descriptions>
+        
+        <div v-if="moderateDialog.form.reportInfo" class="report-info-section">
+          <el-divider content-position="left">相关举报信息</el-divider>
+          <el-descriptions :column="1" border size="small">
+            <el-descriptions-item label="举报类型">
+              <el-tag size="small" :type="typeTagType(moderateDialog.form.reportInfo.reportType)">
+                {{ typeText(moderateDialog.form.reportInfo.reportType) }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="举报描述">{{ moderateDialog.form.reportInfo.description || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="举报人">{{ moderateDialog.form.reportInfo.reporterName || `用户${moderateDialog.form.reportInfo.reporterId || ''}` }}</el-descriptions-item>
+            <el-descriptions-item label="举报时间">{{ moderateDialog.form.reportInfo.createdAt || '-' }}</el-descriptions-item>
+          </el-descriptions>
+        </div>
+        
+        <el-form :model="moderateDialog.form" label-width="100px" style="margin-top: 20px;">
+          <el-form-item label="处理原因" required>
+            <el-input v-model="moderateDialog.form.reason" type="textarea" :rows="4" placeholder="请填写具体原因" />
+          </el-form-item>
+        </el-form>
+      </div>
       <template #footer>
         <el-button @click="moderateDialog.visible=false">取消</el-button>
-        <el-button type="primary" :loading="moderateDialog.loading" @click="submitModerate">确定</el-button>
+        <el-button type="primary" :loading="moderateDialog.loading" @click="submitModerate" :disabled="!moderateDialog.form.reason?.trim()">确定</el-button>
       </template>
     </el-dialog>
   </div>
@@ -188,6 +242,14 @@ const statusText = (s) => ({0:'待处理',1:'处理中',2:'已处理',3:'已驳
 const statusType = (s) => ({0:'danger',1:'warning',2:'success',3:'info'}[s] || '')
 const typeText = (t) => ({ spam:'垃圾广告', porn:'色情低俗', violence:'暴力违法', attack:'人身攻击', fake:'虚假信息', plagiarism:'抄袭侵权', other:'其他' }[t] || t || '其他')
 const typeTagType = (t) => ({ spam:'warning', porn:'danger', violence:'danger', attack:'danger', fake:'warning', plagiarism:'warning', other:'info' }[t] || 'info')
+const getAuditStatusText = (status) => {
+  const texts = { 0: '待审核', 1: '已通过', 2: '未通过' }
+  return texts[status] || '未知'
+}
+const getAuditStatusType = (status) => {
+  const types = { 0: 'warning', 1: 'success', 2: 'danger' }
+  return types[status] || ''
+}
 
 // 获取基础URL(用于图片资源)
 const getBaseUrl = () => {
@@ -361,33 +423,49 @@ const parseMediaUrls = (raw) => {
   }
 }
 const openDynamic = async (row) => {
-  const dynamicId = row?.dynamicId
-  if (!dynamicId) {
-    dynamicDialog.data = { content: '未获取到动态ID' }
+  const reportId = row?.reportId
+  if (!reportId) {
+    dynamicDialog.data = { content: '未获取到举报ID' }
     dynamicDialog.medias = getScreenshotUrls(row)
     dynamicDialog.visible = true
     return
   }
-      dynamicDialog.visible = true
-      dynamicDialog.loading = true
-      dynamicDialog.data = null
-      dynamicDialog.medias = []
-      try {
-        const res = await request.get(`${API_ENDPOINTS.DYNAMIC_DETAIL}/${dynamicId}`)
-        if (res.code === 200 && res.data) {
-          dynamicDialog.data = res.data
-          const medias = parseMediaUrls(res.data?.mediaUrls)
-          dynamicDialog.medias = medias.length ? medias.map(formatMedia) : getScreenshotUrls(row)
-        } else {
-          dynamicDialog.data = { content: res.msg || '动态不存在或已被删除' }
-          dynamicDialog.medias = getScreenshotUrls(row)
-        }
-      } catch (e) {
-        console.error('获取动态详情失败', e)
-        dynamicDialog.data = { content: e?.message ? `加载失败:${e.message}` : '加载失败' }
-        dynamicDialog.medias = getScreenshotUrls(row)
-      }
-      finally { dynamicDialog.loading = false }
+  dynamicDialog.visible = true
+  dynamicDialog.loading = true
+  dynamicDialog.data = null
+  dynamicDialog.medias = []
+  try {
+    // 通过 reportId 获取举报详情(包含动态详情)
+    const reportRes = await request.get(`${API_ENDPOINTS.REPORT_DETAIL}/${reportId}`)
+    if (reportRes.code !== 200 || !reportRes.data) {
+      dynamicDialog.data = { content: reportRes.msg || '举报不存在' }
+      dynamicDialog.medias = getScreenshotUrls(row)
+      dynamicDialog.loading = false
+      return
+    }
+    
+    const report = reportRes.data
+    const dynamic = report.dynamic
+    
+    if (!dynamic) {
+      dynamicDialog.data = { content: '动态不存在或已被删除' }
+      dynamicDialog.medias = getScreenshotUrls(row)
+      dynamicDialog.loading = false
+      return
+    }
+    
+    // 直接使用举报详情接口返回的动态信息
+    dynamicDialog.data = dynamic
+    // 使用 screenshots 字段作为展示图片
+    const screens = parseScreens(dynamic?.screenshots)
+    dynamicDialog.medias = screens.length ? screens.map(formatMedia) : getScreenshotUrls(row)
+  } catch (e) {
+    console.error('获取动态详情失败', e)
+    dynamicDialog.data = { content: e?.message ? `加载失败:${e.message}` : '加载失败' }
+    dynamicDialog.medias = getScreenshotUrls(row)
+  } finally { 
+    dynamicDialog.loading = false 
+  }
 }
 
 // 处理举报
@@ -419,12 +497,85 @@ const submitHandle = async () => {
 onMounted(loadList)
 
 // 管理操作:封禁/删除
-const moderateDialog = reactive({ visible:false, loading:false, action:2, form:{ dynamicId:null, reason:'' } })
-const openModerate = (row, action) => {
+const moderateDialog = reactive({ 
+  visible: false, 
+  loading: false, 
+  action: 2, 
+  form: { 
+    dynamicId: null, 
+    userId: null,
+    userNickname: null,
+    content: null,
+    createTime: null,
+    likeCount: 0,
+    commentCount: 0,
+    auditStatus: null,
+    reportInfo: null,
+    reason: '' 
+  } 
+})
+const openModerate = async (row, action) => {
   moderateDialog.visible = true
   moderateDialog.action = action
-  moderateDialog.form.dynamicId = row.dynamicId
+  moderateDialog.loading = true
   moderateDialog.form.reason = ''
+  // 重置其他字段
+  moderateDialog.form.dynamicId = null
+  moderateDialog.form.userId = null
+  moderateDialog.form.userNickname = null
+  moderateDialog.form.content = null
+  moderateDialog.form.createTime = null
+  moderateDialog.form.likeCount = 0
+  moderateDialog.form.commentCount = 0
+  moderateDialog.form.auditStatus = null
+  moderateDialog.form.reportInfo = null
+  
+  const reportId = row?.reportId
+  if (!reportId) {
+    moderateDialog.loading = false
+    ElMessage.error('未获取到举报ID')
+    return
+  }
+  
+  try {
+    // 先通过 reportId 获取举报详情
+    const reportRes = await request.get(`${API_ENDPOINTS.REPORT_DETAIL}/${reportId}`)
+    if (reportRes.code !== 200 || !reportRes.data) {
+      ElMessage.error(reportRes.msg || '举报不存在')
+      moderateDialog.loading = false
+      return
+    }
+    
+    const report = reportRes.data
+    const dynamic = report.dynamic
+    
+    // 加载举报信息
+    moderateDialog.form.reportInfo = {
+      reportId: report.reportId,
+      reportType: report.reportType,
+      description: report.description,
+      reporterId: report.reporterId,
+      reporterName: report.reporterName || row.reporterName,
+      createdAt: report.createdAt || row.createdAt
+    }
+    
+    // 加载动态详情(从举报详情接口返回的 dynamic 字段中获取)
+    if (dynamic) {
+      moderateDialog.form.dynamicId = dynamic.dynamicId
+      moderateDialog.form.userId = dynamic.userId
+      moderateDialog.form.userNickname = dynamic.userNickname
+      moderateDialog.form.content = dynamic.content
+      moderateDialog.form.createTime = dynamic.createTime
+      moderateDialog.form.likeCount = dynamic.likeCount || 0
+      moderateDialog.form.commentCount = dynamic.commentCount || 0
+      moderateDialog.form.auditStatus = dynamic.auditStatus
+    }
+  } catch (e) {
+    console.error('加载举报详情失败', e)
+    ElMessage.error('加载举报详情失败')
+  } finally {
+    moderateDialog.loading = false
+  }
 }
 const submitModerate = async () => {
   if (!moderateDialog.form.reason.trim()) return ElMessage.error('请填写处理原因')
@@ -442,11 +593,41 @@ const submitModerate = async () => {
   finally { moderateDialog.loading = false }
 }
 
-const openUnban = (row) => {
+const openUnban = async (row) => {
   moderateDialog.visible = true
   moderateDialog.action = 1
+  moderateDialog.loading = true
   moderateDialog.form.dynamicId = row.dynamicId
   moderateDialog.form.reason = ''
+  // 重置其他字段
+  moderateDialog.form.userId = null
+  moderateDialog.form.userNickname = null
+  moderateDialog.form.content = null
+  moderateDialog.form.createTime = null
+  moderateDialog.form.likeCount = 0
+  moderateDialog.form.commentCount = 0
+  moderateDialog.form.auditStatus = null
+  moderateDialog.form.reportInfo = null
+  
+  // 加载动态详情
+  if (row.dynamicId) {
+    try {
+      const res = await request.get(`${API_ENDPOINTS.DYNAMIC_DETAIL}/${row.dynamicId}`)
+      if (res.code === 200 && res.data) {
+        moderateDialog.form.userId = res.data.userId
+        moderateDialog.form.userNickname = res.data.userNickname
+        moderateDialog.form.content = res.data.content
+        moderateDialog.form.createTime = res.data.createTime
+        moderateDialog.form.likeCount = res.data.likeCount || 0
+        moderateDialog.form.commentCount = res.data.commentCount || 0
+        moderateDialog.form.auditStatus = res.data.auditStatus
+      }
+    } catch (e) {
+      console.error('加载动态详情失败', e)
+    }
+  }
+  
+  moderateDialog.loading = false
 }
 </script>
 
@@ -460,8 +641,40 @@ const openUnban = (row) => {
 .img-error { width:64px;height:64px;border-radius:6px;background:#fef0f0;color:#f56c6c;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:10px; }
 .img-error .el-icon { font-size:20px;margin-bottom:2px; }
 .dynamic-content { margin-bottom: 10px; color:#333; }
+.dynamic-content-text { 
+  max-height: 200px; 
+  overflow-y: auto; 
+  padding: 8px; 
+  background: #f5f7fa; 
+  border-radius: 4px; 
+  line-height: 1.6; 
+  word-break: break-word;
+  white-space: pre-wrap;
+}
+.media-section { margin-top: 20px; }
+.media-title { 
+  font-size: 14px; 
+  font-weight: 600; 
+  color: #606266; 
+  margin-bottom: 12px; 
+}
 .media-list { display:flex; flex-wrap: wrap; }
-.loading { color:#909399; }
+.loading { 
+  color:#909399; 
+  text-align: center; 
+  padding: 40px 0; 
+}
+.error-message { 
+  color: #f56c6c; 
+  text-align: center; 
+  padding: 40px 0; 
+}
+.report-info-section { 
+  margin-top: 20px; 
+  padding: 16px; 
+  background: #f9fafc; 
+  border-radius: 4px; 
+}
 </style>
 
 

+ 1 - 0
pom.xml

@@ -108,6 +108,7 @@
           <configuration>
             <source>8</source>
             <target>8</target>
+            <parameters>true</parameters>
           </configuration>
         </plugin>
       </plugins>

+ 6 - 0
service/Recommend/pom.xml

@@ -43,6 +43,12 @@
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-commons</artifactId>
         </dependency>
+        
+        <!-- Spring Security (用于配置允许所有请求) -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 33 - 0
service/Recommend/src/main/java/com/zhentao/config/SecurityConfig.java

@@ -0,0 +1,33 @@
+package com.zhentao.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+
+/**
+ * Security配置 - 允许所有请求通过(Recommend服务不需要认证)
+ */
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+    
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http
+            // 禁用CSRF
+            .csrf().disable()
+            // 设置Session为无状态
+            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+            .and()
+            // 允许所有请求
+            .authorizeRequests(auth -> auth
+                .anyRequest().permitAll()
+            );
+        
+        return http.build();
+    }
+}
+

+ 14 - 14
service/Recommend/src/main/java/com/zhentao/controller/RecommendController.java

@@ -30,11 +30,11 @@ public class RecommendController {
 
     @GetMapping("/users")
     public Result<List<RecommendUserVO>> getRecommendedUsers(
-            @RequestParam Integer userId,
-            @RequestParam(required = false, defaultValue = "1") Integer oppoOnly,
-            @RequestParam(required = false, defaultValue = "50") Integer limit,
-            @RequestParam(required = false, defaultValue = "0") Integer offset,
-            @RequestParam(required = false) String excludeIds
+            @RequestParam("userId") Integer userId,
+            @RequestParam(value = "oppoOnly", required = false, defaultValue = "1") Integer oppoOnly,
+            @RequestParam(value = "limit", required = false, defaultValue = "50") Integer limit,
+            @RequestParam(value = "offset", required = false, defaultValue = "0") Integer offset,
+            @RequestParam(value = "excludeIds", required = false) String excludeIds
     ) {
         try {
             // 强制只推荐异性,不允许推荐同性
@@ -86,9 +86,9 @@ public class RecommendController {
     // 用户行为反馈:喜欢/不喜欢
     @RequestMapping(value = "/feedback", method = {RequestMethod.GET, RequestMethod.POST})
     public Result<String> feedback(
-            @RequestParam Integer userId,
-            @RequestParam Integer targetUserId,
-            @RequestParam String type // like | dislike
+            @RequestParam("userId") Integer userId,
+            @RequestParam("targetUserId") Integer targetUserId,
+            @RequestParam("type") String type // like | dislike
     ) {
         try {
             String likeKey = likeKey(userId);
@@ -118,8 +118,8 @@ public class RecommendController {
     // 曝光上报:便于重排/多样性控制
     @RequestMapping(value = "/exposure", method = {RequestMethod.GET, RequestMethod.POST})
     public Result<String> exposure(
-            @RequestParam Integer userId,
-            @RequestParam String shownUserIds // 逗号分隔
+            @RequestParam("userId") Integer userId,
+            @RequestParam("shownUserIds") String shownUserIds // 逗号分隔
     ) {
         System.out.println("exposure: user=" + userId + ", shown=" + shownUserIds);
         return Result.success("ok", "ok");
@@ -128,8 +128,8 @@ public class RecommendController {
     // 查询双方关系:like / dislike / none
     @GetMapping("/relation")
     public Result<Map<String, Object>> relation(
-            @RequestParam Integer userId,
-            @RequestParam Integer targetUserId
+            @RequestParam("userId") Integer userId,
+            @RequestParam("targetUserId") Integer targetUserId
     ) {
         try {
             boolean liked = Boolean.TRUE.equals(
@@ -201,7 +201,7 @@ public class RecommendController {
     }
 
     @GetMapping("/area/cities")
-    public Result<java.util.List<java.util.Map<String,Object>>> cities(@RequestParam(required = false) Integer provinceId) {
+    public Result<java.util.List<java.util.Map<String,Object>>> cities(@RequestParam(value = "provinceId", required = false) Integer provinceId) {
         try {
             java.util.List<com.zhentao.pojo.City> cities;
             if (provinceId != null) {
@@ -228,7 +228,7 @@ public class RecommendController {
     }
 
     @GetMapping("/area/areas")
-    public Result<java.util.List<java.util.Map<String,Object>>> areas(@RequestParam Integer cityId) {
+    public Result<java.util.List<java.util.Map<String,Object>>> areas(@RequestParam("cityId") Integer cityId) {
         try {
             if (cityId == null) {
                 return Result.error("cityId参数不能为空");

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

@@ -100,9 +100,9 @@ public class AdminUserController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) String keyword) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "keyword", required = false) String keyword) {
         try {
             com.baomidou.mybatisplus.extension.plugins.pagination.Page<AdminUser> result = userService.getUserList(page, pageSize, keyword);
             

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

@@ -24,9 +24,9 @@ public class AnnouncementController {
      */
     @GetMapping("/list")
     public Result getAnnouncementList(
-            @RequestParam(defaultValue = "1") int current,
-            @RequestParam(defaultValue = "10") int size,
-            @RequestParam(required = false) Integer status) {
+            @RequestParam(value = "current", defaultValue = "1") int current,
+            @RequestParam(value = "size", defaultValue = "10") int size,
+            @RequestParam(value = "status", required = false) Integer status) {
         Page<Announcement> page = announcementService.getAnnouncementList(current, size, status);
         return Result.success(page);
     }

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

@@ -36,9 +36,9 @@ public class BannerController {
      */
     @GetMapping("/list")
     public Result<Page<Banner>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) String keyword) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "keyword", required = false) String keyword) {
         try {
             Page<Banner> bannerPage = bannerService.getBannerList(page, pageSize, keyword);
             return Result.success(bannerPage);

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

@@ -135,7 +135,7 @@ public class DashboardController {
      */
     @GetMapping("/recent-activities")
     public Result<List<Activity>> getRecentActivities(
-            @RequestParam(defaultValue = "10") Integer limit,
+            @RequestParam(value = "limit", defaultValue = "10") Integer limit,
             @RequestHeader(value = "Authorization", required = false) String token) {
         // 检查是否是超级管理员
         if (!checkSuperAdmin(token)) {
@@ -183,7 +183,7 @@ public class DashboardController {
      */
     @GetMapping("/user-trend")
     public Result<Map<String, Object>> getUserTrend(
-            @RequestParam(defaultValue = "7") Integer days,
+            @RequestParam(value = "days", defaultValue = "7") Integer days,
             @RequestHeader(value = "Authorization", required = false) String token) {
         // 检查是否是超级管理员
         if (!checkSuperAdmin(token)) {
@@ -231,7 +231,7 @@ public class DashboardController {
      */
     @GetMapping("/activity-stats")
     public Result<Map<String, Object>> getActivityStats(
-            @RequestParam(defaultValue = "7") Integer days,
+            @RequestParam(value = "days", defaultValue = "7") Integer days,
             @RequestHeader(value = "Authorization", required = false) String token) {
         // 检查是否是超级管理员
         if (!checkSuperAdmin(token)) {

+ 22 - 4
service/admin/src/main/java/com/zhentao/controller/DynamicController.java

@@ -32,10 +32,10 @@ public class DynamicController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) Integer auditStatus,
-            @RequestParam(required = false, defaultValue = "false") Boolean reportedOnly) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "auditStatus", required = false) Integer auditStatus,
+            @RequestParam(value = "reportedOnly", required = false, defaultValue = "false") Boolean reportedOnly) {
         
         try {
             Page<Dynamic> pageInfo = new Page<>(page, pageSize);
@@ -153,6 +153,24 @@ public class DynamicController {
             if (dynamic == null) {
                 return Result.error("动态不存在");
             }
+            
+            // 查询用户昵称并回填
+            try {
+                if (dynamic.getUserId() != null) {
+                    java.util.List<Integer> userIds = java.util.Collections.singletonList(dynamic.getUserId());
+                    java.util.List<java.util.Map<String, Object>> rows = dynamicMapper.findNicknamesByUserIds(userIds);
+                    if (rows != null && !rows.isEmpty()) {
+                        java.util.Map<String, Object> row = rows.get(0);
+                        Object nick = row.get("nickname");
+                        if (nick != null) {
+                            dynamic.setUserNickname(nick.toString());
+                        }
+                    }
+                }
+            } catch (Exception ex) {
+                System.err.println("查询用户昵称异常: " + ex.getMessage());
+            }
+            
             return Result.success(dynamic);
         } catch (Exception e) {
             e.printStackTrace();

+ 20 - 4
service/admin/src/main/java/com/zhentao/controller/DynamicReportController.java

@@ -3,7 +3,9 @@ package com.zhentao.controller;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zhentao.common.Result;
+import com.zhentao.entity.Dynamic;
 import com.zhentao.entity.DynamicReport;
+import com.zhentao.mapper.DynamicMapper;
 import com.zhentao.mapper.DynamicReportMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -22,13 +24,16 @@ public class DynamicReportController {
     @Autowired
     private DynamicReportMapper reportMapper;
 
+    @Autowired
+    private DynamicMapper dynamicMapper;
+
     /** 举报列表 */
     @GetMapping("/list")
     public Result<Map<String, Object>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) Integer status,
-            @RequestParam(required = false) Long dynamicId) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "dynamicId", required = false) Long dynamicId) {
         Page<DynamicReport> p = new Page<>(page, pageSize);
         LambdaQueryWrapper<DynamicReport> qw = new LambdaQueryWrapper<>();
         if (status != null) qw.eq(DynamicReport::getStatus, status);
@@ -101,6 +106,17 @@ public class DynamicReportController {
     public Result<DynamicReport> detail(@PathVariable Long reportId) {
         DynamicReport r = reportMapper.selectById(reportId);
         if (r == null) return Result.error("举报不存在");
+        
+        // 通过 report_id 直接查询动态详情(包含用户昵称)
+        try {
+            Dynamic dynamic = reportMapper.findDynamicByReportId(reportId);
+            if (dynamic != null) {
+                r.setDynamic(dynamic);
+            }
+        } catch (Exception e) {
+            System.err.println("通过report_id查询动态详情异常: " + e.getMessage());
+        }
+        
         return Result.success(r);
     }
 

+ 68 - 10
service/admin/src/main/java/com/zhentao/controller/MarrApplyControllor.java

@@ -7,7 +7,9 @@ import com.zhentao.service.MarrApplyService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -25,10 +27,10 @@ public class MarrApplyControllor {
      * 红娘申请列表(可按姓名/手机号模糊查询,按创建时间倒序)
      */
     @GetMapping("/list")
-    public Result<Map<String, Object>> list(@RequestParam(defaultValue = "1") Integer page,
-                                            @RequestParam(defaultValue = "10") Integer pageSize,
-                                            @RequestParam(required = false) String name,
-                                            @RequestParam(required = false) String phone) {
+    public Result<Map<String, Object>> list(@RequestParam(value = "page", defaultValue = "1") Integer page,
+                                            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+                                            @RequestParam(value = "name", required = false) String name,
+                                            @RequestParam(value = "phone", required = false) String phone) {
         try {
             Page<MarrApply> pageData = marrApplyService.pageQuery(page, pageSize, name, phone);
             Map<String, Object> data = new HashMap<>();
@@ -44,20 +46,76 @@ public class MarrApplyControllor {
     }
 
     /**
-     * 审核通过:更新用户isMatchmaker为1
+     * 审核:支持同意或不同意,必须填写审核原因
      */
     @PostMapping("/approve/{applyId}")
-    public Result<String> approve(@PathVariable Long applyId, @RequestParam Integer userId) {
+    public Result<String> approve(@PathVariable Long applyId, 
+                                  @RequestParam Integer userId,
+                                  @RequestParam Boolean approved,
+                                  @RequestParam String reason) {
         try {
-            boolean success = marrApplyService.approve(applyId, userId);
+            if (reason == null || reason.trim().isEmpty()) {
+                return Result.error("审核原因不能为空");
+            }
+            boolean success = marrApplyService.approve(applyId, userId, approved, reason.trim());
+            if (success) {
+                String message = approved ? "审核通过成功" : "审核不通过成功";
+                return Result.success(message);
+            } else {
+                return Result.error("审核失败");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("审核失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 批量审核
+     */
+    @PostMapping("/batch-approve")
+    public Result<String> batchApprove(@RequestBody Map<String, Object> params) {
+        try {
+            // 安全地转换 applyIds,兼容 Integer 和 Long
+            @SuppressWarnings("unchecked")
+            List<?> applyIdsRaw = (List<?>) params.get("applyIds");
+            List<Long> applyIds = new ArrayList<>();
+            if (applyIdsRaw != null) {
+                for (Object id : applyIdsRaw) {
+                    if (id instanceof Integer) {
+                        applyIds.add(((Integer) id).longValue());
+                    } else if (id instanceof Long) {
+                        applyIds.add((Long) id);
+                    } else if (id instanceof Number) {
+                        applyIds.add(((Number) id).longValue());
+                    } else {
+                        applyIds.add(Long.parseLong(id.toString()));
+                    }
+                }
+            }
+            Boolean approved = (Boolean) params.get("approved");
+            String reason = (String) params.get("reason");
+            
+            if (applyIds == null || applyIds.isEmpty()) {
+                return Result.error("申请ID列表不能为空");
+            }
+            if (approved == null) {
+                return Result.error("审核结果不能为空");
+            }
+            if (reason == null || reason.trim().isEmpty()) {
+                return Result.error("审核原因不能为空");
+            }
+            
+            boolean success = marrApplyService.batchApprove(applyIds, approved, reason.trim());
             if (success) {
-                return Result.success("审核通过成功");
+                String message = approved ? "批量审核通过成功" : "批量审核不通过成功";
+                return Result.success(message);
             } else {
-                return Result.error("审核通过失败");
+                return Result.error("批量审核失败");
             }
         } catch (Exception e) {
             e.printStackTrace();
-            return Result.error("审核通过失败:" + e.getMessage());
+            return Result.error("批量审核失败:" + e.getMessage());
         }
     }
 

+ 33 - 6
service/admin/src/main/java/com/zhentao/controller/MatchmakerSuccessCaseUploadController.java

@@ -41,11 +41,11 @@ public class MatchmakerSuccessCaseUploadController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) String maleRealName,
-            @RequestParam(required = false) String femaleRealName,
-            @RequestParam(required = false) Integer auditStatus) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "maleRealName", required = false) String maleRealName,
+            @RequestParam(value = "femaleRealName", required = false) String femaleRealName,
+            @RequestParam(value = "auditStatus", required = false) Integer auditStatus) {
         try {
             Page<MatchmakerSuccessCaseUpload> pageInfo = new Page<>(page, pageSize);
             QueryWrapper<MatchmakerSuccessCaseUpload> queryWrapper = new QueryWrapper<>();
@@ -90,13 +90,33 @@ public class MatchmakerSuccessCaseUploadController {
         }
     }
 
+    /**
+     * 获取案例详情
+     */
+    @GetMapping("/{id}")
+    public Result<MatchmakerSuccessCaseUploadVO> detail(@PathVariable Long id) {
+        try {
+            MatchmakerSuccessCaseUpload upload = successCaseUploadService.getById(id);
+            if (upload == null) {
+                return Result.error("案例不存在");
+            }
+            
+            MatchmakerSuccessCaseUploadVO vo = convertToVO(upload);
+            return Result.success(vo);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取案例详情失败:" + e.getMessage());
+        }
+    }
+
     /**
      * 审核通过
      * 审核通过时,根据points_reward字段的值给对应红娘增加积分
+     * 支持可选的auditRemark参数
      */
     @PostMapping("/approve/{id}")
     @Transactional(rollbackFor = Exception.class)
-    public Result<String> approve(@PathVariable Long id) {
+    public Result<String> approve(@PathVariable Long id, @RequestBody(required = false) Map<String, String> request) {
         try {
             MatchmakerSuccessCaseUpload upload = successCaseUploadService.getById(id);
             if (upload == null) {
@@ -123,6 +143,13 @@ public class MatchmakerSuccessCaseUploadController {
             }
 
             upload.setAuditStatus(1); // 审核通过
+            // 如果提供了auditRemark,则设置
+            if (request != null && request.containsKey("auditRemark")) {
+                String auditRemark = request.get("auditRemark");
+                if (auditRemark != null && !auditRemark.trim().isEmpty()) {
+                    upload.setAuditRemark(auditRemark.trim());
+                }
+            }
             upload.setAuditedAt(new Date());
             upload.setUpdatedAt(new Date());
 

+ 1 - 1
service/admin/src/main/java/com/zhentao/controller/MatchmakerWorkbenchController.java

@@ -72,7 +72,7 @@ public class MatchmakerWorkbenchController {
      * 获取我的资源
      */
     @GetMapping("/resources")
-    public Result<List<Map<String, Object>>> getMyResources(@RequestParam(required = false) String keyword) {
+    public Result<List<Map<String, Object>>> getMyResources(@RequestParam(value = "keyword", required = false) String keyword) {
         try {
             List<Map<String, Object>> resources = new ArrayList<>();
             

+ 16 - 15
service/admin/src/main/java/com/zhentao/controller/MyResourceController.java

@@ -35,13 +35,13 @@ public class MyResourceController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) String name,
-            @RequestParam(required = false) String phone,
-            @RequestParam(required = false) Integer status,
-            @RequestParam(required = false) String diploma,
-            @RequestParam(required = false) Integer marrStatus) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "name", required = false) String name,
+            @RequestParam(value = "phone", required = false) String phone,
+            @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "diploma", required = false) String diploma,
+            @RequestParam(value = "marrStatus", required = false) Integer marrStatus) {
         
         try {
             Page<MyResource> pageInfo = new Page<>(page, pageSize);
@@ -124,13 +124,13 @@ public class MyResourceController {
      * 审核线索
      * @param id 线索ID
      * @param status 审核状态(1-审核通过,2-审核失败)
-     * @param rejectReason 审核失败原因(可选
+     * @param rejectReason 审核原因(必填,无论通过还是失败都需要填写
      */
     @PutMapping("/{id}/audit")
     public Result<String> audit(
             @PathVariable Integer id,
-            @RequestParam Integer status,
-            @RequestParam(required = false) String rejectReason) {
+            @RequestParam("status") Integer status,
+            @RequestParam("rejectReason") String rejectReason) {
         try {
             MyResource resource = myResourceService.getById(id);
             if (resource == null) {
@@ -141,13 +141,14 @@ public class MyResourceController {
                 return Result.error("审核状态参数错误");
             }
             
-            resource.setStatus(status);
-            if (status == 2 && rejectReason != null && !rejectReason.trim().isEmpty()) {
-                resource.setRejectReason(rejectReason.trim());
-            } else if (status == 1) {
-                resource.setRejectReason(null);
+            // 审核原因必填验证
+            if (rejectReason == null || rejectReason.trim().isEmpty()) {
+                return Result.error("审核原因不能为空");
             }
             
+            resource.setStatus(status);
+            resource.setRejectReason(rejectReason.trim());
+            
             boolean success = myResourceService.updateById(resource);
             if (success) {
                 return Result.success(status == 1 ? "审核通过成功" : "审核失败成功");

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

@@ -31,9 +31,9 @@ public class PointsMallController {
      */
     @GetMapping("/products")
     public Result<Map<String, Object>> getProductList(
-            @RequestParam(required = false) Integer category,
-            @RequestParam(defaultValue = "1") Integer pageNum,
-            @RequestParam(defaultValue = "10") Integer pageSize) {
+            @RequestParam(value = "category", required = false) Integer category,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         try {
             Map<String, Object> data = pointsMallService.getProductList(category, pageNum, pageSize);
             return Result.success(data);
@@ -49,7 +49,7 @@ public class PointsMallController {
      */
     @GetMapping("/products/recommend")
     public Result<List<PointsProduct>> getRecommendProducts(
-            @RequestParam(defaultValue = "10") Integer limit) {
+            @RequestParam(value = "limit", defaultValue = "10") Integer limit) {
         try {
             List<PointsProduct> products = pointsMallService.getRecommendProducts(limit);
             return Result.success(products);
@@ -103,9 +103,9 @@ public class PointsMallController {
      */
     @GetMapping("/records")
     public Result<Map<String, Object>> getPointsRecords(
-            @RequestParam Long makerId,
-            @RequestParam(defaultValue = "1") Integer pageNum,
-            @RequestParam(defaultValue = "20") Integer pageSize) {
+            @RequestParam("makerId") Long makerId,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
         try {
             Map<String, Object> data = pointsMallService.getPointsRecords(makerId, pageNum, pageSize);
             // 同时返回当前余额
@@ -169,10 +169,10 @@ public class PointsMallController {
      */
     @GetMapping("/orders")
     public Result<Map<String, Object>> getOrderList(
-            @RequestParam Long makerId,
-            @RequestParam(required = false) Integer status,
-            @RequestParam(defaultValue = "1") Integer pageNum,
-            @RequestParam(defaultValue = "10") Integer pageSize) {
+            @RequestParam("makerId") Long makerId,
+            @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         try {
             Map<String, Object> data = pointsMallService.getOrderList(makerId, status, pageNum, pageSize);
             return Result.success(data);

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

@@ -42,12 +42,12 @@ public class PointsOrderController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) String orderNo,
-            @RequestParam(required = false) String productName,
-            @RequestParam(required = false) Integer review,
-            @RequestParam(required = false) Integer status) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "orderNo", required = false) String orderNo,
+            @RequestParam(value = "productName", required = false) String productName,
+            @RequestParam(value = "review", required = false) Integer review,
+            @RequestParam(value = "status", required = false) Integer status) {
         
         try {
             Page<PointsOrder> pageInfo = new Page<>(page, pageSize);
@@ -103,8 +103,8 @@ public class PointsOrderController {
     @PostMapping("/review/{id}")
     public Result<String> review(
             @PathVariable Long id,
-            @RequestParam Integer review,
-            @RequestParam(required = false) String reviewFail) {
+            @RequestParam("review") Integer review,
+            @RequestParam(value = "reviewFail", required = false) String reviewFail) {
         
         try {
             PointsOrder order = pointsOrderService.getById(id);

+ 5 - 5
service/admin/src/main/java/com/zhentao/controller/PointsProductController.java

@@ -29,11 +29,11 @@ public class PointsProductController {
      */
     @GetMapping("/list")
     public Result<Page<PointsProduct>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size,
-            @RequestParam(required = false) String keyword,
-            @RequestParam(required = false) Integer status,
-            @RequestParam(required = false) Integer category) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "size", defaultValue = "10") Integer size,
+            @RequestParam(value = "keyword", required = false) String keyword,
+            @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "category", required = false) Integer category) {
         
         log.info("查询积分商品列表: page={}, size={}, keyword={}, status={}, category={}", 
                 page, size, keyword, status, category);

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

@@ -46,11 +46,11 @@ public class UserController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) Integer gender,
-            @RequestParam(required = false) Integer status,
-            @RequestParam(required = false) String keyword) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "gender", required = false) Integer gender,
+            @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "keyword", required = false) String keyword) {
         
         try {
             Page<Users> pageInfo = new Page<>(page, pageSize);
@@ -228,8 +228,8 @@ public class UserController {
      */
     @GetMapping("/vip/list")
     public Result<Map<String, Object>> vipList(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer pageSize) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         
         try {
             Page<UserVip> pageInfo = new Page<>(page, pageSize);

+ 4 - 4
service/admin/src/main/java/com/zhentao/controller/VipPackageController.java

@@ -30,10 +30,10 @@ public class VipPackageController {
      */
     @GetMapping("/package/list")
     public Result<Page<VipPackage>> list(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size,
-            @RequestParam(required = false) String keyword,
-            @RequestParam(required = false) Integer status) {
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "size", defaultValue = "10") Integer size,
+            @RequestParam(value = "keyword", required = false) String keyword,
+            @RequestParam(value = "status", required = false) Integer status) {
         
         log.info("查询VIP套餐列表: page={}, size={}, keyword={}, status={}", page, size, keyword, status);
         

+ 8 - 3
service/admin/src/main/java/com/zhentao/entity/Dynamic.java

@@ -9,7 +9,6 @@ import lombok.Data;
 
 import java.io.Serializable;
 import java.time.LocalDateTime;
-import com.baomidou.mybatisplus.annotation.TableField;
 
 /**
  * 动态实体
@@ -32,12 +31,12 @@ public class Dynamic implements Serializable {
     private Integer commentCount;
     private Integer auditStatus;
     
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @TableField("created_at")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
     
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @TableField("updated_at")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime updateTime;
 
     /**
@@ -51,5 +50,11 @@ public class Dynamic implements Serializable {
      */
     @TableField(exist = false)
     private String coverUrl;
+
+    /**
+     * 举报截图(非表字段,从举报表获取)
+     */
+    @TableField(exist = false)
+    private String screenshots;
 }
 

+ 3 - 0
service/admin/src/main/java/com/zhentao/entity/DynamicReport.java

@@ -59,6 +59,9 @@ public class DynamicReport implements Serializable {
 
     @TableField(exist = false)
     private String reportedName;   // 被举报人(动态作者)昵称
+
+    @TableField(exist = false)
+    private Dynamic dynamic;   // 动态详情
 }
 
 

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

@@ -59,6 +59,10 @@ public class MarrApply {
      * 婚姻介绍经验
      */
     private String experience;
+    /**
+    * 审核原因
+    * */
+    private String reason;
 
     /**
      * 可服务时间
@@ -91,7 +95,7 @@ public class MarrApply {
     private String updateMan;
 
     /**
-     * 状态0-正常,1-禁止
+     * 状态0-同意,1-不同意,2-待审核
      */
     private Integer status;
     /**

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

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import java.util.Date;
 import lombok.Data;
 
@@ -102,11 +103,13 @@ public class PointsOrder {
     /**
      * 创建时间
      */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 
     /**
      * 更新时间
      */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date updateTime;
     /**
      * 是否删除 0-否,1-是

+ 24 - 0
service/admin/src/main/java/com/zhentao/mapper/DynamicReportMapper.java

@@ -1,6 +1,7 @@
 package com.zhentao.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.Dynamic;
 import com.zhentao.entity.DynamicReport;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -34,6 +35,29 @@ public interface DynamicReportMapper extends BaseMapper<DynamicReport> {
             "</script>"
     })
     List<java.util.Map<String,Object>> findAuthorNickByDynamicIds(@Param("dynamicIds") List<Integer> dynamicIds);
+
+    /**
+     * 根据举报ID查询动态详情(通过report_id JOIN查询)
+     */
+    @Select({
+            "SELECT ",
+            "d.dynamic_id as dynamicId, ",
+            "d.user_id as userId, ",
+            "d.content, ",
+            "d.media_urls as mediaUrls, ",
+            "d.like_count as likeCount, ",
+            "d.comment_count as commentCount, ",
+            "d.audit_status as auditStatus, ",
+            "d.created_at as createTime, ",
+            "d.updated_at as updateTime, ",
+            "u.nickname as userNickname, ",
+            "dr.screenshots ",
+            "FROM dynamic_reports dr ",
+            "INNER JOIN user_dynamics d ON dr.dynamic_id = d.dynamic_id ",
+            "LEFT JOIN users u ON d.user_id = u.user_id ",
+            "WHERE dr.report_id = #{reportId}"
+    })
+    Dynamic findDynamicByReportId(@Param("reportId") Long reportId);
 }
 
 

+ 14 - 2
service/admin/src/main/java/com/zhentao/service/MarrApplyService.java

@@ -19,9 +19,21 @@ public interface MarrApplyService extends IService<MarrApply> {
     Page<MarrApply> pageQuery(Integer pageNum, Integer pageSize, String name, String phone);
     
     /**
-     * 审核通过:更新用户isMatchmaker为1
+     * 审核:更新用户isMatchmaker为1(同意)或保持原状(不同意)
+     * @param applyId 申请ID
+     * @param userId 用户ID
+     * @param approved true-同意,false-不同意
+     * @param reason 审核原因
      */
-    boolean approve(Long applyId, Integer userId);
+    boolean approve(Long applyId, Integer userId, Boolean approved, String reason);
+    
+    /**
+     * 批量审核
+     * @param applyIds 申请ID列表
+     * @param approved true-同意,false-不同意
+     * @param reason 审核原因
+     */
+    boolean batchApprove(List<Long> applyIds, Boolean approved, String reason);
     
     /**
      * 删除申请

+ 162 - 109
service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java

@@ -28,6 +28,7 @@ import com.zhentao.mapper.AreaMapper;
 import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -70,9 +71,9 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean approve(Long applyId, Integer userId) {
+    public boolean approve(Long applyId, Integer userId, Boolean approved, String reason) {
         System.out.println("========== 开始审核红娘申请 ==========");
-        System.out.println("申请ID: " + applyId + ", 用户ID: " + userId);
+        System.out.println("申请ID: " + applyId + ", 用户ID: " + userId + ", 审核结果: " + (approved ? "同意" : "不同意") + ", 原因: " + reason);
         
         // 1. 查询申请信息
         MarrApply apply = this.getById(applyId);
@@ -80,128 +81,180 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
             throw new RuntimeException("申请记录不存在");
         }
         
-        // 2. 查询用户是否存在
-        Users user = usersMapper.selectById(userId);
-        if (user == null) {
-            throw new RuntimeException("用户不存在");
+        // 2. 检查申请状态,只有待审核(status=2)的记录才能被审核
+        if (apply.getStatus() == null || apply.getStatus() != 2) {
+            throw new RuntimeException("该申请不是待审核状态,无法进行审核操作。当前状态: " + 
+                (apply.getStatus() == null ? "未设置" : 
+                 apply.getStatus() == 0 ? "已同意" : 
+                 apply.getStatus() == 1 ? "已不同意" : "未知"));
         }
         
-        // 3. 更新用户的isMatchmaker为1
-        // 直接使用 MyBatis-Plus 的 update 方法
-        Users updateUser = new Users();
-        updateUser.setUserId(userId);
-        updateUser.setIsMatchmaker(1);
-        int userUpdateResult = usersMapper.updateById(updateUser);
-        if (userUpdateResult <= 0) {
-            throw new RuntimeException("更新用户红娘状态失败");
-        }
-        System.out.println("✅ 已更新 users 表,is_matchmaker = 1");
-        
-        // 4. 创建或更新 matchmakers 记录
-        // 先检查该用户是否已经存在红娘记录
-        QueryWrapper<Matchmaker> matchmakerWrapper = new QueryWrapper<>();
-        matchmakerWrapper.eq("user_id", apply.getUserId());
-        Matchmaker existingMatchmaker = matchmakerMapper.selectOne(matchmakerWrapper);
-        
-        Matchmaker matchmaker;
-        boolean isUpdate = false;
+        // 3. 更新申请记录的审核原因、状态、更新人和更新时间
+        apply.setReason(reason);
+        apply.setUpdateTime(new Date());
+        apply.setUpdateMan("系统");
         
-        if (existingMatchmaker != null) {
-            // 如果已存在,则更新现有记录
-            matchmaker = existingMatchmaker;
-            isUpdate = true;
-            System.out.println("⚠️ 该用户已存在红娘记录,将更新现有记录,matchmaker_id = " + matchmaker.getMatchmakerId());
+        if (approved) {
+            // 同意:继续原有流程
+            // 3. 查询用户是否存在
+            Users user = usersMapper.selectById(userId);
+            if (user == null) {
+                throw new RuntimeException("用户不存在");
+            }
+            
+            // 4. 更新用户的isMatchmaker为1
+            Users updateUser = new Users();
+            updateUser.setUserId(userId);
+            updateUser.setIsMatchmaker(1);
+            int userUpdateResult = usersMapper.updateById(updateUser);
+            if (userUpdateResult <= 0) {
+                throw new RuntimeException("更新用户红娘状态失败");
+            }
+            System.out.println("✅ 已更新 users 表,is_matchmaker = 1");
+            
+            // 5. 创建或更新 matchmakers 记录
+            QueryWrapper<Matchmaker> matchmakerWrapper = new QueryWrapper<>();
+            matchmakerWrapper.eq("user_id", apply.getUserId());
+            Matchmaker existingMatchmaker = matchmakerMapper.selectOne(matchmakerWrapper);
+            
+            Matchmaker matchmaker;
+            boolean isUpdate = false;
+            
+            if (existingMatchmaker != null) {
+                matchmaker = existingMatchmaker;
+                isUpdate = true;
+                System.out.println("⚠️ 该用户已存在红娘记录,将更新现有记录,matchmaker_id = " + matchmaker.getMatchmakerId());
+            } else {
+                matchmaker = new Matchmaker();
+                matchmaker.setCreateTime(LocalDateTime.now());
+            }
+            
+            matchmaker.setUserId(apply.getUserId());
+            matchmaker.setRealName(apply.getName());
+            matchmaker.setPhone(apply.getPhone());
+            matchmaker.setEmail(apply.getEmail());
+            matchmaker.setGender(apply.getGender());
+            matchmaker.setProfile(apply.getExperience());
+            
+            parseAreaToIds(apply.getArea(), matchmaker);
+            
+            String baseUsername = user.getNickname() != null ? user.getNickname() : user.getPhone();
+            if (!isUpdate || matchmaker.getUsername() == null || !baseUsername.equals(matchmaker.getUsername())) {
+                String finalUsername = generateUniqueUsername(baseUsername, matchmaker.getUserId());
+                matchmaker.setUsername(finalUsername);
+            }
+            
+            matchmaker.setPassword(user.getPassword());
+            matchmaker.setBirthDate(user.getBirthDate());
+            
+            if (apply.getGender() != null && apply.getGender() == 1) {
+                matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/5c645152-9940-41d3-83a9-69ee6e0c0aaa.png");
+            } else {
+                matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/c7fb04d7-ee4d-4b3d-bcef-f246da9c841f.png");
+            }
+            
+            if (!isUpdate) {
+                matchmaker.setMatchmakerType(1);
+                matchmaker.setLevel(1);
+                matchmaker.setSuccessCouples(0);
+            }
+            matchmaker.setStatus(1);
+            matchmaker.setUpdateTime(LocalDateTime.now());
+            
+            int result;
+            if (isUpdate) {
+                result = matchmakerMapper.updateById(matchmaker);
+                if (result <= 0) {
+                    throw new RuntimeException("更新红娘记录失败");
+                }
+                System.out.println("✅ 已更新 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
+            } else {
+                result = matchmakerMapper.insert(matchmaker);
+                if (result <= 0) {
+                    throw new RuntimeException("创建红娘记录失败");
+                }
+                System.out.println("✅ 已创建 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
+            }
+            
+            // 6. 导入到腾讯云 IM
+            String imUserId = "m_" + matchmaker.getMatchmakerId();
+            try {
+                Map<String, String> imParams = new HashMap<>();
+                imParams.put("userId", imUserId);
+                imParams.put("nickname", matchmaker.getRealName());
+                if (matchmaker.getAvatarUrl() != null && !matchmaker.getAvatarUrl().isEmpty()) {
+                    imParams.put("faceUrl", matchmaker.getAvatarUrl());
+                }
+                
+                HttpHeaders headers = new HttpHeaders();
+                headers.setContentType(MediaType.APPLICATION_JSON);
+                HttpEntity<Map<String, String>> request = new HttpEntity<>(imParams, headers);
+                
+                restTemplate.postForObject(IM_SERVICE_URL + "/importUser", request, Map.class);
+                System.out.println("✅ 已导入到腾讯云 IM: imUserId = " + imUserId);
+            } catch (Exception e) {
+                System.err.println("❌ 导入到 IM 失败: " + e.getMessage());
+                e.printStackTrace();
+            }
+            
+            // 7. 更新申请状态为审核通过(从2变成0)
+            apply.setStatus(0); // 0-同意
         } else {
-            // 如果不存在,创建新记录
-            matchmaker = new Matchmaker();
-            matchmaker.setCreateTime(LocalDateTime.now());
+            // 不同意:只更新审核原因和状态(从2变成1)
+            apply.setStatus(1); // 1-不同意
         }
         
-        // 从 marr_apply 表获取数据(按照用户要求映射)
-        matchmaker.setUserId(apply.getUserId());           // 用户ID
-        matchmaker.setRealName(apply.getName());           // 姓名 -> 真实姓名
-        matchmaker.setPhone(apply.getPhone());             // 手机号
-        matchmaker.setEmail(apply.getEmail());             // 邮箱
-        matchmaker.setGender(apply.getGender());           // 性别
-        matchmaker.setProfile(apply.getExperience());      // 经验 -> 个人简介
-        
-        // 解析地区信息:area 字段包含三级联动,需要解析为 provinceId, cityId, areaId
-        parseAreaToIds(apply.getArea(), matchmaker);
-        
-        // 从 users 表获取数据
-        String baseUsername = user.getNickname() != null ? user.getNickname() : user.getPhone(); // 用户名(优先使用昵称,否则使用手机号)
-        
-        // 检查用户名是否已存在(如果不存在记录或用户名发生变化)
-        if (!isUpdate || matchmaker.getUsername() == null || !baseUsername.equals(matchmaker.getUsername())) {
-            String finalUsername = generateUniqueUsername(baseUsername, matchmaker.getUserId());
-            matchmaker.setUsername(finalUsername);
+        this.updateById(apply);
+        System.out.println("========== 审核完成 ==========");
+        return true;
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean batchApprove(List<Long> applyIds, Boolean approved, String reason) {
+        if (applyIds == null || applyIds.isEmpty()) {
+            throw new RuntimeException("申请ID列表不能为空");
         }
         
-        matchmaker.setPassword(user.getPassword());        // 密码(使用用户的密码)
-        matchmaker.setBirthDate(user.getBirthDate());      // 出生日期(更准确)
+        System.out.println("========== 开始批量审核红娘申请 ==========");
+        System.out.println("申请数量: " + applyIds.size() + ", 审核结果: " + (approved ? "同意" : "不同意") + ", 原因: " + reason);
         
-        // 头像:根据性别强制设置默认头像
-        if (apply.getGender() != null && apply.getGender() == 1) {
-            // 男性默认头像
-            matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/5c645152-9940-41d3-83a9-69ee6e0c0aaa.png");
-        } else {
-            // 女性默认头像(包括性别为2或null的情况)
-            matchmaker.setAvatarUrl("http://115.190.125.125:9000/dynamic-comments/dynamics/c7fb04d7-ee4d-4b3d-bcef-f246da9c841f.png");
-        }
+        int successCount = 0;
+        int failCount = 0;
         
-        // 设置默认值(无法从其他表获取的字段)
-        if (!isUpdate) {
-            matchmaker.setMatchmakerType(1);                   // 默认类型:1-普通红娘
-            matchmaker.setLevel(1);                            // 默认等级:1级
-            matchmaker.setSuccessCouples(0);                   // 初始成功撮合数:0
-        }
-        matchmaker.setStatus(1);                           // 状态:1-正常
-        matchmaker.setUpdateTime(LocalDateTime.now());
-        
-        int result;
-        if (isUpdate) {
-            result = matchmakerMapper.updateById(matchmaker);
-            if (result <= 0) {
-                throw new RuntimeException("更新红娘记录失败");
-            }
-            System.out.println("✅ 已更新 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
-        } else {
-            result = matchmakerMapper.insert(matchmaker);
-            if (result <= 0) {
-                throw new RuntimeException("创建红娘记录失败");
+        for (Long applyId : applyIds) {
+            try {
+                // 查询申请信息获取userId
+                MarrApply apply = this.getById(applyId);
+                if (apply == null) {
+                    System.err.println("❌ 申请ID " + applyId + " 不存在,跳过");
+                    failCount++;
+                    continue;
+                }
+                
+                if (apply.getUserId() == null) {
+                    System.err.println("❌ 申请ID " + applyId + " 的用户ID为空,跳过");
+                    failCount++;
+                    continue;
+                }
+                
+                // 调用单个审核方法
+                approve(applyId, apply.getUserId(), approved, reason);
+                successCount++;
+            } catch (Exception e) {
+                System.err.println("❌ 审核申请ID " + applyId + " 失败: " + e.getMessage());
+                e.printStackTrace();
+                failCount++;
             }
-            System.out.println("✅ 已创建 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
         }
         
-        // 5. 导入到腾讯云 IM(使用 m_ + matchmaker_id)
-        String imUserId = "m_" + matchmaker.getMatchmakerId();
-        try {
-            Map<String, String> imParams = new HashMap<>();
-            imParams.put("userId", imUserId);
-            imParams.put("nickname", matchmaker.getRealName());
-            if (matchmaker.getAvatarUrl() != null && !matchmaker.getAvatarUrl().isEmpty()) {
-                imParams.put("faceUrl", matchmaker.getAvatarUrl());
-            }
-            
-            HttpHeaders headers = new HttpHeaders();
-            headers.setContentType(MediaType.APPLICATION_JSON);
-            HttpEntity<Map<String, String>> request = new HttpEntity<>(imParams, headers);
-            
-            restTemplate.postForObject(IM_SERVICE_URL + "/importUser", request, Map.class);
-            System.out.println("✅ 已导入到腾讯云 IM: imUserId = " + imUserId);
-        } catch (Exception e) {
-            System.err.println("❌ 导入到 IM 失败: " + e.getMessage());
-            e.printStackTrace();
-            // 导入失败不影响审核流程,只记录日志
-        }
+        System.out.println("========== 批量审核完成 ==========");
+        System.out.println("成功: " + successCount + ", 失败: " + failCount);
         
-        // 6. 更新申请记录的审核状态、更新人和更新时间
-        apply.setStatus(0); // 0-正常(审核通过)
-        apply.setUpdateTime(new Date());
-        apply.setUpdateMan("系统");
-        this.updateById(apply);
+        if (failCount > 0) {
+            throw new RuntimeException("批量审核完成,成功 " + successCount + " 条,失败 " + failCount + " 条");
+        }
         
-        System.out.println("========== 审核完成 ==========");
         return true;
     }
 

+ 4 - 4
service/admin/src/main/java/com/zhentao/vo/MatchmakerSuccessCaseUploadVO.java

@@ -58,7 +58,7 @@ public class MatchmakerSuccessCaseUploadVO implements Serializable {
     /**
      * 成功日期(结婚日期/确定关系日期)
      */
-    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private Date caseDate;
 
     /**
@@ -109,19 +109,19 @@ public class MatchmakerSuccessCaseUploadVO implements Serializable {
     /**
      * 上传时间
      */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createdAt;
 
     /**
      * 更新时间(更改时间)
      */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date updatedAt;
 
     /**
      * 审核时间
      */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date auditedAt;
     
     /**

+ 2 - 1
service/admin/src/main/resources/com/zhentao/mapper/MarrApplyMapper.xml

@@ -22,12 +22,13 @@
             <result property="updateMan" column="update_man" />
             <result property="status" column="status" />
             <result property="idStatus" column="id_status" />
+            <result property="reason" column="reason" />
     </resultMap>
 
     <sql id="Base_Column_List">
         apply_id,user_id,name,phone,email,age,
         gender,area,experience,server_time,introduction,
-        create_time,create_man,update_time,update_man,status,id_status
+        create_time,create_man,update_time,update_man,status,id_status,reason
     </sql>
 
     <select id="selectPageByCondition" resultMap="BaseResultMap">

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

@@ -52,9 +52,9 @@ public class CourseController {
      */
     @GetMapping("/list")
     public Result<Map<String, Object>> getCourseList(
-            @RequestParam(required = false, defaultValue = "1") Integer page,
-            @RequestParam(required = false, defaultValue = "10") Integer pageSize,
-            @RequestParam(required = false) Integer status) {
+            @RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "status", required = false) Integer status) {
         try {
             // 参数校验和默认值设置
             if (page == null || page < 1) {

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

@@ -1,6 +1,8 @@
 package com.zhentao.controller;
 
 import com.zhentao.common.Result;
+import com.zhentao.service.HomeFunctionGridService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
@@ -12,6 +14,9 @@ import java.util.*;
 @RequestMapping("/api/home")
 public class HomeController {
     
+    @Autowired
+    private HomeFunctionGridService homeFunctionGridService;
+    
     /**
      * 获取公告列表
      * 
@@ -60,6 +65,22 @@ public class HomeController {
             return Result.error("获取未读消息数失败:" + e.getMessage());
         }
     }
+    
+    /**
+     * 获取金刚区功能列表
+     * 
+     * @return 金刚区功能列表
+     */
+    @GetMapping("/function-grid")
+    public Result<List> getFunctionGrid() {
+        try {
+            // 从数据库获取启用的金刚区功能列表
+            return Result.success(homeFunctionGridService.getEnabledFunctionGrids());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取金刚区功能列表失败:" + e.getMessage());
+        }
+    }
 }
 
 

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

@@ -130,8 +130,8 @@ public class MatchmakerController {
      */
     @GetMapping("/formal")
     public Result<Page<MatchmakerVO>> getFormalMatchmakers(
-            @RequestParam(defaultValue = "1") Integer pageNum,
-            @RequestParam(defaultValue = "10") Integer pageSize) {
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         try {
             Page<MatchmakerVO> page = matchmakerService.getFormalMatchmakers(pageNum, pageSize);
             return Result.success(page);
@@ -308,7 +308,7 @@ public class MatchmakerController {
      */
     @GetMapping("/ranking")
     public Result<List<MatchmakerVO>> getRankingList(
-            @RequestParam(defaultValue = "20") Integer limit) {
+            @RequestParam(value = "limit", defaultValue = "20") Integer limit) {
         try {
             List<MatchmakerVO> rankingList = matchmakerService.getRankingList(limit);
             return Result.success(rankingList);
@@ -327,8 +327,8 @@ public class MatchmakerController {
      */
     @GetMapping("/weekly-ranking")
     public Result<List<MatchmakerVO>> getWeeklyRankingList(
-            @RequestParam(defaultValue = "20") Integer limit,
-            @RequestParam(required = false) Long userId) {
+            @RequestParam(value = "limit", defaultValue = "20") Integer limit,
+            @RequestParam(value = "userId", required = false) Long userId) {
         try {
             List<MatchmakerVO> rankingList = matchmakerService.getWeeklyRankingList(limit);
             

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

@@ -24,7 +24,7 @@ public class MatchmakerCourseController {
      * 获取所有启用的红娘课程列表,可按分类类型筛选
      */
     @GetMapping("/list")
-    public Result<List<MatchmakerCourse>> getCourseList(@RequestParam(required = false, defaultValue = "all") String categoryType) {
+    public Result<List<MatchmakerCourse>> getCourseList(@RequestParam(value = "categoryType", required = false, defaultValue = "all") String categoryType) {
         try {
             List<MatchmakerCourse> courses = matchmakerCourseService.getActiveCourses(categoryType);
             return Result.success(courses);

+ 11 - 11
service/homePage/src/main/java/com/zhentao/controller/MyResourceController.java

@@ -270,12 +270,12 @@ public class MyResourceController {
      */
     @GetMapping("/list")
     public Result<Page<MyResourceVO>> getResourceList(
-            @RequestParam(required = false) Integer matchmakerId,
-            @RequestParam(required = false) Integer currentUserId,
-            @RequestParam(required = false) String keyword,
-            @RequestParam(required = false) Integer isUser,
-            @RequestParam(defaultValue = "1") Integer pageNum,
-            @RequestParam(defaultValue = "10") Integer pageSize) {
+            @RequestParam(value = "matchmakerId", required = false) Integer matchmakerId,
+            @RequestParam(value = "currentUserId", required = false) Integer currentUserId,
+            @RequestParam(value = "keyword", required = false) String keyword,
+            @RequestParam(value = "isUser", required = false) Integer isUser,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         try {
             // 如果matchmakerId为空,根据currentUserId从matchmakers表中查询对应的matchmaker_id
             if (matchmakerId == null) {
@@ -761,11 +761,11 @@ public class MyResourceController {
      */
     @GetMapping("/list-by-tag")
     public Result<Page<MyResourceVO>> getResourceListByTagName(
-            @RequestParam String tagName,
-            @RequestParam(required = false) String keyword,
-            @RequestParam(required = false) Integer currentUserId,
-            @RequestParam(defaultValue = "1") Integer pageNum,
-            @RequestParam(defaultValue = "10") Integer pageSize) {
+            @RequestParam("tagName") String tagName,
+            @RequestParam(value = "keyword", required = false) String keyword,
+            @RequestParam(value = "currentUserId", required = false) Integer currentUserId,
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         try {
             // 如果提供了currentUserId,查询对应的matchmaker_id
             Integer matchmakerId = null;

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

@@ -85,8 +85,8 @@ public class SuccessCaseController {
      */
     @GetMapping("/api/case/list")
     public Result<Map<String, Object>> getAdminCaseList(
-            @RequestParam(required = false, defaultValue = "1") Integer page,
-            @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+            @RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
+            @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
         try {
             // 参数校验和默认值设置
             if (page == null || page < 1) {

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

@@ -0,0 +1,210 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 首页金刚区功能表
+ * </p>
+ *
+ * @author zhentao
+ * @since 2024-05-20
+ */
+@TableName("home_function_grid")
+public class HomeFunctionGrid implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 功能名称
+     */
+    @TableField("name")
+    private String name;
+
+    /**
+     * 图标(emoji)
+     */
+    @TableField("icon")
+    private String icon;
+
+    /**
+     * 跳转路径
+     */
+    @TableField("path")
+    private String path;
+
+    /**
+     * 背景颜色
+     */
+    @TableField("bg_color")
+    private String bgColor;
+
+    /**
+     * 图标颜色
+     */
+    @TableField("icon_color")
+    private String iconColor;
+
+    /**
+     * 渐变背景
+     */
+    @TableField("gradient")
+    private String gradient;
+
+    /**
+     * 是否需要登录:0-不需要 1-需要
+     */
+    @TableField("need_login")
+    private Boolean needLogin;
+
+    /**
+     * 排序
+     */
+    @TableField("sort")
+    private Integer sort;
+
+    /**
+     * 状态:0-禁用 1-启用
+     */
+    @TableField("status")
+    private Boolean status;
+
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    // getter和setter方法
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getIcon() {
+        return icon;
+    }
+
+    public void setIcon(String icon) {
+        this.icon = icon;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public String getBgColor() {
+        return bgColor;
+    }
+
+    public void setBgColor(String bgColor) {
+        this.bgColor = bgColor;
+    }
+
+    public String getIconColor() {
+        return iconColor;
+    }
+
+    public void setIconColor(String iconColor) {
+        this.iconColor = iconColor;
+    }
+
+    public String getGradient() {
+        return gradient;
+    }
+
+    public void setGradient(String gradient) {
+        this.gradient = gradient;
+    }
+
+    public Boolean getNeedLogin() {
+        return needLogin;
+    }
+
+    public void setNeedLogin(Boolean needLogin) {
+        this.needLogin = needLogin;
+    }
+
+    public Integer getSort() {
+        return sort;
+    }
+
+    public void setSort(Integer sort) {
+        this.sort = sort;
+    }
+
+    public Boolean getStatus() {
+        return status;
+    }
+
+    public void setStatus(Boolean status) {
+        this.status = status;
+    }
+
+    public LocalDateTime getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(LocalDateTime createTime) {
+        this.createTime = createTime;
+    }
+
+    public LocalDateTime getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(LocalDateTime updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    @Override
+    public String toString() {
+        return "HomeFunctionGrid{" +
+                "id=" + id +
+                ", name='" + name + '\'' +
+                ", icon='" + icon + '\'' +
+                ", path='" + path + '\'' +
+                ", bgColor='" + bgColor + '\'' +
+                ", iconColor='" + iconColor + '\'' +
+                ", gradient='" + gradient + '\'' +
+                ", needLogin=" + needLogin +
+                ", sort=" + sort +
+                ", status=" + status +
+                ", createTime=" + createTime +
+                ", updateTime=" + updateTime +
+                '}';
+    }
+}

+ 18 - 0
service/homePage/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;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 首页金刚区功能表 Mapper 接口
+ * </p>
+ *
+ * @author zhentao
+ * @since 2024-05-20
+ */
+@Mapper
+public interface HomeFunctionGridMapper extends BaseMapper<HomeFunctionGrid> {
+
+}

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

@@ -0,0 +1,22 @@
+package com.zhentao.service;
+
+import com.zhentao.entity.HomeFunctionGrid;
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+
+/**
+ * <p>
+ * 首页金刚区功能表 服务类
+ * </p>
+ *
+ * @author zhentao
+ * @since 2024-05-20
+ */
+public interface HomeFunctionGridService extends IService<HomeFunctionGrid> {
+
+    /**
+     * 获取启用的金刚区功能列表,按排序排序
+     * @return 金刚区功能列表
+     */
+    List<HomeFunctionGrid> getEnabledFunctionGrids();
+}

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

@@ -0,0 +1,30 @@
+package com.zhentao.service.impl;
+
+import com.zhentao.entity.HomeFunctionGrid;
+import com.zhentao.mapper.HomeFunctionGridMapper;
+import com.zhentao.service.HomeFunctionGridService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+/**
+ * <p>
+ * 首页金刚区功能表 服务实现类
+ * </p>
+ *
+ * @author zhentao
+ * @since 2024-05-20
+ */
+@Service
+public class HomeFunctionGridServiceImpl extends ServiceImpl<HomeFunctionGridMapper, HomeFunctionGrid> implements HomeFunctionGridService {
+
+    @Override
+    public List<HomeFunctionGrid> getEnabledFunctionGrids() {
+        // 查询启用的金刚区功能,按排序字段升序排列
+        return baseMapper.selectList(new LambdaQueryWrapper<HomeFunctionGrid>()
+                .eq(HomeFunctionGrid::getStatus, true)
+                .orderByAsc(HomeFunctionGrid::getSort)
+        );
+    }
+}

+ 24 - 0
service/homePage/src/main/resources/sql/home_function_grid.sql

@@ -0,0 +1,24 @@
+-- 金刚区功能表
+CREATE TABLE IF NOT EXISTS `home_function_grid` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `name` VARCHAR(50) NOT NULL COMMENT '功能名称',
+  `icon` VARCHAR(20) NOT NULL COMMENT '图标(emoji)',
+  `path` VARCHAR(255) NOT NULL COMMENT '跳转路径',
+  `bg_color` VARCHAR(50) NULL COMMENT '背景颜色',
+  `icon_color` VARCHAR(50) NULL COMMENT '图标颜色',
+  `gradient` VARCHAR(255) NULL COMMENT '渐变背景',
+  `need_login` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否需要登录:0-不需要 1-需要',
+  `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`)
+) 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);

+ 491 - 0
代码结构详解.md

@@ -0,0 +1,491 @@
+# XINxiangqin 代码结构详解
+
+> 详细的代码组织结构说明,帮助快速定位代码位置
+
+---
+
+## 📂 一、项目根目录结构
+
+```
+XINxiangqin/
+├── gateway/                    # API网关服务
+├── common/                     # 公共模块
+├── service/                    # 业务服务模块(Maven聚合)
+│   ├── login/                 # 登录服务
+│   ├── homePage/              # 首页服务
+│   ├── dynamic/               # 动态服务
+│   ├── websocket/             # WebSocket聊天服务
+│   ├── randomMatch/           # 随机匹配服务
+│   ├── Essential/             # 基础服务
+│   ├── Recommend/            # 推荐服务
+│   └── admin/                 # 管理端服务
+├── LiangZhiYUMao/             # 小程序前端(uni-app)
+├── marriageAdmin-vue/         # 管理后台(Vue 3)
+├── pom.xml                    # Maven父项目配置
+└── 项目文档/                   # 项目文档目录
+```
+
+---
+
+## 🔧 二、后端代码结构
+
+### 2.1 Gateway服务结构
+
+```
+gateway/
+├── src/
+│   ├── main/
+│   │   ├── java/
+│   │   │   └── com/zhentao/
+│   │   │       └── gateway/
+│   │   │           ├── GatewayApplication.java    # 启动类
+│   │   │           └── config/
+│   │   │               └── CorsConfig.java        # CORS配置
+│   │   └── resources/
+│   │       └── application.yml                    # 网关路由配置(重要!)
+│   └── test/
+```
+
+**关键文件**:
+- `application.yml` - 所有路由规则、CORS配置、WebSocket支持
+
+### 2.2 Common公共模块结构
+
+```
+common/
+├── src/
+│   ├── main/
+│   │   ├── java/
+│   │   │   └── com/zhentao/
+│   │   │       ├── pojo/                         # 公共实体类
+│   │   │       │   ├── Users.java                # 用户实体
+│   │   │       │   ├── PartnerRequirement.java   # 择偶要求实体
+│   │   │       │   └── MatchmakerApply.java      # 红娘申请实体
+│   │   │       ├── mapper/                       # 数据访问层
+│   │   │       │   ├── UsersMapper.java
+│   │   │       │   ├── PartnerRequirementMapper.java
+│   │   │       │   └── MatchmakerApplyMapper.java
+│   │   │       └── service/                      # 公共服务接口
+│   │   │           ├── UsersService.java
+│   │   │           ├── PartnerRequirementService.java
+│   │   │           └── MatchmakerApplyService.java
+│   │   └── resources/
+│   └── test/
+```
+
+**关键文件**:
+- `pojo/` - 所有服务共享的实体类
+- `mapper/` - MyBatis Mapper接口
+- `service/` - 公共服务接口和实现
+
+### 2.3 业务服务标准结构
+
+每个业务服务(login、homePage、dynamic等)都遵循相同的结构:
+
+```
+service/{module}/
+├── src/
+│   ├── main/
+│   │   ├── java/
+│   │   │   └── com/zhentao/
+│   │   │       ├── controller/                   # 控制器层(REST API)
+│   │   │       │   └── XxxController.java        # 处理HTTP请求
+│   │   │       ├── service/                       # 服务层(业务逻辑)
+│   │   │       │   ├── XxxService.java           # 服务接口
+│   │   │       │   └── impl/
+│   │   │       │       └── XxxServiceImpl.java  # 服务实现
+│   │   │       ├── mapper/                        # 数据访问层
+│   │   │       │   └── XxxMapper.java            # MyBatis Mapper接口
+│   │   │       ├── pojo/                          # 实体类(本服务专用)
+│   │   │       │   └── Xxx.java
+│   │   │       ├── config/                        # 配置类
+│   │   │       │   ├── RedisConfig.java          # Redis配置
+│   │   │       │   ├── MinIOConfig.java          # MinIO配置
+│   │   │       │   └── MybatisPlusConfig.java    # MyBatis-Plus配置
+│   │   │       └── {Module}Application.java      # 启动类
+│   │   └── resources/
+│   │       ├── application.yml                    # 服务配置(数据库、Redis等)
+│   │       ├── mapper/                            # MyBatis XML映射文件
+│   │       │   └── XxxMapper.xml
+│   │       └── sql/                               # SQL脚本
+│   │           └── xxx.sql
+│   └── test/
+├── pom.xml                                        # Maven配置
+└── target/                                        # 编译输出
+```
+
+### 2.4 各服务详细说明
+
+#### HomePage服务 (端口: 8081)
+```
+service/homePage/
+├── controller/
+│   ├── ActivityController.java        # 活动管理
+│   ├── BannerController.java         # 轮播图
+│   ├── AnnouncementController.java    # 公告
+│   ├── MatchmakerController.java      # 红娘
+│   ├── CourseController.java         # 课程
+│   └── SuccessCaseController.java    # 成功案例
+└── ...
+```
+
+#### Dynamic服务 (端口: 8086)
+```
+service/dynamic/
+├── controller/
+│   ├── DynamicController.java         # 动态管理
+│   ├── CommentController.java         # 评论
+│   ├── LikeController.java            # 点赞
+│   └── ReportController.java          # 举报
+└── ...
+```
+
+#### WebSocket服务 (端口: 1004)
+```
+service/websocket/
+├── controller/
+│   ├── ChatController.java            # 聊天REST API
+│   ├── OnlineController.java         # 在线状态
+│   └── IMController.java              # 腾讯云IM
+├── config/
+│   └── WebSocketConfig.java          # WebSocket配置
+└── ...
+```
+
+#### Essential服务 (端口: 1005)
+```
+service/Essential/
+├── controller/
+│   ├── UserController.java            # 用户信息
+│   ├── ProfileController.java         # 用户资料
+│   ├── VipController.java             # VIP管理
+│   ├── CheckinController.java         # 签到
+│   └── FeedbackController.java        # 反馈
+└── ...
+```
+
+#### Admin服务 (端口: 8088)
+```
+service/admin/
+├── controller/
+│   ├── AdminUserController.java       # 管理员管理
+│   ├── DashboardController.java       # 数据面板
+│   ├── DynamicAuditController.java    # 动态审核
+│   └── ReportController.java          # 举报处理
+└── ...
+```
+
+---
+
+## 📱 三、前端代码结构
+
+### 3.1 小程序端结构 (LiangZhiYUMao)
+
+```
+LiangZhiYUMao/
+├── pages/                              # 页面目录
+│   ├── index/                          # 首页
+│   │   └── index.vue
+│   ├── match/                          # 匹配
+│   │   └── index.vue
+│   ├── recommend/                      # 推荐
+│   │   ├── index.vue
+│   │   ├── user-detail.vue
+│   │   └── user-dynamics.vue
+│   ├── plaza/                          # 动态广场
+│   │   ├── index.vue
+│   │   ├── detail.vue
+│   │   ├── publish.vue
+│   │   └── report.vue
+│   ├── message/                        # 消息
+│   │   ├── index.vue
+│   │   ├── chat.vue
+│   │   └── system.vue
+│   ├── astrology/                      # 星命运算
+│   │   ├── index.vue
+│   │   ├── zodiac.vue                  # 属相
+│   │   ├── constellation.vue           # 星座
+│   │   ├── constellation-match.vue     # 星座配对
+│   │   ├── bazi.vue                    # 八字
+│   │   ├── bazi-match.vue              # 八字配对
+│   │   └── mbti.vue                    # MBTI
+│   ├── matchmaker-workbench/           # 红娘工作台
+│   │   ├── index.vue
+│   │   ├── my-resources.vue
+│   │   ├── resource-input.vue
+│   │   ├── client-detail.vue
+│   │   ├── precise-match.vue
+│   │   ├── quality-resources.vue
+│   │   ├── points-mall.vue
+│   │   ├── courses.vue
+│   │   ├── ranking.vue
+│   │   ├── message.vue
+│   │   ├── system-messages.vue
+│   │   └── audit-records.vue
+│   ├── activities/                     # 活动
+│   │   ├── list.vue
+│   │   └── detail.vue
+│   ├── matchmakers/                     # 红娘
+│   │   ├── list.vue
+│   │   └── detail.vue
+│   ├── courses/                         # 课程
+│   │   ├── list.vue
+│   │   └── detail.vue
+│   ├── success-case/                    # 成功案例
+│   │   ├── list.vue
+│   │   └── detail.vue
+│   ├── profile/                         # 个人资料
+│   │   └── index.vue
+│   ├── mine/                            # 我的
+│   │   ├── index.vue
+│   │   ├── my-activities.vue
+│   │   └── my-dynamics.vue
+│   ├── settings/                        # 设置
+│   │   ├── index.vue
+│   │   ├── id-verification.vue
+│   │   └── phone-binding.vue
+│   ├── vip/                             # VIP
+│   │   └── index.vue
+│   ├── customize/                       # 私人定制
+│   │   └── index.vue
+│   ├── blacklist/                       # 黑名单
+│   │   └── index.vue
+│   ├── feedback/                        # 反馈
+│   │   └── index.vue
+│   └── partner-requirement/             # 择偶要求
+│       └── index.vue
+├── components/                          # 组件
+│   └── butlogin.vue                     # 登录按钮组件
+├── utils/                               # 工具函数
+│   ├── api.js                           # API封装(重要!)
+│   ├── chat-api.js                      # 聊天API
+│   ├── userAuth.js                      # 用户认证
+│   ├── tim-manager.js                   # 腾讯云IM管理
+│   ├── tim-presence-manager.js          # 在线状态管理
+│   ├── websocket.js                     # WebSocket封装
+│   ├── bazi.js                          # 八字工具
+│   ├── constellation.js                 # 星座工具
+│   ├── mbti.js                          # MBTI工具
+│   ├── zodiac.js                       # 生肖工具
+│   ├── zodiac-api.js                   # 生肖API
+│   ├── zodiac-enhanced.js              # 生肖增强
+│   └── util.js                         # 通用工具
+├── config/                              # 配置文件
+│   ├── api-config.js                   # API配置(重要!)
+│   ├── match-config.js                 # 匹配配置
+│   └── index.js                        # 主配置
+├── store/                               # 状态管理
+│   └── index.js
+├── pages.json                           # 页面路由配置(重要!)
+├── manifest.json                        # 应用配置
+├── App.vue                              # 根组件
+└── main.js                              # 入口文件
+```
+
+**关键文件说明**:
+- `utils/api.js` - 所有API请求的封装,包含Token处理、错误处理
+- `config/api-config.js` - 第三方API配置(天行数据、极速数据)
+- `pages.json` - 页面路由配置,定义所有页面路径和导航栏
+- `utils/tim-manager.js` - 腾讯云IM SDK封装
+
+### 3.2 管理后台结构 (marriageAdmin-vue)
+
+```
+marriageAdmin-vue/
+├── src/
+│   ├── views/                           # 页面组件
+│   │   ├── Login.vue                    # 登录页
+│   │   ├── Dashboard.vue                # 数据面板
+│   │   ├── banner/                       # 轮播图管理
+│   │   │   └── BannerList.vue
+│   │   ├── announcement/                # 公告管理
+│   │   │   └── AnnouncementList.vue
+│   │   ├── activity/                    # 活动管理
+│   │   │   ├── ActivityList.vue
+│   │   │   ├── ActivityForm.vue
+│   │   │   └── ActivityRegistrations.vue
+│   │   ├── matchmaker/                  # 红娘管理
+│   │   │   ├── MatchmakerList.vue
+│   │   │   ├── MatchmakerForm.vue
+│   │   │   ├── MatchmakerAudit.vue
+│   │   │   ├── CaseAudit.vue
+│   │   │   ├── PointsOrderList.vue
+│   │   │   └── ResourceList.vue
+│   │   ├── course/                      # 课程管理
+│   │   │   ├── CourseList.vue
+│   │   │   ├── CourseForm.vue
+│   │   │   └── CourseDetail.vue
+│   │   ├── success-case/                # 成功案例
+│   │   │   ├── SuccessCaseList.vue
+│   │   │   └── SuccessCaseForm.vue
+│   │   ├── user/                        # 用户管理
+│   │   │   ├── UserList.vue
+│   │   │   └── UserVipList.vue
+│   │   ├── vip/                         # VIP管理
+│   │   │   └── VipPackageList.vue
+│   │   ├── dynamic/                     # 动态管理
+│   │   │   ├── DynamicList.vue
+│   │   │   └── DynamicDetail.vue
+│   │   ├── report/                      # 举报管理
+│   │   │   └── ReportList.vue
+│   │   ├── admin/                       # 管理员管理
+│   │   │   └── AdminUserList.vue
+│   │   └── 404.vue                      # 404页面
+│   ├── components/                      # 通用组件
+│   │   ├── MainLayout.vue               # 主布局
+│   │   ├── Sidebar.vue                  # 侧边栏
+│   │   ├── Header.vue                   # 头部
+│   │   └── ...
+│   ├── router/                          # 路由配置
+│   │   └── index.js                     # 路由定义和守卫(重要!)
+│   ├── stores/                          # Pinia状态管理
+│   │   ├── user.js                      # 用户状态(重要!)
+│   │   └── counter.js
+│   ├── utils/                           # 工具函数
+│   │   └── request.js                   # Axios封装(重要!)
+│   ├── config/                          # 配置文件
+│   │   └── api.js                       # API端点定义(重要!)
+│   ├── assets/                          # 静态资源
+│   │   ├── css/                         # 样式文件
+│   │   └── images/                      # 图片
+│   ├── layouts/                         # 布局组件
+│   │   └── MainLayout.vue
+│   ├── App.vue                          # 根组件
+│   └── main.js                          # 入口文件
+├── public/                              # 公共资源
+├── vite.config.js                       # Vite配置
+├── package.json                         # 依赖配置
+└── README.md                            # 说明文档
+```
+
+**关键文件说明**:
+- `src/config/api.js` - 所有API端点定义
+- `src/utils/request.js` - Axios请求封装,包含Token处理、错误处理
+- `src/router/index.js` - 路由配置和权限守卫
+- `src/stores/user.js` - 用户状态管理(Token、用户信息、权限)
+
+---
+
+## 🔍 四、代码查找指南
+
+### 4.1 如何查找功能代码
+
+#### 查找API接口
+1. **小程序端**: 查看 `LiangZhiYUMao/utils/api.js`
+2. **管理后台**: 查看 `marriageAdmin-vue/src/config/api.js`
+3. **后端接口**: 查看对应服务的 `controller/` 目录
+
+#### 查找业务逻辑
+1. **后端**: 查看对应服务的 `service/impl/` 目录
+2. **前端**: 查看对应页面的 `.vue` 文件
+
+#### 查找数据库操作
+1. **Mapper接口**: 查看 `mapper/` 目录
+2. **SQL映射**: 查看 `resources/mapper/` 目录
+3. **SQL脚本**: 查看 `resources/sql/` 目录
+
+#### 查找配置
+1. **网关路由**: `gateway/src/main/resources/application.yml`
+2. **服务配置**: `service/{module}/src/main/resources/application.yml`
+3. **前端API配置**: `LiangZhiYUMao/config/api-config.js` 或 `marriageAdmin-vue/src/config/api.js`
+
+### 4.2 常见功能代码位置
+
+| 功能 | 后端位置 | 前端位置 |
+|------|---------|---------|
+| 用户登录 | `service/login/` | `LiangZhiYUMao/utils/userAuth.js` |
+| 用户信息 | `service/Essential/controller/UserController.java` | `LiangZhiYUMao/pages/profile/` |
+| 动态管理 | `service/dynamic/controller/DynamicController.java` | `LiangZhiYUMao/pages/plaza/` |
+| 聊天功能 | `service/websocket/` | `LiangZhiYUMao/pages/message/` |
+| 匹配推荐 | `service/randomMatch/` | `LiangZhiYUMao/pages/match/` |
+| 活动管理 | `service/homePage/controller/ActivityController.java` | `LiangZhiYUMao/pages/activities/` |
+| 管理后台登录 | `service/admin/controller/AdminAuthController.java` | `marriageAdmin-vue/src/views/Login.vue` |
+| 数据面板 | `service/admin/controller/DashboardController.java` | `marriageAdmin-vue/src/views/Dashboard.vue` |
+
+---
+
+## 📝 五、代码组织规范
+
+### 5.1 后端代码规范
+
+#### Controller层
+- 职责: 处理HTTP请求,参数校验,调用Service
+- 命名: `XxxController.java`
+- 注解: `@RestController`, `@RequestMapping`
+
+#### Service层
+- 职责: 业务逻辑处理
+- 命名: `XxxService.java` (接口), `XxxServiceImpl.java` (实现)
+- 注解: `@Service`
+
+#### Mapper层
+- 职责: 数据库操作
+- 命名: `XxxMapper.java` (接口), `XxxMapper.xml` (SQL映射)
+- 注解: `@Mapper` 或继承 `BaseMapper<T>`
+
+#### Pojo层
+- 职责: 实体类定义
+- 命名: 实体名(如 `User.java`)
+- 注解: `@Data`, `@TableName`, `@TableId` 等
+
+### 5.2 前端代码规范
+
+#### 小程序端
+- 页面: `pages/` 目录下,使用 `.vue` 文件
+- 组件: `components/` 目录下
+- 工具: `utils/` 目录下
+- 配置: `config/` 目录下
+
+#### 管理后台
+- 页面: `src/views/` 目录下
+- 组件: `src/components/` 目录下
+- 工具: `src/utils/` 目录下
+- 配置: `src/config/` 目录下
+- 状态: `src/stores/` 目录下(Pinia)
+
+---
+
+## 🎯 六、开发建议
+
+### 6.1 添加新功能
+
+#### 后端
+1. 在对应服务的 `pojo/` 创建实体类
+2. 在 `mapper/` 创建Mapper接口
+3. 在 `resources/mapper/` 创建SQL映射文件
+4. 在 `service/` 创建Service接口和实现
+5. 在 `controller/` 创建Controller
+6. 在网关 `application.yml` 添加路由(如需要)
+
+#### 前端(小程序)
+1. 在 `pages/` 创建页面文件
+2. 在 `pages.json` 添加路由配置
+3. 在 `utils/api.js` 添加API方法
+4. 在页面中调用API
+
+#### 前端(管理后台)
+1. 在 `src/views/` 创建页面组件
+2. 在 `src/router/index.js` 添加路由
+3. 在 `src/config/api.js` 添加API端点
+4. 在页面中调用API
+
+### 6.2 修改现有功能
+
+1. **定位代码**: 使用本文档的"代码查找指南"
+2. **理解结构**: 查看相关文件,理解代码组织
+3. **修改代码**: 遵循现有代码规范
+4. **测试验证**: 修改后充分测试
+
+---
+
+## 📚 七、相关文档
+
+- `项目全面分析报告.md` - 详细的项目分析
+- `项目快速参考手册.md` - 快速查找参考
+- `项目架构分析报告.md` - 架构设计说明
+- `技术栈快速参考.md` - 技术栈清单
+
+---
+
+**最后更新**: 2025-01-27
+

+ 101 - 0
修复说明-参数绑定错误.md

@@ -0,0 +1,101 @@
+# 修复说明:参数绑定错误
+
+## 问题描述
+
+访问 `http://localhost:5173/admin/my-resource` 路径时,返回400错误:
+```
+code: 400
+msg: "参数错误: Name for argument of type [java.lang.Integer] not specified, and parameter name information not found in class file either."
+```
+
+## 问题原因
+
+这是Spring Boot参数绑定的经典问题。当Java编译时没有保留参数名信息时,Spring无法正确绑定方法参数,特别是当参数没有明确的注解或注解信息不完整时。
+
+## 修复方案
+
+### 1. 已修复:Maven编译配置
+
+在 `pom.xml` 中添加了 `-parameters` 编译参数,这样Java编译时会保留参数名信息:
+
+```xml
+<plugin>
+  <groupId>org.apache.maven.plugins</groupId>
+  <artifactId>maven-compiler-plugin</artifactId>
+  <version>3.11.0</version>
+  <configuration>
+    <source>8</source>
+    <target>8</target>
+    <parameters>true</parameters>  <!-- 新增:保留参数名信息 -->
+  </configuration>
+</plugin>
+```
+
+### 2. 需要执行的操作
+
+**重要:修复后需要重新编译项目才能生效!**
+
+```bash
+# 在项目根目录执行
+mvn clean compile
+
+# 或者重新打包
+mvn clean package
+
+# 然后重启admin服务
+```
+
+### 3. 验证修复
+
+修复后,访问以下路径应该正常工作:
+- `GET /admin/my-resource/list?page=1&pageSize=10`
+- `GET /admin/my-resource/{id}`
+- `PUT /admin/my-resource/{id}/audit?status=1`
+
+## 技术说明
+
+### 为什么需要 `-parameters` 参数?
+
+在Java 8之前,编译后的class文件默认不包含方法参数名信息。Spring Boot在运行时需要通过参数名来绑定HTTP请求参数,如果class文件中没有参数名信息,就会出现这个错误。
+
+### 解决方案对比
+
+1. **使用 `-parameters` 编译参数**(推荐)
+   - 优点:编译时保留参数名,运行时无需额外配置
+   - 缺点:需要重新编译
+
+2. **使用 `@RequestParam` 明确指定参数名**
+   - 优点:不依赖编译参数
+   - 缺点:代码冗余,每个参数都需要注解
+
+3. **使用 `@RequestParam` 的 `value` 属性**
+   - 优点:明确指定参数名,不依赖编译参数
+   - 缺点:代码冗余
+
+当前代码已经使用了方案2和3(所有参数都有 `@RequestParam` 注解),但为了确保兼容性,还是添加了 `-parameters` 参数。
+
+## 相关文件
+
+- `pom.xml` - Maven编译配置(已修复)
+- `service/admin/src/main/java/com/zhentao/controller/MyResourceController.java` - Controller代码(无需修改)
+
+## 注意事项
+
+1. **必须重新编译**:修改Maven配置后,必须重新编译项目才能生效
+2. **所有服务都需要**:如果其他服务也有类似问题,也需要在对应的pom.xml中添加此配置
+3. **IDE配置**:如果使用IDE(如IntelliJ IDEA),也需要确保IDE的编译配置也启用了参数名保留
+
+## 如果问题仍然存在
+
+如果重新编译后问题仍然存在,请检查:
+
+1. **确认编译成功**:检查编译日志,确认 `-parameters` 参数已生效
+2. **检查Controller代码**:确认所有参数都有 `@RequestParam` 注解
+3. **检查请求参数**:确认前端发送的请求参数名称与后端一致
+4. **查看完整错误日志**:查看后端服务的完整错误日志,获取更多信息
+
+---
+
+**修复时间**: 2025-01-27  
+**修复人**: Auto
+

+ 739 - 0
项目全面分析报告.md

@@ -0,0 +1,739 @@
+# XINxiangqin 项目全面分析报告
+
+**生成时间**: 2025-01-27  
+**项目类型**: 婚恋社交平台(微服务架构)  
+**分析范围**: 项目结构、技术栈、代码组织、功能模块、配置管理
+
+---
+
+## 📋 一、项目整体架构
+
+### 1.1 项目结构概览
+
+```
+XINxiangqin/
+├── gateway/                    # API网关服务 (Spring Cloud Gateway)
+│   ├── src/main/java/         # 网关核心代码
+│   └── src/main/resources/    # 网关配置(路由、CORS等)
+│
+├── common/                     # 公共模块
+│   └── src/main/java/         # 公共实体类、Mapper、Service
+│       ├── pojo/              # 实体类(Users, PartnerRequirement等)
+│       ├── mapper/            # 数据访问层
+│       └── service/           # 公共服务接口
+│
+├── service/                    # 业务服务模块(Maven聚合模块)
+│   ├── login/                 # 登录服务
+│   ├── homePage/              # 首页服务 (端口: 8081)
+│   ├── dynamic/               # 动态服务 (端口: 8086)
+│   ├── websocket/             # WebSocket聊天服务 (端口: 1004)
+│   ├── randomMatch/           # 随机匹配服务 (端口: 1003)
+│   ├── Essential/             # 基础服务 (端口: 1005)
+│   ├── Recommend/            # 推荐服务 (端口: 8089)
+│   └── admin/                 # 管理端服务 (端口: 8088)
+│
+├── LiangZhiYUMao/             # 小程序前端 (uni-app)
+│   ├── pages/                 # 页面文件
+│   ├── components/            # 组件
+│   ├── utils/                 # 工具函数(API封装、认证等)
+│   ├── config/                # 配置文件(API配置、匹配配置)
+│   └── store/                 # 状态管理
+│
+└── marriageAdmin-vue/         # 管理后台 (Vue 3)
+    ├── src/
+    │   ├── views/             # 页面组件
+    │   ├── components/        # 通用组件
+    │   ├── router/            # 路由配置
+    │   ├── stores/            # Pinia状态管理
+    │   ├── utils/             # 工具函数
+    │   └── config/            # 配置文件
+    └── vite.config.js         # Vite构建配置
+```
+
+### 1.2 微服务架构设计
+
+**架构模式**: Spring Cloud Gateway + 多服务模块
+
+- **网关层**: 统一入口,路由转发,CORS处理
+- **服务层**: 按业务领域划分的独立服务
+- **公共层**: 共享实体类和工具类
+- **前端层**: 小程序端 + 管理后台
+
+---
+
+## 🔧 二、技术栈详细分析
+
+### 2.1 后端技术栈
+
+| 技术 | 版本 | 用途 | 位置 |
+|------|------|------|------|
+| **Java** | 8 | 开发语言 | 所有后端服务 |
+| **Spring Boot** | 2.7.15 | 核心框架 | 所有服务 |
+| **Spring Cloud** | 2021.0.8 | 微服务框架 | Gateway |
+| **Spring Cloud Alibaba** | 2021.0.5.0 | 阿里云组件 | Gateway |
+| **MyBatis-Plus** | 3.5.3 | ORM框架 | 所有服务 |
+| **MySQL** | 8.0.33 | 数据库 | 所有服务 |
+| **Redis** | - | 缓存/Token存储 | 所有服务 |
+| **MinIO** | 7.1.0 | 对象存储 | 文件上传服务 |
+| **Lombok** | 1.18.30 | 代码简化 | 所有服务 |
+| **Maven** | - | 构建工具 | 所有后端模块 |
+
+**特殊依赖**:
+- **腾讯云IM SDK** (tim-wx-sdk 2.27.6): 用于小程序聊天功能
+- **WebSocket**: 实时聊天服务
+
+### 2.2 前端技术栈
+
+#### 小程序端 (LiangZhiYUMao)
+| 技术 | 版本 | 用途 |
+|------|------|------|
+| **uni-app** | - | 跨平台框架 |
+| **Vue** | 2 | 前端框架 |
+| **Axios** | 1.12.2 | HTTP客户端 |
+| **tim-wx-sdk** | 2.27.6 | 腾讯云IM SDK |
+
+#### 管理后台 (marriageAdmin-vue)
+| 技术 | 版本 | 用途 |
+|------|------|------|
+| **Vue** | 3.5.22 | 前端框架 |
+| **Element Plus** | 2.11.5 | UI组件库 |
+| **Pinia** | 3.0.3 | 状态管理 |
+| **Vue Router** | 4.5.1 | 路由管理 |
+| **Vite** | 7.1.7 | 构建工具 |
+| **ECharts** | 6.0.0 | 图表库 |
+| **Axios** | 1.12.2 | HTTP客户端 |
+
+---
+
+## 🌐 三、服务端口与路由配置
+
+### 3.1 服务端口分配
+
+| 服务名称 | 端口 | 说明 |
+|---------|------|------|
+| **Gateway** | 8083 | API网关(统一入口) |
+| **HomePage** | 8081 | 首页服务 |
+| **Dynamic** | 8086 | 动态服务 |
+| **WebSocket** | 1004 | 聊天服务 |
+| **RandomMatch** | 1003 | 匹配服务 |
+| **Essential** | 1005 | 基础服务(用户信息等) |
+| **Recommend** | 8089 | 推荐服务 |
+| **Admin** | 8088 | 管理端服务 |
+
+### 3.2 网关路由配置(关键路由)
+
+| 路由路径 | 目标服务 | 端口 | 说明 |
+|---------|---------|------|------|
+| `/admin/**` | admin服务 | 8088 | 管理端页面和API |
+| `/api/admin/**` | admin服务 | 8088 | 管理端API |
+| `/ws/chat/**` | websocket服务 | 1004 | WebSocket聊天 |
+| `/api/chat/**` | websocket服务 | 1004 | 聊天REST API |
+| `/api/online/**` | websocket服务 | 1004 | 在线状态API |
+| `/api/im/**` | websocket服务 | 1004 | IM服务(腾讯云IM) |
+| `/match/**` | randomMatch服务 | 1003 | 匹配服务 |
+| `/api/dynamic/**` | dynamic服务 | 8086 | 动态服务 |
+| `/media/**` | dynamic服务 | 8086 | 媒体文件 |
+| `/api/message/**` | dynamic服务 | 8086 | 系统消息 |
+| `/api/user/**` | Essential服务 | 1005 | 用户基础服务 |
+| `/api/checkin/**` | Essential服务 | 1005 | 签到服务 |
+| `/api/feedback/**` | Essential服务 | 1005 | 反馈服务 |
+| `/api/partner-requirement/**` | Essential服务 | 1005 | 择偶要求 |
+| `/api/announcement/**` | homePage服务 | 8081 | 公告服务 |
+| `/api/course/**` | homePage服务 | 8081 | 课程服务 |
+| `/api/matchmaker-course/**` | homePage服务 | 8081 | 红娘课程 |
+| `/api/recommend/**` | Recommend服务 | 8089 | 推荐服务 |
+| `/api/course-order/**` | Essential服务 | 1005 | 课程订单(微信支付) |
+| `/api/my-resource/**` | homePage服务 | 8081 | 我的资源(线索管理) |
+| `/api/points/**` | admin服务 | 8088 | 积分商城 |
+| `/api/success-case-upload/**` | websocket服务 | 1004 | 成功案例上传 |
+| `/api/**` | homePage服务 | 8081 | 首页服务(兜底路由) |
+
+**路由优先级**: 路由配置顺序很重要,更具体的路由必须在通用路由之前。
+
+---
+
+## 📱 四、功能模块详细分析
+
+### 4.1 小程序端功能 (LiangZhiYUMao)
+
+#### 核心功能模块
+
+**1. 用户认证模块**
+- 微信授权登录
+- 手机号绑定
+- 身份证验证
+- 用户资料管理
+- 位置: `pages/settings/`, `utils/userAuth.js`
+
+**2. 匹配推荐模块**
+- 今日推荐 (`pages/today-recommend/`)
+- 智能推荐 (`pages/recommend/`)
+- 在线匹配 (`pages/match/`)
+- 随机匹配
+- 对象要求设置 (`pages/partner-requirement/`)
+- 位置: `pages/recommend/`, `pages/match/`, `config/match-config.js`
+
+**3. 社交功能模块**
+- 动态广场 (`pages/plaza/`)
+  - 发布动态
+  - 浏览动态
+  - 点赞、评论
+  - 举报功能
+- 消息系统 (`pages/message/`)
+  - 聊天 (`pages/message/chat.vue`)
+  - 系统消息 (`pages/message/system.vue`)
+- 黑名单管理 (`pages/blacklist/`)
+- 位置: `pages/plaza/`, `pages/message/`, `utils/chat-api.js`
+
+**4. 星命运算模块** ⭐
+- 属相测算 (`pages/astrology/zodiac.vue`)
+- 星座卡片 (`pages/astrology/constellation.vue`)
+- 星座配对 (`pages/astrology/constellation-match.vue`)
+- 八字测算 (`pages/astrology/bazi.vue`)
+- 八字配对 (`pages/astrology/bazi-match.vue`)
+- MBTI测试 (`pages/astrology/mbti.vue`)
+- 位置: `pages/astrology/`, `utils/bazi.js`, `utils/constellation.js`, `utils/mbti.js`
+- 第三方API: 天行数据、极速数据
+
+**5. 服务模块**
+- 红娘服务 (`pages/matchmakers/`)
+- 活动管理 (`pages/activities/`)
+- 精品课程 (`pages/courses/`)
+- 成功案例 (`pages/success-case/`)
+- 兼职红娘申请 (`pages/part-time-matchmaker/`)
+- 位置: `pages/matchmakers/`, `pages/activities/`, `pages/courses/`
+
+**6. 会员功能**
+- VIP会员 (`pages/vip/`)
+- 私人定制 (`pages/customize/`)
+- 位置: `pages/vip/`, `pages/customize/`
+
+**7. 红娘工作台** (`pages/matchmaker-workbench/`)
+- 我的资源
+- 信息录入
+- 精准匹配
+- 优质资源
+- 积分商城
+- 培训课程
+- 红娘排行榜
+- 消息管理
+- 审核记录
+
+**8. 个人中心**
+- 我的资料 (`pages/profile/`)
+- 我的活动 (`pages/mine/my-activities.vue`)
+- 我的动态 (`pages/mine/my-dynamics.vue`)
+- 设置中心 (`pages/settings/`)
+
+### 4.2 管理后台功能 (marriageAdmin-vue)
+
+#### 管理功能模块
+
+**1. 数据面板** (`views/Dashboard.vue`)
+- 用户统计
+- 活动统计
+- 收入趋势
+- ECharts图表展示
+- **权限**: 仅超级管理员可访问
+
+**2. 内容管理**
+- 轮播图管理 (`views/banner/`)
+- 小喇叭公告管理 (`views/announcement/`)
+- 活动管理 (`views/activity/`)
+  - 活动列表
+  - 创建/编辑活动
+  - 报名统计
+- 红娘管理 (`views/matchmaker/`)
+  - 红娘列表
+  - 红娘审核
+  - 案例审核
+  - 积分商城订单
+  - 线索管理
+- 课程管理 (`views/course/`)
+- 成功案例管理 (`views/success-case/`)
+
+**3. 用户管理**
+- 用户列表 (`views/user/UserList.vue`)
+- VIP用户管理 (`views/user/UserVipList.vue`)
+- VIP套餐管理 (`views/vip/VipPackageList.vue`)
+
+**4. 审核管理**
+- 动态审核 (`views/dynamic/`)
+- 举报处理 (`views/report/`)
+
+**5. 管理员管理**
+- 管理员列表 (`views/admin/AdminUserList.vue`)
+- 角色权限管理
+
+---
+
+## 🗄️ 五、数据库设计分析
+
+### 5.1 核心数据表
+
+#### 用户相关
+- **users**: 用户主表
+  - 字段: userId, phone(加密), email, nickname, password(bcrypt), gender, birthDate, avatarUrl, status等
+  - 安全: 手机号AES-256加密,密码bcrypt加密
+- **user_profile**: 用户扩展信息表
+  - 字段: 房产、车辆、身高、体重、星座、生肖、学历、工作、薪资等
+  - 认证: 实名认证、学历认证、工作认证、婚姻状态认证
+- **vip**: VIP会员表
+- **blacklist**: 黑名单表
+- **checkin**: 签到表
+
+#### 社交相关
+- **user_dynamics**: 用户动态表
+- **dynamic_comments**: 动态评论表
+- **dynamic_likes**: 动态点赞表
+- **dynamic_reports**: 动态举报表
+
+#### 聊天相关
+- **chat_messages**: 聊天消息表
+- **chat_sessions**: 聊天会话表
+
+#### 业务相关
+- **activities**: 活动表
+- **activity_registrations**: 活动报名表
+- **matchmakers**: 红娘表
+- **courses**: 课程表
+- **success_cases**: 成功案例表
+- **announcements**: 公告表
+- **banners**: 轮播图表
+- **my_resource**: 线索表(红娘资源)
+- **my_resource_tag**: 线索标签表
+
+#### 管理相关
+- **admin_users**: 管理员用户表
+- **roles**: 角色表
+- **permissions**: 权限表
+- **user_roles**: 用户角色关联表
+- **role_permissions**: 角色权限关联表
+
+### 5.2 数据库脚本位置
+
+- `service/admin/src/main/resources/sql/` - 管理端相关表
+- `service/dynamic/src/main/resources/sql/` - 动态相关表
+- `service/Essential/src/main/resources/sql/` - 用户相关表
+- `service/websocket/src/main/resources/sql/` - 聊天相关表
+- `service/homePage/src/main/resources/sql/` - 首页相关表
+
+---
+
+## 🔐 六、安全机制分析
+
+### 6.1 数据安全
+
+| 安全措施 | 实现方式 | 位置 |
+|---------|---------|------|
+| **手机号加密** | AES-256加密 | Essential服务 |
+| **密码加密** | bcrypt哈希 | Login服务 |
+| **Token认证** | JWT Token + Redis存储 | 所有服务 |
+| **CORS配置** | 网关统一处理 | Gateway配置 |
+| **跨域** | 网关配置 | Gateway配置 |
+
+### 6.2 认证流程
+
+**小程序端**:
+1. 微信授权登录 → 获取openid/unionid
+2. 后端验证 → 生成Token
+3. Token存储在Redis中,设置过期时间
+4. 后续请求携带Token
+
+**管理后台**:
+1. 用户名密码登录
+2. 后端验证 → 生成Token
+3. Token存储在Redis中
+4. 路由守卫验证Token
+
+---
+
+## 📡 七、API接口设计
+
+### 7.1 接口规范
+
+- **统一响应格式**: `{code, message, data}`
+- **统一错误处理**: 网关层统一处理
+- **RESTful风格**: 遵循REST规范
+
+### 7.2 主要接口分类
+
+#### 用户相关 (`/api/user/**`)
+- 用户信息查询
+- 用户资料管理
+- 签到功能
+
+#### 动态相关 (`/api/dynamic/**`)
+- 动态CRUD
+- 点赞、评论
+- 举报功能
+
+#### 匹配相关 (`/match/**`)
+- 匹配算法
+- 推荐服务
+
+#### 聊天相关
+- `/api/chat/**` - 聊天消息REST API
+- `/ws/chat/**` - WebSocket实时聊天
+- `/api/im/**` - 腾讯云IM相关
+
+#### 管理相关
+- `/admin/**` - 管理端页面和API
+- `/api/admin/**` - 管理端API
+
+---
+
+## 🔧 八、关键配置文件位置
+
+### 8.1 后端配置
+
+| 配置文件 | 位置 | 说明 |
+|---------|------|------|
+| **网关配置** | `gateway/src/main/resources/application.yml` | 路由规则、CORS配置 |
+| **服务配置** | `service/*/src/main/resources/application.yml` | 各服务独立配置 |
+| **Maven父POM** | `pom.xml` | 依赖版本管理 |
+| **服务聚合POM** | `service/pom.xml` | 服务模块聚合 |
+
+### 8.2 前端配置
+
+| 配置文件 | 位置 | 说明 |
+|---------|------|------|
+| **小程序API配置** | `LiangZhiYUMao/config/api-config.js` | 天行数据、极速数据API Key |
+| **小程序路由** | `LiangZhiYUMao/pages.json` | 页面路由配置 |
+| **小程序匹配配置** | `LiangZhiYUMao/config/match-config.js` | 匹配算法配置 |
+| **管理后台API配置** | `marriageAdmin-vue/src/config/api.js` | API端点定义 |
+| **管理后台路由** | `marriageAdmin-vue/src/router/index.js` | 路由配置和守卫 |
+| **Vite配置** | `marriageAdmin-vue/vite.config.js` | 构建配置 |
+
+---
+
+## 📂 九、核心代码文件索引
+
+### 9.1 后端核心代码
+
+#### 公共模块
+- **实体类**: `common/src/main/java/com/zhentao/pojo/`
+  - `Users.java` - 用户实体
+  - `PartnerRequirement.java` - 择偶要求实体
+  - `MatchmakerApply.java` - 红娘申请实体
+
+#### 各服务核心代码结构
+```
+service/{module}/src/main/java/com/zhentao/
+├── controller/          # 控制器层(REST API)
+├── service/             # 服务层(业务逻辑)
+├── mapper/              # 数据访问层(MyBatis)
+├── pojo/                # 实体类
+└── config/              # 配置类
+```
+
+### 9.2 前端核心代码
+
+#### 小程序端
+- **API封装**: `LiangZhiYUMao/utils/api.js`
+- **聊天API**: `LiangZhiYUMao/utils/chat-api.js`
+- **用户认证**: `LiangZhiYUMao/utils/userAuth.js`
+- **腾讯云IM**: `LiangZhiYUMao/utils/tim-manager.js`
+- **工具函数**: `LiangZhiYUMao/utils/util.js`
+
+#### 管理后台
+- **API请求**: `marriageAdmin-vue/src/utils/request.js`
+- **状态管理**: `marriageAdmin-vue/src/stores/user.js`
+- **路由配置**: `marriageAdmin-vue/src/router/index.js`
+
+---
+
+## 🎯 十、第三方服务集成
+
+### 10.1 第三方API
+
+| 服务 | 用途 | 配置位置 | API Key位置 |
+|------|------|---------|------------|
+| **天行数据API** | 星座、生肖配对、八字查询 | `LiangZhiYUMao/config/api-config.js` | TIANAPI_CONFIG.API_KEY |
+| **极速数据API** | 专业八字排盘 | `LiangZhiYUMao/config/api-config.js` | BAZI_API_CONFIG.JISU_API.API_KEY |
+| **腾讯云IM** | 实时聊天功能 | `LiangZhiYUMao/utils/tim-manager.js` | 需配置SDKAppID和密钥 |
+
+### 10.2 第三方服务配置
+
+**天行数据API配置**:
+```javascript
+// LiangZhiYUMao/config/api-config.js
+export const TIANAPI_CONFIG = {
+  API_KEY: '23e38be1fddb7c6aee4ea3e4294c6b4a', // ⚠️ 需要替换为真实Key
+  BASE_URL: 'https://apis.tianapi.com',
+  ENDPOINTS: {
+    zodiacMatch: '/zodiac/index',      // 生肖配对
+    constellation: '/star/index',       // 星座运势
+    constellationMatch: '/xingzuo/index', // 星座配对
+    bazi: '/bazi/index'                 // 八字查询
+  }
+}
+```
+
+**极速数据API配置**:
+```javascript
+// LiangZhiYUMao/config/api-config.js
+export const BAZI_API_CONFIG = {
+  JISU_API: {
+    BASE_URL: 'https://api.jisuapi.com',
+    API_KEY: '34bf028ce1b089a9', // ⚠️ 需要替换为真实Key
+    ENDPOINTS: {
+      bazi: '/bazi/paipan'  // 八字排盘
+    }
+  }
+}
+```
+
+---
+
+## ⚠️ 十一、潜在问题与改进建议
+
+### 11.1 架构层面
+
+**当前问题**:
+1. ❌ 服务发现: 使用硬编码URI,未引入服务注册中心
+2. ❌ 配置中心: 配置分散在各服务,未统一管理
+3. ❌ 监控体系: 缺少服务监控和链路追踪
+4. ❌ API文档: 未集成Swagger/Knife4j
+5. ❌ 测试覆盖: 缺少单元测试和集成测试
+
+**改进建议**:
+1. ✅ 引入Nacos或Eureka作为服务注册中心
+2. ✅ 使用Nacos Config或Spring Cloud Config统一管理配置
+3. ✅ 集成Spring Boot Admin或Prometheus监控
+4. ✅ 集成Swagger/Knife4j生成API文档
+5. ✅ 添加单元测试和集成测试
+
+### 11.2 代码层面
+
+**当前问题**:
+1. ❌ 异常处理: 各服务异常处理不统一
+2. ❌ API版本控制: 未引入版本号管理
+3. ❌ 参数校验: 未统一使用Bean Validation
+4. ❌ 日志规范: 日志格式和级别不统一
+
+**改进建议**:
+1. ✅ 统一异常处理机制(全局异常处理器)
+2. ✅ 引入API版本号(如 `/api/v1/user/**`)
+3. ✅ 统一使用Bean Validation进行参数校验
+4. ✅ 统一日志格式和级别(使用Logback或Log4j2)
+
+### 11.3 功能层面
+
+**待优化功能**:
+1. 🔍 搜索功能: 用户搜索、动态搜索
+2. 🤖 推荐算法优化: 基于机器学习的推荐
+3. 📊 数据分析: 用户行为分析、匹配成功率分析
+4. 📱 消息推送: 集成推送服务(极光推送等)
+5. 💰 支付功能: 集成支付功能(微信支付、支付宝)
+
+### 11.4 安全层面
+
+**待加强**:
+1. 🔒 API限流: 防止接口被恶意调用
+2. 🔒 熔断降级: 服务异常时的降级策略
+3. 🔒 数据脱敏: 敏感数据展示时脱敏
+4. 🔒 操作日志: 记录关键操作日志
+
+---
+
+## 🚀 十二、启动与部署指南
+
+### 12.1 启动顺序
+
+**1. 基础服务**
+- MySQL数据库
+- Redis缓存
+- MinIO对象存储
+
+**2. 后端服务**(按依赖顺序)
+1. common模块(公共模块)
+2. Essential服务(基础服务)
+3. HomePage服务
+4. Dynamic服务
+5. WebSocket服务
+6. RandomMatch服务
+7. Recommend服务
+8. Admin服务
+9. Gateway网关(最后启动)
+
+**3. 前端服务**
+- 管理后台: `cd marriageAdmin-vue && npm run dev` (端口: 5173)
+- 小程序: 使用HBuilderX或微信开发者工具
+
+### 12.2 环境配置检查清单
+
+- [ ] MySQL数据库连接配置
+- [ ] Redis连接配置
+- [ ] MinIO配置(文件上传)
+- [ ] 网关路由配置
+- [ ] 各服务端口配置
+- [ ] 小程序API地址配置
+- [ ] 管理后台API地址配置
+- [ ] 第三方API Key配置(天行数据、极速数据)
+- [ ] 腾讯云IM配置(SDKAppID、密钥)
+
+---
+
+## 🔍 十三、问题诊断指南
+
+### 13.1 常见问题排查
+
+| 问题 | 检查项 | 解决方案 |
+|------|--------|---------|
+| **跨域问题** | 网关CORS配置 | 检查 `gateway/application.yml` 中的CORS配置 |
+| **路由404** | 网关路由配置顺序 | 确保具体路由在通用路由之前 |
+| **Token失效** | Redis连接、Token过期时间 | 检查Redis连接和Token配置 |
+| **API调用失败** | 服务端口、网关路由 | 检查服务是否启动,路由是否正确 |
+| **文件上传失败** | MinIO配置和权限 | 检查MinIO配置和存储桶权限 |
+| **聊天功能异常** | 腾讯云IM配置 | 检查SDKAppID和密钥配置 |
+
+### 13.2 调试建议
+
+1. **开启网关DEBUG日志**: 查看路由转发情况
+2. **检查各服务日志**: 查看具体错误信息
+3. **使用Postman测试API**: 验证接口是否正常
+4. **检查Redis和MySQL连接**: 确保数据库连接正常
+5. **查看小程序网络请求日志**: 检查前端请求
+
+---
+
+## 📚 十四、相关文档索引
+
+项目包含的文档:
+- ✅ `项目架构分析报告.md` - 项目架构详细分析
+- ✅ `技术栈快速参考.md` - 技术栈快速查询
+- ✅ `LiangZhiYUMao/API接口说明.md` - API接口文档
+- ✅ `marriageAdmin-vue/README.md` - 管理后台说明
+- ✅ `marriageAdmin-vue/美化方案说明.md` - UI美化方案
+- ✅ `marriageAdmin-vue/数据展示优化说明.md` - 数据展示优化
+
+---
+
+## 🎯 十五、后续修改建议(优先级排序)
+
+### 高优先级 🔴
+
+1. **统一异常处理机制**
+   - 位置: 各服务的Controller层
+   - 建议: 创建全局异常处理器
+
+2. **API版本控制**
+   - 位置: 网关路由配置
+   - 建议: 引入 `/api/v1/` 版本前缀
+
+3. **参数校验统一**
+   - 位置: Controller层
+   - 建议: 使用 `@Valid` 和 `@NotNull` 等注解
+
+4. **日志规范统一**
+   - 位置: 所有服务
+   - 建议: 统一日志格式和级别
+
+### 中优先级 🟡
+
+1. **服务注册发现**
+   - 位置: 网关和服务配置
+   - 建议: 引入Nacos
+
+2. **配置中心**
+   - 位置: 所有服务配置
+   - 建议: 使用Nacos Config
+
+3. **API文档**
+   - 位置: 各服务Controller
+   - 建议: 集成Swagger/Knife4j
+
+### 低优先级 🟢
+
+1. **监控体系**
+   - 建议: 集成Spring Boot Admin
+
+2. **测试覆盖**
+   - 建议: 添加单元测试和集成测试
+
+3. **功能扩展**
+   - 搜索功能
+   - 推荐算法优化
+   - 消息推送
+   - 支付功能
+
+---
+
+## 📝 十六、关键文件快速索引
+
+### 后端关键文件
+
+| 文件路径 | 说明 |
+|---------|------|
+| `pom.xml` | Maven父项目配置 |
+| `gateway/src/main/resources/application.yml` | 网关路由配置 |
+| `service/*/src/main/resources/application.yml` | 各服务配置 |
+| `common/src/main/java/com/zhentao/pojo/` | 公共实体类 |
+
+### 前端关键文件
+
+| 文件路径 | 说明 |
+|---------|------|
+| `LiangZhiYUMao/config/api-config.js` | 小程序API配置 |
+| `LiangZhiYUMao/utils/api.js` | 小程序API封装 |
+| `LiangZhiYUMao/pages.json` | 小程序路由配置 |
+| `marriageAdmin-vue/src/config/api.js` | 管理后台API配置 |
+| `marriageAdmin-vue/src/router/index.js` | 管理后台路由配置 |
+
+---
+
+## 📊 十七、项目统计信息
+
+### 代码规模(估算)
+
+- **后端Java文件**: ~500+ 个
+- **前端Vue文件**: ~100+ 个
+- **数据库表**: ~30+ 个
+- **API接口**: ~200+ 个
+- **服务模块**: 8个微服务 + 1个网关
+
+### 功能模块统计
+
+- **小程序页面**: ~50+ 个
+- **管理后台页面**: ~30+ 个
+- **核心业务模块**: 10+ 个
+
+---
+
+## 🎓 十八、学习与开发建议
+
+### 对于新开发者
+
+1. **先理解整体架构**: 阅读 `项目架构分析报告.md`
+2. **熟悉技术栈**: 查看 `技术栈快速参考.md`
+3. **从简单模块开始**: 建议从 `common` 模块开始
+4. **理解路由配置**: 重点理解网关路由配置
+5. **熟悉API调用**: 查看小程序和管理后台的API封装
+
+### 对于维护者
+
+1. **关注配置文件**: 修改前先查看相关配置
+2. **理解服务依赖**: 注意服务启动顺序
+3. **查看日志**: 遇到问题先查看日志
+4. **测试验证**: 修改后充分测试
+
+---
+
+**报告生成时间**: 2025-01-27  
+**分析工具**: 代码扫描 + 配置文件分析  
+**建议更新频率**: 每次重大架构变更后更新
+
+---
+
+## 📞 技术支持
+
+如有问题,请参考:
+1. 项目文档目录下的相关文档
+2. 各服务的README文件
+3. 代码注释和配置说明
+
+---
+
+**祝开发顺利!** 🚀
+

+ 169 - 0
项目快速参考手册.md

@@ -0,0 +1,169 @@
+# XINxiangqin 项目快速参考手册
+
+> 快速查找项目关键信息,方便日常开发使用
+
+---
+
+## 🚀 快速启动
+
+### 启动顺序
+```
+1. MySQL + Redis + MinIO
+2. common模块
+3. Essential(1005) → HomePage(8081) → Dynamic(8086) → WebSocket(1004) → RandomMatch(1003) → Recommend(8089) → Admin(8088)
+4. Gateway(8083) - 最后启动
+5. 前端: marriageAdmin-vue (npm run dev)
+```
+
+---
+
+## 📍 服务端口速查
+
+| 服务 | 端口 | 说明 |
+|------|------|------|
+| Gateway | 8083 | 统一入口 |
+| HomePage | 8081 | 首页 |
+| Dynamic | 8086 | 动态 |
+| WebSocket | 1004 | 聊天 |
+| RandomMatch | 1003 | 匹配 |
+| Essential | 1005 | 用户基础 |
+| Recommend | 8089 | 推荐 |
+| Admin | 8088 | 管理端 |
+
+---
+
+## 🔗 常用路由速查
+
+| 路由 | 服务 | 说明 |
+|------|------|------|
+| `/admin/**` | Admin(8088) | 管理端 |
+| `/api/admin/**` | Admin(8088) | 管理API |
+| `/api/user/**` | Essential(1005) | 用户服务 |
+| `/api/dynamic/**` | Dynamic(8086) | 动态服务 |
+| `/api/chat/**` | WebSocket(1004) | 聊天API |
+| `/ws/chat/**` | WebSocket(1004) | WebSocket |
+| `/match/**` | RandomMatch(1003) | 匹配服务 |
+| `/api/recommend/**` | Recommend(8089) | 推荐服务 |
+| `/api/course/**` | HomePage(8081) | 课程服务 |
+
+---
+
+## 📁 关键文件位置
+
+### 配置文件
+- 网关路由: `gateway/src/main/resources/application.yml`
+- 小程序API: `LiangZhiYUMao/config/api-config.js`
+- 管理后台API: `marriageAdmin-vue/src/config/api.js`
+- 小程序路由: `LiangZhiYUMao/pages.json`
+- 管理后台路由: `marriageAdmin-vue/src/router/index.js`
+
+### 核心代码
+- 公共实体: `common/src/main/java/com/zhentao/pojo/`
+- 小程序API封装: `LiangZhiYUMao/utils/api.js`
+- 管理后台请求: `marriageAdmin-vue/src/utils/request.js`
+
+---
+
+## 🔑 第三方API配置
+
+### 天行数据API
+- 配置文件: `LiangZhiYUMao/config/api-config.js`
+- API Key: `TIANAPI_CONFIG.API_KEY`
+- 用途: 星座、生肖、八字
+
+### 极速数据API
+- 配置文件: `LiangZhiYUMao/config/api-config.js`
+- API Key: `BAZI_API_CONFIG.JISU_API.API_KEY`
+- 用途: 专业八字排盘
+
+---
+
+## 🗄️ 核心数据表
+
+| 表名 | 说明 | 位置 |
+|------|------|------|
+| `users` | 用户主表 | Essential服务 |
+| `user_profile` | 用户扩展信息 | Essential服务 |
+| `user_dynamics` | 用户动态 | Dynamic服务 |
+| `chat_messages` | 聊天消息 | WebSocket服务 |
+| `activities` | 活动 | HomePage服务 |
+| `matchmakers` | 红娘 | HomePage服务 |
+| `admin_users` | 管理员 | Admin服务 |
+
+---
+
+## 🐛 常见问题排查
+
+| 问题 | 检查项 |
+|------|--------|
+| 跨域 | 网关CORS配置 |
+| 路由404 | 网关路由顺序 |
+| Token失效 | Redis连接 |
+| API失败 | 服务端口+路由 |
+| 文件上传 | MinIO配置 |
+
+---
+
+## 📝 代码规范
+
+### 后端
+- Controller层: RESTful风格
+- Service层: 业务逻辑
+- Mapper层: 数据访问
+- 统一响应: `{code, message, data}`
+
+### 前端
+- 小程序: uni-app规范
+- 管理后台: Vue3 Composition API
+- API调用: 统一封装
+
+---
+
+## 🔐 安全机制
+
+- 手机号: AES-256加密
+- 密码: bcrypt哈希
+- Token: JWT + Redis
+- CORS: 网关统一处理
+
+---
+
+## 📊 项目规模
+
+- 后端服务: 8个微服务 + 1个网关
+- 前端页面: 小程序50+,管理后台30+
+- 数据库表: 30+
+- API接口: 200+
+
+---
+
+## 🎯 修改建议优先级
+
+### 高优先级 🔴
+1. 统一异常处理
+2. API版本控制
+3. 参数校验统一
+4. 日志规范统一
+
+### 中优先级 🟡
+1. 服务注册发现(Nacos)
+2. 配置中心(Nacos Config)
+3. API文档(Swagger)
+
+### 低优先级 🟢
+1. 监控体系
+2. 测试覆盖
+3. 功能扩展
+
+---
+
+## 📚 相关文档
+
+- `项目全面分析报告.md` - 详细分析
+- `项目架构分析报告.md` - 架构分析
+- `技术栈快速参考.md` - 技术栈
+
+---
+
+**最后更新**: 2025-01-27
+