index.vue 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751
  1. <template>
  2. <view class="home-page">
  3. <!-- 顶部状态栏 -->
  4. <view class="top-bar">
  5. <view class="logo">婚恋</view>
  6. <view class="msg-icon" @click="goToMessages">
  7. <text class="icon">🔔</text>
  8. <view v-if="unreadCount > 0" class="badge">{{ unreadCount }}</view>
  9. </view>
  10. </view>
  11. <!-- 个性化欢迎语 -->
  12. <!-- <view class="welcome-card" v-if="userInfo.nickname">
  13. <view class="welcome-content">
  14. <text class="welcome-text">你好,{{ userInfo.nickname }}!</text>
  15. <text class="match-info">今天有 <text class="highlight">{{ matchCount }}</text> 位新嘉宾和你匹配哦 ❤️</text>
  16. </view>
  17. </view> -->
  18. <!-- 轮播图 -->
  19. <view class="banner-section" v-if="bannerList && bannerList.length > 0">
  20. <swiper class="banner-swiper" :indicator-dots="true" :autoplay="true" :interval="4000" :duration="1000"
  21. :circular="true" indicator-color="rgba(255, 255, 255, 0.5)" indicator-active-color="#E91E63">
  22. <swiper-item v-for="(banner, index) in bannerList" :key="index">
  23. <view class="banner-item" @click="handleBannerClick(banner)">
  24. <image :src="banner.cover_image || banner.coverImage" class="banner-image" mode="aspectFill"
  25. :data-section="'banner'" :data-index="index"
  26. @error="handleImageError" @load="handleImageLoad"></image>
  27. <view class="banner-mask">
  28. <view class="banner-title">{{ banner.name }}</view>
  29. <view class="banner-subtitle" v-if="banner.subtitle">{{ banner.subtitle }}</view>
  30. <view class="banner-btn">查看更多</view>
  31. </view>
  32. </view>
  33. </swiper-item>
  34. </swiper>
  35. </view>
  36. <!-- 小喇叭公告栏 -->
  37. <view class="notice-bar" v-if="noticeList && noticeList.length > 0">
  38. <text class="notice-icon">📢</text>
  39. <swiper class="notice-swiper" vertical :autoplay="true" :interval="3000" :duration="500"
  40. :circular="true">
  41. <swiper-item v-for="(notice, index) in noticeList" :key="index">
  42. <view class="notice-text">{{ notice.content }}</view>
  43. </swiper-item>
  44. </swiper>
  45. </view>
  46. <!-- 核心功能入口 -->
  47. <view class="function-grid">
  48. <view class="grid-item" v-for="(item, index) in functionList" :key="index" @click="handleFunctionClick(item)">
  49. <view class="grid-icon-wrapper" :style="{ background: item.gradient || item.bgColor || '#FFE5F1' }">
  50. <text class="grid-icon" :style="{ color: item.iconColor || '#333333' }">{{ item.icon }}</text>
  51. </view>
  52. <text class="grid-text">{{ item.name }}</text>
  53. </view>
  54. </view>
  55. <!-- 成功案例 -->
  56. <view class="section success-case-section" v-if="successCases && successCases.length > 0">
  57. <view class="section-header">
  58. <text class="section-title">💕 成功案例</text>
  59. <text class="section-more" @click="goToSuccessCaseList">查看更多 ></text>
  60. </view>
  61. <view class="success-case-grid">
  62. <view class="case-card" v-for="(caseItem, index) in successCases.slice(0, 2)" :key="index"
  63. @click="handleSuccessCaseClick(caseItem)">
  64. <view class="case-image-wrapper">
  65. <image :src="caseItem.imageUrl" class="case-image" mode="aspectFill"
  66. :data-section="'successCase'" :data-index="index"
  67. @error="handleImageError" @load="handleImageLoad"></image>
  68. </view>
  69. <view class="case-content">
  70. <view class="couple-names">{{ caseItem.maleUserNickname || '先生' }}&{{ caseItem.femaleUserNickname || '女士' }}</view>
  71. <view class="case-quote">{{ caseItem.quote || '我们通过平台找到了彼此❤️' }}</view>
  72. <view class="case-meta">
  73. <text class="marriage-date">相识于{{ formatMarriageDate(caseItem.marriageDate) }}</text>
  74. </view>
  75. </view>
  76. </view>
  77. </view>
  78. </view>
  79. <!-- 平台活动 -->
  80. <view class="section activity-recommend-section" v-if="hotActivities && hotActivities.length > 0">
  81. <view class="section-header">
  82. <text class="section-title">🎉 平台活动</text>
  83. <text class="section-more" @click="goToActivityList">查看更多 ></text>
  84. </view>
  85. <view class="activity-scroll-container">
  86. <view class="activity-scroll-wrapper">
  87. <view class="activity-scroll-content">
  88. <!-- 第一组活动 -->
  89. <view class="activity-card-scroll" v-for="(activity, index) in hotActivities" :key="index"
  90. @click="handleActivityClick(activity)">
  91. <view class="activity-image-wrapper">
  92. <image :src="activity.cover_image" class="activity-image" mode="aspectFill"
  93. :data-section="'activity'" :data-index="index"
  94. @error="handleImageError" @load="handleImageLoad"></image>
  95. <view class="activity-status" v-if="activity.status === 1">报名中</view>
  96. </view>
  97. <view class="activity-info-static">
  98. <view class="activity-name-static">{{ activity.name }}</view>
  99. <view class="activity-time-static">{{ formatActivityTime(activity.startTime, activity.endTime) }}</view>
  100. </view>
  101. </view>
  102. <!-- 第二组活动(用于无缝滚动) -->
  103. <view class="activity-card-scroll" v-for="(activity, index) in hotActivities" :key="index + 100"
  104. @click="handleActivityClick(activity)">
  105. <view class="activity-image-wrapper">
  106. <image :src="activity.coverImage" class="activity-image" mode="aspectFill"
  107. :data-section="'activity'" :data-index="index + 100"
  108. @error="handleImageError" @load="handleImageLoad"></image>
  109. <view class="activity-status" v-if="activity.status === 1">报名中</view>
  110. </view>
  111. <view class="activity-info-static">
  112. <view class="activity-name-static">{{ activity.name }}</view>
  113. <view class="activity-time-static">{{ formatActivityTime(activity.startTime, activity.endTime) }}</view>
  114. </view>
  115. </view>
  116. </view>
  117. </view>
  118. </view>
  119. </view>
  120. <!-- 今日缘分推荐 -->
  121. <!-- <view class="section fate-recommend-section" v-if="todayRecommendUsers && todayRecommendUsers.length > 0">
  122. <view class="section-header">
  123. <text class="section-title">🌟 今日缘分</text>
  124. <text class="section-more" @click="goToRecommend">查看更多 ></text>
  125. </view>
  126. <view class="fate-cards">
  127. <view class="fate-card" v-for="(user, index) in todayRecommendUsers" :key="index"
  128. @click="handleUserClick(user)">
  129. <view class="user-avatar-wrapper">
  130. <image :src="user.avatarUrl || user.avatar" class="user-avatar" mode="aspectFill"
  131. :data-section="'todayRecommend'" :data-index="index"
  132. @error="handleImageError" @load="handleImageLoad"></image>
  133. <view class="online-status" v-if="user.isOnline"></view>
  134. </view>
  135. <view class="user-basic-info">
  136. <text class="user-nickname">{{ user.nickname }}</text>
  137. <text class="user-age-location">{{ user.age }}岁 · {{ user.location || user.city }}</text>
  138. </view>
  139. <view class="match-score">
  140. <text class="match-text">匹配度</text>
  141. <text class="match-percentage">{{ formatMatchScore(user.compatibilityScore) }}%</text>
  142. </view>
  143. <view class="user-tags">
  144. <text class="user-tag" v-for="(tag, tagIndex) in parseUserTags(user).slice(0, 2)" :key="tagIndex">
  145. {{ tag }}
  146. </text>
  147. </view>
  148. </view>
  149. </view>
  150. </view> -->
  151. <!-- 个人魅力指数 -->
  152. <view class="section charm-index-section" v-if="userInfo.userId">
  153. <view class="charm-card">
  154. <view class="charm-header">
  155. <text class="charm-title">💖 个人魅力指数</text>
  156. <text class="charm-score">{{ charmIndex.totalScore }}</text>
  157. </view>
  158. <view class="charm-progress">
  159. <view class="progress-bar">
  160. <view class="progress-fill" :style="{ width: charmIndex.totalScore + '%' }"></view>
  161. </view>
  162. <text class="progress-text">超越了{{ charmIndex.beatPercentage }}%的用户</text>
  163. </view>
  164. <view class="charm-items">
  165. <view class="charm-item" v-for="(item, index) in charmIndex.items" :key="index">
  166. <text class="charm-item-label">{{ item.label }}</text>
  167. <view class="charm-item-progress">
  168. <view class="item-progress-bar">
  169. <view class="item-progress-fill" :style="{ width: item.score + '%' }"></view>
  170. </view>
  171. <text class="charm-item-score">{{ item.score }}%</text>
  172. </view>
  173. </view>
  174. </view>
  175. <view class="charm-tip">
  176. <text class="tip-text">💡 {{ charmIndex.tip }}</text>
  177. </view>
  178. </view>
  179. </view>
  180. <!-- 底部占位 -->
  181. <view class="bottom-placeholder"></view>
  182. <!-- 底部导航栏 -->
  183. <view class="tabbar">
  184. <view class="tabbar-item active" @click="switchTab('index')">
  185. <text class="tabbar-icon">🏠</text>
  186. <text class="tabbar-text">首页</text>
  187. </view>
  188. <view class="tabbar-item" @click="switchTab('plaza')">
  189. <text class="tabbar-icon">💕</text>
  190. <text class="tabbar-text">动态</text>
  191. </view>
  192. <view class="tabbar-item" @click="switchTab('recommend')">
  193. <text class="tabbar-icon">👍</text>
  194. <text class="tabbar-text">推荐</text>
  195. </view>
  196. <view class="tabbar-item" @click="switchTab('message')">
  197. <text class="tabbar-icon">💬</text>
  198. <text class="tabbar-text">消息</text>
  199. <view v-if="unreadCount >= 1" class="tabbar-badge">{{ unreadCount }}</view>
  200. </view>
  201. <view class="tabbar-item" @click="switchTab('mine')">
  202. <text class="tabbar-icon">👤</text>
  203. <text class="tabbar-text">我的</text>
  204. </view>
  205. </view>
  206. <!-- 红娘提示弹框 -->
  207. <uni-popup ref="matchmakerPopup" type="dialog">
  208. <uni-popup-dialog
  209. type="confirm"
  210. title="红娘工作台"
  211. content="您是平台认证红娘,是否前往红娘工作台?"
  212. confirmText="前往工作台"
  213. cancelText="取消"
  214. @confirm="goToMatchmakerWorkspace"
  215. @cancel="closeMatchmakerPopup">
  216. </uni-popup-dialog>
  217. </uni-popup>
  218. </view>
  219. </template>
  220. <script>
  221. import api, { request } from '@/utils/api.js'
  222. import { formatTime, formatCountdown, isLoggedIn, goToLogin } from '@/utils/util.js'
  223. import { DEFAULT_IMAGES, ACTIVITY_TYPES } from '@/config/index.js'
  224. export default {
  225. data() {
  226. return {
  227. // 用户信息
  228. userInfo: {
  229. nickname: '小张',
  230. userId: null
  231. },
  232. matchCount: 3,
  233. // 轮播图数据
  234. bannerList: [
  235. ],
  236. // 公告数据
  237. noticeList: [
  238. ],
  239. // 功能入口
  240. functionList: [
  241. { id: 1, name: '星命测算', icon: '💖', path: '/pages/astrology/index', bgColor: '#FF6B9D', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF6B9D 0%, #FF8EAB 100%)' },
  242. { id: 2, name: '红娘列表', icon: '👤', path: '/pages/matchmakers/list', bgColor: '#6BC5F8', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #6BC5F8 0%, #87CEEB 100%)' },
  243. // { id: 3, name: '加入红娘', icon: '💕', path: '/pages/part-time-matchmaker/index', bgColor: '#9B7EDE', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #9B7EDE 0%, #B19CD9 100%)' },
  244. // { id: 3, name: '加入红娘', icon: '💕', path: '/pages/part-time-matchmaker/index', bgColor: '#9B7EDE', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #9B7EDE 0%, #B19CD9 100%)' },
  245. { id: 4, name: '精品课程', icon: '📚', path: '/pages/courses/list', bgColor: '#FF8C42', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF8C42 0%, #FFA366 100%)' },
  246. { id: 5, name: '今日缘分', icon: '💝', path: '/pages/recommend/index', bgColor: '#FF69B4', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FF69B4 0%, #FF8CC8 100%)', needLogin: true },
  247. // { id: 6, name: '专属定制', icon: '🎁', path: '/pages/customize/index', bgColor: '#FFA500', iconColor: '#FFFFFF', gradient: 'linear-gradient(135deg, #FFA500 0%, #FFB84D 100%)' }
  248. ],
  249. // 热门活动
  250. hotActivities: [],
  251. // 成功案例
  252. successCases: [],
  253. // VIP特权(保留但不在首页显示)
  254. vipPrivileges: [
  255. { icon: '💎', text: '专属红娘服务' },
  256. { icon: '⚡', text: '优先匹配推荐' },
  257. { icon: '👑', text: '贵族身份标识' },
  258. { icon: '🔒', text: '隐身访问模式' }
  259. ],
  260. // 今日推荐用户
  261. todayRecommendUsers: [],
  262. // 个人魅力指数
  263. charmIndex: {
  264. totalScore: 75,
  265. beatPercentage: 68,
  266. items: [
  267. { label: '资料完整度', score: 80 },
  268. { label: '活跃度', score: 70 },
  269. { label: '互动质量', score: 75 },
  270. { label: '形象分数', score: 72 }
  271. ],
  272. tip: '完善更多个人信息可提升魅力指数'
  273. },
  274. // 倒计时定时器
  275. countdownTimer: null,
  276. // 图片加载错误计数
  277. imageErrorCount: 0,
  278. // 是否已经显示过红娘弹框(避免重复显示)
  279. hasShownMatchmakerPopup: false
  280. }
  281. },
  282. computed: {
  283. // 从Vuex获取全局未读数
  284. unreadCount() {
  285. return this.$store.getters.getTotalUnread || 0;
  286. }
  287. },
  288. onLoad() {
  289. this.loadUserInfo()
  290. this.loadBannerData()
  291. this.loadNoticeData()
  292. // this.loadTodayRecommend()
  293. this.loadCharmIndex()
  294. this.loadHotActivities()
  295. this.loadSuccessCases()
  296. // 监听未读消息数更新事件
  297. uni.$on('conversationUnreadUpdate', (totalUnread) => {
  298. console.log('📬 首页收到未读数更新:', totalUnread);
  299. this.$store.dispatch('updateTotalUnread', totalUnread);
  300. });
  301. },
  302. onUnload() {
  303. // 清理资源
  304. uni.$off('conversationUnreadUpdate');
  305. },
  306. onShow() {
  307. console.log('📱 页面onShow触发')
  308. // 页面显示时检查是否为红娘
  309. this.checkMatchmakerStatus()
  310. },
  311. methods: {
  312. // 检查红娘状态
  313. checkMatchmakerStatus() {
  314. // 从本地存储获取用户信息
  315. const userInfo = uni.getStorageSync('userInfo')
  316. console.log('🔍 检查红娘状态,用户信息:', userInfo)
  317. // 严格判断:只有明确为红娘(值为1)的用户才显示弹框
  318. // 使用 parseInt 确保类型一致,避免字符串和数字比较问题
  319. if (userInfo && userInfo.isMatchmaker && parseInt(userInfo.isMatchmaker) === 1) {
  320. const storageKey = 'matchmakerPopupHandled_' + userInfo.userId
  321. console.log('📝 存储键:', storageKey)
  322. // 检查用户是否已经处理过红娘工作台提示
  323. const matchmakerPopupHandled = uni.getStorageSync(storageKey)
  324. console.log('✅ 已处理标记:', matchmakerPopupHandled)
  325. if (matchmakerPopupHandled) {
  326. console.log('❌ 用户已处理过红娘工作台提示,不再显示弹框')
  327. return
  328. }
  329. // 避免在同一次会话中重复显示弹框
  330. if (this.hasShownMatchmakerPopup) {
  331. console.log('❌ 本次会话已显示过弹框,不再重复显示')
  332. return
  333. }
  334. console.log('✨ 用户是红娘且未处理过提示,准备显示弹框')
  335. // 标记已显示(仅在当前会话中有效)
  336. this.hasShownMatchmakerPopup = true
  337. // 延迟显示弹框,确保页面已完全加载
  338. setTimeout(() => {
  339. if (this.$refs.matchmakerPopup) {
  340. this.$refs.matchmakerPopup.open()
  341. console.log('🎉 弹框已打开')
  342. } else {
  343. console.error('❌ 弹框组件引用不存在')
  344. }
  345. }, 500)
  346. } else {
  347. console.log('ℹ️ 用户不是红娘,isMatchmaker值:', userInfo?.isMatchmaker)
  348. }
  349. },
  350. // 前往红娘工作台
  351. goToMatchmakerWorkspace() {
  352. console.log('🚀 前往红娘工作台')
  353. this.$refs.matchmakerPopup.close()
  354. // 标记用户已处理过弹框(选择了前往)
  355. const userInfo = uni.getStorageSync('userInfo')
  356. if (userInfo && userInfo.userId) {
  357. const storageKey = 'matchmakerPopupHandled_' + userInfo.userId
  358. uni.setStorageSync(storageKey, true)
  359. console.log('💾 已保存处理标记(前往):', storageKey, '=', true)
  360. // 验证保存
  361. const saved = uni.getStorageSync(storageKey)
  362. console.log('🔍 验证保存结果:', saved)
  363. } else {
  364. console.error('❌ 无法保存标记,用户信息不完整:', userInfo)
  365. }
  366. // 跳转到红娘工作台
  367. uni.navigateTo({
  368. url: '/pages/matchmaker-workbench/index'
  369. })
  370. },
  371. // 关闭红娘弹框
  372. closeMatchmakerPopup() {
  373. console.log('🚫 用户选择不前往红娘工作台')
  374. this.$refs.matchmakerPopup.close()
  375. // 标记用户已处理过弹框(选择了取消)
  376. const userInfo = uni.getStorageSync('userInfo')
  377. if (userInfo && userInfo.userId) {
  378. const storageKey = 'matchmakerPopupHandled_' + userInfo.userId
  379. uni.setStorageSync(storageKey, true)
  380. console.log('💾 已保存处理标记(取消):', storageKey, '=', true)
  381. // 验证保存
  382. const saved = uni.getStorageSync(storageKey)
  383. console.log('🔍 验证保存结果:', saved)
  384. } else {
  385. console.error('❌ 无法保存标记,用户信息不完整:', userInfo)
  386. }
  387. },
  388. // 加载用户信息
  389. loadUserInfo() {
  390. // 从本地存储或接口获取用户信息
  391. const userInfo = uni.getStorageSync('userInfo')
  392. if (userInfo) {
  393. this.userInfo = userInfo
  394. this.matchCount = Math.floor(Math.random() * 10) + 1
  395. }
  396. },
  397. // 加载轮播图数据
  398. async loadBannerData() {
  399. try {
  400. // 调用 API 获取轮播图数据
  401. const data = await api.home.getBanners()
  402. if (data && data.length > 0) {
  403. // 处理轮播图数据,确保字段格式正确
  404. this.bannerList = data.map(banner => ({
  405. ...banner,
  406. cover_image: banner.cover_image || banner.coverImage || DEFAULT_IMAGES.banner,
  407. name: banner.name || banner.title || '',
  408. subtitle: banner.subtitle || '',
  409. type: banner.type || 'activity',
  410. targetId: banner.targetId || banner.id
  411. }))
  412. }
  413. } catch (error) {
  414. console.error('获取轮播图失败:', error)
  415. // 使用默认数据
  416. }
  417. },
  418. // 加载公告数据
  419. async loadNoticeData() {
  420. try {
  421. // 调用 API 获取公告数据
  422. const data = await api.home.getNotices()
  423. if (data && data.length > 0) {
  424. this.noticeList = data
  425. }
  426. } catch (error) {
  427. console.error('获取公告失败:', error)
  428. // 使用默认数据
  429. }
  430. },
  431. // 加载今日推荐
  432. async loadTodayRecommend() {
  433. try {
  434. const userId = this.userInfo.userId || parseInt(uni.getStorageSync('userId'))
  435. if (!userId) {
  436. console.log('用户未登录,跳过今日推荐加载')
  437. return
  438. }
  439. // 调用推荐API获取推荐用户列表
  440. const data = await api.recommend.getUsers({
  441. userId: userId,
  442. oppoOnly: 1, // 只推荐异性
  443. limit: 20 // 获取20个用户用于筛选
  444. })
  445. if (data && data.length > 0) {
  446. // 按匹配度降序排序,取前3个匹配度最高的用户
  447. const sortedUsers = data
  448. .filter(user => user.compatibilityScore && user.compatibilityScore > 0)
  449. .sort((a, b) => (b.compatibilityScore || 0) - (a.compatibilityScore || 0))
  450. .slice(0, 3)
  451. // 处理用户数据
  452. this.todayRecommendUsers = sortedUsers.map(user => ({
  453. ...user,
  454. avatarUrl: this.validateImageUrl(user.avatarUrl || DEFAULT_IMAGES.avatar),
  455. age: user.age || 0,
  456. location: this.formatLocation(user),
  457. isOnline: false // 可以根据实际情况设置
  458. }))
  459. console.log('今日缘分推荐加载成功,匹配度最高的用户:', this.todayRecommendUsers)
  460. }
  461. } catch (error) {
  462. console.error('获取推荐用户失败:', error)
  463. this.todayRecommendUsers = []
  464. }
  465. },
  466. // 格式化位置信息
  467. formatLocation(user) {
  468. const parts = []
  469. if (user.provinceName) parts.push(user.provinceName)
  470. if (user.cityName) parts.push(user.cityName)
  471. if (parts.length === 0 && user.location) return user.location
  472. if (parts.length === 0 && user.city) return user.city
  473. return parts.join(' ')
  474. },
  475. // 格式化匹配度分数
  476. formatMatchScore(score) {
  477. if (!score) return '0.0'
  478. return Number(score).toFixed(1)
  479. },
  480. // 解析用户标签
  481. parseUserTags(user) {
  482. const tags = []
  483. if (user.star) tags.push(user.star)
  484. if (user.jobTitle) tags.push(user.jobTitle)
  485. if (user.hobby) {
  486. try {
  487. const hobbies = typeof user.hobby === 'string' ? JSON.parse(user.hobby) : user.hobby
  488. if (Array.isArray(hobbies)) {
  489. tags.push(...hobbies.slice(0, 2))
  490. }
  491. } catch (e) {
  492. console.error('解析兴趣爱好失败:', e)
  493. }
  494. }
  495. return tags
  496. },
  497. // 加载魅力指数
  498. async loadCharmIndex() {
  499. try {
  500. if (this.userInfo.userId) {
  501. // 调用 API 获取魅力指数
  502. // const data = await api.user.getCharmIndex(this.userInfo.userId)
  503. // if (data) {
  504. // this.charmIndex = data
  505. // }
  506. }
  507. } catch (error) {
  508. console.error('获取魅力指数失败:', error)
  509. }
  510. },
  511. // 加载热门活动
  512. async loadHotActivities() {
  513. try {
  514. // 调用 API 获取活动列表(获取所有状态为1的活动,而不是只获取热门活动)
  515. const data = await api.activity.getList({
  516. status: 1,
  517. limit: 10
  518. })
  519. if (data && data.length > 0) {
  520. this.hotActivities = data.map((activity, index) => {
  521. const rawCover = this.validateImageUrl(activity.cover_image || activity.coverImage || activity.imageUrl || activity.image_url)
  522. return {
  523. ...activity,
  524. coverImage: this.ensureRomanticCover(rawCover, index),
  525. name: activity.name || activity.title || '',
  526. startTime: activity.startTime || activity.start_time || activity.startDate || '',
  527. endTime: activity.endTime || activity.end_time || activity.endDate || '',
  528. status: activity.status || 0
  529. }})
  530. } else {
  531. // 使用默认活动数据
  532. this.hotActivities = [
  533. ]
  534. }
  535. } catch (error) {
  536. console.error('获取热门活动失败:', error)
  537. // 使用默认数据
  538. this.hotActivities = [
  539. {
  540. id: 1,
  541. name: '七夕单身派对',
  542. description: '浪漫七夕夜,遇见更好的ta',
  543. coverImage: DEFAULT_IMAGES.activity || 'https://images.unsplash.com/photo-1511632765486-a01980e01a18?w=750&h=400&fit=crop',
  544. startTime: '2024-02-14 19:00:00',
  545. location: '杭州西湖文化广场',
  546. price: 99,
  547. status: 1
  548. },
  549. {
  550. id: 2,
  551. name: '情人节主题聚会',
  552. description: '高端单身聚会,品味生活',
  553. coverImage: DEFAULT_IMAGES.banner || 'https://images.unsplash.com/photo-1522673607200-164d1b6ce486?w=750&h=360&fit=crop',
  554. startTime: '2024-02-14 20:00:00',
  555. location: '上海外滩茶餐厅',
  556. price: 128,
  557. status: 1
  558. },
  559. {
  560. id: 3,
  561. name: '春日户外踏青',
  562. description: '拥抱春天,邂逅美好',
  563. coverImage: DEFAULT_IMAGES.couple || 'https://images.unsplash.com/photo-1516589178581-6cd7833ae3b2?w=520&h=360&fit=crop',
  564. startTime: '2024-03-20 14:00:00',
  565. location: '苏州园林博物馆',
  566. price: 68,
  567. status: 1
  568. }
  569. ]
  570. }
  571. },
  572. // 格式化活动时间(支持开始和结束时间)
  573. formatActivityTime(startTime, endTime) {
  574. if (!startTime) return '时间待定'
  575. try {
  576. const start = new Date(startTime)
  577. const startMonth = start.getMonth() + 1
  578. const startDay = start.getDate()
  579. const startHour = start.getHours().toString().padStart(2, '0')
  580. const startMinute = start.getMinutes().toString().padStart(2, '0')
  581. let timeStr = `${startMonth}月${startDay}日 ${startHour}:${startMinute}`
  582. // 如果有结束时间,添加结束时间
  583. if (endTime) {
  584. const end = new Date(endTime)
  585. const endMonth = end.getMonth() + 1
  586. const endDay = end.getDate()
  587. const endHour = end.getHours().toString().padStart(2, '0')
  588. const endMinute = end.getMinutes().toString().padStart(2, '0')
  589. timeStr += ` - ${endMonth}月${endDay}日 ${endHour}:${endMinute}`
  590. }
  591. return timeStr
  592. } catch (error) {
  593. return '时间待定'
  594. }
  595. },
  596. // 加载成功案例
  597. async loadSuccessCases() {
  598. try {
  599. // 调用 API 获取成功案例列表
  600. const data = await api.successCase.getList({
  601. pageNum: 1,
  602. pageSize: 5
  603. })
  604. if (data && data.length > 0) {
  605. this.successCases = data.map(caseItem => ({
  606. ...caseItem,
  607. maleUserNickname: caseItem.maleUserNickname || caseItem.male_user_nickname || '先生',
  608. femaleUserNickname: caseItem.femaleUserNickname || caseItem.female_user_nickname || '女士',
  609. imageUrl: this.validateImageUrl(caseItem.imageUrl || caseItem.image_url || DEFAULT_IMAGES.couple),
  610. quote: caseItem.quote || '我们通过平台找到了彼此❤️',
  611. marriageDate: caseItem.marriageDate || caseItem.marriage_date || ''
  612. }))
  613. } else {
  614. // 使用默认数据
  615. this.successCases = []
  616. }
  617. } catch (error) {
  618. console.error('获取成功案例失败:', error)
  619. // 使用默认数据
  620. this.successCases = [
  621. {
  622. caseNo: 'CASE001',
  623. maleUserNickname: '张先生',
  624. femaleUserNickname: '李女士',
  625. imageUrl: DEFAULT_IMAGES.couple,
  626. quote: '从第一次咖啡约会到领证,只用了90天',
  627. marriageDate: '2024-08-20'
  628. },
  629. {
  630. caseNo: 'CASE002',
  631. maleUserNickname: '王先生',
  632. femaleUserNickname: '赵女士',
  633. imageUrl: DEFAULT_IMAGES.couple,
  634. quote: '感谢红娘的专业服务,让我们相遇相知',
  635. marriageDate: '2024-09-15'
  636. }
  637. ]
  638. }
  639. },
  640. // 格式化结婚日期
  641. formatMarriageDate(dateStr) {
  642. if (!dateStr) return '佳音渐近'
  643. try {
  644. const date = new Date(dateStr)
  645. const year = date.getFullYear()
  646. const month = date.getMonth() + 1
  647. const day = date.getDate()
  648. return `${year}年${month}月${day}日`
  649. } catch (error) {
  650. return '佳音渐近'
  651. }
  652. },
  653. // 跳转到VIP页面
  654. goToVip() {
  655. uni.navigateTo({
  656. url: '/pages/vip/index'
  657. })
  658. },
  659. // 跳转到活动列表
  660. goToActivityList() {
  661. uni.navigateTo({
  662. url: '/pages/activities/list'
  663. })
  664. },
  665. // 处理活动点击
  666. handleActivityClick(activity) {
  667. uni.navigateTo({
  668. url: `/pages/activities/detail?id=${activity.id}`
  669. })
  670. },
  671. // 跳转到成功案例列表
  672. goToSuccessCaseList() {
  673. uni.navigateTo({
  674. url: '/pages/success-case/list'
  675. })
  676. },
  677. // 处理成功案例点击
  678. handleSuccessCaseClick(caseItem) {
  679. const caseNo = caseItem.caseNo || caseItem.case_no
  680. uni.navigateTo({
  681. url: `/pages/success-case/detail?caseNo=${caseNo}`
  682. })
  683. },
  684. // 处理图片加载错误并回退到默认图
  685. handleImageError(e) {
  686. const failedSrc = e.detail ? e.detail.errMsg : '未知错误'
  687. const dataset = (e && e.target && e.target.dataset) ? e.target.dataset : {}
  688. const section = dataset.section
  689. const index = dataset.index
  690. console.warn('图片加载失败:', failedSrc, 'section:', section, 'index:', index)
  691. const fallbackMap = {
  692. banner: DEFAULT_IMAGES.banner,
  693. activity: DEFAULT_IMAGES.activity,
  694. successCase: DEFAULT_IMAGES.couple,
  695. todayRecommend: DEFAULT_IMAGES.avatar
  696. }
  697. const setFallback = (list, targetIndex, key, fallback, extraKeys = []) => {
  698. if (!Array.isArray(list) || list.length === 0) return
  699. const numIndex = Number(targetIndex)
  700. if (Number.isNaN(numIndex)) return
  701. const normalizedIndex = ((numIndex % list.length) + list.length) % list.length
  702. if (!list[normalizedIndex]) return
  703. this.$set(list[normalizedIndex], key, fallback)
  704. extraKeys.forEach(extraKey => this.$set(list[normalizedIndex], extraKey, fallback))
  705. }
  706. switch (section) {
  707. case 'banner':
  708. setFallback(this.bannerList, index, 'cover_image', fallbackMap.banner)
  709. setFallback(this.bannerList, index, 'coverImage', fallbackMap.banner)
  710. break
  711. case 'activity':
  712. setFallback(this.hotActivities, index, 'coverImage', this.getRomanticImage(index))
  713. break
  714. case 'successCase':
  715. setFallback(this.successCases, index, 'imageUrl', fallbackMap.successCase)
  716. break
  717. case 'todayRecommend':
  718. setFallback(this.todayRecommendUsers, index, 'avatar', fallbackMap.todayRecommend)
  719. break
  720. default:
  721. break
  722. }
  723. },
  724. // 处理图片加载成功
  725. handleImageLoad(e) {
  726. // 图片加载成功,重置错误计数
  727. this.imageErrorCount = 0
  728. },
  729. // 验证和处理图片URL
  730. validateImageUrl(url) {
  731. if (!url || url === 'null' || url === 'undefined') {
  732. return DEFAULT_IMAGES.activity || DEFAULT_IMAGES.banner || 'http://115.190.125.125:9001/static-images/login-bg.png'
  733. }
  734. let cleanedUrl = String(url).trim()
  735. const httpIndex = cleanedUrl.indexOf('http')
  736. if (httpIndex > 0) {
  737. cleanedUrl = cleanedUrl.slice(httpIndex)
  738. }
  739. if (!cleanedUrl) {
  740. return DEFAULT_IMAGES.activity || DEFAULT_IMAGES.banner || 'http://115.190.125.125:9001/static-images/login-bg.png'
  741. }
  742. // 如果是相对路径,添加协议和域名
  743. if (cleanedUrl.startsWith('/')) {
  744. // 检查是否是完整的服务器路径
  745. if (cleanedUrl.startsWith('/banners/') || cleanedUrl.startsWith('/activities/') || cleanedUrl.startsWith('/images/')) {
  746. return `http://115.190.125.125:9000${cleanedUrl}`
  747. }
  748. return `http://115.190.125.125:9000${cleanedUrl}`
  749. }
  750. // 如果已经是完整URL,直接返回
  751. if (cleanedUrl.startsWith('http://') || cleanedUrl.startsWith('https://')) {
  752. return cleanedUrl
  753. }
  754. // 其他情况返回默认图片
  755. return DEFAULT_IMAGES.activity || DEFAULT_IMAGES.banner || 'http://115.190.125.125:9001/static-images/login-bg.png'
  756. },
  757. getRomanticImage(index = 0) {
  758. if (!this.romanticImages || this.romanticImages.length === 0) {
  759. return DEFAULT_IMAGES.activity || DEFAULT_IMAGES.banner || 'http://115.190.125.125:9001/static-images/login-bg.png'
  760. }
  761. const idx = Math.abs(Number(index) || 0) % this.romanticImages.length
  762. return this.romanticImages[idx]
  763. },
  764. ensureRomanticCover(url, index = 0) {
  765. if (!url || url === DEFAULT_IMAGES.activity || url === DEFAULT_IMAGES.banner || url === 'http://115.190.125.125:9001/static-images/login-bg.png') {
  766. return this.getRomanticImage(index)
  767. }
  768. return url
  769. },
  770. // 跳转到推荐页面
  771. goToRecommend() {
  772. uni.navigateTo({
  773. url: '/pages/recommend/index'
  774. })
  775. },
  776. // 处理用户点击
  777. handleUserClick(user) {
  778. const userId = user.userId || user.id
  779. if (!userId) {
  780. uni.showToast({
  781. title: '用户信息错误',
  782. icon: 'none'
  783. })
  784. return
  785. }
  786. uni.navigateTo({
  787. url: `/pages/recommend/user-detail?userId=${userId}`
  788. })
  789. },
  790. // 格式化时间
  791. formatTime,
  792. // Banner点击
  793. handleBannerClick(banner) {
  794. if (banner.type === 'activity') {
  795. uni.navigateTo({
  796. url: `/pages/activities/detail?id=${banner.targetId}`
  797. })
  798. } else if (banner.type === 'course') {
  799. uni.navigateTo({
  800. url: `/pages/courses/detail?id=${banner.targetId}`
  801. })
  802. } else if (banner.type === 'case') {
  803. uni.navigateTo({
  804. url: '/pages/success-case/list'
  805. })
  806. }
  807. },
  808. // 功能入口点击
  809. handleFunctionClick(item) {
  810. // 今日缘分:直接跳转到专门的推荐页面
  811. if (item && (item.id === 5 || item.name === '今日缘分' || item.name === '今日推荐')) {
  812. console.log('点击今日缘分,跳转到专门页面')
  813. uni.navigateTo({
  814. url: '/pages/today-recommend/index',
  815. success: () => {
  816. console.log('跳转今日缘分页面成功')
  817. },
  818. fail: (err) => {
  819. console.error('跳转今日缘分页面失败:', err)
  820. uni.showToast({
  821. title: '页面跳转失败',
  822. icon: 'none'
  823. })
  824. }
  825. })
  826. return
  827. }
  828. // 检查是否需要登录
  829. if (item.needLogin && !isLoggedIn()) {
  830. uni.navigateTo({
  831. url: '/pages/page3/page3'
  832. })
  833. return
  834. }
  835. if (item.path) {
  836. let url = item.path
  837. if (item.query) {
  838. const queryStr = Object.keys(item.query).map(key => `${key}=${item.query[key]}`).join('&')
  839. url += '?' + queryStr
  840. }
  841. uni.navigateTo({
  842. url: url,
  843. fail: () => {
  844. uni.showToast({
  845. title: '功能开发中',
  846. icon: 'none'
  847. })
  848. }
  849. })
  850. }
  851. },
  852. // 跳转到消息中心
  853. goToMessages() {
  854. uni.navigateTo({
  855. url: '/pages/message/index',
  856. fail: () => {
  857. uni.showToast({
  858. title: '功能开发中',
  859. icon: 'none'
  860. })
  861. }
  862. })
  863. },
  864. // 切换Tab
  865. switchTab(tab) {
  866. if (tab === 'index') return
  867. const tabPages = {
  868. plaza: '/pages/plaza/index',
  869. recommend: '/pages/recommend/index',
  870. message: '/pages/message/index',
  871. mine: '/pages/mine/index'
  872. }
  873. if (tabPages[tab]) {
  874. uni.redirectTo({
  875. url: tabPages[tab]
  876. })
  877. }
  878. }
  879. }
  880. }
  881. </script>
  882. <style lang="scss" scoped>
  883. .home-page {
  884. min-height: 100vh;
  885. background: #F5F5F5;
  886. padding-bottom: 120rpx;
  887. }
  888. /* 顶部状态栏 */
  889. .top-bar {
  890. display: flex;
  891. justify-content: space-between;
  892. align-items: center;
  893. padding: 25rpx 35rpx;
  894. background: #FFFFFF;
  895. border-bottom: 2rpx solid #E0E0E0;
  896. .logo {
  897. font-size: 38rpx;
  898. font-weight: 800;
  899. color: #E91E63;
  900. }
  901. .msg-icon {
  902. position: relative;
  903. width: 68rpx;
  904. height: 68rpx;
  905. display: flex;
  906. align-items: center;
  907. justify-content: center;
  908. background: #FFE5F1;
  909. border-radius: 50%;
  910. transition: all 0.2s ease;
  911. &:active {
  912. opacity: 0.8;
  913. }
  914. .icon {
  915. font-size: 44rpx;
  916. }
  917. .badge {
  918. position: absolute;
  919. top: -5rpx;
  920. right: -5rpx;
  921. min-width: 36rpx;
  922. height: 36rpx;
  923. line-height: 36rpx;
  924. padding: 0 8rpx;
  925. background: #FF6B6B;
  926. color: #FFFFFF;
  927. font-size: 20rpx;
  928. font-weight: bold;
  929. border-radius: 18rpx;
  930. text-align: center;
  931. }
  932. }
  933. }
  934. /* 个性化欢迎语 */
  935. .welcome-card {
  936. margin: 25rpx 35rpx;
  937. padding: 35rpx;
  938. background: #FFFFFF;
  939. border-radius: 16rpx;
  940. border: 2rpx solid #E0E0E0;
  941. .welcome-content {
  942. display: flex;
  943. flex-direction: column;
  944. gap: 16rpx;
  945. .welcome-text {
  946. font-size: 34rpx;
  947. font-weight: 700;
  948. color: #333333;
  949. line-height: 1.3;
  950. }
  951. .match-info {
  952. font-size: 28rpx;
  953. color: #666666;
  954. line-height: 1.4;
  955. .highlight {
  956. color: #E91E63;
  957. font-weight: 800;
  958. font-size: 36rpx;
  959. }
  960. }
  961. }
  962. }
  963. /* 轮播图 */
  964. .banner-section {
  965. margin: 10rpx 10rpx;
  966. .banner-swiper {
  967. height: 480rpx;
  968. border-radius: 16rpx;
  969. overflow: hidden;
  970. border: 2rpx solid #E0E0E0;
  971. .banner-item {
  972. position: relative;
  973. width: 100%;
  974. height: 100%;
  975. overflow: hidden;
  976. .banner-image {
  977. width: 100%;
  978. height: 100%;
  979. }
  980. .banner-mask {
  981. position: absolute;
  982. bottom: 0;
  983. left: 0;
  984. right: 0;
  985. padding: 35rpx;
  986. background: rgba(0, 0, 0, 0.6);
  987. color: #FFFFFF;
  988. .banner-title {
  989. font-size: 38rpx;
  990. font-weight: 800;
  991. margin-bottom: 12rpx;
  992. line-height: 1.2;
  993. }
  994. .banner-subtitle {
  995. font-size: 26rpx;
  996. margin-bottom: 24rpx;
  997. }
  998. .banner-btn {
  999. display: inline-block;
  1000. padding: 12rpx 32rpx;
  1001. background: #E91E63;
  1002. border-radius: 8rpx;
  1003. font-size: 26rpx;
  1004. font-weight: 600;
  1005. transition: all 0.2s ease;
  1006. &:active {
  1007. opacity: 0.8;
  1008. }
  1009. }
  1010. }
  1011. }
  1012. }
  1013. }
  1014. /* 公告栏 */
  1015. .notice-bar {
  1016. display: flex;
  1017. align-items: center;
  1018. margin: 25rpx 35rpx;
  1019. padding: 24rpx 30rpx;
  1020. background: #FFF8E1;
  1021. border-radius: 8rpx;
  1022. border: 2rpx solid #FFD54F;
  1023. border-left: 6rpx solid #FFC107;
  1024. .notice-icon {
  1025. font-size: 34rpx;
  1026. margin-right: 24rpx;
  1027. }
  1028. .notice-swiper {
  1029. flex: 1;
  1030. height: 44rpx;
  1031. .notice-text {
  1032. font-size: 28rpx;
  1033. color: #795548;
  1034. line-height: 44rpx;
  1035. white-space: nowrap;
  1036. overflow: hidden;
  1037. text-overflow: ellipsis;
  1038. font-weight: 500;
  1039. }
  1040. }
  1041. }
  1042. /* 功能入口 */
  1043. .function-grid {
  1044. display: grid;
  1045. grid-template-columns: repeat(4, 1fr);
  1046. gap: 24rpx;
  1047. margin: 20rpx 1rpx;
  1048. padding: 30rpx;
  1049. background: #FFFFFF;
  1050. border-radius: 16rpx;
  1051. border: 2rpx solid #E0E0E0;
  1052. .grid-item {
  1053. display: flex;
  1054. flex-direction: column;
  1055. align-items: center;
  1056. gap: 14rpx;
  1057. padding: 20rpx 12rpx;
  1058. transition: all 0.3s ease;
  1059. border-radius: 12rpx;
  1060. &:active {
  1061. transform: scale(0.95);
  1062. opacity: 0.9;
  1063. }
  1064. .grid-icon-wrapper {
  1065. width: 140rpx;
  1066. height: 140rpx;
  1067. display: flex;
  1068. align-items: center;
  1069. justify-content: center;
  1070. border-radius: 20rpx;
  1071. position: relative;
  1072. overflow: hidden;
  1073. /* 外阴影 + 内阴影 - 增强立体感和深度感 */
  1074. box-shadow: inset 0 2rpx 8rpx rgba(255, 255, 255, 0.3),
  1075. 0 10rpx 30rpx rgba(0, 0, 0, 0.2),
  1076. 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
  1077. transition: all 0.3s ease;
  1078. /* 光泽效果 - 左上角强烈高光,模拟图片中的光泽 */
  1079. &::before {
  1080. content: '';
  1081. position: absolute;
  1082. top: -60%;
  1083. left: -60%;
  1084. width: 220%;
  1085. height: 220%;
  1086. background: linear-gradient(135deg,
  1087. rgba(255, 255, 255, 0.6) 0%,
  1088. rgba(255, 255, 255, 0.4) 15%,
  1089. rgba(255, 255, 255, 0.2) 30%,
  1090. rgba(255, 255, 255, 0.05) 50%,
  1091. transparent 70%);
  1092. transform: rotate(45deg);
  1093. pointer-events: none;
  1094. z-index: 1;
  1095. border-radius: 50%;
  1096. }
  1097. /* 底部深度阴影 - 增强立体感 */
  1098. &::after {
  1099. content: '';
  1100. position: absolute;
  1101. bottom: 0;
  1102. left: 0;
  1103. right: 0;
  1104. height: 40%;
  1105. background: linear-gradient(to top,
  1106. rgba(0, 0, 0, 0.15) 0%,
  1107. rgba(0, 0, 0, 0.08) 50%,
  1108. transparent 100%);
  1109. pointer-events: none;
  1110. z-index: 1;
  1111. border-radius: 0 0 20rpx 20rpx;
  1112. }
  1113. &:active {
  1114. transform: scale(0.92);
  1115. box-shadow: inset 0 2rpx 8rpx rgba(255, 255, 255, 0.2),
  1116. 0 6rpx 20rpx rgba(0, 0, 0, 0.25);
  1117. }
  1118. .grid-icon {
  1119. font-size: 70rpx;
  1120. position: relative;
  1121. z-index: 2;
  1122. /* 图标阴影 - 让图标更突出 */
  1123. filter: drop-shadow(0 3rpx 8rpx rgba(0, 0, 0, 0.2));
  1124. text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
  1125. }
  1126. }
  1127. .grid-text {
  1128. font-size: 24rpx;
  1129. font-weight: 600;
  1130. color: #333333;
  1131. text-align: center;
  1132. line-height: 1.2;
  1133. }
  1134. }
  1135. }
  1136. /* 区块样式 */
  1137. .section {
  1138. margin: 40rpx 0;
  1139. .section-header {
  1140. display: flex;
  1141. justify-content: space-between;
  1142. align-items: center;
  1143. padding: 0 35rpx;
  1144. margin-bottom: 25rpx;
  1145. .section-title {
  1146. font-size: 36rpx;
  1147. font-weight: 800;
  1148. color: #333333;
  1149. border-left: 6rpx solid #E91E63;
  1150. padding-left: 16rpx;
  1151. }
  1152. .section-more {
  1153. font-size: 26rpx;
  1154. color: #E91E63;
  1155. font-weight: 600;
  1156. transition: all 0.2s ease;
  1157. &:active {
  1158. opacity: 0.6;
  1159. }
  1160. }
  1161. }
  1162. }
  1163. /* 平台活动展示区 */
  1164. .activity-recommend-section {
  1165. .activity-scroll-container {
  1166. margin: 0 35rpx;
  1167. overflow: hidden;
  1168. position: relative;
  1169. .activity-scroll-wrapper {
  1170. width: 100%;
  1171. overflow: hidden;
  1172. .activity-scroll-content {
  1173. display: flex;
  1174. animation: activityScroll 30s linear infinite;
  1175. gap: 20rpx;
  1176. width: fit-content;
  1177. &:hover {
  1178. animation-play-state: paused;
  1179. }
  1180. .activity-card-scroll {
  1181. flex-shrink: 0;
  1182. width: 500rpx;
  1183. background: #FFFFFF;
  1184. border-radius: 16rpx;
  1185. overflow: hidden;
  1186. border: 2rpx solid #E0E0E0;
  1187. transition: all 0.3s ease;
  1188. &:active {
  1189. transform: scale(0.98);
  1190. opacity: 0.9;
  1191. }
  1192. .activity-image-wrapper {
  1193. position: relative;
  1194. width: 100%;
  1195. height: 280rpx;
  1196. overflow: hidden;
  1197. .activity-image {
  1198. width: 100%;
  1199. height: 100%;
  1200. }
  1201. .activity-status {
  1202. position: absolute;
  1203. top: 15rpx;
  1204. right: 15rpx;
  1205. padding: 6rpx 12rpx;
  1206. background: #4CAF50;
  1207. color: #FFFFFF;
  1208. font-size: 22rpx;
  1209. font-weight: 600;
  1210. border-radius: 8rpx;
  1211. }
  1212. }
  1213. .activity-info-static {
  1214. padding: 20rpx;
  1215. .activity-name-static {
  1216. font-size: 28rpx;
  1217. font-weight: 700;
  1218. color: #333333;
  1219. margin-bottom: 8rpx;
  1220. line-height: 1.3;
  1221. }
  1222. .activity-time-static {
  1223. font-size: 24rpx;
  1224. color: #666666;
  1225. line-height: 1.4;
  1226. }
  1227. }
  1228. }
  1229. }
  1230. }
  1231. }
  1232. }
  1233. @keyframes activityScroll {
  1234. 0% {
  1235. transform: translateX(0);
  1236. }
  1237. 100% {
  1238. transform: translateX(-50%);
  1239. }
  1240. }
  1241. /* 今日缘分推荐 */
  1242. .fate-recommend-section {
  1243. .fate-cards {
  1244. display: flex;
  1245. gap: 20rpx;
  1246. padding: 0 35rpx;
  1247. overflow-x: auto;
  1248. scrollbar-width: none;
  1249. &::-webkit-scrollbar {
  1250. display: none;
  1251. }
  1252. .fate-card {
  1253. min-width: 320rpx;
  1254. background: #FFFFFF;
  1255. border-radius: 16rpx;
  1256. padding: 25rpx;
  1257. border: 2rpx solid #E0E0E0;
  1258. display: flex;
  1259. flex-direction: column;
  1260. align-items: center;
  1261. gap: 15rpx;
  1262. position: relative;
  1263. transition: all 0.2s ease;
  1264. &:active {
  1265. background: #F5F5F5;
  1266. }
  1267. .user-avatar-wrapper {
  1268. position: relative;
  1269. .user-avatar {
  1270. width: 100rpx;
  1271. height: 100rpx;
  1272. border-radius: 50%;
  1273. border: 3rpx solid #E0E0E0;
  1274. }
  1275. .online-status {
  1276. position: absolute;
  1277. bottom: 5rpx;
  1278. right: 5rpx;
  1279. width: 20rpx;
  1280. height: 20rpx;
  1281. background: #4CAF50;
  1282. border-radius: 50%;
  1283. border: 3rpx solid white;
  1284. }
  1285. }
  1286. .user-basic-info {
  1287. text-align: center;
  1288. .user-nickname {
  1289. font-size: 28rpx;
  1290. font-weight: 700;
  1291. color: #333333;
  1292. display: block;
  1293. margin-bottom: 5rpx;
  1294. }
  1295. .user-age-location {
  1296. font-size: 24rpx;
  1297. color: #666666;
  1298. }
  1299. }
  1300. .match-score {
  1301. display: flex;
  1302. align-items: center;
  1303. gap: 8rpx;
  1304. padding: 8rpx 16rpx;
  1305. background: #FFE5F1;
  1306. border-radius: 8rpx;
  1307. .match-text {
  1308. font-size: 22rpx;
  1309. color: #666666;
  1310. }
  1311. .match-percentage {
  1312. font-size: 26rpx;
  1313. font-weight: 700;
  1314. color: #E91E63;
  1315. }
  1316. }
  1317. .user-tags {
  1318. display: flex;
  1319. gap: 8rpx;
  1320. flex-wrap: wrap;
  1321. justify-content: center;
  1322. .user-tag {
  1323. font-size: 20rpx;
  1324. color: #666666;
  1325. background: #F5F5F5;
  1326. padding: 6rpx 12rpx;
  1327. border-radius: 8rpx;
  1328. border: 1rpx solid #E0E0E0;
  1329. }
  1330. }
  1331. }
  1332. }
  1333. }
  1334. /* 个人魅力指数 */
  1335. .charm-index-section {
  1336. margin: 25rpx 35rpx;
  1337. .charm-card {
  1338. background: #FFFFFF;
  1339. border-radius: 16rpx;
  1340. padding: 35rpx;
  1341. border: 2rpx solid #E0E0E0;
  1342. .charm-header {
  1343. display: flex;
  1344. justify-content: space-between;
  1345. align-items: center;
  1346. margin-bottom: 25rpx;
  1347. .charm-title {
  1348. font-size: 32rpx;
  1349. font-weight: 800;
  1350. color: #333333;
  1351. }
  1352. .charm-score {
  1353. font-size: 48rpx;
  1354. font-weight: 800;
  1355. color: #E91E63;
  1356. }
  1357. }
  1358. .charm-progress {
  1359. margin-bottom: 30rpx;
  1360. .progress-bar {
  1361. width: 100%;
  1362. height: 12rpx;
  1363. background: #FFE5F1;
  1364. border-radius: 6rpx;
  1365. overflow: hidden;
  1366. margin-bottom: 10rpx;
  1367. .progress-fill {
  1368. height: 100%;
  1369. background: #E91E63;
  1370. border-radius: 6rpx;
  1371. transition: width 1s ease;
  1372. }
  1373. }
  1374. .progress-text {
  1375. font-size: 24rpx;
  1376. color: #666666;
  1377. text-align: center;
  1378. display: block;
  1379. }
  1380. }
  1381. .charm-items {
  1382. display: flex;
  1383. flex-direction: column;
  1384. gap: 20rpx;
  1385. margin-bottom: 25rpx;
  1386. .charm-item {
  1387. display: flex;
  1388. justify-content: space-between;
  1389. align-items: center;
  1390. .charm-item-label {
  1391. font-size: 26rpx;
  1392. color: #666666;
  1393. font-weight: 500;
  1394. }
  1395. .charm-item-progress {
  1396. display: flex;
  1397. align-items: center;
  1398. gap: 12rpx;
  1399. flex: 1;
  1400. margin-left: 20rpx;
  1401. .item-progress-bar {
  1402. flex: 1;
  1403. height: 8rpx;
  1404. background: #FFE5F1;
  1405. border-radius: 4rpx;
  1406. overflow: hidden;
  1407. .item-progress-fill {
  1408. height: 100%;
  1409. background: #E91E63;
  1410. border-radius: 4rpx;
  1411. transition: width 1s ease;
  1412. }
  1413. }
  1414. .charm-item-score {
  1415. font-size: 24rpx;
  1416. color: #E91E63;
  1417. font-weight: 600;
  1418. min-width: 60rpx;
  1419. text-align: right;
  1420. }
  1421. }
  1422. }
  1423. }
  1424. .charm-tip {
  1425. padding: 20rpx;
  1426. background: #FFF8E1;
  1427. border-radius: 8rpx;
  1428. border: 2rpx solid #FFD54F;
  1429. .tip-text {
  1430. font-size: 24rpx;
  1431. color: #666666;
  1432. line-height: 1.5;
  1433. }
  1434. }
  1435. }
  1436. }
  1437. /* 底部占位 */
  1438. .bottom-placeholder {
  1439. height: 30rpx;
  1440. }
  1441. /* 底部导航栏 */
  1442. .tabbar {
  1443. position: fixed;
  1444. bottom: 0;
  1445. left: 0;
  1446. right: 0;
  1447. display: flex;
  1448. background-color: #FFFFFF;
  1449. border-top: 1rpx solid #F0F0F0;
  1450. padding-bottom: constant(safe-area-inset-bottom);
  1451. padding-bottom: env(safe-area-inset-bottom);
  1452. z-index: 999;
  1453. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  1454. .tabbar-item {
  1455. flex: 1;
  1456. display: flex;
  1457. flex-direction: column;
  1458. align-items: center;
  1459. justify-content: center;
  1460. padding: 15rpx 0;
  1461. position: relative;
  1462. .tabbar-icon {
  1463. font-size: 44rpx;
  1464. margin-bottom: 5rpx;
  1465. }
  1466. .tabbar-text {
  1467. font-size: 22rpx;
  1468. color: #666666;
  1469. }
  1470. &.active {
  1471. .tabbar-text {
  1472. color: #E91E63;
  1473. font-weight: bold;
  1474. }
  1475. }
  1476. }
  1477. }
  1478. /* 成功案例 */
  1479. .success-case-section {
  1480. .success-case-grid {
  1481. display: grid;
  1482. grid-template-columns: repeat(2, 1fr);
  1483. gap: 20rpx;
  1484. margin: 0 35rpx;
  1485. .case-card {
  1486. background: #FFFFFF;
  1487. border-radius: 16rpx;
  1488. overflow: hidden;
  1489. border: 2rpx solid #E0E0E0;
  1490. transition: all 0.3s ease;
  1491. &:active {
  1492. transform: scale(0.98);
  1493. opacity: 0.9;
  1494. }
  1495. .case-image-wrapper {
  1496. width: 100%;
  1497. height: 320rpx;
  1498. position: relative;
  1499. overflow: hidden;
  1500. .case-image {
  1501. width: 100%;
  1502. height: 100%;
  1503. }
  1504. }
  1505. .case-content {
  1506. padding: 20rpx;
  1507. .couple-names {
  1508. font-size: 28rpx;
  1509. font-weight: 700;
  1510. color: #333333;
  1511. margin-bottom: 10rpx;
  1512. line-height: 1.3;
  1513. }
  1514. .case-quote {
  1515. font-size: 24rpx;
  1516. color: #666666;
  1517. line-height: 1.5;
  1518. margin-bottom: 12rpx;
  1519. display: -webkit-box;
  1520. -webkit-box-orient: vertical;
  1521. -webkit-line-clamp: 2;
  1522. overflow: hidden;
  1523. }
  1524. .case-meta {
  1525. .marriage-date {
  1526. font-size: 22rpx;
  1527. color: #999999;
  1528. line-height: 1.4;
  1529. }
  1530. }
  1531. }
  1532. }
  1533. }
  1534. }
  1535. @keyframes marquee {
  1536. 0% {
  1537. transform: translateX(0);
  1538. }
  1539. 100% {
  1540. transform: translateX(-50%);
  1541. }
  1542. }
  1543. .tabbar-badge {
  1544. position: absolute;
  1545. top: 8rpx;
  1546. right: 50%;
  1547. margin-right: -40rpx;
  1548. min-width: 32rpx;
  1549. height: 32rpx;
  1550. line-height: 32rpx;
  1551. padding: 0 6rpx;
  1552. background-color: #FA5151;
  1553. border-radius: 16rpx;
  1554. font-size: 20rpx;
  1555. color: #FFFFFF;
  1556. text-align: center;
  1557. }
  1558. </style>