Explorar o código

我的资源模块 跟进部分和精准匹配

yuxy hai 1 mes
pai
achega
03745c783f
Modificáronse 19 ficheiros con 2208 adicións e 129 borrados
  1. 353 17
      LiangZhiYUMao/pages/matchmaker-workbench/client-detail.vue
  2. 238 60
      LiangZhiYUMao/pages/matchmaker-workbench/my-resources.vue
  3. 253 49
      LiangZhiYUMao/pages/matchmaker-workbench/precise-match.vue
  4. 283 0
      service/homePage/src/main/java/com/zhentao/controller/MyResourceController.java
  5. 82 0
      service/homePage/src/main/java/com/zhentao/entity/FollowUpStatistics.java
  6. 94 0
      service/homePage/src/main/java/com/zhentao/entity/UserFollowUp.java
  7. 26 0
      service/homePage/src/main/java/com/zhentao/mapper/FollowUpStatisticsMapper.java
  8. 17 0
      service/homePage/src/main/java/com/zhentao/mapper/MyResourceMapper.java
  9. 26 0
      service/homePage/src/main/java/com/zhentao/mapper/UserFollowUpMapper.java
  10. 39 0
      service/homePage/src/main/java/com/zhentao/service/FollowUpStatisticsService.java
  11. 17 0
      service/homePage/src/main/java/com/zhentao/service/MyResourceService.java
  12. 28 0
      service/homePage/src/main/java/com/zhentao/service/UserFollowUpService.java
  13. 145 0
      service/homePage/src/main/java/com/zhentao/service/impl/FollowUpStatisticsServiceImpl.java
  14. 412 0
      service/homePage/src/main/java/com/zhentao/service/impl/MyResourceServiceImpl.java
  15. 53 0
      service/homePage/src/main/java/com/zhentao/service/impl/UserFollowUpServiceImpl.java
  16. 5 0
      service/homePage/src/main/java/com/zhentao/vo/ClientDetailVO.java
  17. 102 0
      service/homePage/src/main/java/com/zhentao/vo/MatchResultVO.java
  18. 15 0
      service/homePage/src/main/java/com/zhentao/vo/MyResourceVO.java
  19. 20 3
      service/homePage/src/main/resources/mapper/MyResourceMapper.xml

+ 353 - 17
LiangZhiYUMao/pages/matchmaker-workbench/client-detail.vue

@@ -12,7 +12,12 @@
 			<image :src="clientInfo.avatar" class="client-avatar" mode="aspectFill"></image>
 			<view class="basic-info">
 				<text class="client-name">{{ clientInfo.gender }} · {{ clientInfo.name }}</text>
-				<text class="client-status">{{ clientInfo.status }}</text>
+				<view class="status-tag-wrapper">
+					<text class="status-tag register-tag registered">已注册</text>
+					<text class="status-tag match-tag" :class="{ 'matched': followForm.matchStatus === 1, 'unmatched': followForm.matchStatus === 0 || followForm.matchStatus === null }">
+						{{ followForm.matchStatus === 1 ? '已匹配' : '未匹配' }}
+					</text>
+				</view>
 				<view class="client-tags">
 					<text class="tag" v-for="(tag, index) in clientInfo.tags" :key="index">{{ tag }}</text>
 				</view>
@@ -92,7 +97,10 @@
 			</view>
 			<view class="detail-item">
 				<text class="detail-label">收入:</text>
-				<text class="detail-value">{{ clientInfo.details.income }}</text>
+				<text class="detail-value">
+					{{ clientInfo.details.income }}
+					<text v-if="clientInfo.details.income && clientInfo.details.income !== '未知'">万元/年</text>
+				</text>
 			</view>
 			<view class="detail-item">
 				<text class="detail-label">购房情况:</text>
@@ -150,22 +158,26 @@
 				</view>
 			</view>
 
+			<!-- 匹配状态 -->
+			<view class="follow-group">
+				<text class="group-label">匹配状态</text>
+				<view class="status-tags-row">
+					<view class="status-tag" :class="{ active: followForm.matchStatus === 0 }" @click="handleMatchStatusChange(0)">未匹配</view>
+					<view class="status-tag" :class="{ active: followForm.matchStatus === 1 }" @click="handleMatchStatusChange(1)">已匹配</view>
+				</view>
+			</view>
+
 			<!-- 跟进进度 -->
 			<view class="follow-group">
 				<text class="group-label">跟进进度</text>
 				<view class="status-tags-row">
-					<view class="status-tag" :class="{ active: followForm.progress === 'matched' }" @click="followForm.progress = 'matched'">已匹配</view>
 					<view class="status-tag" :class="{ active: followForm.progress === 'notMet' }" @click="followForm.progress = 'notMet'">未见面</view>
 					<view class="status-tag" :class="{ active: followForm.progress === 'communicating' }" @click="followForm.progress = 'communicating'">沟通中</view>
+					<view class="status-tag" :class="{ active: followForm.invitationStatus === 0 }" @click="handleInvitationStatusChange(0)">未邀约</view>
+					<view class="status-tag" :class="{ active: followForm.invitationStatus === 1 }" @click="handleInvitationStatusChange(1)">已邀约</view>
 				</view>
 			</view>
 
-			<!-- 邀约状态 -->
-			<view class="follow-group">
-				<text class="group-label">邀约状态</text>
-				<view class="input-like">{{ followForm.inviteStatus || '未邀约' }}</view>
-			</view>
-
 			<!-- 邀约时间 -->
 			<view class="follow-group">
 				<text class="group-label">邀约时间</text>
@@ -197,10 +209,12 @@ export default {
 			followForm: {
 				type: 'phone',
 				progress: 'matched',
-				inviteStatus: '未邀约',
+				matchStatus: 0, // 0-未匹配,1-已匹配
+				invitationStatus: 0, // 0-未邀约,1-已邀约
 				inviteDate: '',
 				remark: ''
 			},
+			resourceId: null, // 资源ID
 			clientInfo: {
 				id: 1,
 				name: '',
@@ -242,6 +256,11 @@ export default {
 		if (stored === true || stored === false) {
 			this.isDarkTheme = stored
 		}
+		
+		// 如果已经有resourceId,刷新统计数据
+		if (this.resourceId) {
+			this.loadFollowStatistics(this.resourceId)
+		}
 	},
 	onLoad(options) {
 		console.log('=== 客户详情页面 onLoad ===')
@@ -270,6 +289,7 @@ export default {
 				return
 			}
 			console.log('最终使用的resourceId:', resourceId, '类型:', typeof resourceId)
+			this.resourceId = resourceId
 			this.loadClientInfo(resourceId)
 		} else {
 			console.error('未获取到资源ID')
@@ -321,12 +341,272 @@ export default {
 		onInviteDateChange(e) {
 			this.followForm.inviteDate = e.detail.value
 		},
-		// 提交跟进(暂时仅提示)
-		handleSubmitFollow() {
-			uni.showToast({
-				title: '跟进已保存',
-				icon: 'success'
-			})
+		// 匹配状态变更
+		handleMatchStatusChange(status) {
+			this.followForm.matchStatus = status
+		},
+		// 邀约状态变更
+		handleInvitationStatusChange(status) {
+			this.followForm.invitationStatus = status
+			// 如果选择已邀约且没有设置邀约时间,提示用户选择
+			if (status === 1 && !this.followForm.inviteDate) {
+				uni.showToast({
+					title: '请选择邀约时间',
+					icon: 'none'
+				})
+			}
+		},
+		// 加载最新的跟进信息,更新表单字段
+		async loadLatestFollowInfo(resourceId) {
+			try {
+				const baseUrl = process.env.NODE_ENV === 'development' 
+					? 'http://localhost:8083/api'  // 开发环境 - 通过网关
+					: 'https://your-domain.com/api'  // 生产环境
+				
+				// 获取最新的跟进记录
+				const [error, res] = await uni.request({
+					url: `${baseUrl}/my-resource/latest-follow/${resourceId}`,
+					method: 'GET',
+					header: {
+						'Content-Type': 'application/json'
+					}
+				})
+				
+				if (error) {
+					console.error('加载最新跟进信息失败:', error)
+					return
+				}
+				
+				if (res.statusCode === 200 && res.data && res.data.code === 200) {
+					const followUp = res.data.data
+					
+					if (followUp) {
+						// 更新跟进方式(1-电话,2-面谈,3-其他)
+						if (followUp.followType !== null && followUp.followType !== undefined) {
+							const followType = followUp.followType
+							if (followType === 1) {
+								this.followForm.type = 'phone'
+							} else if (followType === 2) {
+								this.followForm.type = 'meet'
+							} else if (followType === 3) {
+								this.followForm.type = 'other'
+							}
+						}
+						
+						// 更新跟进进度(1-已匹配,2-未见面,3-沟通中)
+						if (followUp.followProgress !== null && followUp.followProgress !== undefined) {
+							const progress = followUp.followProgress
+							if (progress === 1) {
+								this.followForm.progress = 'matched'
+							} else if (progress === 2) {
+								this.followForm.progress = 'notMet'
+							} else if (progress === 3) {
+								this.followForm.progress = 'communicating'
+							}
+						}
+						
+						// 更新匹配状态
+						if (followUp.isMatch !== null && followUp.isMatch !== undefined) {
+							this.followForm.matchStatus = parseInt(followUp.isMatch) || 0
+						}
+						
+						// 更新邀约状态
+						if (followUp.isInvitation !== null && followUp.isInvitation !== undefined) {
+							this.followForm.invitationStatus = parseInt(followUp.isInvitation) || 0
+						}
+						
+						// 更新邀约时间
+						if (followUp.invitationTime) {
+							const date = new Date(followUp.invitationTime)
+							const year = date.getFullYear()
+							const month = String(date.getMonth() + 1).padStart(2, '0')
+							const day = String(date.getDate()).padStart(2, '0')
+							this.followForm.inviteDate = `${year}-${month}-${day}`
+						} else {
+							this.followForm.inviteDate = ''
+						}
+						
+						// 更新备注
+						if (followUp.remark !== null && followUp.remark !== undefined) {
+							this.followForm.remark = followUp.remark || ''
+						} else {
+							this.followForm.remark = ''
+						}
+						
+						console.log('表单字段已更新:', {
+							type: this.followForm.type,
+							progress: this.followForm.progress,
+							matchStatus: this.followForm.matchStatus,
+							invitationStatus: this.followForm.invitationStatus,
+							inviteDate: this.followForm.inviteDate,
+							remark: this.followForm.remark
+						})
+					} else {
+						console.log('暂无跟进记录,保持表单当前值')
+					}
+				}
+			} catch (e) {
+				console.error('加载最新跟进信息异常:', e)
+			}
+		},
+		// 加载跟进统计数据
+		async loadFollowStatistics(resourceId) {
+			try {
+				const baseUrl = process.env.NODE_ENV === 'development' 
+					? 'http://localhost:8083/api'  // 开发环境 - 通过网关
+					: 'https://your-domain.com/api'  // 生产环境
+				
+				const [error, res] = await uni.request({
+					url: `${baseUrl}/my-resource/follow-statistics/${resourceId}`,
+					method: 'GET',
+					header: {
+						'Content-Type': 'application/json'
+					}
+				})
+				
+				if (error) {
+					console.error('加载统计数据失败:', error)
+					return
+				}
+				
+				if (res.statusCode === 200 && res.data && res.data.code === 200) {
+					const statistics = res.data.data
+					console.log('=== 统计数据响应 ===')
+					console.log('完整响应:', JSON.stringify(res.data, null, 2))
+					console.log('统计数据对象:', statistics)
+					console.log('followCount:', statistics?.followCount, statistics?.follow_count)
+					console.log('matchCount:', statistics?.matchCount, statistics?.match_count)
+					console.log('phoneCount:', statistics?.phoneCount, statistics?.phone_count)
+					console.log('interviewCount:', statistics?.interviewCount, statistics?.interview_count)
+					
+					// 更新统计数据(支持驼峰和下划线两种字段名格式)
+					if (this.clientInfo && this.clientInfo.stats) {
+						const followCount = statistics?.followCount ?? statistics?.follow_count ?? 0
+						const matchCount = statistics?.matchCount ?? statistics?.match_count ?? 0
+						const phoneCount = statistics?.phoneCount ?? statistics?.phone_count ?? 0
+						const interviewCount = statistics?.interviewCount ?? statistics?.interview_count ?? 0
+						
+						this.clientInfo.stats.followTimes = followCount
+						this.clientInfo.stats.matchCount = matchCount
+						this.clientInfo.stats.phoneCalls = phoneCount
+						this.clientInfo.stats.interviews = interviewCount
+						
+						console.log('更新后的统计数据:', {
+							followTimes: this.clientInfo.stats.followTimes,
+							matchCount: this.clientInfo.stats.matchCount,
+							phoneCalls: this.clientInfo.stats.phoneCalls,
+							interviews: this.clientInfo.stats.interviews
+						})
+					} else {
+						console.warn('clientInfo或stats不存在')
+					}
+				} else {
+					console.error('统计数据接口返回错误:', res.data)
+				}
+			} catch (e) {
+				console.error('加载统计数据异常:', e)
+			}
+		},
+		// 提交跟进
+		async handleSubmitFollow() {
+			if (!this.resourceId) {
+				uni.showToast({
+					title: '资源ID不存在',
+					icon: 'none'
+				})
+				return
+			}
+			
+			try {
+				uni.showLoading({
+					title: '保存中...'
+				})
+				
+				const baseUrl = process.env.NODE_ENV === 'development' 
+					? 'http://localhost:8083/api'  // 开发环境 - 通过网关
+					: 'https://your-domain.com/api'  // 生产环境
+				
+				// 准备请求数据
+				// 跟进方式转换:phone->1, meet->2, other->3
+				let followType = 1 // 默认电话
+				if (this.followForm.type === 'meet') {
+					followType = 2
+				} else if (this.followForm.type === 'other') {
+					followType = 3
+				}
+				
+				// 跟进进度转换:matched->1, notMet->2, communicating->3
+				let progress = 1 // 默认已匹配
+				if (this.followForm.progress === 'notMet') {
+					progress = 2
+				} else if (this.followForm.progress === 'communicating') {
+					progress = 3
+				}
+				
+				const requestData = {
+					followType: followType,
+					progress: this.followForm.progress, // 保持字符串格式,后端会转换
+					matchStatus: this.followForm.matchStatus,
+					invitationStatus: this.followForm.invitationStatus,
+					invitationTime: this.followForm.inviteDate || null,
+					remark: this.followForm.remark || '' // 添加备注字段
+				}
+				
+				console.log('保存跟进数据:', requestData)
+				
+				const [error, res] = await uni.request({
+					url: `${baseUrl}/my-resource/update-follow/${this.resourceId}`,
+					method: 'PUT',
+					data: requestData,
+					header: {
+						'Content-Type': 'application/json'
+					}
+				})
+				
+				uni.hideLoading()
+				
+				if (error) {
+					console.error('保存跟进失败:', error)
+					uni.showToast({
+						title: '保存失败',
+						icon: 'none'
+					})
+					return
+				}
+				
+				if (res.statusCode === 200 && res.data && res.data.code === 200) {
+					uni.showToast({
+						title: '跟进已保存',
+						icon: 'success'
+					})
+					
+					// 刷新统计数据
+					if (this.resourceId) {
+						this.loadFollowStatistics(this.resourceId)
+					}
+					
+					// 重新加载最新的跟进信息,更新表单字段
+					if (this.resourceId) {
+						this.loadLatestFollowInfo(this.resourceId)
+					}
+					
+					// 触发资源列表刷新事件
+					uni.$emit('refreshResourceList')
+				} else {
+					const errorMsg = res.data?.message || '保存失败'
+					uni.showToast({
+						title: errorMsg,
+						icon: 'none'
+					})
+				}
+			} catch (e) {
+				uni.hideLoading()
+				console.error('保存跟进异常:', e)
+				uni.showToast({
+					title: '保存失败,请稍后重试',
+					icon: 'none'
+				})
+			}
 		},
 		// 从API加载客户信息
 		async loadClientInfo(resourceId) {
@@ -431,6 +711,24 @@ export default {
 						}
 					}
 					
+					// 加载匹配状态和邀约状态
+					const isMatch = data.isMatch !== null && data.isMatch !== undefined ? data.isMatch : 
+					               (data.is_match !== null && data.is_match !== undefined ? data.is_match : 0)
+					const isInvitation = data.isInvitation !== null && data.isInvitation !== undefined ? data.isInvitation : 
+					                    (data.is_Invitation !== null && data.is_Invitation !== undefined ? data.is_Invitation : 0)
+					const invitationTime = data.invitationTime || data.Invitation_time
+					
+					this.followForm.matchStatus = parseInt(isMatch) || 0
+					this.followForm.invitationStatus = parseInt(isInvitation) || 0
+					if (invitationTime) {
+						// 将日期转换为YYYY-MM-DD格式
+						const date = new Date(invitationTime)
+						const year = date.getFullYear()
+						const month = String(date.getMonth() + 1).padStart(2, '0')
+						const day = String(date.getDate()).padStart(2, '0')
+						this.followForm.inviteDate = `${year}-${month}-${day}`
+					}
+					
 					// 映射数据到clientInfo
 					this.clientInfo = {
 						id: resourceId,
@@ -445,7 +743,7 @@ export default {
 						originalPhone: data.phone || '', // 保存原始手机号
 						originalBackupPhone: backupPhone || '', // 保存原始备用手机号
 						stats: {
-							followTimes: 0, // 可以从其他接口获取
+							followTimes: 0, // 将从统计数据接口获取
 							matchCount: 0,
 							phoneCalls: 0,
 							interviews: 0
@@ -470,6 +768,9 @@ export default {
 					
 					console.log('=== 映射后的clientInfo ===')
 					console.log('clientInfo:', JSON.stringify(this.clientInfo, null, 2))
+					
+					// 加载跟进统计数据
+					this.loadFollowStatistics(resourceId)
 				} else {
 					uni.showToast({
 						title: res.data.message || '加载失败',
@@ -592,6 +893,41 @@ export default {
 			color: #2C2C2C;
 			margin-bottom: 12rpx;
 			letter-spacing: 0.5rpx;
+			display: block;
+		}
+
+		.status-tag-wrapper {
+			display: flex;
+			align-items: center;
+			gap: 10rpx;
+			margin-bottom: 18rpx;
+		}
+
+		.status-tag {
+			display: inline-block;
+			padding: 4rpx 12rpx;
+			border-radius: 12rpx;
+			font-size: 22rpx;
+			font-weight: 500;
+
+			&.register-tag {
+				&.registered {
+					background: #E8F5E9;
+					color: #4CAF50;
+				}
+			}
+
+			&.match-tag {
+				&.matched {
+					background: #E3F2FD;
+					color: #2196F3;
+				}
+
+				&.unmatched {
+					background: #FFF3E0;
+					color: #FF9800;
+				}
+			}
 		}
 
 		.client-status {

+ 238 - 60
LiangZhiYUMao/pages/matchmaker-workbench/my-resources.vue

@@ -16,9 +16,77 @@
 			</view>
 		</view>
 
-		<scroll-view scroll-y class="content">
-			<!-- 资源列表 -->
-			<view class="resource-item" v-for="(item, index) in resources" :key="index" @click="handleResourceClick(item)">
+	<scroll-view scroll-y class="content">
+		<!-- 资源部分(已注册用户) -->
+		<view class="resource-section" v-if="registeredResources.length > 0">
+			<view class="section-header">
+				<text class="section-title">资源</text>
+				<text class="section-count">({{ registeredResources.length }})</text>
+			</view>
+			<view class="resource-item" v-for="(item, index) in registeredResources" :key="item.id" @click="handleResourceClick(item)">
+				<!-- 右上角选中图标 -->
+				<view class="select-icon">✓-</view>
+				
+				<view class="resource-header">
+					<image 
+						:src="item.avatar" 
+						mode="aspectFill" 
+						class="resource-avatar"
+						@error="handleImageErrorRegistered(index)"
+						@load="handleImageLoadRegistered(index)"
+						:lazy-load="true"
+					></image>
+					<view class="resource-basic-info">
+						<view class="name-gender">
+							<text class="resource-name">{{ item.name }}</text>
+							<text class="resource-gender gender-tag">{{ item.gender }}</text>
+						</view>
+						<view class="status-tag-wrapper">
+							<text class="status-tag register-tag registered">已注册</text>
+							<text class="status-tag match-tag" :class="{ 'matched': item.isMatch === 1, 'unmatched': item.isMatch === 0 || !item.isMatch }">
+								{{ item.isMatch === 1 ? '已匹配' : '未匹配' }}
+							</text>
+						</view>
+					</view>
+				</view>
+
+				<view class="resource-details">
+					<view class="labels">
+						<text class="label" v-for="(label, idx) in item.labels" :key="idx">{{ label }}</text>
+					</view>
+
+					<view class="requirement-box">
+						<text class="requirement-label">择偶要求:</text>
+						<text class="requirement-content">{{ item.requirement }}</text>
+					</view>
+
+					<view class="contact-box">
+						<text class="contact-label">联系方式:</text>
+						<text class="contact-number">{{ item.contact }}</text>
+						<text class="copy-btn" @click.stop="handleCopyContact(item.originalPhone || item.contact)">复制</text>
+					</view>
+
+					<view class="action-buttons">
+						<view class="delete-btn" @click.stop="handleDelete(item.id)">删除</view>
+						<view class="match-btn" @click.stop="handleMatch(item.id)">精准匹配</view>
+					</view>
+				</view>
+
+				<!-- 优质资源标签 -->
+				<view class="quality-tag" v-if="item.isQuality">
+					<text class="quality-star">★</text>
+					<text class="quality-text">优质资源</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 线索部分(未注册用户) -->
+		<view class="resource-section" v-if="unregisteredResources.length > 0">
+			<view class="section-header">
+				<text class="section-title">线索</text>
+				<text class="section-count">({{ unregisteredResources.length }})</text>
+			</view>
+			<view class="resource-item" v-for="(item, index) in unregisteredResources" :key="item.id" @click="handleResourceClick(item)">
 				<!-- 右上角选中图标 -->
 				<view class="select-icon">✓-</view>
 				
@@ -27,8 +95,8 @@
 						:src="item.avatar" 
 						mode="aspectFill" 
 						class="resource-avatar"
-						@error="handleImageError(index)"
-						@load="handleImageLoad(index)"
+						@error="handleImageErrorUnregistered(index)"
+						@load="handleImageLoadUnregistered(index)"
 						:lazy-load="true"
 					></image>
 					<view class="resource-basic-info">
@@ -37,7 +105,7 @@
 							<text class="resource-gender gender-tag">{{ item.gender }}</text>
 						</view>
 						<view class="status-tag-wrapper">
-							<text class="status-tag register-tag" :class="{ 'registered': item.isUser === 1, 'unregistered': item.isUser === 0 }">{{ item.isUser === 1 ? '已注册' : '未注册' }}</text>
+							<text class="status-tag register-tag unregistered">未注册</text>
 						</view>
 					</view>
 				</view>
@@ -70,7 +138,13 @@
 					<text class="quality-text">优质资源</text>
 				</view>
 			</view>
-		</scroll-view>
+		</view>
+
+		<!-- 空状态 -->
+		<view class="empty-state" v-if="registeredResources.length === 0 && unregisteredResources.length === 0">
+			<text class="empty-text">暂无资源</text>
+		</view>
+	</scroll-view>
 
 		<!-- 底部添加按钮 -->
 		<view class="add-button" @click="handleAdd">
@@ -113,44 +187,10 @@ export default {
 			return {
 				searchKeyword: '',
 				isFirstLoad: true, // 标记是否为首次加载
-				resources: [
-					{
-						id: 1,
-						avatar: 'https://example.com/avatar1.jpg',
-						name: '小高',
-						gender: '男',
-						status: '已审核',
-						isPlus: true,
-						labels: ['气质男', '小清新'],
-						requirement: '165+ 本科',
-						contact: '123****8912',
-						isQuality: false
-					},
-					{
-						id: 2,
-						avatar: 'https://example.com/avatar2.jpg',
-						name: '小美',
-						gender: '女',
-						status: '已审核',
-						isPlus: true,
-						labels: ['温柔', '知性', '爱读书'],
-						requirement: '175+ 硕士 有房',
-						contact: '138****6543',
-						isQuality: false
-					},
-					{
-						id: 3,
-						avatar: 'https://example.com/avatar3.jpg',
-						name: '阿强',
-						gender: '男',
-						status: '待审核',
-						isPlus: false,
-						labels: ['阳光', '运动型'],
-						requirement: '160+ 大专以上',
-						contact: '159****2234',
-						isQuality: true
-					}
-				]
+				resources: [], // 保留用于兼容
+				registeredResources: [], // 已注册用户(资源部分)
+				unregisteredResources: [], // 未注册用户(线索部分)
+				refreshTimer: null // 定时刷新器
 			}
 		},
 		onLoad() {
@@ -163,16 +203,26 @@ export default {
 				console.log('收到刷新资源列表事件')
 				this.loadMyResources()
 			})
+			
+			// 启动定时刷新,每30秒检查一次用户注册状态变化
+			this.startAutoRefresh()
 		},
 		onShow() {
 			// 页面显示时刷新列表(从其他页面返回时,非首次加载)
 			if (!this.isFirstLoad) {
 				this.loadMyResources()
 			}
+			// 重新启动定时刷新
+			this.startAutoRefresh()
+		},
+		onHide() {
+			// 页面隐藏时停止定时刷新
+			this.stopAutoRefresh()
 		},
 		onUnload() {
-			// 页面卸载时移除事件监听
+			// 页面卸载时移除事件监听和停止定时刷新
 			uni.$off('refreshResourceList')
+			this.stopAutoRefresh()
 		},
 		methods: {
 			// 加载我的资源数据
@@ -232,7 +282,7 @@ export default {
 								console.log('第一条记录的完整数据:', JSON.stringify(pageData.records[0], null, 2))
 							}
 							// 将后端数据转换为前端需要的格式
-							this.resources = pageData.records.map(item => {
+							const allResources = pageData.records.map(item => {
 								// 直接使用mate_selection_criteria字段作为择偶要求
 								// 支持多种可能的字段名(mateSelectionCriteria或mate_selection_criteria)
 								let requirement = item.mateSelectionCriteria || item.mate_selection_criteria || ''
@@ -319,6 +369,11 @@ export default {
 								// 确保isUser是数字类型
 								isUser = parseInt(isUser) || 0
 								
+								// 处理isMatch字段
+								let isMatch = item.isMatch !== null && item.isMatch !== undefined ? item.isMatch : 
+								              (item.is_match !== null && item.is_match !== undefined ? item.is_match : 0)
+								isMatch = parseInt(isMatch) || 0
+								
 								// 调试日志:检查isUser字段
 								console.log('=== isUser字段处理 ===')
 								console.log('资源ID:', resourceId, '姓名:', item.name)
@@ -347,6 +402,7 @@ export default {
 									gender: item.gender === 1 ? '男' : item.gender === 2 ? '女' : '未知',
 									status: '已审核', // 可以根据实际字段判断
 									isUser: isUser, // 添加isUser字段,默认为0
+									isMatch: isMatch, // 添加isMatch字段,默认为0
 									isPlus: false,
 									labels: resourceTags, // 使用处理后的标签列表
 									requirement: requirement || '暂无要求',
@@ -356,7 +412,14 @@ export default {
 								}
 							}).filter(item => item !== null) // 过滤掉无效的资源(resourceId无效的)
 							
-							console.log('处理后的资源列表:', this.resources)
+							// 将资源分为两部分:已注册和未注册
+							this.registeredResources = allResources.filter(item => item.isUser === 1)
+							this.unregisteredResources = allResources.filter(item => item.isUser === 0)
+							
+							// 保留resources用于兼容(包含所有资源)
+							this.resources = allResources
+							
+							console.log('处理后的资源列表 - 已注册:', this.registeredResources.length, '未注册:', this.unregisteredResources.length)
 						}
 					}
 				} catch (e) {
@@ -449,10 +512,32 @@ export default {
 		},
 		// 精准匹配
 		handleMatch(id) {
-			// 实现精准匹配功能
+			// 查找资源项
+			const allResources = [...this.registeredResources, ...this.unregisteredResources]
+			const resource = allResources.find(item => item.id === id)
+			
+			if (!resource) {
+				uni.showToast({
+					title: '资源不存在',
+					icon: 'none'
+				})
+				return
+			}
+			
+			// 检查用户是否已注册
+			if (resource.isUser !== 1) {
+				uni.showToast({
+					title: '该用户还未注册用户端,注册之后才能进行匹配',
+					icon: 'none',
+					duration: 3000
+				})
+				return
+			}
+			
+			// 已注册用户,跳转到精准匹配页面
 			console.log('精准匹配:', id)
 			uni.navigateTo({
-				url: `/pages/matchmaker-workbench/precise-match?id=${id}`
+				url: `/pages/matchmaker-workbench/precise-match?resourceId=${id}`
 			})
 		},
 		// 复制联系方式
@@ -565,18 +650,53 @@ export default {
 				url: '/pages/matchmaker-workbench/mine'
 			})
 		},
-		// 图片加载错误处理
-		handleImageError(index) {
+		// 启动自动刷新
+		startAutoRefresh() {
+			// 清除之前的定时器
+			this.stopAutoRefresh()
+			// 每30秒刷新一次,检查用户注册状态变化
+			this.refreshTimer = setInterval(() => {
+				console.log('定时刷新:检查用户注册状态变化')
+				this.loadMyResources()
+			}, 30000) // 30秒
+		},
+		// 停止自动刷新
+		stopAutoRefresh() {
+			if (this.refreshTimer) {
+				clearInterval(this.refreshTimer)
+				this.refreshTimer = null
+			}
+		},
+		// 已注册资源图片加载错误处理
+		handleImageErrorRegistered(index) {
+			this.handleImageError(index, 'registered')
+		},
+		// 未注册资源图片加载错误处理
+		handleImageErrorUnregistered(index) {
+			this.handleImageError(index, 'unregistered')
+		},
+		// 已注册资源图片加载成功处理
+		handleImageLoadRegistered(index) {
+			this.handleImageLoad(index, 'registered')
+		},
+		// 未注册资源图片加载成功处理
+		handleImageLoadUnregistered(index) {
+			this.handleImageLoad(index, 'unregistered')
+		},
+		// 图片加载错误处理(内部方法)
+		handleImageError(index, type) {
 			try {
-				const resource = this.resources && this.resources[index]
+				const resourceList = type === 'registered' ? this.registeredResources : this.unregisteredResources
+				const resource = resourceList && resourceList[index]
 				if (!resource) {
-					console.warn('图片加载失败:资源不存在,index:', index)
+					console.warn('图片加载失败:资源不存在,index:', index, 'type:', type)
 					return
 				}
 				
 				const originalUrl = resource.avatar
 				console.error('❌ 图片加载失败:', {
 					index: index,
+					type: type,
 					resourceName: resource.name,
 					resourceId: resource.id,
 					originalAvatarUrl: originalUrl,
@@ -595,7 +715,11 @@ export default {
 					!originalUrl.includes('via.placeholder')) {
 					// 设置为空字符串,让CSS默认背景显示
 					console.log('图片加载失败,将URL设置为空,显示CSS默认背景')
-					this.$set(this.resources[index], 'avatar', '')
+					if (type === 'registered') {
+						this.$set(this.registeredResources[index], 'avatar', '')
+					} else {
+						this.$set(this.unregisteredResources[index], 'avatar', '')
+					}
 				} else {
 					console.log('图片加载失败,但URL已经是占位图或空,无需处理')
 					console.log('当前URL:', originalUrl)
@@ -604,14 +728,16 @@ export default {
 				console.error('处理图片错误时发生异常:', e)
 			}
 		},
-		// 图片加载成功处理
-		handleImageLoad(index) {
+		// 图片加载成功处理(内部方法)
+		handleImageLoad(index, type) {
 			try {
-				if (this.resources && this.resources[index]) {
+				const resourceList = type === 'registered' ? this.registeredResources : this.unregisteredResources
+				if (resourceList && resourceList[index]) {
 					console.log('图片加载成功:', {
 						index: index,
-						resource: this.resources[index]?.name,
-						avatarUrl: this.resources[index]?.avatar
+						type: type,
+						resource: resourceList[index]?.name,
+						avatarUrl: resourceList[index]?.avatar
 					})
 				}
 			} catch (e) {
@@ -698,6 +824,44 @@ export default {
 		padding: 0 20rpx 140rpx;
 	}
 
+	/* 资源分组 */
+	.resource-section {
+		margin-bottom: 30rpx;
+		
+		.section-header {
+			display: flex;
+			align-items: center;
+			padding: 20rpx 0 15rpx;
+			margin-bottom: 10rpx;
+			
+			.section-title {
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #9C27B0;
+				margin-right: 10rpx;
+			}
+			
+			.section-count {
+				font-size: 26rpx;
+				color: #999;
+				font-weight: normal;
+			}
+		}
+	}
+
+	/* 空状态 */
+	.empty-state {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		padding: 100rpx 0;
+		
+		.empty-text {
+			font-size: 28rpx;
+			color: #999;
+		}
+	}
+
 	/* 资源列表 */
 	.resource-item {
 		background: #FFFFFF;
@@ -809,6 +973,20 @@ export default {
 								color: #E91E63;
 							}
 						}
+						
+						&.match-tag {
+							margin-left: 10rpx;
+							
+							&.matched {
+								background: #E3F2FD;
+								color: #2196F3;
+							}
+							
+							&.unmatched {
+								background: #FFF3E0;
+								color: #FF9800;
+							}
+						}
 					}
 				}
 			}

+ 253 - 49
LiangZhiYUMao/pages/matchmaker-workbench/precise-match.vue

@@ -7,9 +7,40 @@
 			<view class="header-right"></view>
 		</view>
 
+		<!-- 加载中 -->
+		<view class="loading-container" v-if="loading">
+			<text class="loading-text">正在匹配中...</text>
+		</view>
+
+		<!-- 资源跟进状态 -->
+		<view class="follow-status-section" v-if="!loading && resourceFollowInfo">
+			<!-- 匹配状态 -->
+			<view class="follow-status-group">
+				<text class="status-label">匹配状态</text>
+				<view class="status-tags">
+					<text class="status-tag" :class="{ 'active': resourceFollowInfo.isMatch === 1 }">
+						{{ resourceFollowInfo.isMatch === 1 ? '已匹配' : '未匹配' }}
+					</text>
+				</view>
+			</view>
+			<!-- 跟进进度 -->
+			<view class="follow-status-group">
+				<text class="status-label">跟进进度</text>
+				<view class="status-tags">
+					<text class="status-tag" :class="{ 'active': resourceFollowInfo.isInvitation === 1 }">
+						{{ resourceFollowInfo.isInvitation === 1 ? '已邀约' : '未邀约' }}
+					</text>
+				</view>
+			</view>
+		</view>
+
 		<!-- 匹配客户列表 -->
-		<scroll-view scroll-y class="match-list">
-			<view class="match-client" v-for="(client, index) in matchClients" :key="index">
+		<scroll-view scroll-y class="match-list" v-else>
+			<view class="match-client" v-for="(client, index) in matchClients" :key="client.id || index">
+				<!-- 匹配度标签 -->
+				<view class="match-score-badge" v-if="client.matchScore">
+					<text class="score-text">匹配度: {{ Math.round(client.matchScore) }}%</text>
+				</view>
 				<view class="client-info">
 					<image :src="client.avatar" class="client-avatar" mode="aspectFill"></image>
 					<view class="info-content">
@@ -21,6 +52,13 @@
 						<view class="client-tags">
 							<text class="tag" v-for="(tag, tagIndex) in client.tags" :key="tagIndex">{{ tag }}</text>
 						</view>
+						<view class="user-details" v-if="client.age || client.height || client.diploma">
+							<text class="detail-item" v-if="client.age">{{ client.age }}岁</text>
+							<text class="detail-item" v-if="client.height">{{ client.height }}cm</text>
+							<text class="detail-item" v-if="client.diploma">{{ client.diploma }}</text>
+							<text class="detail-item" v-if="client.income">{{ client.income }}</text>
+							<text class="detail-item" v-if="client.occupation">{{ client.occupation }}</text>
+						</view>
 						<view class="requirement">
 							<text class="requirement-label">择偶要求:</text>
 							<text class="requirement-content">{{ client.requirement }}</text>
@@ -67,48 +105,31 @@
 <script>
 export default {
 	name: 'precise-match',
-	data() {
-		return {
-			matchClients: [
-				{
-					id: 1,
-					name: '小高',
-					gender: '女',
-					status: '未匹配',
-					tags: ['气质男', '小清新'],
-					avatar: 'https://images.unsplash.com/photo-1506744038136-46273834b3fb?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=60',
-					requirement: '165+ 本科',
-					contact: '123****8912'
-				},
-				{
-					id: 2,
-					name: '小子',
-					gender: '女',
-					status: '未匹配',
-					tags: ['周末有空'],
-					avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=60',
-					requirement: '165+ 本科',
-					contact: '123****8912'
-				},
-				{
-					id: 3,
-					name: '小博',
-					gender: '女',
-					status: '未匹配',
-					tags: ['电话联系', '运动'],
-					avatar: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=60',
-					requirement: '180+ 本科',
-					contact: '123****8912'
-				}
-			]
-		}
-	},
+		data() {
+			return {
+				resourceId: null,
+				matchClients: [],
+				loading: false,
+				resourceFollowInfo: null // 资源跟进信息
+			}
+		},
 	onLoad(options) {
-		// 从URL参数获取客户ID
-		if (options.id) {
-			// 这里可以根据ID从API获取匹配客户信息
-			console.log('当前客户ID:', options.id)
-			// this.loadMatchClients(options.id)
+		// 从URL参数获取资源ID
+		if (options.resourceId) {
+			console.log('当前资源ID:', options.resourceId)
+			this.loadMatchClients(options.resourceId)
+		} else if (options.id) {
+			// 兼容旧的参数名
+			console.log('当前资源ID (兼容):', options.id)
+			this.loadMatchClients(options.id)
+		} else {
+			uni.showToast({
+				title: '缺少资源ID参数',
+				icon: 'none'
+			})
+			setTimeout(() => {
+				uni.navigateBack()
+			}, 1500)
 		}
 	},
 	methods: {
@@ -137,15 +158,99 @@ export default {
 			})
 		},
 		// 从API加载匹配客户信息
-		async loadMatchClients(id) {
+		async loadMatchClients(resourceId) {
+			if (!resourceId) {
+				console.error('资源ID为空')
+				return
+			}
+			
+			this.resourceId = resourceId
+			this.loading = true
+			
 			try {
-				// 这里调用API获取匹配客户信息
-				// const res = await api.matchmaker.getPreciseMatch(id)
-				// this.matchClients = res.data
+				const baseUrl = process.env.NODE_ENV === 'development' 
+					? 'http://localhost:8083/api'  // 开发环境 - 通过网关
+					: 'https://your-domain.com/api'  // 生产环境
+				
+				// 同时加载匹配结果和资源跟进信息
+				const [matchError, matchRes] = await uni.request({
+					url: `${baseUrl}/my-resource/precise-match/${resourceId}`,
+					method: 'GET'
+				})
+				
+				// 加载资源跟进信息
+				const [followError, followRes] = await uni.request({
+					url: `${baseUrl}/my-resource/client-detail/${resourceId}`,
+					method: 'GET'
+				})
+				
+				this.loading = false
+				
+				// 处理跟进信息
+				if (!followError && followRes.statusCode === 200 && followRes.data && followRes.data.code === 200) {
+					const followData = followRes.data.data
+					this.resourceFollowInfo = {
+						isMatch: followData.isMatch !== null && followData.isMatch !== undefined ? followData.isMatch : 
+						        (followData.is_match !== null && followData.is_match !== undefined ? followData.is_match : 0),
+						isInvitation: followData.isInvitation !== null && followData.isInvitation !== undefined ? followData.isInvitation : 
+						             (followData.is_Invitation !== null && followData.is_Invitation !== undefined ? followData.is_Invitation : 0)
+					}
+				}
+				
+				if (matchError) {
+					console.error('加载匹配客户失败:', matchError)
+					uni.showToast({
+						title: '加载失败',
+						icon: 'none'
+					})
+					return
+				}
+				
+				if (matchRes.statusCode === 200 && matchRes.data && matchRes.data.code === 200) {
+					const matchList = matchRes.data.data || []
+					
+					// 转换数据格式
+					this.matchClients = matchList.map(item => {
+						return {
+							id: item.userId,
+							name: item.nickname || '未知',
+							gender: item.gender === 1 ? '男' : item.gender === 2 ? '女' : '未知',
+							status: '未匹配',
+							tags: item.tags || [],
+							avatar: item.avatarUrl || 'https://via.placeholder.com/150',
+							requirement: item.requirement || '暂无要求',
+							contact: item.phone || '',
+							matchScore: item.matchScore || 0,
+							age: item.age,
+							height: item.height,
+							diploma: item.diploma,
+							income: item.income,
+							occupation: item.occupation,
+							address: item.address
+						}
+					})
+					
+					console.log('匹配结果:', this.matchClients)
+					
+					if (this.matchClients.length === 0) {
+						uni.showToast({
+							title: '未找到匹配的用户',
+							icon: 'none'
+						})
+					}
+				} else {
+					const errorMsg = matchRes.data?.message || '加载失败'
+					console.error('加载匹配客户失败:', errorMsg)
+					uni.showToast({
+						title: errorMsg,
+						icon: 'none'
+					})
+				}
 			} catch (e) {
-				console.error('加载匹配客户失败:', e)
+				this.loading = false
+				console.error('加载匹配客户异常:', e)
 				uni.showToast({
-					title: '加载失败',
+					title: '加载失败,请稍后重试',
 					icon: 'none'
 				})
 			}
@@ -309,6 +414,30 @@ export default {
 							color: #333;
 						}
 					}
+					
+					.user-details {
+						display: flex;
+						flex-wrap: wrap;
+						gap: 10rpx;
+						margin-bottom: 15rpx;
+						font-size: 24rpx;
+						color: #999;
+						
+						.detail-item {
+							display: flex;
+							align-items: center;
+							
+							&::after {
+								content: '|';
+								margin: 0 10rpx;
+								color: #ddd;
+							}
+							
+							&:last-child::after {
+								display: none;
+							}
+						}
+					}
 
 					.contact-info {
 						display: flex;
@@ -350,6 +479,81 @@ export default {
 				font-weight: bold;
 				margin-left: auto;
 			}
+			
+			.match-score-badge {
+				position: absolute;
+				top: 20rpx;
+				right: 20rpx;
+				background: linear-gradient(135deg, #FF6B8A 0%, #FF8E9B 100%);
+				color: #FFFFFF;
+				padding: 8rpx 16rpx;
+				border-radius: 20rpx;
+				font-size: 22rpx;
+				font-weight: bold;
+				z-index: 10;
+				
+				.score-text {
+					color: #FFFFFF;
+				}
+			}
+		}
+	}
+	
+	/* 加载中 */
+	.loading-container {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		padding: 100rpx 0;
+		
+		.loading-text {
+			font-size: 28rpx;
+			color: #999;
+		}
+	}
+
+	/* 资源跟进状态区域 */
+	.follow-status-section {
+		background-color: #FFFFFF;
+		margin: 20rpx;
+		padding: 30rpx;
+		border-radius: 20rpx;
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
+		display: flex;
+		justify-content: space-around;
+		align-items: center;
+	}
+
+	.follow-status-group {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		gap: 12rpx;
+	}
+
+	.status-label {
+		font-size: 26rpx;
+		color: #666;
+		font-weight: 500;
+	}
+
+	.status-tags {
+		display: flex;
+		gap: 10rpx;
+	}
+
+	.status-tag {
+		padding: 8rpx 20rpx;
+		border-radius: 20rpx;
+		font-size: 24rpx;
+		background-color: #FAF5FF;
+		color: #9C27B0;
+		border: 2rpx solid #E1BEE7;
+
+		&.active {
+			background-color: #F8BBD0;
+			border-color: #F06292;
+			color: #FFFFFF;
 		}
 	}
 

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

@@ -22,6 +22,12 @@ public class MyResourceController {
     @Autowired
     private MyResourceService myResourceService;
     
+    @Autowired
+    private com.zhentao.service.UserFollowUpService userFollowUpService;
+    
+    @Autowired
+    private com.zhentao.service.FollowUpStatisticsService followUpStatisticsService;
+    
     @Autowired
     private UserMapper userMapper;
     
@@ -287,5 +293,282 @@ public class MyResourceController {
             return Result.error("资源删除失败:" + e.getMessage());
         }
     }
+    
+    /**
+     * 精准匹配
+     * 根据资源用户的择偶要求,匹配推荐异性用户
+     * 
+     * @param resourceId 资源ID
+     * @return 匹配结果列表(最多3个)
+     */
+    @GetMapping("/precise-match/{resourceId}")
+    public Result<java.util.List<com.zhentao.vo.MatchResultVO>> preciseMatch(@PathVariable Integer resourceId) {
+        try {
+            System.out.println("=== 精准匹配请求 ===");
+            System.out.println("资源ID: " + resourceId);
+            
+            java.util.List<com.zhentao.vo.MatchResultVO> matchResults = myResourceService.preciseMatch(resourceId);
+            
+            if (matchResults == null || matchResults.isEmpty()) {
+                return Result.success("未找到匹配的用户", new java.util.ArrayList<>());
+            }
+            
+            System.out.println("匹配成功,返回 " + matchResults.size() + " 个结果");
+            return Result.success(matchResults);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("精准匹配失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 更新跟进信息(保存到跟进表)
+     * 
+     * @param resourceId 资源ID
+     * @param requestBody 请求体,包含followType、followProgress、isMatch、isInvitation、invitationTime、remark
+     * @return 更新结果
+     */
+    @PutMapping("/update-follow/{resourceId}")
+    public Result<Void> updateFollow(
+            @PathVariable Integer resourceId,
+            @RequestBody java.util.Map<String, Object> requestBody) {
+        try {
+            System.out.println("=== 保存跟进信息到跟进表 ===");
+            System.out.println("资源ID: " + resourceId);
+            System.out.println("请求数据: " + requestBody);
+            
+            // 获取资源信息
+            MyResource resource = myResourceService.getById(resourceId);
+            if (resource == null) {
+                return Result.error("资源不存在");
+            }
+            
+            // 获取当前登录的红娘ID(这里需要从session或token中获取,暂时使用resource的matchmakerId)
+            Integer matchmakerId = resource.getMatchmakerId();
+            if (matchmakerId == null) {
+                // 如果资源没有红娘ID,尝试从请求参数获取
+                if (requestBody.containsKey("matchmakerId")) {
+                    matchmakerId = Integer.valueOf(requestBody.get("matchmakerId").toString());
+                } else {
+                    return Result.error("无法获取红娘ID");
+                }
+            }
+            
+            // 创建跟进记录
+            com.zhentao.entity.UserFollowUp followUp = new com.zhentao.entity.UserFollowUp();
+            followUp.setResourceId(resourceId);
+            followUp.setUserId(resource.getUserId());
+            followUp.setMatchmakerId(matchmakerId);
+            
+            // 跟进方式(1-电话,2-面谈,3-其他)
+            if (requestBody.containsKey("followType")) {
+                Object followTypeObj = requestBody.get("followType");
+                if (followTypeObj != null) {
+                    String followTypeStr = followTypeObj.toString();
+                    // 将字符串转换为数字:phone->1, face->2, other->3
+                    int followType = 1; // 默认电话
+                    if ("face".equals(followTypeStr)) {
+                        followType = 2;
+                    } else if ("other".equals(followTypeStr)) {
+                        followType = 3;
+                    } else if (followTypeStr.matches("\\d+")) {
+                        followType = Integer.parseInt(followTypeStr);
+                    }
+                    followUp.setFollowType(followType);
+                }
+            } else {
+                followUp.setFollowType(1); // 默认电话
+            }
+            
+            // 跟进进度(1-已匹配,2-未见面,3-沟通中)
+            if (requestBody.containsKey("progress")) {
+                Object progressObj = requestBody.get("progress");
+                if (progressObj != null) {
+                    String progressStr = progressObj.toString();
+                    int followProgress = 1; // 默认已匹配
+                    if ("notMet".equals(progressStr)) {
+                        followProgress = 2;
+                    } else if ("communicating".equals(progressStr)) {
+                        followProgress = 3;
+                    } else if ("matched".equals(progressStr)) {
+                        followProgress = 1;
+                    } else if (progressStr.matches("\\d+")) {
+                        followProgress = Integer.parseInt(progressStr);
+                    }
+                    followUp.setFollowProgress(followProgress);
+                }
+            }
+            
+            // 匹配状态(0-未匹配,1-已匹配)
+            if (requestBody.containsKey("matchStatus")) {
+                Object matchStatusObj = requestBody.get("matchStatus");
+                Integer isMatch = matchStatusObj != null ? Integer.valueOf(matchStatusObj.toString()) : 0;
+                followUp.setIsMatch(isMatch);
+                System.out.println("设置isMatch: " + isMatch);
+            } else {
+                followUp.setIsMatch(0); // 默认未匹配
+            }
+            
+            // 邀约状态(0-未邀约,1-已邀约)
+            if (requestBody.containsKey("invitationStatus")) {
+                Object invitationStatusObj = requestBody.get("invitationStatus");
+                Integer isInvitation = invitationStatusObj != null ? Integer.valueOf(invitationStatusObj.toString()) : 0;
+                followUp.setIsInvitation(isInvitation);
+                System.out.println("设置isInvitation: " + isInvitation);
+            } else {
+                followUp.setIsInvitation(0); // 默认未邀约
+            }
+            
+            // 邀约时间
+            if (requestBody.containsKey("invitationTime") || requestBody.containsKey("inviteDate")) {
+                Object invitationTimeObj = requestBody.get("invitationTime");
+                if (invitationTimeObj == null) {
+                    invitationTimeObj = requestBody.get("inviteDate");
+                }
+                if (invitationTimeObj != null && !invitationTimeObj.toString().trim().isEmpty()) {
+                    try {
+                        // 解析日期字符串 (格式: YYYY-MM-DD)
+                        String dateStr = invitationTimeObj.toString().trim();
+                        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
+                        java.util.Date invitationTime = sdf.parse(dateStr);
+                        followUp.setInvitationTime(invitationTime);
+                        System.out.println("设置invitationTime: " + invitationTime);
+                    } catch (Exception e) {
+                        System.err.println("解析邀约时间失败: " + e.getMessage());
+                        followUp.setInvitationTime(null);
+                    }
+                } else {
+                    followUp.setInvitationTime(null);
+                }
+            }
+            
+            // 跟进备注
+            if (requestBody.containsKey("remark")) {
+                Object remarkObj = requestBody.get("remark");
+                if (remarkObj != null) {
+                    String remark = remarkObj.toString().trim();
+                    // 如果备注不为空,则设置;如果为空字符串,设置为null
+                    followUp.setRemark(remark.isEmpty() ? null : remark);
+                    System.out.println("设置remark: " + (remark.isEmpty() ? "null" : remark));
+                } else {
+                    followUp.setRemark(null);
+                }
+            } else {
+                // 如果没有remark字段,设置为null(不更新现有备注)
+                // 注意:这里不设置,让Service层处理
+            }
+            
+            // 保存或更新跟进记录
+            boolean success = userFollowUpService.saveOrUpdateFollowUp(followUp);
+            if (success) {
+                System.out.println("✅ 跟进信息保存成功");
+                
+                // 更新统计数据
+                try {
+                    followUpStatisticsService.updateStatistics(resourceId, matchmakerId);
+                    System.out.println("✅ 统计数据更新成功");
+                } catch (Exception e) {
+                    System.err.println("⚠️ 统计数据更新失败: " + e.getMessage());
+                    e.printStackTrace();
+                    // 统计数据更新失败不影响跟进信息保存
+                }
+                
+                return Result.success("跟进信息保存成功", null);
+            } else {
+                System.out.println("❌ 跟进信息保存失败");
+                return Result.error("跟进信息保存失败");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("保存跟进信息失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 获取跟进统计数据
+     * 
+     * @param resourceId 资源ID
+     * @return 统计数据
+     */
+    @GetMapping("/follow-statistics/{resourceId}")
+    public Result<com.zhentao.entity.FollowUpStatistics> getFollowStatistics(@PathVariable Integer resourceId) {
+        try {
+            System.out.println("=== 获取跟进统计数据 ===");
+            System.out.println("资源ID: " + resourceId);
+            
+            // 获取资源信息
+            MyResource resource = myResourceService.getById(resourceId);
+            if (resource == null) {
+                return Result.error("资源不存在");
+            }
+            
+            Integer matchmakerId = resource.getMatchmakerId();
+            if (matchmakerId == null) {
+                return Result.error("无法获取红娘ID");
+            }
+            
+            // 获取或创建统计数据
+            com.zhentao.entity.FollowUpStatistics statistics = 
+                followUpStatisticsService.getOrCreateStatistics(resourceId, matchmakerId);
+            
+            if (statistics == null) {
+                return Result.error("统计数据不存在");
+            }
+            
+            System.out.println("=== 返回统计数据 ===");
+            System.out.println("statistics_id: " + statistics.getStatisticsId());
+            System.out.println("resource_id: " + statistics.getResourceId());
+            System.out.println("matchmaker_id: " + statistics.getMatchmakerId());
+            System.out.println("follow_count: " + statistics.getFollowCount());
+            System.out.println("match_count: " + statistics.getMatchCount());
+            System.out.println("phone_count: " + statistics.getPhoneCount());
+            System.out.println("interview_count: " + statistics.getInterviewCount());
+            System.out.println("完整对象: " + statistics);
+            
+            return Result.success(statistics);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取统计数据失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 获取最新的跟进记录
+     * 
+     * @param resourceId 资源ID
+     * @return 最新的跟进记录
+     */
+    @GetMapping("/latest-follow/{resourceId}")
+    public Result<com.zhentao.entity.UserFollowUp> getLatestFollow(@PathVariable Integer resourceId) {
+        try {
+            System.out.println("=== 获取最新跟进记录 ===");
+            System.out.println("资源ID: " + resourceId);
+            
+            // 获取资源信息
+            MyResource resource = myResourceService.getById(resourceId);
+            if (resource == null) {
+                return Result.error("资源不存在");
+            }
+            
+            Integer matchmakerId = resource.getMatchmakerId();
+            if (matchmakerId == null) {
+                return Result.error("无法获取红娘ID");
+            }
+            
+            // 获取最新的跟进记录
+            com.zhentao.entity.UserFollowUp followUp = 
+                userFollowUpService.getLatestByResourceIdAndMatchmakerId(resourceId, matchmakerId);
+            
+            if (followUp == null) {
+                return Result.success("暂无跟进记录", null);
+            }
+            
+            System.out.println("最新跟进记录: " + followUp);
+            return Result.success(followUp);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取最新跟进记录失败:" + e.getMessage());
+        }
+    }
 }
 

+ 82 - 0
service/homePage/src/main/java/com/zhentao/entity/FollowUpStatistics.java

@@ -0,0 +1,82 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 跟进统计表
+ * 记录红娘对资源用户的跟进统计数据
+ */
+@Data
+@TableName("follow_up_statistics")
+public class FollowUpStatistics implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 统计ID(主键)
+     */
+    @TableId(value = "statistics_id", type = IdType.AUTO)
+    private Integer statisticsId;
+    
+    /**
+     * 资源ID(关联my_resource表的resource_id)
+     */
+    @TableField("resource_id")
+    private Integer resourceId;
+    
+    /**
+     * 用户ID(关联users表的user_id,资源用户)
+     */
+    @TableField("user_id")
+    private Integer userId;
+    
+    /**
+     * 红娘ID(关联matchmakers表的matchmaker_id)
+     */
+    @TableField("matchmaker_id")
+    private Integer matchmakerId;
+    
+    /**
+     * 跟进次数(统计user_follow_up表中的记录数)
+     */
+    @TableField("follow_count")
+    private Integer followCount;
+    
+    /**
+     * 匹配对象数(统计is_match=1的记录数)
+     */
+    @TableField("match_count")
+    private Integer matchCount;
+    
+    /**
+     * 电话沟通次数(统计follow_type=1的记录数)
+     */
+    @TableField("phone_count")
+    private Integer phoneCount;
+    
+    /**
+     * 面谈次数(统计follow_type=2的记录数)
+     */
+    @TableField("interview_count")
+    private Integer interviewCount;
+    
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private Date createTime;
+    
+    /**
+     * 更新时间
+     */
+    @TableField("update_time")
+    private Date updateTime;
+}
+

+ 94 - 0
service/homePage/src/main/java/com/zhentao/entity/UserFollowUp.java

@@ -0,0 +1,94 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户跟进表
+ * 记录红娘对资源用户的跟进信息
+ */
+@Data
+@TableName("user_follow_up")
+public class UserFollowUp implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 跟进ID(主键)
+     */
+    @TableId(value = "follow_up_id", type = IdType.AUTO)
+    private Integer followUpId;
+    
+    /**
+     * 资源ID(关联my_resource表的resource_id)
+     */
+    @TableField("resource_id")
+    private Integer resourceId;
+    
+    /**
+     * 用户ID(关联users表的user_id,资源用户)
+     */
+    @TableField("user_id")
+    private Integer userId;
+    
+    /**
+     * 红娘ID(关联matchmakers表的matchmaker_id)
+     */
+    @TableField("matchmaker_id")
+    private Integer matchmakerId;
+    
+    /**
+     * 跟进方式(1-电话,2-面谈,3-其他)
+     */
+    @TableField("follow_type")
+    private Integer followType;
+    
+    /**
+     * 跟进进度(1-已匹配,2-未见面,3-沟通中)
+     */
+    @TableField("follow_progress")
+    private Integer followProgress;
+    
+    /**
+     * 匹配状态(0-未匹配,1-已匹配)
+     */
+    @TableField("is_match")
+    private Integer isMatch;
+    
+    /**
+     * 邀约状态(0-未邀约,1-已邀约)
+     */
+    @TableField("is_invitation")
+    private Integer isInvitation;
+    
+    /**
+     * 邀约时间
+     */
+    @TableField("invitation_time")
+    private Date invitationTime;
+    
+    /**
+     * 跟进备注
+     */
+    @TableField("remark")
+    private String remark;
+    
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private Date createTime;
+    
+    /**
+     * 更新时间
+     */
+    @TableField("update_time")
+    private Date updateTime;
+}
+

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

@@ -0,0 +1,26 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.FollowUpStatistics;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 跟进统计表Mapper
+ */
+@Mapper
+public interface FollowUpStatisticsMapper extends BaseMapper<FollowUpStatistics> {
+    
+    /**
+     * 根据资源ID和红娘ID查询统计数据
+     * 
+     * @param resourceId 资源ID
+     * @param matchmakerId 红娘ID
+     * @return 统计数据
+     */
+    FollowUpStatistics selectByResourceIdAndMatchmakerId(
+            @Param("resourceId") Integer resourceId,
+            @Param("matchmakerId") Integer matchmakerId
+    );
+}
+

+ 17 - 0
service/homePage/src/main/java/com/zhentao/mapper/MyResourceMapper.java

@@ -6,6 +6,9 @@ import com.zhentao.entity.MyResource;
 import com.zhentao.vo.MyResourceVO;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Map;
 
 /**
 * @author Administrator
@@ -38,6 +41,20 @@ public interface MyResourceMapper extends BaseMapper<MyResource> {
             @Param("matchmakerId") Integer matchmakerId,
             @Param("keyword") String keyword
     );
+    
+    /**
+     * 根据用户ID查询用户资料信息
+     * 
+     * @param userId 用户ID
+     * @return 用户资料Map
+     */
+    @Select("SELECT up.height, up.weight, up.marital_status, up.house, up.car, " +
+            "up.education_level, up.salary_range, up.job_title, up.company, " +
+            "u.birth_date " +
+            "FROM user_profile up " +
+            "LEFT JOIN users u ON up.user_id = u.user_id " +
+            "WHERE up.user_id = #{userId}")
+    Map<String, Object> selectUserProfileByUserId(@Param("userId") Integer userId);
 }
 
 

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

@@ -0,0 +1,26 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.UserFollowUp;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 用户跟进表Mapper
+ */
+@Mapper
+public interface UserFollowUpMapper extends BaseMapper<UserFollowUp> {
+    
+    /**
+     * 根据资源ID和红娘ID查询最新的跟进记录
+     * 
+     * @param resourceId 资源ID
+     * @param matchmakerId 红娘ID
+     * @return 跟进记录
+     */
+    UserFollowUp selectLatestByResourceIdAndMatchmakerId(
+            @Param("resourceId") Integer resourceId,
+            @Param("matchmakerId") Integer matchmakerId
+    );
+}
+

+ 39 - 0
service/homePage/src/main/java/com/zhentao/service/FollowUpStatisticsService.java

@@ -0,0 +1,39 @@
+package com.zhentao.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.entity.FollowUpStatistics;
+
+/**
+ * 跟进统计服务接口
+ */
+public interface FollowUpStatisticsService extends IService<FollowUpStatistics> {
+    
+    /**
+     * 根据资源ID和红娘ID获取或创建统计数据
+     * 如果不存在则创建,如果存在则更新统计数据
+     * 
+     * @param resourceId 资源ID
+     * @param matchmakerId 红娘ID
+     * @return 统计数据
+     */
+    FollowUpStatistics getOrCreateStatistics(Integer resourceId, Integer matchmakerId);
+    
+    /**
+     * 更新统计数据(根据user_follow_up表重新统计)
+     * 
+     * @param resourceId 资源ID
+     * @param matchmakerId 红娘ID
+     * @return 是否成功
+     */
+    boolean updateStatistics(Integer resourceId, Integer matchmakerId);
+    
+    /**
+     * 根据资源ID和红娘ID获取统计数据
+     * 
+     * @param resourceId 资源ID
+     * @param matchmakerId 红娘ID
+     * @return 统计数据,如果不存在返回null
+     */
+    FollowUpStatistics getStatistics(Integer resourceId, Integer matchmakerId);
+}
+

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

@@ -56,4 +56,21 @@ public interface MyResourceService extends IService<MyResource> {
      * @return 客户详情
      */
     com.zhentao.vo.ClientDetailVO getClientDetail(Integer resourceId);
+    
+    /**
+     * 根据手机号更新资源的注册状态
+     * 当用户注册时,如果my_resource表中存在相同手机号的资源记录,自动更新is_user和user_id
+     * @param phone 手机号
+     * @param userId 用户ID
+     * @return 是否成功更新
+     */
+    boolean updateResourceStatusByPhone(String phone, Integer userId);
+    
+    /**
+     * 精准匹配
+     * 根据资源用户的择偶要求,匹配推荐异性用户
+     * @param resourceId 资源ID
+     * @return 匹配结果列表(最多3个,按匹配度降序)
+     */
+    java.util.List<com.zhentao.vo.MatchResultVO> preciseMatch(Integer resourceId);
 }

+ 28 - 0
service/homePage/src/main/java/com/zhentao/service/UserFollowUpService.java

@@ -0,0 +1,28 @@
+package com.zhentao.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.entity.UserFollowUp;
+
+/**
+ * 用户跟进服务接口
+ */
+public interface UserFollowUpService extends IService<UserFollowUp> {
+    
+    /**
+     * 保存或更新跟进信息
+     * 
+     * @param followUp 跟进信息
+     * @return 是否成功
+     */
+    boolean saveOrUpdateFollowUp(UserFollowUp followUp);
+    
+    /**
+     * 根据资源ID和红娘ID获取最新的跟进记录
+     * 
+     * @param resourceId 资源ID
+     * @param matchmakerId 红娘ID
+     * @return 跟进记录,如果不存在返回null
+     */
+    UserFollowUp getLatestByResourceIdAndMatchmakerId(Integer resourceId, Integer matchmakerId);
+}
+

+ 145 - 0
service/homePage/src/main/java/com/zhentao/service/impl/FollowUpStatisticsServiceImpl.java

@@ -0,0 +1,145 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.FollowUpStatistics;
+import com.zhentao.entity.UserFollowUp;
+import com.zhentao.mapper.FollowUpStatisticsMapper;
+import com.zhentao.mapper.UserFollowUpMapper;
+import com.zhentao.service.FollowUpStatisticsService;
+import com.zhentao.service.MyResourceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 跟进统计服务实现类
+ */
+@Service
+public class FollowUpStatisticsServiceImpl extends ServiceImpl<FollowUpStatisticsMapper, FollowUpStatistics> 
+        implements FollowUpStatisticsService {
+    
+    @Autowired
+    private UserFollowUpMapper userFollowUpMapper;
+    
+    @Autowired
+    private MyResourceService myResourceService;
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public FollowUpStatistics getOrCreateStatistics(Integer resourceId, Integer matchmakerId) {
+        // 查询是否已存在统计数据
+        QueryWrapper<FollowUpStatistics> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("resource_id", resourceId);
+        queryWrapper.eq("matchmaker_id", matchmakerId);
+        FollowUpStatistics statistics = this.getOne(queryWrapper);
+        
+        if (statistics == null) {
+            // 创建新的统计数据
+            statistics = new FollowUpStatistics();
+            statistics.setResourceId(resourceId);
+            statistics.setMatchmakerId(matchmakerId);
+            
+            // 查询资源信息获取user_id
+            com.zhentao.entity.MyResource resource = myResourceService.getById(resourceId);
+            if (resource != null && resource.getUserId() != null) {
+                statistics.setUserId(resource.getUserId());
+            }
+            
+            Date now = new Date();
+            statistics.setCreateTime(now);
+            statistics.setUpdateTime(now);
+            
+            // 初始化统计数据
+            statistics.setFollowCount(0);
+            statistics.setMatchCount(0);
+            statistics.setPhoneCount(0);
+            statistics.setInterviewCount(0);
+            
+            this.save(statistics);
+        }
+        
+        // 更新统计数据
+        updateStatistics(resourceId, matchmakerId);
+        
+        // 重新查询返回最新数据
+        return this.getOne(queryWrapper);
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateStatistics(Integer resourceId, Integer matchmakerId) {
+        // 查询该资源的所有跟进记录
+        QueryWrapper<UserFollowUp> followUpQuery = new QueryWrapper<>();
+        followUpQuery.eq("resource_id", resourceId);
+        followUpQuery.eq("matchmaker_id", matchmakerId);
+        List<UserFollowUp> followUpList = userFollowUpMapper.selectList(followUpQuery);
+        
+        // 统计各项数据
+        int followCount = followUpList.size();
+        int matchCount = 0;
+        int phoneCount = 0;
+        int interviewCount = 0;
+        
+        for (UserFollowUp followUp : followUpList) {
+            // 统计匹配对象数(is_match=1)
+            if (followUp.getIsMatch() != null && followUp.getIsMatch() == 1) {
+                matchCount++;
+            }
+            
+            // 统计电话沟通次数(follow_type=1)
+            if (followUp.getFollowType() != null && followUp.getFollowType() == 1) {
+                phoneCount++;
+            }
+            
+            // 统计面谈次数(follow_type=2)
+            if (followUp.getFollowType() != null && followUp.getFollowType() == 2) {
+                interviewCount++;
+            }
+        }
+        
+        // 查询或创建统计数据
+        QueryWrapper<FollowUpStatistics> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("resource_id", resourceId);
+        queryWrapper.eq("matchmaker_id", matchmakerId);
+        FollowUpStatistics statistics = this.getOne(queryWrapper);
+        
+        if (statistics == null) {
+            // 创建新的统计数据
+            statistics = new FollowUpStatistics();
+            statistics.setResourceId(resourceId);
+            statistics.setMatchmakerId(matchmakerId);
+            
+            // 查询资源信息获取user_id
+            com.zhentao.entity.MyResource resource = myResourceService.getById(resourceId);
+            if (resource != null && resource.getUserId() != null) {
+                statistics.setUserId(resource.getUserId());
+            }
+            
+            Date now = new Date();
+            statistics.setCreateTime(now);
+            statistics.setUpdateTime(now);
+        }
+        
+        // 更新统计数据
+        statistics.setFollowCount(followCount);
+        statistics.setMatchCount(matchCount);
+        statistics.setPhoneCount(phoneCount);
+        statistics.setInterviewCount(interviewCount);
+        statistics.setUpdateTime(new Date());
+        
+        return this.saveOrUpdate(statistics);
+    }
+    
+    @Override
+    public FollowUpStatistics getStatistics(Integer resourceId, Integer matchmakerId) {
+        QueryWrapper<FollowUpStatistics> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("resource_id", resourceId);
+        queryWrapper.eq("matchmaker_id", matchmakerId);
+        return this.getOne(queryWrapper);
+    }
+}
+

+ 412 - 0
service/homePage/src/main/java/com/zhentao/service/impl/MyResourceServiceImpl.java

@@ -49,6 +49,9 @@ public class MyResourceServiceImpl extends ServiceImpl<MyResourceMapper, MyResou
     
     @Autowired(required = false)
     private PartnerRequirementService partnerRequirementService;
+    
+    @Autowired(required = false)
+    private com.zhentao.service.UserFollowUpService userFollowUpService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -241,6 +244,27 @@ public class MyResourceServiceImpl extends ServiceImpl<MyResourceMapper, MyResou
         // 择偶要求从my_resource表的mate_selection_criteria字段获取
         clientDetail.setMateSelectionCriteria(myResource.getMateSelectionCriteria());
         
+        // 从跟进表获取匹配和邀约状态
+        if (userFollowUpService != null) {
+            com.zhentao.entity.UserFollowUp followUp = userFollowUpService.getLatestByResourceIdAndMatchmakerId(
+                    myResource.getResourceId(), myResource.getMatchmakerId());
+            if (followUp != null) {
+                clientDetail.setIsMatch(followUp.getIsMatch());
+                clientDetail.setIsInvitation(followUp.getIsInvitation());
+                clientDetail.setInvitationTime(followUp.getInvitationTime());
+            } else {
+                // 如果没有跟进记录,设置默认值
+                clientDetail.setIsMatch(0);
+                clientDetail.setIsInvitation(0);
+                clientDetail.setInvitationTime(null);
+            }
+        } else {
+            // 如果服务不可用,设置默认值
+            clientDetail.setIsMatch(0);
+            clientDetail.setIsInvitation(0);
+            clientDetail.setInvitationTime(null);
+        }
+        
         // 查询该资源的标签
         if (myResource.getResourceId() != null) {
             List<Integer> tagIds = myResourceTagMapper.selectTagIdsByResourceId(myResource.getResourceId());
@@ -317,6 +341,394 @@ public class MyResourceServiceImpl extends ServiceImpl<MyResourceMapper, MyResou
         
         return clientDetail;
     }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateResourceStatusByPhone(String phone, Integer userId) {
+        if (phone == null || phone.trim().isEmpty() || userId == null) {
+            System.out.println("⚠️ 更新资源状态失败:手机号或用户ID为空");
+            return false;
+        }
+        
+        try {
+            // 根据手机号查询资源列表
+            QueryWrapper<MyResource> queryWrapper = new QueryWrapper<>();
+            queryWrapper.eq("phone", phone.trim());
+            List<MyResource> resources = this.list(queryWrapper);
+            
+            if (resources == null || resources.isEmpty()) {
+                System.out.println("未找到匹配的资源记录,手机号: " + phone);
+                return false;
+            }
+            
+            // 更新所有匹配的资源记录
+            boolean hasUpdate = false;
+            for (MyResource resource : resources) {
+                // 只更新未注册的资源(is_user = 0)
+                if (resource.getIsUser() == null || resource.getIsUser() == 0) {
+                    resource.setIsUser(1);
+                    resource.setUserId(userId);
+                    resource.setUpdateTime(new Date());
+                    boolean updated = this.updateById(resource);
+                    if (updated) {
+                        hasUpdate = true;
+                        System.out.println("✅ 更新资源注册状态成功 - 资源ID: " + resource.getResourceId() + 
+                                         ", 手机号: " + phone + ", 用户ID: " + userId);
+                    } else {
+                        System.out.println("⚠️ 更新资源注册状态失败 - 资源ID: " + resource.getResourceId());
+                    }
+                } else {
+                    System.out.println("资源已注册,跳过更新 - 资源ID: " + resource.getResourceId());
+                }
+            }
+            
+            return hasUpdate;
+        } catch (Exception e) {
+            System.err.println("❌ 更新资源注册状态时发生异常: " + e.getMessage());
+            e.printStackTrace();
+            return false;
+        }
+    }
+    
+    @Override
+    public java.util.List<com.zhentao.vo.MatchResultVO> preciseMatch(Integer resourceId) {
+        System.out.println("=== 开始精准匹配 ===");
+        System.out.println("资源ID: " + resourceId);
+        
+        // 1. 获取资源用户信息
+        MyResource resource = this.getById(resourceId);
+        if (resource == null) {
+            System.out.println("资源不存在,resourceId: " + resourceId);
+            return new java.util.ArrayList<>();
+        }
+        
+        // 检查用户是否已注册
+        if (resource.getIsUser() == null || resource.getIsUser() != 1 || resource.getUserId() == null) {
+            System.out.println("资源用户未注册,无法进行匹配");
+            throw new RuntimeException("该用户还未注册用户端,注册之后才能进行匹配");
+        }
+        
+        Integer resourceUserId = resource.getUserId();
+        Integer resourceGender = resource.getGender(); // 1-男, 2-女
+        
+        System.out.println("资源用户ID: " + resourceUserId + ", 性别: " + resourceGender);
+        
+        // 2. 获取择偶要求
+        PartnerRequirement requirement = null;
+        if (partnerRequirementService != null) {
+            requirement = partnerRequirementService.getByUserId(resourceUserId.longValue());
+        }
+        
+        if (requirement == null) {
+            System.out.println("未找到择偶要求,使用默认匹配逻辑");
+        } else {
+            System.out.println("择偶要求: 年龄" + requirement.getMinAge() + "-" + requirement.getMaxAge() + 
+                             ", 身高" + requirement.getMinHeight() + "-" + requirement.getMaxHeight() + 
+                             ", 学历" + requirement.getEducationLevel());
+        }
+        
+        // 3. 确定目标性别(异性)
+        Integer targetGender = (resourceGender == 1) ? 2 : 1; // 1-男找女, 2-女找男
+        
+        // 4. 查询所有异性用户
+        QueryWrapper<User> userQuery = new QueryWrapper<>();
+        userQuery.eq("gender", targetGender);
+        userQuery.eq("status", 1); // 只查询正常状态的用户
+        List<User> allUsers = userMapper.selectList(userQuery);
+        
+        System.out.println("找到 " + allUsers.size() + " 个异性用户");
+        
+        if (allUsers.isEmpty()) {
+            return new java.util.ArrayList<>();
+        }
+        
+        // 5. 计算每个用户的匹配度
+        List<com.zhentao.vo.MatchResultVO> matchResults = new java.util.ArrayList<>();
+        
+        for (User user : allUsers) {
+            // 跳过自己
+            if (user.getUserId().equals(resourceUserId)) {
+                continue;
+            }
+            
+            com.zhentao.vo.MatchResultVO matchResult = new com.zhentao.vo.MatchResultVO();
+            matchResult.setUserId(user.getUserId());
+            matchResult.setNickname(user.getNickname());
+            matchResult.setGender(user.getGender());
+            matchResult.setAvatarUrl(user.getAvatarUrl());
+            
+            // 查询用户资料(通过SQL)
+            // 由于homePage服务可能没有UserProfileMapper,我们通过MyResourceMapper执行SQL查询
+            java.util.Map<String, Object> profileMap = myResourceMapper.selectUserProfileByUserId(user.getUserId());
+            
+            if (profileMap != null) {
+                matchResult.setHeight((Integer) profileMap.get("height"));
+                Object birthDateObj = profileMap.get("birth_date");
+                if (birthDateObj != null) {
+                    java.util.Date birthDate = null;
+                    if (birthDateObj instanceof java.util.Date) {
+                        birthDate = (java.util.Date) birthDateObj;
+                    } else if (birthDateObj instanceof java.sql.Date) {
+                        birthDate = new java.util.Date(((java.sql.Date) birthDateObj).getTime());
+                    } else if (birthDateObj instanceof java.time.LocalDate) {
+                        java.time.LocalDate localDate = (java.time.LocalDate) birthDateObj;
+                        birthDate = java.sql.Date.valueOf(localDate);
+                    }
+                    matchResult.setAge(calculateAge(birthDate));
+                }
+                matchResult.setMaritalStatus((Integer) profileMap.get("marital_status"));
+                matchResult.setHouse((Integer) profileMap.get("house"));
+                matchResult.setCar((Integer) profileMap.get("car"));
+                
+                // 学历转换
+                Integer educationLevel = (Integer) profileMap.get("education_level");
+                matchResult.setDiploma(convertEducationLevel(educationLevel));
+                
+                // 收入转换
+                Integer salaryRange = (Integer) profileMap.get("salary_range");
+                matchResult.setIncome(convertSalaryRange(salaryRange));
+                
+                // 职业
+                matchResult.setOccupation((String) profileMap.get("job_title"));
+                
+                // 住址(从company获取,这里简化处理)
+                matchResult.setAddress((String) profileMap.get("company"));
+            }
+            
+            // 查询用户手机号(脱敏)
+            matchResult.setPhone(user.getPhone() != null ? maskPhone(user.getPhone()) : "");
+            
+            // 查询标签
+            List<String> tags = new java.util.ArrayList<>();
+            // 这里可以查询用户的标签,暂时使用空列表
+            matchResult.setTags(tags);
+            
+            // 计算匹配度
+            double matchScore = calculateMatchScore(resource, requirement, matchResult);
+            matchResult.setMatchScore(matchScore);
+            
+            // 查询匹配到的用户的择偶要求(而不是资源用户的择偶要求)
+            PartnerRequirement candidateRequirement = null;
+            if (partnerRequirementService != null) {
+                candidateRequirement = partnerRequirementService.getByUserId(user.getUserId().longValue());
+            }
+            
+            // 择偶要求文本(显示匹配到的用户的要求)
+            matchResult.setRequirement(formatRequirement(candidateRequirement));
+            
+            matchResults.add(matchResult);
+        }
+        
+        // 6. 按匹配度降序排序,取前3个
+        matchResults.sort((a, b) -> Double.compare(b.getMatchScore(), a.getMatchScore()));
+        
+        int limit = Math.min(3, matchResults.size());
+        List<com.zhentao.vo.MatchResultVO> topMatches = matchResults.subList(0, limit);
+        
+        System.out.println("匹配完成,返回 " + topMatches.size() + " 个结果");
+        for (com.zhentao.vo.MatchResultVO result : topMatches) {
+            System.out.println("用户: " + result.getNickname() + ", 匹配度: " + result.getMatchScore());
+        }
+        
+        return topMatches;
+    }
+    
+    /**
+     * 计算匹配度分数
+     */
+    private double calculateMatchScore(MyResource resource, PartnerRequirement requirement, com.zhentao.vo.MatchResultVO candidate) {
+        double totalScore = 0.0;
+        int factorCount = 0;
+        
+        if (requirement == null) {
+            // 如果没有择偶要求,返回基础分数
+            return 60.0;
+        }
+        
+        // 1. 年龄匹配 (权重: 20%)
+        if (requirement.getMinAge() != null && requirement.getMaxAge() != null && candidate.getAge() != null) {
+            int age = candidate.getAge();
+            if (age >= requirement.getMinAge() && age <= requirement.getMaxAge()) {
+                totalScore += 20.0;
+            } else {
+                // 年龄差距越大,分数越低
+                int gap = Math.min(Math.abs(age - requirement.getMinAge()), Math.abs(age - requirement.getMaxAge()));
+                totalScore += Math.max(0, 20.0 - gap * 2);
+            }
+            factorCount++;
+        }
+        
+        // 2. 身高匹配 (权重: 20%)
+        if (requirement.getMinHeight() != null && requirement.getMaxHeight() != null && candidate.getHeight() != null) {
+            int height = candidate.getHeight();
+            if (height >= requirement.getMinHeight() && height <= requirement.getMaxHeight()) {
+                totalScore += 20.0;
+            } else {
+                int gap = Math.min(Math.abs(height - requirement.getMinHeight()), Math.abs(height - requirement.getMaxHeight()));
+                totalScore += Math.max(0, 20.0 - gap * 0.5);
+            }
+            factorCount++;
+        }
+        
+        // 3. 学历匹配 (权重: 15%)
+        if (requirement.getEducationLevel() != null && candidate.getDiploma() != null) {
+            int requiredLevel = requirement.getEducationLevel();
+            int candidateLevel = parseEducationLevel(candidate.getDiploma());
+            if (candidateLevel >= requiredLevel) {
+                totalScore += 15.0;
+            } else {
+                totalScore += Math.max(0, 15.0 - (requiredLevel - candidateLevel) * 3);
+            }
+            factorCount++;
+        }
+        
+        // 4. 收入匹配 (权重: 15%)
+        if (requirement.getSalaryRange() != null && candidate.getIncome() != null) {
+            int requiredRange = requirement.getSalaryRange();
+            int candidateRange = parseSalaryRange(candidate.getIncome());
+            if (candidateRange >= requiredRange) {
+                totalScore += 15.0;
+            } else {
+                totalScore += Math.max(0, 15.0 - (requiredRange - candidateRange) * 3);
+            }
+            factorCount++;
+        }
+        
+        // 5. 房产匹配 (权重: 10%)
+        if (requirement.getHouse() != null && requirement.getHouse() != 0 && candidate.getHouse() != null) {
+            if (candidate.getHouse() == 1) {
+                totalScore += 10.0;
+            }
+            factorCount++;
+        }
+        
+        // 6. 车辆匹配 (权重: 10%)
+        if (requirement.getCar() != null && requirement.getCar() != 0 && candidate.getCar() != null) {
+            if (candidate.getCar() == 1) {
+                totalScore += 10.0;
+            }
+            factorCount++;
+        }
+        
+        // 7. 婚姻状况匹配 (权重: 10%)
+        if (requirement.getMaritalStatus() != null && requirement.getMaritalStatus() != 0 && candidate.getMaritalStatus() != null) {
+            if (candidate.getMaritalStatus().equals(requirement.getMaritalStatus())) {
+                totalScore += 10.0;
+            }
+            factorCount++;
+        }
+        
+        // 如果没有任何匹配因子,返回基础分数
+        if (factorCount == 0) {
+            return 60.0;
+        }
+        
+        // 归一化到0-100
+        double maxScore = 100.0;
+        return Math.min(100.0, totalScore);
+    }
+    
+    /**
+     * 计算年龄(从出生日期)
+     */
+    private Integer calculateAge(java.util.Date birthDate) {
+        if (birthDate == null) {
+            return null;
+        }
+        java.util.Calendar birth = java.util.Calendar.getInstance();
+        birth.setTime(birthDate);
+        java.util.Calendar now = java.util.Calendar.getInstance();
+        int age = now.get(java.util.Calendar.YEAR) - birth.get(java.util.Calendar.YEAR);
+        if (now.get(java.util.Calendar.DAY_OF_YEAR) < birth.get(java.util.Calendar.DAY_OF_YEAR)) {
+            age--;
+        }
+        return age;
+    }
+    
+    /**
+     * 转换学历等级为文本
+     */
+    private String convertEducationLevel(Integer level) {
+        if (level == null) return null;
+        switch (level) {
+            case 1: return "高中";
+            case 2: return "专科";
+            case 3: return "本科";
+            case 4: return "硕士";
+            case 5: return "博士";
+            default: return "未知";
+        }
+    }
+    
+    /**
+     * 解析学历文本为等级
+     */
+    private int parseEducationLevel(String diploma) {
+        if (diploma == null) return 0;
+        if (diploma.contains("博士")) return 5;
+        if (diploma.contains("硕士")) return 4;
+        if (diploma.contains("本科")) return 3;
+        if (diploma.contains("专科")) return 2;
+        if (diploma.contains("高中")) return 1;
+        return 0;
+    }
+    
+    /**
+     * 转换收入范围为文本
+     */
+    private String convertSalaryRange(Integer range) {
+        if (range == null) return null;
+        switch (range) {
+            case 1: return "5k以下";
+            case 2: return "5-10k";
+            case 3: return "10-20k";
+            case 4: return "20-50k";
+            case 5: return "50k+";
+            default: return "未知";
+        }
+    }
+    
+    /**
+     * 解析收入文本为范围
+     */
+    private int parseSalaryRange(String income) {
+        if (income == null) return 0;
+        if (income.contains("50k+") || income.contains("50k以上")) return 5;
+        if (income.contains("20-50k") || income.contains("20k")) return 4;
+        if (income.contains("10-20k") || income.contains("10k")) return 3;
+        if (income.contains("5-10k") || income.contains("5k")) return 2;
+        return 1;
+    }
+    
+    /**
+     * 手机号脱敏
+     */
+    private String maskPhone(String phone) {
+        if (phone == null || phone.length() < 11) {
+            return phone;
+        }
+        return phone.substring(0, 3) + "****" + phone.substring(7);
+    }
+    
+    /**
+     * 格式化择偶要求文本
+     */
+    private String formatRequirement(PartnerRequirement requirement) {
+        if (requirement == null) {
+            return "暂无要求";
+        }
+        StringBuilder sb = new StringBuilder();
+        if (requirement.getMinAge() != null && requirement.getMaxAge() != null) {
+            sb.append("年龄").append(requirement.getMinAge()).append("-").append(requirement.getMaxAge()).append("岁 ");
+        }
+        if (requirement.getMinHeight() != null && requirement.getMaxHeight() != null) {
+            sb.append("身高").append(requirement.getMinHeight()).append("-").append(requirement.getMaxHeight()).append("cm ");
+        }
+        if (requirement.getEducationLevel() != null && requirement.getEducationLevel() != 0) {
+            sb.append(convertEducationLevel(requirement.getEducationLevel())).append(" ");
+        }
+        return sb.length() > 0 ? sb.toString().trim() : "暂无要求";
+    }
 }
 
 

+ 53 - 0
service/homePage/src/main/java/com/zhentao/service/impl/UserFollowUpServiceImpl.java

@@ -0,0 +1,53 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.UserFollowUp;
+import com.zhentao.mapper.UserFollowUpMapper;
+import com.zhentao.service.UserFollowUpService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+
+/**
+ * 用户跟进服务实现类
+ */
+@Service
+public class UserFollowUpServiceImpl extends ServiceImpl<UserFollowUpMapper, UserFollowUp> 
+        implements UserFollowUpService {
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean saveOrUpdateFollowUp(UserFollowUp followUp) {
+        Date now = new Date();
+        
+        // 每次保存跟进信息都创建新记录,以支持累加统计
+        // 这样每次跟进都会留下历史记录,统计时可以累加所有记录
+        
+        // 处理备注字段:如果为空字符串,设置为null
+        if (followUp.getRemark() != null && followUp.getRemark().trim().isEmpty()) {
+            followUp.setRemark(null);
+        }
+        
+        // 设置时间
+        followUp.setCreateTime(now);
+        followUp.setUpdateTime(now);
+        
+        // 创建新记录(不更新现有记录)
+        return this.save(followUp);
+    }
+    
+    @Override
+    public UserFollowUp getLatestByResourceIdAndMatchmakerId(Integer resourceId, Integer matchmakerId) {
+        QueryWrapper<UserFollowUp> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("resource_id", resourceId);
+        if (matchmakerId != null) {
+            queryWrapper.eq("matchmaker_id", matchmakerId);
+        }
+        queryWrapper.orderByDesc("update_time");
+        queryWrapper.last("LIMIT 1");
+        return this.getOne(queryWrapper);
+    }
+}
+

+ 5 - 0
service/homePage/src/main/java/com/zhentao/vo/ClientDetailVO.java

@@ -54,5 +54,10 @@ public class ClientDetailVO implements Serializable {
     
     // 客户标签(从my_resource_tag关联表查询)
     private java.util.List<String> tags; // 标签名称列表
+    
+    // 匹配和邀约状态
+    private Integer isMatch; // 该资源用户是否已匹配对象(0-未匹配,1-已匹配)
+    private Integer isInvitation; // 红娘是否邀约该用户(0-未邀约,1-已邀约)
+    private java.util.Date invitationTime; // 邀约用户的时间
 }
 

+ 102 - 0
service/homePage/src/main/java/com/zhentao/vo/MatchResultVO.java

@@ -0,0 +1,102 @@
+package com.zhentao.vo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import java.io.Serializable;
+
+/**
+ * 匹配结果VO
+ */
+@Data
+public class MatchResultVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 用户ID
+     */
+    @JsonProperty("userId")
+    private Integer userId;
+    
+    /**
+     * 昵称
+     */
+    private String nickname;
+    
+    /**
+     * 性别 (1-男, 2-女)
+     */
+    private Integer gender;
+    
+    /**
+     * 年龄
+     */
+    private Integer age;
+    
+    /**
+     * 身高(cm)
+     */
+    private Integer height;
+    
+    /**
+     * 头像URL
+     */
+    @JsonProperty("avatarUrl")
+    private String avatarUrl;
+    
+    /**
+     * 学历
+     */
+    private String diploma;
+    
+    /**
+     * 收入
+     */
+    private String income;
+    
+    /**
+     * 职业
+     */
+    private String occupation;
+    
+    /**
+     * 住址
+     */
+    private String address;
+    
+    /**
+     * 婚姻状况
+     */
+    private Integer maritalStatus;
+    
+    /**
+     * 购房情况 (0-无, 1-有)
+     */
+    private Integer house;
+    
+    /**
+     * 购车情况 (0-无, 1-有)
+     */
+    private Integer car;
+    
+    /**
+     * 手机号(脱敏)
+     */
+    private String phone;
+    
+    /**
+     * 匹配度分数 (0-100)
+     */
+    private Double matchScore;
+    
+    /**
+     * 标签列表
+     */
+    private java.util.List<String> tags;
+    
+    /**
+     * 择偶要求
+     */
+    private String requirement;
+}
+

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

@@ -132,5 +132,20 @@ public class MyResourceVO implements Serializable {
      * 标签列表(从my_resource_tag关联表查询)
      */
     private java.util.List<String> tags;
+    
+    /**
+     * 该资源用户是否已匹配对象(0-未匹配,1-已匹配)
+     */
+    private Integer isMatch;
+    
+    /**
+     * 红娘是否邀约该用户(0-未邀约,1-已邀约)
+     */
+    private Integer isInvitation;
+    
+    /**
+     * 邀约用户的时间
+     */
+    private Date invitationTime;
 }
 

+ 20 - 3
service/homePage/src/main/resources/mapper/MyResourceMapper.xml

@@ -84,6 +84,9 @@
         <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
         <result property="avatarUrl" column="avatar_url" jdbcType="VARCHAR"/>
         <result property="isUser" column="is_user" jdbcType="INTEGER"/>
+        <result property="isMatch" column="is_match" jdbcType="INTEGER"/>
+        <result property="isInvitation" column="is_invitation" jdbcType="INTEGER"/>
+        <result property="invitationTime" column="invitation_time" jdbcType="TIMESTAMP"/>
     </resultMap>
 
     <!-- 分页查询资源列表(关联users表获取头像) -->
@@ -111,9 +114,23 @@
             r.is_user,
             r.create_time,
             r.update_time,
-            u.avatar_url
-        FROM my_resource r
-        LEFT JOIN users u ON r.phone = u.phone
+            u.avatar_url,
+            COALESCE(f.is_match, 0) as is_match,
+            COALESCE(f.is_invitation, 0) as is_invitation,
+            f.invitation_time
+            FROM my_resource r
+            LEFT JOIN users u ON r.user_id = u.user_id
+            LEFT JOIN (
+                SELECT f1.resource_id, f1.matchmaker_id, f1.is_match, f1.is_invitation, f1.invitation_time
+                FROM user_follow_up f1
+                INNER JOIN (
+                    SELECT resource_id, matchmaker_id, MAX(update_time) as max_update_time
+                    FROM user_follow_up
+                    GROUP BY resource_id, matchmaker_id
+                ) f2 ON f1.resource_id = f2.resource_id 
+                    AND f1.matchmaker_id = f2.matchmaker_id 
+                    AND f1.update_time = f2.max_update_time
+            ) f ON r.resource_id = f.resource_id AND r.matchmaker_id = f.matchmaker_id
         WHERE 1=1
         <if test="matchmakerId != null">
             AND r.matchmaker_id = #{matchmakerId}