1276 lines
30 KiB
Vue
1276 lines
30 KiB
Vue
<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>
|