xiangyixiangqin/miniapp/pages/index/index.vue
2026-01-29 02:44:15 +08:00

1276 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="home-page">
<!-- ==================== 弹窗层 ==================== -->
<!-- 页面加载状态 -->
<Loading type="page" :loading="pageLoading" />
<!-- 性别选择弹窗最高优先级- 首次进入时选择"找儿媳""找女婿" -->
<Popup
:visible="showGenderPopup"
type="gender"
:showClose="false"
:closeOnMask="false"
@genderSelect="handleGenderSelect"
/>
<!-- 每日首次弹窗 - 运营活动弹窗每天首次进入显示 -->
<Popup
:visible="showDailyPopup"
type="daily"
:imageUrl="dailyPopup?.imageUrl"
:title="dailyPopup?.title"
:buttonText="dailyPopup?.buttonText"
:linkUrl="dailyPopup?.linkUrl"
@close="handleCloseDailyPopup"
@confirm="handleDailyPopupConfirm"
/>
<!-- 解锁确认弹窗 - 联系用户前确认是否消耗解锁次数 -->
<Popup
:visible="showUnlockPopup"
type="unlock"
:remainingQuota="remainingUnlockQuota"
@close="handleCloseUnlockPopup"
@unlock="handleConfirmUnlock"
@goMember="handleGoMember"
/>
<!-- 服务号关注弹窗 - 引导用户关注微信服务号接收消息通知 -->
<Popup
:visible="showServiceAccountPopup"
type="serviceAccount"
:imageUrl="serviceAccountPopup?.imageUrl"
:linkUrl="serviceAccountPopup?.linkUrl"
@close="handleCloseServiceAccountPopup"
@confirm="handleServiceAccountPopupConfirm"
/>
<!-- 资料审核中弹窗 -->
<Popup
:visible="showAuditingPopup"
type="auditing"
@close="handleCloseAuditingPopup"
/>
<!-- ==================== 固定底部条 ==================== -->
<!-- 会员广告条 - 固定在底部优先级最高 -->
<view class="member-ad-section" v-if="showMemberAd">
<view class="member-ad-bar" :style="memberAdBgStyle" @click="handleMemberAdClick">
<view class="ad-close" @click.stop="handleCloseMemberAd">
<text>×</text>
</view>
</view>
</view>
<!-- 小程序订阅消息提醒条 - 固定在底部,会员广告关闭后显示 -->
<view class="subscribe-reminder-section" v-if="showSubscribeReminder && !showMemberAd">
<view class="subscribe-reminder-bar" :style="subscribeReminderBgStyle">
<view class="reminder-content">
<text class="reminder-text">开启消息通知不错过任何心动对象</text>
</view>
<view class="reminder-btn" @click="handleOpenSubscribe">打开</view>
<view class="reminder-close" @click.stop="handleCloseSubscribeReminder">
<text>×</text>
</view>
</view>
</view>
<!-- ==================== 主内容区 ==================== -->
<!-- 整页滚动区域 - 支持下拉刷新和上拉加载更多 -->
<scroll-view
class="page-scroll"
scroll-y
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="handleRefresh"
@scrolltolower="handleScrollToLower"
>
<!-- Banner 区域导航栏和搜索框浮在 Banner -->
<view class="banner-section">
<!-- Banner 轮播图 -->
<swiper
v-if="banners.length > 0"
class="banner-swiper"
:indicator-dots="banners.length > 1"
:autoplay="true"
:interval="3000"
:duration="500"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#FF6A6A"
circular
>
<swiper-item
v-for="banner in banners"
:key="banner.id"
@click="handleBannerClick(banner)"
>
<image class="banner-image" :src="banner.imageUrl" mode="aspectFill" />
</swiper-item>
</swiper>
<!-- 无 banner 时的默认渐变背景 -->
<view v-else class="banner-placeholder"></view>
<!-- 浮层:导航栏 + 搜索框 -->
<view class="banner-overlay">
<!-- 自定义导航栏 - 适配状态栏高度 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="header-title">相宜相亲</view>
</view>
</view>
<!-- 搜索框 - 点击跳转搜索页 -->
<view class="search-section">
<view class="search-bar" @click="handleSearchClick">
<text class="search-icon">🔍</text>
<text class="search-placeholder">搜索你心目中的TA</text>
</view>
</view>
</view>
</view>
<!-- 金刚位导航 - 快捷功能入口 -->
<view class="kingkong-section" v-if="kingKongs.length > 0">
<view class="kingkong-grid">
<view
class="kingkong-item"
v-for="item in kingKongs"
:key="item.id"
@click="handleKingKongClick(item)"
>
<image class="kingkong-icon" :src="item.iconUrl" mode="aspectFit" />
<text class="kingkong-title">{{ item.title }}</text>
</view>
</view>
</view>
<!-- 推荐标题栏 -->
<view class="section-header">
<image class="recommend-title-img" src="/static/recommend_title.png" mode="widthFix" />
</view>
<!-- 推荐用户列表 -->
<view class="user-list" v-if="recommendList.length > 0">
<UserCard
v-for="user in recommendList"
:key="user.userId"
:userId="user.userId"
:nickname="user.nickname"
:avatar="user.avatar"
:gender="user.gender"
:age="user.age"
:birthYear="user.birthYear"
:workCity="user.workCity"
:hometown="user.hometown"
:height="user.height"
:weight="user.weight"
:education="user.education"
:educationName="user.educationName"
:occupation="user.occupation"
:monthlyIncome="user.monthlyIncome"
:intro="user.intro"
:isMember="user.isMember"
:memberLevel="user.memberLevel"
:isRealName="user.isRealName"
:isPhotoPublic="user.isPhotoPublic"
:firstPhoto="user.firstPhoto"
:viewedToday="user.viewedToday"
@click="handleUserClick"
@contact="handleUserContact"
/>
</view>
<!-- 空状态 - 无推荐用户时显示 -->
<view class="empty-wrapper" v-else-if="!listLoading">
<Empty text="暂无推荐用户" :showButton="false" />
</view>
<!-- 加载更多状态 -->
<Loading type="more" :loading="listLoading" :noMore="noMoreData" />
<!-- 底部占位(为 TabBar 留出空间) -->
<view class="bottom-placeholder"></view>
</scroll-view>
</view>
</template>
<script>
/**
* 首页 - 相亲小程序主页面
*
* 功能模块:
* 1. 弹窗系统 - 性别选择、每日弹窗、解锁确认、服务号关注
* 2. 底部提示条 - 会员广告、订阅消息提醒
* 3. Banner 轮播 - 支持内部跳转、外部链接、小程序跳转
* 4. 金刚位导航 - 快捷功能入口
* 5. 推荐用户列表 - 分页加载、下拉刷新
* 6. 用户解锁流程 - 登录检查、资料检查、解锁确认
*/
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { getRecommend } from '@/api/user.js'
import { checkUnlock, unlock } from '@/api/interact.js'
import { getFullImageUrl } from '@/utils/image.js'
import { navigateToPage, navigateToWebView, LINK_TYPE } from '@/utils/navigate.js'
import Popup from '@/components/Popup/index.vue'
import UserCard from '@/components/UserCard/index.vue'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
// ==================== 常量定义 ====================
/** 用户状态刷新间隔(毫秒)- 防止频繁请求 */
const REFRESH_INTERVAL = 30 * 1000
/** 每页加载数量 */
const PAGE_SIZE = 10
/** 订阅消息错误码 - 用户已关闭订阅权限 */
const SUBSCRIBE_ERROR_PERMISSION_DENIED = 20004
export default {
name: 'HomePage',
components: {
Popup,
UserCard,
Loading,
Empty
},
setup() {
// ==================== Store ====================
const userStore = useUserStore()
const configStore = useConfigStore()
// ==================== 页面状态 ====================
/** 状态栏高度(用于自定义导航栏适配) */
const statusBarHeight = ref(20)
/** 页面整体加载状态 */
const pageLoading = ref(true)
/** 列表加载状态(加载更多时显示) */
const listLoading = ref(false)
/** 是否已加载全部数据 */
const noMoreData = ref(false)
/** 下拉刷新状态 */
const isRefreshing = ref(false)
// ==================== 分页参数 ====================
/** 当前页码 */
const pageIndex = ref(1)
/** 数据总数 */
const total = ref(0)
// ==================== 数据 ====================
/** 推荐用户列表 */
const recommendList = ref([])
// ==================== 解锁弹窗状态 ====================
/** 是否显示解锁弹窗 */
const showUnlockPopup = ref(false)
/** 待解锁的目标用户 ID */
const unlockTargetUserId = ref(0)
/** 剩余免费解锁次数 */
const remainingUnlockQuota = ref(0)
// ==================== 计算属性 ====================
/**
* Banner 列表(处理图片 URL
* 将相对路径转换为完整的图片 URL
*/
const banners = computed(() => {
return configStore.banners.map(banner => ({
...banner,
imageUrl: getFullImageUrl(banner.imageUrl)
}))
})
/**
* 金刚位列表(处理图标 URL
*/
const kingKongs = computed(() => {
return configStore.kingKongs.map(item => ({
...item,
iconUrl: getFullImageUrl(item.iconUrl)
}))
})
/** 是否显示会员广告条 */
const showMemberAd = computed(() => configStore.showMemberAd)
/** 会员广告配置 */
const memberAdConfig = computed(() => configStore.memberAdConfig)
/** 是否显示订阅消息提醒 */
const showSubscribeReminder = computed(() => configStore.showSubscribeReminder)
/** 订阅消息提醒配置 */
const subscribeReminderConfig = computed(() => configStore.subscribeReminderConfig)
/**
* 会员广告条背景样式
* 优先使用配置的图片,否则使用默认渐变
*/
const memberAdBgStyle = computed(() => {
const config = configStore.memberAdConfig
if (config?.imageUrl) {
return {
backgroundImage: `url(${getFullImageUrl(config.imageUrl)})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
}
}
return {
background: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%)'
}
})
/**
* 订阅消息提醒条背景样式
*/
const subscribeReminderBgStyle = computed(() => {
const config = configStore.subscribeReminderConfig
if (config?.imageUrl) {
return {
backgroundImage: `url(${getFullImageUrl(config.imageUrl)})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
}
}
return {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
}
})
/** 是否显示性别选择弹窗 */
const showGenderPopup = computed(() => configStore.showGenderPopup)
/** 是否显示每日弹窗 */
const showDailyPopup = computed(() => configStore.showDailyPopup)
/** 是否显示服务号关注弹窗 */
const showServiceAccountPopup = computed(() => configStore.showServiceAccountPopup)
/**
* 服务号弹窗配置(处理图片 URL
*/
const serviceAccountPopup = computed(() => {
const popup = configStore.serviceAccountPopup
if (!popup) return null
return {
...popup,
imageUrl: getFullImageUrl(popup.imageUrl)
}
})
/**
* 每日弹窗配置(处理图片 URL
*/
const dailyPopup = computed(() => {
const popup = configStore.dailyPopup
if (!popup) return null
return {
...popup,
imageUrl: getFullImageUrl(popup.imageUrl)
}
})
// ==================== 数据加载方法 ====================
/**
* 加载推荐用户列表
*
* @param {boolean} isLoadMore - 是否为加载更多false 则重新加载)
*/
const loadRecommendList = async (isLoadMore = false) => {
// 防止重复请求
if (listLoading.value) return
// 已无更多数据时不再请求
if (isLoadMore && noMoreData.value) return
listLoading.value = true
try {
// 非加载更多时,重置分页状态
if (!isLoadMore) {
pageIndex.value = 1
recommendList.value = []
noMoreData.value = false
}
// 获取用户的性别偏好0=全部1=男2=女)
const genderPref = userStore.genderPreference || 0
const res = await getRecommend(pageIndex.value, PAGE_SIZE, genderPref)
if (res?.code === 0 && res.data) {
const items = res.data.items || []
total.value = res.data.total || 0
// 追加或替换数据
if (isLoadMore) {
recommendList.value = [...recommendList.value, ...items]
} else {
recommendList.value = items
}
// 判断是否已加载全部
noMoreData.value = recommendList.value.length >= total.value
pageIndex.value++
}
} catch (error) {
console.error('[HomePage] 加载推荐列表失败:', error)
// 首次加载失败时提示用户
if (!isLoadMore) {
uni.showToast({
title: '加载失败,请下拉刷新',
icon: 'none'
})
}
} finally {
listLoading.value = false
}
}
/**
* 检查并显示弹窗
* 根据用户状态决定显示哪些弹窗
*/
const checkPopups = () => {
configStore.checkPopupDisplay({
genderPreference: userStore.genderPreference,
isProfileCompleted: userStore.isProfileCompleted,
isMember: userStore.isMember
})
}
/**
* 初始化页面
* 获取系统信息、恢复用户状态、加载配置和数据
*/
const initPage = async () => {
pageLoading.value = true
try {
// 获取状态栏高度(用于自定义导航栏适配)
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20
}
})
// 从本地存储恢复用户状态
userStore.restoreFromStorage()
// 确保应用配置已加载App.vue 中已调用,这里等待完成)
if (!configStore.isLoaded) {
await configStore.loadAppConfig()
}
// 加载推荐用户列表
await loadRecommendList()
// 检查并显示弹窗
checkPopups()
} catch (error) {
console.error('[HomePage] 初始化页面失败:', error)
} finally {
pageLoading.value = false
}
}
// ==================== 搜索相关 ====================
/**
* 搜索框点击 - 跳转搜索页
*/
const handleSearchClick = () => {
navigateToPage('/pages/search/index')
}
// ==================== Banner 相关 ====================
/**
* Banner 点击处理
* 根据 linkType 决定跳转方式
*
* @param {Object} banner - Banner 配置对象
*/
const handleBannerClick = (banner) => {
if (!banner.linkUrl) return
navigateToPage(banner.linkUrl, banner.linkType || LINK_TYPE.INTERNAL)
}
// ==================== 金刚位相关 ====================
/**
* 金刚位点击处理
*
* @param {Object} item - 金刚位配置对象
*/
const handleKingKongClick = (item) => {
if (!item.linkUrl) return
navigateToPage(item.linkUrl, LINK_TYPE.INTERNAL)
}
// ==================== 会员广告相关 ====================
/**
* 会员广告点击 - 跳转会员页
*/
const handleMemberAdClick = () => {
const linkUrl = configStore.memberAdConfig?.linkUrl || '/pages/member/index'
navigateToPage(linkUrl)
}
/**
* 关闭会员广告
* 关闭后检查是否需要显示订阅消息提醒
*/
const handleCloseMemberAd = () => {
configStore.closeMemberAd()
configStore.checkSubscribeReminder(userStore.isMember)
}
// ==================== 订阅消息相关 ====================
/**
* 打开小程序订阅消息权限
* 调用微信订阅消息 API 请求用户授权
*/
const handleOpenSubscribe = () => {
// 从配置获取模板 ID如果没有则使用默认值
const tmplIds = configStore.subscribeReminderConfig?.tmplIds ||
['dQdK2i7ZDkDGQ2Knifv82rDx9HCzR1aE71YmR8JjwBc']
uni.requestSubscribeMessage({
tmplIds,
success: (res) => {
console.log('[HomePage] 订阅消息结果:', res)
uni.showToast({ title: '设置成功', icon: 'success' })
configStore.closeSubscribeReminder()
},
fail: (err) => {
console.error('[HomePage] 订阅消息失败:', err)
handleSubscribeError(err)
}
})
}
/**
* 处理订阅消息错误
*
* @param {Object} err - 错误对象
*/
const handleSubscribeError = (err) => {
// 用户已关闭订阅权限,引导去设置页开启
if (err.errCode === SUBSCRIBE_ERROR_PERMISSION_DENIED) {
uni.showModal({
title: '提示',
content: '您已关闭订阅消息权限,请在小程序设置中开启',
confirmText: '去设置',
success: (modalRes) => {
if (modalRes.confirm) {
uni.openSetting()
}
}
})
}
}
/**
* 关闭订阅消息提醒条
*/
const handleCloseSubscribeReminder = () => {
configStore.closeSubscribeReminder()
}
// ==================== 性别选择弹窗相关 ====================
/**
* 性别选择处理
*
* @param {number} gender - 性别偏好1=找女婿2=找儿媳)
*/
const handleGenderSelect = (gender) => {
userStore.setGenderPref(gender)
configStore.closeGenderPopup()
// 重新加载推荐列表(根据新的性别偏好)
loadRecommendList()
checkPopups()
}
// ==================== 每日弹窗相关 ====================
/**
* 关闭每日弹窗
*/
const handleCloseDailyPopup = () => {
configStore.closeDailyPopup()
checkPopups()
}
/**
* 每日弹窗确认按钮点击
*/
const handleDailyPopupConfirm = () => {
configStore.closeDailyPopup()
}
// ==================== 服务号关注弹窗相关 ====================
/**
* 关闭服务号关注弹窗
*/
const handleCloseServiceAccountPopup = () => {
configStore.closeServiceAccountPopup()
}
/**
* 服务号关注弹窗确认按钮点击
* 跳转 WebView 打开服务号文章
*/
const handleServiceAccountPopupConfirm = () => {
const linkUrl = configStore.serviceAccountPopup?.linkUrl
if (linkUrl) {
navigateToWebView(linkUrl)
}
configStore.closeServiceAccountPopup()
}
// ==================== 用户卡片相关 ====================
/**
* 用户卡片点击 - 跳转用户详情页
*
* @param {number} userId - 用户 ID
*/
const handleUserClick = (userId) => {
navigateToPage(`/pages/profile/detail?userId=${userId}`)
}
/**
* 检查用户登录状态
*
* @returns {boolean} 是否已登录
*/
const checkLoginStatus = () => {
if (!userStore.isLoggedIn) {
uni.showModal({
title: '提示',
content: '请先登录后再联系对方',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
navigateToPage('/pages/login/index')
}
}
})
return false
}
return true
}
// ==================== 审核中弹窗状态 ====================
/** 是否显示审核中弹窗 */
const showAuditingPopup = ref(false)
/**
* 检查用户资料完善状态
*
* @returns {boolean} 资料是否已完善
*/
const checkProfileStatus = () => {
// 资料审核中
if (userStore.isProfileAuditing) {
showAuditingPopup.value = true
return false
}
// 资料未完善
if (!userStore.isProfileCompleted) {
uni.showModal({
title: '提示',
content: '请先完善您的资料,才能联系对方',
confirmText: '去完善',
success: (res) => {
if (res.confirm) {
navigateToPage('/pages/profile/edit')
}
}
})
return false
}
return true
}
/**
* 关闭审核中弹窗
*/
const handleCloseAuditingPopup = () => {
showAuditingPopup.value = false
}
/**
* 检查用户解锁状态并处理
*
* @param {number} userId - 目标用户 ID
*/
const checkAndHandleUnlock = async (userId) => {
try {
uni.showLoading({ title: '加载中...' })
const res = await checkUnlock(userId)
uni.hideLoading()
if (res?.code === 0 && res.data) {
if (res.data.isUnlocked) {
// 已解锁,直接跳转聊天页
navigateToPage(`/pages/chat/index?targetUserId=${userId}`)
} else if (userStore.isMember) {
// 会员直接解锁,不显示弹窗
try {
uni.showLoading({ title: '解锁中...' })
const unlockRes = await unlock(userId)
uni.hideLoading()
if (unlockRes?.code === 0) {
navigateToPage(`/pages/chat/index?targetUserId=${userId}`)
} else {
uni.showToast({ title: unlockRes?.message || '解锁失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
uni.showToast({ title: '解锁失败', icon: 'none' })
}
} else {
// 非会员,显示解锁确认弹窗
unlockTargetUserId.value = userId
remainingUnlockQuota.value = res.data.remainingUnlockQuota || 0
showUnlockPopup.value = true
}
} else {
uni.showToast({ title: '检查解锁状态失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('[HomePage] 检查解锁状态失败:', error)
uni.showToast({ title: '网络错误', icon: 'none' })
}
}
/**
* 联系用户按钮点击
* 流程:登录检查 → 资料检查 → 解锁状态检查 → 跳转聊天
*
* @param {number} userId - 目标用户 ID
*/
const handleUserContact = async (userId) => {
// 1. 检查登录状态
if (!checkLoginStatus()) return
// 2. 检查资料完善状态
if (!checkProfileStatus()) return
// 3. 检查解锁状态
await checkAndHandleUnlock(userId)
}
// ==================== 解锁弹窗相关 ====================
/**
* 关闭解锁弹窗
*/
const handleCloseUnlockPopup = () => {
showUnlockPopup.value = false
}
/**
* 确认解锁
* 消耗解锁次数,成功后跳转聊天页
*/
const handleConfirmUnlock = async () => {
// 无剩余次数时跳转会员页
if (remainingUnlockQuota.value <= 0) {
showUnlockPopup.value = false
navigateToPage('/pages/member/index')
return
}
try {
uni.showLoading({ title: '解锁中...' })
const unlockRes = await unlock(unlockTargetUserId.value)
uni.hideLoading()
if (unlockRes?.code === 0) {
showUnlockPopup.value = false
uni.showToast({ title: '解锁成功', icon: 'success' })
// 延迟跳转,让用户看到成功提示
setTimeout(() => {
navigateToPage(`/pages/chat/index?targetUserId=${unlockTargetUserId.value}`)
}, 1000)
} else {
uni.showToast({
title: unlockRes?.message || '解锁失败',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('[HomePage] 解锁失败:', error)
uni.showToast({ title: '解锁失败', icon: 'none' })
}
}
/**
* 跳转会员页面
*/
const handleGoMember = () => {
showUnlockPopup.value = false
navigateToPage('/pages/member/index')
}
// ==================== 滚动相关 ====================
/**
* 滚动到底部 - 加载更多
*/
const handleScrollToLower = () => {
if (!noMoreData.value && !listLoading.value) {
loadRecommendList(true)
}
}
/**
* 下拉刷新
*/
const handleRefresh = async () => {
isRefreshing.value = true
try {
await loadRecommendList(false)
} finally {
isRefreshing.value = false
}
}
// ==================== 生命周期 ====================
onMounted(() => {
initPage()
})
// ==================== 返回模板需要的数据和方法 ====================
return {
// 状态
statusBarHeight,
pageLoading,
listLoading,
noMoreData,
isRefreshing,
// 数据
banners,
kingKongs,
recommendList,
// 会员广告
showMemberAd,
memberAdConfig,
memberAdBgStyle,
// 订阅消息
showSubscribeReminder,
subscribeReminderConfig,
subscribeReminderBgStyle,
// 弹窗
showGenderPopup,
showDailyPopup,
showServiceAccountPopup,
serviceAccountPopup,
dailyPopup,
showUnlockPopup,
remainingUnlockQuota,
showAuditingPopup,
// 方法
initPage,
loadRecommendList,
handleSearchClick,
handleBannerClick,
handleKingKongClick,
handleMemberAdClick,
handleCloseMemberAd,
handleOpenSubscribe,
handleCloseSubscribeReminder,
handleGenderSelect,
handleCloseDailyPopup,
handleDailyPopupConfirm,
handleCloseServiceAccountPopup,
handleServiceAccountPopupConfirm,
handleUserClick,
handleUserContact,
handleCloseUnlockPopup,
handleConfirmUnlock,
handleGoMember,
handleScrollToLower,
handleRefresh,
handleCloseAuditingPopup
}
},
/**
* 页面显示时触发
* 用于刷新用户状态和检查弹窗显示
*/
onShow() {
const configStore = useConfigStore()
const userStore = useUserStore()
// 从服务器刷新用户信息(带节流,防止频繁请求)
if (userStore.isLoggedIn) {
const now = Date.now()
const lastRefresh = userStore.lastRefreshTime || 0
// 超过刷新间隔才请求
if (now - lastRefresh > REFRESH_INTERVAL) {
userStore.refreshFromServer()
}
}
// 检查各类弹窗显示
configStore.checkPopupDisplay({
genderPreference: userStore.genderPreference,
isProfileCompleted: userStore.isProfileCompleted,
isMember: userStore.isMember
})
// 检查服务号关注弹窗
configStore.checkServiceAccountPopup()
// 检查订阅消息提醒
configStore.checkSubscribeReminder(userStore.isMember)
}
}
</script>
<style lang="scss" scoped>
/* ==================== 页面容器 ==================== */
.home-page {
height: 100vh;
background-color: #F3F3F3;
}
/* ==================== 滚动区域 ==================== */
.page-scroll {
height: 100vh;
}
/* ==================== Banner 区域 ==================== */
.banner-section {
position: relative;
width: 100%;
}
.banner-swiper {
width: 100%;
height: 420rpx;
.banner-image {
width: 100%;
height: 100%;
}
}
/* 无 Banner 时的默认背景 */
.banner-placeholder {
width: 100%;
height: 420rpx;
background: linear-gradient(135deg, #FFB6C1 0%, #FFC0CB 100%);
}
/* 浮层(导航栏 + 搜索框) */
.banner-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 10;
}
/* ==================== 自定义导航栏 ==================== */
.custom-navbar {
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: center;
.header-title {
font-size: 36rpx;
font-weight: 600;
color: #fff;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
}
}
}
/* ==================== 搜索框 ==================== */
.search-section {
padding: 16rpx 24rpx;
.search-bar {
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.95);
border-radius: 40rpx;
padding: 20rpx 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
.search-icon {
font-size: 28rpx;
margin-right: 12rpx;
color: #999;
}
.search-placeholder {
font-size: 28rpx;
color: #999;
}
}
}
/* ==================== 金刚位导航 ==================== */
.kingkong-section {
padding: 20rpx;
.kingkong-grid {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.kingkong-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
padding: 16rpx 0;
.kingkong-icon {
width: 80rpx;
height: 80rpx;
margin-bottom: 12rpx;
}
.kingkong-title {
font-size: 24rpx;
color: #333;
}
}
}
}
/* ==================== 推荐标题栏 ==================== */
.section-header {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 5rpx 20rpx 24rpx;
.recommend-title-img {
width: 410rpx;
}
.section-title-wrapper {
display: flex;
align-items: center;
margin-bottom: 8rpx;
.section-title-main {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.section-title-highlight {
font-size: 42rpx;
color: #FF6A6A;
font-weight: 600;
font-style: italic;
}
}
.section-subtitle {
font-size: 24rpx;
color: #999;
}
}
/* ==================== 用户列表 ==================== */
.user-list {
padding: 0 20rpx;
}
/* ==================== 空状态 ==================== */
.empty-wrapper {
padding: 100rpx 0;
display: flex;
align-items: center;
justify-content: center;
}
/* ==================== 底部占位 ==================== */
.bottom-placeholder {
height: 120rpx;
}
/* ==================== 会员广告条 ==================== */
.member-ad-section {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
.member-ad-bar {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 110rpx;
padding: 0 24rpx;
.ad-content {
display: flex;
align-items: center;
flex: 1;
.ad-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.ad-text {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
}
.ad-btn {
background: #fff;
color: #ff6b6b;
font-size: 24rpx;
padding: 12rpx 24rpx;
border-radius: 30rpx;
margin-right: 16rpx;
font-weight: 500;
}
.ad-close {
position: absolute;
right: 16rpx;
top: 50%;
transform: translateY(-50%);
width: 44rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 36rpx;
color: #666;
line-height: 1;
}
}
}
}
/* ==================== 订阅消息提醒条 ==================== */
.subscribe-reminder-section {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 98;
.subscribe-reminder-bar {
position: relative;
display: flex;
align-items: center;
min-height: 100rpx;
padding: 0 24rpx;
.reminder-content {
flex: 1;
display: flex;
align-items: center;
.reminder-text {
font-size: 26rpx;
color: #fff;
font-weight: 500;
}
}
.reminder-btn {
background: #fff;
color: #667eea;
font-size: 24rpx;
padding: 12rpx 32rpx;
border-radius: 30rpx;
margin-right: 50rpx;
font-weight: 500;
}
.reminder-close {
position: absolute;
right: 16rpx;
top: 50%;
transform: translateY(-50%);
width: 44rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 36rpx;
color: rgba(255, 255, 255, 0.8);
line-height: 1;
}
}
}
}
</style>