| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732 |
- <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" @click="selectMode('smart')">
- <view class="mode-icon">🎯</view>
- <view class="mode-title">智能算法</view>
- <view class="mode-desc">基于兴趣爱好、性格特征智能匹配</view>
- </view>
- <view class="mode-item" @click="selectMode('online')">
- <view class="mode-icon">⚡</view>
- <view class="mode-title">实时在线</view>
- <view class="mode-desc">只匹配当前在线的优质用户</view>
- </view>
- <view class="mode-item" @click="selectMode('precise')">
- <view class="mode-icon">💎</view>
- <view class="mode-title">精准推荐</view>
- <view class="mode-desc">根据您的偏好推荐最合适的对象</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 || '/static/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>
- 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) {
- this.selectedMode = mode;
- uni.showToast({
- title: mode === 'smart' ? '智能算法' : mode === 'online' ? '实时在线' : '精准推荐',
- icon: 'none'
- });
- },
-
- // 开始匹配
- 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
- };
-
- console.log('发送匹配请求:', matchData);
-
- // 调用匹配接口
- // 开发环境使用本地地址,生产环境使用服务器地址
- const baseUrl = 'http://localhost:8083'; // 本地开发
- // const baseUrl = 'http://115.190.125.125:8083'; // 生产环境
-
- const [error, res] = await uni.request({
- url: `${baseUrl}/match/start`,
- method: 'POST',
- header: {
- 'Content-Type': 'application/json'
- },
- data: matchData
- });
-
- 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;
- }
-
- 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);
- } 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() {
- // 每2秒检查一次匹配状态(实际项目中可以用WebSocket)
- this.pollingTimer = setInterval(() => {
- // 这里可以调用一个查询匹配状态的接口
- // 暂时使用定时器模拟
- console.log('轮询匹配状态...');
- }, 2000);
-
- // 30秒后停止匹配
- setTimeout(() => {
- if (this.isMatching) {
- this.cancelMatch();
- uni.showToast({
- title: '暂无合适的匹配对象,请稍后再试',
- icon: 'none'
- });
- }
- }, 30000);
- },
-
- // 取消匹配
- async cancelMatch() {
- try {
- const baseUrl = 'http://localhost:8083'; // 本地开发
- // const baseUrl = 'http://115.190.125.125:8083'; // 生产环境
-
- const userId = String(this.userInfo.userId);
- const res = await uni.request({
- url: `${baseUrl}/match/cancel?userId=${userId}`,
- method: 'POST'
- });
-
- console.log('取消匹配响应:', res.data);
-
- uni.showToast({
- title: '已取消匹配',
- icon: 'none'
- });
- } catch (error) {
- console.error('取消匹配失败:', error);
- }
-
- this.isMatching = false;
- this.matchStatus = '点击下方按钮开始匹配';
-
- if (this.pollingTimer) {
- clearInterval(this.pollingTimer);
- this.pollingTimer = null;
- }
- },
-
- // 处理匹配成功
- handleMatchSuccess(data) {
- console.log('=== 处理匹配成功数据 ===');
- console.log('匹配数据:', data);
-
- 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 || '/static/default-avatar.svg',
- interests: matchedUserInfo.interests || []
- };
- } else {
- // 如果完全无法提取,使用默认值
- console.warn('无法提取匹配用户信息,使用默认值');
- this.matchedUser = {
- userId: '',
- nickname: '匹配用户',
- age: 0,
- city: '未知',
- avatarUrl: '/static/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 || '/static/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;
- }
- .mode-item:active {
- transform: scale(0.98);
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
- }
- .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>
|