【MoodVine】个人信息与修改

个人信息管理页面的设计与实现:从展示到编辑

在现代应用开发中,用户信息管理是一个核心功能。本文将详细介绍如何使用Taro框架开发一个完整的个人信息管理模块,包含展示页面和编辑页面。

设计思路

1. 整体架构

复制
- 我的页面(people.jsx)
  └── 个人信息展示
  └── 功能入口
  └── 进入编辑页面入口

- 编辑页面(peopleDetail.jsx)
  └── 头像上传
  └── 昵称编辑
  └── 性别选择
  └── 生日选择
  └── 保存功能

2. 技术选择

  • 核心框架: Taro 3.x(基于React语法)
  • UI组件库: Taroify(适配小程序)
  • 状态管理: React Hooks(useState)
  • 网络请求: 封装axios实例
  • 本地存储: Taro存储API

实现代码

1. 我的页面 - people.jsx

import { View, Text, Image } from '@tarojs/components'
import { Button } from '@taroify/core'
import { useLoad } from '@tarojs/taro'
import Taro from '@tarojs/taro'
import { useState } from 'react'

import bgweek from '../../assets/week-btn.png'
import bgactivity from '../../assets/activity-btn.png'

import 'normalize.css'
import './people.scss'
import request from '../../utils/request'

const People = () => {
  const [userInfo, setUserInfo] = useState({})

// 获取用户信息const getUserInfo = async() => {
    try {
      const res = await request.get('/user/getUserInfo')
      if (res.data.code === 200) {
        setUserInfo(res.data.data)
        Taro.setStorageSync('userInfo', res.data.data)
      }
    } catch (error) {
      console.error('获取用户信息失败:', error)
      Taro.showToast({ title: '数据加载失败', icon: 'none' })
    }
  }

// 页面加载钩子useLoad(() => {
    getUserInfo()
  });

// 退出登录处理const handleLogout = () => {
    try {
// 清理存储Taro.removeStorageSync('token');
      Taro.removeStorageSync('userInfo');

// 返回首页Taro.reLaunch({ url: '/pages/index/index' });

// 提示用户Taro.showToast({
        title: '已退出登录',
        icon: 'success',
        duration: 1000
      });

    } catch (error) {
      console.error('退出登录失败:', error);
      Taro.showToast({ title: '退出登录失败', icon: 'none' });
    }
  };

  return (
    
      {/* 用户信息头部 */}
      
        
        
          {userInfo.nickname || '未设置昵称'}
          破壳日:{userInfo.birthday || '未设置'}
        
         Taro.navigateTo({ url: '/pages/peopleDetail/peopleDetail' })}
        >
          编辑信息 >
        
      

      {/* 功能入口 */}
      
        
          周报
        
        
           Taro.switchTab({ url: '/pages/activityList/activityList' })}
          >
            活动
          
        
      

      {/* 退出登录按钮 */}
      
    
  );
};

export default People;

页面样式(people.scss)

.header {
    width: 90%;
    height: 200px;
    display: flex;
    align-items: center;
    background-color: rgb(255, 255, 255);
    border-radius: 20px;
    margin: auto;
    padding: 5px;
    top: 10px;
    box-shadow: 0px 0px 2px #00000007;
}

.avatar {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    margin-left: 10%;

  }

.middle {
    width: 50%;
}
.nickname {
    font-weight: bold;
    font-size: 35px;
    margin-left: 30px;
}

.unknown {
    display: block;
    font-size: 25px;
    color: rgb(145, 145, 145);
    margin-left: 30px;
    margin-top: 20px;
}

.seemore {
  
  font-size: small;
  color: rgb(172, 172, 172);
}

.log-button {
  position: fixed;
  bottom: 10%;
  background-color: #fcb391;
  color: white;
  border-radius: 8px;
  font-weight: bold;
  border: none;
  padding: 12px;
  width: 90%;
  margin-left: 5%;

  &:active {
    background-color: #8B4513;
  }
}

.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: auto;
  top: 20px;
}

.week-analysis, .activity {
    // position: relative; /* 定位基准 */
    float: left;
    width: 40%;
    height: 150px;
    margin: auto;
    margin-top: 10px;
    margin-left: 5%;
    margin-right: 5%;
    background-size: 100% 100%;
    border-radius: 10px;
    background-repeat: no-repeat;
    justify-content: center;
}

.week-content {
    font-family: 'summerpoem';
    font-size: 80px;
    color: #343357;
    font-weight: bold;
    margin-left: 55px;
    margin-top: 40px;
}

.activity-content {
    position: relative;
    font-family: 'summerpoem';
    font-size: 80px;
    color: #ffffff;
    font-weight: bold;
    padding-left: 50px;
    margin-top: 60px;
}

2. 个人信息编辑页 - peopleDetail.jsx

import { View, Text, Image, Input } from '@tarojs/components'
import { Button, Field, Radio, Cell } from '@taroify/core'
import { useLoad } from '@tarojs/taro'
import Taro from '@tarojs/taro'
import { useState } from 'react'
import { Popup, DatetimePicker } from '@taroify/core'
import dayjs from 'dayjs'

import 'normalize.css'
import './peopleDetail.scss'
import request from '../../utils/request'

const PeopleDetail = () => {
// 表单状态管理const [avatar, setAvatar] = useState("")
  const [nickname, setNickname] = useState("")
  const [gender, setGender] = useState("0")
  const [birthday, setBirthday] = useState("")

// UI状态const [showDatePicker, setShowDatePicker] = useState(false)
  const [tempDate, setTempDate] = useState(new Date())

// 初始化表单数据useLoad(() => {
    const storedUserInfo = Taro.getStorageSync('userInfo') || {}
    setAvatar(storedUserInfo.avatar || '')
    setNickname(storedUserInfo.nickname || '')
    setGender(String(storedUserInfo.gender || '0'))
    setBirthday(storedUserInfo.birthday || '')

    if (storedUserInfo.birthday) {
      setTempDate(new Date(storedUserInfo.birthday))
    }
  })

// 头像上传处理const uploadAvatar = async () => {
    try {
// 选择图片const res = await Taro.chooseImage({
        count: 1,
        sizeType: ['compressed'],
        sourceType: ['album', 'camera']
      })

      if (!res.tempFilePaths.length) return

// 显示加载状态Taro.showLoading({ title: '上传中...', mask: true })

// 上传图片const uploadRes = await Taro.uploadFile({
        url: '',
        filePath: res.tempFilePaths[0],
        name: 'file',
        formData: {
          timestamp: Date.now()
        },
      })

      Taro.hideLoading()

// 处理响应const data = JSON.parse(uploadRes.data)
      if (data.msg) {
        setAvatar(data.msg)
        Taro.showToast({ title: '头像上传成功', icon: 'success' })
      } else {
        Taro.showToast({ title: '上传失败,请重试', icon: 'none' })
      }

    } catch (error) {
      Taro.hideLoading()
      console.error('图片上传失败:', error)
      Taro.showToast({ title: '图片上传失败', icon: 'error' })
    }
  }

// 处理日期变更const handleDateChange = (date) => {
    setTempDate(date)
  }

// 确认日期选择const handleDateConfirm = () => {
    const formattedDate = dayjs(tempDate).format('YYYY-MM-DD')
    setBirthday(formattedDate)
    setShowDatePicker(false)
  }

// 保存修改const handleSave = async () => {
// 基本验证if (!nickname.trim()) {
      Taro.showToast({ title: '昵称不能为空', icon: 'none' })
      return
    }

    try {
      const updateData = {
        avatar,
        nickname,
        gender: Number(gender),
        birthday
      }

      Taro.showLoading({ title: '保存中...', mask: true })

// 提交修改const res = await request.post('/user/updateUserInfo', updateData)
      Taro.hideLoading()

      if (res.data.code === 200) {
// 更新本地存储const newUserInfo = {
          ...(Taro.getStorageSync('userInfo') || {}),
          ...updateData
        }
        Taro.setStorageSync('userInfo', newUserInfo)

        Taro.showToast({
          title: '保存成功',
          icon: 'success',
          duration: 1500,
          complete: () => Taro.navigateBack()
        })
      } else {
        Taro.showToast({
          title: res.data.msg || '保存失败',
          icon: 'none',
          duration: 2000
        })
      }
    } catch (error) {
      Taro.hideLoading()
      console.error('保存失败:', error)
      Taro.showToast({
        title: '网络错误,请重试',
        icon: 'none',
        duration: 2000
      })
    }
  }

  return (
    
      {/* 表单区域 */}
      
        {/* 头像区域 */}
        
              "}
              />
              点击更换
            
          }
        />

        {/* 昵称区域 */}
        
           setNickname(e.detail.value)}
            className="nickname-input"
          />
        

        {/* 性别区域 */}
        
           setGender(value)}
            direction="horizontal"
            className="gender-group"
          >
            
            
          
        

        {/* 生日区域 */}
         setShowDatePicker(true)}
          className="birthday-field"
        >
          
            {birthday || '请选择生日'}
          
        
      

      {/* 保存按钮 */}
      

      {/* 日期选择器弹窗 */}
       setShowDatePicker(false)}
        className="date-popup"
      >
        
          

          
            
            
          
        
      
    
  )
}

export default PeopleDetail

编辑页样式(peopleDetail.scss)

.people-detail-container {
  padding: 16px;
  min-height: 100vh;
  
  .avatar-image {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background-color: #eee;
  }
  
  .taroify-field__label {
    flex: none;
    width: 80px;
  }
  
  .save-button {
    width: 90%;
    background-color: #fcb391;
    color: white;
    border-radius: 8px;
    font-weight: bold;
    border: none;
    padding: 12px;
    margin: auto;
    margin-top: 40px;
    
    &:active {
        background-color: #8B4513;
    }
    }
}

核心功能实现详解

1. 头像上传功能

头像上传涉及几个关键步骤:

const uploadAvatar = async () => {
// 1. 选择图片const res = await Taro.chooseImage({...})

// 2. 显示加载状态Taro.showLoading({...})

// 3. 上传文件const uploadRes = await Taro.uploadFile({...})

// 4. 处理响应if (responseOk) {
    setAvatar(newUrl)
  } else {
    showError()
  }
}

技术要点

  • 使用Taro.chooseImage获取用户选择的图片
  • 显示加载状态提升用户体验
  • 使用Taro.uploadFile执行文件上传
  • 处理成功/失败响应

2. 性别选择组件

使用Radio.Group实现单选效果:


  


说明

  • 性别存储为字符串"0"(女)或"1"(男)
  • 与后端API的数据格式保持一致
  • 水平排列提供更好的用户体验

3. 日期选择器实现

使用DatetimePicker组件实现生日选择:

{/* 生日字段 */}
 setShowDatePicker(true)}>
  {birthday || '请选择生日'}


{/* 日期选择器弹窗 */}

  
    
    
  


关键点

  • 使用临时状态tempDate存储当前选择的日期
  • 使用dayjs进行日期格式化
  • 自定义操作按钮提供更好的用户体验

4. 表单数据管理

使用React状态管理表单数据:

// 状态声明const [avatar, setAvatar] = useState("")
const [nickname, setNickname] = useState("")
const [gender, setGender] = useState("0")
const [birthday, setBirthday] = useState("")

// 保存处理const handleSave = async () => {
// 表单验证if (!nickname) return

// 构造数据const updateData = {
    avatar,
    nickname,
    gender: Number(gender),
    birthday
  }

// 提交保存...
}

总结与最佳实践

  1. 状态管理策略
  • 使用多个useState分别管理不同的表单字段
  • 初始值从本地存储获取,避免额外API请求
  • 临时状态管理选择中的日期
  1. 用户体验优化
  • 所有操作都提供加载状态反馈
  • 重要操作(上传、保存)提供成功/失败提示
  • 字段验证避免无效数据提交
  • 日期选择器自定义操作按钮提升易用性
  1. 错误处理机制
  • 为所有可能失败的操作添加错误处理
  • 区分不同错误类型提供对应提示
  • 捕获并记录错误信息方便调试
  1. 性能优化
  • 合理使用本地存储减少API请求
  • 使用压缩图片减少上传时间
  • 只在必要时显示弹窗

本实现方案提供了一个完整的用户信息管理模块,包含展示和编辑两大功能,采用了Taro框架结合Taroify UI库进行开发。读者可以根据实际需求进行调整,例如添加更多的用户信息字段,或者结合Redux等状态管理库实现更复杂的应用状态。

【MoodVine】个人信息与修改_第1张图片

【MoodVine】个人信息与修改_第2张图片

你可能感兴趣的:(MoodVine,个人博客,react.js,taro,微信小程序,前端,javascript)