index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. <template>
  2. <view class="match-container">
  3. <!-- 顶部导航栏 -->
  4. <view class="custom-navbar">
  5. <view class="navbar-back" @click="goBack">
  6. <text class="back-icon">←</text>
  7. <text class="back-text">返回</text>
  8. </view>
  9. <view class="navbar-title">智能匹配</view>
  10. <view class="navbar-placeholder"></view>
  11. </view>
  12. <!-- 顶部提示 -->
  13. <view class="tip-box">
  14. <text class="tip-icon">⚡</text>
  15. <text class="tip-text">智能匹配系统正在为您寻找最合适的TA</text>
  16. </view>
  17. <!-- 匹配动画区域 -->
  18. <view class="match-animation">
  19. <view class="heart-container" v-if="isMatching">
  20. <text class="heart">❤️</text>
  21. <text class="heart">❤️</text>
  22. </view>
  23. <text class="match-status">{{ matchStatus }}</text>
  24. </view>
  25. <!-- 匹配方式选择 -->
  26. <view class="match-modes">
  27. <view
  28. class="mode-item"
  29. :class="{ 'mode-item-active': selectedMode === 'smart' }"
  30. @tap="selectMode('smart')"
  31. >
  32. <view class="mode-icon">🎯</view>
  33. <view class="mode-title">智能算法</view>
  34. <view class="mode-desc">基于兴趣爱好、性格特征智能匹配</view>
  35. <view v-if="selectedMode === 'smart'" class="mode-selected">✓ 已选择</view>
  36. </view>
  37. <view
  38. class="mode-item"
  39. :class="{ 'mode-item-active': selectedMode === 'online' }"
  40. @tap="selectMode('online')"
  41. >
  42. <view class="mode-icon">⚡</view>
  43. <view class="mode-title">实时在线</view>
  44. <view class="mode-desc">只匹配当前在线的优质用户</view>
  45. <view v-if="selectedMode === 'online'" class="mode-selected">✓ 已选择</view>
  46. </view>
  47. <view
  48. class="mode-item"
  49. :class="{ 'mode-item-active': selectedMode === 'precise' }"
  50. @tap="selectMode('precise')"
  51. >
  52. <view class="mode-icon">💎</view>
  53. <view class="mode-title">精准推荐</view>
  54. <view class="mode-desc">根据您的偏好推荐最合适的对象</view>
  55. <view v-if="selectedMode === 'precise'" class="mode-selected">✓ 已选择</view>
  56. </view>
  57. </view>
  58. <!-- 开始匹配按钮 -->
  59. <view class="match-button-container" v-if="!isMatching">
  60. <button class="start-match-btn" @click="startMatch">
  61. <text class="btn-text">开始匹配</text>
  62. </button>
  63. </view>
  64. <!-- 取消匹配按钮 -->
  65. <view class="match-button-container" v-else>
  66. <button class="cancel-match-btn" @click="cancelMatch">
  67. <text class="btn-text">取消匹配</text>
  68. </button>
  69. </view>
  70. <!-- 匹配成功弹窗 -->
  71. <uni-popup ref="matchSuccessPopup" type="center">
  72. <view class="success-popup">
  73. <view class="success-icon">🎉</view>
  74. <view class="success-title">匹配成功!</view>
  75. <view class="success-score">匹配度:{{ matchScore }}%</view>
  76. <view class="matched-user-info">
  77. <image class="user-avatar" :src="matchedUser.avatarUrl || 'http://115.190.125.125:9001/static-images/default-avatar.svg'" mode="aspectFill"></image>
  78. <view class="user-name">{{ matchedUser.nickname }}</view>
  79. <view class="user-detail">{{ matchedUser.age }}岁 | {{ matchedUser.city }}</view>
  80. <view class="user-interests">
  81. <text class="interest-tag" v-for="(interest, index) in matchedUser.interests" :key="index">
  82. {{ interest }}
  83. </text>
  84. </view>
  85. </view>
  86. <view class="popup-buttons">
  87. <button class="btn-chat" @click="goToChat">开始聊天</button>
  88. <button class="btn-continue" @click="continueMatch">继续匹配</button>
  89. </view>
  90. </view>
  91. </uni-popup>
  92. </view>
  93. </template>
  94. <script>
  95. import MATCH_API_CONFIG from '@/config/match-config.js'
  96. export default {
  97. data() {
  98. return {
  99. isMatching: false,
  100. matchStatus: '点击下方按钮开始匹配',
  101. selectedMode: 'smart',
  102. matchScore: 0,
  103. matchedUser: {},
  104. userInfo: {},
  105. pollingTimer: null
  106. }
  107. },
  108. onLoad() {
  109. this.loadUserInfo();
  110. },
  111. onUnload() {
  112. // 页面卸载时停止匹配
  113. if (this.isMatching) {
  114. this.cancelMatch();
  115. }
  116. if (this.pollingTimer) {
  117. clearInterval(this.pollingTimer);
  118. }
  119. },
  120. methods: {
  121. // 加载用户信息
  122. loadUserInfo() {
  123. // 从本地存储获取用户信息
  124. const userInfo = uni.getStorageSync('userInfo');
  125. if (userInfo) {
  126. this.userInfo = userInfo;
  127. } else {
  128. uni.showToast({
  129. title: '请先登录',
  130. icon: 'none'
  131. });
  132. setTimeout(() => {
  133. uni.navigateBack();
  134. }, 1500);
  135. }
  136. },
  137. // 选择匹配模式
  138. selectMode(mode) {
  139. console.log('选择匹配模式:', mode);
  140. this.selectedMode = mode;
  141. const modeNames = {
  142. 'smart': '智能算法',
  143. 'online': '实时在线',
  144. 'precise': '精准推荐'
  145. };
  146. uni.showToast({
  147. title: `已选择:${modeNames[mode] || mode}`,
  148. icon: 'none',
  149. duration: 1500
  150. });
  151. },
  152. // 开始匹配
  153. async startMatch() {
  154. if (!this.userInfo.userId) {
  155. uni.showToast({
  156. title: '用户信息不完整',
  157. icon: 'none'
  158. });
  159. return;
  160. }
  161. this.isMatching = true;
  162. this.matchStatus = '正在匹配中...';
  163. try {
  164. // 构建匹配数据
  165. const matchData = {
  166. userId: String(this.userInfo.userId),
  167. nickname: this.userInfo.nickname || '用户',
  168. gender: this.userInfo.gender || 1,
  169. age: this.userInfo.age || 25,
  170. avatarUrl: this.userInfo.avatarUrl || '',
  171. interests: this.userInfo.interests || ['旅游', '美食'],
  172. city: this.userInfo.city || '未知',
  173. introduction: this.userInfo.introduction || '',
  174. latitude: this.userInfo.latitude || null,
  175. longitude: this.userInfo.longitude || null,
  176. matchMode: this.selectedMode || 'smart' // 添加匹配模式
  177. };
  178. console.log('发送匹配请求:', matchData);
  179. // 调用匹配接口 - 使用配置文件中的地址
  180. const baseUrl = MATCH_API_CONFIG.BASE_URL;
  181. const url = `${baseUrl}${MATCH_API_CONFIG.ENDPOINTS.START_MATCH}`;
  182. console.log('匹配API地址:', url);
  183. const [error, res] = await uni.request({
  184. url: url,
  185. method: 'POST',
  186. header: {
  187. 'Content-Type': 'application/json'
  188. },
  189. data: matchData,
  190. timeout: 10000
  191. });
  192. console.log('匹配响应:', res);
  193. console.log('匹配错误:', error);
  194. // 检查是否有错误或响应无效
  195. if (error || !res || !res.data) {
  196. this.isMatching = false;
  197. this.matchStatus = '点击下方按钮开始匹配';
  198. uni.showToast({
  199. title: '服务器连接失败,请确保后端服务已启动',
  200. icon: 'none',
  201. duration: 3000
  202. });
  203. return;
  204. }
  205. // 检查 HTTP 状态码
  206. if (res.statusCode !== 200) {
  207. this.isMatching = false;
  208. this.matchStatus = '点击下方按钮开始匹配';
  209. uni.showToast({
  210. title: `服务器错误 (${res.statusCode}),请联系管理员`,
  211. icon: 'none',
  212. duration: 3000
  213. });
  214. console.error('HTTP错误:', res.statusCode, res.data);
  215. return;
  216. }
  217. console.log('=== 匹配响应解析 ===');
  218. console.log('完整响应:', JSON.stringify(res.data, null, 2));
  219. console.log('code:', res.data.code);
  220. console.log('status:', res.data.status);
  221. console.log('msg:', res.data.msg);
  222. console.log('data字段:', res.data.data);
  223. if (res.data.code === 200) {
  224. if (res.data.status === 'success') {
  225. // 立即匹配成功
  226. const matchData = res.data.data;
  227. if (!matchData) {
  228. console.error('匹配成功但data为空');
  229. this.isMatching = false;
  230. this.matchStatus = '点击下方按钮开始匹配';
  231. uni.showToast({
  232. title: '匹配数据异常,请重试',
  233. icon: 'none'
  234. });
  235. return;
  236. }
  237. this.handleMatchSuccess(matchData);
  238. } else if (res.data.status === 'waiting') {
  239. // 等待匹配,开始轮询
  240. this.matchStatus = '正在寻找合适的对象...';
  241. this.startPolling();
  242. } else {
  243. this.isMatching = false;
  244. this.matchStatus = '点击下方按钮开始匹配';
  245. uni.showToast({
  246. title: res.data.msg || '匹配失败',
  247. icon: 'none'
  248. });
  249. }
  250. } else {
  251. this.isMatching = false;
  252. this.matchStatus = '点击下方按钮开始匹配';
  253. uni.showToast({
  254. title: res.data.msg || '匹配失败',
  255. icon: 'none'
  256. });
  257. }
  258. } catch (error) {
  259. console.error('匹配请求失败:', error);
  260. this.isMatching = false;
  261. this.matchStatus = '点击下方按钮开始匹配';
  262. uni.showToast({
  263. title: '网络错误,请重试',
  264. icon: 'none'
  265. });
  266. }
  267. },
  268. // 开始轮询匹配状态
  269. startPolling() {
  270. let pollCount = 0;
  271. const maxPolls = 15; // 最多轮询15次(30秒)
  272. // 每2秒检查一次匹配状态
  273. this.pollingTimer = setInterval(async () => {
  274. pollCount++;
  275. console.log(`轮询匹配状态 (${pollCount}/${maxPolls})...`);
  276. // 如果超过最大轮询次数,停止匹配
  277. if (pollCount >= maxPolls) {
  278. if (this.isMatching) {
  279. // 轮询超时,不显示取消匹配的提示,而是显示更合适的提示
  280. this.cancelMatch(false);
  281. uni.showToast({
  282. title: '暂无合适的匹配对象,请稍后再试',
  283. icon: 'none',
  284. duration: 2000
  285. });
  286. }
  287. return;
  288. }
  289. // 尝试查询匹配状态(如果后端有状态查询接口)
  290. try {
  291. const baseUrl = MATCH_API_CONFIG.BASE_URL;
  292. const userId = String(this.userInfo.userId);
  293. // 注意:这里假设后端有状态查询接口,如果没有可以注释掉
  294. // const res = await uni.request({
  295. // url: `${baseUrl}${MATCH_API_CONFIG.ENDPOINTS.MATCH_STATUS}?userId=${userId}`,
  296. // method: 'GET'
  297. // });
  298. //
  299. // if (res.data && res.data.code === 200 && res.data.status === 'success') {
  300. // this.handleMatchSuccess(res.data.data);
  301. // }
  302. } catch (error) {
  303. console.error('轮询查询失败:', error);
  304. }
  305. }, MATCH_API_CONFIG.CONFIG.POLLING_INTERVAL);
  306. },
  307. // 取消匹配
  308. // @param showToast 是否显示取消匹配的提示(默认true,轮询超时时传入false)
  309. async cancelMatch(showToast = true) {
  310. try {
  311. const baseUrl = MATCH_API_CONFIG.BASE_URL;
  312. const userId = String(this.userInfo.userId);
  313. const url = `${baseUrl}${MATCH_API_CONFIG.ENDPOINTS.CANCEL_MATCH}?userId=${userId}`;
  314. console.log('取消匹配请求:', url);
  315. const [error, res] = await uni.request({
  316. url: url,
  317. method: 'POST',
  318. timeout: 5000
  319. });
  320. if (error) {
  321. console.error('取消匹配请求失败:', error);
  322. if (showToast) {
  323. uni.showToast({
  324. title: '取消匹配失败',
  325. icon: 'none'
  326. });
  327. }
  328. } else {
  329. console.log('取消匹配响应:', res.data);
  330. // 只有在需要显示提示时才显示
  331. if (showToast && res.data && res.data.code === 200) {
  332. uni.showToast({
  333. title: res.data.msg || '已取消匹配',
  334. icon: 'none'
  335. });
  336. }
  337. }
  338. } catch (error) {
  339. console.error('取消匹配失败:', error);
  340. if (showToast) {
  341. uni.showToast({
  342. title: '取消匹配失败',
  343. icon: 'none'
  344. });
  345. }
  346. }
  347. this.isMatching = false;
  348. this.matchStatus = '点击下方按钮开始匹配';
  349. if (this.pollingTimer) {
  350. clearInterval(this.pollingTimer);
  351. this.pollingTimer = null;
  352. }
  353. },
  354. // 处理匹配成功
  355. handleMatchSuccess(data) {
  356. console.log('=== 处理匹配成功数据 ===');
  357. console.log('匹配数据:', data);
  358. console.log('数据类型:', typeof data);
  359. console.log('数据是否为null:', data === null);
  360. console.log('数据是否为undefined:', data === undefined);
  361. if (!data || typeof data !== 'object') {
  362. console.error('匹配数据无效:', data);
  363. this.isMatching = false;
  364. this.matchStatus = '点击下方按钮开始匹配';
  365. uni.showToast({
  366. title: '匹配数据异常',
  367. icon: 'none'
  368. });
  369. return;
  370. }
  371. this.isMatching = false;
  372. this.matchStatus = '匹配成功!';
  373. if (this.pollingTimer) {
  374. clearInterval(this.pollingTimer);
  375. this.pollingTimer = null;
  376. }
  377. // 解析匹配数据 - 兼容不同的数据结构
  378. this.matchScore = Math.round(data.matchScore || (data.roomId ? 50 : 0));
  379. // 尝试多种可能的数据结构
  380. let matchedUserInfo = null;
  381. // 方式1: data.user2Info
  382. if (data.user2Info && typeof data.user2Info === 'object') {
  383. matchedUserInfo = data.user2Info;
  384. }
  385. // 方式2: data.matchedUser
  386. else if (data.matchedUser && typeof data.matchedUser === 'object') {
  387. matchedUserInfo = data.matchedUser;
  388. }
  389. // 方式3: data直接包含用户信息
  390. else if (data.userId || data.nickname) {
  391. matchedUserInfo = data;
  392. }
  393. // 方式4: 嵌套在matchPair中
  394. else if (data.matchPair) {
  395. matchedUserInfo = data.matchPair.matchedUser || data.matchPair.user2Info || data.matchPair;
  396. }
  397. console.log('提取的匹配对象信息:', matchedUserInfo);
  398. // 设置匹配用户数据
  399. if (matchedUserInfo) {
  400. this.matchedUser = {
  401. userId: matchedUserInfo.userId || matchedUserInfo.user_id || '',
  402. nickname: matchedUserInfo.nickname || matchedUserInfo.name || '匹配用户',
  403. age: matchedUserInfo.age || 0,
  404. city: matchedUserInfo.city || '未知',
  405. avatarUrl: matchedUserInfo.avatarUrl || matchedUserInfo.avatar_url || 'http://115.190.125.125:9001/static-images/default-avatar.svg',
  406. interests: matchedUserInfo.interests || []
  407. };
  408. } else {
  409. // 如果完全无法提取,使用默认值
  410. console.warn('无法提取匹配用户信息,使用默认值');
  411. this.matchedUser = {
  412. userId: '',
  413. nickname: '匹配用户',
  414. age: 0,
  415. city: '未知',
  416. avatarUrl: 'http://115.190.125.125:9001/static-images/default-avatar.svg',
  417. interests: []
  418. };
  419. }
  420. console.log('最终匹配用户数据:', this.matchedUser);
  421. // 显示匹配成功弹窗
  422. this.$refs.matchSuccessPopup.open();
  423. },
  424. // 前往聊天
  425. goToChat() {
  426. this.$refs.matchSuccessPopup.close();
  427. // 跳转到聊天页面(修正参数名称)
  428. const targetUserId = this.matchedUser.userId;
  429. const targetUserName = encodeURIComponent(this.matchedUser.nickname || '用户');
  430. const targetUserAvatar = encodeURIComponent(this.matchedUser.avatarUrl || 'http://115.190.125.125:9001/static-images/default-avatar.svg');
  431. uni.navigateTo({
  432. url: `/pages/message/chat?targetUserId=${targetUserId}&targetUserName=${targetUserName}&targetUserAvatar=${targetUserAvatar}`
  433. });
  434. },
  435. // 继续匹配
  436. continueMatch() {
  437. this.$refs.matchSuccessPopup.close();
  438. this.matchStatus = '点击下方按钮开始匹配';
  439. this.matchedUser = {};
  440. },
  441. // 返回上一页
  442. goBack() {
  443. // 如果正在匹配中,先取消匹配
  444. if (this.isMatching) {
  445. uni.showModal({
  446. title: '提示',
  447. content: '正在匹配中,确定要退出吗?',
  448. success: (res) => {
  449. if (res.confirm) {
  450. this.cancelMatch();
  451. setTimeout(() => {
  452. uni.navigateBack();
  453. }, 500);
  454. }
  455. }
  456. });
  457. } else {
  458. uni.navigateBack();
  459. }
  460. }
  461. }
  462. }
  463. </script>
  464. <style scoped>
  465. .match-container {
  466. min-height: 100vh;
  467. background: linear-gradient(180deg, #FFF5F7 0%, #FFFFFF 100%);
  468. padding: 0;
  469. }
  470. /* 自定义导航栏 */
  471. .custom-navbar {
  472. height: 88rpx;
  473. background: white;
  474. display: flex;
  475. align-items: center;
  476. justify-content: space-between;
  477. padding: 0 20rpx;
  478. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  479. position: sticky;
  480. top: 0;
  481. z-index: 999;
  482. }
  483. .navbar-back {
  484. display: flex;
  485. align-items: center;
  486. padding: 10rpx 20rpx;
  487. cursor: pointer;
  488. }
  489. .navbar-back:active {
  490. opacity: 0.7;
  491. }
  492. .back-icon {
  493. font-size: 40rpx;
  494. color: #E91E63;
  495. font-weight: bold;
  496. margin-right: 8rpx;
  497. }
  498. .back-text {
  499. font-size: 28rpx;
  500. color: #333;
  501. }
  502. .navbar-title {
  503. font-size: 32rpx;
  504. font-weight: bold;
  505. color: #333;
  506. position: absolute;
  507. left: 50%;
  508. transform: translateX(-50%);
  509. }
  510. .navbar-placeholder {
  511. width: 120rpx;
  512. }
  513. .tip-box {
  514. background: #E3F2FD;
  515. border-left: 6rpx solid #2196F3;
  516. padding: 24rpx;
  517. margin: 20rpx;
  518. border-radius: 12rpx;
  519. display: flex;
  520. align-items: center;
  521. }
  522. .tip-icon {
  523. font-size: 36rpx;
  524. margin-right: 16rpx;
  525. }
  526. .tip-text {
  527. font-size: 28rpx;
  528. color: #1976D2;
  529. flex: 1;
  530. }
  531. .match-animation {
  532. height: 500rpx;
  533. display: flex;
  534. flex-direction: column;
  535. justify-content: center;
  536. align-items: center;
  537. margin: 40rpx 0;
  538. }
  539. .heart-container {
  540. position: relative;
  541. width: 200rpx;
  542. height: 200rpx;
  543. animation: pulse 1.5s ease-in-out infinite;
  544. }
  545. .heart {
  546. font-size: 100rpx;
  547. position: absolute;
  548. top: 50%;
  549. left: 50%;
  550. transform: translate(-50%, -50%);
  551. }
  552. .heart:nth-child(1) {
  553. animation: heart-beat 1.5s ease-in-out infinite;
  554. }
  555. .heart:nth-child(2) {
  556. animation: heart-beat 1.5s ease-in-out 0.75s infinite;
  557. opacity: 0.6;
  558. }
  559. .match-status {
  560. margin-top: 60rpx;
  561. font-size: 32rpx;
  562. color: #E91E63;
  563. font-weight: bold;
  564. }
  565. .match-modes {
  566. margin: 40rpx 20rpx;
  567. }
  568. .mode-item {
  569. background: white;
  570. border-radius: 16rpx;
  571. padding: 32rpx;
  572. margin-bottom: 24rpx;
  573. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
  574. transition: all 0.3s;
  575. position: relative;
  576. cursor: pointer;
  577. -webkit-tap-highlight-color: transparent;
  578. }
  579. .mode-item:active {
  580. transform: scale(0.98);
  581. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  582. }
  583. .mode-item-active {
  584. background: linear-gradient(135deg, #FFF5F7 0%, #FFFFFF 100%);
  585. border: 2rpx solid #E91E63;
  586. box-shadow: 0 6rpx 20rpx rgba(233, 30, 99, 0.2);
  587. }
  588. .mode-selected {
  589. position: absolute;
  590. top: 16rpx;
  591. right: 16rpx;
  592. background: #E91E63;
  593. color: white;
  594. padding: 8rpx 16rpx;
  595. border-radius: 20rpx;
  596. font-size: 24rpx;
  597. font-weight: bold;
  598. }
  599. .mode-icon {
  600. font-size: 60rpx;
  601. text-align: center;
  602. margin-bottom: 16rpx;
  603. }
  604. .mode-title {
  605. font-size: 32rpx;
  606. font-weight: bold;
  607. color: #333;
  608. text-align: center;
  609. margin-bottom: 12rpx;
  610. }
  611. .mode-desc {
  612. font-size: 24rpx;
  613. color: #999;
  614. text-align: center;
  615. line-height: 36rpx;
  616. }
  617. .match-button-container {
  618. position: fixed;
  619. bottom: 60rpx;
  620. left: 40rpx;
  621. right: 40rpx;
  622. }
  623. .start-match-btn {
  624. background: linear-gradient(135deg, #E91E63 0%, #C2185B 100%);
  625. color: white;
  626. border: none;
  627. border-radius: 50rpx;
  628. height: 100rpx;
  629. line-height: 100rpx;
  630. font-size: 36rpx;
  631. font-weight: bold;
  632. box-shadow: 0 8rpx 24rpx rgba(233, 30, 99, 0.4);
  633. }
  634. .start-match-btn:active {
  635. background: linear-gradient(135deg, #C2185B 0%, #AD1457 100%);
  636. }
  637. .cancel-match-btn {
  638. background: #FF9800;
  639. color: white;
  640. border: none;
  641. border-radius: 50rpx;
  642. height: 100rpx;
  643. line-height: 100rpx;
  644. font-size: 36rpx;
  645. font-weight: bold;
  646. box-shadow: 0 8rpx 24rpx rgba(255, 152, 0, 0.4);
  647. }
  648. .btn-text {
  649. color: white;
  650. }
  651. /* 匹配成功弹窗 */
  652. .success-popup {
  653. width: 600rpx;
  654. background: white;
  655. border-radius: 24rpx;
  656. padding: 60rpx 40rpx;
  657. text-align: center;
  658. }
  659. .success-icon {
  660. font-size: 120rpx;
  661. margin-bottom: 20rpx;
  662. }
  663. .success-title {
  664. font-size: 40rpx;
  665. font-weight: bold;
  666. color: #E91E63;
  667. margin-bottom: 16rpx;
  668. }
  669. .success-score {
  670. font-size: 28rpx;
  671. color: #666;
  672. margin-bottom: 40rpx;
  673. }
  674. .matched-user-info {
  675. background: #F5F5F5;
  676. border-radius: 16rpx;
  677. padding: 32rpx;
  678. margin-bottom: 40rpx;
  679. }
  680. .user-avatar {
  681. width: 120rpx;
  682. height: 120rpx;
  683. border-radius: 60rpx;
  684. margin: 0 auto 20rpx;
  685. display: block;
  686. }
  687. .user-name {
  688. font-size: 36rpx;
  689. font-weight: bold;
  690. color: #333;
  691. margin-bottom: 12rpx;
  692. }
  693. .user-detail {
  694. font-size: 28rpx;
  695. color: #999;
  696. margin-bottom: 20rpx;
  697. }
  698. .user-interests {
  699. display: flex;
  700. flex-wrap: wrap;
  701. justify-content: center;
  702. gap: 12rpx;
  703. }
  704. .interest-tag {
  705. background: #E91E63;
  706. color: white;
  707. padding: 8rpx 20rpx;
  708. border-radius: 20rpx;
  709. font-size: 24rpx;
  710. }
  711. .popup-buttons {
  712. display: flex;
  713. gap: 20rpx;
  714. }
  715. .btn-chat,
  716. .btn-continue {
  717. flex: 1;
  718. height: 80rpx;
  719. line-height: 80rpx;
  720. border-radius: 40rpx;
  721. font-size: 28rpx;
  722. border: none;
  723. }
  724. .btn-chat {
  725. background: #E91E63;
  726. color: white;
  727. }
  728. .btn-continue {
  729. background: white;
  730. color: #E91E63;
  731. border: 2rpx solid #E91E63 !important;
  732. }
  733. /* 动画 */
  734. @keyframes pulse {
  735. 0%, 100% {
  736. transform: scale(1);
  737. }
  738. 50% {
  739. transform: scale(1.1);
  740. }
  741. }
  742. @keyframes heart-beat {
  743. 0%, 100% {
  744. transform: translate(-50%, -50%) scale(1);
  745. opacity: 1;
  746. }
  747. 50% {
  748. transform: translate(-50%, -50%) scale(1.2);
  749. opacity: 0.8;
  750. }
  751. }
  752. </style>