index.vue 42 KB

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