my-resources.vue 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773
  1. <template>
  2. <view class="my-resources">
  3. <!-- 顶部导航栏 -->
  4. <view class="header">
  5. <text class="header-title">我的资源</text>
  6. <view class="header-right">
  7. <text class="dropdown-arrow">▼</text>
  8. </view>
  9. </view>
  10. <!-- 搜索栏 -->
  11. <view class="search-bar">
  12. <view class="search-input-wrapper">
  13. <view class="search-icon-small"></view>
  14. <input type="text" class="search-input" placeholder="请输入姓名、手机号或标签(如:温柔)" v-model="searchKeyword" @input="handleSearch" />
  15. </view>
  16. </view>
  17. <!-- 资源/线索切换标签 -->
  18. <view class="tab-switcher">
  19. <view class="tab-item" :class="{ active: currentTab === 'resource' }" @click="switchToResource">
  20. <text class="tab-text">资源</text>
  21. <text class="tab-count">({{ registeredTotal }})</text>
  22. </view>
  23. <view class="tab-item" :class="{ active: currentTab === 'clue' }" @click="switchToClue">
  24. <text class="tab-text">线索</text>
  25. <text class="tab-count">({{ unregisteredTotal }})</text>
  26. </view>
  27. </view>
  28. <scroll-view scroll-y class="content" @scrolltolower="handleScrollToLower" :lower-threshold="50" :enable-back-to-top="true" :scroll-with-animation="true">
  29. <!-- 资源部分(已注册用户) -->
  30. <view class="resource-section" v-if="currentTab === 'resource'">
  31. <view class="section-header" v-if="registeredResources.length > 0">
  32. <text class="section-title">资源</text>
  33. <text class="section-count">({{ registeredTotal }})</text>
  34. </view>
  35. <view class="resource-list-container">
  36. <view class="resource-item" v-for="(item, index) in getRegisteredDisplayData" :key="getRegisteredItemKey(item, index)" @click="handleResourceClickSafe(item, index)">
  37. <!-- 右上角选中图标(左移) -->
  38. <view class="select-icon">✓-</view>
  39. <!-- 编辑按钮 -->
  40. <view class="edit-icon" @click.stop="handleEdit(item.id || item.resourceId || item.resource_id)">
  41. <text class="edit-text">编辑</text>
  42. </view>
  43. <view class="resource-header">
  44. <image
  45. :src="item.avatar"
  46. mode="aspectFill"
  47. class="resource-avatar"
  48. @error="handleImageErrorRegistered(index)"
  49. @load="handleImageLoadRegistered(index)"
  50. :lazy-load="true"
  51. ></image>
  52. <view class="resource-basic-info">
  53. <view class="name-gender">
  54. <text class="resource-name">{{ item.name }}</text>
  55. <text class="resource-gender gender-tag">{{ item.gender }}</text>
  56. </view>
  57. <view class="status-tag-wrapper">
  58. <text class="status-tag register-tag registered">已注册</text>
  59. <text class="status-tag match-tag" :class="{ 'matched': item.isMatch === 1, 'unmatched': item.isMatch === 0 || !item.isMatch }">
  60. {{ item.isMatch === 1 ? '已匹配' : '未匹配' }}
  61. </text>
  62. </view>
  63. </view>
  64. </view>
  65. <view class="resource-details">
  66. <view class="labels">
  67. <text class="label" v-for="(label, idx) in item.labels" :key="idx">{{ label }}</text>
  68. </view>
  69. <view class="requirement-box">
  70. <text class="requirement-label">择偶要求:</text>
  71. <text class="requirement-content">{{ item.requirement }}</text>
  72. </view>
  73. <view class="contact-box">
  74. <text class="contact-label">联系方式:</text>
  75. <text class="contact-number">{{ item.contact }}</text>
  76. <text class="copy-btn" @click.stop="handleCopyContact(item.originalPhone || item.contact)">复制</text>
  77. </view>
  78. <view class="action-buttons">
  79. <view class="delete-btn" @click.stop="handleDelete(item.id || item.resourceId || item.resource_id)">删除</view>
  80. <view class="match-btn" @click.stop="handleMatch(item.id || item.resourceId || item.resource_id)">精准匹配</view>
  81. </view>
  82. </view>
  83. <!-- 优质资源标签 -->
  84. <view class="quality-tag" v-if="item.isQuality">
  85. <text class="quality-star">★</text>
  86. <text class="quality-text">优质资源</text>
  87. </view>
  88. </view>
  89. </view>
  90. <!-- 加载更多提示 -->
  91. <view class="load-more-tip" v-if="registeredResources.length < registeredTotal && currentTab === 'resource'">
  92. <text class="load-more-text">下拉加载更多</text>
  93. </view>
  94. <!-- 没有更多数据提示 -->
  95. <view class="no-more-tip" v-if="registeredResources.length >= registeredTotal && registeredTotal > 0 && currentTab === 'resource'">
  96. <text class="no-more-text">已加载全部数据</text>
  97. </view>
  98. </view>
  99. <!-- 线索部分(未注册用户) -->
  100. <view class="resource-section" v-if="currentTab === 'clue'">
  101. <view class="section-header" v-if="unregisteredResources.length > 0">
  102. <text class="section-title">线索</text>
  103. <text class="section-count">({{ unregisteredTotal }})</text>
  104. </view>
  105. <view class="resource-list-container">
  106. <view class="resource-item" v-for="(item, index) in getUnregisteredDisplayData" :key="getUnregisteredItemKey(item, index)" @click="handleResourceClickSafe(item, index)">
  107. <!-- 右上角选中图标(左移) -->
  108. <view class="select-icon">✓-</view>
  109. <!-- 编辑按钮 -->
  110. <view class="edit-icon" @click.stop="handleEdit(item.id || item.resourceId || item.resource_id)">
  111. <text class="edit-text">编辑</text>
  112. </view>
  113. <view class="resource-header">
  114. <image
  115. :src="item.avatar"
  116. mode="aspectFill"
  117. class="resource-avatar"
  118. @error="handleImageErrorUnregistered(index)"
  119. @load="handleImageLoadUnregistered(index)"
  120. :lazy-load="true"
  121. ></image>
  122. <view class="resource-basic-info">
  123. <view class="name-gender">
  124. <text class="resource-name">{{ item.name }}</text>
  125. <text class="resource-gender gender-tag">{{ item.gender }}</text>
  126. </view>
  127. <view class="status-tag-wrapper">
  128. <text class="status-tag register-tag unregistered">未注册</text>
  129. <!-- 审核状态标签(仅线索列表显示) -->
  130. <text class="status-tag audit-tag" :class="{ 'audit-pending': item.status === 0 || item.status === null || item.status === undefined, 'audit-approved': item.status === 1, 'audit-rejected': item.status === 2 }">
  131. {{ item.status === 0 || item.status === null || item.status === undefined ? '待审核' : (item.status === 1 ? '审核通过' : (item.status === 2 ? '审核未通过' : '待审核')) }}
  132. </text>
  133. </view>
  134. </view>
  135. </view>
  136. <view class="resource-details">
  137. <view class="labels">
  138. <text class="label" v-for="(label, idx) in item.labels" :key="idx">{{ label }}</text>
  139. </view>
  140. <!-- 未审核通过原因 -->
  141. <view class="reject-reason" v-if="item.status === 2 && item.rejectReason">
  142. <text class="reject-reason-text">审核未通过原因:{{ item.rejectReason }}</text>
  143. </view>
  144. <view class="requirement-box">
  145. <text class="requirement-label">择偶要求:</text>
  146. <text class="requirement-content">{{ item.requirement }}</text>
  147. </view>
  148. <view class="contact-box">
  149. <text class="contact-label">联系方式:</text>
  150. <text class="contact-number">{{ item.contact }}</text>
  151. <text class="copy-btn" @click.stop="handleCopyContact(item.originalPhone || item.contact)">复制</text>
  152. </view>
  153. <view class="action-buttons">
  154. <view class="delete-btn" @click.stop="handleDelete(item.id || item.resourceId || item.resource_id)">删除</view>
  155. <view class="match-btn" @click.stop="handleMatch(item.id || item.resourceId || item.resource_id)">精准匹配</view>
  156. </view>
  157. </view>
  158. <!-- 优质资源标签 -->
  159. <view class="quality-tag" v-if="item.isQuality">
  160. <text class="quality-star">★</text>
  161. <text class="quality-text">优质资源</text>
  162. </view>
  163. </view>
  164. </view>
  165. <!-- 加载更多提示 -->
  166. <view class="load-more-tip" v-if="unregisteredResources.length < unregisteredTotal && currentTab === 'clue'">
  167. <text class="load-more-text">下拉加载更多</text>
  168. </view>
  169. <!-- 没有更多数据提示 -->
  170. <view class="no-more-tip" v-if="unregisteredResources.length >= unregisteredTotal && unregisteredTotal > 0 && currentTab === 'clue'">
  171. <text class="no-more-text">已加载全部数据</text>
  172. </view>
  173. </view>
  174. <!-- 空状态 -->
  175. <view class="empty-state" v-if="(currentTab === 'resource' && registeredTotal === 0 && registeredResources.length === 0) || (currentTab === 'clue' && unregisteredTotal === 0 && unregisteredResources.length === 0)">
  176. <text class="empty-text">{{ currentTab === 'resource' ? '暂无资源' : '暂无线索' }}</text>
  177. </view>
  178. </scroll-view>
  179. <!-- 底部添加按钮 -->
  180. <view class="add-button" @click="handleAdd">
  181. <text class="add-button-icon">+</text>
  182. </view>
  183. <!-- 底部导航 -->
  184. <view class="tabbar">
  185. <view class="tabbar-item" @click="navigateToWorkbench">
  186. <view class="tabbar-icon home"></view>
  187. <text class="tabbar-text">工作台</text>
  188. </view>
  189. <view class="tabbar-item active" @click="navigateToMyResources">
  190. <view class="tabbar-icon resources"></view>
  191. <text class="tabbar-text">我的资源</text>
  192. </view>
  193. <view class="tabbar-item" @click="navigateToRanking">
  194. <view class="tabbar-icon trophy"></view>
  195. <text class="tabbar-text">排行榜</text>
  196. </view>
  197. <!-- <view class="tabbar-item" @click="navigateToMessage">
  198. <view class="tabbar-icon message">
  199. <view v-if="unreadCount > 0" class="badge">{{ unreadCount }}</view>
  200. </view>
  201. <text class="tabbar-text">消息</text>
  202. </view> -->
  203. <view class="tabbar-item" @click="navigateToMine">
  204. <view class="tabbar-icon mine"></view>
  205. <text class="tabbar-text">我的</text>
  206. </view>
  207. </view>
  208. </view>
  209. </template>
  210. <script>
  211. import api from '@/utils/api.js'
  212. export default {
  213. data() {
  214. return {
  215. searchKeyword: '',
  216. isFirstLoad: true, // 标记是否为首次加载
  217. currentTab: 'resource', // 当前选中的标签:'resource' 或 'clue'
  218. resources: [], // 保留用于兼容
  219. registeredResources: [], // 已注册用户(资源部分)- 已加载的数据
  220. unregisteredResources: [], // 未注册用户(线索部分)- 已加载的数据
  221. allRegisteredResources: [], // 已注册用户(资源部分)- 全部数据(从后端获取)
  222. allUnregisteredResources: [], // 未注册用户(线索部分)- 全部数据(从后端获取)
  223. refreshTimer: null, // 定时刷新器
  224. // 资源分页
  225. registeredPageNum: 1, // 资源当前页码
  226. registeredPageSize: 3, // 资源每页显示数量
  227. registeredTotal: 0, // 资源总数
  228. registeredLoading: false, // 资源加载中标志
  229. // 线索分页
  230. unregisteredPageNum: 1, // 线索当前页码
  231. unregisteredPageSize: 3, // 线索每页显示数量
  232. unregisteredTotal: 0, // 线索总数
  233. unregisteredLoading: false // 线索加载中标志
  234. }
  235. },
  236. computed: {
  237. // 全局未读消息数
  238. unreadCount() {
  239. return this.$store.getters.getTotalUnread || 0
  240. },
  241. // 获取资源已加载的数据(用于显示)
  242. getRegisteredDisplayData() {
  243. const data = (this.registeredResources || []).filter(item => {
  244. const isValid = item && item.id !== undefined && item.id !== null
  245. if (!isValid) {
  246. }
  247. return isValid
  248. })
  249. return data
  250. },
  251. // 获取线索已加载的数据(用于显示)
  252. getUnregisteredDisplayData() {
  253. const data = (this.unregisteredResources || []).filter(item => {
  254. const isValid = item && item.id !== undefined && item.id !== null
  255. if (!isValid) {
  256. }
  257. return isValid
  258. })
  259. }
  260. },
  261. onLoad() {
  262. // 加载我的资源数据
  263. this.loadMyResources()
  264. this.isFirstLoad = false
  265. // 监听刷新事件
  266. uni.$on('refreshResourceList', () => {
  267. this.loadMyResources()
  268. })
  269. // 启动定时刷新,每30秒检查一次用户注册状态变化
  270. this.startAutoRefresh()
  271. },
  272. onShow() {
  273. // 页面显示时刷新列表(从其他页面返回时,非首次加载)
  274. if (!this.isFirstLoad) {
  275. this.loadMyResources()
  276. }
  277. // 重新启动定时刷新
  278. this.startAutoRefresh()
  279. },
  280. onHide() {
  281. // 页面隐藏时停止定时刷新
  282. this.stopAutoRefresh()
  283. },
  284. onUnload() {
  285. // 页面卸载时移除事件监听和停止定时刷新
  286. uni.$off('refreshResourceList')
  287. this.stopAutoRefresh()
  288. },
  289. methods: {
  290. // 获取审核状态文本
  291. getAuditStatusText(status) {
  292. if (status === null || status === undefined) {
  293. return '待审核'
  294. }
  295. switch (status) {
  296. case 0:
  297. return '待审核'
  298. case 1:
  299. return '审核通过'
  300. case 2:
  301. return '审核未通过'
  302. default:
  303. return '待审核'
  304. }
  305. },
  306. // 获取审核状态样式类(返回对象,用于:class绑定)
  307. getAuditStatusClass(status) {
  308. if (status === null || status === undefined) {
  309. return { 'audit-pending': true }
  310. }
  311. switch (status) {
  312. case 0:
  313. return { 'audit-pending': true }
  314. case 1:
  315. return { 'audit-approved': true }
  316. case 2:
  317. return { 'audit-rejected': true }
  318. default:
  319. return { 'audit-pending': true }
  320. }
  321. },
  322. // 获取资源项的key
  323. getRegisteredItemKey(item, index) {
  324. if (item && item.id) {
  325. return 'registered-' + item.id
  326. }
  327. return 'registered-placeholder-' + index
  328. },
  329. // 获取线索项的key
  330. getUnregisteredItemKey(item, index) {
  331. if (item && item.id) {
  332. return 'unregistered-' + item.id
  333. }
  334. return 'unregistered-placeholder-' + index
  335. },
  336. // 切换到资源标签
  337. switchToResource() {
  338. this.currentTab = 'resource'
  339. // 切换标签时重置已加载数据
  340. this.registeredPageNum = 1
  341. this.loadRegisteredPage(1)
  342. },
  343. // 切换到线索标签
  344. switchToClue() {
  345. this.currentTab = 'clue'
  346. // 切换标签时重置已加载数据
  347. this.unregisteredPageNum = 1
  348. this.loadUnregisteredPage(1)
  349. },
  350. // 滚动到底部触发加载更多
  351. handleScrollToLower(e) {
  352. if (this.currentTab === 'resource') {
  353. // 资源列表加载更多
  354. if (!this.registeredLoading) {
  355. // 如果已加载数量小于总数,继续加载
  356. if (this.registeredTotal > 0 && this.registeredResources.length < this.registeredTotal) {
  357. this.loadMoreRegistered()
  358. } else if (this.registeredTotal === 0) {
  359. this.loadMoreRegistered()
  360. }
  361. }
  362. } else {
  363. if (!this.unregisteredLoading) {
  364. // 如果已加载数量小于总数,继续加载
  365. if (this.unregisteredTotal > 0 && this.unregisteredResources.length < this.unregisteredTotal) {
  366. this.loadMoreUnregistered()
  367. } else if (this.unregisteredTotal === 0) {
  368. this.loadMoreUnregistered()
  369. }
  370. }
  371. }
  372. },
  373. // 加载资源指定页数据(从后端加载,使用isUser参数过滤已注册用户)
  374. async loadRegisteredPage(pageNum) {
  375. if (this.registeredLoading) return
  376. this.registeredLoading = true
  377. this.registeredPageNum = pageNum
  378. try {
  379. // 获取当前登录用户ID
  380. const userInfo = uni.getStorageSync('userInfo') || {}
  381. const userId = uni.getStorageSync('userId')
  382. let currentUserId = userInfo.userId || userId || null
  383. if (currentUserId !== null && currentUserId !== undefined) {
  384. currentUserId = parseInt(currentUserId)
  385. if (isNaN(currentUserId) || currentUserId <= 0) {
  386. currentUserId = null
  387. }
  388. }
  389. if (!currentUserId) {
  390. this.registeredResources = []
  391. this.registeredLoading = false
  392. return
  393. }
  394. const baseUrl = process.env.NODE_ENV === 'development'
  395. ? 'https://api.zhongruanke.cn/api'
  396. : 'https://your-domain.com/api'
  397. // 构建查询参数,使用isUser=1参数只获取已注册用户
  398. let url = `${baseUrl}/my-resource/list?currentUserId=${currentUserId}&pageNum=${pageNum}&pageSize=${this.registeredPageSize}&isUser=1`
  399. if (this.searchKeyword && this.searchKeyword.trim()) {
  400. url += `&keyword=${encodeURIComponent(this.searchKeyword.trim())}`
  401. }
  402. const [error, res] = await uni.request({
  403. url: url,
  404. method: 'GET'
  405. })
  406. if (error) {
  407. this.registeredResources = []
  408. this.registeredLoading = false
  409. return
  410. }
  411. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  412. const pageData = res.data.data
  413. if (pageData && pageData.records) {
  414. // 转换数据格式,并过滤掉无效数据(null值)
  415. const resources = this.convertResourceData(pageData.records).filter(item => item !== null && item !== undefined)
  416. // 如果是第一页,直接替换;否则追加
  417. if (pageNum === 1) {
  418. this.registeredResources = resources
  419. } else {
  420. this.registeredResources = [...this.registeredResources, ...resources]
  421. }
  422. // 更新总数(使用后端返回的total)
  423. this.registeredTotal = pageData.total || 0
  424. } else {
  425. if (pageNum === 1) {
  426. this.registeredResources = []
  427. this.registeredTotal = 0
  428. }
  429. }
  430. } else {
  431. }
  432. } catch (e) {
  433. if (this.registeredPageNum === 1) {
  434. this.registeredResources = []
  435. }
  436. } finally {
  437. this.registeredLoading = false
  438. }
  439. },
  440. // 加载线索指定页数据(从后端加载,使用isUser参数过滤未注册用户)
  441. async loadUnregisteredPage(pageNum) {
  442. if (this.unregisteredLoading) return
  443. this.unregisteredLoading = true
  444. this.unregisteredPageNum = pageNum
  445. try {
  446. // 获取当前登录用户ID
  447. const userInfo = uni.getStorageSync('userInfo') || {}
  448. const userId = uni.getStorageSync('userId')
  449. let currentUserId = userInfo.userId || userId || null
  450. if (currentUserId !== null && currentUserId !== undefined) {
  451. currentUserId = parseInt(currentUserId)
  452. if (isNaN(currentUserId) || currentUserId <= 0) {
  453. currentUserId = null
  454. }
  455. }
  456. if (!currentUserId) {
  457. this.unregisteredResources = []
  458. this.unregisteredLoading = false
  459. return
  460. }
  461. const baseUrl = process.env.NODE_ENV === 'development'
  462. ? 'https://api.zhongruanke.cn/api'
  463. : 'https://your-domain.com/api'
  464. // 构建查询参数,使用isUser=0参数只获取未注册用户(线索)
  465. let url = `${baseUrl}/my-resource/list?currentUserId=${currentUserId}&pageNum=${pageNum}&pageSize=${this.unregisteredPageSize}&isUser=0`
  466. if (this.searchKeyword && this.searchKeyword.trim()) {
  467. url += `&keyword=${encodeURIComponent(this.searchKeyword.trim())}`
  468. }
  469. const [error, res] = await uni.request({
  470. url: url,
  471. method: 'GET'
  472. })
  473. if (error) {
  474. this.unregisteredResources = []
  475. this.unregisteredLoading = false
  476. return
  477. }
  478. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  479. const pageData = res.data.data
  480. if (pageData && pageData.records) {
  481. // 转换数据格式,并过滤掉无效数据(null值)
  482. const resources = this.convertResourceData(pageData.records).filter(item => item !== null && item !== undefined)
  483. // 如果是第一页,直接替换;否则追加
  484. if (pageNum === 1) {
  485. this.unregisteredResources = resources
  486. } else {
  487. this.unregisteredResources = [...this.unregisteredResources, ...resources]
  488. }
  489. // 更新总数(使用后端返回的total)
  490. this.unregisteredTotal = pageData.total || 0
  491. } else {
  492. if (pageNum === 1) {
  493. this.unregisteredResources = []
  494. this.unregisteredTotal = 0
  495. }
  496. }
  497. } else {
  498. }
  499. } catch (e) {
  500. if (this.unregisteredPageNum === 1) {
  501. this.unregisteredResources = []
  502. }
  503. } finally {
  504. this.unregisteredLoading = false
  505. }
  506. },
  507. // 加载更多资源(从后端分页加载)
  508. async loadMoreRegistered() {
  509. if (this.registeredLoading) return
  510. // 如果已加载的数据已经达到或超过估算的总数,不再加载
  511. if (this.registeredResources.length >= this.registeredTotal && this.registeredTotal > 0) return
  512. this.registeredPageNum++
  513. await this.loadRegisteredPage(this.registeredPageNum)
  514. },
  515. // 加载更多线索(从后端分页加载)
  516. async loadMoreUnregistered() {
  517. if (this.unregisteredLoading) return
  518. // 如果已加载的数据已经达到或超过估算的总数,不再加载
  519. if (this.unregisteredResources.length >= this.unregisteredTotal && this.unregisteredTotal > 0) return
  520. this.unregisteredPageNum++
  521. await this.loadUnregisteredPage(this.unregisteredPageNum)
  522. },
  523. // 转换资源数据格式(提取公共方法)
  524. convertResourceData(records) {
  525. return records.map(item => {
  526. let requirement = item.mateSelectionCriteria || item.mate_selection_criteria || ''
  527. if (requirement) {
  528. requirement = requirement.trim()
  529. }
  530. const labels = []
  531. if (item.constellation) {
  532. labels.push(item.constellation)
  533. }
  534. if (item.occupation) {
  535. labels.push(item.occupation)
  536. }
  537. let avatarUrl = item.avatarUrl || item.avatar_url || item.avatar || ''
  538. if (!avatarUrl || avatarUrl.trim() === '' || avatarUrl === 'null' || avatarUrl === null || avatarUrl === undefined) {
  539. avatarUrl = ''
  540. } else {
  541. avatarUrl = avatarUrl.trim()
  542. }
  543. let resourceId = item.resourceId || item.resource_id
  544. if (resourceId === null || resourceId === undefined || resourceId === 'undefined' || resourceId === 'null') {
  545. return null
  546. }
  547. resourceId = parseInt(resourceId)
  548. if (isNaN(resourceId) || resourceId <= 0) {
  549. return null
  550. }
  551. let isUser = item.isUser !== null && item.isUser !== undefined ? item.isUser :
  552. (item.is_user !== null && item.is_user !== undefined ? item.is_user : 0)
  553. isUser = parseInt(isUser) || 0
  554. let userId = item.userId !== null && item.userId !== undefined ? item.userId :
  555. (item.user_id !== null && item.user_id !== undefined ? item.user_id : null)
  556. if (userId !== null && userId !== undefined) {
  557. userId = parseInt(userId)
  558. if (isNaN(userId) || userId <= 0) {
  559. userId = null
  560. }
  561. }
  562. let isMatch = item.isMatch !== null && item.isMatch !== undefined ? item.isMatch :
  563. (item.is_match !== null && item.is_match !== undefined ? item.is_match : 0)
  564. isMatch = parseInt(isMatch) || 0
  565. let resourceTags = []
  566. if (item.tags && Array.isArray(item.tags) && item.tags.length > 0) {
  567. resourceTags = item.tags
  568. } else if (labels && labels.length > 0) {
  569. resourceTags = labels
  570. }
  571. return {
  572. id: resourceId,
  573. resourceId: resourceId,
  574. resource_id: resourceId,
  575. avatar: avatarUrl,
  576. name: item.name || '',
  577. gender: item.gender === 1 ? '男' : item.gender === 2 ? '女' : '未知',
  578. status: item.status !== undefined && item.status !== null ? item.status : 0,
  579. rejectReason: item.rejectReason || item.reject_reason || '',
  580. isUser: isUser,
  581. userId: userId,
  582. isMatch: isMatch,
  583. isPlus: false,
  584. labels: resourceTags,
  585. requirement: requirement || '暂无要求',
  586. contact: item.phone ? item.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '',
  587. originalPhone: item.phone || '',
  588. isQuality: false
  589. }
  590. }).filter(item => item !== null)
  591. },
  592. // 加载我的资源数据(改为分页加载)
  593. async loadMyResources() {
  594. try {
  595. // 重置分页
  596. this.registeredPageNum = 1
  597. this.unregisteredPageNum = 1
  598. // 同时加载资源和线索,进入页面即可两端都查询完毕
  599. await Promise.all([
  600. this.loadRegisteredPage(1),
  601. this.loadUnregisteredPage(1)
  602. ])
  603. } catch (e) {
  604. }
  605. },
  606. // 搜索
  607. async handleSearch() {
  608. // 重置分页到第一页
  609. this.registeredPageNum = 1
  610. this.unregisteredPageNum = 1
  611. // 重新加载资源数据,包含搜索关键词
  612. await this.loadMyResources()
  613. },
  614. // 删除资源
  615. async handleDelete(id) {
  616. // 如果id无效,尝试从当前资源列表中查找
  617. if (id === null || id === undefined || id === 'undefined' || id === 'null' || id === '') {
  618. // 尝试从当前显示的资源中找到对应的资源
  619. const allDisplayData = this.currentTab === 'resource'
  620. ? this.getRegisteredDisplayData
  621. : this.getUnregisteredDisplayData
  622. // 如果仍然找不到,显示错误
  623. uni.showToast({
  624. title: '资源ID无效,无法删除',
  625. icon: 'none'
  626. })
  627. return
  628. }
  629. // 确保id是有效的整数
  630. let resourceId = parseInt(id)
  631. if (isNaN(resourceId) || resourceId <= 0) {
  632. // 如果解析失败,尝试从字符串中提取数字
  633. const match = String(id).match(/\d+/)
  634. if (match) {
  635. resourceId = parseInt(match[0])
  636. }
  637. if (isNaN(resourceId) || resourceId <= 0) {
  638. uni.showToast({
  639. title: '资源ID格式错误,无法删除',
  640. icon: 'none'
  641. })
  642. return
  643. }
  644. }
  645. uni.showModal({
  646. title: '删除确认',
  647. content: '确定要删除该资源吗?',
  648. success: async (res) => {
  649. if (res.confirm) {
  650. try {
  651. uni.showLoading({
  652. title: '删除中...'
  653. })
  654. const baseUrl = process.env.NODE_ENV === 'development'
  655. ? 'https://api.zhongruanke.cn/api' // 开发环境 - 通过网关
  656. : 'https://your-domain.com/api' // 生产环境
  657. const [error, deleteRes] = await uni.request({
  658. url: `${baseUrl}/my-resource/delete/${resourceId}`,
  659. method: 'DELETE'
  660. })
  661. uni.hideLoading()
  662. if (error) {
  663. uni.showToast({
  664. title: '删除失败',
  665. icon: 'none'
  666. })
  667. return
  668. }
  669. if (deleteRes.statusCode === 200 && deleteRes.data && deleteRes.data.code === 200) {
  670. uni.showToast({
  671. title: '删除成功',
  672. icon: 'success'
  673. })
  674. // 删除成功后刷新资源列表
  675. await this.loadMyResources()
  676. } else {
  677. uni.showToast({
  678. title: deleteRes.data.message || '删除失败',
  679. icon: 'none'
  680. })
  681. }
  682. } catch (e) {
  683. uni.hideLoading()
  684. uni.showToast({
  685. title: '删除失败,请稍后重试',
  686. icon: 'none'
  687. })
  688. }
  689. }
  690. }
  691. })
  692. },
  693. // 精准匹配
  694. // 编辑资源
  695. handleEdit(id) {
  696. // 跳转到资源录入页面(编辑模式),传递资源ID
  697. uni.navigateTo({
  698. url: `/pages/matchmaker-workbench/resource-input?resourceId=${id}`,
  699. success: () => {
  700. },
  701. fail: (err) => {
  702. uni.showToast({
  703. title: '跳转失败',
  704. icon: 'none'
  705. })
  706. }
  707. })
  708. },
  709. handleMatch(id) {
  710. // 查找资源项
  711. const allResources = [...this.registeredResources, ...this.unregisteredResources]
  712. const resource = allResources.find(item => item.id === id)
  713. if (!resource) {
  714. uni.showToast({
  715. title: '资源不存在',
  716. icon: 'none'
  717. })
  718. return
  719. }
  720. // 检查用户是否已注册(isUser === 1 且 userId !== null)
  721. if (resource.isUser !== 1 || resource.userId === null || resource.userId === undefined) {
  722. uni.showToast({
  723. title: '该用户还未注册用户端,注册之后才能进行匹配',
  724. icon: 'none',
  725. duration: 3000
  726. })
  727. return
  728. }
  729. // 已注册用户,跳转到精准匹配页面
  730. uni.navigateTo({
  731. url: `/pages/matchmaker-workbench/precise-match?resourceId=${id}`
  732. })
  733. },
  734. // 复制联系方式
  735. handleCopyContact(contact) {
  736. if (!contact) {
  737. uni.showToast({
  738. title: '联系方式为空',
  739. icon: 'none'
  740. })
  741. return
  742. }
  743. // 如果是脱敏的手机号,需要从原始数据中获取完整号码
  744. // 这里需要根据实际情况调整,可能需要从item中获取原始phone
  745. // 暂时先复制显示的文本
  746. uni.setClipboardData({
  747. data: contact,
  748. success: () => {
  749. uni.showToast({
  750. title: '已复制到剪贴板',
  751. icon: 'success'
  752. })
  753. },
  754. fail: () => {
  755. uni.showToast({
  756. title: '复制失败',
  757. icon: 'none'
  758. })
  759. }
  760. })
  761. },
  762. // 客户点击事件,跳转到客户详情页面
  763. handleClientClick(id) {
  764. uni.navigateTo({
  765. url: `/pages/matchmaker-workbench/client-detail?resourceId=${id}`
  766. })
  767. },
  768. // 资源项点击事件(安全包装方法)
  769. handleResourceClickSafe(item, index) {
  770. // 如果 item 无效,尝试从数组中获取
  771. if (!item || item === null || item === undefined) {
  772. const dataArray = this.currentTab === 'resource' ? this.getRegisteredDisplayData : this.getUnregisteredDisplayData
  773. if (dataArray && Array.isArray(dataArray) && index >= 0 && index < dataArray.length) {
  774. item = dataArray[index]
  775. }
  776. }
  777. // 如果仍然无效,直接返回
  778. if (!item || item === null || item === undefined) {
  779. return
  780. }
  781. // 调用实际的点击处理方法
  782. this.handleResourceClick(item)
  783. },
  784. // 资源项点击事件
  785. handleResourceClick(item) {
  786. // 检查 item 是否存在
  787. if (!item || item === null || item === undefined) {
  788. return
  789. }
  790. // 检查 item.id 是否存在
  791. if (!item.id && item.id !== 0) {
  792. uni.showToast({
  793. title: '资源ID无效',
  794. icon: 'none',
  795. duration: 2000
  796. })
  797. return
  798. }
  799. // 判断是否为已注册用户(isUser === 1 且 userId !== null)
  800. if (item.isUser === 1 && item.userId !== null && item.userId !== undefined) {
  801. // 已注册,跳转到客户详情页面
  802. const resourceId = item.id
  803. uni.navigateTo({
  804. url: `/pages/matchmaker-workbench/client-detail?resourceId=${resourceId}`,
  805. success: () => {
  806. },
  807. fail: (err) => {
  808. uni.showToast({
  809. title: '跳转失败,请重试',
  810. icon: 'none'
  811. })
  812. }
  813. })
  814. } else {
  815. // 未注册,提示用户
  816. uni.showToast({
  817. title: '该用户还未注册用户端',
  818. icon: 'none',
  819. duration: 2000
  820. })
  821. }
  822. },
  823. // 添加资源
  824. handleAdd() {
  825. // 跳转到信息录入页面
  826. uni.navigateTo({
  827. url: '/pages/matchmaker-workbench/resource-input',
  828. success: () => {
  829. },
  830. fail: (err) => {
  831. uni.showToast({
  832. title: '页面跳转失败',
  833. icon: 'none'
  834. })
  835. }
  836. })
  837. },
  838. // 导航到工作台
  839. navigateToWorkbench() {
  840. uni.navigateTo({
  841. url: '/pages/matchmaker-workbench/index'
  842. })
  843. },
  844. // 导航到我的资源
  845. navigateToMyResources() {
  846. // 已在我的资源页面,无需跳转
  847. },
  848. // 导航到排行榜
  849. navigateToRanking() {
  850. uni.navigateTo({
  851. url: '/pages/matchmaker-workbench/ranking'
  852. })
  853. },
  854. // 导航到消息
  855. navigateToMessage() {
  856. uni.navigateTo({
  857. url: '/pages/matchmaker-workbench/message'
  858. })
  859. },
  860. // 导航到我的
  861. navigateToMine() {
  862. uni.navigateTo({
  863. url: '/pages/matchmaker-workbench/mine'
  864. })
  865. },
  866. // 启动自动刷新
  867. startAutoRefresh() {
  868. // 清除之前的定时器
  869. this.stopAutoRefresh()
  870. // 每30秒刷新一次,检查用户注册状态变化
  871. this.refreshTimer = setInterval(async () => {
  872. // 先批量检查并更新线索用户的注册状态
  873. await this.batchCheckClueRegistrationStatus()
  874. // 然后刷新资源列表
  875. await this.loadMyResources()
  876. }, 30000) // 30秒
  877. },
  878. // 批量检查并更新线索用户的注册状态
  879. async batchCheckClueRegistrationStatus() {
  880. try {
  881. // 获取红娘ID,优先使用userInfo中的matchmakerId,否则使用userId
  882. const userInfo = uni.getStorageSync('userInfo') || {}
  883. const userId = uni.getStorageSync('userId')
  884. let matchmakerId = userInfo.matchmakerId || userId || null
  885. if (!matchmakerId) {
  886. return
  887. }
  888. // 确保matchmakerId是有效的整数
  889. matchmakerId = parseInt(matchmakerId)
  890. if (isNaN(matchmakerId) || matchmakerId <= 0) {
  891. return
  892. }
  893. const baseUrl = process.env.NODE_ENV === 'development'
  894. ? 'https://api.zhongruanke.cn/api' // 开发环境 - 通过网关
  895. : 'https://your-domain.com/api' // 生产环境
  896. const [error, res] = await uni.request({
  897. url: `${baseUrl}/my-resource/batch-check-clue-registration`,
  898. method: 'POST',
  899. data: {
  900. matchmakerId: matchmakerId
  901. }
  902. })
  903. if (error) {
  904. return
  905. }
  906. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  907. const updateCount = res.data.data?.updateCount || 0
  908. }
  909. } catch (e) {
  910. }
  911. },
  912. // 停止自动刷新
  913. stopAutoRefresh() {
  914. if (this.refreshTimer) {
  915. clearInterval(this.refreshTimer)
  916. this.refreshTimer = null
  917. }
  918. },
  919. // 已注册资源图片加载错误处理
  920. handleImageErrorRegistered(index) {
  921. this.handleImageError(index, 'registered')
  922. },
  923. // 未注册资源图片加载错误处理
  924. handleImageErrorUnregistered(index) {
  925. this.handleImageError(index, 'unregistered')
  926. },
  927. // 已注册资源图片加载成功处理
  928. handleImageLoadRegistered(index) {
  929. this.handleImageLoad(index, 'registered')
  930. },
  931. // 未注册资源图片加载成功处理
  932. handleImageLoadUnregistered(index) {
  933. this.handleImageLoad(index, 'unregistered')
  934. },
  935. // 图片加载错误处理(内部方法)
  936. handleImageError(index, type) {
  937. try {
  938. // 获取当前显示的数据
  939. const displayData = type === 'registered' ? this.getRegisteredDisplayData : this.getUnregisteredDisplayData
  940. const resource = displayData && displayData[index]
  941. if (!resource) {
  942. return
  943. }
  944. const originalUrl = resource.avatar
  945. const resourceId = resource.id
  946. if (originalUrl &&
  947. originalUrl.trim() !== '' &&
  948. !originalUrl.includes('placeholder') &&
  949. !originalUrl.includes('default') &&
  950. !originalUrl.includes('via.placeholder')) {
  951. if (type === 'registered') {
  952. const displayListIndex = this.registeredResources.findIndex(item => item.id === resourceId)
  953. if (displayListIndex !== -1) {
  954. this.$set(this.registeredResources[displayListIndex], 'avatar', '')
  955. }
  956. } else {
  957. const displayListIndex = this.unregisteredResources.findIndex(item => item.id === resourceId)
  958. if (displayListIndex !== -1) {
  959. this.$set(this.unregisteredResources[displayListIndex], 'avatar', '')
  960. }
  961. }
  962. }
  963. } catch (e) {
  964. }
  965. },
  966. // 图片加载成功处理(内部方法)
  967. handleImageLoad(index, type) {
  968. try {
  969. // 获取当前显示的数据
  970. const displayData = type === 'registered' ? this.getRegisteredDisplayData : this.getUnregisteredDisplayData
  971. if (displayData && displayData[index]) {
  972. }
  973. } catch (e) {
  974. }
  975. }
  976. }
  977. }
  978. </script>
  979. <style lang="scss" scoped>
  980. .my-resources {
  981. min-height: 100vh;
  982. background: #F5F5F5;
  983. display: flex;
  984. flex-direction: column;
  985. }
  986. /* 顶部导航栏 */
  987. .header {
  988. display: flex;
  989. align-items: center;
  990. justify-content: space-between;
  991. padding: 20rpx 30rpx;
  992. padding-top: calc(20rpx + env(safe-area-inset-top));
  993. background: #FFFFFF;
  994. border-bottom: 1rpx solid #F0F0F0;
  995. .header-title {
  996. font-size: 36rpx;
  997. font-weight: bold;
  998. color: #9C27B0;
  999. }
  1000. .header-right {
  1001. .dropdown-arrow {
  1002. font-size: 24rpx;
  1003. color: #9C27B0;
  1004. font-weight: normal;
  1005. }
  1006. }
  1007. }
  1008. /* 资源/线索切换标签 */
  1009. .tab-switcher {
  1010. display: flex;
  1011. align-items: center;
  1012. background: #FFFFFF;
  1013. border-bottom: 1rpx solid #F0F0F0;
  1014. padding: 0 30rpx;
  1015. .tab-item {
  1016. flex: 1;
  1017. display: flex;
  1018. align-items: center;
  1019. justify-content: center;
  1020. padding: 25rpx 0;
  1021. position: relative;
  1022. cursor: pointer;
  1023. transition: all 0.3s;
  1024. .tab-text {
  1025. font-size: 32rpx;
  1026. font-weight: 500;
  1027. color: #666;
  1028. margin-right: 8rpx;
  1029. }
  1030. .tab-count {
  1031. font-size: 26rpx;
  1032. color: #999;
  1033. }
  1034. &.active {
  1035. .tab-text {
  1036. color: #9C27B0;
  1037. font-weight: bold;
  1038. }
  1039. .tab-count {
  1040. color: #9C27B0;
  1041. }
  1042. &::after {
  1043. content: '';
  1044. position: absolute;
  1045. bottom: 0;
  1046. left: 0;
  1047. right: 0;
  1048. height: 4rpx;
  1049. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  1050. border-radius: 2rpx 2rpx 0 0;
  1051. }
  1052. }
  1053. &:active {
  1054. opacity: 0.7;
  1055. }
  1056. }
  1057. }
  1058. /* 搜索栏 */
  1059. .search-bar {
  1060. padding: 20rpx;
  1061. background: #F5F5F5;
  1062. .search-input-wrapper {
  1063. display: flex;
  1064. align-items: center;
  1065. background: #FFFFFF;
  1066. border-radius: 30rpx;
  1067. padding: 12rpx 20rpx;
  1068. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  1069. .search-icon-small {
  1070. width: 32rpx;
  1071. height: 32rpx;
  1072. 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>');
  1073. background-size: contain;
  1074. background-repeat: no-repeat;
  1075. background-position: center;
  1076. margin-right: 15rpx;
  1077. }
  1078. .search-input {
  1079. flex: 1;
  1080. font-size: 28rpx;
  1081. color: #333;
  1082. border: none;
  1083. outline: none;
  1084. background: transparent;
  1085. &::placeholder {
  1086. color: #999;
  1087. }
  1088. }
  1089. }
  1090. }
  1091. .content {
  1092. flex: 1;
  1093. height: 0; /* 配合 flex: 1 使用,确保 scroll-view 有明确高度 */
  1094. padding: 0 20rpx 140rpx;
  1095. box-sizing: border-box;
  1096. }
  1097. /* 资源分组 */
  1098. .resource-section {
  1099. margin-bottom: 30rpx;
  1100. min-height: calc(50vh - 100rpx); /* 确保至少占半屏高度 */
  1101. display: flex;
  1102. flex-direction: column;
  1103. .section-header {
  1104. display: flex;
  1105. align-items: center;
  1106. padding: 20rpx 0 15rpx;
  1107. margin-bottom: 10rpx;
  1108. flex-shrink: 0;
  1109. .section-title {
  1110. font-size: 32rpx;
  1111. font-weight: bold;
  1112. color: #9C27B0;
  1113. margin-right: 10rpx;
  1114. }
  1115. .section-count {
  1116. font-size: 26rpx;
  1117. color: #999;
  1118. font-weight: normal;
  1119. }
  1120. }
  1121. /* 资源列表容器,确保有固定高度 */
  1122. .resource-list-container {
  1123. flex: 1;
  1124. display: flex;
  1125. flex-direction: column;
  1126. min-height: calc(50vh - 200rpx);
  1127. }
  1128. }
  1129. /* 加载更多提示 */
  1130. .load-more-tip {
  1131. display: flex;
  1132. justify-content: center;
  1133. align-items: center;
  1134. padding: 30rpx 0;
  1135. .load-more-text {
  1136. font-size: 26rpx;
  1137. color: #999;
  1138. }
  1139. }
  1140. /* 没有更多数据提示 */
  1141. .no-more-tip {
  1142. display: flex;
  1143. justify-content: center;
  1144. align-items: center;
  1145. padding: 30rpx 0;
  1146. .no-more-text {
  1147. font-size: 26rpx;
  1148. color: #999;
  1149. }
  1150. }
  1151. /* 空状态 */
  1152. .empty-state {
  1153. display: flex;
  1154. justify-content: center;
  1155. align-items: center;
  1156. padding: 100rpx 0;
  1157. .empty-text {
  1158. font-size: 28rpx;
  1159. color: #999;
  1160. }
  1161. }
  1162. /* 资源列表 */
  1163. .resource-item {
  1164. background: #FFFFFF;
  1165. border-radius: 20rpx;
  1166. margin-bottom: 20rpx;
  1167. padding: 25rpx;
  1168. position: relative;
  1169. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  1170. /* 右上角选中图标 */
  1171. .select-icon {
  1172. position: absolute;
  1173. top: 20rpx;
  1174. right: 120rpx; /* 左移,为编辑按钮留出空间 */
  1175. width: 40rpx;
  1176. height: 40rpx;
  1177. background: #FF4444;
  1178. color: #FFFFFF;
  1179. border-radius: 50%;
  1180. display: flex;
  1181. align-items: center;
  1182. justify-content: center;
  1183. font-size: 24rpx;
  1184. font-weight: bold;
  1185. z-index: 10;
  1186. }
  1187. .edit-icon {
  1188. position: absolute;
  1189. top: 20rpx;
  1190. right: 20rpx;
  1191. padding: 8rpx 20rpx;
  1192. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  1193. border-radius: 20rpx;
  1194. display: flex;
  1195. align-items: center;
  1196. justify-content: center;
  1197. z-index: 10;
  1198. box-shadow: 0 2rpx 8rpx rgba(156, 39, 176, 0.3);
  1199. .edit-text {
  1200. color: #FFFFFF;
  1201. font-size: 24rpx;
  1202. font-weight: 500;
  1203. }
  1204. }
  1205. .resource-header {
  1206. display: flex;
  1207. align-items: flex-start;
  1208. margin-bottom: 20rpx;
  1209. padding-right: 50rpx;
  1210. .resource-avatar {
  1211. width: 100rpx;
  1212. height: 100rpx;
  1213. border-radius: 8rpx;
  1214. margin-right: 20rpx;
  1215. flex-shrink: 0;
  1216. background-color: #F5F5F5;
  1217. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23CCCCCC"><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>');
  1218. background-size: 60% 60%;
  1219. background-position: center;
  1220. background-repeat: no-repeat;
  1221. }
  1222. .resource-basic-info {
  1223. flex: 1;
  1224. min-width: 0;
  1225. .name-gender {
  1226. display: flex;
  1227. align-items: center;
  1228. gap: 10rpx;
  1229. margin-bottom: 12rpx;
  1230. .resource-name {
  1231. font-size: 30rpx;
  1232. font-weight: bold;
  1233. color: #333;
  1234. }
  1235. .resource-gender {
  1236. font-size: 26rpx;
  1237. color: #666;
  1238. &.gender-tag {
  1239. display: inline-block;
  1240. padding: 4rpx 12rpx;
  1241. background: #F3E5F5;
  1242. color: #9C27B0;
  1243. border-radius: 12rpx;
  1244. font-size: 22rpx;
  1245. font-weight: 500;
  1246. }
  1247. }
  1248. }
  1249. .status-tag-wrapper {
  1250. display: flex;
  1251. align-items: center;
  1252. .status-tag {
  1253. display: inline-block;
  1254. padding: 4rpx 12rpx;
  1255. border-radius: 12rpx;
  1256. font-size: 22rpx;
  1257. font-weight: 500;
  1258. margin-right: 8rpx;
  1259. &.approved {
  1260. background: #E3F2FD;
  1261. color: #2196F3;
  1262. }
  1263. &.pending {
  1264. background: #FFF3E0;
  1265. color: #FF9800;
  1266. }
  1267. &.register-tag {
  1268. &.registered {
  1269. background: #E8F5E9;
  1270. color: #4CAF50;
  1271. }
  1272. &.unregistered {
  1273. background: #FCE4EC;
  1274. color: #E91E63;
  1275. }
  1276. }
  1277. &.match-tag {
  1278. margin-left: 10rpx;
  1279. &.matched {
  1280. background: #E3F2FD;
  1281. color: #2196F3;
  1282. }
  1283. &.unmatched {
  1284. background: #FFF3E0;
  1285. color: #FF9800;
  1286. }
  1287. }
  1288. /* 审核状态标签样式 */
  1289. &.audit-tag {
  1290. margin-left: 10rpx;
  1291. /* 待审核 - 橙色/黄色 */
  1292. &.audit-pending {
  1293. background: #FFF3E0;
  1294. color: #FF9800;
  1295. border: 1rpx solid #FFB74D;
  1296. }
  1297. /* 审核通过 - 绿色 */
  1298. &.audit-approved {
  1299. background: #E8F5E9;
  1300. color: #4CAF50;
  1301. border: 1rpx solid #81C784;
  1302. }
  1303. /* 审核未通过 - 红色 */
  1304. &.audit-rejected {
  1305. background: #FFEBEE;
  1306. color: #F44336;
  1307. border: 1rpx solid #E57373;
  1308. }
  1309. }
  1310. }
  1311. }
  1312. }
  1313. }
  1314. .resource-details {
  1315. .labels {
  1316. display: flex;
  1317. flex-wrap: wrap;
  1318. gap: 10rpx;
  1319. margin-bottom: 15rpx;
  1320. .label {
  1321. display: inline-block;
  1322. padding: 6rpx 14rpx;
  1323. background: #F3E5F5;
  1324. color: #9C27B0;
  1325. border-radius: 15rpx;
  1326. font-size: 22rpx;
  1327. font-weight: 500;
  1328. }
  1329. }
  1330. .requirement-box {
  1331. background: #F3E5F5;
  1332. border-radius: 12rpx;
  1333. padding: 16rpx 20rpx;
  1334. margin-bottom: 15rpx;
  1335. display: flex;
  1336. align-items: center;
  1337. flex-wrap: wrap;
  1338. .requirement-label {
  1339. font-size: 28rpx;
  1340. color: #7B1FA2;
  1341. font-weight: 500;
  1342. margin-right: 8rpx;
  1343. white-space: nowrap;
  1344. }
  1345. .requirement-content {
  1346. font-size: 28rpx;
  1347. color: #7B1FA2;
  1348. font-weight: 400;
  1349. }
  1350. }
  1351. .contact-box {
  1352. margin-bottom: 20rpx;
  1353. display: flex;
  1354. align-items: center;
  1355. flex-wrap: wrap;
  1356. .contact-label {
  1357. font-size: 26rpx;
  1358. color: #333;
  1359. font-weight: 500;
  1360. margin-right: 10rpx;
  1361. white-space: nowrap;
  1362. }
  1363. .contact-number {
  1364. flex: 1;
  1365. font-size: 26rpx;
  1366. color: #333;
  1367. font-weight: 400;
  1368. min-width: 0;
  1369. }
  1370. .copy-btn {
  1371. font-size: 24rpx;
  1372. color: #9C27B0;
  1373. font-weight: 500;
  1374. margin-left: 15rpx;
  1375. white-space: nowrap;
  1376. cursor: pointer;
  1377. }
  1378. }
  1379. .action-buttons {
  1380. display: flex;
  1381. justify-content: space-between;
  1382. align-items: center;
  1383. gap: 15rpx;
  1384. .delete-btn {
  1385. flex: 1;
  1386. padding: 14rpx 0;
  1387. background: #FFEBEE;
  1388. color: #E91E63;
  1389. border-radius: 25rpx;
  1390. font-size: 26rpx;
  1391. font-weight: 500;
  1392. text-align: center;
  1393. }
  1394. .match-btn {
  1395. flex: 1.5;
  1396. padding: 14rpx 0;
  1397. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  1398. color: #FFFFFF;
  1399. border-radius: 25rpx;
  1400. font-size: 26rpx;
  1401. font-weight: 500;
  1402. text-align: center;
  1403. }
  1404. }
  1405. }
  1406. /* 优质资源标签 */
  1407. .quality-tag {
  1408. position: absolute;
  1409. top: 80rpx;
  1410. right: 20rpx;
  1411. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  1412. color: #FFFFFF;
  1413. font-size: 22rpx;
  1414. font-weight: bold;
  1415. padding: 8rpx 16rpx;
  1416. border-radius: 20rpx;
  1417. display: flex;
  1418. align-items: center;
  1419. gap: 4rpx;
  1420. box-shadow: 0 2rpx 8rpx rgba(156, 39, 176, 0.3);
  1421. z-index: 5;
  1422. .quality-star {
  1423. font-size: 20rpx;
  1424. }
  1425. .quality-text {
  1426. font-size: 22rpx;
  1427. }
  1428. }
  1429. }
  1430. /* 审核未通过原因 */
  1431. .reject-reason {
  1432. margin-top: 12rpx;
  1433. padding: 14rpx 16rpx;
  1434. background: #FFF3E0;
  1435. border: 1rpx solid #FFCC80;
  1436. border-radius: 10rpx;
  1437. color: #E65100;
  1438. font-size: 24rpx;
  1439. line-height: 1.5;
  1440. }
  1441. /* 添加按钮 */
  1442. .add-button {
  1443. position: fixed;
  1444. bottom: 130rpx;
  1445. right: 40rpx;
  1446. width: 100rpx;
  1447. height: 100rpx;
  1448. border-radius: 50%;
  1449. background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%);
  1450. display: flex;
  1451. align-items: center;
  1452. justify-content: center;
  1453. box-shadow: 0 4rpx 20rpx rgba(156, 39, 176, 0.4);
  1454. z-index: 999;
  1455. .add-button-icon {
  1456. font-size: 60rpx;
  1457. color: #FFFFFF;
  1458. line-height: 1;
  1459. font-weight: bold;
  1460. }
  1461. }
  1462. /* 底部导航 */
  1463. .tabbar {
  1464. position: fixed;
  1465. bottom: 0;
  1466. left: 0;
  1467. right: 0;
  1468. height: 100rpx;
  1469. background: #FFFFFF;
  1470. border-top: 1rpx solid #F0F0F0;
  1471. display: flex;
  1472. justify-content: space-around;
  1473. align-items: center;
  1474. padding-bottom: env(safe-area-inset-bottom);
  1475. .tabbar-item {
  1476. display: flex;
  1477. flex-direction: column;
  1478. align-items: center;
  1479. gap: 8rpx;
  1480. padding: 10rpx 0;
  1481. .tabbar-icon {
  1482. width: 44rpx;
  1483. height: 44rpx;
  1484. background-size: contain;
  1485. background-repeat: no-repeat;
  1486. background-position: center;
  1487. position: relative;
  1488. .badge {
  1489. position: absolute;
  1490. top: -8rpx;
  1491. right: -8rpx;
  1492. background: #FF4444;
  1493. color: #FFFFFF;
  1494. font-size: 20rpx;
  1495. font-weight: bold;
  1496. width: 32rpx;
  1497. height: 32rpx;
  1498. display: flex;
  1499. align-items: center;
  1500. justify-content: center;
  1501. border-radius: 16rpx;
  1502. }
  1503. }
  1504. .tabbar-text {
  1505. font-size: 20rpx;
  1506. color: #999;
  1507. }
  1508. &.active {
  1509. .tabbar-text {
  1510. color: #9C27B0;
  1511. font-weight: bold;
  1512. }
  1513. }
  1514. &.home .tabbar-icon {
  1515. 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>');
  1516. }
  1517. &.active.home .tabbar-icon {
  1518. 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="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
  1519. }
  1520. &.resources .tabbar-icon {
  1521. 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>');
  1522. }
  1523. &.trophy .tabbar-icon {
  1524. 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>');
  1525. }
  1526. &.active.trophy .tabbar-icon {
  1527. 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="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>');
  1528. }
  1529. &.message .tabbar-icon {
  1530. 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>');
  1531. }
  1532. &.active.message .tabbar-icon {
  1533. 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 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>');
  1534. }
  1535. &.mine .tabbar-icon {
  1536. 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>');
  1537. }
  1538. &.active.mine .tabbar-icon {
  1539. 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="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>');
  1540. }
  1541. }
  1542. }
  1543. </style>