precise-match.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. <template>
  2. <view class="precise-match">
  3. <!-- 顶部导航栏 -->
  4. <view class="header">
  5. <view class="back-icon" @click="handleBack"></view>
  6. <text class="header-title">精准匹配</text>
  7. <view class="header-right"></view>
  8. </view>
  9. <!-- 加载中 -->
  10. <view class="loading-container" v-if="loading">
  11. <text class="loading-text">正在匹配中...</text>
  12. </view>
  13. <!-- 资源跟进状态 -->
  14. <view class="follow-status-section" v-if="!loading && resourceFollowInfo">
  15. <!-- 匹配状态 -->
  16. <view class="follow-status-group">
  17. <text class="status-label">匹配状态</text>
  18. <view class="status-tags">
  19. <text class="status-tag" :class="{ 'active': resourceFollowInfo.isMatch === 1 }">
  20. {{ resourceFollowInfo.isMatch === 1 ? '已匹配' : '未匹配' }}
  21. </text>
  22. </view>
  23. </view>
  24. <!-- 跟进进度 -->
  25. <view class="follow-status-group">
  26. <text class="status-label">跟进进度</text>
  27. <view class="status-tags">
  28. <text class="status-tag" :class="{ 'active': resourceFollowInfo.isInvitation === 1 }">
  29. {{ resourceFollowInfo.isInvitation === 1 ? '已邀约' : '未邀约' }}
  30. </text>
  31. </view>
  32. </view>
  33. </view>
  34. <!-- 匹配客户列表 -->
  35. <scroll-view scroll-y class="match-list" v-else>
  36. <view class="match-client" v-for="(client, index) in matchClients" :key="client.id || index">
  37. <!-- 匹配度标签 -->
  38. <view class="match-score-badge" v-if="client.matchScore">
  39. <text class="score-text">匹配度: {{ Math.round(client.matchScore) }}%</text>
  40. </view>
  41. <view class="client-info">
  42. <image :src="client.avatar" class="client-avatar" mode="aspectFill"></image>
  43. <view class="info-content">
  44. <view class="client-name">
  45. <text class="name-text">{{ client.name }}</text>
  46. <text class="client-gender">{{ client.gender }}</text>
  47. <text class="client-status">{{ client.status }}</text>
  48. </view>
  49. <view class="client-tags">
  50. <text class="tag" v-for="(tag, tagIndex) in client.tags" :key="tagIndex">{{ tag }}</text>
  51. </view>
  52. <view class="user-details" v-if="client.age || client.height || client.diploma">
  53. <text class="detail-item" v-if="client.age">{{ client.age }}岁</text>
  54. <text class="detail-item" v-if="client.height">{{ client.height }}cm</text>
  55. <text class="detail-item" v-if="client.diploma">{{ client.diploma }}</text>
  56. <text class="detail-item" v-if="client.income">{{ client.income }}</text>
  57. <text class="detail-item" v-if="client.occupation">{{ client.occupation }}</text>
  58. </view>
  59. <view class="requirement">
  60. <text class="requirement-label">择偶要求:</text>
  61. <text class="requirement-content">{{ client.requirement }}</text>
  62. </view>
  63. <view class="contact-info">
  64. <text class="contact-label">联系方式:</text>
  65. <text class="contact-number">{{ client.contact }}</text>
  66. <text class="copy-btn" @click="handleCopy(client.contact)">复制</text>
  67. </view>
  68. </view>
  69. </view>
  70. <view class="action-btn" @click="handleChat(client)">牵线聊聊</view>
  71. </view>
  72. </scroll-view>
  73. <!-- 底部导航 -->
  74. <view class="tabbar">
  75. <view class="tabbar-item" @click="handleHome">
  76. <view class="tabbar-icon home"></view>
  77. <text class="tabbar-text">工作台</text>
  78. </view>
  79. <view class="tabbar-item active" @click="handleMyResources">
  80. <view class="tabbar-icon resources"></view>
  81. <text class="tabbar-text">我的资源</text>
  82. </view>
  83. <view class="tabbar-item" @click="handleRanking">
  84. <view class="tabbar-icon trophy"></view>
  85. <text class="tabbar-text">排行榜</text>
  86. </view>
  87. <view class="tabbar-item" @click="handleMessage">
  88. <view class="tabbar-icon message">
  89. <text class="badge">3</text>
  90. </view>
  91. <text class="tabbar-text">消息</text>
  92. </view>
  93. <view class="tabbar-item" @click="handleMine">
  94. <view class="tabbar-icon mine"></view>
  95. <text class="tabbar-text">我的</text>
  96. </view>
  97. </view>
  98. </view>
  99. </template>
  100. <script>
  101. export default {
  102. name: 'precise-match',
  103. data() {
  104. return {
  105. resourceId: null,
  106. matchClients: [],
  107. loading: false,
  108. resourceFollowInfo: null // 资源跟进信息
  109. }
  110. },
  111. onLoad(options) {
  112. // 从URL参数获取资源ID
  113. if (options.resourceId) {
  114. console.log('当前资源ID:', options.resourceId)
  115. this.loadMatchClients(options.resourceId)
  116. } else if (options.id) {
  117. // 兼容旧的参数名
  118. console.log('当前资源ID (兼容):', options.id)
  119. this.loadMatchClients(options.id)
  120. } else {
  121. uni.showToast({
  122. title: '缺少资源ID参数',
  123. icon: 'none'
  124. })
  125. setTimeout(() => {
  126. uni.navigateBack()
  127. }, 1500)
  128. }
  129. },
  130. methods: {
  131. // 返回上一页
  132. handleBack() {
  133. uni.navigateBack()
  134. },
  135. // 复制联系方式
  136. handleCopy(contact) {
  137. uni.setClipboardData({
  138. data: contact.replace(/\*+/g, ''),
  139. success: () => {
  140. uni.showToast({
  141. title: '复制成功',
  142. icon: 'success'
  143. })
  144. }
  145. })
  146. },
  147. // 牵线聊聊
  148. handleChat(client) {
  149. // 跳转到聊天页面或打开聊天弹窗
  150. uni.showToast({
  151. title: '牵线成功',
  152. icon: 'success'
  153. })
  154. },
  155. // 从API加载匹配客户信息
  156. async loadMatchClients(resourceId) {
  157. if (!resourceId) {
  158. console.error('资源ID为空')
  159. return
  160. }
  161. this.resourceId = resourceId
  162. this.loading = true
  163. try {
  164. const baseUrl = process.env.NODE_ENV === 'development'
  165. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  166. : 'https://your-domain.com/api' // 生产环境
  167. // 同时加载匹配结果和资源跟进信息
  168. const [matchError, matchRes] = await uni.request({
  169. url: `${baseUrl}/my-resource/precise-match/${resourceId}`,
  170. method: 'GET'
  171. })
  172. // 加载资源跟进信息
  173. const [followError, followRes] = await uni.request({
  174. url: `${baseUrl}/my-resource/client-detail/${resourceId}`,
  175. method: 'GET'
  176. })
  177. this.loading = false
  178. // 处理跟进信息
  179. if (!followError && followRes.statusCode === 200 && followRes.data && followRes.data.code === 200) {
  180. const followData = followRes.data.data
  181. this.resourceFollowInfo = {
  182. isMatch: followData.isMatch !== null && followData.isMatch !== undefined ? followData.isMatch :
  183. (followData.is_match !== null && followData.is_match !== undefined ? followData.is_match : 0),
  184. isInvitation: followData.isInvitation !== null && followData.isInvitation !== undefined ? followData.isInvitation :
  185. (followData.is_Invitation !== null && followData.is_Invitation !== undefined ? followData.is_Invitation : 0)
  186. }
  187. }
  188. if (matchError) {
  189. console.error('加载匹配客户失败:', matchError)
  190. uni.showToast({
  191. title: '加载失败',
  192. icon: 'none'
  193. })
  194. return
  195. }
  196. if (matchRes.statusCode === 200 && matchRes.data && matchRes.data.code === 200) {
  197. const matchList = matchRes.data.data || []
  198. // 转换数据格式
  199. this.matchClients = matchList.map(item => {
  200. return {
  201. id: item.userId,
  202. name: item.nickname || '未知',
  203. gender: item.gender === 1 ? '男' : item.gender === 2 ? '女' : '未知',
  204. status: '未匹配',
  205. tags: item.tags || [],
  206. avatar: item.avatarUrl || 'https://via.placeholder.com/150',
  207. requirement: item.requirement || '暂无要求',
  208. contact: item.phone || '',
  209. matchScore: item.matchScore || 0,
  210. age: item.age,
  211. height: item.height,
  212. diploma: item.diploma,
  213. income: item.income,
  214. occupation: item.occupation,
  215. address: item.address
  216. }
  217. })
  218. console.log('匹配结果:', this.matchClients)
  219. if (this.matchClients.length === 0) {
  220. uni.showToast({
  221. title: '未找到匹配的用户',
  222. icon: 'none'
  223. })
  224. }
  225. } else {
  226. const errorMsg = matchRes.data?.message || '加载失败'
  227. console.error('加载匹配客户失败:', errorMsg)
  228. uni.showToast({
  229. title: errorMsg,
  230. icon: 'none'
  231. })
  232. }
  233. } catch (e) {
  234. this.loading = false
  235. console.error('加载匹配客户异常:', e)
  236. uni.showToast({
  237. title: '加载失败,请稍后重试',
  238. icon: 'none'
  239. })
  240. }
  241. },
  242. // 底部导航 - 工作台
  243. handleHome() {
  244. uni.navigateTo({
  245. url: '/pages/matchmaker-workbench/index'
  246. })
  247. },
  248. // 底部导航 - 我的资源
  249. handleMyResources() {
  250. uni.navigateTo({
  251. url: '/pages/matchmaker-workbench/my-resources'
  252. })
  253. },
  254. // 底部导航 - 排行榜
  255. handleRanking() {
  256. uni.navigateTo({
  257. url: '/pages/matchmaker-workbench/ranking'
  258. })
  259. },
  260. // 底部导航 - 消息
  261. handleMessage() {
  262. uni.navigateTo({
  263. url: '/pages/matchmaker-workbench/message'
  264. })
  265. },
  266. // 底部导航 - 我的
  267. handleMine() {
  268. uni.navigateTo({
  269. url: '/pages/matchmaker-workbench/mine'
  270. })
  271. }
  272. }
  273. }
  274. </script>
  275. <style lang="scss" scoped>
  276. .precise-match {
  277. min-height: 100vh;
  278. background-color: #FFF9F9;
  279. padding-top: 90rpx;
  280. }
  281. /* 顶部导航栏 */
  282. .header {
  283. position: fixed;
  284. top: 0;
  285. left: 0;
  286. right: 0;
  287. height: 90rpx;
  288. display: flex;
  289. align-items: center;
  290. justify-content: space-between;
  291. padding: 0 20rpx;
  292. background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD0 100%);
  293. z-index: 999;
  294. .back-icon {
  295. width: 44rpx;
  296. height: 44rpx;
  297. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23333"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
  298. background-size: contain;
  299. background-repeat: no-repeat;
  300. background-position: center;
  301. }
  302. .header-title {
  303. font-size: 38rpx;
  304. font-weight: bold;
  305. color: #333;
  306. }
  307. .header-right {
  308. width: 44rpx;
  309. }
  310. }
  311. /* 匹配客户列表 */
  312. .match-list {
  313. padding: 20rpx;
  314. padding-bottom: 120rpx;
  315. .match-client {
  316. background-color: #FFFFFF;
  317. border-radius: 20rpx;
  318. margin-bottom: 20rpx;
  319. padding: 25rpx;
  320. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  321. .client-info {
  322. display: flex;
  323. align-items: flex-start;
  324. margin-bottom: 20rpx;
  325. .client-avatar {
  326. width: 120rpx;
  327. height: 120rpx;
  328. border-radius: 50%;
  329. margin-right: 20rpx;
  330. background-color: #F5F5F5;
  331. }
  332. .info-content {
  333. flex: 1;
  334. .client-name {
  335. display: flex;
  336. align-items: center;
  337. gap: 15rpx;
  338. margin-bottom: 10rpx;
  339. .name-text {
  340. font-size: 32rpx;
  341. font-weight: bold;
  342. color: #333;
  343. }
  344. .client-gender {
  345. font-size: 26rpx;
  346. color: #666;
  347. }
  348. .client-status {
  349. font-size: 24rpx;
  350. color: #FF6B8A;
  351. background-color: #FFF3F5;
  352. padding: 4rpx 12rpx;
  353. border-radius: 15rpx;
  354. }
  355. }
  356. .client-tags {
  357. display: flex;
  358. flex-wrap: wrap;
  359. gap: 10rpx;
  360. margin-bottom: 15rpx;
  361. .tag {
  362. padding: 6rpx 16rpx;
  363. background-color: #F3E5F5;
  364. color: #9C27B0;
  365. border-radius: 15rpx;
  366. font-size: 22rpx;
  367. }
  368. }
  369. .requirement {
  370. display: flex;
  371. margin-bottom: 15rpx;
  372. .requirement-label {
  373. font-size: 26rpx;
  374. color: #666;
  375. margin-right: 10rpx;
  376. }
  377. .requirement-content {
  378. font-size: 26rpx;
  379. color: #333;
  380. }
  381. }
  382. .user-details {
  383. display: flex;
  384. flex-wrap: wrap;
  385. gap: 10rpx;
  386. margin-bottom: 15rpx;
  387. font-size: 24rpx;
  388. color: #999;
  389. .detail-item {
  390. display: flex;
  391. align-items: center;
  392. &::after {
  393. content: '|';
  394. margin: 0 10rpx;
  395. color: #ddd;
  396. }
  397. &:last-child::after {
  398. display: none;
  399. }
  400. }
  401. }
  402. .contact-info {
  403. display: flex;
  404. align-items: center;
  405. .contact-label {
  406. font-size: 26rpx;
  407. color: #666;
  408. margin-right: 10rpx;
  409. }
  410. .contact-number {
  411. font-size: 26rpx;
  412. color: #333;
  413. margin-right: 15rpx;
  414. }
  415. .copy-btn {
  416. font-size: 24rpx;
  417. color: #9C27B0;
  418. padding: 6rpx 16rpx;
  419. background-color: #F3E5F5;
  420. border-radius: 15rpx;
  421. }
  422. }
  423. }
  424. }
  425. .action-btn {
  426. width: 200rpx;
  427. height: 60rpx;
  428. display: flex;
  429. align-items: center;
  430. justify-content: center;
  431. background-color: #9C27B0;
  432. color: #FFFFFF;
  433. border-radius: 30rpx;
  434. font-size: 28rpx;
  435. font-weight: bold;
  436. margin-left: auto;
  437. }
  438. .match-score-badge {
  439. position: absolute;
  440. top: 20rpx;
  441. right: 20rpx;
  442. background: linear-gradient(135deg, #FF6B8A 0%, #FF8E9B 100%);
  443. color: #FFFFFF;
  444. padding: 8rpx 16rpx;
  445. border-radius: 20rpx;
  446. font-size: 22rpx;
  447. font-weight: bold;
  448. z-index: 10;
  449. .score-text {
  450. color: #FFFFFF;
  451. }
  452. }
  453. }
  454. }
  455. /* 加载中 */
  456. .loading-container {
  457. display: flex;
  458. justify-content: center;
  459. align-items: center;
  460. padding: 100rpx 0;
  461. .loading-text {
  462. font-size: 28rpx;
  463. color: #999;
  464. }
  465. }
  466. /* 资源跟进状态区域 */
  467. .follow-status-section {
  468. background-color: #FFFFFF;
  469. margin: 20rpx;
  470. padding: 30rpx;
  471. border-radius: 20rpx;
  472. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  473. display: flex;
  474. justify-content: space-around;
  475. align-items: center;
  476. }
  477. .follow-status-group {
  478. display: flex;
  479. flex-direction: column;
  480. align-items: center;
  481. gap: 12rpx;
  482. }
  483. .status-label {
  484. font-size: 26rpx;
  485. color: #666;
  486. font-weight: 500;
  487. }
  488. .status-tags {
  489. display: flex;
  490. gap: 10rpx;
  491. }
  492. .status-tag {
  493. padding: 8rpx 20rpx;
  494. border-radius: 20rpx;
  495. font-size: 24rpx;
  496. background-color: #FAF5FF;
  497. color: #9C27B0;
  498. border: 2rpx solid #E1BEE7;
  499. &.active {
  500. background-color: #F8BBD0;
  501. border-color: #F06292;
  502. color: #FFFFFF;
  503. }
  504. }
  505. /* 底部导航 */
  506. .tabbar {
  507. position: fixed;
  508. bottom: 0;
  509. left: 0;
  510. right: 0;
  511. height: 100rpx;
  512. background: #FFFFFF;
  513. border-top: 1rpx solid #F0F0F0;
  514. display: flex;
  515. justify-content: space-around;
  516. align-items: center;
  517. padding-bottom: env(safe-area-inset-bottom);
  518. .tabbar-item {
  519. display: flex;
  520. flex-direction: column;
  521. align-items: center;
  522. gap: 8rpx;
  523. padding: 10rpx 0;
  524. .tabbar-icon {
  525. width: 44rpx;
  526. height: 44rpx;
  527. background-size: contain;
  528. background-repeat: no-repeat;
  529. background-position: center;
  530. position: relative;
  531. .badge {
  532. position: absolute;
  533. top: -8rpx;
  534. right: -8rpx;
  535. width: 32rpx;
  536. height: 32rpx;
  537. display: flex;
  538. align-items: center;
  539. justify-content: center;
  540. background-color: #FF4444;
  541. color: #FFFFFF;
  542. font-size: 20rpx;
  543. font-weight: bold;
  544. border-radius: 50%;
  545. }
  546. }
  547. .tabbar-text {
  548. font-size: 20rpx;
  549. color: #999;
  550. }
  551. &.active {
  552. .tabbar-text {
  553. color: #9C27B0;
  554. font-weight: bold;
  555. }
  556. }
  557. &.home .tabbar-icon {
  558. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
  559. }
  560. &.resources .tabbar-icon {
  561. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
  562. }
  563. &.trophy .tabbar-icon {
  564. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M18 6l-1.42 1.42-1.59-1.59L13 8.17l-1.42-1.42L9 8.17l-1.59-1.59L6 6l3 3V18c0 1.1.9 2 2 2h4c1.1 0 2-.9 2-2V9l3-3zm-4 12H8v-7.5l4-4 4 4V18z"/></svg>');
  565. }
  566. &.message .tabbar-icon {
  567. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
  568. }
  569. &.mine .tabbar-icon {
  570. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
  571. }
  572. }
  573. }
  574. </style>