完善个人页(测试NTRP水平未做)

This commit is contained in:
2025-09-14 11:46:29 +08:00
parent 01aad920ad
commit 6f0c0c30fa
8 changed files with 317 additions and 59 deletions

View File

@@ -210,7 +210,8 @@
gap: 8px; gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
.tag_item { .tag_item,
.button_edit {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
@@ -224,11 +225,7 @@
.tag_icon { .tag_icon {
width: 12px; width: 12px;
height: 12px; height: 12px;
/* Frame 1912054928 */ /* Frame 1912054928 */
} }
.tag_text { .tag_text {
@@ -240,15 +237,59 @@
color: #000000; color: #000000;
} }
} }
.button_edit {
font-family: 'PingFang SC';
font-weight: 500;
font-size: 11px;
line-height: 1.8em;
letter-spacing: -2.1%;
color: rgba(60, 60, 67, 0.6);
display: flex;
align-items: center;
position: relative;
padding-right: 20px;
&::before, &::after {
content: '';
width: 6px;
height: 1px;
display: inline-block;
background-color: rgba(60, 60, 67, 0.6);
position: absolute;
right: 12px;
transform: rotate(45deg);
margin-left: 4px;
}
&::after {
transform: rotate(-45deg);
translate: 4.2px 0;
}
}
} }
.bio_text { .personal_profile {
font-family: 'PingFang SC'; .personal_profile_edit {
font-weight: 400; display: flex;
font-size: 14px; align-items: center;
line-height: 1.571em; gap: 4px;
color: rgba(0, 0, 0, 0.65); cursor: pointer;
white-space: pre-line;
.edit_icon {
width: 16px;
height: 16px;
}
}
.bio_text {
font-family: 'PingFang SC';
font-weight: 400;
font-size: 14px;
line-height: 1.571em;
color: rgba(0, 0, 0, 0.65);
white-space: pre-line;
}
} }
} }
} }

View File

@@ -1,8 +1,11 @@
import React from 'react'; import React, { useState } from 'react';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import { View, Text, Image, Button } from '@tarojs/components'; import { View, Text, Image, Button } from '@tarojs/components';
import './index.scss'; import './index.scss';
import { EditModal } from '@/components';
import { UserService } from '@/services/userService';
// 用户信息接口 // 用户信息接口
export interface UserInfo { export interface UserInfo {
id: string; id: string;
@@ -21,9 +24,10 @@ export interface UserInfo {
ntrp_level: string; ntrp_level: string;
phone?: string; phone?: string;
gender?: string; gender?: string;
bio?: string,
latitude?: string, latitude?: string,
longitude?: string, longitude?: string,
birthday?: string,
} }
@@ -35,6 +39,7 @@ interface UserInfoCardProps {
on_follow?: () => void; on_follow?: () => void;
on_message?: () => void; on_message?: () => void;
on_share?: () => void; on_share?: () => void;
set_user_info?: (info: UserInfo) => void;
} }
@@ -51,8 +56,64 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
is_following = false, is_following = false,
on_follow, on_follow,
on_message, on_message,
on_share on_share,
set_user_info,
}) => { }) => {
console.log("用户信息xx:", user_info);
// 编辑个人简介弹窗状态
const [edit_modal_visible, setEditModalVisible] = useState(false);
const [editing_field, setEditingField] = useState<string>('');
// 表单状态
const [form_data, setFormData] = useState<UserInfo>({...user_info});
// 处理编辑弹窗
const handle_open_edit_modal = (field: string) => {
if (field === 'nickname') {
// 手动输入
setEditingField(field);
setEditModalVisible(true);
} else {
setEditingField(field);
setEditModalVisible(true);
}
};
const handle_edit_modal_save = async (value: string) => {
try {
// 调用更新用户信息接口,只传递修改的字段
const update_data = { [editing_field]: value };
await UserService.update_user_info(update_data);
// 更新本地状态
setFormData(prev => {
const updated = { ...prev, [editing_field]: value };
typeof set_user_info === 'function' && set_user_info(updated);
return updated;
});
// 关闭弹窗
setEditModalVisible(false);
setEditingField('');
// 显示成功提示
Taro.showToast({
title: '保存成功',
icon: 'success'
});
} catch (error) {
console.error('保存失败:', error);
Taro.showToast({
title: '保存失败',
icon: 'error'
});
}
};
const handle_edit_modal_cancel = () => {
setEditModalVisible(false);
setEditingField('');
};
return ( return (
<View className="user_info_card"> <View className="user_info_card">
{/* 头像和基本信息 */} {/* 头像和基本信息 */}
@@ -130,7 +191,6 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
<View className="tags_bio_section"> <View className="tags_bio_section">
<View className="tags_container"> <View className="tags_container">
<View className="tag_item"> <View className="tag_item">
{user_info.gender === "0" && ( {user_info.gender === "0" && (
<Image <Image
className="tag_icon" className="tag_icon"
@@ -143,24 +203,76 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
src={require('../../static/userInfo/female.svg')} src={require('../../static/userInfo/female.svg')}
/> />
)} )}
{
</View> !user_info.gender && (
<View className="tag_item"> <View className='button_edit'>
<Text className="tag_text">{user_info.ntrp_level || '未设置'}</Text> <Text></Text>
</View> </View>
<View className="tag_item"> )
<Text className="tag_text">{user_info.occupation || '未设置'}</Text> }
</View>
<View className="tag_item">
<Image
className="tag_icon"
src={require('../../static/userInfo/location.svg')}
/>
<Text className="tag_text">{user_info.location || '未设置'}</Text>
</View> </View>
{user_info.ntrp_level ?
<View className="tag_item">
{/* <Image
className="tag_icon"
src={require('../../static/userInfo/level.svg')}
/> */}
<Text className="tag_text">{user_info.ntrp_level}</Text>
</View> :
<View className="button_edit">
<Text>NTRP水平</Text>
</View>
}
{user_info.occupation ?
<View className="tag_item">
<Text className="tag_text">{user_info.occupation}</Text>
</View> :
<View className="button_edit">
<Text></Text>
</View>
}
{user_info.location ?
<View className="tag_item">
{/* <Image
className="tag_icon"
src={require('../../static/userInfo/location.svg')}
/> */}
<Text className="tag_text">{user_info.location}</Text>
</View> :
<View className="button_edit">
<Text></Text>
</View>
}
</View>
<View className="personal_profile">
{
user_info.personal_profile ? (
<Text className="bio_text">{user_info.personal_profile}</Text>
) : (
<View className='personal_profile_edit' onClick={() => handle_open_edit_modal('personal_profile')}>
<Image
className="edit_icon"
src={require('../../static/userInfo/info_edit.svg')}
/>
<Text className="bio_text"></Text>
</View>
)
}
</View> </View>
<Text className="bio_text">{user_info.personal_profile}</Text>
</View> </View>
{/* 编辑个人简介弹窗 */}
<EditModal
visible={edit_modal_visible}
type={editing_field}
title='编辑简介'
placeholder='介绍一下你的喜好,或者训练习惯'
initialValue={form_data['personal_profile'] || ''}
maxLength={100}
onSave={handle_edit_modal_save}
onCancel={handle_edit_modal_cancel}
validationMessage='请填写 2-100 个字符'
/>
</View> </View>
); );
}; };

View File

@@ -11,7 +11,8 @@ export const API_CONFIG = {
FOLLOW: '/user/follow', FOLLOW: '/user/follow',
UNFOLLOW: '/user/unfollow', UNFOLLOW: '/user/unfollow',
HOSTED_GAMES: '/user/games', HOSTED_GAMES: '/user/games',
PARTICIPATED_GAMES: '/user/participated' PARTICIPATED_GAMES: '/user/participated',
PARSE_PHONE: '/user/parse_phone',
}, },
// 文件上传接口 // 文件上传接口

View File

@@ -28,6 +28,9 @@ interface UserDetailData {
last_login_time: string; last_login_time: string;
create_time: string; create_time: string;
last_modify_time: string; last_modify_time: string;
personal_profile: string;
occupation: string;
birthday: string;
stats: { stats: {
followers_count: number; followers_count: number;
following_count: number; following_count: number;
@@ -226,12 +229,13 @@ export class UserService {
participated: userData.stats?.participated_games_count || 0 participated: userData.stats?.participated_games_count || 0
}, },
personal_profile: '', personal_profile: userData.personal_profile || '',
location: userData.city + userData.district|| '', location: userData.city + userData.district|| '',
occupation: '', occupation: userData.occupation || '',
ntrp_level: '', ntrp_level: '',
phone: userData.phone || '', phone: userData.phone || '',
gender: userData.gender || '' gender: userData.gender || '',
birthday: userData.birthday || '',
}; };
} else { } else {
throw new Error(response.message || '获取用户信息失败'); throw new Error(response.message || '获取用户信息失败');
@@ -449,6 +453,24 @@ export class UserService {
return require('../static/userInfo/default_avatar.svg'); return require('../static/userInfo/default_avatar.svg');
} }
} }
// 解析用户手机号
static async parse_phone(phone_code: string): Promise<string> {
try {
const response = await httpService.post<{ phone: string }>(API_CONFIG.USER.PARSE_PHONE, { phone_code }, {
showLoading: true,
loadingText: '获取手机号中...'
});
if (response.code === 0) {
return response.data.phone || '';
} else {
throw new Error(response.message || '获取手机号失败');
}
} catch (error) {
console.error('获取手机号失败:', error);
return '';
}
}
} }
// 从 loginService 移过来的用户相关方法 // 从 loginService 移过来的用户相关方法

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.33337 14H14.3334" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.66663 8.90663V11.3333H6.10569L13 4.43603L10.565 2L3.66663 8.90663Z" stroke="black" stroke-width="1.33333" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 364 B

View File

@@ -145,12 +145,13 @@
padding: 10px 12px; padding: 10px 12px;
min-height: 44px; min-height: 44px;
box-sizing: border-box; box-sizing: border-box;
gap: 20px;
.item_left { .item_left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
flex: 1; flex-shrink: 0;
.item_icon { .item_icon {
width: 16px; width: 16px;
@@ -172,6 +173,7 @@
gap: 8px; gap: 8px;
flex: 1; flex: 1;
justify-content: flex-end; justify-content: flex-end;
overflow: hidden;
.item_input { .item_input {
flex: 1; flex: 1;
@@ -184,6 +186,7 @@
border: none; border: none;
background: transparent; background: transparent;
outline: none; outline: none;
min-width: 0;
&::placeholder { &::placeholder {
color: rgba(0, 0, 0, 0.4); color: rgba(0, 0, 0, 0.4);
@@ -196,6 +199,11 @@
font-size: 14px; font-size: 14px;
line-height: 1.71em; line-height: 1.71em;
color: #000000; color: #000000;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
} }
.bio_textarea { .bio_textarea {
@@ -222,6 +230,26 @@
height: 14px; height: 14px;
opacity: 0.2; opacity: 0.2;
} }
Button {
padding: 0;
font-family: 'PingFang SC';
font-weight: 600;
font-size: 14px;
line-height: 1.71em;
color: #000000;
border: none;
flex: 1;
text-align: right;
background-color: unset;
&.placeholer {
font-weight: 400;
color: rgba(60, 60, 67, 0.3);
}
&:after {
border: none;
}
}
} }
.divider { .divider {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, Image, ScrollView, Picker, Input } from '@tarojs/components'; import { View, Text, Image, ScrollView, Picker, Input, Button } from '@tarojs/components';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import './index.scss'; import './index.scss';
import { UserInfo } from '@/components/UserInfo'; import { UserInfo } from '@/components/UserInfo';
@@ -67,7 +67,7 @@ const EditProfilePage: React.FC = () => {
ntrp_level: user_data.ntrp_level || 'NTRP 4.0', ntrp_level: user_data.ntrp_level || 'NTRP 4.0',
phone: user_data.phone || '', phone: user_data.phone || '',
gender: user_data.gender || '', gender: user_data.gender || '',
birthday: '2000-01-01' // 默认生日,实际应该从用户数据获取 birthday: user_data.birthday || '', // 默认生日,实际应该从用户数据获取
}); });
} catch (error) { } catch (error) {
console.error('加载用户信息失败:', error); console.error('加载用户信息失败:', error);
@@ -180,9 +180,7 @@ const EditProfilePage: React.FC = () => {
// 处理性别选择 // 处理性别选择
const handle_gender_change = (e: any) => { const handle_gender_change = (e: any) => {
const gender_value = e.detail.value; const gender_value = e.detail.value;
// 使用工具函数转换:页面选择器值(0/1) -> 数据库值('0'/'1') handle_field_edit('gender', gender_value);
const gender_db_value = gender_value === 0 ? '0' : '1';
handle_field_edit('gender', gender_db_value);
}; };
// 处理生日选择 // 处理生日选择
@@ -191,6 +189,17 @@ const EditProfilePage: React.FC = () => {
handle_field_edit('birthday', birthday_value); handle_field_edit('birthday', birthday_value);
}; };
// NTRP水平输入 - 实时更新本地状态
const handle_ntrp_level_input = (e: any) => {
const ntrp_level_value = e.detail.value;
setFormData(prev => ({ ...prev, ntrp_level: ntrp_level_value }));
}
// NTRP水平输入 - 失去焦点时保存到服务器
const handle_ntrp_level_blur = (e: any) => {
const ntrp_level_value = e.detail.value;
handle_field_edit('ntrp_level', ntrp_level_value);
}
// 处理职业输入 - 实时更新本地状态 // 处理职业输入 - 实时更新本地状态
const handle_occupation_input = (e: any) => { const handle_occupation_input = (e: any) => {
const occupation_value = e.detail.value; const occupation_value = e.detail.value;
@@ -215,17 +224,17 @@ const EditProfilePage: React.FC = () => {
handle_field_edit('location', location_value); handle_field_edit('location', location_value);
}; };
// 处理手机号输入 - 实时更新本地状态 // // 处理手机号输入 - 实时更新本地状态
const handle_phone_input = (e: any) => { // const handle_phone_input = (e: any) => {
const phone_value = e.detail.value; // const phone_value = e.detail.value;
setFormData(prev => ({ ...prev, phone: phone_value })); // setFormData(prev => ({ ...prev, phone: phone_value }));
}; // };
// 处理手机号输入 - 失去焦点时保存到服务器 // // 处理手机号输入 - 失去焦点时保存到服务器
const handle_phone_blur = (e: any) => { // const handle_phone_blur = (e: any) => {
const phone_value = e.detail.value; // const phone_value = e.detail.value;
handle_field_edit('phone', phone_value); // handle_field_edit('phone', phone_value);
}; // };
// 处理退出登录 // 处理退出登录
@@ -246,6 +255,29 @@ const EditProfilePage: React.FC = () => {
}); });
}; };
const onGetPhoneNumber = async (e) => {
if (!e.detail || !e.detail.code) {
Taro.showToast({
title: '获取手机号失败,请重试',
icon: 'none',
duration: 2000
});
return;
}
console.log('用户手机号信息aaa', e)
try {
const phone = await UserService.parse_phone(e.detail.code);
handle_field_edit('phone', phone);
} catch (e) {
console.error('解析手机号失败:', e);
Taro.showToast({
title: '解析手机号失败,请重试',
icon: 'none',
duration: 2000
});
}
}
return ( return (
<View className="edit_profile_page"> <View className="edit_profile_page">
{/* 导航栏 */} {/* 导航栏 */}
@@ -345,7 +377,7 @@ const EditProfilePage: React.FC = () => {
</View> </View>
<View className="item_right"> <View className="item_right">
<Text className="item_value"> <Text className="item_value">
{form_data.personal_profile || '介绍一下自己'} {form_data.personal_profile.replace(/\n/g, ' ') || '介绍一下自己'}
</Text> </Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} /> <Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
</View> </View>
@@ -370,6 +402,7 @@ const EditProfilePage: React.FC = () => {
onInput={handle_location_input} onInput={handle_location_input}
onBlur={handle_location_blur} onBlur={handle_location_blur}
/> />
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
</View> </View>
</View> </View>
<View className="divider"></View> <View className="divider"></View>
@@ -381,7 +414,14 @@ const EditProfilePage: React.FC = () => {
<Text className="item_label">NTRP </Text> <Text className="item_label">NTRP </Text>
</View> </View>
<View className="item_right"> <View className="item_right">
<Text className="item_value">{form_data.ntrp_level}</Text> {/* <Text className="item_value">{form_data.ntrp_level}</Text> */}
<Input
className="item_input"
value={form_data.ntrp_level}
placeholder="请输入NTRP水平"
onInput={handle_ntrp_level_input}
onBlur={handle_ntrp_level_blur}
/>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} /> <Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
</View> </View>
</View> </View>
@@ -401,6 +441,7 @@ const EditProfilePage: React.FC = () => {
onInput={handle_occupation_input} onInput={handle_occupation_input}
onBlur={handle_occupation_blur} onBlur={handle_occupation_blur}
/> />
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
</View> </View>
</View> </View>
</View> </View>
@@ -415,14 +456,16 @@ const EditProfilePage: React.FC = () => {
<Text className="item_label"></Text> <Text className="item_label"></Text>
</View> </View>
<View className="item_right"> <View className="item_right">
<Input {/* <Input
className="item_input" className="item_input"
value={form_data.phone} value={form_data.phone}
placeholder="请输入手机号" placeholder="请输入手机号"
type="number" type="number"
onInput={handle_phone_input} onInput={handle_phone_input}
onBlur={handle_phone_blur} onBlur={handle_phone_blur}
/> /> */}
<Button className={form_data.phone ? '' : 'placeholer'} openType='getPhoneNumber' onGetPhoneNumber={onGetPhoneNumber}>{form_data.phone || '未绑定'}</Button>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
</View> </View>
</View> </View>
<View className="divider"></View> <View className="divider"></View>

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { View, Text, Image, ScrollView } from "@tarojs/components"; import { View, Text, Image, ScrollView } from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro, { useDidShow } from "@tarojs/taro";
import "./index.scss"; import "./index.scss";
import GuideBar from "@/components/GuideBar"; import GuideBar from "@/components/GuideBar";
import { UserInfoCard, UserInfo } from "@/components/UserInfo/index"; import { UserInfoCard, UserInfo } from "@/components/UserInfo/index";
@@ -33,6 +33,7 @@ const MyselfPage: React.FC = () => {
location: "加载中...", location: "加载中...",
occupation: "加载中...", occupation: "加载中...",
ntrp_level: "NTRP 3.0", ntrp_level: "NTRP 3.0",
personal_profile: "加载中...",
}); });
// 球局记录状态 // 球局记录状态
@@ -77,9 +78,13 @@ const MyselfPage: React.FC = () => {
}; };
// 页面加载时获取数据 // 页面加载时获取数据
useEffect(() => { // useEffect(() => {
load_user_data(); // load_user_data();
}, [user_id]); // }, [user_id]);
useDidShow(() => {
load_user_data(); // 确保从编辑页面返回时刷新数据
});
// 切换标签页时重新加载球局数据 // 切换标签页时重新加载球局数据
useEffect(() => { useEffect(() => {
@@ -157,6 +162,7 @@ const MyselfPage: React.FC = () => {
is_current_user={is_current_user} is_current_user={is_current_user}
is_following={is_following} is_following={is_following}
on_follow={handle_follow} on_follow={handle_follow}
set_user_info={set_user_info}
/> />
)} )}
{/* 球局订单和收藏功能 */} {/* 球局订单和收藏功能 */}
@@ -208,6 +214,7 @@ const MyselfPage: React.FC = () => {
loading={loading} loading={loading}
error={null} error={null}
reload={load_game_data} reload={load_game_data}
loadMoreMatches={() => { }}
/> />
</ScrollView> </ScrollView>
</View> </View>