quality-resources.vue 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. <template>
  2. <view class="quality-resources">
  3. <!-- 顶部导航栏 -->
  4. <view class="header">
  5. <view class="back-icon" @click="handleBack"></view>
  6. <text class="header-title">优质资源</text>
  7. <view class="filter-icon" @click="handleFilter"></view>
  8. </view>
  9. <!-- 搜索栏 -->
  10. <view class="search-bar">
  11. <view class="search-input-wrapper">
  12. <view class="search-icon-small"></view>
  13. <input type="text" class="search-input" placeholder="请输入搜索关键词" placeholder-style="color: #999;" v-model="searchKeyword" @input="handleSearch" />
  14. </view>
  15. </view>
  16. <!-- 资源列表 -->
  17. <scroll-view scroll-y class="content" @scrolltolower="loadMore" :scroll-with-animation="true">
  18. <view v-if="loading" class="loading-wrapper">
  19. <text class="loading-text">加载中...</text>
  20. </view>
  21. <view v-else-if="resources.length === 0" class="empty-wrapper">
  22. <text class="empty-text">暂无优质资源</text>
  23. </view>
  24. <view class="resource-item" v-for="(resource, index) in resources" :key="getResourceKey(resource, index)" @click="handleResourceClick(index)">
  25. <view class="resource-header">
  26. <image
  27. :src="getAvatarUrl(resource) || '/static/default-avatar.svg'"
  28. mode="aspectFill"
  29. class="avatar"
  30. @error="handleImageError(index)"
  31. ></image>
  32. <view class="resource-info">
  33. <view class="name-gender">
  34. <text class="name">{{ resource.name }}</text>
  35. <text class="gender">{{ resource.gender === 1 ? '男' : '女' }}</text>
  36. </view>
  37. <view class="status-tag-wrapper">
  38. <text class="status-tag register-tag registered">已注册</text>
  39. <text class="status-tag match-tag" :class="{ 'matched': getIsMatch(resource) === 1, 'unmatched': getIsMatch(resource) === 0 || !getIsMatch(resource) }">
  40. {{ getIsMatch(resource) === 1 ? '已匹配' : '未匹配' }}
  41. </text>
  42. </view>
  43. </view>
  44. </view>
  45. <view class="resource-details">
  46. <view class="labels" v-if="resource.tags && resource.tags.length > 0">
  47. <text class="label" v-for="(tag, tagIndex) in resource.tags" :key="tagIndex">{{ tag }}</text>
  48. </view>
  49. <view class="requirement-box">
  50. <text class="requirement-label">择偶要求:</text>
  51. <text class="requirement-content">{{ getMateSelectionCriteria(resource) || '暂无' }}</text>
  52. </view>
  53. <view class="contact-box">
  54. <text class="contact-label">联系方式:</text>
  55. <text class="contact-number">{{ getMaskedPhone(resource.phone) || '暂无' }}</text>
  56. <text class="copy-btn" @click.stop="handleCopy(resource.phone)">复制</text>
  57. </view>
  58. <view class="action-buttons">
  59. <view class="action-btn add-resource-btn" @click.stop="handleAddToMyResources(index)">添加到我的资源</view>
  60. <view class="action-btn chat-btn" @click.stop="handleChat(resource, index)">牵线聊聊</view>
  61. </view>
  62. </view>
  63. </view>
  64. <view v-if="hasMore && !loading" class="load-more-wrapper">
  65. <text class="load-more-text">上拉加载更多</text>
  66. </view>
  67. <view v-if="!hasMore && resources.length > 0" class="no-more-wrapper">
  68. <text class="no-more-text">没有更多数据了</text>
  69. </view>
  70. </scroll-view>
  71. </view>
  72. </template>
  73. <script>
  74. export default {
  75. name: 'quality-resources',
  76. data() {
  77. return {
  78. resources: [],
  79. searchKeyword: '',
  80. pageNum: 1,
  81. pageSize: 10,
  82. loading: false,
  83. hasMore: true,
  84. currentUserId: null // 当前登录用户的用户ID
  85. }
  86. },
  87. onLoad() {
  88. // 获取当前登录用户的ID
  89. const userId = uni.getStorageSync('userId')
  90. if (userId) {
  91. const rawUserId = parseInt(userId)
  92. if (!isNaN(rawUserId) && rawUserId > 0) {
  93. this.currentUserId = rawUserId
  94. console.log('当前登录用户ID:', this.currentUserId)
  95. }
  96. }
  97. this.loadResources()
  98. },
  99. methods: {
  100. // 返回上一页
  101. handleBack() {
  102. uni.navigateBack()
  103. },
  104. // 筛选
  105. handleFilter() {
  106. // 实现筛选功能
  107. },
  108. // 搜索
  109. handleSearch() {
  110. this.pageNum = 1
  111. this.resources = []
  112. this.hasMore = true
  113. this.loadResources()
  114. },
  115. // 加载资源列表
  116. async loadResources() {
  117. if (this.loading) return
  118. try {
  119. this.loading = true
  120. const baseUrl = process.env.NODE_ENV === 'development'
  121. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  122. : 'https://your-domain.com/api' // 生产环境
  123. // 构建请求参数
  124. const requestData = {
  125. tagName: '优质资源',
  126. keyword: this.searchKeyword || '',
  127. pageNum: this.pageNum,
  128. pageSize: this.pageSize
  129. }
  130. // 如果已登录,传递currentUserId,用于排除该红娘已拥有的资源
  131. if (this.currentUserId) {
  132. requestData.currentUserId = this.currentUserId
  133. }
  134. const [error, res] = await uni.request({
  135. url: `${baseUrl}/my-resource/list-by-tag`,
  136. method: 'GET',
  137. data: requestData,
  138. timeout: 10000
  139. })
  140. if (error) {
  141. console.error('加载优质资源失败:', error)
  142. uni.showToast({
  143. title: '加载失败',
  144. icon: 'none'
  145. })
  146. return
  147. }
  148. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  149. const pageData = res.data.data
  150. if (pageData && pageData.records) {
  151. // 调试:打印第一条数据的详细信息
  152. if (pageData.records.length > 0) {
  153. const firstRecord = pageData.records[0]
  154. console.log('=== 第一条资源数据 ===')
  155. console.log('完整数据:', JSON.stringify(firstRecord, null, 2))
  156. console.log('isUser值:', firstRecord.isUser, '类型:', typeof firstRecord.isUser)
  157. console.log('is_user值:', firstRecord.is_user, '类型:', typeof firstRecord.is_user)
  158. console.log('userId值:', firstRecord.userId, '类型:', typeof firstRecord.userId)
  159. console.log('user_id值:', firstRecord.user_id, '类型:', typeof firstRecord.user_id)
  160. console.log('mateSelectionCriteria (驼峰):', firstRecord.mateSelectionCriteria)
  161. console.log('mate_selection_criteria (下划线):', firstRecord.mate_selection_criteria)
  162. console.log('phone:', firstRecord.phone)
  163. }
  164. // 过滤掉空元素,确保数组中不包含 null 或 undefined
  165. const validRecords = pageData.records.filter(record => record !== null && record !== undefined)
  166. if (this.pageNum === 1) {
  167. this.resources = validRecords
  168. } else {
  169. this.resources = this.resources.concat(validRecords)
  170. }
  171. // 判断是否还有更多数据
  172. this.hasMore = pageData.records && pageData.records.length >= this.pageSize
  173. console.log('加载优质资源成功,数量:', this.resources.length)
  174. } else {
  175. console.error('加载优质资源失败:', res.data.message)
  176. }
  177. } else {
  178. console.error('加载优质资源失败:', res.data.message)
  179. uni.showToast({
  180. title: res.data.message || '加载失败',
  181. icon: 'none'
  182. })
  183. }
  184. } catch (e) {
  185. console.error('加载优质资源异常:', e)
  186. uni.showToast({
  187. title: '加载异常',
  188. icon: 'none'
  189. })
  190. } finally {
  191. this.loading = false
  192. }
  193. },
  194. // 加载更多
  195. loadMore() {
  196. if (this.hasMore && !this.loading) {
  197. this.pageNum++
  198. this.loadResources()
  199. }
  200. },
  201. // 图片加载错误处理
  202. handleImageError(index) {
  203. if (this.resources[index]) {
  204. // 设置头像为空,让CSS默认背景显示
  205. // 支持下划线和驼峰两种格式
  206. this.resources[index].avatarUrl = ''
  207. this.resources[index].avatar_url = ''
  208. this.resources[index].avatar = ''
  209. }
  210. },
  211. // 复制联系方式
  212. handleCopy(contact) {
  213. if (!contact) {
  214. uni.showToast({
  215. title: '联系方式为空',
  216. icon: 'none'
  217. })
  218. return
  219. }
  220. uni.setClipboardData({
  221. data: contact,
  222. success: () => {
  223. uni.showToast({
  224. title: '复制成功',
  225. icon: 'success'
  226. })
  227. }
  228. })
  229. },
  230. // 牵线聊聊
  231. handleChat(resource, index) {
  232. console.log('=== 牵线聊聊 ===')
  233. console.log('传入的resource:', resource)
  234. console.log('传入的index:', index, '类型:', typeof index)
  235. console.log('resources数组长度:', this.resources ? this.resources.length : 'resources未定义')
  236. // 优先使用传入的 resource 对象,如果为空则通过 index 获取
  237. let targetResource = resource
  238. // 如果传入的 resource 为空,尝试通过 index 获取
  239. if (!targetResource || targetResource === null || targetResource === undefined) {
  240. console.warn('⚠️ 传入的resource为空,尝试通过index获取')
  241. // 检查 resources 数组是否存在
  242. if (!this.resources || !Array.isArray(this.resources)) {
  243. console.error('❌ resources数组不存在或不是数组')
  244. uni.showToast({
  245. title: '资源列表异常,请刷新重试',
  246. icon: 'none'
  247. })
  248. return
  249. }
  250. // 检查index是否有效
  251. if (index === undefined || index === null || isNaN(index) || index < 0 || index >= this.resources.length) {
  252. console.error('❌ index无效:', index, '数组长度:', this.resources.length)
  253. console.log('resources数组内容:', JSON.stringify(this.resources, null, 2))
  254. uni.showToast({
  255. title: '资源索引无效',
  256. icon: 'none'
  257. })
  258. return
  259. }
  260. // 从数组中获取resource对象
  261. targetResource = this.resources[index]
  262. console.log('从数组获取的resource对象:', targetResource)
  263. }
  264. console.log('最终使用的resource对象:', targetResource)
  265. console.log('资源信息类型:', typeof targetResource)
  266. console.log('资源信息是否为null:', targetResource === null)
  267. console.log('资源信息是否为undefined:', targetResource === undefined)
  268. console.log('资源信息的所有键:', targetResource ? Object.keys(targetResource) : 'resource为空')
  269. // 验证 resource 对象是否存在
  270. if (!targetResource || targetResource === null || targetResource === undefined) {
  271. console.error('❌ resource对象为空')
  272. console.error('传入的resource:', resource)
  273. console.error('传入的index:', index)
  274. console.error('resources数组:', JSON.stringify(this.resources, null, 2))
  275. if (index !== undefined && index !== null && !isNaN(index) && index >= 0 && index < (this.resources ? this.resources.length : 0)) {
  276. console.error('resources[index]:', this.resources[index])
  277. }
  278. uni.showToast({
  279. title: '资源信息为空,请刷新重试',
  280. icon: 'none',
  281. duration: 3000
  282. })
  283. return
  284. }
  285. // 使用 targetResource 继续后续处理
  286. // 检查用户是否已注册(只有已注册用户才能聊天)
  287. // 支持下划线和驼峰两种格式
  288. const isUser = targetResource.isUser !== undefined ? targetResource.isUser :
  289. (targetResource.is_user !== undefined ? targetResource.is_user : 0)
  290. if (isUser !== 1 && isUser !== '1') {
  291. console.warn('⚠️ 用户未注册,无法聊天')
  292. uni.showToast({
  293. title: '该用户尚未注册,无法聊天',
  294. icon: 'none',
  295. duration: 3000
  296. })
  297. return
  298. }
  299. // 尝试多种方式获取用户ID
  300. let targetUserId = null
  301. // 方式1: 使用 userId 字段(驼峰格式)
  302. if (targetResource.userId !== null && targetResource.userId !== undefined && targetResource.userId !== '') {
  303. targetUserId = String(targetResource.userId)
  304. console.log('✅ 从 targetResource.userId 获取用户ID:', targetUserId)
  305. }
  306. // 方式2: 使用 user_id 字段(下划线格式)
  307. else if (targetResource.user_id !== null && targetResource.user_id !== undefined && targetResource.user_id !== '') {
  308. targetUserId = String(targetResource.user_id)
  309. console.log('✅ 从 targetResource.user_id 获取用户ID:', targetUserId)
  310. }
  311. // 方式3: 使用 id 字段
  312. else if (targetResource.id !== null && targetResource.id !== undefined && targetResource.id !== '') {
  313. targetUserId = String(targetResource.id)
  314. console.log('✅ 从 targetResource.id 获取用户ID:', targetUserId)
  315. }
  316. // 如果仍然没有获取到用户ID
  317. if (!targetUserId || targetUserId === 'null' || targetUserId === 'undefined' || targetUserId === '') {
  318. console.error('❌ 无法获取用户ID')
  319. console.log('targetResource对象完整内容:', JSON.stringify(targetResource, null, 2))
  320. uni.showToast({
  321. title: '无法获取用户ID,请刷新重试',
  322. icon: 'none',
  323. duration: 3000
  324. })
  325. return
  326. }
  327. // 获取其他必要信息(支持下划线和驼峰格式)
  328. const targetUserName = targetResource.name || '用户'
  329. const targetUserAvatar = targetResource.avatarUrl || targetResource.avatar_url || targetResource.avatar || '/static/default-avatar.svg'
  330. console.log('跳转参数:')
  331. console.log(' - targetUserId:', targetUserId)
  332. console.log(' - targetUserName:', targetUserName)
  333. console.log(' - targetUserAvatar:', targetUserAvatar)
  334. // 跳转到聊天页面
  335. // 注意:fromMatchmaker=1 表示来自红娘工作台,会跳过消息限制和审核
  336. uni.navigateTo({
  337. url: `/pages/message/chat?targetUserId=${targetUserId}&targetUserName=${encodeURIComponent(targetUserName)}&targetUserAvatar=${encodeURIComponent(targetUserAvatar)}&fromMatchmaker=1`,
  338. success: () => {
  339. console.log('✅ 跳转聊天页面成功')
  340. },
  341. fail: (err) => {
  342. console.error('❌ 跳转聊天页面失败:', err)
  343. uni.showToast({
  344. title: '跳转失败,请重试',
  345. icon: 'none'
  346. })
  347. }
  348. })
  349. },
  350. // 获取资源项的key(用于v-for)
  351. getResourceKey(resource, index) {
  352. if (!resource) return `resource-${index}`
  353. // 支持下划线和驼峰两种格式
  354. const resourceId = resource.resourceId || resource.resource_id
  355. if (resourceId !== null && resourceId !== undefined && resourceId !== '') {
  356. return `resource-${resourceId}`
  357. }
  358. return `resource-${index}`
  359. },
  360. // 点击资源项
  361. handleResourceClick(index) {
  362. console.log('=== 点击资源项 ===')
  363. console.log('传入的index:', index)
  364. console.log('resources数组长度:', this.resources.length)
  365. // 检查index是否有效
  366. if (index === undefined || index === null || index < 0 || index >= this.resources.length) {
  367. console.error('❌ index无效:', index)
  368. uni.showToast({
  369. title: '资源索引无效',
  370. icon: 'none'
  371. })
  372. return
  373. }
  374. // 从数组中获取resource对象
  375. const resource = this.resources[index]
  376. console.log('从数组获取的resource对象:', JSON.stringify(resource, null, 2))
  377. if (!resource) {
  378. console.error('❌ resource对象为空')
  379. uni.showToast({
  380. title: '资源信息为空',
  381. icon: 'none'
  382. })
  383. return
  384. }
  385. // 支持下划线和驼峰两种格式
  386. const resourceId = resource.resourceId || resource.resource_id
  387. const isUser = resource.isUser !== undefined ? resource.isUser :
  388. (resource.is_user !== undefined ? resource.is_user : 0)
  389. console.log('resource.resourceId:', resourceId)
  390. console.log('resource.isUser:', isUser)
  391. // 检查resourceId是否有效
  392. if (!resourceId || resourceId === null || resourceId === undefined || resourceId === '') {
  393. console.error('❌ resourceId无效:', resourceId)
  394. uni.showToast({
  395. title: '资源ID无效,无法查看详情',
  396. icon: 'none'
  397. })
  398. return
  399. }
  400. // 判断是否为已注册用户
  401. if (isUser === 1 || isUser === '1') {
  402. // 已注册,跳转到客户详情页面,添加fromQualityResources参数标识来源
  403. console.log('准备跳转,resourceId:', resourceId)
  404. uni.navigateTo({
  405. url: `/pages/matchmaker-workbench/client-detail?resourceId=${resourceId}&fromQualityResources=1`,
  406. success: () => {
  407. console.log('✅ 跳转成功,resourceId:', resourceId)
  408. },
  409. fail: (err) => {
  410. console.error('❌ 跳转失败:', err)
  411. uni.showToast({
  412. title: '跳转失败,请重试',
  413. icon: 'none'
  414. })
  415. }
  416. })
  417. } else {
  418. // 未注册,提示用户(虽然后端已经过滤了,但为了容错仍然保留)
  419. uni.showToast({
  420. title: '该用户还未注册用户端',
  421. icon: 'none',
  422. duration: 2000
  423. })
  424. }
  425. },
  426. // 添加到我的资源
  427. async handleAddToMyResources(index) {
  428. console.log('=== 添加到我的资源 ===')
  429. console.log('index:', index)
  430. // 检查index是否有效
  431. if (index === undefined || index === null || index < 0 || index >= this.resources.length) {
  432. console.error('❌ index无效:', index)
  433. uni.showToast({
  434. title: '资源索引无效',
  435. icon: 'none'
  436. })
  437. return
  438. }
  439. // 从数组中获取resource对象
  440. const resource = this.resources[index]
  441. console.log('从数组获取的resource对象:', JSON.stringify(resource, null, 2))
  442. if (!resource) {
  443. console.error('❌ resource对象为空')
  444. uni.showToast({
  445. title: '资源信息为空',
  446. icon: 'none'
  447. })
  448. return
  449. }
  450. // 检查是否已登录
  451. if (!this.currentUserId) {
  452. uni.showToast({
  453. title: '请先登录',
  454. icon: 'none'
  455. })
  456. return
  457. }
  458. try {
  459. uni.showLoading({
  460. title: '检查中...'
  461. })
  462. const baseUrl = process.env.NODE_ENV === 'development'
  463. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  464. : 'https://your-domain.com/api' // 生产环境
  465. // 获取要添加的手机号和备用手机号
  466. const phone = resource.phone
  467. const backupPhone = resource.backupPhone || resource.backup_phone
  468. // 检查手机号是否已存在(检查整个my_resource表,不限制红娘)
  469. const checkUrl = `${baseUrl}/my-resource/check-phone?phone=${encodeURIComponent(phone || '')}${backupPhone ? '&backupPhone=' + encodeURIComponent(backupPhone) : ''}`
  470. const [checkError, checkRes] = await uni.request({
  471. url: checkUrl,
  472. method: 'GET'
  473. })
  474. if (checkError) {
  475. console.error('检查手机号失败:', checkError)
  476. uni.hideLoading()
  477. uni.showToast({
  478. title: '检查手机号失败,请重试',
  479. icon: 'none'
  480. })
  481. return
  482. }
  483. if (checkRes.statusCode === 200 && checkRes.data && checkRes.data.code === 200) {
  484. const checkResult = checkRes.data.data
  485. const existingResource = checkResult.existingResource
  486. // 如果发现重复的手机号,提示用户
  487. if (checkResult.phoneExists || checkResult.backupPhoneExists || checkResult.phoneAsBackupExists || checkResult.backupPhoneAsMainExists) {
  488. let message = '该手机号已经绑定资源啦'
  489. // 根据不同的检查结果,显示具体的手机号类型
  490. if (checkResult.phoneExists) {
  491. message = '该主手机号已经绑定资源啦'
  492. } else if (checkResult.phoneAsBackupExists) {
  493. message = '该主手机号已经作为备用手机号绑定资源啦'
  494. } else if (checkResult.backupPhoneAsMainExists) {
  495. message = '该备用手机号已经作为主手机号绑定资源啦'
  496. } else if (checkResult.backupPhoneExists) {
  497. message = '该备用手机号已经绑定资源啦'
  498. }
  499. uni.hideLoading()
  500. uni.showToast({
  501. title: message,
  502. icon: 'none',
  503. duration: 3000
  504. })
  505. return
  506. }
  507. }
  508. // 构建要添加的资源数据(复制原资源的所有信息,但使用当前红娘的matchmaker_id)
  509. const resourceData = {
  510. name: resource.name,
  511. age: resource.age,
  512. gender: resource.gender,
  513. constellation: resource.constellation,
  514. height: resource.height,
  515. weight: resource.weight,
  516. marrStatus: resource.marrStatus || resource.marr_status,
  517. diploma: resource.diploma,
  518. income: resource.income,
  519. address: resource.address,
  520. domicile: resource.domicile,
  521. occupation: resource.occupation,
  522. house: resource.house,
  523. phone: phone,
  524. backupPhone: backupPhone,
  525. car: resource.car,
  526. mateSelectionCriteria: resource.mateSelectionCriteria || resource.mate_selection_criteria,
  527. isUser: resource.isUser || resource.is_user || 1,
  528. userId: resource.userId || resource.user_id
  529. }
  530. uni.showLoading({
  531. title: '添加中...'
  532. })
  533. // 获取标签ID列表
  534. let tagIds = []
  535. // 方法1: 如果resource有resourceId,直接根据resourceId获取标签ID
  536. const resourceId = resource.resourceId || resource.resource_id
  537. if (resourceId) {
  538. try {
  539. const [tagError, tagRes] = await uni.request({
  540. url: `${baseUrl}/my-resource/tag-ids/${resourceId}`,
  541. method: 'GET'
  542. })
  543. if (!tagError && tagRes.statusCode === 200 && tagRes.data && tagRes.data.code === 200) {
  544. tagIds = tagRes.data.data || []
  545. console.log('根据resourceId获取的标签ID:', tagIds)
  546. }
  547. } catch (e) {
  548. console.warn('根据resourceId获取标签ID失败,将使用标签名称查询:', e)
  549. }
  550. }
  551. // 方法2: 如果方法1失败或没有resourceId,根据标签名称查询标签ID
  552. if (tagIds.length === 0 && resource.tags && resource.tags.length > 0) {
  553. try {
  554. // 获取所有标签列表
  555. const [tagListError, tagListRes] = await uni.request({
  556. url: `${baseUrl}/tag/list`,
  557. method: 'GET'
  558. })
  559. if (!tagListError && tagListRes.statusCode === 200 && tagListRes.data && tagListRes.data.code === 200) {
  560. const allTags = tagListRes.data.data || []
  561. console.log('所有标签列表:', allTags)
  562. // 根据标签名称匹配标签ID
  563. for (const tagName of resource.tags) {
  564. const matchedTag = allTags.find(tag => {
  565. const tagNameField = tag.name || tag.tag_name || tag.tagName
  566. return tagNameField === tagName
  567. })
  568. if (matchedTag) {
  569. const tagId = matchedTag.id || matchedTag.tag_id
  570. if (tagId && !tagIds.includes(tagId)) {
  571. tagIds.push(tagId)
  572. console.log(`找到标签 "${tagName}" 的ID:`, tagId)
  573. }
  574. } else {
  575. console.warn(`未找到标签 "${tagName}" 的ID`)
  576. }
  577. }
  578. console.log('根据标签名称查询到的标签ID列表:', tagIds)
  579. }
  580. } catch (e) {
  581. console.error('根据标签名称查询标签ID异常:', e)
  582. }
  583. }
  584. // 确保tagIds是整数数组
  585. tagIds = tagIds.map(id => parseInt(id)).filter(id => !isNaN(id) && id > 0)
  586. if (tagIds.length === 0) {
  587. console.warn('⚠️ 未获取到任何标签ID,资源将不包含标签')
  588. } else {
  589. console.log('✅ 最终获取到的标签ID列表:', tagIds)
  590. }
  591. // 调用后端API添加资源
  592. const url = `${baseUrl}/my-resource/add?currentUserId=${this.currentUserId}`
  593. console.log('添加资源请求URL:', url)
  594. console.log('添加资源数据:', JSON.stringify(resourceData, null, 2))
  595. console.log('标签ID列表:', tagIds)
  596. const [error, res] = await uni.request({
  597. url: url,
  598. method: 'POST',
  599. data: {
  600. ...resourceData,
  601. tagIds: tagIds
  602. },
  603. header: {
  604. 'Content-Type': 'application/json'
  605. }
  606. })
  607. uni.hideLoading()
  608. if (error) {
  609. console.error('添加到我的资源失败:', error)
  610. uni.showToast({
  611. title: '添加失败,请重试',
  612. icon: 'none'
  613. })
  614. return
  615. }
  616. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  617. uni.showToast({
  618. title: '添加成功',
  619. icon: 'success'
  620. })
  621. // 发送刷新事件,通知我的资源页面刷新列表
  622. uni.$emit('refreshResourceList')
  623. } else {
  624. console.error('添加到我的资源失败:', res.data.message)
  625. uni.showToast({
  626. title: res.data.message || '添加失败',
  627. icon: 'none',
  628. duration: 2000
  629. })
  630. }
  631. } catch (e) {
  632. uni.hideLoading()
  633. console.error('添加到我的资源异常:', e)
  634. uni.showToast({
  635. title: '添加异常,请稍后重试',
  636. icon: 'none'
  637. })
  638. }
  639. },
  640. // 获取头像URL(支持驼峰和下划线格式)
  641. getAvatarUrl(resource) {
  642. if (!resource) return ''
  643. // 优先使用 avatarUrl(驼峰),如果没有则使用 avatar_url(下划线),最后使用 avatar
  644. return resource.avatarUrl || resource.avatar_url || resource.avatar || ''
  645. },
  646. // 获取匹配状态(支持驼峰和下划线格式)
  647. getIsMatch(resource) {
  648. if (!resource) return 0
  649. // 支持 isMatch(驼峰)和 is_match(下划线)两种格式
  650. return resource.isMatch !== undefined ? resource.isMatch :
  651. (resource.is_match !== undefined ? resource.is_match : 0)
  652. },
  653. // 获取择偶要求(支持驼峰和下划线格式)
  654. getMateSelectionCriteria(resource) {
  655. if (!resource) {
  656. console.warn('getMateSelectionCriteria: resource为空')
  657. return ''
  658. }
  659. // 支持 mateSelectionCriteria(驼峰)和 mate_selection_criteria(下划线)两种格式
  660. let criteria = resource.mateSelectionCriteria || resource.mate_selection_criteria || ''
  661. // 如果为空,直接返回
  662. if (!criteria || criteria.trim() === '') {
  663. return ''
  664. }
  665. // 去除首尾空格
  666. criteria = criteria.trim()
  667. // 如果获取到的是电话号码格式(11位数字或脱敏后的格式),则返回空字符串,让模板显示"暂无"
  668. // 检查是否是纯数字(11位)或包含****的脱敏格式
  669. const phonePattern = /^(\d{3}\*{4}\d{4}|\d{11})$/
  670. if (phonePattern.test(criteria)) {
  671. console.warn('⚠️ 择偶要求字段包含电话号码格式,返回空字符串', {
  672. criteria: criteria,
  673. phone: resource.phone,
  674. 'resource完整对象': resource
  675. })
  676. return ''
  677. }
  678. // 如果择偶要求等于电话号码(未脱敏),也返回空
  679. if (criteria === resource.phone || criteria === (resource.phone || '').replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')) {
  680. console.warn('⚠️ 择偶要求等于电话号码,返回空字符串', {
  681. criteria: criteria,
  682. phone: resource.phone
  683. })
  684. return ''
  685. }
  686. return criteria
  687. },
  688. // 获取脱敏手机号
  689. getMaskedPhone(phone) {
  690. if (!phone) return ''
  691. // 手机号脱敏:显示前3位和后4位,中间用****代替
  692. return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
  693. }
  694. }
  695. }
  696. </script>
  697. <style lang="scss" scoped>
  698. .quality-resources {
  699. min-height: 100vh;
  700. background: #FFF9F9;
  701. display: flex;
  702. flex-direction: column;
  703. }
  704. /* 顶部导航栏 */
  705. .header {
  706. display: flex;
  707. align-items: center;
  708. justify-content: space-between;
  709. padding: 25rpx 30rpx;
  710. padding-top: calc(25rpx + env(safe-area-inset-top));
  711. background: #FFF9F9;
  712. border-bottom: 1rpx solid #F0F0F0;
  713. .back-icon {
  714. width: 44rpx;
  715. height: 44rpx;
  716. 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="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
  717. background-size: contain;
  718. background-repeat: no-repeat;
  719. background-position: center;
  720. }
  721. .header-title {
  722. font-size: 38rpx;
  723. font-weight: bold;
  724. color: #9C27B0;
  725. }
  726. .filter-icon {
  727. width: 44rpx;
  728. height: 44rpx;
  729. 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 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/></svg>');
  730. background-size: contain;
  731. background-repeat: no-repeat;
  732. background-position: center;
  733. }
  734. }
  735. /* 搜索栏 */
  736. .search-bar {
  737. padding: 20rpx 30rpx;
  738. background: #FFF9F9;
  739. .search-input-wrapper {
  740. display: flex;
  741. align-items: center;
  742. background: #FFFFFF;
  743. border-radius: 25rpx;
  744. padding: 15rpx 20rpx;
  745. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  746. .search-icon-small {
  747. width: 32rpx;
  748. height: 32rpx;
  749. 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="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>');
  750. background-size: contain;
  751. background-repeat: no-repeat;
  752. background-position: center;
  753. margin-right: 15rpx;
  754. }
  755. .search-input {
  756. flex: 1;
  757. font-size: 28rpx;
  758. color: #333;
  759. placeholder-color: #999;
  760. }
  761. }
  762. }
  763. /* 内容区域 */
  764. .content {
  765. flex: 1;
  766. padding: 0 30rpx 20rpx;
  767. }
  768. /* 资源列表项 */
  769. .resource-item {
  770. background: #FFFFFF;
  771. border-radius: 20rpx;
  772. padding: 25rpx;
  773. margin-bottom: 20rpx;
  774. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  775. }
  776. .resource-header {
  777. display: flex;
  778. align-items: flex-start;
  779. margin-bottom: 20rpx;
  780. .avatar {
  781. width: 100rpx;
  782. height: 100rpx;
  783. border-radius: 50%;
  784. background: #F0F0F0;
  785. margin-right: 20rpx;
  786. flex-shrink: 0;
  787. }
  788. .resource-info {
  789. flex: 1;
  790. .name-gender {
  791. display: flex;
  792. align-items: center;
  793. gap: 15rpx;
  794. margin-bottom: 10rpx;
  795. .name {
  796. font-size: 32rpx;
  797. font-weight: bold;
  798. color: #333;
  799. }
  800. .gender {
  801. font-size: 24rpx;
  802. color: #999;
  803. padding: 4rpx 12rpx;
  804. background: #F5F5F5;
  805. border-radius: 12rpx;
  806. }
  807. }
  808. .status-tag-wrapper {
  809. display: flex;
  810. align-items: center;
  811. gap: 10rpx;
  812. margin-bottom: 10rpx;
  813. .status-tag {
  814. font-size: 22rpx;
  815. padding: 6rpx 14rpx;
  816. border-radius: 12rpx;
  817. font-weight: 500;
  818. &.register-tag {
  819. &.registered {
  820. color: #4CAF50;
  821. background: #E8F5E9;
  822. border: 1rpx solid #C8E6C9;
  823. }
  824. }
  825. &.match-tag {
  826. &.matched {
  827. color: #2196F3;
  828. background: #E3F2FD;
  829. border: 1rpx solid #BBDEFB;
  830. }
  831. &.unmatched {
  832. color: #FF9800;
  833. background: #FFF3E0;
  834. border: 1rpx solid #FFE0B2;
  835. }
  836. }
  837. }
  838. }
  839. }
  840. }
  841. .resource-details {
  842. .labels {
  843. display: flex;
  844. flex-wrap: wrap;
  845. gap: 10rpx;
  846. margin-bottom: 15rpx;
  847. .label {
  848. display: inline-block;
  849. padding: 6rpx 14rpx;
  850. background: #F3E5F5;
  851. color: #9C27B0;
  852. border-radius: 15rpx;
  853. font-size: 22rpx;
  854. font-weight: 500;
  855. }
  856. }
  857. .requirement-box {
  858. background: #F3E5F5;
  859. border-radius: 12rpx;
  860. padding: 16rpx 20rpx;
  861. margin-bottom: 15rpx;
  862. display: flex;
  863. align-items: center;
  864. flex-wrap: wrap;
  865. .requirement-label {
  866. font-size: 28rpx;
  867. color: #7B1FA2;
  868. font-weight: 500;
  869. margin-right: 8rpx;
  870. white-space: nowrap;
  871. }
  872. .requirement-content {
  873. font-size: 28rpx;
  874. color: #7B1FA2;
  875. font-weight: 400;
  876. }
  877. }
  878. .contact-box {
  879. margin-bottom: 20rpx;
  880. display: flex;
  881. align-items: center;
  882. flex-wrap: wrap;
  883. .contact-label {
  884. font-size: 26rpx;
  885. color: #333;
  886. font-weight: 500;
  887. margin-right: 10rpx;
  888. white-space: nowrap;
  889. }
  890. .contact-number {
  891. flex: 1;
  892. font-size: 26rpx;
  893. color: #333;
  894. font-weight: 400;
  895. min-width: 0;
  896. }
  897. .copy-btn {
  898. font-size: 24rpx;
  899. color: #9C27B0;
  900. font-weight: 500;
  901. margin-left: 15rpx;
  902. white-space: nowrap;
  903. cursor: pointer;
  904. }
  905. }
  906. .action-buttons {
  907. display: flex;
  908. justify-content: flex-end;
  909. align-items: center;
  910. gap: 15rpx;
  911. .action-btn {
  912. padding: 14rpx 30rpx;
  913. border-radius: 25rpx;
  914. font-size: 26rpx;
  915. font-weight: 500;
  916. text-align: center;
  917. &.add-resource-btn {
  918. background: #FF9800;
  919. color: #FFFFFF;
  920. }
  921. &.chat-btn {
  922. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  923. color: #FFFFFF;
  924. }
  925. }
  926. }
  927. }
  928. .loading-wrapper,
  929. .empty-wrapper,
  930. .load-more-wrapper,
  931. .no-more-wrapper {
  932. display: flex;
  933. justify-content: center;
  934. align-items: center;
  935. padding: 40rpx 0;
  936. .loading-text,
  937. .empty-text,
  938. .load-more-text,
  939. .no-more-text {
  940. font-size: 26rpx;
  941. color: #999;
  942. }
  943. }
  944. .empty-text {
  945. color: #666;
  946. }
  947. .no-more-text {
  948. color: #999;
  949. }
  950. </style>