xiangyixiangqin/miniapp/pages/interact/favoritedMe.vue
2026-01-23 19:38:29 +08:00

607 lines
14 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="interact-page">
<!-- 页面加载状态 -->
<Loading type="page" :loading="pageLoading" />
<!-- 顶部背景图 -->
<view class="top-bg">
<image src="/static/title_bg.png" mode="aspectFill" class="bg-img" />
</view>
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="navbar-back" @click="handleBack">
<text class="back-icon"></text>
</view>
<text class="navbar-title">{{ activeTab === 'favoritedMe' ? '收藏我' : '我收藏的' }}</text>
<view class="navbar-placeholder"></view>
</view>
</view>
<!-- Tab切换 -->
<view class="tab-header" :style="{ top: (statusBarHeight + 44) + 'px' }">
<view
class="tab-item"
:class="{ active: activeTab === 'favoritedMe' }"
@click="switchTab('favoritedMe')"
>
<text>收藏我</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'myFavorite' }"
@click="switchTab('myFavorite')"
>
<text>我收藏的</text>
</view>
</view>
<!-- 可滚动内容区域 -->
<scroll-view
class="content-scroll"
scroll-y
:style="{
top: (statusBarHeight + 44 + 80) + 'px',
height: 'calc(100vh - ' + (statusBarHeight + 44 + 80) + 'px)'
}"
@scrolltolower="handleScrollToLower"
>
<!-- 用户列表 -->
<view class="user-list" v-if="list.length > 0">
<view
class="user-card"
v-for="item in list"
:key="item.id || item.userId"
@click="handleUserClick(item.userId)"
>
<!-- 时间信息 -->
<view class="time-row">
<text class="time-text">{{ formatViewTime(item.createTime) }}{{ activeTab === 'favoritedMe' ? '收藏了我' : '收藏' }}</text>
</view>
<!-- 性别年份 -->
<view class="title-row">
<text class="gender-year">{{ item.gender === 1 ? '男' : '女' }} · {{ item.birthYear || getYearFromAge(item.age) }}年</text>
</view>
<!-- 内容区域 -->
<view class="content-row">
<!-- 左侧信息 -->
<view class="info-section" :class="{ 'full-width': !item.isPhotoPublic || !item.firstPhoto }">
<view class="info-grid">
<view class="info-item">
<text class="label">现居</text>
<text class="value">{{ item.workCity || '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">身高</text>
<text class="value">{{ item.height ? item.height + 'cm' : '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">学历</text>
<text class="value">{{ item.educationName || '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">体重</text>
<text class="value">{{ item.weight ? item.weight + 'kg' : '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">职业</text>
<text class="value">{{ item.occupation || '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">家乡</text>
<text class="value">{{ item.hometown || '未填写' }}</text>
</view>
</view>
</view>
<!-- 右侧照片 -->
<view class="photo-section" v-if="item.isPhotoPublic && item.firstPhoto">
<image class="user-photo" :src="getFullUrl(item.firstPhoto)" mode="aspectFill" />
</view>
</view>
<!-- 简介 -->
<view class="intro-section" v-if="item.intro">
<text class="intro-text">{{ item.intro }}</text>
</view>
<!-- 操作按钮 -->
<view class="action-buttons" @click.stop>
<button class="btn-detail" @click="handleUserClick(item.userId)">查看详细资料</button>
<button class="btn-contact" @click="handleContact(item.userId)">联系对方</button>
</view>
</view>
</view>
<!-- 空状态 -->
<Empty
v-else-if="!listLoading"
:text="activeTab === 'favoritedMe' ? '暂无人收藏你' : '你还没有收藏别人'"
buttonText="去相亲"
buttonUrl="/pages/index/index"
/>
<!-- 加载更多 -->
<Loading
type="more"
:loading="listLoading"
:noMore="noMoreData && list.length > 0"
/>
</scroll-view>
</view>
</template>
<script>
import { ref, computed, onMounted } from 'vue'
import { getFavoritedMe, getMyFavorite } from '@/api/interact.js'
import { getFullImageUrl } from '@/utils/image.js'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import Loading from '@/components/Loading/index.vue'
import Empty from '@/components/Empty/index.vue'
export default {
name: 'FavoritedMePage',
components: {
Loading,
Empty
},
setup() {
const userStore = useUserStore()
const configStore = useConfigStore()
// 状态栏高度
const statusBarHeight = ref(20)
// 页面状态
const pageLoading = ref(true)
const listLoading = ref(false)
const noMoreData = ref(false)
const activeTab = ref('favoritedMe')
// 数据
const list = ref([])
const pageIndex = ref(1)
const pageSize = 20
// 从 configStore 获取默认头像
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
// 获取系统信息
const getSystemInfo = () => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20
}
})
}
// 返回上一页
const handleBack = () => {
uni.navigateBack()
}
// 获取完整图片URL
const getFullUrl = (url) => {
return getFullImageUrl(url)
}
// 从年龄计算出生年份
const getYearFromAge = (age) => {
if (!age) return ''
return new Date().getFullYear() - age
}
// 格式化时间 - 显示为"今天15:21"或"昨天15:21"或"01-20 15:21"
const formatViewTime = (timeStr) => {
if (!timeStr) return ''
const date = new Date(timeStr)
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const timeStr2 = `${hours}:${minutes}`
if (targetDate.getTime() === today.getTime()) {
return `今天${timeStr2}`
} else if (targetDate.getTime() === yesterday.getTime()) {
return `昨天${timeStr2}`
} else {
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${month}-${day} ${timeStr2}`
}
}
// 加载列表
const loadList = async (refresh = false) => {
if (refresh) {
pageIndex.value = 1
noMoreData.value = false
}
if (noMoreData.value && !refresh) return
listLoading.value = true
try {
const api = activeTab.value === 'favoritedMe' ? getFavoritedMe : getMyFavorite
const res = await api(pageIndex.value, pageSize)
if (res && (res.success || res.code === 0)) {
const newList = res.data?.items || []
if (refresh) {
list.value = newList
} else {
list.value = [...list.value, ...newList]
}
// 判断是否还有更多数据
if (newList.length < pageSize) {
noMoreData.value = true
} else {
pageIndex.value++
}
}
} catch (error) {
console.error('加载列表失败:', error)
} finally {
listLoading.value = false
}
}
// 初始化页面
const initPage = async () => {
pageLoading.value = true
try {
getSystemInfo()
await loadList(true)
} finally {
pageLoading.value = false
}
}
// 切换Tab
const switchTab = (tab) => {
if (activeTab.value === tab) return
activeTab.value = tab
// 重新加载数据
loadList(true)
}
// 滚动到底部加载更多
const handleScrollToLower = () => {
if (!noMoreData.value && !listLoading.value) {
loadList()
}
}
// 点击用户
const handleUserClick = (userId) => {
uni.navigateTo({ url: `/pages/profile/detail?userId=${userId}` })
}
// 联系用户
const handleContact = (userId) => {
// 检查是否完善资料
if (!userStore.isProfileCompleted) {
uni.showModal({
title: '提示',
content: '请先完善您的资料,才能联系对方',
confirmText: '去完善',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: '/pages/profile/edit' })
}
}
})
return
}
uni.navigateTo({ url: `/pages/chat/index?targetUserId=${userId}` })
}
onMounted(() => {
initPage()
})
return {
statusBarHeight,
pageLoading,
listLoading,
noMoreData,
activeTab,
list,
defaultAvatar,
handleBack,
switchTab,
formatViewTime,
getFullUrl,
getYearFromAge,
handleUserClick,
handleContact,
handleScrollToLower,
loadList
}
},
// 下拉刷新
onPullDownRefresh() {
this.loadList && this.loadList(true).finally(() => {
uni.stopPullDownRefresh()
})
}
}
</script>
<style lang="scss" scoped>
.interact-page {
height: 100vh;
background-color: #f5f5f5;
overflow: hidden;
}
// 顶部背景图
.top-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 400rpx;
z-index: 0;
.bg-img {
width: 100%;
height: 100%;
}
}
// 自定义导航栏
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 200;
.navbar-content {
position: relative;
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
.navbar-back {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.back-icon {
font-size: 56rpx;
color: #333;
font-weight: 300;
}
}
.navbar-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.navbar-placeholder {
width: 80rpx;
}
}
}
// Tab切换
.tab-header {
position: fixed;
left: 0;
right: 0;
z-index: 100;
display: flex;
padding: 20rpx 40rpx;
gap: 24rpx;
.tab-item {
padding: 20rpx 40rpx;
border-radius: 40rpx;
background-color: #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
text {
font-size: 30rpx;
color: #2F2F2F;
}
&.active {
background-color: #FFF0F0;
position: relative;
box-shadow: 0 4rpx 16rpx rgba(255, 107, 107, 0.2);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 40rpx;
padding: 2rpx;
background: linear-gradient(to right, #FFCBCB, #FF7B7B);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
text {
color: #FF5F5F;
font-weight: 500;
}
}
}
}
// 可滚动内容区域
.content-scroll {
position: fixed;
left: 0;
right: 0;
z-index: 1;
}
// 用户列表
.user-list {
padding: 20rpx;
}
// 用户卡片
.user-card {
background: linear-gradient(to bottom, #FFEBEA 0%, #FFFFFF 100%);
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
}
// 时间行
.time-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.time-text {
font-size: 26rpx;
color: #666;
}
}
// 标题行
.title-row {
margin-bottom: 24rpx;
.gender-year {
font-size: 40rpx;
font-weight: 600;
color: #ff6b6b;
}
}
// 内容区域
.content-row {
display: flex;
justify-content: space-between;
}
// 信息区域
.info-section {
flex: 1;
&.full-width {
.info-grid {
display: flex;
flex-wrap: wrap;
}
.info-item {
width: 50%;
}
}
}
.info-grid {
.info-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.label {
font-size: 28rpx;
color: #999;
width: 80rpx;
flex-shrink: 0;
}
.value {
font-size: 28rpx;
color: #333;
margin-left: 16rpx;
}
}
}
// 照片区域
.photo-section {
width: 200rpx;
height: 200rpx;
margin-left: 24rpx;
flex-shrink: 0;
.user-photo {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
}
// 简介区域
.intro-section {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f5f5f5;
.intro-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
// 操作按钮
.action-buttons {
display: flex;
gap: 24rpx;
margin-top: 32rpx;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
border-radius: 40rpx;
border: none;
&::after {
border: none;
}
}
.btn-detail {
background: #fff;
color: #333;
border: 2rpx solid #FFCBCB;
}
.btn-contact {
background: linear-gradient(to right, #FFBDC2, #FF8A93);
color: #fff;
}
}
</style>