This commit is contained in:
张成
2025-09-07 13:26:43 +08:00
parent 9830cd4b2d
commit 8fdee42ab8
7 changed files with 726 additions and 296 deletions

View File

@@ -7,17 +7,75 @@
box-sizing: border-box;
}
// 导航栏
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
height: 98px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 44px 44px 0px 0px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 21px 16px 0px;
box-sizing: border-box;
.navbar_left {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
cursor: pointer;
.back_icon {
width: 18px;
height: 18px;
}
}
.navbar_title {
font-family: 'PingFang SC';
font-weight: 600;
font-size: 17px;
line-height: 1.4em;
color: #000000;
text-align: center;
flex: 1;
}
.navbar_right {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
cursor: pointer;
.save_text {
font-family: 'PingFang SC';
font-weight: 600;
font-size: 16px;
line-height: 1.4em;
color: #000000;
}
}
}
// 主要内容区域
.main_content {
position: relative;
z-index: 5;
flex: 1;
margin-top: 0;
margin-top: 98px;
box-sizing: border-box;
overflow-y: auto;
padding: 15px;
padding: 0px 16px;
padding-bottom: 48px;
// 头像编辑区域
.avatar_section {
@@ -25,195 +83,197 @@
flex-direction: column;
align-items: center;
gap: 12px;
margin-bottom: 30px;
margin-bottom: 48px;
margin-top: 98px;
.avatar_container {
position: relative;
width: 100px;
height: 100px;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.12);
.avatar {
width: 100%;
height: 100%;
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
cursor: pointer;
box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.2);
border: 0.5px solid rgba(255, 255, 255, 0.65);
overflow: hidden;
}
.avatar_overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
right: 0;
width: 32px;
height: 32px;
background: #000000;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
border: 0.5px solid rgba(0, 0, 0, 0.06);
z-index: 10;
.upload_icon {
width: 24px;
height: 24px;
width: 16px;
height: 16px;
}
}
&:hover .avatar_overlay {
opacity: 1;
}
}
.avatar_tip {
font-family: 'PingFang SC';
font-weight: 400;
font-size: 14px;
line-height: 1.4em;
color: rgba(0, 0, 0, 0.6);
}
}
// 表单区域
.form_section {
display: flex;
flex-direction: column;
gap: 24px;
margin-bottom: 48px;
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
.form_item {
.form_group {
background: #FFFFFF;
border-radius: 12px;
overflow: hidden;
&:not(:first-child) {
border-top: 1px solid rgba(0, 0, 0, 0.06);
}
.form_item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
min-height: 44px;
box-sizing: border-box;
.item_left {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.item_icon {
width: 16px;
height: 16px;
}
.item_label {
font-family: 'PingFang SC';
font-weight: 500;
font-size: 14px;
line-height: 1.71em;
color: #000000;
}
}
.item_right {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
justify-content: flex-end;
.item_input {
flex: 1;
text-align: right;
font-family: 'PingFang SC';
font-weight: 600;
font-size: 14px;
line-height: 1.71em;
color: #000000;
border: none;
background: transparent;
outline: none;
&::placeholder {
color: rgba(0, 0, 0, 0.4);
}
}
.item_value {
font-family: 'PingFang SC';
font-weight: 600;
font-size: 14px;
line-height: 1.71em;
color: #000000;
}
.bio_textarea {
flex: 1;
text-align: right;
font-family: 'PingFang SC';
font-weight: 600;
font-size: 14px;
line-height: 1.71em;
color: #000000;
border: none;
background: transparent;
outline: none;
min-height: 20px;
resize: none;
&::placeholder {
color: rgba(0, 0, 0, 0.4);
}
}
.arrow_icon {
width: 14px;
height: 14px;
opacity: 0.2;
}
}
.divider {
position: absolute;
left: 36px;
right: 12px;
height: 0.5px;
background: rgba(0, 0, 0, 0.06);
border-radius: 99px;
}
}
}
}
// 退出登录区域
.logout_section {
margin-top: 16px;
.logout_button {
background: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 16px;
padding: 2px 6px;
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
justify-content: center;
cursor: pointer;
min-height: 48px;
.form_label {
.logout_text {
font-family: 'PingFang SC';
font-weight: 600;
font-size: 16px;
line-height: 1.4em;
color: #000000;
}
}
}
.form_input {
padding: 12px 16px;
background: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 12px;
font-family: 'PingFang SC';
font-weight: 400;
font-size: 16px;
line-height: 1.4em;
color: #000000;
// 加载状态
.loading_container {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
&::placeholder {
color: rgba(0, 0, 0, 0.4);
}
}
.form_textarea {
padding: 12px 16px;
background: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 12px;
font-family: 'PingFang SC';
font-weight: 400;
font-size: 16px;
line-height: 1.5em;
color: #000000;
min-height: 100px;
resize: none;
&::placeholder {
color: rgba(0, 0, 0, 0.4);
}
}
.char_count {
font-family: 'PingFang SC';
font-weight: 400;
font-size: 12px;
line-height: 1.4em;
color: rgba(0, 0, 0, 0.4);
text-align: right;
}
// NTRP等级选择器
.level_selector {
display: flex;
flex-wrap: wrap;
gap: 8px;
.level_item {
padding: 8px 16px;
background: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
&.selected {
background: #000000;
border-color: #000000;
.level_text {
color: #FFFFFF;
}
}
.level_text {
font-family: 'PingFang SC';
font-weight: 500;
font-size: 14px;
line-height: 1.4em;
color: #000000;
transition: color 0.3s ease;
}
&:hover {
border-color: #000000;
}
}
}
// 性别选择器
.gender_selector {
display: flex;
gap: 12px;
.gender_item {
flex: 1;
padding: 12px 16px;
background: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
&.selected {
background: #000000;
border-color: #000000;
.gender_text {
color: #FFFFFF;
}
}
.gender_text {
font-family: 'PingFang SC';
font-weight: 500;
font-size: 16px;
line-height: 1.4em;
color: #000000;
transition: color 0.3s ease;
}
&:hover {
border-color: #000000;
}
}
}
.loading_text {
font-family: 'PingFang SC';
font-weight: 400;
font-size: 16px;
line-height: 1.4em;
color: rgba(0, 0, 0, 0.6);
}
}
}

View File

@@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react';
import { View, Text, Image, ScrollView, Button, Input, Textarea } from '@tarojs/components';
import { View, Text, Image, ScrollView, Input } from '@tarojs/components';
import Taro from '@tarojs/taro';
import './index.scss';
import { UserInfo } from '@/components/UserInfo';
import { UserService } from '@/services/userService';
import { EditModal } from '@/components';
const EditProfilePage: React.FC = () => {
// 用户信息状态
@@ -33,14 +34,19 @@ const EditProfilePage: React.FC = () => {
bio: '',
location: '',
occupation: '',
ntrp_level: 'NTRP 3.0',
ntrp_level: '4.0',
phone: '',
gender: ''
gender: '',
birthday: '2000-01-01'
});
// 加载状态
const [loading, setLoading] = useState(true);
// 编辑弹窗状态
const [edit_modal_visible, setEditModalVisible] = useState(false);
const [editing_field, setEditingField] = useState<string>('');
// 页面加载时初始化数据
useEffect(() => {
load_user_info();
@@ -57,9 +63,10 @@ const EditProfilePage: React.FC = () => {
bio: user_data.bio,
location: user_data.location,
occupation: user_data.occupation,
ntrp_level: user_data.ntrp_level,
ntrp_level: user_data.ntrp_level.replace('NTRP ', ''),
phone: user_data.phone || '',
gender: user_data.gender || ''
gender: user_data.gender || '',
birthday: '2000-01-01' // 默认生日,实际应该从用户数据获取
});
} catch (error) {
console.error('加载用户信息失败:', error);
@@ -110,40 +117,45 @@ const EditProfilePage: React.FC = () => {
});
};
// 处理保存
const handle_save = async () => {
// 验证表单
if (!form_data.nickname.trim()) {
Taro.showToast({
title: '请输入昵称',
icon: 'none'
});
return;
}
try {
await UserService.save_user_info(form_data);
Taro.showToast({
title: '保存成功',
icon: 'success'
});
Taro.navigateBack();
} catch (error) {
console.error('保存失败:', error);
Taro.showToast({
title: '保存失败',
icon: 'none'
});
}
// 处理编辑弹窗
const handle_open_edit_modal = (field: string) => {
setEditingField(field);
setEditModalVisible(true);
};
// 处理返回
const handle_back = () => {
Taro.navigateBack();
const handle_edit_modal_save = (value: string) => {
setFormData(prev => ({ ...prev, [editing_field]: value }));
setEditModalVisible(false);
setEditingField('');
};
const handle_edit_modal_cancel = () => {
setEditModalVisible(false);
setEditingField('');
};
// 处理退出登录
const handle_logout = () => {
Taro.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
// 清除用户数据
Taro.removeStorageSync('user_token');
Taro.removeStorageSync('user_info');
Taro.reLaunch({
url: '/pages/login/index/index'
});
}
}
});
};
return (
<View className="edit_profile_page">
{/* 导航栏 */}
{/* 主要内容 */}
<ScrollView className="main_content" scrollY>
{loading ? (
@@ -152,120 +164,172 @@ const EditProfilePage: React.FC = () => {
</View>
) : (
<>
{/* 头像编辑区域 */}
<View className="avatar_section">
<View className="avatar_container" onClick={handle_avatar_upload}>
<Image className="avatar" src={user_info.avatar} />
<View className="avatar_overlay">
<Image
className="upload_icon"
src={require('../../../static/userInfo/edit2.svg')}
/>
</View>
</View>
{/* 头像编辑区域 */}
<View className="avatar_section">
<View className="avatar_container" onClick={handle_avatar_upload}>
<Image className="avatar" src={user_info.avatar} />
<View className="avatar_overlay">
<Image
className="upload_icon"
src={require('../../../static/userinfo/icon_upload.svg')}
/>
</View>
</View>
<Text className="avatar_tip"></Text>
</View>
{/* 基本信息编辑 */}
<View className="form_section">
{/* 昵称 */}
<View className="form_item">
<Text className="form_label"></Text>
<Input
className="form_input"
value={form_data.nickname}
placeholder="请输入昵称"
onInput={(e) => handle_input_change('nickname', e.detail.value)}
/>
</View>
{/* 个人简介 */}
<View className="form_item">
<Text className="form_label"></Text>
<Textarea
className="form_textarea"
value={form_data.bio}
placeholder="介绍一下自己吧..."
maxlength={200}
onInput={(e) => handle_input_change('bio', e.detail.value)}
/>
<Text className="char_count">{form_data.bio.length}/200</Text>
</View>
{/* 所在地区 */}
<View className="form_item">
<Text className="form_label"></Text>
<Input
className="form_input"
value={form_data.location}
placeholder="请输入所在地区"
onInput={(e) => handle_input_change('location', e.detail.value)}
/>
</View>
{/* 职业 */}
<View className="form_item">
<Text className="form_label"></Text>
<Input
className="form_input"
value={form_data.occupation}
placeholder="请输入职业"
onInput={(e) => handle_input_change('occupation', e.detail.value)}
/>
</View>
{/* 手机号 */}
<View className="form_item">
<Text className="form_label"></Text>
<Input
className="form_input"
value={form_data.phone}
placeholder="请输入手机号"
type="number"
maxlength={11}
onInput={(e) => handle_input_change('phone', e.detail.value)}
/>
</View>
{/* NTRP等级 */}
<View className="form_item">
<Text className="form_label">NTRP等级</Text>
<View className="level_selector">
{['1.0', '1.5', '2.0', '2.5', '3.0', '3.5', '4.0', '4.5', '5.0'].map((level) => (
<View
key={level}
className={`level_item ${form_data.ntrp_level.includes(level) ? 'selected' : ''}`}
onClick={() => handle_input_change('ntrp_level', `NTRP ${level}`)}
>
<Text className="level_text">{level}</Text>
{/* 基本信息编辑 */}
<View className="form_section">
{/* 名字 */}
<View className="form_group">
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/user1.svg')} />
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Input
className="item_input"
value={form_data.nickname}
placeholder="188的王晨"
onInput={(e) => handle_input_change('nickname', e.detail.value)}
/>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
))}
</View>
</View>
<View className="divider"></View>
</View>
{/* 性别 */}
<View className="form_item">
<Text className="form_label"></Text>
<View className="gender_selector">
<View
className={`gender_item ${form_data.gender === '男' ? 'selected' : ''}`}
onClick={() => handle_input_change('gender', '男')}
>
<Text className="gender_text"></Text>
{/* 性别 */}
<View className="form_group">
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/user2.svg')} />
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.gender || '男'}</Text>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
<View className="divider"></View>
</View>
<View
className={`gender_item ${form_data.gender === '女' ? 'selected' : ''}`}
onClick={() => handle_input_change('gender', '女')}
>
<Text className="gender_text"></Text>
{/* 生日 */}
<View className="form_group">
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/tennis.svg')} />
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.birthday}</Text>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
</View>
</View>
{/* 简介编辑 */}
<View className="form_section">
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('bio')}>
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/message.svg')} />
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">
{form_data.bio || '介绍一下自己'}
</Text>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
</View>
</View>
{/* 地区、NTRP水平、职业 */}
<View className="form_section">
<View className="form_group">
{/* 地区 */}
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/location.svg')} />
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.location || '上海 黄浦'}</Text>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
<View className="divider"></View>
{/* NTRP水平 */}
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/tennis.svg')} />
<Text className="item_label">NTRP </Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.ntrp_level}</Text>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
<View className="divider"></View>
{/* 职业 */}
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/sc.svg')} />
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.occupation || '互联网'}</Text>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
</View>
</View>
{/* 手机号 */}
<View className="form_section">
<View className="form_group">
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('../../../static/userInfo/message.svg')} />
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.phone || '+86 130 1234 1234'}</Text>
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
</View>
</View>
<View className="divider"></View>
</View>
</View>
{/* 退出登录 */}
<View className="logout_section">
<View className="logout_button" onClick={handle_logout}>
<Text className="logout_text">退</Text>
</View>
</View>
</View>
</View>
</>
)}
</ScrollView>
{/* 编辑弹窗 */}
<EditModal
visible={edit_modal_visible}
title="编辑简介"
placeholder="介绍一下你的喜好,或者训练习惯"
initialValue={form_data[editing_field as keyof typeof form_data] || ''}
maxLength={100}
onSave={handle_edit_modal_save}
onCancel={handle_edit_modal_cancel}
validationMessage="请填写 2-100 个字符"
/>
</View>
);
};