pet/pages/profile/user-info.vue

775 lines
17 KiB
Vue
Raw Permalink 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="user-info-container page-container-with-bg">
<!-- 模式提示仅首次设置模式显示 -->
<view v-if="isSetupMode" class="mode-tip">
<text class="tip-text">{{ phoneSkipped ? '完善个人信息' : '欢迎!请完善您的个人信息' }}</text>
</view>
<!-- 头像设置卡片 -->
<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"
></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 ? '保存中...' : (isSetupMode ? '完成设置' : '保存修改') }}
</u-button>
<!-- 跳过按钮(仅首次设置模式显示) -->
<view v-if="isSetupMode" class="skip-section">
<text class="skip-button" @click="skipSetup">暂时跳过</text>
</view>
</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'
import { completeUserProfile } from '@/http/api/profile.js'
import {
saveProfileData,
STORAGE_KEYS
} from '@/utils/loginState.js'
export default {
name: 'UserInfoPage',
setup() {
// 响应式数据
const userInfo = reactive({
nickName: '',
avatarUrl: '',
gender: '',
birthday: '',
region: '',
bio: '',
registerTime: ''
})
const loginDays = ref(0)
const saving = ref(false)
// 页面模式相关
const isSetupMode = ref(false) // 是否为首次设置模式
const phoneSkipped = 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(() => {
initPageMode()
loadUserInfo()
})
// 方法定义
const initPageMode = () => {
// 获取页面参数
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options || {}
// 判断页面模式
isSetupMode.value = options.mode === 'setup'
phoneSkipped.value = options.phoneSkipped === 'true'
console.log('页面模式:', isSetupMode.value ? '首次设置' : '编辑模式')
console.log('手机号授权状态:', phoneSkipped.value ? '已跳过' : '已授权')
}
const loadUserInfo = async () => {
try {
if (isSetupMode.value) {
// 首次设置模式:尝试获取微信用户信息
await loadWechatUserInfo()
} else {
// 编辑模式:加载现有用户信息
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 loadWechatUserInfo = async () => {
try {
// 在首次设置模式下,尝试获取微信用户信息
console.log('尝试获取微信用户信息...')
// 检查是否有存储的微信用户信息
const wxUserInfo = uni.getStorageSync(STORAGE_KEYS.WX_USER_INFO)
if (wxUserInfo) {
userInfo.nickName = wxUserInfo.nickName || ''
userInfo.avatarUrl = wxUserInfo.avatarUrl || ''
console.log('从存储中获取微信用户信息:', wxUserInfo)
return
}
// 如果没有存储的信息,使用默认值
userInfo.nickName = ''
userInfo.avatarUrl = ''
userInfo.registerTime = new Date().toISOString()
console.log('使用默认用户信息')
} catch (error) {
console.error('获取微信用户信息失败:', error)
// 使用默认值
userInfo.nickName = ''
userInfo.avatarUrl = ''
userInfo.registerTime = new Date().toISOString()
}
}
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 (saving.value) {
return
}
if (!userInfo.nickName.trim()) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
})
return
}
saving.value = true
try {
if (isSetupMode.value) {
// 首次设置模式调用完善用户信息API
await completeUserProfile({
nickName: userInfo.nickName.trim(),
avatarUrl: userInfo.avatarUrl,
gender: userInfo.gender,
birthday: userInfo.birthday,
region: userInfo.region,
bio: userInfo.bio
}, {
custom: {
loading: false // 使用页面自己的loading状态
}
})
// 使用状态管理工具保存用户信息并标记完成
saveProfileData(userInfo)
} else {
// 编辑模式:直接保存到本地存储
uni.setStorageSync(STORAGE_KEYS.USER_INFO, userInfo)
// 模拟网络请求延迟
await new Promise(resolve => setTimeout(resolve, 1000))
}
uni.showToast({
title: '保存成功',
icon: 'success'
})
// 根据模式决定跳转逻辑
setTimeout(() => {
if (isSetupMode.value) {
// 首次设置完成后跳转到profile页面
uni.reLaunch({
url: '/pages/profile/profile'
})
} else {
// 编辑模式返回上一页
uni.navigateBack()
}
}, 1500)
} catch (error) {
console.error('保存用户信息失败:', error)
// 根据错误类型显示不同的提示信息
let errorMessage = '保存失败,请重试'
if (error.message) {
if (error.message.includes('网络')) {
errorMessage = '网络连接异常,请检查网络后重试'
} else if (error.statusCode === 401 || error.statusCode === 403) {
errorMessage = '登录已过期,请重新登录'
} else if (error.statusCode >= 500) {
errorMessage = '服务器异常,请稍后重试'
} else if (error.message.includes('昵称')) {
errorMessage = '昵称格式不正确,请修改后重试'
} else {
errorMessage = error.message
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
})
} finally {
saving.value = false
}
}
const skipSetup = () => {
if (isSetupMode.value) {
uni.showModal({
title: '确认跳过',
content: '跳过个人信息设置,您可以稍后在个人中心完善信息',
success: (res) => {
if (res.confirm) {
// 设置默认用户信息
const defaultUserInfo = {
nickName: '用户' + Date.now().toString().slice(-6),
avatarUrl: '',
gender: '',
birthday: '',
region: '',
bio: '',
registerTime: new Date().toISOString()
}
// 使用状态管理工具保存默认用户信息
saveProfileData(defaultUserInfo)
// 跳转到profile页面
uni.reLaunch({
url: '/pages/profile/profile'
})
}
}
})
}
}
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,
// 新增的模式相关变量和方法
isSetupMode,
phoneSkipped,
skipSetup
}
}
}
</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;
}
/* 模式提示 */
.mode-tip {
margin: 20rpx 30rpx;
padding: 24rpx;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20rpx);
border-radius: 24rpx;
text-align: center;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.tip-text {
font-size: 32rpx;
font-weight: 600;
color: #333333;
line-height: 1.4;
}
}
/* 通用卡片样式 */
.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;
.skip-section {
margin-top: 24rpx;
text-align: center;
.skip-button {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
text-decoration: underline;
padding: 12rpx 24rpx;
&:active {
opacity: 0.7;
}
}
}
}
/* 响应式设计 */
@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>