pet/pages/profile/profile.vue

1623 lines
35 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-with-bg">
<!-- 用户信息卡片 -->
<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 class="user-action" v-if="userInfo.nickName" @click="handleUserAction">
<text class="action-icon">⚙️</text>
</view>
</view>
<!-- 宠物概览统计卡片 -->
<view class="stats-card">
<view class="stats-header">
<text class="stats-title">我的宠物</text>
<view class="stats-action" @click="navigateTo('/pages/pets/pets')">
<text class="action-text">管理</text>
<text class="action-arrow">→</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 class="header-right" @click="navigateToFamily">
<text class="member-count">{{ familyStats.memberCount }}人</text>
<text class="action-arrow">→</text>
</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 class="header-right" @click="navigateTo('/pages/adoption/my-adoption')">
<text class="action-arrow">→</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="settings-list">
<view class="setting-item" @click="navigateTo('/pages/profile/notifications')">
<view class="setting-left">
<text class="setting-icon">🔔</text>
<text class="setting-text">消息通知</text>
</view>
<view class="setting-right">
<text class="setting-badge" v-if="notificationCount > 0">{{ notificationCount }}</text>
<text class="setting-arrow">→</text>
</view>
</view>
<view class="setting-item" @click="navigateTo('/pages/profile/privacy')">
<view class="setting-left">
<text class="setting-icon">🔒</text>
<text class="setting-text">隐私设置</text>
</view>
<view class="setting-right">
<text class="setting-arrow">→</text>
</view>
</view>
<view class="setting-item" @click="navigateTo('/pages/profile/feedback')">
<view class="setting-left">
<text class="setting-icon">💬</text>
<text class="setting-text">意见反馈</text>
</view>
<view class="setting-right">
<text class="setting-arrow">→</text>
</view>
</view>
<view class="setting-item" @click="navigateTo('/pages/profile/about')">
<view class="setting-left">
<text class="setting-icon"></text>
<text class="setting-text">关于我们</text>
</view>
<view class="setting-right">
<text class="setting-arrow">→</text>
</view>
</view>
</view>
</view>
<!-- 用户信息设置模态框 -->
<u-modal
:show="showUserInfoModal"
title="完善个人信息"
@confirm="saveUserInfo"
@cancel="cancelUserInfo"
:show-cancel-button="true"
confirm-text="保存"
cancel-text="取消"
>
<view class="user-info-form" :class="{ 'form-animate': showUserInfoModal }">
<view class="form-item">
<text class="form-label">头像</text>
<view class="avatar-selector">
<button
class="avatar-button"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
<u-avatar
:src="tempUserInfo.avatarUrl || ''"
:text="tempUserInfo.nickName ? tempUserInfo.nickName.charAt(0) : '👤'"
size="80"
shape="circle"
bg-color="linear-gradient(135deg, #FF8A80, #FFB6C1)"
color="white"
font-size="32"
></u-avatar>
<view class="avatar-edit-overlay">
<text class="edit-icon">📷</text>
</view>
</button>
<text class="avatar-tips">点击选择头像</text>
</view>
</view>
<view class="form-item">
<text class="form-label">昵称</text>
<input
type="nickname"
v-model="tempUserInfo.nickName"
placeholder="请输入昵称"
class="nickname-input"
:class="{ 'error': nicknameError }"
maxlength="20"
@input="onNicknameInput"
@blur="onNicknameBlur"
@focus="onNicknameFocus"
/>
<view class="nickname-tips" v-if="nicknameTips">
<text class="tips-text" :class="{ 'error': nicknameError }">{{ nicknameTips }}</text>
</view>
</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 notificationCount = ref(3)
// 用户信息设置模态框相关数据
const showUserInfoModal = ref(false)
const tempUserInfo = ref({
nickName: '',
avatarUrl: ''
})
// 昵称输入相关状态
const nicknameTips = ref('')
const nicknameError = ref(false)
const lastValidNickname = ref('')
// 宠物统计数据
const petStats = reactive({
petCount: 0,
recordCount: 0,
reminderCount: 0
})
// 家庭统计数据
const familyStats = reactive({
memberCount: 1
})
// 领养统计数据
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: [] }
familyStats.memberCount = familyData.members.length || 1
// 加载领养数据
const adoptionData = uni.getStorageSync('adoptionData') || {
published: [],
applications: []
}
adoptionStats.publishedCount = adoptionData.published.length
adoptionStats.applicationCount = adoptionData.applications.length
// 加载通知数量
const notifications = uni.getStorageSync('notifications') || []
const unreadNotifications = notifications.filter(n => !n.read)
notificationCount.value = unreadNotifications.length
} catch (error) {
console.error('加载用户统计数据失败:', error)
// 设置默认值
petStats.petCount = 0
petStats.recordCount = 0
petStats.reminderCount = 0
familyStats.memberCount = 1
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: ''
}
// 重置昵称相关状态
nicknameTips.value = ''
nicknameError.value = false
lastValidNickname.value = ''
// 显示用户信息设置模态框
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 checkNewFeatures = () => {
const lastVersion = uni.getStorageSync('lastAppVersion') || '1.0.0'
const currentVersion = '1.1.0' // 当前版本
if (lastVersion !== currentVersion) {
// 显示新功能提示
setTimeout(() => {
uni.showModal({
title: '发现新功能',
content: '新增了家庭共享和领养管理功能,快来体验吧!',
confirmText: '去看看',
success: (res) => {
if (res.confirm) {
uni.setStorageSync('lastAppVersion', currentVersion)
}
}
})
}, 2000)
}
}
// 头像选择处理
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 (!validateNickname(tempUserInfo.value.nickName)) {
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: ''
}
nicknameTips.value = ''
nicknameError.value = false
lastValidNickname.value = ''
// 显示成功提示
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: ''
}
// 重置昵称状态
nicknameTips.value = ''
nicknameError.value = false
lastValidNickname.value = ''
}
// 昵称输入处理
const onNicknameInput = (e) => {
const value = e.detail.value
tempUserInfo.value.nickName = value
// 实时验证昵称
validateNickname(value)
}
// 昵称获得焦点
const onNicknameFocus = () => {
nicknameTips.value = '支持中文、英文、数字2-20个字符'
nicknameError.value = false
}
// 昵称失去焦点
const onNicknameBlur = () => {
const currentNickname = tempUserInfo.value.nickName
// 保存当前有效昵称,以备安全检测失败时恢复
if (currentNickname && !nicknameError.value) {
lastValidNickname.value = currentNickname
}
// 微信会在失去焦点时进行内容安全检测
// 如果检测不通过,内容会被自动清空
setTimeout(() => {
// 检查是否被清空(安全检测失败)
if (lastValidNickname.value && !tempUserInfo.value.nickName) {
nicknameTips.value = '昵称内容不符合规范,已自动清空'
nicknameError.value = true
} else if (tempUserInfo.value.nickName) {
// 内容未被清空,验证通过
nicknameTips.value = '昵称可用'
nicknameError.value = false
}
}, 100)
}
// 昵称验证
const validateNickname = (nickname) => {
if (!nickname) {
nicknameTips.value = ''
nicknameError.value = false
return true
}
// 长度检查
if (nickname.length < 2) {
nicknameTips.value = '昵称至少需要2个字符'
nicknameError.value = true
return false
}
if (nickname.length > 20) {
nicknameTips.value = '昵称不能超过20个字符'
nicknameError.value = true
return false
}
// 字符格式检查(中文、英文、数字、部分特殊字符)
const validPattern = /^[\u4e00-\u9fa5a-zA-Z0-9_\-\s]+$/
if (!validPattern.test(nickname)) {
nicknameTips.value = '昵称只能包含中文、英文、数字、下划线和连字符'
nicknameError.value = true
return false
}
// 验证通过
nicknameTips.value = '昵称格式正确'
nicknameError.value = false
return true
}
// 初始化时检查新功能
onMounted(() => {
initPage()
checkNewFeatures()
})
return {
userInfo,
loginDays,
notificationCount,
petStats,
familyStats,
adoptionStats,
handleUserAction,
navigateTo,
navigateToFamily,
inviteFamily,
showReminders,
refreshData,
formatNumber,
onShow,
onPullDownRefresh,
// 用户信息设置模态框相关
showUserInfoModal,
tempUserInfo,
onChooseAvatar,
saveUserInfo,
cancelUserInfo,
// 昵称输入相关
nicknameTips,
nicknameError,
onNicknameInput,
onNicknameFocus,
onNicknameBlur,
validateNickname
}
},
// 页面生命周期
onShow() {
this.onShow()
},
onPullDownRefresh() {
this.onPullDownRefresh()
}
}
</script>
<style lang="scss" scoped>
.profile-container {
padding-bottom: 40rpx;
}
/* 用户信息卡片 */
.user-info-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
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 30rpx 24rpx 30rpx;
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;
.stats-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.stats-action {
display: flex;
align-items: center;
.action-text {
font-size: 24rpx;
color: #FF8A80;
margin-right: 8rpx;
}
.action-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
}
.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 30rpx 24rpx 30rpx;
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;
}
.action-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
}
.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 30rpx 24rpx 30rpx;
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 {
.action-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
}
.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;
}
}
}
/* 设置功能卡片 */
.settings-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
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;
.settings-list {
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
&: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 {
display: flex;
align-items: center;
.setting-badge {
background: #FF8A80;
color: white;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 20rpx;
margin-right: 12rpx;
min-width: 32rpx;
text-align: center;
}
.setting-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
}
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.profile-container {
.user-info-card {
margin: 0 20rpx 20rpx 20rpx;
padding: 24rpx;
}
.stats-card,
.family-card,
.adoption-card,
.settings-card {
margin: 0 20rpx 20rpx 20rpx;
padding: 24rpx;
}
.stats-grid {
gap: 16rpx;
.stat-item {
padding: 20rpx 12rpx;
.stat-number {
font-size: 36rpx;
}
}
}
.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;
}
}
/* 用户信息设置模态框样式 */
.user-info-form {
padding: 40rpx 20rpx;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.3s ease;
&.form-animate {
opacity: 1;
transform: translateY(0);
}
.form-item {
margin-bottom: 40rpx;
.form-label {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.avatar-selector {
display: flex;
flex-direction: column;
align-items: center;
.avatar-button {
position: relative;
background: none;
border: none;
padding: 8rpx;
margin-bottom: 20rpx;
border-radius: 50%;
transition: all 0.3s ease;
&::after {
border: none;
}
&:hover {
background: rgba(255, 138, 128, 0.1);
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
.avatar-edit-overlay {
position: absolute;
bottom: 4rpx;
right: 4rpx;
width: 36rpx;
height: 36rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(255, 138, 128, 0.4);
border: 2rpx solid white;
.edit-icon {
font-size: 20rpx;
color: white;
font-weight: bold;
}
}
}
.avatar-tips {
font-size: 26rpx;
color: #666;
background: rgba(255, 138, 128, 0.1);
padding: 8rpx 16rpx;
border-radius: 20rpx;
border: 1rpx solid rgba(255, 138, 128, 0.2);
}
}
.nickname-input {
width: 100%;
height: 88rpx;
padding: 0 28rpx;
border: 2rpx solid #E8E8E8;
border-radius: 16rpx;
font-size: 32rpx;
color: #333;
background: #FAFAFA;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
&:focus {
border-color: #FF8A80;
background: white;
box-shadow: 0 0 0 6rpx rgba(255, 138, 128, 0.12);
transform: translateY(-2rpx);
}
&::placeholder {
color: #BDBDBD;
font-size: 30rpx;
}
&.error {
border-color: #FF5722;
background: rgba(255, 87, 34, 0.05);
&:focus {
box-shadow: 0 0 0 6rpx rgba(255, 87, 34, 0.12);
}
}
}
.nickname-tips {
margin-top: 16rpx;
padding: 8rpx 12rpx;
border-radius: 8rpx;
background: rgba(102, 102, 102, 0.08);
.tips-text {
font-size: 24rpx;
color: #666;
line-height: 1.5;
display: flex;
align-items: center;
&::before {
content: '';
margin-right: 8rpx;
font-size: 20rpx;
}
&.error {
color: #FF5722;
&::before {
content: '⚠️';
}
}
}
}
}
}
/* 动画效果 */
@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 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 16rpx;
.form-item {
margin-bottom: 32rpx;
.form-label {
font-size: 30rpx;
margin-bottom: 16rpx;
}
.avatar-selector {
.avatar-tips {
font-size: 24rpx;
padding: 6rpx 12rpx;
}
}
.nickname-input {
height: 80rpx;
font-size: 30rpx;
padding: 0 24rpx;
}
}
}
}
@media screen and (min-width: 1200rpx) {
.user-info-form {
padding: 48rpx 32rpx;
max-width: 600rpx;
margin: 0 auto;
.form-item {
margin-bottom: 48rpx;
.form-label {
font-size: 34rpx;
margin-bottom: 24rpx;
}
.nickname-input {
height: 96rpx;
font-size: 34rpx;
padding: 0 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>