| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- <template>
- <view class="match-container">
- <!-- 顶部导航栏 -->
- <view class="custom-navbar">
- <view class="navbar-back" @click="goBack">
- <text class="back-icon">←</text>
- <text class="back-text">返回</text>
- </view>
- <view class="navbar-title">智能匹配</view>
- <view class="navbar-placeholder"></view>
- </view>
-
- <!-- 顶部提示 -->
- <view class="tip-box">
- <text class="tip-icon">⚡</text>
- <text class="tip-text">智能匹配系统正在为您寻找最合适的TA</text>
- </view>
- <!-- 匹配动画区域 -->
- <view class="match-animation">
- <view class="heart-container" v-if="isMatching">
- <text class="heart">❤️</text>
- <text class="heart">❤️</text>
- </view>
- <text class="match-status">{{ matchStatus }}</text>
- </view>
- <!-- 匹配方式选择 -->
- <view class="match-modes">
- <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"
- :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"
- :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>
- <!-- 开始匹配按钮 -->
- <view class="match-button-container" v-if="!isMatching">
- <button class="start-match-btn" @click="startMatch">
- <text class="btn-text">开始匹配</text>
- </button>
- </view>
- <!-- 取消匹配按钮 -->
- <view class="match-button-container" v-else>
- <button class="cancel-match-btn" @click="cancelMatch">
- <text class="btn-text">取消匹配</text>
- </button>
- </view>
- <!-- 匹配成功弹窗 -->
- <uni-popup ref="matchSuccessPopup" type="center">
- <view class="success-popup">
- <view class="success-icon">🎉</view>
- <view class="success-title">匹配成功!</view>
- <view class="success-score">匹配度:{{ matchScore }}%</view>
-
- <view class="matched-user-info">
- <image class="user-avatar" :src="matchedUser.avatarUrl || 'http://115.190.125.125:9001/static-images/default-avatar.svg'" mode="aspectFill"></image>
- <view class="user-name">{{ matchedUser.nickname }}</view>
- <view class="user-detail">{{ matchedUser.age }}岁 | {{ matchedUser.city }}</view>
- <view class="user-interests">
- <text class="interest-tag" v-for="(interest, index) in matchedUser.interests" :key="index">
- {{ interest }}
- </text>
- </view>
- </view>
- <view class="popup-buttons">
- <button class="btn-chat" @click="goToChat">开始聊天</button>
- <button class="btn-continue" @click="continueMatch">继续匹配</button>
- </view>
- </view>
- </uni-popup>
- </view>
- </template>
- <script>
- import MATCH_API_CONFIG from '@/config/match-config.js'
- export default {
- data() {
- return {
- isMatching: false,
- matchStatus: '点击下方按钮开始匹配',
- selectedMode: 'smart',
- matchScore: 0,
- matchedUser: {},
- userInfo: {},
- pollingTimer: null
- }
- },
-
- onLoad() {
- this.loadUserInfo();
- },
-
- onUnload() {
- // 页面卸载时停止匹配
- if (this.isMatching) {
- this.cancelMatch();
- }
- if (this.pollingTimer) {
- clearInterval(this.pollingTimer);
- }
- },
-
- methods: {
- // 加载用户信息
- loadUserInfo() {
- // 从本地存储获取用户信息
- const userInfo = uni.getStorageSync('userInfo');
- if (userInfo) {
- this.userInfo = userInfo;
- } else {
- uni.showToast({
- title: '请先登录',
- icon: 'none'
- });
- setTimeout(() => {
- uni.navigateBack();
- }, 1500);
- }
- },
-
- // 选择匹配模式
- selectMode(mode) {
- console.log('选择匹配模式:', mode);
- this.selectedMode = mode;
- const modeNames = {
- 'smart': '智能算法',
- 'online': '实时在线',
- 'precise': '精准推荐'
- };
- uni.showToast({
- title: `已选择:${modeNames[mode] || mode}`,
- icon: 'none',
- duration: 1500
- });
- },
-
- // 开始匹配
- async startMatch() {
- if (!this.userInfo.userId) {
- uni.showToast({
- title: '用户信息不完整',
- icon: 'none'
- });
- return;
- }
-
- this.isMatching = true;
- this.matchStatus = '正在匹配中...';
-
- try {
- // 构建匹配数据
- const matchData = {
- userId: String(this.userInfo.userId),
- nickname: this.userInfo.nickname || '用户',
- gender: this.userInfo.gender || 1,
- age: this.userInfo.age || 25,
- avatarUrl: this.userInfo.avatarUrl || '',
- interests: this.userInfo.interests || ['旅游', '美食'],
- city: this.userInfo.city || '未知',
- introduction: this.userInfo.introduction || '',
- latitude: this.userInfo.latitude || null,
- longitude: this.userInfo.longitude || null,
- matchMode: this.selectedMode || 'smart' // 添加匹配模式
- };
-
- console.log('发送匹配请求:', matchData);
-
- // 调用匹配接口 - 使用配置文件中的地址
- 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: url,
- method: 'POST',
- header: {
- 'Content-Type': 'application/json'
- },
- data: matchData,
- timeout: 10000
- });
-
- console.log('匹配响应:', res);
- console.log('匹配错误:', error);
-
- // 检查是否有错误或响应无效
- if (error || !res || !res.data) {
- this.isMatching = false;
- this.matchStatus = '点击下方按钮开始匹配';
- uni.showToast({
- title: '服务器连接失败,请确保后端服务已启动',
- icon: 'none',
- duration: 3000
- });
- return;
- }
-
- // 检查 HTTP 状态码
- if (res.statusCode !== 200) {
- this.isMatching = false;
- this.matchStatus = '点击下方按钮开始匹配';
- uni.showToast({
- title: `服务器错误 (${res.statusCode}),请联系管理员`,
- icon: 'none',
- duration: 3000
- });
- console.error('HTTP错误:', res.statusCode, res.data);
- 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) {
- if (res.data.status === 'success') {
- // 立即匹配成功
- 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 = '正在寻找合适的对象...';
- this.startPolling();
- } else {
- this.isMatching = false;
- this.matchStatus = '点击下方按钮开始匹配';
- uni.showToast({
- title: res.data.msg || '匹配失败',
- icon: 'none'
- });
- }
- } else {
- this.isMatching = false;
- this.matchStatus = '点击下方按钮开始匹配';
- uni.showToast({
- title: res.data.msg || '匹配失败',
- icon: 'none'
- });
- }
- } catch (error) {
- console.error('匹配请求失败:', error);
- this.isMatching = false;
- this.matchStatus = '点击下方按钮开始匹配';
- uni.showToast({
- title: '网络错误,请重试',
- icon: 'none'
- });
- }
- },
-
- // 开始轮询匹配状态
- startPolling() {
- let pollCount = 0;
- const maxPolls = 15; // 最多轮询15次(30秒)
-
- // 每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;
- }
-
- // 尝试查询匹配状态(如果后端有状态查询接口)
- 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);
- },
-
- // 取消匹配
- // @param showToast 是否显示取消匹配的提示(默认true,轮询超时时传入false)
- async cancelMatch(showToast = true) {
- try {
- const baseUrl = MATCH_API_CONFIG.BASE_URL;
- const userId = String(this.userInfo.userId);
- const url = `${baseUrl}${MATCH_API_CONFIG.ENDPOINTS.CANCEL_MATCH}?userId=${userId}`;
-
- console.log('取消匹配请求:', url);
-
- 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;
- this.matchStatus = '点击下方按钮开始匹配';
-
- if (this.pollingTimer) {
- clearInterval(this.pollingTimer);
- this.pollingTimer = null;
- }
- },
-
- // 处理匹配成功
- 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 = '匹配成功!';
-
- if (this.pollingTimer) {
- clearInterval(this.pollingTimer);
- this.pollingTimer = null;
- }
-
- // 解析匹配数据 - 兼容不同的数据结构
- this.matchScore = Math.round(data.matchScore || (data.roomId ? 50 : 0));
-
- // 尝试多种可能的数据结构
- let matchedUserInfo = null;
-
- // 方式1: data.user2Info
- if (data.user2Info && typeof data.user2Info === 'object') {
- matchedUserInfo = data.user2Info;
- }
- // 方式2: data.matchedUser
- else if (data.matchedUser && typeof data.matchedUser === 'object') {
- matchedUserInfo = data.matchedUser;
- }
- // 方式3: data直接包含用户信息
- else if (data.userId || data.nickname) {
- matchedUserInfo = data;
- }
- // 方式4: 嵌套在matchPair中
- else if (data.matchPair) {
- matchedUserInfo = data.matchPair.matchedUser || data.matchPair.user2Info || data.matchPair;
- }
-
- console.log('提取的匹配对象信息:', matchedUserInfo);
-
- // 设置匹配用户数据
- if (matchedUserInfo) {
- this.matchedUser = {
- userId: matchedUserInfo.userId || matchedUserInfo.user_id || '',
- nickname: matchedUserInfo.nickname || matchedUserInfo.name || '匹配用户',
- age: matchedUserInfo.age || 0,
- city: matchedUserInfo.city || '未知',
- avatarUrl: matchedUserInfo.avatarUrl || matchedUserInfo.avatar_url || 'http://115.190.125.125:9001/static-images/default-avatar.svg',
- interests: matchedUserInfo.interests || []
- };
- } else {
- // 如果完全无法提取,使用默认值
- console.warn('无法提取匹配用户信息,使用默认值');
- this.matchedUser = {
- userId: '',
- nickname: '匹配用户',
- age: 0,
- city: '未知',
- avatarUrl: 'http://115.190.125.125:9001/static-images/default-avatar.svg',
- interests: []
- };
- }
-
- console.log('最终匹配用户数据:', this.matchedUser);
-
- // 显示匹配成功弹窗
- this.$refs.matchSuccessPopup.open();
- },
-
- // 前往聊天
- goToChat() {
- this.$refs.matchSuccessPopup.close();
-
- // 跳转到聊天页面(修正参数名称)
- const targetUserId = this.matchedUser.userId;
- const targetUserName = encodeURIComponent(this.matchedUser.nickname || '用户');
- const targetUserAvatar = encodeURIComponent(this.matchedUser.avatarUrl || 'http://115.190.125.125:9001/static-images/default-avatar.svg');
-
- uni.navigateTo({
- url: `/pages/message/chat?targetUserId=${targetUserId}&targetUserName=${targetUserName}&targetUserAvatar=${targetUserAvatar}`
- });
- },
-
- // 继续匹配
- continueMatch() {
- this.$refs.matchSuccessPopup.close();
- this.matchStatus = '点击下方按钮开始匹配';
- this.matchedUser = {};
- },
-
- // 返回上一页
- goBack() {
- // 如果正在匹配中,先取消匹配
- if (this.isMatching) {
- uni.showModal({
- title: '提示',
- content: '正在匹配中,确定要退出吗?',
- success: (res) => {
- if (res.confirm) {
- this.cancelMatch();
- setTimeout(() => {
- uni.navigateBack();
- }, 500);
- }
- }
- });
- } else {
- uni.navigateBack();
- }
- }
- }
- }
- </script>
- <style scoped>
- .match-container {
- min-height: 100vh;
- background: linear-gradient(180deg, #FFF5F7 0%, #FFFFFF 100%);
- padding: 0;
- }
- /* 自定义导航栏 */
- .custom-navbar {
- height: 88rpx;
- background: white;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
- position: sticky;
- top: 0;
- z-index: 999;
- }
- .navbar-back {
- display: flex;
- align-items: center;
- padding: 10rpx 20rpx;
- cursor: pointer;
- }
- .navbar-back:active {
- opacity: 0.7;
- }
- .back-icon {
- font-size: 40rpx;
- color: #E91E63;
- font-weight: bold;
- margin-right: 8rpx;
- }
- .back-text {
- font-size: 28rpx;
- color: #333;
- }
- .navbar-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- }
- .navbar-placeholder {
- width: 120rpx;
- }
- .tip-box {
- background: #E3F2FD;
- border-left: 6rpx solid #2196F3;
- padding: 24rpx;
- margin: 20rpx;
- border-radius: 12rpx;
- display: flex;
- align-items: center;
- }
- .tip-icon {
- font-size: 36rpx;
- margin-right: 16rpx;
- }
- .tip-text {
- font-size: 28rpx;
- color: #1976D2;
- flex: 1;
- }
- .match-animation {
- height: 500rpx;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- margin: 40rpx 0;
- }
- .heart-container {
- position: relative;
- width: 200rpx;
- height: 200rpx;
- animation: pulse 1.5s ease-in-out infinite;
- }
- .heart {
- font-size: 100rpx;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- }
- .heart:nth-child(1) {
- animation: heart-beat 1.5s ease-in-out infinite;
- }
- .heart:nth-child(2) {
- animation: heart-beat 1.5s ease-in-out 0.75s infinite;
- opacity: 0.6;
- }
- .match-status {
- margin-top: 60rpx;
- font-size: 32rpx;
- color: #E91E63;
- font-weight: bold;
- }
- .match-modes {
- margin: 40rpx 20rpx;
- }
- .mode-item {
- background: white;
- border-radius: 16rpx;
- padding: 32rpx;
- 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 {
- transform: scale(0.98);
- 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;
- margin-bottom: 16rpx;
- }
- .mode-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- text-align: center;
- margin-bottom: 12rpx;
- }
- .mode-desc {
- font-size: 24rpx;
- color: #999;
- text-align: center;
- line-height: 36rpx;
- }
- .match-button-container {
- position: fixed;
- bottom: 60rpx;
- left: 40rpx;
- right: 40rpx;
- }
- .start-match-btn {
- background: linear-gradient(135deg, #E91E63 0%, #C2185B 100%);
- color: white;
- border: none;
- border-radius: 50rpx;
- height: 100rpx;
- line-height: 100rpx;
- font-size: 36rpx;
- font-weight: bold;
- box-shadow: 0 8rpx 24rpx rgba(233, 30, 99, 0.4);
- }
- .start-match-btn:active {
- background: linear-gradient(135deg, #C2185B 0%, #AD1457 100%);
- }
- .cancel-match-btn {
- background: #FF9800;
- color: white;
- border: none;
- border-radius: 50rpx;
- height: 100rpx;
- line-height: 100rpx;
- font-size: 36rpx;
- font-weight: bold;
- box-shadow: 0 8rpx 24rpx rgba(255, 152, 0, 0.4);
- }
- .btn-text {
- color: white;
- }
- /* 匹配成功弹窗 */
- .success-popup {
- width: 600rpx;
- background: white;
- border-radius: 24rpx;
- padding: 60rpx 40rpx;
- text-align: center;
- }
- .success-icon {
- font-size: 120rpx;
- margin-bottom: 20rpx;
- }
- .success-title {
- font-size: 40rpx;
- font-weight: bold;
- color: #E91E63;
- margin-bottom: 16rpx;
- }
- .success-score {
- font-size: 28rpx;
- color: #666;
- margin-bottom: 40rpx;
- }
- .matched-user-info {
- background: #F5F5F5;
- border-radius: 16rpx;
- padding: 32rpx;
- margin-bottom: 40rpx;
- }
- .user-avatar {
- width: 120rpx;
- height: 120rpx;
- border-radius: 60rpx;
- margin: 0 auto 20rpx;
- display: block;
- }
- .user-name {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 12rpx;
- }
- .user-detail {
- font-size: 28rpx;
- color: #999;
- margin-bottom: 20rpx;
- }
- .user-interests {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- gap: 12rpx;
- }
- .interest-tag {
- background: #E91E63;
- color: white;
- padding: 8rpx 20rpx;
- border-radius: 20rpx;
- font-size: 24rpx;
- }
- .popup-buttons {
- display: flex;
- gap: 20rpx;
- }
- .btn-chat,
- .btn-continue {
- flex: 1;
- height: 80rpx;
- line-height: 80rpx;
- border-radius: 40rpx;
- font-size: 28rpx;
- border: none;
- }
- .btn-chat {
- background: #E91E63;
- color: white;
- }
- .btn-continue {
- background: white;
- color: #E91E63;
- border: 2rpx solid #E91E63 !important;
- }
- /* 动画 */
- @keyframes pulse {
- 0%, 100% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.1);
- }
- }
- @keyframes heart-beat {
- 0%, 100% {
- transform: translate(-50%, -50%) scale(1);
- opacity: 1;
- }
- 50% {
- transform: translate(-50%, -50%) scale(1.2);
- opacity: 0.8;
- }
- }
- </style>
|