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

1330 lines
32 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="profile-detail-page">
<!-- 加载状态 -->
<Loading type="page" :loading="loading" />
<!-- 资料完善提示弹窗 -->
<Popup
:visible="showProfilePopup"
type="profile"
@close="showProfilePopup = false"
@goProfile="handleGoProfile"
/>
<!-- 解锁确认弹窗 -->
<Popup
:visible="showUnlockPopup"
type="unlock"
:remainingQuota="remainingUnlockQuota"
@close="showUnlockPopup = false"
@unlock="handleConfirmUnlock"
@goMember="handleGoMember"
/>
<!-- 拨打电话底部弹窗 -->
<view class="phone-popup-mask" v-if="showPhonePopup" @click="showPhonePopup = false">
<view class="phone-popup" @click.stop>
<view class="phone-popup-header">
<text class="phone-popup-title">{{ userDetail?.nickname }}的联系电话</text>
<text class="phone-popup-close" @click="showPhonePopup = false">×</text>
</view>
<view class="phone-popup-content">
<text class="phone-number">{{ userDetail?.phone || '暂无电话' }}</text>
</view>
<button class="phone-popup-btn" @click="handleMakeCall" :disabled="!userDetail?.phone">
拨打电话
</button>
</view>
</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">详情资料</text>
<view class="navbar-placeholder"></view>
</view>
</view>
<!-- 内容区域 -->
<scroll-view
class="content-scroll"
scroll-y
:style="{ paddingTop: (statusBarHeight + 44) + 'px' }"
>
<view class="content-wrapper" v-if="userDetail">
<!-- 用户头像和基本信息 -->
<view class="user-header-card">
<view class="header-left">
<image
class="avatar"
:src="userDetail.avatar"
mode="aspectFill"
/>
</view>
<view class="header-right">
<view class="name-row">
<text class="nickname">{{ userDetail.nickname }}</text>
<text class="relationship" v-if="!userDetail.nickname?.includes('')">({{ relationshipText }})</text>
<view class="header-tags">
<image v-if="userDetail.isMember && memberIconUrl" class="member-icon" :src="memberIconUrl" mode="aspectFit" />
<text v-else-if="userDetail.isMember" class="tag tag-member">会员</text>
<text v-if="userDetail.isRealName" class="tag tag-realname">已实名</text>
</view>
</view>
<text class="xiangqin-no">相亲ID: {{ userDetail.xiangQinNo }}</text>
</view>
</view>
<!-- 孩子的基本资料 -->
<view class="section-card">
<view class="section-title">孩子的基本资料</view>
<view class="gender-year-row">
<text class="gender-year" :class="{ male: userDetail.childGender === 1 }">
{{ genderText }} · {{ userDetail.birthYear }}年
</text>
<view class="flex-spacer"></view>
<button class="share-btn" open-type="share">分享</button>
</view>
<view class="info-grid">
<view class="info-item">
<text class="label">年龄</text>
<text class="value">{{ userDetail.age }}岁</text>
</view>
<view class="info-item">
<text class="label">身高</text>
<text class="value">{{ userDetail.height }}cm</text>
</view>
<view class="info-item">
<text class="label">学历</text>
<text class="value">{{ educationText }}</text>
</view>
<view class="info-item">
<text class="label">体重</text>
<text class="value">{{ userDetail.weight ? userDetail.weight + 'kg' : '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">收入</text>
<text class="value">{{ incomeText }}</text>
</view>
<view class="info-item">
<text class="label">职业</text>
<text class="value">{{ userDetail.occupation || '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">现居</text>
<text class="value">{{ userDetail.workCity || '未填写' }}</text>
</view>
<view class="info-item">
<text class="label">家乡</text>
<text class="value">{{ userDetail.homeCity || '未填写' }}</text>
</view>
</view>
</view>
<!-- 孩子的照片 -->
<view class="section-card" v-if="displayPhotos.length > 0">
<view class="section-title-row">
<text class="section-title">孩子的照片</text>
<text class="photo-count">共{{ displayPhotos.length }}张</text>
</view>
<view class="photo-grid">
<view
v-for="(photo, index) in displayPhotos"
:key="index"
class="photo-item"
@click="previewPhoto(index)"
>
<image
class="photo-image"
:class="{ 'photo-blurred': !userDetail.isPhotoPublic && !isUnlocked }"
:src="photo.photoUrl"
mode="aspectFill"
/>
<view v-if="!userDetail.isPhotoPublic && !isUnlocked" class="photo-lock-overlay"></view>
</view>
</view>
</view>
<!-- 孩子的相亲说明 -->
<view class="section-card" v-if="userDetail.introduction">
<view class="section-title">孩子的相亲说明</view>
<view class="intro-content">
<text>{{ userDetail.introduction }}</text>
</view>
</view>
<!-- 孩子的择偶标准 -->
<view class="section-card">
<view class="section-title">孩子的择偶标准</view>
<view class="requirement-grid">
<view class="req-item">
<text class="label">年龄范围</text>
<text class="value">{{ requirementAgeText }}</text>
</view>
<view class="req-item">
<text class="label">身高要求</text>
<text class="value">{{ requirementHeightText }}</text>
</view>
<view class="req-item">
<text class="label">学历要求</text>
<text class="value">{{ requirementEducationText }}</text>
</view>
<view class="req-item">
<text class="label">城市要求</text>
<text class="value">{{ requirementCityText }}</text>
</view>
<view class="req-item">
<text class="label">收入要求</text>
<text class="value">{{ requirementIncomeText }}</text>
</view>
<view class="req-item">
<text class="label">房产要求</text>
<text class="value">{{ requirementHouseText }}</text>
</view>
<view class="req-item">
<text class="label">车产要求</text>
<text class="value">{{ requirementCarText }}</text>
</view>
<view class="req-item">
<text class="label">婚姻要求</text>
<text class="value">{{ requirementMarriageText }}</text>
</view>
</view>
</view>
<!-- 孩子的更多信息 -->
<view class="section-card">
<view class="section-title">孩子的更多信息</view>
<view class="more-info-grid">
<view class="more-item">
<text class="label">父母情况</text>
<text class="value">{{ userDetail.parentStatus || '未填写' }}</text>
</view>
<view class="more-item">
<text class="label">现居地有房</text>
<text class="value">{{ houseText }}</text>
</view>
<view class="more-item">
<text class="label">婚姻状况</text>
<text class="value">{{ marriageText }}</text>
</view>
<view class="more-item">
<text class="label">生肖属相</text>
<text class="value">{{ zodiacText }}</text>
</view>
<view class="more-item full-width">
<text class="label">何时结婚</text>
<text class="value">{{ expectMarryText }}</text>
</view>
</view>
</view>
<!-- 孩子的基本资料(父母信息) -->
<view class="section-card">
<view class="section-title">父母的基本资料</view>
<view class="parent-info-grid">
<view class="parent-item">
<text class="label">父母情况</text>
<text class="value">{{ userDetail.parentStatus || '未填写' }}</text>
</view>
<view class="parent-item">
<text class="label">退休情况</text>
<text class="value">{{ userDetail.parentRetireStatus || '未填写' }}</text>
</view>
<view class="parent-item">
<text class="label">父母现居</text>
<text class="value">{{ userDetail.parentCity || '未填写' }}</text>
</view>
<view class="parent-item">
<text class="label">住房情况</text>
<text class="value">{{ userDetail.parentHousingStatus || '未填写' }}</text>
</view>
<view class="parent-item">
<text class="label">养老保险</text>
<text class="value">{{ userDetail.parentPensionStatus || '未填写' }}</text>
</view>
<view class="parent-item">
<text class="label">医疗保险</text>
<text class="value">{{ userDetail.parentMedicalStatus || '未填写' }}</text>
</view>
</view>
</view>
<!-- 联系方式(已解锁时显示) -->
<view class="section-card" v-if="isUnlocked && userDetail.weChatNo">
<view class="section-title">联系方式</view>
<view class="contact-row">
<text class="label">微信号</text>
<text class="value">{{ userDetail.weChatNo }}</text>
<button class="copy-btn" @click="handleCopyWeChat">复制</button>
</view>
</view>
<!-- 底部提示 -->
<view class="bottom-tip" v-if="!isUnlocked">
<text>感兴趣请尽早联系~</text>
<text>明天将无法查看对方资料</text>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-actions" v-if="userDetail">
<button class="btn-greet" @click="handleContact">打招呼</button>
<button
class="btn-favorite"
:class="{ active: isFavorited }"
@click="handleFavorite"
>
{{ isFavorited ? '已收藏' : '收藏' }}
</button>
<button class="btn-call" @click="handleCall">拨打电话</button>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShareAppMessage } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { getUserDetail } from '@/api/user.js'
import { favorite, unlock } from '@/api/interact.js'
import { getFullImageUrl } from '@/utils/image.js'
import Loading from '@/components/Loading/index.vue'
import Popup from '@/components/Popup/index.vue'
const userStore = useUserStore()
const configStore = useConfigStore()
// 状态栏高度
const statusBarHeight = ref(20)
// 页面参数
const userId = ref(0)
// 状态
const loading = ref(true)
const userDetail = ref(null)
const isFavorited = ref(false)
const isUnlocked = ref(false)
const remainingUnlockQuota = ref(0)
const showProfilePopup = ref(false)
const showUnlockPopup = ref(false)
const showPhonePopup = ref(false)
// 获取系统信息
const getSystemInfo = () => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20
}
})
}
// 返回
const handleBack = () => {
const pages = getCurrentPages()
if (pages.length > 1) {
// 有上一页,正常返回
uni.navigateBack()
} else {
// 没有上一页(从分享链接进入),跳转到首页
uni.switchTab({
url: '/pages/index/index'
})
}
}
// 选项映射
const educationMap = {
1: '高中',
2: '中专',
3: '大专',
4: '本科',
5: '研究生',
6: '博士及以上'
}
const incomeMap = {
1: '5千以下',
2: '5千-8千/月',
3: '8千-1万/月',
4: '1万-2万/月',
5: '2万以上'
}
const houseMap = {
1: '现居地已购房',
2: '家乡已购房',
3: '婚后购房',
4: '父母同住',
5: '租房',
6: '近期有购房计划'
}
const carMap = {
1: '已购车',
2: '无车',
3: '近期购车'
}
const marriageMap = {
1: '未婚',
2: '离异未育',
3: '离异已育'
}
const expectMarryMap = {
1: '尽快结婚',
2: '一到两年内',
3: '孩子满意就结婚'
}
const relationshipMap = {
1: '父亲',
2: '母亲',
3: '本人'
}
// 生肖计算
const zodiacList = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']
const getZodiac = (year) => {
if (!year) return '未知'
return zodiacList[(year - 4) % 12]
}
// 计算属性
const displayPhotos = computed(() => {
if (!userDetail.value || !userDetail.value.photos) return []
return userDetail.value.photos.map(p => ({
...p,
photoUrl: getFullImageUrl(p.photoUrl)
}))
})
const genderText = computed(() => {
return userDetail.value?.childGender === 1 ? '男' : '女'
})
const relationshipText = computed(() => {
return relationshipMap[userDetail.value?.relationship] || '本人'
})
const memberIconUrl = computed(() => {
if (!userDetail.value?.isMember || !userDetail.value?.memberLevel) return ''
const iconUrl = configStore.getMemberIcon(userDetail.value.memberLevel)
return iconUrl ? getFullImageUrl(iconUrl) : ''
})
const educationText = computed(() => {
return educationMap[userDetail.value?.education] || '未填写'
})
const incomeText = computed(() => {
return incomeMap[userDetail.value?.monthlyIncome] || '未填写'
})
const houseText = computed(() => {
return houseMap[userDetail.value?.houseStatus] || '未填写'
})
const carText = computed(() => {
return carMap[userDetail.value?.carStatus] || '未填写'
})
const marriageText = computed(() => {
return marriageMap[userDetail.value?.marriageStatus] || '未填写'
})
const expectMarryText = computed(() => {
return expectMarryMap[userDetail.value?.expectMarryTime] || '未填写'
})
const zodiacText = computed(() => {
return getZodiac(userDetail.value?.birthYear)
})
// 择偶要求文本 - 有范围的显示"不限",其他显示"无"
const requirementAgeText = computed(() => {
const req = userDetail.value?.requirement
if (!req) return '不限'
if (req.ageMin && req.ageMax) return `${req.ageMin}-${req.ageMax}`
if (req.ageMin) return `${req.ageMin}岁以上`
if (req.ageMax) return `${req.ageMax}岁以下`
return '不限'
})
const requirementHeightText = computed(() => {
const req = userDetail.value?.requirement
if (!req) return '不限'
if (req.heightMin && req.heightMax) return `${req.heightMin}cm-${req.heightMax}cm`
if (req.heightMin) return `${req.heightMin}cm以上`
if (req.heightMax) return `${req.heightMax}cm以下`
return '不限'
})
const requirementEducationText = computed(() => {
const req = userDetail.value?.requirement
if (!req || !req.education || req.education.length === 0) return '无'
return req.education.map(e => educationMap[e]).filter(Boolean).join('、')
})
const requirementCityText = computed(() => {
const req = userDetail.value?.requirement
if (!req) return '无'
const cities = []
if (req.city1City) cities.push(req.city1City)
if (req.city2City) cities.push(req.city2City)
return cities.length > 0 ? cities.join('、') : '无'
})
const requirementIncomeText = computed(() => {
const req = userDetail.value?.requirement
if (!req || !req.monthlyIncomeMin) return '不限'
return incomeMap[req.monthlyIncomeMin] || '不限'
})
// 房产要求文本
const requirementHouseText = computed(() => {
const req = userDetail.value?.requirement
if (!req || !req.houseStatus || req.houseStatus.length === 0) return '无'
return req.houseStatus.map(h => houseMap[h]).filter(Boolean).join('、')
})
// 车产要求文本
const requirementCarText = computed(() => {
const req = userDetail.value?.requirement
if (!req || !req.carStatus || req.carStatus.length === 0) return '无'
return req.carStatus.map(c => carMap[c]).filter(Boolean).join('、')
})
// 婚姻要求文本
const requirementMarriageText = computed(() => {
const req = userDetail.value?.requirement
if (!req || !req.marriageStatus || req.marriageStatus.length === 0) return '无'
return req.marriageStatus.map(m => marriageMap[m]).filter(Boolean).join('、')
})
// 加载用户详情
const loadUserDetail = async () => {
loading.value = true
try {
const res = await getUserDetail(userId.value)
// 后端返回 code: 0 表示成功
if (res && (res.success || res.code === 0) && res.data) {
userDetail.value = res.data
isFavorited.value = res.data.isFavorited || false
isUnlocked.value = res.data.isUnlocked || false
remainingUnlockQuota.value = res.data.remainingUnlockQuota || 0
}
} catch (error) {
console.error('加载用户详情失败:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
}
}
// 预览照片
const previewPhoto = (index) => {
if (!userDetail.value?.isPhotoPublic && !isUnlocked.value) {
uni.showToast({ title: '私密照片,解锁后可查看', icon: 'none' })
return
}
const urls = displayPhotos.value.map(p => p.photoUrl)
uni.previewImage({
urls,
current: urls[index] || urls[0]
})
}
// 收藏处理
const handleFavorite = async () => {
// 检查是否登录
if (!userStore.isLoggedIn) {
uni.showModal({
title: '提示',
content: '请先登录后再收藏',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: '/pages/login/index' })
}
}
})
return
}
try {
const res = await favorite(userId.value)
if (res && (res.success || res.code === 0)) {
isFavorited.value = res.data?.isFavorited ?? !isFavorited.value
uni.showToast({
title: isFavorited.value ? '收藏成功' : '已取消收藏',
icon: 'success'
})
}
} catch (error) {
uni.showToast({ title: '操作失败', icon: 'none' })
}
}
// 联系处理
const handleContact = async () => {
console.log('[Detail] handleContact - 检查实名状态:', {
targetIsRealName: userDetail.value?.isRealName,
myIsRealName: userStore.isRealName,
isLoggedIn: userStore.isLoggedIn,
isProfileCompleted: userStore.isProfileCompleted,
isProfileAuditing: userStore.isProfileAuditing
})
// 检查是否登录
if (!userStore.isLoggedIn) {
uni.showModal({
title: '提示',
content: '请先登录后再联系对方',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: '/pages/login/index' })
}
}
})
return
}
// 检查是否资料审核中
if (userStore.isProfileAuditing) {
uni.showModal({
title: '资料审核中',
content: '正在加急审核您的相亲资料,请耐心等待',
showCancel: false,
confirmText: '确定'
})
return
}
if (!userStore.isProfileCompleted) {
showProfilePopup.value = true
return
}
// 如果对方已实名,检查当前用户是否也已实名
if (userDetail.value?.isRealName && !userStore.isRealName) {
console.log('[Detail] 对方已实名但我未实名,跳转实名认证页')
uni.showModal({
title: '提示',
content: '对方已开启实名相亲,请先完成实名认证才能联系',
confirmText: '去认证',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: '/pages/realname/index' })
}
}
})
return
}
if (isUnlocked.value) {
// 已解锁,直接跳转聊天页
uni.navigateTo({ url: `/pages/chat/index?targetUserId=${userId.value}` })
} else if (userStore.isMember) {
// 会员直接解锁,不显示弹窗
try {
const res = await unlock(userId.value)
if (res && (res.success || res.code === 0)) {
isUnlocked.value = true
uni.navigateTo({ url: `/pages/chat/index?targetUserId=${userId.value}` })
}
} catch (error) {
uni.showToast({ title: '解锁失败', icon: 'none' })
}
} else {
// 非会员显示解锁弹窗
showUnlockPopup.value = true
}
}
// 去完善资料
const handleGoProfile = () => {
showProfilePopup.value = false
uni.navigateTo({ url: '/pages/profile/edit' })
}
// 确认解锁
const handleConfirmUnlock = async () => {
if (remainingUnlockQuota.value <= 0) {
showUnlockPopup.value = false
uni.showModal({
title: '提示',
content: '您的解锁次数已用完,请购买会员获取更多解锁次数',
confirmText: '去购买',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: '/pages/member/index' })
}
}
})
return
}
try {
const res = await unlock(userId.value)
if (res && (res.success || res.code === 0)) {
isUnlocked.value = true
showUnlockPopup.value = false
remainingUnlockQuota.value = Math.max(0, remainingUnlockQuota.value - 1)
uni.navigateTo({ url: `/pages/chat/index?targetUserId=${userId.value}` })
}
} catch (error) {
uni.showToast({ title: '解锁失败', icon: 'none' })
}
}
// 跳转会员页面
const handleGoMember = () => {
showUnlockPopup.value = false
uni.navigateTo({ url: '/pages/member/index' })
}
// 拨打电话
const handleCall = () => {
// 检查是否登录
if (!userStore.isLoggedIn) {
uni.showModal({
title: '提示',
content: '请先登录后再联系对方',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
uni.navigateTo({ url: '/pages/login/index' })
}
}
})
return
}
if (!isUnlocked.value) {
showUnlockPopup.value = true
return
}
// 已解锁,显示电话弹窗
showPhonePopup.value = true
}
// 拨打电话
const handleMakeCall = () => {
const phone = userDetail.value?.phone
if (!phone) {
uni.showToast({ title: '暂无电话号码', icon: 'none' })
return
}
uni.makePhoneCall({
phoneNumber: phone,
fail: (err) => {
console.error('拨打电话失败:', err)
}
})
}
// 复制微信号
const handleCopyWeChat = () => {
if (userDetail.value?.weChatNo) {
uni.setClipboardData({
data: userDetail.value.weChatNo,
success: () => {
uni.showToast({ title: '已复制微信号', icon: 'success' })
}
})
}
}
// 页面加载
onMounted(() => {
getSystemInfo()
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage?.options || {}
if (options.userId) {
userId.value = parseInt(options.userId)
loadUserDetail()
} else {
uni.showToast({ title: '参数错误', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
})
// 分享配置
onShareAppMessage(() => {
return {
title: userDetail.value?.nickname ? `${userDetail.value.nickname}的相亲资料` : '相宜相亲',
path: `/pages/profile/detail?userId=${userId.value}`,
imageUrl: '/static/logo.png'
}
})
</script>
<style lang="scss" scoped>
.profile-detail-page {
min-height: 100vh;
background-color: #f5f6fa;
overflow: hidden;
}
// 自定义导航栏
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: linear-gradient(135deg, #ffb5b5 0%, #ff9a9a 100%);
.navbar-content {
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: #fff;
font-weight: 400;
}
}
.navbar-title {
font-size: 34rpx;
font-weight: 600;
color: #fff;
}
.navbar-placeholder {
width: 60rpx;
}
}
}
// 内容滚动区域
.content-scroll {
height: calc(100vh - 160rpx);
box-sizing: border-box;
}
.content-wrapper {
padding: 24rpx;
padding-bottom: 40rpx;
}
// 用户头像卡片
.user-header-card {
display: flex;
align-items: center;
padding: 24rpx;
background: #fff;
border-radius: 24rpx;
margin-bottom: 24rpx;
.header-left {
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #f0f0f0;
}
}
.header-right {
margin-left: 24rpx;
flex: 1;
.name-row {
display: flex;
align-items: center;
margin-bottom: 8rpx;
flex-wrap: wrap;
gap: 8rpx;
.nickname {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.relationship {
font-size: 28rpx;
color: #666;
}
.header-tags {
display: inline-flex;
align-items: center;
gap: 8rpx;
margin-left: 8rpx;
vertical-align: middle;
.member-icon {
height: 42rpx;
width: 120rpx;
vertical-align: middle;
}
.tag {
display: inline-block;
font-size: 22rpx;
padding: 6rpx 16rpx;
border-radius: 8rpx;
height: 36rpx;
line-height: 24rpx;
box-sizing: border-box;
vertical-align: middle;
&.tag-realname {
background: #3d4a4a;
color: #fff;
}
&.tag-member {
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
color: #ff9800;
}
}
}
}
.xiangqin-no {
font-size: 24rpx;
color: #999;
}
}
}
// 通用卡片样式
.section-card {
background: #fff;
border-radius: 24rpx;
padding: 28rpx;
margin-bottom: 24rpx;
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
.section-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
.section-title {
margin-bottom: 0;
}
.photo-count {
font-size: 24rpx;
color: #999;
}
}
}
// 性别年份行
.gender-year-row {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.gender-year {
font-size: 40rpx;
font-weight: 600;
color: #ff6b6b;
&.male {
color: #4a90d9;
}
}
.flex-spacer {
flex: 1;
}
.share-btn {
padding: 16rpx 40rpx;
font-size: 28rpx;
color: #fff;
background: #4cd964;
border: none;
border-radius: 12rpx;
line-height: 1;
&::after {
border: none;
}
}
}
// 信息网格
.info-grid {
display: flex;
flex-wrap: wrap;
.info-item {
width: 50%;
display: flex;
align-items: center;
margin-bottom: 20rpx;
.label {
font-size: 28rpx;
color: #999;
width: 80rpx;
flex-shrink: 0;
}
.value {
font-size: 28rpx;
color: #333;
margin-left: 16rpx;
}
}
}
// 照片网格
.photo-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
.photo-item {
width: calc(33.33% - 12rpx);
aspect-ratio: 1;
border-radius: 16rpx;
overflow: hidden;
position: relative;
.photo-image {
width: 100%;
height: 100%;
&.photo-blurred {
filter: blur(6px);
transform: scale(1.02);
}
}
.photo-lock-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(3px);
}
.photo-blur {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #e0e0e0 0%, #c0c0c0 100%);
display: flex;
align-items: center;
justify-content: center;
.blur-text {
font-size: 24rpx;
color: #666;
}
}
}
}
// 介绍内容
.intro-content {
text {
font-size: 28rpx;
color: #666;
line-height: 1.8;
}
}
// 择偶要求网格
.requirement-grid {
display: flex;
flex-wrap: wrap;
.req-item {
width: 50%;
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
.label {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.value {
font-size: 28rpx;
color: #333;
}
}
}
.req-extra {
padding-top: 16rpx;
border-top: 1rpx solid #f0f0f0;
.label {
font-size: 24rpx;
color: #999;
display: block;
margin-bottom: 8rpx;
}
.value {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
// 更多信息网格
.more-info-grid {
display: flex;
flex-wrap: wrap;
.more-item {
width: 50%;
display: flex;
align-items: center;
margin-bottom: 20rpx;
&.full-width {
width: 100%;
}
.label {
font-size: 28rpx;
color: #999;
margin-right: 16rpx;
}
.value {
font-size: 28rpx;
color: #333;
}
}
}
// 父母信息网格
.parent-info-grid {
display: flex;
flex-wrap: wrap;
.parent-item {
width: 50%;
display: flex;
align-items: center;
margin-bottom: 20rpx;
.label {
font-size: 28rpx;
color: #999;
margin-right: 16rpx;
}
.value {
font-size: 28rpx;
color: #333;
}
}
}
// 联系方式行
.contact-row {
display: flex;
align-items: center;
.label {
font-size: 28rpx;
color: #999;
margin-right: 16rpx;
}
.value {
font-size: 28rpx;
color: #333;
flex: 1;
}
.copy-btn {
padding: 12rpx 24rpx;
font-size: 24rpx;
color: #fff;
background: #ff6b6b;
border-radius: 20rpx;
border: none;
&::after {
border: none;
}
}
}
// 底部提示
.bottom-tip {
text-align: center;
padding: 40rpx 0;
text {
display: block;
font-size: 26rpx;
color: #999;
line-height: 1.8;
}
}
// 底部操作栏
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 20rpx 24rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
gap: 20rpx;
button {
flex: 1;
height: 88rpx;
line-height: 88rpx;
font-size: 30rpx;
border-radius: 16rpx;
border: none;
&::after {
border: none;
}
}
.btn-greet {
background: #4cd964;
color: #fff;
}
.btn-favorite {
background: #f5d742;
color: #fff;
&.active {
background: #e5c732;
}
}
.btn-call {
background: #ff9a9a;
color: #fff;
}
}
// 拨打电话弹窗
.phone-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: flex-end;
justify-content: center;
}
.phone-popup {
width: 100%;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 40rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
.phone-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
.phone-popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.phone-popup-close {
font-size: 48rpx;
color: #999;
line-height: 1;
}
}
.phone-popup-content {
text-align: center;
padding: 40rpx 0;
.phone-number {
font-size: 56rpx;
font-weight: 600;
color: #333;
letter-spacing: 4rpx;
}
}
.phone-popup-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background: #4cd964;
color: #fff;
font-size: 34rpx;
border-radius: 48rpx;
border: none;
&::after {
border: none;
}
&[disabled] {
opacity: 0.5;
}
}
}
</style>