pet/pages/profile/profile.vue

1757 lines
36 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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-container page-container-unified">
<!-- 用户信息卡片 -->
<view class="user-info-card">
<view class="user-avatar-section" :class="{ 'logged-in': userInfo.nickName }" @click="handleUserAction">
<view class="avatar-wrapper">
<u-avatar
:src="userInfo.avatarUrl || ''"
:text="userInfo.nickName ? userInfo.nickName.charAt(0) : '👤'"
size="100"
shape="circle"
bg-color="linear-gradient(135deg, #FF8A80, #FFB6C1)"
color="white"
font-size="40"
></u-avatar>
<view class="online-status" v-if="userInfo.nickName">
<view class="status-dot"></view>
</view>
<!-- 登录提示图标仅在未登录时显示 -->
<view class="login-hint" v-if="!userInfo.nickName">
<text class="hint-icon">👋</text>
</view>
</view>
</view>
<view class="user-info-section">
<view class="user-name">{{ userInfo.nickName || '点击头像登录' }}</view>
<view class="user-status">{{ userInfo.nickName ? '已登录' : '未登录' }}</view>
<view class="login-days" v-if="userInfo.nickName">已使用 {{ loginDays }} </view>
</view>
</view>
<!-- 宠物概览统计卡片 -->
<view class="stats-card">
<view class="stats-header">
<view class="header-left">
<text class="card-icon">🐱</text>
<text class="card-title">我的宠物</text>
</view>
</view>
<view class="stats-grid">
<view class="stat-item" @click="navigateTo('/pages/pets/pets')">
<view class="stat-number">{{ petStats.petCount }}</view>
<view class="stat-label">宠物</view>
<view class="stat-icon">🐱</view>
</view>
<view class="stat-item" @click="navigateTo('/pages/pets/pet-records')">
<view class="stat-number">{{ petStats.recordCount }}</view>
<view class="stat-label">记录</view>
<view class="stat-icon">📝</view>
</view>
<view class="stat-item" @click="showReminders">
<view class="stat-number">{{ petStats.reminderCount }}</view>
<view class="stat-label">提醒</view>
<view class="stat-icon"></view>
</view>
</view>
</view>
<!-- 家庭管理卡片 -->
<view class="family-card">
<view class="card-header">
<view class="header-left">
<text class="card-icon">👨👩👧👦</text>
<text class="card-title">我的家庭</text>
</view>
</view>
<view class="family-stats">
<view class="stat-item" @click="navigateToFamily">
<view class="stat-number">{{ familyStats.memberCount }}</view>
<view class="stat-label">成员</view>
<view class="stat-icon">👥</view>
</view>
<view class="stat-item" @click="navigateTo('/pages/pets/pets')">
<view class="stat-number">{{ familyStats.sharedPets || petStats.petCount }}</view>
<view class="stat-label">共享宠物</view>
<view class="stat-icon">🐾</view>
</view>
<view class="stat-item" @click="navigateToFamily">
<view class="stat-number">{{ familyStats.activityCount || 0 }}</view>
<view class="stat-label">动态</view>
<view class="stat-icon">📱</view>
</view>
</view>
<view class="family-actions">
<view class="action-btn" @click="inviteFamily">
<text class="btn-icon"></text>
<text class="btn-text">邀请成员</text>
</view>
<view class="action-btn" @click="navigateToFamily">
<text class="btn-icon">👥</text>
<text class="btn-text">家庭设置</text>
</view>
</view>
</view>
<!-- 领养管理卡片 -->
<view class="adoption-card">
<view class="card-header">
<view class="header-left">
<text class="card-icon">🏠</text>
<text class="card-title">领养管理</text>
</view>
</view>
<view class="adoption-stats">
<view class="adoption-item" @click="navigateTo('/pages/adoption/my-published')">
<view class="adoption-number">{{ adoptionStats.publishedCount }}</view>
<view class="adoption-label">已发布</view>
</view>
<view class="adoption-item" @click="navigateTo('/pages/adoption/my-applications')">
<view class="adoption-number">{{ adoptionStats.applicationCount }}</view>
<view class="adoption-label">已申请</view>
</view>
</view>
<view class="publish-btn" @click="navigateTo('/pages/adoption/publish')">
<text class="publish-icon">📝</text>
<text class="publish-text">发布领养信息</text>
</view>
</view>
<!-- 设置与帮助卡片 -->
<view class="settings-card">
<view class="card-header">
<view class="header-left">
<text class="card-icon"></text>
<text class="card-title">设置与帮助</text>
</view>
</view>
<view class="settings-list">
<view class="setting-item floating-indicator" @click="navigateTo('/pages/profile/feedback')">
<view class="setting-left">
<text class="setting-icon">💬</text>
<text class="setting-text">意见反馈</text>
</view>
</view>
</view>
</view>
<!-- 用户信息设置模态框 -->
<u-modal
:show="showUserInfoModal"
title="完善个人信息"
@cancel="cancelUserInfo"
:show-cancel-button="false"
:show-confirm-button="false"
:mask-close-able="true"
>
<view class="user-info-form" :class="{ 'form-animate': showUserInfoModal }">
<view class="form-item avatar-form-item">
<text class="form-label">选择头像</text>
<view class="avatar-selector">
<button
class="avatar-button"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
<view class="avatar-container">
<u-avatar
:src="tempUserInfo.avatarUrl || ''"
:text="tempUserInfo.nickName ? tempUserInfo.nickName.charAt(0) : '👤'"
size="100"
shape="circle"
bg-color="linear-gradient(135deg, #FF8A80, #FFB6C1)"
color="white"
font-size="36"
></u-avatar>
<view class="avatar-edit-overlay">
<view class="edit-icon-wrapper">
<text class="edit-icon">📷</text>
</view>
</view>
</view>
</button>
</view>
</view>
<view class="form-item">
<text class="form-label">昵称</text>
<input
type="nickname"
v-model="tempUserInfo.nickName"
placeholder="请输入昵称"
class="nickname-input"
/>
</view>
<!-- 自定义保存按钮 -->
<view class="custom-button-area">
<button class="custom-save-button" @click="saveUserInfo">
<view class="button-content">
<text class="button-text">保存信息</text>
</view>
<view class="button-shine"></view>
</button>
</view>
</view>
</u-modal>
</view>
</template>
<script>
import { reactive, ref, onMounted, computed } from 'vue'
import { uploadImage } from '@/http/api/common.js'
import { updateUserInfo } from '@/http/api/profile.js'
export default {
name: 'ProfilePage',
setup() {
// 响应式数据
const userInfo = ref({
nickName: '',
avatarUrl: ''
})
const loginDays = ref(0)
// 用户信息设置模态框相关数据
const showUserInfoModal = ref(false)
const tempUserInfo = ref({
nickName: '',
avatarUrl: ''
})
// 宠物统计数据
const petStats = reactive({
petCount: 0,
recordCount: 0,
reminderCount: 0
})
// 家庭统计数据
const familyStats = reactive({
memberCount: 1,
sharedPets: 0,
activityCount: 0
})
// 领养统计数据
const adoptionStats = reactive({
publishedCount: 0,
applicationCount: 0
})
// 方法定义
const initPage = () => {
checkLogin()
loadUserStats()
}
const checkLogin = () => {
// 检查是否已登录
const savedUserInfo = uni.getStorageSync('userInfo')
if (savedUserInfo) {
userInfo.value = savedUserInfo
calculateLoginDays()
}
}
const calculateLoginDays = () => {
const loginDate = uni.getStorageSync('loginDate')
if (loginDate) {
const now = new Date()
const login = new Date(loginDate)
const diffTime = Math.abs(now - login)
loginDays.value = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
}
}
const loadUserStats = () => {
try {
// 加载宠物统计数据
const pets = uni.getStorageSync('pets') || []
petStats.petCount = pets.length
// 加载记录统计数据 - 统计所有宠物的记录
let totalRecords = 0
pets.forEach(pet => {
const petRecords = uni.getStorageSync(`petRecords_${pet.id}`) || []
totalRecords += petRecords.length
})
petStats.recordCount = totalRecords
// 计算待处理提醒数量
const today = new Date()
const reminders = uni.getStorageSync('reminders') || []
const pendingReminders = reminders.filter(reminder => {
const reminderDate = new Date(reminder.date)
return reminderDate <= today && !reminder.completed
})
petStats.reminderCount = pendingReminders.length
// 加载家庭数据
const familyData = uni.getStorageSync('familyData') || { members: [], activities: [] }
familyStats.memberCount = familyData.members.length || 1
familyStats.sharedPets = Math.min(petStats.petCount, familyStats.memberCount > 1 ? petStats.petCount : 0)
familyStats.activityCount = familyData.activities ? familyData.activities.length : 0
// 加载领养数据
const adoptionData = uni.getStorageSync('adoptionData') || {
published: [],
applications: []
}
adoptionStats.publishedCount = adoptionData.published.length
adoptionStats.applicationCount = adoptionData.applications.length
} catch (error) {
console.error('加载用户统计数据失败:', error)
// 设置默认值
petStats.petCount = 0
petStats.recordCount = 0
petStats.reminderCount = 0
familyStats.memberCount = 1
familyStats.sharedPets = 0
familyStats.activityCount = 0
adoptionStats.publishedCount = 0
adoptionStats.applicationCount = 0
}
}
const handleUserAction = () => {
if (userInfo.value.nickName) {
// 已登录,跳转到个人信息设置
navigateTo('/pages/profile/user-info')
} else {
// 未登录,执行登录
wxLogin()
}
}
const wxLogin = () => {
uni.login({
provider: 'weixin',
success: (res) => {
console.log('登录成功', res)
// 登录成功后显示用户信息设置模态框
// 清空临时用户信息,准备用户输入
tempUserInfo.value = {
nickName: '',
avatarUrl: ''
}
// 显示用户信息设置模态框
showUserInfoModal.value = true
// 保存登录时间,但不设置用户信息(等用户完善后再设置)
uni.setStorageSync('loginDate', new Date().toISOString())
calculateLoginDays()
uni.showToast({
title: '登录成功,请完善个人信息',
icon: 'success',
duration: 2000
})
},
fail: (err) => {
console.error('登录失败', err)
uni.showToast({
title: '登录失败,请重试',
icon: 'none'
})
}
})
}
const navigateTo = (url) => {
uni.navigateTo({
url,
fail: () => {
uni.showToast({
title: '页面开发中',
icon: 'none'
})
}
})
}
const navigateToFamily = () => {
navigateTo('/pages/profile/family')
}
const inviteFamily = () => {
uni.showModal({
title: '邀请家庭成员',
content: '通过微信分享邀请码邀请家人加入',
confirmText: '分享邀请',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '功能开发中',
icon: 'none'
})
}
}
})
}
const showReminders = () => {
if (petStats.reminderCount > 0) {
navigateTo('/pages/profile/reminders')
} else {
uni.showToast({
title: '暂无提醒事项',
icon: 'none'
})
}
}
// 刷新页面数据
const refreshData = () => {
loadUserStats()
}
// 页面显示时刷新数据
const onShow = () => {
refreshData()
}
// 下拉刷新
const onPullDownRefresh = () => {
refreshData()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
}
// 格式化数字显示
const formatNumber = (num) => {
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'k'
}
return num.toString()
}
// 头像选择处理
const onChooseAvatar = async (e) => {
try {
console.log('选择头像:', e.detail)
// 检查微信基础库版本兼容性
const systemInfo = uni.getSystemInfoSync()
const SDKVersion = systemInfo.SDKVersion
if (SDKVersion) {
const versionArray = SDKVersion.split('.').map(Number)
const requiredVersion = [2, 21, 2]
let isSupported = false
for (let i = 0; i < 3; i++) {
if (versionArray[i] > requiredVersion[i]) {
isSupported = true
break
} else if (versionArray[i] < requiredVersion[i]) {
break
}
}
if (!isSupported && versionArray.join('.') !== requiredVersion.join('.')) {
uni.showToast({
title: '微信版本过低,请升级后使用',
icon: 'none'
})
return
}
}
const { avatarUrl } = e.detail
// 检查是否获取到头像路径
if (!avatarUrl) {
uni.showToast({
title: '头像选择失败',
icon: 'none'
})
return
}
// 先显示临时头像
tempUserInfo.value.avatarUrl = avatarUrl
// 上传头像到服务器
const imageData = {
filePath: avatarUrl,
name: 'avatar',
formData: {
type: 'user_avatar'
}
}
const uploadResult = await uploadImage(imageData, {
custom: {
auth: true,
loading: true,
loadingText: '正在上传头像...'
}
})
// 更新为服务器返回的永久URL
if (uploadResult && uploadResult.url) {
tempUserInfo.value.avatarUrl = uploadResult.url
uni.showToast({
title: '头像上传成功',
icon: 'success',
duration: 1500
})
} else {
throw new Error('上传结果无效')
}
} catch (error) {
console.error('头像上传失败:', error)
// 根据错误类型显示不同提示
let errorMessage = '头像上传失败'
// 处理微信内容安全检测失败
if (error.errCode === 87014 || (error.message && error.message.includes('安全检测'))) {
errorMessage = '头像内容不符合规范,请重新选择'
}
// 处理网络错误
else if (error.errCode === -1 || (error.message && error.message.includes('网络'))) {
errorMessage = '网络异常,请稍后重试'
}
// 处理文件大小超限
else if (error.errCode === 1003 || (error.message && error.message.includes('文件大小'))) {
errorMessage = '头像文件过大,请选择较小的图片'
}
// 处理服务器错误
else if (error.statusCode >= 500) {
errorMessage = '服务器异常,请稍后重试'
}
// 处理权限错误
else if (error.statusCode === 401 || error.statusCode === 403) {
errorMessage = '权限不足,请重新登录'
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 2000
})
// 重置头像为空
tempUserInfo.value.avatarUrl = ''
}
}
// 保存用户信息
const saveUserInfo = async () => {
try {
// 验证头像和昵称信息的完整性
if (!tempUserInfo.value.nickName || !tempUserInfo.value.nickName.trim()) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
})
return
}
// 验证头像
if (!tempUserInfo.value.avatarUrl) {
uni.showToast({
title: '请选择头像',
icon: 'none'
})
return
}
console.log('开始保存用户信息:', tempUserInfo.value)
// 调用API同步到服务器
const updateResult = await updateUserInfo({
nickName: tempUserInfo.value.nickName.trim(),
avatarUrl: tempUserInfo.value.avatarUrl
}, {
custom: {
auth: true,
loading: true,
loadingText: '正在保存用户信息...'
}
})
console.log('服务器保存成功:', updateResult)
// 更新本地存储中的userInfo
userInfo.value = {
nickName: tempUserInfo.value.nickName.trim(),
avatarUrl: tempUserInfo.value.avatarUrl
}
uni.setStorageSync('userInfo', userInfo.value)
// 关闭模态框
showUserInfoModal.value = false
// 重置临时数据
tempUserInfo.value = {
nickName: '',
avatarUrl: ''
}
// 显示成功提示
uni.showToast({
title: '用户信息保存成功',
icon: 'success',
duration: 2000
})
// 更新页面显示状态
calculateLoginDays()
} catch (error) {
console.error('保存用户信息失败:', error)
// 根据错误类型显示不同提示
let errorMessage = '保存失败,请重试'
if (error.statusCode === 401 || error.statusCode === 403) {
errorMessage = '登录已过期,请重新登录'
} else if (error.statusCode >= 500) {
errorMessage = '服务器异常,请稍后重试'
} else if (error.message && error.message.includes('网络')) {
errorMessage = '网络异常,请检查网络连接'
} else if (error.message && error.message.includes('昵称')) {
errorMessage = '昵称不符合要求,请修改后重试'
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
})
}
}
// 取消用户信息设置
const cancelUserInfo = () => {
showUserInfoModal.value = false
// 重置临时数据
tempUserInfo.value = {
nickName: '',
avatarUrl: ''
}
}
// 页面初始化
onMounted(() => {
initPage()
})
return {
userInfo,
loginDays,
petStats,
familyStats,
adoptionStats,
handleUserAction,
navigateTo,
navigateToFamily,
inviteFamily,
showReminders,
refreshData,
formatNumber,
onShow,
onPullDownRefresh,
// 用户信息设置模态框相关
showUserInfoModal,
tempUserInfo,
onChooseAvatar,
saveUserInfo,
cancelUserInfo
}
},
// 页面生命周期
onShow() {
this.onShow()
},
onPullDownRefresh() {
this.onPullDownRefresh()
}
}
</script>
<style lang="scss" scoped>
.profile-container {
/* 边距由外层page-container-unified统一管理 */
}
/* 用户信息卡片 */
.user-info-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
display: flex;
align-items: center;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
.user-avatar-section {
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 50%;
padding: 12rpx;
position: relative;
// 未登录时的可点击提示
&:not(.logged-in) {
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 2rpx dashed rgba(255, 138, 128, 0.3);
border-radius: 50%;
animation: breathe 3s ease-in-out infinite;
}
}
&:hover {
background: rgba(255, 138, 128, 0.15);
transform: scale(1.08);
box-shadow: 0 8rpx 24rpx rgba(255, 138, 128, 0.25);
}
&:active {
transform: scale(0.92);
transition: all 0.1s ease;
}
.avatar-wrapper {
position: relative;
.online-status {
position: absolute;
bottom: 8rpx;
right: 8rpx;
width: 24rpx;
height: 24rpx;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.status-dot {
width: 16rpx;
height: 16rpx;
background: #4CAF50;
border-radius: 50%;
}
}
.login-hint {
position: absolute;
bottom: -8rpx;
right: -8rpx;
width: 40rpx;
height: 40rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.4);
animation: pulse 2s infinite;
border: 3rpx solid white;
z-index: 10;
&::before {
content: '';
position: absolute;
top: -6rpx;
left: -6rpx;
right: -6rpx;
bottom: -6rpx;
border: 2rpx solid rgba(255, 138, 128, 0.3);
border-radius: 50%;
animation: ripple 2s infinite;
}
.hint-icon {
font-size: 22rpx;
color: white;
font-weight: bold;
}
}
}
}
.user-info-section {
flex: 1;
margin-left: 24rpx;
.user-name {
font-size: 36rpx;
font-weight: 600;
color: #333333;
margin-bottom: 8rpx;
}
.user-status {
font-size: 24rpx;
color: #FF8A80;
margin-bottom: 4rpx;
}
.login-days {
font-size: 22rpx;
color: #999999;
}
}
.user-action {
.action-icon {
font-size: 32rpx;
}
}
}
/* 宠物概览统计卡片 */
.stats-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.stats-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.header-left {
display: flex;
align-items: center;
.card-icon {
font-size: 36rpx;
margin-right: 16rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
}
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 24rpx;
.stat-item {
text-align: center;
padding: 24rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
background: rgba(255, 138, 128, 0.1);
}
.stat-number {
font-size: 40rpx;
font-weight: 700;
color: #FF8A80;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 22rpx;
color: #666666;
margin-bottom: 8rpx;
}
.stat-icon {
font-size: 28rpx;
}
}
}
}
/* 家庭管理卡片 */
.family-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.header-left {
display: flex;
align-items: center;
.card-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
}
.header-right {
display: flex;
align-items: center;
.member-count {
font-size: 24rpx;
color: #FF8A80;
margin-right: 8rpx;
}
}
}
.family-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
margin-bottom: 32rpx;
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 16rpx;
background: rgba(255, 138, 128, 0.05);
border-radius: 16rpx;
transition: all 0.3s ease;
cursor: pointer;
&:active {
transform: scale(0.95);
background: rgba(255, 138, 128, 0.1);
}
.stat-number {
font-size: 36rpx;
font-weight: 600;
color: #FF8A80;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #666666;
margin-bottom: 8rpx;
}
.stat-icon {
font-size: 28rpx;
}
}
}
.family-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
.action-btn {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
background: rgba(255, 138, 128, 0.1);
}
.btn-icon {
font-size: 32rpx;
margin-bottom: 8rpx;
}
.btn-text {
font-size: 22rpx;
color: #666666;
}
}
}
}
/* 领养管理卡片 */
.adoption-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.header-left {
display: flex;
align-items: center;
.card-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
}
.header-right {
/* 悬浮指示器样式已在通用样式中定义 */
}
}
.adoption-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
margin-bottom: 24rpx;
.adoption-item {
text-align: center;
padding: 20rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
background: rgba(255, 138, 128, 0.1);
}
.adoption-number {
font-size: 36rpx;
font-weight: 700;
color: #FF8A80;
margin-bottom: 8rpx;
}
.adoption-label {
font-size: 22rpx;
color: #666666;
}
}
}
.publish-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx;
border-radius: 16rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
.publish-icon {
font-size: 24rpx;
margin-right: 8rpx;
}
.publish-text {
font-size: 26rpx;
color: white;
font-weight: 500;
}
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.profile-container {
.user-info-card {
margin: 0 0 20rpx 0;
padding: 24rpx;
}
.stats-card,
.family-card,
.adoption-card,
.settings-card {
margin: 0 0 20rpx 0;
padding: 24rpx;
}
.stats-grid {
gap: 16rpx;
.stat-item {
padding: 20rpx 12rpx;
.stat-number {
font-size: 36rpx;
}
}
}
.family-stats {
gap: 16rpx;
margin-bottom: 24rpx;
.stat-item {
padding: 20rpx 12rpx;
.stat-number {
font-size: 32rpx;
}
.stat-label {
font-size: 22rpx;
}
.stat-icon {
font-size: 24rpx;
}
}
}
.family-actions {
gap: 12rpx;
.action-btn {
padding: 20rpx 12rpx;
}
}
}
}
/* 加载状态 */
.loading-state {
display: flex;
justify-content: center;
align-items: center;
padding: 60rpx;
.loading-text {
font-size: 24rpx;
color: #999999;
margin-left: 16rpx;
}
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 60rpx 40rpx;
.empty-icon {
font-size: 80rpx;
margin-bottom: 16rpx;
opacity: 0.5;
}
.empty-text {
font-size: 24rpx;
color: #999999;
line-height: 1.5;
}
}
/* 悬浮指示器通用样式 */
.floating-indicator {
position: relative;
cursor: pointer;
&::after {
content: '';
position: absolute;
right: 16rpx;
top: 50%;
transform: translateY(-50%);
width: 12rpx;
height: 12rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 50%;
animation: pulse 2s infinite;
transition: all 0.3s ease;
opacity: 0.8;
}
&:hover::after {
width: 16rpx;
height: 16rpx;
box-shadow: 0 0 0 6rpx rgba(255, 138, 128, 0.2);
opacity: 1;
}
&:active::after {
transform: translateY(-50%) scale(0.8);
}
}
/* 设置与帮助卡片 */
.settings-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 0 24rpx 0;
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
overflow: hidden;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
.header-left {
display: flex;
align-items: center;
.card-icon {
font-size: 36rpx;
margin-right: 16rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
}
}
.settings-list {
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
transition: all 0.3s ease;
&:active {
background: rgba(255, 138, 128, 0.05);
}
.setting-left {
display: flex;
align-items: center;
.setting-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.setting-text {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
}
.setting-right {
/* 悬浮指示器样式已在通用样式中定义 */
}
}
}
}
/* 用户信息设置模态框样式 */
.user-info-form {
padding: 40rpx 32rpx 20rpx 32rpx;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.3s ease;
background: linear-gradient(135deg, rgba(255, 138, 128, 0.02), rgba(255, 182, 193, 0.02));
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
&.form-animate {
opacity: 1;
transform: translateY(0);
}
.form-item {
margin-bottom: 40rpx;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
&:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
text-align: center;
}
}
.avatar-form-item {
margin-bottom: 44rpx;
}
.avatar-selector {
display: flex;
flex-direction: column;
align-items: center;
.avatar-button {
position: relative;
background: none;
border: none;
padding: 0;
margin-bottom: 24rpx;
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&::after {
border: none;
}
&:hover {
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
.avatar-container {
position: relative;
padding: 8rpx;
border-radius: 50%;
background: linear-gradient(135deg, rgba(255, 138, 128, 0.1), rgba(255, 182, 193, 0.1));
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.15);
.avatar-edit-overlay {
position: absolute;
bottom: 8rpx;
right: 8rpx;
.edit-icon-wrapper {
width: 40rpx;
height: 40rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.4);
border: 3rpx solid white;
.edit-icon {
font-size: 22rpx;
color: white;
}
}
}
}
}
.avatar-tips {
font-size: 28rpx;
color: #666;
background: linear-gradient(135deg, rgba(255, 138, 128, 0.08), rgba(255, 182, 193, 0.08));
padding: 12rpx 24rpx;
border-radius: 24rpx;
border: 1rpx solid rgba(255, 138, 128, 0.15);
box-shadow: 0 2rpx 8rpx rgba(255, 138, 128, 0.1);
}
}
.nickname-input {
width: 80%;
max-width: 400rpx;
height: 96rpx;
padding: 0 32rpx;
border: 2rpx solid rgba(255, 138, 128, 0.2);
border-radius: 24rpx;
font-size: 32rpx;
color: #333;
background: linear-gradient(135deg, rgba(255, 138, 128, 0.03), rgba(255, 182, 193, 0.03));
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
text-align: center;
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.08);
box-sizing: border-box;
&:focus {
border-color: #FF8A80;
background: white;
box-shadow: 0 0 0 8rpx rgba(255, 138, 128, 0.12), 0 8rpx 24rpx rgba(255, 138, 128, 0.15);
transform: translateY(-2rpx);
}
&::placeholder {
color: rgba(255, 138, 128, 0.6);
font-size: 30rpx;
}
}
}
/* 自定义保存按钮样式 */
.custom-button-area {
width: 100%;
margin-top: 24rpx;
display: flex;
justify-content: center;
padding: 0 32rpx 8rpx 32rpx;
.custom-save-button {
position: relative;
width: 80%;
max-width: 400rpx;
height: 72rpx;
border: none;
border-radius: 24rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
box-shadow: 0 6rpx 20rpx rgba(255, 138, 128, 0.3);
overflow: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
box-sizing: border-box;
&::after {
border: none;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
opacity: 0;
transition: opacity 0.3s ease;
}
.button-content {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
gap: 8rpx;
.button-icon {
font-size: 24rpx;
color: white;
transition: transform 0.3s ease;
}
.button-text {
font-size: 28rpx;
font-weight: 600;
color: white;
letter-spacing: 0.5rpx;
}
}
.button-shine {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transform: translateX(-100%) translateY(-100%) rotate(45deg);
transition: transform 0.6s ease;
}
&:hover {
background: linear-gradient(135deg, #FF7A70, #FFA6B1);
transform: translateY(-3rpx) scale(1.02);
box-shadow: 0 12rpx 32rpx rgba(255, 138, 128, 0.4);
&::before {
opacity: 1;
}
.button-content .button-icon {
transform: scale(1.1) rotate(5deg);
}
.button-shine {
transform: translateX(100%) translateY(100%) rotate(45deg);
}
}
&:active {
transform: translateY(-1rpx) scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.3);
transition: all 0.1s ease;
}
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
/* 悬浮指示器脉冲动画 */
@keyframes pulse {
0%, 100% {
opacity: 0.7;
transform: translateY(-50%) scale(1);
}
50% {
opacity: 1;
transform: translateY(-50%) scale(1.2);
}
}
@keyframes breathe {
0%, 100% {
opacity: 0.3;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.02);
}
}
@keyframes ripple {
0% {
transform: scale(1);
opacity: 0.8;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.profile-container > view {
animation: fadeIn 0.5s ease-out;
}
/* 响应式设计 */
@media screen and (max-width: 750rpx) {
.user-info-form {
padding: 32rpx 24rpx 16rpx 24rpx;
.form-item {
margin-bottom: 32rpx;
.form-label {
font-size: 30rpx;
margin-bottom: 16rpx;
}
}
.avatar-form-item {
margin-bottom: 36rpx;
.avatar-selector {
.avatar-button {
.avatar-container {
padding: 6rpx;
}
}
.avatar-tips {
font-size: 26rpx;
padding: 10rpx 20rpx;
}
}
}
.nickname-input {
width: 90%;
height: 88rpx;
font-size: 30rpx;
padding: 0 28rpx;
box-sizing: border-box;
}
}
// 小屏幕自定义按钮优化
.custom-button-area {
margin-top: 20rpx;
padding: 0 24rpx 6rpx 24rpx;
.custom-save-button {
width: 90%;
height: 64rpx;
border-radius: 24rpx;
.button-content {
gap: 6rpx;
.button-icon {
font-size: 20rpx;
}
.button-text {
font-size: 24rpx;
}
}
}
}
}
@media screen and (min-width: 1200rpx) {
.user-info-form {
padding: 48rpx 40rpx 24rpx 40rpx;
max-width: 640rpx;
margin: 0 auto;
.form-item {
margin-bottom: 48rpx;
.form-label {
font-size: 36rpx;
margin-bottom: 24rpx;
}
}
.avatar-form-item {
margin-bottom: 52rpx;
.avatar-selector {
.avatar-button {
margin-bottom: 28rpx;
.avatar-container {
padding: 12rpx;
}
}
.avatar-tips {
font-size: 30rpx;
padding: 14rpx 28rpx;
}
}
}
.nickname-input {
width: 70%;
height: 104rpx;
font-size: 36rpx;
padding: 0 40rpx;
box-sizing: border-box;
}
}
// 大屏幕自定义按钮优化
.custom-button-area {
margin-top: 32rpx;
padding: 0 40rpx 12rpx 40rpx;
.custom-save-button {
width: 70%;
height: 80rpx;
border-radius: 24rpx;
.button-content {
gap: 10rpx;
.button-icon {
font-size: 28rpx;
}
.button-text {
font-size: 32rpx;
}
}
}
}
}
.profile-container > view:nth-child(1) { animation-delay: 0.1s; }
.profile-container > view:nth-child(2) { animation-delay: 0.2s; }
.profile-container > view:nth-child(3) { animation-delay: 0.3s; }
.profile-container > view:nth-child(4) { animation-delay: 0.4s; }
.profile-container > view:nth-child(5) { animation-delay: 0.5s; }
</style>