Просмотр исходного кода

Merge branch 'yxy' into test_dev

yuxy 1 месяц назад
Родитель
Сommit
2a1f9446f4

+ 166 - 45
LiangZhiYUMao/pages/match/index.vue

@@ -27,22 +27,37 @@
 
 		<!-- 匹配方式选择 -->
 		<view class="match-modes">
-			<view class="mode-item" @click="selectMode('smart')">
+			<view 
+				class="mode-item" 
+				:class="{ 'mode-item-active': selectedMode === 'smart' }"
+				@tap="selectMode('smart')"
+			>
 				<view class="mode-icon">🎯</view>
 				<view class="mode-title">智能算法</view>
 				<view class="mode-desc">基于兴趣爱好、性格特征智能匹配</view>
+				<view v-if="selectedMode === 'smart'" class="mode-selected">✓ 已选择</view>
 			</view>
 
-			<view class="mode-item" @click="selectMode('online')">
+			<view 
+				class="mode-item" 
+				:class="{ 'mode-item-active': selectedMode === 'online' }"
+				@tap="selectMode('online')"
+			>
 				<view class="mode-icon">⚡</view>
 				<view class="mode-title">实时在线</view>
 				<view class="mode-desc">只匹配当前在线的优质用户</view>
+				<view v-if="selectedMode === 'online'" class="mode-selected">✓ 已选择</view>
 			</view>
 
-			<view class="mode-item" @click="selectMode('precise')">
+			<view 
+				class="mode-item" 
+				:class="{ 'mode-item-active': selectedMode === 'precise' }"
+				@tap="selectMode('precise')"
+			>
 				<view class="mode-icon">💎</view>
 				<view class="mode-title">精准推荐</view>
 				<view class="mode-desc">根据您的偏好推荐最合适的对象</view>
+				<view v-if="selectedMode === 'precise'" class="mode-selected">✓ 已选择</view>
 			</view>
 		</view>
 
@@ -88,6 +103,8 @@
 </template>
 
 <script>
+import MATCH_API_CONFIG from '@/config/match-config.js'
+
 export default {
 	data() {
 		return {
@@ -135,10 +152,17 @@ export default {
 		
 		// 选择匹配模式
 		selectMode(mode) {
+			console.log('选择匹配模式:', mode);
 			this.selectedMode = mode;
+			const modeNames = {
+				'smart': '智能算法',
+				'online': '实时在线',
+				'precise': '精准推荐'
+			};
 			uni.showToast({
-				title: mode === 'smart' ? '智能算法' : mode === 'online' ? '实时在线' : '精准推荐',
-				icon: 'none'
+				title: `已选择:${modeNames[mode] || mode}`,
+				icon: 'none',
+				duration: 1500
 			});
 		},
 		
@@ -167,23 +191,26 @@ export default {
 					city: this.userInfo.city || '未知',
 					introduction: this.userInfo.introduction || '',
 					latitude: this.userInfo.latitude || null,
-					longitude: this.userInfo.longitude || null
+					longitude: this.userInfo.longitude || null,
+					matchMode: this.selectedMode || 'smart' // 添加匹配模式
 				};
 				
 				console.log('发送匹配请求:', matchData);
 				
-				// 调用匹配接口
-				// 开发环境使用本地地址,生产环境使用服务器地址
-				const baseUrl = 'http://localhost:8083'; // 本地开发
-				// const baseUrl = 'http://115.190.125.125:8083'; // 生产环境
+				// 调用匹配接口 - 使用配置文件中的地址
+				const baseUrl = MATCH_API_CONFIG.BASE_URL;
+				const url = `${baseUrl}${MATCH_API_CONFIG.ENDPOINTS.START_MATCH}`;
+				
+				console.log('匹配API地址:', url);
 				
 				const [error, res] = await uni.request({
-					url: `${baseUrl}/match/start`,
+					url: url,
 					method: 'POST',
 					header: {
 						'Content-Type': 'application/json'
 					},
-					data: matchData
+					data: matchData,
+					timeout: 10000
 				});
 				
 				console.log('匹配响应:', res);
@@ -214,14 +241,28 @@ export default {
 					return;
 				}
 				
+				console.log('=== 匹配响应解析 ===');
+				console.log('完整响应:', JSON.stringify(res.data, null, 2));
+				console.log('code:', res.data.code);
+				console.log('status:', res.data.status);
+				console.log('msg:', res.data.msg);
+				console.log('data字段:', res.data.data);
+				
 				if (res.data.code === 200) {
-					console.log('=== 匹配成功,解析数据 ===');
-					console.log('status:', res.data.status);
-					console.log('完整data:', res.data.data);
-					
 					if (res.data.status === 'success') {
 						// 立即匹配成功
-						this.handleMatchSuccess(res.data.data);
+						const matchData = res.data.data;
+						if (!matchData) {
+							console.error('匹配成功但data为空');
+							this.isMatching = false;
+							this.matchStatus = '点击下方按钮开始匹配';
+							uni.showToast({
+								title: '匹配数据异常,请重试',
+								icon: 'none'
+							});
+							return;
+						}
+						this.handleMatchSuccess(matchData);
 					} else if (res.data.status === 'waiting') {
 						// 等待匹配,开始轮询
 						this.matchStatus = '正在寻找合适的对象...';
@@ -255,45 +296,90 @@ export default {
 		
 		// 开始轮询匹配状态
 		startPolling() {
-			// 每2秒检查一次匹配状态(实际项目中可以用WebSocket)
-			this.pollingTimer = setInterval(() => {
-				// 这里可以调用一个查询匹配状态的接口
-				// 暂时使用定时器模拟
-				console.log('轮询匹配状态...');
-			}, 2000);
+			let pollCount = 0;
+			const maxPolls = 15; // 最多轮询15次(30秒)
 			
-			// 30秒后停止匹配
-			setTimeout(() => {
-				if (this.isMatching) {
-					this.cancelMatch();
-					uni.showToast({
-						title: '暂无合适的匹配对象,请稍后再试',
-						icon: 'none'
-					});
+			// 每2秒检查一次匹配状态
+			this.pollingTimer = setInterval(async () => {
+				pollCount++;
+				console.log(`轮询匹配状态 (${pollCount}/${maxPolls})...`);
+				
+				// 如果超过最大轮询次数,停止匹配
+				if (pollCount >= maxPolls) {
+					if (this.isMatching) {
+						// 轮询超时,不显示取消匹配的提示,而是显示更合适的提示
+						this.cancelMatch(false);
+						uni.showToast({
+							title: '暂无合适的匹配对象,请稍后再试',
+							icon: 'none',
+							duration: 2000
+						});
+					}
+					return;
 				}
-			}, 30000);
+				
+				// 尝试查询匹配状态(如果后端有状态查询接口)
+				try {
+					const baseUrl = MATCH_API_CONFIG.BASE_URL;
+					const userId = String(this.userInfo.userId);
+					
+					// 注意:这里假设后端有状态查询接口,如果没有可以注释掉
+					// const res = await uni.request({
+					// 	url: `${baseUrl}${MATCH_API_CONFIG.ENDPOINTS.MATCH_STATUS}?userId=${userId}`,
+					// 	method: 'GET'
+					// });
+					// 
+					// if (res.data && res.data.code === 200 && res.data.status === 'success') {
+					// 	this.handleMatchSuccess(res.data.data);
+					// }
+				} catch (error) {
+					console.error('轮询查询失败:', error);
+				}
+			}, MATCH_API_CONFIG.CONFIG.POLLING_INTERVAL);
 		},
 		
 		// 取消匹配
-		async cancelMatch() {
+		// @param showToast 是否显示取消匹配的提示(默认true,轮询超时时传入false)
+		async cancelMatch(showToast = true) {
 			try {
-				const baseUrl = 'http://localhost:8083'; // 本地开发
-				// const baseUrl = 'http://115.190.125.125:8083'; // 生产环境
-				
+				const baseUrl = MATCH_API_CONFIG.BASE_URL;
 				const userId = String(this.userInfo.userId);
-				const res = await uni.request({
-					url: `${baseUrl}/match/cancel?userId=${userId}`,
-					method: 'POST'
-				});
+				const url = `${baseUrl}${MATCH_API_CONFIG.ENDPOINTS.CANCEL_MATCH}?userId=${userId}`;
 				
-				console.log('取消匹配响应:', res.data);
+				console.log('取消匹配请求:', url);
 				
-				uni.showToast({
-					title: '已取消匹配',
-					icon: 'none'
+				const [error, res] = await uni.request({
+					url: url,
+					method: 'POST',
+					timeout: 5000
 				});
+				
+				if (error) {
+					console.error('取消匹配请求失败:', error);
+					if (showToast) {
+						uni.showToast({
+							title: '取消匹配失败',
+							icon: 'none'
+						});
+					}
+				} else {
+					console.log('取消匹配响应:', res.data);
+					// 只有在需要显示提示时才显示
+					if (showToast && res.data && res.data.code === 200) {
+						uni.showToast({
+							title: res.data.msg || '已取消匹配',
+							icon: 'none'
+						});
+					}
+				}
 			} catch (error) {
 				console.error('取消匹配失败:', error);
+				if (showToast) {
+					uni.showToast({
+						title: '取消匹配失败',
+						icon: 'none'
+					});
+				}
 			}
 			
 			this.isMatching = false;
@@ -309,6 +395,20 @@ export default {
 		handleMatchSuccess(data) {
 			console.log('=== 处理匹配成功数据 ===');
 			console.log('匹配数据:', data);
+			console.log('数据类型:', typeof data);
+			console.log('数据是否为null:', data === null);
+			console.log('数据是否为undefined:', data === undefined);
+			
+			if (!data || typeof data !== 'object') {
+				console.error('匹配数据无效:', data);
+				this.isMatching = false;
+				this.matchStatus = '点击下方按钮开始匹配';
+				uni.showToast({
+					title: '匹配数据异常',
+					icon: 'none'
+				});
+				return;
+			}
 			
 			this.isMatching = false;
 			this.matchStatus = '匹配成功!';
@@ -319,7 +419,7 @@ export default {
 			}
 			
 			// 解析匹配数据 - 兼容不同的数据结构
-			this.matchScore = Math.round(data.matchScore || data.roomId ? 50 : 0);
+			this.matchScore = Math.round(data.matchScore || (data.roomId ? 50 : 0));
 			
 			// 尝试多种可能的数据结构
 			let matchedUserInfo = null;
@@ -546,6 +646,9 @@ export default {
 	margin-bottom: 24rpx;
 	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
 	transition: all 0.3s;
+	position: relative;
+	cursor: pointer;
+	-webkit-tap-highlight-color: transparent;
 }
 
 .mode-item:active {
@@ -553,6 +656,24 @@ export default {
 	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
 }
 
+.mode-item-active {
+	background: linear-gradient(135deg, #FFF5F7 0%, #FFFFFF 100%);
+	border: 2rpx solid #E91E63;
+	box-shadow: 0 6rpx 20rpx rgba(233, 30, 99, 0.2);
+}
+
+.mode-selected {
+	position: absolute;
+	top: 16rpx;
+	right: 16rpx;
+	background: #E91E63;
+	color: white;
+	padding: 8rpx 16rpx;
+	border-radius: 20rpx;
+	font-size: 24rpx;
+	font-weight: bold;
+}
+
 .mode-icon {
 	font-size: 60rpx;
 	text-align: center;

+ 18 - 3
LiangZhiYUMao/pages/plaza/detail.vue

@@ -67,7 +67,7 @@
 			</view>
 			
 			<!-- 评论区域 -->
-			<view class="comment-section">
+			<view id="comment-section" class="comment-section">
 				<view class="section-title">
 					<text class="title-text">评论区</text>
 					<text class="title-count">{{ getCommentCount() }}</text>
@@ -230,10 +230,25 @@ export default {
 	},
 	
 	onLoad(options) {
+		// 兼容 id 和 dynamicId 两种参数名
 		if (options.id) {
 			this.dynamicId = options.id
-            this.loadDynamicDetail()
-            this.loadComments()
+		} else if (options.dynamicId) {
+			this.dynamicId = options.dynamicId
+		}
+		
+		if (this.dynamicId) {
+			this.loadDynamicDetail()
+			this.loadComments()
+			
+			// 如果需要滚动到评论区域
+			if (options.scrollToComment === 'true') {
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.scrollIntoView = 'comment-section'
+					}, 500)
+				})
+			}
 		}
 	},
 	

+ 109 - 25
LiangZhiYUMao/pages/recommend/user-detail.vue

@@ -84,9 +84,9 @@
 			<view class="dynamic-section">
 				<view class="section-header">
 					<text class="section-title">动态</text>
-					<text class="section-more" @click="viewAllDynamics" v-if="dynamicList.length > 0">
-						查看全部({{ totalDynamics }}) >
-					</text>
+					<view class="section-more" @tap.stop="viewAllDynamics" v-if="dynamicList.length > 0">
+						<text>查看全部({{ totalDynamics }}) ></text>
+					</view>
 				</view>
 				
 				<!-- 动态列表 -->
@@ -102,7 +102,7 @@
 						v-for="(item, index) in dynamicList" 
 						:key="item.dynamicId || index" 
 						class="dynamic-item"
-						@click="viewDynamicDetail(item)"
+						@tap.stop="viewDynamicDetail(item, index)"
 					>
 						<!-- 动态内容 -->
 						<view class="dynamic-content" v-if="item.content">
@@ -118,7 +118,6 @@
 									:src="url" 
 									class="media-image"
 									mode="aspectFill"
-									@click.stop="previewMedia(item.mediaUrls, idx)"
 								/>
 							</view>
 						</view>
@@ -128,8 +127,8 @@
 							<text class="dynamic-time">{{ formatTime(item.createdAt) }}</text>
 							<view class="dynamic-stats">
 								<text class="stat-item">❤️ {{ item.likeCount || 0 }}</text>
-								<text class="stat-item comment-stat" @click.stop="viewDynamicComments(item)">💬 {{ item.commentCount || 0 }}</text>
 								<text class="stat-item">💬 {{ item.commentCount || 0 }}</text>
+								<text class="stat-item">⭐ {{ item.favoriteCount || 0 }}</text>
 							</view>
 						</view>
 					</view>
@@ -157,7 +156,7 @@ export default {
 			loadingDynamics: false,
 			totalDynamics: 0,
 			pageNum: 1,
-			pageSize: 6
+			pageSize: 3
 		}
 	},
 	
@@ -265,6 +264,14 @@ export default {
 				
 				// 处理mediaUrls字段(可能是字符串需要解析)
 				list = list.map(item => {
+					// 确保 dynamicId 存在
+					if (!item.dynamicId && item.id) {
+						item.dynamicId = item.id
+					}
+					if (!item.dynamicId && item.dynamic_id) {
+						item.dynamicId = item.dynamic_id
+					}
+					
 					if (item.mediaUrls && typeof item.mediaUrls === 'string') {
 						try {
 							item.mediaUrls = JSON.parse(item.mediaUrls)
@@ -276,12 +283,21 @@ export default {
 					if (!item.mediaUrls || !Array.isArray(item.mediaUrls)) {
 						item.mediaUrls = []
 					}
+					
+					console.log('处理后的动态项:', {
+						dynamicId: item.dynamicId,
+						content: item.content,
+						hasMedia: item.mediaUrls && item.mediaUrls.length > 0
+					})
+					
 					return item
 				})
 				
-				this.dynamicList = list
+				// 最多只显示3条动态
+				this.dynamicList = list.slice(0, 3)
 				this.totalDynamics = total
 				console.log('处理后的动态列表:', this.dynamicList)
+				console.log('动态列表中的 dynamicId:', this.dynamicList.map(item => item.dynamicId))
 			} catch (e) {
 				console.error('获取用户动态失败:', e)
 				uni.showToast({
@@ -295,41 +311,87 @@ export default {
 		
 		// 查看全部动态
 		viewAllDynamics() {
+			console.log('=== 点击查看全部动态 ===')
+			console.log('userId:', this.userId)
+			if (!this.userId) {
+				uni.showToast({
+					title: '用户ID无效',
+					icon: 'none'
+				})
+				return
+			}
+			const url = `/pages/recommend/user-dynamics?userId=${this.userId}`
+			console.log('准备跳转, url:', url)
 			uni.navigateTo({
-				url: `/pages/recommend/user-dynamics?userId=${this.userId}`
+				url: url,
+				success: (res) => {
+					console.log('跳转成功:', res)
+				},
+				fail: (err) => {
+					console.error('跳转失败:', err)
+					uni.showToast({
+						title: '跳转失败',
+						icon: 'none',
+						duration: 2000
+					})
+				}
 			})
 		},
 		
 		// 查看动态详情
-		viewDynamicDetail(item) {
-			if (!item || !item.dynamicId) {
-				console.error('动态项无效:', item)
+		viewDynamicDetail(item, index) {
+			console.log('=== 点击动态详情 ===')
+			console.log('传入的 item:', item)
+			console.log('传入的 index:', index)
+			console.log('dynamicList:', this.dynamicList)
+			
+			// 如果 item 无效,尝试从列表中根据索引获取
+			if (!item && typeof index === 'number' && this.dynamicList && this.dynamicList[index]) {
+				item = this.dynamicList[index]
+				console.log('从列表中获取 item:', item)
+			}
+			
+			if (!item) {
+				console.error('动态项为空,无法获取')
 				uni.showToast({
-					title: '动态信息无效',
+					title: '动态信息为空',
 					icon: 'none'
 				})
 				return
 			}
-			uni.navigateTo({
-				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}`
-			})
-		},
-		
-		// 查看动态评论
-		viewDynamicComments(item) {
-			if (!item || !item.dynamicId) {
-				console.error('动态项无效:', item)
+			
+			// 尝试多种可能的字段名
+			const dynamicId = item.dynamicId || item.id || item.dynamic_id
+			console.log('提取的 dynamicId:', dynamicId)
+			console.log('完整的 item 对象:', JSON.stringify(item))
+			
+			if (!dynamicId) {
+				console.error('动态ID无效,item 内容:', item)
 				uni.showToast({
-					title: '动态信息无效',
+					title: '动态ID无效',
 					icon: 'none'
 				})
 				return
 			}
+			
+			const url = `/pages/plaza/detail?dynamicId=${dynamicId}`
+			console.log('准备跳转, url:', url)
 			uni.navigateTo({
-				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}&scrollToComment=true`
+				url: url,
+				success: (res) => {
+					console.log('跳转成功:', res)
+				},
+				fail: (err) => {
+					console.error('跳转失败:', err)
+					uni.showToast({
+						title: '跳转失败: ' + (err.errMsg || '未知错误'),
+						icon: 'none',
+						duration: 2000
+					})
+				}
 			})
 		},
-
+		
 		// 预览媒体
 		previewMedia(urls, index) {
 			if (!urls || urls.length === 0) return
@@ -586,6 +648,15 @@ export default {
 .section-more {
 	font-size: 24rpx;
 	color: #E91E63;
+	cursor: pointer;
+	transition: opacity 0.2s;
+	padding: 8rpx 12rpx;
+	-webkit-tap-highlight-color: transparent;
+	user-select: none;
+}
+
+.section-more:active {
+	opacity: 0.6;
 }
 
 .dynamic-list {
@@ -599,6 +670,16 @@ export default {
 	background: #F8F9FA;
 	border-radius: 12rpx;
 	border: 1rpx solid #E0E0E0;
+	cursor: pointer;
+	transition: background-color 0.2s;
+	position: relative;
+	z-index: 1;
+	-webkit-tap-highlight-color: transparent;
+}
+
+.dynamic-item:active {
+	background: #E8E8E8;
+	opacity: 0.8;
 }
 
 .dynamic-content {
@@ -641,6 +722,9 @@ export default {
 	height: 200rpx;
 	border-radius: 8rpx;
 	background: #F5F5F5;
+	pointer-events: none;
+	-webkit-user-select: none;
+	user-select: none;
 }
 
 .dynamic-info {

+ 72 - 38
LiangZhiYUMao/pages/recommend/user-dynamics.vue

@@ -30,33 +30,31 @@
 					v-for="(item, index) in dynamicList" 
 					:key="item.dynamicId || index" 
 					class="dynamic-card"
-					@click="viewDynamicDetail(item)"
+					@tap="viewDynamicDetail(item, index)"
 				>
 					<!-- 动态内容 -->
 					<view class="dynamic-content" v-if="item.content">
 						<text class="dynamic-text">{{ item.content }}</text>
 					</view>
 					
-					<!-- 动态媒体(照片/视频) -->
-					<view class="dynamic-media" v-if="item.mediaUrls && item.mediaUrls.length > 0">
-						<view :class="['media-grid', getGridClass(item)]">
-							<image 
-								v-for="(url, idx) in item.mediaUrls.slice(0, 9)" 
-								:key="idx"
-								:src="url" 
-								class="media-image"
-								mode="aspectFill"
-								@click.stop="previewMedia(item.mediaUrls, idx)"
-							/>
+						<!-- 动态媒体(照片/视频) -->
+						<view class="dynamic-media" v-if="item.mediaUrls && item.mediaUrls.length > 0">
+							<view :class="['media-grid', getGridClass(item)]">
+								<image 
+									v-for="(url, idx) in item.mediaUrls.slice(0, 9)" 
+									:key="idx"
+									:src="url" 
+									class="media-image"
+									mode="aspectFill"
+								/>
+							</view>
 						</view>
-					</view>
 					
 					<!-- 动态信息 -->
 					<view class="dynamic-info">
 						<text class="dynamic-time">{{ formatTime(item.createdAt) }}</text>
 						<view class="dynamic-stats">
 							<text class="stat-item">❤️ {{ item.likeCount || 0 }}</text>
-							<text class="stat-item comment-stat" @click.stop="viewDynamicComments(item, index)">💬 {{ item.commentCount || 0 }}</text>
 							<text class="stat-item">💬 {{ item.commentCount || 0 }}</text>
 							<text class="stat-item">⭐ {{ item.favoriteCount || 0 }}</text>
 						</view>
@@ -146,6 +144,14 @@ export default {
 				
 				// 处理mediaUrls字段(可能是字符串需要解析)
 				list = list.map(item => {
+					// 确保 dynamicId 存在
+					if (!item.dynamicId && item.id) {
+						item.dynamicId = item.id
+					}
+					if (!item.dynamicId && item.dynamic_id) {
+						item.dynamicId = item.dynamic_id
+					}
+					
 					if (item.mediaUrls && typeof item.mediaUrls === 'string') {
 						try {
 							item.mediaUrls = JSON.parse(item.mediaUrls)
@@ -197,43 +203,58 @@ export default {
 		},
 		
 		// 查看动态详情
-		viewDynamicDetail(item) {
-			if (!item || !item.dynamicId) {
-				console.error('动态项无效:', item)
+		viewDynamicDetail(item, index) {
+			console.log('=== 点击动态详情 ===')
+			console.log('传入的 item:', item)
+			console.log('传入的 index:', index)
+			console.log('dynamicList:', this.dynamicList)
+			
+			// 如果 item 无效,尝试从列表中根据索引获取
+			if (!item && typeof index === 'number' && this.dynamicList && this.dynamicList[index]) {
+				item = this.dynamicList[index]
+				console.log('从列表中获取 item:', item)
+			}
+			
+			if (!item) {
+				console.error('动态项为空,无法获取')
 				uni.showToast({
-					title: '动态信息无效',
+					title: '动态信息为空',
 					icon: 'none'
 				})
 				return
 			}
-			uni.navigateTo({
-				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}`
-			})
-		},
-		
-		// 查看动态评论
-		viewDynamicComments(item, index) {
-			// 如果 item 无效,尝试从 dynamicList 中根据索引获取
-			if (!item || !item.dynamicId) {
-				if (typeof index === 'number' && this.dynamicList && this.dynamicList[index]) {
-					item = this.dynamicList[index]
-				}
-			}
-
-			if (!item || !item.dynamicId) {
-				console.error('动态项无效:', item, 'index:', index, 'dynamicList:', this.dynamicList)
+			
+			// 尝试多种可能的字段名
+			const dynamicId = item.dynamicId || item.id || item.dynamic_id
+			console.log('提取的 dynamicId:', dynamicId)
+			
+			if (!dynamicId) {
+				console.error('动态ID无效,item 内容:', item)
 				uni.showToast({
-					title: '动态信息无效',
+					title: '动态ID无效',
 					icon: 'none'
 				})
 				return
 			}
-
+			
+			const url = `/pages/plaza/detail?dynamicId=${dynamicId}`
+			console.log('准备跳转, url:', url)
 			uni.navigateTo({
-				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}&scrollToComment=true`
+				url: url,
+				success: (res) => {
+					console.log('跳转成功:', res)
+				},
+				fail: (err) => {
+					console.error('跳转失败:', err)
+					uni.showToast({
+						title: '跳转失败: ' + (err.errMsg || '未知错误'),
+						icon: 'none',
+						duration: 2000
+					})
+				}
 			})
 		},
-
+		
 		// 预览媒体
 		previewMedia(urls, index) {
 			if (!urls || urls.length === 0) return
@@ -335,6 +356,16 @@ export default {
 	border-radius: 16rpx;
 	padding: 24rpx;
 	border: 2rpx solid #E0E0E0;
+	cursor: pointer;
+	transition: background-color 0.2s;
+	position: relative;
+	z-index: 1;
+	-webkit-tap-highlight-color: transparent;
+}
+
+.dynamic-card:active {
+	background: #F8F9FA;
+	opacity: 0.8;
 }
 
 .dynamic-content {
@@ -373,6 +404,9 @@ export default {
 	height: 300rpx;
 	border-radius: 12rpx;
 	background: #F5F5F5;
+	pointer-events: none;
+	-webkit-user-select: none;
+	user-select: none;
 }
 
 .dynamic-info {

+ 1 - 0
service/randomMatch/src/main/java/com/zhentao/entity/MatchData.java

@@ -15,5 +15,6 @@ public class MatchData {
     private String introduction;
     private Double latitude;
     private Double longitude;
+    private String matchMode; // 匹配模式: smart(智能算法), online(实时在线), precise(精准推荐)
 }
 

+ 128 - 3
service/randomMatch/src/main/java/com/zhentao/service/MatchService.java

@@ -17,7 +17,10 @@ import java.util.concurrent.TimeUnit;
 @Service
 public class MatchService {
 
-    private static final double MATCH_THRESHOLD = 50.0;
+    // 不同匹配模式的阈值
+    private static final double MATCH_THRESHOLD_SMART = 50.0;   // 智能算法:50%
+    private static final double MATCH_THRESHOLD_ONLINE = 45.0;  // 实时在线:45%(降低阈值,提高匹配速度)
+    private static final double MATCH_THRESHOLD_PRECISE = 70.0; // 精准推荐:70%(提高阈值,更严格筛选)
 
     private final RedisMatchPool redisMatchPool;
     private final ObjectMapper objectMapper;
@@ -69,33 +72,133 @@ public class MatchService {
     }
 
     public MatchResult tryMatch(String userId, MatchData userData) {
+        // 获取匹配模式和阈值
+        String matchMode = userData.getMatchMode();
+        if (matchMode == null || matchMode.isEmpty()) {
+            matchMode = "smart"; // 默认智能算法
+        }
+        double threshold = getMatchThreshold(matchMode);
+        
         List<String> online = redisMatchPool.getAllUsersInPool();
         Set<String> unmatched = redisMatchPool.getUserUnmatchedSet(userId);
         Map<String, Double> scores = new HashMap<>();
+        
         for (String other : online) {
             if (userId.equals(other)) continue;
             if (unmatched.contains(other)) continue;
+            
             MatchData od = redisMatchPool.getUserMatchData(other);
             if (od == null) continue;
+            
+            // 性别筛选:只匹配异性
+            if (!MatchingAlgorithmUtils.isOppositeGender(userData.getGender(), od.getGender())) {
+                continue;
+            }
+            
+            // 根据匹配模式进行额外筛选
+            if (!shouldMatchByMode(userData, od, matchMode)) {
+                continue;
+            }
+            
             String otherLockKey = "match:lock:" + other;
             String otherLockVal = UUID.randomUUID().toString();
             boolean ok;
             try { ok = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(otherLockKey, otherLockVal, 5, TimeUnit.SECONDS)); }
             catch (Exception ex) { ok = acquireLockWithFallback(otherLockKey, otherLockVal, 5_000); }
             if (!ok) continue;
+            
             try {
                 if (!redisMatchPool.isUserInPool(other)) continue;
                 double score = MatchingAlgorithmUtils.calculateMatchScore(userData, od);
                 scores.put(other, score);
-                if (score >= MATCH_THRESHOLD) return new MatchResult(true, other, score);
+                if (score >= threshold) {
+                    return new MatchResult(true, other, score);
+                }
             } finally { releaseLockWithFallback(otherLockKey, otherLockVal); }
         }
+        
         if (!scores.isEmpty()) {
             Map.Entry<String, Double> best = scores.entrySet().stream().max(Map.Entry.comparingByValue()).get();
             redisMatchPool.recordUnmatch(userId, best.getKey());
         }
         return new MatchResult(false, null, 0);
     }
+    
+    /**
+     * 根据匹配模式获取阈值
+     */
+    private double getMatchThreshold(String matchMode) {
+        switch (matchMode.toLowerCase()) {
+            case "online":
+                return MATCH_THRESHOLD_ONLINE;
+            case "precise":
+                return MATCH_THRESHOLD_PRECISE;
+            case "smart":
+            default:
+                return MATCH_THRESHOLD_SMART;
+        }
+    }
+    
+    /**
+     * 根据匹配模式判断是否应该匹配
+     */
+    private boolean shouldMatchByMode(MatchData userData, MatchData otherData, String matchMode) {
+        switch (matchMode.toLowerCase()) {
+            case "precise":
+                // 精准推荐:更严格的筛选条件
+                // 1. 年龄差距不超过10岁
+                if (userData.getAge() != null && otherData.getAge() != null) {
+                    int ageDiff = Math.abs(userData.getAge() - otherData.getAge());
+                    if (ageDiff > 10) {
+                        return false;
+                    }
+                }
+                // 2. 地理位置距离不超过50公里
+                if (userData.getLatitude() != null && userData.getLongitude() != null &&
+                    otherData.getLatitude() != null && otherData.getLongitude() != null) {
+                    double distance = calculateDistance(
+                        userData.getLatitude(), userData.getLongitude(),
+                        otherData.getLatitude(), otherData.getLongitude()
+                    );
+                    if (distance > 50) {
+                        return false;
+                    }
+                }
+                // 3. 至少有一个共同兴趣
+                if (userData.getInterests() != null && otherData.getInterests() != null &&
+                    !userData.getInterests().isEmpty() && !otherData.getInterests().isEmpty()) {
+                    boolean hasCommonInterest = userData.getInterests().stream()
+                        .anyMatch(otherData.getInterests()::contains);
+                    if (!hasCommonInterest) {
+                        return false;
+                    }
+                }
+                return true;
+                
+            case "online":
+                // 实时在线:只匹配当前在匹配池中的用户(已经在池中,无需额外筛选)
+                return true;
+                
+            case "smart":
+            default:
+                // 智能算法:使用默认算法,无额外筛选
+                return true;
+        }
+    }
+    
+    /**
+     * 计算两点间距离(公里)
+     */
+    private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
+        final int R = 6371; // 地球半径(公里)
+        double dLat = Math.toRadians(lat2 - lat1);
+        double dLon = Math.toRadians(lon2 - lon1);
+        double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
+                   Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
+                   Math.sin(dLon/2) * Math.sin(dLon/2);
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+        return R * c;
+    }
 
     public Map<String, Object> createMatch(String u1, String u2, double score) {
         try {
@@ -109,6 +212,19 @@ public class MatchService {
             log.info("用户1信息: {}", user1Info);
             log.info("用户2信息: {}", user2Info);
             
+            // 确保用户信息不为空
+            if (user1Info.isEmpty() || user2Info.isEmpty()) {
+                log.warn("用户信息为空,使用默认值");
+                if (user1Info.isEmpty()) {
+                    user1Info.put("userId", u1);
+                    user1Info.put("nickname", "用户" + u1);
+                }
+                if (user2Info.isEmpty()) {
+                    user2Info.put("userId", u2);
+                    user2Info.put("nickname", "用户" + u2);
+                }
+            }
+            
             // 再移除用户
             redisMatchPool.removeFromPool(u1);
             redisMatchPool.removeFromPool(u2);
@@ -129,7 +245,16 @@ public class MatchService {
             return matchInfo;
         } catch (Exception e) {
             log.error("createMatch error", e);
-            return new HashMap<>();
+            // 返回一个包含基本信息的Map,而不是空Map
+            Map<String, Object> errorMatchInfo = new HashMap<>();
+            errorMatchInfo.put("roomId", UUID.randomUUID().toString());
+            errorMatchInfo.put("matchScore", 0.0);
+            errorMatchInfo.put("user1Id", u1);
+            errorMatchInfo.put("user2Id", u2);
+            errorMatchInfo.put("user1Info", buildUserInfo(u1));
+            errorMatchInfo.put("user2Info", buildUserInfo(u2));
+            errorMatchInfo.put("matchTime", System.currentTimeMillis());
+            return errorMatchInfo;
         }
     }
 

+ 16 - 7
service/randomMatch/src/main/java/com/zhentao/util/MatchingAlgorithmUtils.java

@@ -5,19 +5,28 @@ import com.zhentao.entity.MatchData;
 import java.util.*;
 
 public class MatchingAlgorithmUtils {
+    /**
+     * 计算匹配分数(取消性别权重,重新分配权重)
+     * 权重分配:兴趣爱好 50%,年龄 35%,地理位置 15%
+     */
     public static double calculateMatchScore(MatchData a, MatchData b) {
         if (a == null || b == null) return 0;
-        double genderBonus = genderBonus(a.getGender(), b.getGender());
         double interest = jaccard(a.getInterests(), b.getInterests());
         double age = ageScore(a.getAge(), b.getAge());
         double loc = locationScore(a.getLatitude(), a.getLongitude(), b.getLatitude(), b.getLongitude());
-        double base = Math.min(100, ((interest * 0.4 + age * 0.3 + loc * 0.2) / (0.4 + 0.3 + 0.2)) * 100);
-        return Math.min(100, base * (1 + genderBonus));
+        // 重新分配权重:兴趣爱好 50%,年龄 35%,地理位置 15%
+        double score = interest * 0.5 + age * 0.35 + loc * 0.15;
+        return Math.min(100, score * 100);
     }
-
-    private static double genderBonus(Integer g1, Integer g2) {
-        if (g1 != null && g2 != null && !g1.equals(g2) && g1 < 2 && g2 < 2) return 0.2;
-        return 0.0;
+    
+    /**
+     * 检查是否为异性匹配(1=男, 2=女)
+     * @return true 表示是异性,false 表示是同性或性别信息不完整
+     */
+    public static boolean isOppositeGender(Integer g1, Integer g2) {
+        if (g1 == null || g2 == null) return false;
+        // 1=男, 2=女,异性匹配:1和2,或2和1
+        return !g1.equals(g2) && g1 >= 1 && g1 <= 2 && g2 >= 1 && g2 <= 2;
     }
 
     private static double jaccard(List<String> a, List<String> b) {