pet/pages/profile/user-info.vue

582 lines
12 KiB
Vue

<template>
<view class="user-info-container page-container-with-bg">
<!-- 头像设置卡片 -->
<view class="avatar-card">
<view class="card-header">
<text class="card-title">头像设置</text>
</view>
<view class="avatar-section">
<view class="avatar-wrapper" @click="chooseAvatar">
<u-avatar
:src="userInfo.avatarUrl || ''"
:text="userInfo.nickName ? userInfo.nickName.charAt(0) : '👤'"
size="120"
shape="circle"
bg-color="linear-gradient(135deg, #FF8A80, #FFB6C1)"
color="white"
font-size="50"
></u-avatar>
<view class="avatar-edit-icon">
<text class="edit-text">📷</text>
</view>
</view>
<view class="avatar-tips">
<text class="tips-text">点击更换头像</text>
</view>
</view>
</view>
<!-- 基本信息卡片 -->
<view class="info-card">
<view class="card-header">
<text class="card-title">基本信息</text>
</view>
<view class="info-list">
<view class="info-item">
<view class="info-label">
<text class="label-text">昵称</text>
</view>
<view class="info-input">
<u-input
v-model="userInfo.nickName"
placeholder="请输入昵称"
border="none"
:custom-style="inputStyle"
@change="onNickNameChange"
></u-input>
</view>
</view>
<view class="info-item">
<view class="info-label">
<text class="label-text">性别</text>
</view>
<view class="info-value" @click="showGenderPicker">
<text class="value-text">{{ userInfo.gender || '请选择' }}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<view class="info-item">
<view class="info-label">
<text class="label-text">生日</text>
</view>
<view class="info-value" @click="showDatePicker">
<text class="value-text">{{ userInfo.birthday || '请选择' }}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<view class="info-item">
<view class="info-label">
<text class="label-text">地区</text>
</view>
<view class="info-value" @click="showRegionPicker">
<text class="value-text">{{ userInfo.region || '请选择' }}</text>
<text class="arrow-icon">→</text>
</view>
</view>
</view>
</view>
<!-- 个人简介卡片 -->
<view class="bio-card">
<view class="card-header">
<text class="card-title">个人简介</text>
</view>
<view class="bio-section">
<u-textarea
v-model="userInfo.bio"
placeholder="介绍一下自己和你的宠物吧..."
:maxlength="200"
count
:custom-style="textareaStyle"
@change="onBioChange"
></u-textarea>
</view>
</view>
<!-- 账号信息卡片 -->
<view class="account-card">
<view class="card-header">
<text class="card-title">账号信息</text>
</view>
<view class="account-list">
<view class="account-item">
<view class="account-label">
<text class="label-text">注册时间</text>
</view>
<view class="account-value">
<text class="value-text">{{ formatDate(userInfo.registerTime) }}</text>
</view>
</view>
<view class="account-item">
<view class="account-label">
<text class="label-text">使用天数</text>
</view>
<view class="account-value">
<text class="value-text">{{ loginDays }} 天</text>
</view>
</view>
</view>
</view>
<!-- 保存按钮 -->
<view class="save-section">
<u-button
type="primary"
:custom-style="buttonStyle"
@click="saveUserInfo"
:loading="saving"
>
{{ saving ? '保存中...' : '保存修改' }}
</u-button>
</view>
<!-- 性别选择器 -->
<u-picker
:show="showGender"
:columns="genderColumns"
@confirm="onGenderConfirm"
@cancel="showGender = false"
></u-picker>
<!-- 日期选择器 -->
<u-datetime-picker
:show="showDate"
v-model="selectedDate"
mode="date"
@confirm="onDateConfirm"
@cancel="showDate = false"
></u-datetime-picker>
<!-- 地区选择器 -->
<u-picker
:show="showRegion"
:columns="regionColumns"
@confirm="onRegionConfirm"
@cancel="showRegion = false"
></u-picker>
</view>
</template>
<script>
import { reactive, ref, onMounted, computed } from 'vue'
export default {
name: 'UserInfoPage',
setup() {
// 响应式数据
const userInfo = reactive({
nickName: '',
avatarUrl: '',
gender: '',
birthday: '',
region: '',
bio: '',
registerTime: ''
})
const loginDays = ref(0)
const saving = ref(false)
// 选择器状态
const showGender = ref(false)
const showDate = ref(false)
const showRegion = ref(false)
const selectedDate = ref(Date.now())
// 选择器数据
const genderColumns = ref([
['男', '女', '保密']
])
const regionColumns = ref([
['北京市', '上海市', '广州市', '深圳市', '杭州市', '成都市', '武汉市', '西安市', '南京市', '重庆市']
])
// 样式配置
const inputStyle = {
fontSize: '30rpx',
color: '#333333'
}
const textareaStyle = {
fontSize: '28rpx',
color: '#333333'
}
const buttonStyle = {
background: 'linear-gradient(135deg, #FF8A80, #FFB6C1)',
borderRadius: '24rpx',
height: '88rpx',
fontSize: '32rpx'
}
// 生命周期
onMounted(() => {
loadUserInfo()
})
// 方法定义
const loadUserInfo = () => {
try {
const savedUserInfo = uni.getStorageSync('userInfo') || {}
Object.assign(userInfo, {
nickName: savedUserInfo.nickName || '',
avatarUrl: savedUserInfo.avatarUrl || '',
gender: savedUserInfo.gender || '',
birthday: savedUserInfo.birthday || '',
region: savedUserInfo.region || '',
bio: savedUserInfo.bio || '',
registerTime: savedUserInfo.registerTime || new Date().toISOString()
})
calculateLoginDays()
} catch (error) {
console.error('加载用户信息失败:', error)
}
}
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 chooseAvatar = () => {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
userInfo.avatarUrl = res.tempFilePaths[0]
uni.showToast({
title: '头像已更新',
icon: 'success'
})
},
fail: (err) => {
console.error('选择头像失败:', err)
uni.showToast({
title: '选择头像失败',
icon: 'none'
})
}
})
}
const showGenderPicker = () => {
showGender.value = true
}
const showDatePicker = () => {
showDate.value = true
}
const showRegionPicker = () => {
showRegion.value = true
}
const onGenderConfirm = (e) => {
userInfo.gender = e.value[0]
showGender.value = false
}
const onDateConfirm = (e) => {
const date = new Date(e.value)
userInfo.birthday = date.toISOString().split('T')[0]
showDate.value = false
}
const onRegionConfirm = (e) => {
userInfo.region = e.value[0]
showRegion.value = false
}
const onNickNameChange = (value) => {
userInfo.nickName = value
}
const onBioChange = (value) => {
userInfo.bio = value
}
const saveUserInfo = async () => {
if (!userInfo.nickName.trim()) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
})
return
}
saving.value = true
try {
// 保存到本地存储
uni.setStorageSync('userInfo', userInfo)
// 模拟网络请求延迟
await new Promise(resolve => setTimeout(resolve, 1000))
uni.showToast({
title: '保存成功',
icon: 'success'
})
// 返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('保存用户信息失败:', error)
uni.showToast({
title: '保存失败',
icon: 'none'
})
} finally {
saving.value = false
}
}
const formatDate = (dateString) => {
if (!dateString) return '未设置'
const date = new Date(dateString)
return date.toLocaleDateString('zh-CN')
}
return {
userInfo,
loginDays,
saving,
showGender,
showDate,
showRegion,
selectedDate,
genderColumns,
regionColumns,
inputStyle,
textareaStyle,
buttonStyle,
chooseAvatar,
showGenderPicker,
showDatePicker,
showRegionPicker,
onGenderConfirm,
onDateConfirm,
onRegionConfirm,
onNickNameChange,
onBioChange,
saveUserInfo,
formatDate
}
}
}
</script>
<style lang="scss" scoped>
.user-info-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
padding-bottom: 40rpx;
}
/* 通用卡片样式 */
.avatar-card,
.info-card,
.bio-card,
.account-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 {
margin-bottom: 24rpx;
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
}
}
/* 头像设置卡片 */
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
.avatar-wrapper {
position: relative;
margin-bottom: 16rpx;
.avatar-edit-icon {
position: absolute;
bottom: 8rpx;
right: 8rpx;
width: 48rpx;
height: 48rpx;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
.edit-text {
font-size: 24rpx;
}
}
}
.avatar-tips {
.tips-text {
font-size: 24rpx;
color: #999999;
}
}
}
/* 基本信息卡片 */
.info-list {
.info-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
&:last-child {
border-bottom: none;
}
.info-label {
width: 160rpx;
.label-text {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
}
.info-input {
flex: 1;
}
.info-value {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
.value-text {
font-size: 30rpx;
color: #333333;
}
.arrow-icon {
font-size: 24rpx;
color: #FF8A80;
}
}
}
}
/* 个人简介卡片 */
.bio-section {
.bio-textarea {
width: 100%;
min-height: 200rpx;
padding: 20rpx;
border: 1rpx solid rgba(255, 138, 128, 0.2);
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.5);
font-size: 28rpx;
color: #333333;
line-height: 1.6;
}
}
/* 账号信息卡片 */
.account-list {
.account-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
&:last-child {
border-bottom: none;
}
.account-label {
.label-text {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
}
.account-value {
.value-text {
font-size: 30rpx;
color: #666666;
}
}
}
}
/* 保存按钮 */
.save-section {
margin: 40rpx 30rpx 0 30rpx;
}
/* 响应式设计 */
@media (max-width: 375px) {
.user-info-container {
.avatar-card,
.info-card,
.bio-card,
.account-card {
margin: 0 20rpx 20rpx 20rpx;
padding: 24rpx;
}
.save-section {
margin: 32rpx 20rpx 0 20rpx;
}
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.user-info-container > view {
animation: fadeIn 0.5s ease-out;
}
.user-info-container > view:nth-child(1) { animation-delay: 0.1s; }
.user-info-container > view:nth-child(2) { animation-delay: 0.2s; }
.user-info-container > view:nth-child(3) { animation-delay: 0.3s; }
.user-info-container > view:nth-child(4) { animation-delay: 0.4s; }
.user-info-container > view:nth-child(5) { animation-delay: 0.5s; }
</style>