优化个人页

This commit is contained in:
2025-10-17 16:24:07 +08:00
parent 8f688378e1
commit f3ab0020d3
7 changed files with 549 additions and 429 deletions

View File

@@ -17,6 +17,8 @@ interface PickerOption {
} }
interface PickerProps { interface PickerProps {
minYear?: number;
maxYear?: number;
title?: string; title?: string;
showHeader?: boolean; showHeader?: boolean;
confirmText?: string; confirmText?: string;
@@ -28,10 +30,12 @@ interface PickerProps {
img?: string; img?: string;
onConfirm?: (options: PickerOption[], values: (string | number)[]) => void; onConfirm?: (options: PickerOption[], values: (string | number)[]) => void;
onChange?: (value: (string | number)[]) => void; onChange?: (value: (string | number)[]) => void;
style?: React.CSSProperties style?: React.CSSProperties;
} }
const PopupPicker = ({ const PopupPicker = ({
minYear,
maxYear,
confirmText, confirmText,
title, title,
showHeader, showHeader,
@@ -87,7 +91,7 @@ const PopupPicker = ({
if (type === "month") { if (type === "month") {
setDefaultOptions(renderYearMonth()); setDefaultOptions(renderYearMonth());
} else if (type === "day") { } else if (type === "day") {
setDefaultOptions(renderYearMonthDay()); setDefaultOptions(renderYearMonthDay(minYear, maxYear));
} else if (type === "hour") { } else if (type === "hour") {
setDefaultOptions(renderHourMinute()); setDefaultOptions(renderHourMinute());
} else { } else {

View File

@@ -6,45 +6,47 @@ import "./index.scss";
import { EditModal } from "@/components"; import { EditModal } from "@/components";
import { UserService, PickerOption } from "@/services/userService"; import { UserService, PickerOption } from "@/services/userService";
import { PopupPicker } from "@/components/Picker/index"; import { PopupPicker } from "@/components/Picker/index";
import { useUserActions } from "@/store/userStore";
import { UserInfoType } from "@/services/userService";
// 用户信息接口 // 用户信息接口
export interface UserInfo { // export interface UserInfo {
id: string | number; // id: string | number;
nickname: string; // nickname: string;
avatar: string; // avatar: string;
join_date: string; // join_date: string;
stats: { // stats: {
following: number; // following: number;
friends: number; // friends: number;
hosted: number; // hosted: number;
participated: number; // participated: number;
}; // };
personal_profile: string; // personal_profile: string;
occupation: string; // occupation: string;
ntrp_level: string; // ntrp_level: string;
phone?: string; // phone?: string;
gender: string; // gender: string;
bio?: string; // bio?: string;
latitude?: string; // latitude?: string;
longitude?: string; // longitude?: string;
birthday?: string; // birthday?: string;
is_following?: boolean; // is_following?: boolean;
tags?: string[]; // tags?: string[];
ongoing_games?: string[]; // ongoing_games?: string[];
country: string; // country: string;
province: string; // province: string;
city: string; // city: string;
} // }
// 用户信息卡片组件属性 // 用户信息卡片组件属性
interface UserInfoCardProps { interface UserInfoCardProps {
user_info: UserInfo; user_info: Partial<UserInfoType>;
is_current_user: boolean; is_current_user: boolean;
is_following?: boolean; is_following?: boolean;
on_follow?: () => void; on_follow?: () => void;
on_message?: () => void; on_message?: () => void;
on_share?: () => void; on_share?: () => void;
set_user_info?: (info: UserInfo) => void; set_user_info?: (info: Partial<UserInfoType>) => void;
} }
// 处理编辑用户信息 // 处理编辑用户信息
@@ -63,6 +65,7 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
on_share, on_share,
set_user_info, set_user_info,
}) => { }) => {
const { updateUserInfo } = useUserActions();
console.log("UserInfoCard 用户信息:", user_info); console.log("UserInfoCard 用户信息:", user_info);
// 编辑个人简介弹窗状态 // 编辑个人简介弹窗状态
const [edit_modal_visible, setEditModalVisible] = useState(false); const [edit_modal_visible, setEditModalVisible] = useState(false);
@@ -74,7 +77,9 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
useState(false); useState(false);
// 表单状态 // 表单状态
const [form_data, setFormData] = useState<UserInfo>({ ...user_info }); const [form_data, setFormData] = useState<Partial<UserInfoType>>({
...user_info,
});
// 职业数据 // 职业数据
const [professions, setProfessions] = useState<PickerOption[]>([]); const [professions, setProfessions] = useState<PickerOption[]>([]);
@@ -172,15 +177,14 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
field !== null && field !== null &&
!Array.isArray(field) !Array.isArray(field)
) { ) {
await UserService.update_user_info({ ...field }); await updateUserInfo({ ...field });
// 更新本地状态 // 更新本地状态
setFormData((prev) => ({ ...prev, ...field })); setFormData((prev) => ({ ...prev, ...field }));
// setUserInfo((prev) => ({ ...prev, ...field })); // setUserInfo((prev) => ({ ...prev, ...field }));
} else { } else {
// 调用更新用户信息接口,只传递修改的字段 // 调用更新用户信息接口,只传递修改的字段
const update_data = { [field as string]: value }; const update_data = { [field as string]: value };
await UserService.update_user_info(update_data); await updateUserInfo(update_data);
// 更新本地状态 // 更新本地状态
setFormData((prev) => ({ ...prev, [field as string]: value })); setFormData((prev) => ({ ...prev, [field as string]: value }));
// setUserInfo((prev) => ({ ...prev, [field as string]: value })); // setUserInfo((prev) => ({ ...prev, [field as string]: value }));
@@ -259,11 +263,15 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
{/* 头像和基本信息 */} {/* 头像和基本信息 */}
<View className="basic_info"> <View className="basic_info">
<View className="avatar_container"> <View className="avatar_container">
<Image className="avatar" src={user_info.avatar} mode="aspectFill" /> <Image
className="avatar"
src={user_info.avatar_url || ""}
mode="aspectFill"
/>
</View> </View>
<View className="info_container"> <View className="info_container">
<Text className="nickname">{user_info.nickname}</Text> <Text className="nickname">{user_info.nickname || ""}</Text>
<Text className="join_date">{user_info.join_date}</Text> <Text className="join_date">{user_info.join_date || ""}</Text>
</View> </View>
{is_current_user && ( {is_current_user && (
<View className="tag_item" onClick={on_edit}> <View className="tag_item" onClick={on_edit}>
@@ -282,22 +290,30 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
className="stat_item clickable" className="stat_item clickable"
onClick={() => handle_stats_click("following")} onClick={() => handle_stats_click("following")}
> >
<Text className="stat_number">{user_info.stats.following}</Text> <Text className="stat_number">
{user_info.stats?.following_count || 0}
</Text>
<Text className="stat_label"></Text> <Text className="stat_label"></Text>
</View> </View>
<View <View
className="stat_item clickable" className="stat_item clickable"
onClick={() => handle_stats_click("friends")} onClick={() => handle_stats_click("friends")}
> >
<Text className="stat_number">{user_info.stats.friends}</Text> <Text className="stat_number">
{user_info.stats?.followers_count || 0}
</Text>
<Text className="stat_label"></Text> <Text className="stat_label"></Text>
</View> </View>
<View className="stat_item"> <View className="stat_item">
<Text className="stat_number">{user_info.stats.hosted}</Text> <Text className="stat_number">
{user_info.stats?.hosted_games_count || 0}
</Text>
<Text className="stat_label"></Text> <Text className="stat_label"></Text>
</View> </View>
<View className="stat_item"> <View className="stat_item">
<Text className="stat_number">{user_info.stats.participated}</Text> <Text className="stat_number">
{user_info.stats?.participated_games_count || 0}
</Text>
<Text className="stat_label"></Text> <Text className="stat_label"></Text>
</View> </View>
</View> </View>
@@ -455,7 +471,7 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
]} ]}
visible={gender_picker_visible} visible={gender_picker_visible}
setvisible={setGenderPickerVisible} setvisible={setGenderPickerVisible}
value={[form_data.gender]} value={[form_data.gender || ""]}
onChange={handle_gender_change} onChange={handle_gender_change}
/> />
)} )}
@@ -467,7 +483,11 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
options={cities} options={cities}
visible={location_picker_visible} visible={location_picker_visible}
setvisible={setLocationPickerVisible} setvisible={setLocationPickerVisible}
value={[form_data.country, form_data.province, form_data.city]} value={[
form_data.country || "",
form_data.province || "",
form_data.city || "",
]}
onChange={handle_location_change} onChange={handle_location_change}
/> />
)} )}
@@ -488,10 +508,10 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
], ],
]} ]}
type="ntrp" type="ntrp"
img={user_info.avatar} img={user_info.avatar_url || ""}
visible={ntrp_picker_visible} visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible} setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level]} value={[form_data.ntrp_level || ""]}
onChange={handle_ntrp_level_change} onChange={handle_ntrp_level_change}
/> />
)} )}
@@ -503,7 +523,7 @@ export const UserInfoCard: React.FC<UserInfoCardProps> = ({
options={professions} options={professions}
visible={occupation_picker_visible} visible={occupation_picker_visible}
setvisible={setOccupationPickerVisible} setvisible={setOccupationPickerVisible}
value={[...form_data.occupation.split(" ")]} value={[...(form_data.occupation || "").split(" ")]}
onChange={handle_occupation_change} onChange={handle_occupation_change}
/> />
)} )}
@@ -644,7 +664,8 @@ export const GameTabs: React.FC<GameTabsProps> = ({
<Text className="tab_text">{hosted_text}</Text> <Text className="tab_text">{hosted_text}</Text>
</View> </View>
<View <View
className={`tab_item ${active_tab === "participated" ? "active" : "" className={`tab_item ${
active_tab === "participated" ? "active" : ""
}`} }`}
onClick={() => on_tab_change("participated")} onClick={() => on_tab_change("participated")}
> >

View File

@@ -1,12 +1,11 @@
import { UserInfo } from '@/components/UserInfo'; import { UserInfo } from "@/components/UserInfo";
import { API_CONFIG } from '@/config/api'; import { API_CONFIG } from "@/config/api";
import httpService, { ApiResponse } from './httpService'; import httpService, { ApiResponse } from "./httpService";
import uploadFiles from './uploadFiles'; import uploadFiles from "./uploadFiles";
import Taro from '@tarojs/taro'; import Taro from "@tarojs/taro";
import getCurrentConfig from '@/config/env'; import getCurrentConfig from "@/config/env";
import { clear_login_state } from "@/services/loginService"; import { clear_login_state } from "@/services/loginService";
// 用户详情接口 // 用户详情接口
interface UserDetailData { interface UserDetailData {
id: number; id: number;
@@ -33,7 +32,7 @@ interface UserDetailData {
personal_profile: string; personal_profile: string;
occupation: string; occupation: string;
birthday: string; birthday: string;
ntrp_level: string, ntrp_level: string;
stats: { stats: {
followers_count: number; followers_count: number;
following_count: number; following_count: number;
@@ -55,27 +54,42 @@ export interface Profession {
// 用户详细信息接口(从 loginService 移过来) // 用户详细信息接口(从 loginService 移过来)
export interface UserInfoType { export interface UserInfoType {
id: number id: number;
openid: string openid: string;
unionid: string unionid: string;
session_key: string session_key: string;
nickname: string nickname: string;
avatar_url: string avatar_url: string;
gender: string gender: string;
country: string country: string;
province: string province: string;
city: string city: string;
district: string district: string;
language: string language: string;
phone: string phone: string;
is_subscribed: string is_subscribed: string;
latitude: number latitude: number;
longitude: number longitude: number;
subscribe_time: string subscribe_time: string;
last_login_time: string last_login_time: string;
avatar: string;
join_date: string;
stats: {
following_count: number;
followers_count: number;
hosted_games_count: number;
participated_games_count: number;
};
personal_profile: string;
occupation: string;
ntrp_level: string;
bio?: string;
birthday?: string;
is_following?: boolean;
tags?: string[];
ongoing_games?: string[];
} }
// 后端球局数据接口 // 后端球局数据接口
interface BackendGameData { interface BackendGameData {
id: number; id: number;
@@ -138,37 +152,43 @@ const formatOptions = (data: Profession[]): PickerOption[] => {
const itm: PickerOption = { const itm: PickerOption = {
text, text,
value: text, value: text,
children: children ? formatOptions(children) : [] children: children ? formatOptions(children) : [],
} };
if (!itm.children!.length) { if (!itm.children!.length) {
delete itm.children delete itm.children;
}
return itm
})
} }
return itm;
});
};
// 用户服务类 // 用户服务类
export class UserService { export class UserService {
// 数据转换函数将后端数据转换为ListContainer期望的格式 // 数据转换函数将后端数据转换为ListContainer期望的格式
private static transform_game_data(backend_data: BackendGameData[]): any[] { private static transform_game_data(backend_data: BackendGameData[]): any[] {
return backend_data.map(game => { return backend_data.map((game) => {
// 处理时间格式 // 处理时间格式
const start_time = new Date(game.start_time.replace(/\s/, 'T')); const start_time = new Date(game.start_time.replace(/\s/, "T"));
const date_time = this.format_date_time(start_time); const date_time = this.format_date_time(start_time);
// 处理图片数组 - 兼容两种数据格式 // 处理图片数组 - 兼容两种数据格式
let images: string[] = []; let images: string[] = [];
if (game.image_list && game.image_list.length > 0) { if (game.image_list && game.image_list.length > 0) {
images = game.image_list.filter(img => img && img.trim() !== ''); images = game.image_list.filter((img) => img && img.trim() !== "");
} else if (game.venue_image_list && game.venue_image_list.length > 0) { } else if (game.venue_image_list && game.venue_image_list.length > 0) {
images = game.venue_image_list images = game.venue_image_list
.filter(img => img && img.url && img.url.trim() !== '') .filter((img) => img && img.url && img.url.trim() !== "")
.map(img => img.url); .map((img) => img.url);
} }
// 处理距离 - 优先使用venue_dtl中的坐标其次使用game中的坐标 // 处理距离 - 优先使用venue_dtl中的坐标其次使用game中的坐标
let latitude: number = typeof game.latitude === 'number' ? game.latitude : parseFloat(game.latitude || '0') || 0; let latitude: number =
let longitude: number = typeof game.longitude === 'number' ? game.longitude : parseFloat(game.longitude || '0') || 0; typeof game.latitude === "number"
? game.latitude
: parseFloat(game.latitude || "0") || 0;
let longitude: number =
typeof game.longitude === "number"
? game.longitude
: parseFloat(game.longitude || "0") || 0;
if (game.venue_dtl) { if (game.venue_dtl) {
latitude = parseFloat(game.venue_dtl.latitude) || latitude; latitude = parseFloat(game.venue_dtl.latitude) || latitude;
longitude = parseFloat(game.venue_dtl.longitude) || longitude; longitude = parseFloat(game.venue_dtl.longitude) || longitude;
@@ -176,33 +196,34 @@ export class UserService {
const distance = this.calculate_distance(latitude, longitude); const distance = this.calculate_distance(latitude, longitude);
// 处理地点信息 - 优先使用venue_dtl中的信息 // 处理地点信息 - 优先使用venue_dtl中的信息
let location = game.location_name || game.location || '未知地点'; let location = game.location_name || game.location || "未知地点";
if (game.venue_dtl && game.venue_dtl.name) { if (game.venue_dtl && game.venue_dtl.name) {
location = game.venue_dtl.name; location = game.venue_dtl.name;
} }
// 处理人数统计 - 兼容不同的字段名 // 处理人数统计 - 兼容不同的字段名
const registered_count = game.current_players || game.participant_count || 0; const registered_count =
game.current_players || game.participant_count || 0;
const max_count = game.max_players || game.max_participants || 0; const max_count = game.max_players || game.max_participants || 0;
// 转换为 ListCard 期望的格式 // 转换为 ListCard 期望的格式
return { return {
id: game.id, id: game.id,
title: game.title || '未命名球局', title: game.title || "未命名球局",
start_time: date_time, start_time: date_time,
original_start_time: game.start_time, original_start_time: game.start_time,
end_time: game.end_time || '', end_time: game.end_time || "",
location: location, location: location,
distance_km: parseFloat(distance.replace('km', '')) || 0, distance_km: parseFloat(distance.replace("km", "")) || 0,
current_players: registered_count, current_players: registered_count,
max_players: max_count, max_players: max_count,
skill_level_min: parseInt(game.skill_level_min) || 0, skill_level_min: parseInt(game.skill_level_min) || 0,
skill_level_max: parseInt(game.skill_level_max) || 0, skill_level_max: parseInt(game.skill_level_max) || 0,
play_type: game.play_type || '不限', play_type: game.play_type || "不限",
image_list: images, image_list: images,
court_type: game.court_type || '未知', court_type: game.court_type || "未知",
matchType: game.play_type || '不限', matchType: game.play_type || "不限",
shinei: game.court_type || '未知', shinei: game.court_type || "未知",
participants: game.participants || [], participants: game.participants || [],
}; };
}); });
@@ -210,8 +231,12 @@ export class UserService {
private static is_date_in_this_week(date: Date): boolean { private static is_date_in_this_week(date: Date): boolean {
const today = new Date(); const today = new Date();
const firstDayOfWeek = new Date(today.setDate(today.getDate() - today.getDay())); const firstDayOfWeek = new Date(
const lastDayOfWeek = new Date(firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 6)); today.setDate(today.getDate() - today.getDay())
);
const lastDayOfWeek = new Date(
firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 6)
);
return date >= firstDayOfWeek && date <= lastDayOfWeek; return date >= firstDayOfWeek && date <= lastDayOfWeek;
} }
@@ -219,17 +244,27 @@ export class UserService {
// 格式化时间显示 // 格式化时间显示
private static format_date_time(start_time: Date): string { private static format_date_time(start_time: Date): string {
const now = new Date(); const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate()); const today = new Date(
now.getFullYear(),
now.getMonth() + 1,
now.getDate()
);
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000); const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const day_after_tomorrow = new Date(today.getTime() + 2 * 24 * 60 * 60 * 1000); const day_after_tomorrow = new Date(
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; today.getTime() + 2 * 24 * 60 * 60 * 1000
);
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
const start_date = new Date(start_time.getFullYear(), start_time.getMonth() + 1, start_time.getDate()); const start_date = new Date(
start_time.getFullYear(),
start_time.getMonth() + 1,
start_time.getDate()
);
const weekday = weekdays[start_time.getDay()]; const weekday = weekdays[start_time.getDay()];
let date_str = ''; let date_str = "";
if (start_date.getTime() === today.getTime()) { if (start_date.getTime() === today.getTime()) {
date_str = '今天'; date_str = "今天";
} else if (start_date.getTime() === tomorrow.getTime()) { } else if (start_date.getTime() === tomorrow.getTime()) {
date_str = `明天(${weekday})`; date_str = `明天(${weekday})`;
} else if (start_date.getTime() === day_after_tomorrow.getTime()) { } else if (start_date.getTime() === day_after_tomorrow.getTime()) {
@@ -237,63 +272,84 @@ export class UserService {
} else if (this.is_date_in_this_week(start_time)) { } else if (this.is_date_in_this_week(start_time)) {
date_str = weekday; date_str = weekday;
} else { } else {
date_str = `${start_time.getFullYear()}-${(start_time.getMonth() + 1).toString().padStart(2, '0')}-${start_time.getDate().toString().padStart(2, '0')}(${weekday})`; date_str = `${start_time.getFullYear()}-${(start_time.getMonth() + 1)
.toString()
.padStart(2, "0")}-${start_time
.getDate()
.toString()
.padStart(2, "0")}(${weekday})`;
} }
const time_str = `${start_time.getHours().toString().padStart(2, '0')}:${start_time.getMinutes().toString().padStart(2, '0')}`; const time_str = `${start_time
.getHours()
.toString()
.padStart(2, "0")}:${start_time
.getMinutes()
.toString()
.padStart(2, "0")}`;
return `${date_str} ${time_str}`; return `${date_str} ${time_str}`;
} }
// 计算距离(模拟实现,实际需要根据用户位置计算) // 计算距离(模拟实现,实际需要根据用户位置计算)
private static calculate_distance(latitude: number, longitude: number): string { private static calculate_distance(
latitude: number,
longitude: number
): string {
if (latitude === 0 && longitude === 0) { if (latitude === 0 && longitude === 0) {
return '未知距离'; return "未知距离";
} }
// 这里应该根据用户当前位置计算实际距离 // 这里应该根据用户当前位置计算实际距离
// 暂时返回模拟距离 // 暂时返回模拟距离
const distances = ['1.2km', '2.5km', '3.8km', '5.1km', '7.3km']; const distances = ["1.2km", "2.5km", "3.8km", "5.1km", "7.3km"];
return distances[Math.floor(Math.random() * distances.length)]; return distances[Math.floor(Math.random() * distances.length)];
} }
// 获取用户信息 // 获取用户信息
static async get_user_info(user_id?: string): Promise<UserInfo> { static async get_user_info(user_id?: string): Promise<UserInfo> {
try { try {
const response = await httpService.post<UserDetailData>(API_CONFIG.USER.DETAIL, user_id ? { user_id } : {}, { const response = await httpService.post<UserDetailData>(
API_CONFIG.USER.DETAIL,
showLoading: false user_id ? { user_id } : {},
}); {
showLoading: false,
}
);
if (response.code === 0) { if (response.code === 0) {
const userData = response.data; const userData = response.data;
return { return {
id: userData.id || '', id: userData.id || "",
nickname: userData.nickname || '', nickname: userData.nickname || "",
avatar: userData.avatar_url || '', avatar: userData.avatar_url || "",
join_date: userData.subscribe_time ? `${new Date(userData.subscribe_time).getFullYear()}${new Date(userData.subscribe_time).getMonth() + 1}月加入` : '', join_date: userData.subscribe_time
? `${new Date(userData.subscribe_time).getFullYear()}${
new Date(userData.subscribe_time).getMonth() + 1
}月加入`
: "",
stats: { stats: {
following: userData.stats?.following_count || 0, following: userData.stats?.following_count || 0,
friends: userData.stats?.followers_count || 0, friends: userData.stats?.followers_count || 0,
hosted: userData.stats?.hosted_games_count || 0, hosted: userData.stats?.hosted_games_count || 0,
participated: userData.stats?.participated_games_count || 0 participated: userData.stats?.participated_games_count || 0,
}, },
personal_profile: userData.personal_profile || '', personal_profile: userData.personal_profile || "",
occupation: userData.occupation || '', occupation: userData.occupation || "",
ntrp_level: userData.ntrp_level || '', ntrp_level: userData.ntrp_level || "",
phone: userData.phone || '', phone: userData.phone || "",
gender: userData.gender || '', gender: userData.gender || "",
birthday: userData.birthday || '', birthday: userData.birthday || "",
country: userData.country || '', country: userData.country || "",
province: userData.province || '', province: userData.province || "",
city: userData.city || '', city: userData.city || "",
}; };
} else { } else {
throw new Error(response.message || '获取用户信息失败'); throw new Error(response.message || "获取用户信息失败");
} }
} catch (error) { } catch (error) {
console.error('获取用户信息失败:', error); console.error("获取用户信息失败:", error);
// 返回默认用户信息 // 返回默认用户信息
return {} as UserInfo return {} as UserInfo;
} }
} }
@@ -303,32 +359,36 @@ export class UserService {
// 过滤掉空字段 // 过滤掉空字段
const filtered_data: Record<string, any> = {}; const filtered_data: Record<string, any> = {};
Object.keys(update_data).forEach(key => { Object.keys(update_data).forEach((key) => {
const value = update_data[key as keyof UserInfo]; const value = update_data[key as keyof UserInfo];
// 只添加非空且非空字符串的字段 // 只添加非空且非空字符串的字段
if (value !== null && value !== undefined && value !== '') { if (value !== null && value !== undefined && value !== "") {
if (typeof value === 'string' && value.trim() !== '') { if (typeof value === "string" && value.trim() !== "") {
filtered_data[key] = value.trim(); filtered_data[key] = value.trim();
} else if (typeof value !== 'string') { } else if (typeof value !== "string") {
filtered_data[key] = value; filtered_data[key] = value;
} }
} }
}); });
// 如果没有需要更新的字段,直接返回 // 如果没有需要更新的字段,直接返回
if (Object.keys(filtered_data).length === 0) { if (Object.keys(filtered_data).length === 0) {
console.log('没有需要更新的字段'); console.log("没有需要更新的字段");
return; return;
} }
const response = await httpService.post(API_CONFIG.USER.UPDATE, filtered_data, { const response = await httpService.post(
showLoading: true API_CONFIG.USER.UPDATE,
}); filtered_data,
{
showLoading: true,
}
);
if (response.code !== 0) { if (response.code !== 0) {
throw new Error(response.message || '更新用户信息失败'); throw new Error(response.message || "更新用户信息失败");
} }
} catch (error) { } catch (error) {
console.error('更新用户信息失败:', error); console.error("更新用户信息失败:", error);
throw error; throw error;
} }
} }
@@ -336,53 +396,61 @@ export class UserService {
// 获取用户主办的球局 // 获取用户主办的球局
static async get_hosted_games(userId: string | number): Promise<any[]> { static async get_hosted_games(userId: string | number): Promise<any[]> {
try { try {
const response = await httpService.post<any>(API_CONFIG.USER.HOSTED_GAMES, { const response = await httpService.post<any>(
userId API_CONFIG.USER.HOSTED_GAMES,
}, { {
userId,
showLoading: false },
}); {
showLoading: false,
}
);
if (response.code === 0) { if (response.code === 0) {
// 使用数据转换函数将后端数据转换为ListContainer期望的格式 // 使用数据转换函数将后端数据转换为ListContainer期望的格式
return this.transform_game_data(response.data.rows || []); return this.transform_game_data(response.data.rows || []);
} else { } else {
throw new Error(response.message || '获取主办球局失败'); throw new Error(response.message || "获取主办球局失败");
} }
} catch (error) { } catch (error) {
console.error('获取主办球局失败:', error); console.error("获取主办球局失败:", error);
// 返回符合ListContainer data格式的模拟数据 // 返回符合ListContainer data格式的模拟数据
return [] return [];
} }
} }
// 获取用户参与的球局 // 获取用户参与的球局
static async get_participated_games(userId: string | number): Promise<any[]> { static async get_participated_games(userId: string | number): Promise<any[]> {
try { try {
const response = await httpService.post<any>(API_CONFIG.USER.PARTICIPATED_GAMES, { const response = await httpService.post<any>(
userId API_CONFIG.USER.PARTICIPATED_GAMES,
}, { {
userId,
showLoading: false },
}); {
showLoading: false,
}
);
if (response.code === 0) { if (response.code === 0) {
// 使用数据转换函数将后端数据转换为ListContainer期望的格式 // 使用数据转换函数将后端数据转换为ListContainer期望的格式
return this.transform_game_data(response.data.rows || []); return this.transform_game_data(response.data.rows || []);
} else { } else {
throw new Error(response.message || '获取参与球局失败'); throw new Error(response.message || "获取参与球局失败");
} }
} catch (error) { } catch (error) {
console.error('获取参与球局失败:', error); console.error("获取参与球局失败:", error);
// 返回符合ListContainer data格式的模拟数据 // 返回符合ListContainer data格式的模拟数据
return []; return [];
} }
} }
// 获取用户球局记录(兼容旧方法) // 获取用户球局记录(兼容旧方法)
static async get_user_games(user_id: string | number, type: 'hosted' | 'participated'): Promise<any[]> { static async get_user_games(
if (type === 'hosted') { user_id: string | number,
type: "hosted" | "participated"
): Promise<any[]> {
if (type === "hosted") {
return this.get_hosted_games(user_id); return this.get_hosted_games(user_id);
} else { } else {
return this.get_participated_games(user_id); return this.get_participated_games(user_id);
@@ -390,98 +458,118 @@ export class UserService {
} }
// 关注/取消关注用户 // 关注/取消关注用户
static async toggle_follow(following_id: string | number, is_following: boolean): Promise<boolean> { static async toggle_follow(
following_id: string | number,
is_following: boolean
): Promise<boolean> {
try { try {
const endpoint = is_following ? API_CONFIG.USER.UNFOLLOW : API_CONFIG.USER.FOLLOW; const endpoint = is_following
const response = await httpService.post<any>(endpoint, { following_id }, { ? API_CONFIG.USER.UNFOLLOW
: API_CONFIG.USER.FOLLOW;
const response = await httpService.post<any>(
endpoint,
{ following_id },
{
showLoading: true, showLoading: true,
loadingText: is_following ? '取消关注中...' : '关注中...' loadingText: is_following ? "取消关注中..." : "关注中...",
}); }
);
if (response.code === 0) { if (response.code === 0) {
return !is_following; return !is_following;
} else { } else {
throw new Error(response.message || '操作失败'); throw new Error(response.message || "操作失败");
} }
} catch (error) { } catch (error) {
console.error('关注操作失败:', error); console.error("关注操作失败:", error);
throw error; throw error;
} }
} }
// 保存用户信息 // 保存用户信息
static async save_user_info(user_info: Partial<UserInfo> & { phone?: string; gender?: string }): Promise<boolean> { static async save_user_info(
user_info: Partial<UserInfo> & { phone?: string; gender?: string }
): Promise<boolean> {
try { try {
// 字段映射配置 // 字段映射配置
const field_mapping: Record<string, string> = { const field_mapping: Record<string, string> = {
nickname: 'nickname', nickname: "nickname",
avatar: 'avatar_url', avatar: "avatar_url",
gender: 'gender', gender: "gender",
phone: 'phone', phone: "phone",
latitude: 'latitude', latitude: "latitude",
longitude: 'longitude', longitude: "longitude",
province: 'province', province: "province",
country: "country", country: "country",
city: "city", city: "city",
personal_profile: 'personal_profile', personal_profile: "personal_profile",
occupation: 'occupation', occupation: "occupation",
ntrp_level: 'ntrp_level' ntrp_level: "ntrp_level",
}; };
// 构建更新参数,只包含非空字段 // 构建更新参数,只包含非空字段
const updateParams: Record<string, string> = {}; const updateParams: Record<string, string> = {};
// 循环处理所有字段 // 循环处理所有字段
Object.keys(field_mapping).forEach(key => { Object.keys(field_mapping).forEach((key) => {
const value = user_info[key as keyof typeof user_info]; const value = user_info[key as keyof typeof user_info];
if (value && typeof value === 'string' && value.trim() !== '') { if (value && typeof value === "string" && value.trim() !== "") {
updateParams[field_mapping[key]] = value.trim(); updateParams[field_mapping[key]] = value.trim();
} }
}); });
// 如果没有需要更新的字段,直接返回成功 // 如果没有需要更新的字段,直接返回成功
if (Object.keys(updateParams).length === 0) { if (Object.keys(updateParams).length === 0) {
console.log('没有需要更新的字段'); console.log("没有需要更新的字段");
return true; return true;
} }
const response = await httpService.post<any>(API_CONFIG.USER.UPDATE, updateParams, { const response = await httpService.post<any>(
API_CONFIG.USER.UPDATE,
updateParams,
{
showLoading: true, showLoading: true,
loadingText: '保存中...' loadingText: "保存中...",
}); }
);
if (response.code === 0) { if (response.code === 0) {
return true; return true;
} else { } else {
throw new Error(response.message || '更新用户信息失败'); throw new Error(response.message || "更新用户信息失败");
} }
} catch (error) { } catch (error) {
console.error('保存用户信息失败:', error); console.error("保存用户信息失败:", error);
throw error; throw error;
} }
} }
// 获取用户动态 // 获取用户动态
static async get_user_activities(user_id: string, page: number = 1, limit: number = 10): Promise<any[]> { static async get_user_activities(
user_id: string,
page: number = 1,
limit: number = 10
): Promise<any[]> {
try { try {
const response = await httpService.post<any>('/user/activities', { const response = await httpService.post<any>(
"/user/activities",
{
user_id, user_id,
page, page,
limit limit,
}, { },
{
showLoading: false showLoading: false,
}); }
);
if (response.code === 0) { if (response.code === 0) {
return response.data.activities || []; return response.data.activities || [];
} else { } else {
throw new Error(response.message || '获取用户动态失败'); throw new Error(response.message || "获取用户动态失败");
} }
} catch (error) { } catch (error) {
console.error('获取用户动态失败:', error); console.error("获取用户动态失败:", error);
return []; return [];
} }
} }
@@ -490,34 +578,38 @@ export class UserService {
static async upload_avatar(file_path: string): Promise<string> { static async upload_avatar(file_path: string): Promise<string> {
try { try {
// 先上传文件到服务器 // 先上传文件到服务器
const result = await uploadFiles.upload_oss_img(file_path) const result = await uploadFiles.upload_oss_img(file_path);
await this.save_user_info({ avatar: result.ossPath }) await this.save_user_info({ avatar: result.ossPath });
// 使用新的响应格式中的file_url字段 // 使用新的响应格式中的file_url字段
return result.ossPath; return result.ossPath;
} catch (error) { } catch (error) {
console.error('头像上传失败:', error); console.error("头像上传失败:", error);
// 如果上传失败,返回默认头像 // 如果上传失败,返回默认头像
return require('../static/userInfo/default_avatar.svg'); return require("../static/userInfo/default_avatar.svg");
} }
} }
// 解析用户手机号 // 解析用户手机号
static async parse_phone(phone_code: string): Promise<string> { static async parse_phone(phone_code: string): Promise<string> {
try { try {
const response = await httpService.post<{ phone: string }>(API_CONFIG.USER.PARSE_PHONE, { phone_code }, { const response = await httpService.post<{ phone: string }>(
API_CONFIG.USER.PARSE_PHONE,
{ phone_code },
{
showLoading: true, showLoading: true,
loadingText: '获取手机号中...' loadingText: "获取手机号中...",
}); }
);
if (response.code === 0) { if (response.code === 0) {
return response.data.phone || ''; return response.data.phone || "";
} else { } else {
throw new Error(response.message || '获取手机号失败'); throw new Error(response.message || "获取手机号失败");
} }
} catch (error) { } catch (error) {
console.error('获取手机号失败:', error); console.error("获取手机号失败:", error);
return ''; return "";
} }
} }
@@ -529,10 +621,10 @@ export class UserService {
if (code === 0) { if (code === 0) {
return formatOptions(data || []); return formatOptions(data || []);
} else { } else {
throw new Error(message || '获取职业树失败'); throw new Error(message || "获取职业树失败");
} }
} catch (error) { } catch (error) {
console.error('获取职业树失败:', error); console.error("获取职业树失败:", error);
return []; return [];
} }
} }
@@ -545,10 +637,10 @@ export class UserService {
if (code === 0) { if (code === 0) {
return formatOptions(data || []); return formatOptions(data || []);
} else { } else {
throw new Error(message || '获取城市树失败'); throw new Error(message || "获取城市树失败");
} }
} catch (error) { } catch (error) {
console.error('获取职业树失败:', error); console.error("获取职业树失败:", error);
return []; return [];
} }
} }
@@ -566,10 +658,10 @@ export class UserService {
url: "/login_pages/index/index", url: "/login_pages/index/index",
}); });
} else { } else {
throw new Error(message || '注销账户失败'); throw new Error(message || "注销账户失败");
} }
} catch (error) { } catch (error) {
console.error('注销账户失败:', error); console.error("注销账户失败:", error);
} }
} }
} }
@@ -577,12 +669,14 @@ export class UserService {
// 从 loginService 移过来的用户相关方法 // 从 loginService 移过来的用户相关方法
// 获取用户详细信息 // 获取用户详细信息
export const fetchUserProfile = async (): Promise<ApiResponse<UserInfoType>> => { export const fetchUserProfile = async (): Promise<
ApiResponse<UserInfoType>
> => {
try { try {
const response = await httpService.post('user/detail'); const response = await httpService.post("user/detail");
return response; return response;
} catch (error) { } catch (error) {
console.error('获取用户信息失败:', error); console.error("获取用户信息失败:", error);
throw error; throw error;
} }
}; };
@@ -590,24 +684,27 @@ export const fetchUserProfile = async (): Promise<ApiResponse<UserInfoType>> =>
// 更新用户信息 // 更新用户信息
export const updateUserProfile = async (payload: Partial<UserInfoType>) => { export const updateUserProfile = async (payload: Partial<UserInfoType>) => {
try { try {
const response = await httpService.post('/user/update', payload); const response = await httpService.post("/user/update", payload);
return response; return response;
} catch (error) { } catch (error) {
console.error('更新用户信息失败:', error); console.error("更新用户信息失败:", error);
throw error; throw error;
} }
}; };
// 更新用户坐标位置 // 更新用户坐标位置
export const updateUserLocation = async (latitude: number, longitude: number) => { export const updateUserLocation = async (
latitude: number,
longitude: number
) => {
try { try {
const response = await httpService.post('/user/update_location', { const response = await httpService.post("/user/update_location", {
latitude, latitude,
longitude, longitude,
}); });
return response; return response;
} catch (error) { } catch (error) {
console.error('更新用户坐标位置失败:', error); console.error("更新用户坐标位置失败:", error);
throw error; throw error;
} }
}; };
@@ -615,9 +712,9 @@ export const updateUserLocation = async (latitude: number, longitude: number) =>
// 获取用户信息(从本地存储) // 获取用户信息(从本地存储)
export const get_user_info = (): any | null => { export const get_user_info = (): any | null => {
try { try {
let userinfo = Taro.getStorageSync('user_info') let userinfo = Taro.getStorageSync("user_info");
if (userinfo) { if (userinfo) {
return JSON.parse(userinfo) return JSON.parse(userinfo);
} }
return null; return null;
} catch (error) { } catch (error) {
@@ -632,25 +729,25 @@ export const handleCustomerService = async (): Promise<void> => {
const config = getCurrentConfig; const config = getCurrentConfig;
const { customerService } = config; const { customerService } = config;
console.log('打开客服中心,配置信息:', customerService); console.log("打开客服中心,配置信息:", customerService);
// 使用微信官方客服能力 // 使用微信官方客服能力
await Taro.openCustomerServiceChat({ await Taro.openCustomerServiceChat({
extInfo: { extInfo: {
url: customerService.serviceUrl url: customerService.serviceUrl,
}, },
corpId: customerService.corpId, corpId: customerService.corpId,
success: (res) => { success: (res) => {
console.log('打开客服成功:', res); console.log("打开客服成功:", res);
}, },
fail: (error) => { fail: (error) => {
console.error('打开客服失败:', error); console.error("打开客服失败:", error);
// 如果官方客服不可用,显示备用联系方式 // 如果官方客服不可用,显示备用联系方式
showCustomerServiceFallback(customerService); showCustomerServiceFallback(customerService);
} },
}); });
} catch (error) { } catch (error) {
console.error('客服功能异常:', error); console.error("客服功能异常:", error);
// 备用方案:显示联系信息 // 备用方案:显示联系信息
showCustomerServiceFallback(); showCustomerServiceFallback();
} }
@@ -658,14 +755,14 @@ export const handleCustomerService = async (): Promise<void> => {
// 客服备用方案 // 客服备用方案
const showCustomerServiceFallback = (customerInfo?: any) => { const showCustomerServiceFallback = (customerInfo?: any) => {
const options = ['拨打客服电话', '复制邮箱地址']; const options = ["拨打客服电话", "复制邮箱地址"];
// 如果没有客服信息,只显示通用提示 // 如果没有客服信息,只显示通用提示
if (!customerInfo?.phoneNumber && !customerInfo?.email) { if (!customerInfo?.phoneNumber && !customerInfo?.email) {
Taro.showModal({ Taro.showModal({
title: '联系客服', title: "联系客服",
content: '如需帮助,请通过其他方式联系我们', content: "如需帮助,请通过其他方式联系我们",
showCancel: false showCancel: false,
}); });
return; return;
} }
@@ -677,33 +774,33 @@ const showCustomerServiceFallback = (customerInfo?: any) => {
// 拨打客服电话 // 拨打客服电话
try { try {
await Taro.makePhoneCall({ await Taro.makePhoneCall({
phoneNumber: customerInfo.phoneNumber phoneNumber: customerInfo.phoneNumber,
}); });
} catch (error) { } catch (error) {
console.error('拨打电话失败:', error); console.error("拨打电话失败:", error);
Taro.showToast({ Taro.showToast({
title: '拨打电话失败', title: "拨打电话失败",
icon: 'none' icon: "none",
}); });
} }
} else if (res.tapIndex === 1 && customerInfo?.email) { } else if (res.tapIndex === 1 && customerInfo?.email) {
// 复制邮箱地址 // 复制邮箱地址
try { try {
await Taro.setClipboardData({ await Taro.setClipboardData({
data: customerInfo.email data: customerInfo.email,
}); });
Taro.showToast({ Taro.showToast({
title: '邮箱地址已复制', title: "邮箱地址已复制",
icon: 'success' icon: "success",
}); });
} catch (error) { } catch (error) {
console.error('复制邮箱失败:', error); console.error("复制邮箱失败:", error);
Taro.showToast({ Taro.showToast({
title: '复制失败', title: "复制失败",
icon: 'none' icon: "none",
}); });
} }
} }
} },
}); });
}; };

View File

@@ -11,18 +11,31 @@ export interface UserState {
updateUserInfo: (userInfo: Partial<UserInfoType>) => void; updateUserInfo: (userInfo: Partial<UserInfoType>) => void;
} }
export const useUser = create<UserState>()((set) => ({ const fetchUserInfo = async (set) => {
user: {},
fetchUserInfo: async () => {
try { try {
const res = await fetchUserProfile(); const res = await fetchUserProfile();
set({ user: res.data }); set({ user: res.data });
return res.data return res.data;
} catch {} } catch {}
}, };
export const useUser = create<UserState>()((set) => ({
user: {},
fetchUserInfo: fetchUserInfo.bind(null, set),
updateUserInfo: async (userInfo: Partial<UserInfoType>) => { updateUserInfo: async (userInfo: Partial<UserInfoType>) => {
const res = await updateUserProfile(userInfo); try {
set({ user: res.data }); // 先更新后端
await updateUserProfile(userInfo);
// 然后立即更新本地状态
set((state) => ({
user: { ...state.user, ...userInfo },
}));
// 最后重新获取完整用户信息确保数据一致性
await fetchUserInfo(set);
} catch (error) {
console.error("更新用户信息失败:", error);
throw error;
}
}, },
})); }));

View File

@@ -3,53 +3,36 @@ import { View, Text, Image, ScrollView, Button } from "@tarojs/components";
import { PopupPicker } from "@/components/Picker/index"; import { PopupPicker } from "@/components/Picker/index";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import "./index.scss"; import "./index.scss";
import { UserInfo } from "@/components/UserInfo";
import { UserService, PickerOption } from "@/services/userService"; import { UserService, PickerOption } from "@/services/userService";
import { clear_login_state } from "@/services/loginService"; import { clear_login_state } from "@/services/loginService";
import { convert_db_gender_to_display } from "@/utils/genderUtils"; import { convert_db_gender_to_display } from "@/utils/genderUtils";
import { EditModal } from "@/components"; import { EditModal } from "@/components";
import img from "@/config/images"; import img from "@/config/images";
import CommonDialog from "@/components/CommonDialog"; import CommonDialog from "@/components/CommonDialog";
import { useUserActions, useUserInfo } from "@/store/userStore";
import { UserInfoType } from "@/services/userService";
const EditProfilePage: React.FC = () => { const EditProfilePage: React.FC = () => {
// 用户信息状态 const { updateUserInfo } = useUserActions();
const [user_info, setUserInfo] = useState<UserInfo>({ // 直接从store获取用户信息确保响应式更新
id: "1", const user_info = useUserInfo();
nickname: "加载中...",
avatar: require("@/static/userInfo/default_avatar.svg"),
join_date: "加载中...",
stats: {
following: 0,
friends: 0,
hosted: 0,
participated: 0,
},
personal_profile: "加载中...",
occupation: "加载中...",
ntrp_level: "NTRP 3.0",
phone: "",
gender: "",
country: "",
province: "",
city: "",
});
// 表单状态 // 表单状态基于store中的用户信息初始化
const [form_data, setFormData] = useState({ const [form_data, setFormData] = useState({
nickname: "", nickname: (user_info as UserInfoType)?.nickname || "",
personal_profile: "", personal_profile: (user_info as UserInfoType)?.personal_profile || "",
occupation: "", occupation: (user_info as UserInfoType)?.occupation || "",
ntrp_level: "4.0", ntrp_level: (user_info as UserInfoType)?.ntrp_level || "4.0",
phone: "", phone: (user_info as UserInfoType)?.phone || "",
gender: "", gender: (user_info as UserInfoType)?.gender || "",
birthday: "2000-01-01", birthday: (user_info as UserInfoType)?.birthday || "2000-01-01",
country: "", country: (user_info as UserInfoType)?.country || "",
province: "", province: (user_info as UserInfoType)?.province || "",
city: "", city: (user_info as UserInfoType)?.city || "",
}); });
// 加载状态 // 加载状态
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(false);
const [showLogoutDialog, setShowLogoutDialog] = useState(false); const [showLogoutDialog, setShowLogoutDialog] = useState(false);
// 编辑弹窗状态 // 编辑弹窗状态
@@ -68,11 +51,28 @@ const EditProfilePage: React.FC = () => {
// 城市数据 // 城市数据
const [cities, setCities] = useState<PickerOption[]>([]); const [cities, setCities] = useState<PickerOption[]>([]);
// 监听store中的用户信息变化同步到表单状态
useEffect(() => {
if (user_info && Object.keys(user_info).length > 0) {
setFormData({
nickname: (user_info as UserInfoType)?.nickname || "",
personal_profile: (user_info as UserInfoType)?.personal_profile || "",
occupation: (user_info as UserInfoType)?.occupation || "",
ntrp_level: (user_info as UserInfoType)?.ntrp_level || "4.0",
phone: (user_info as UserInfoType)?.phone || "",
gender: (user_info as UserInfoType)?.gender || "",
birthday: (user_info as UserInfoType)?.birthday || "2000-01-01",
country: (user_info as UserInfoType)?.country || "",
province: (user_info as UserInfoType)?.province || "",
city: (user_info as UserInfoType)?.city || "",
});
}
}, [user_info]);
// 页面加载时初始化数据 // 页面加载时初始化数据
useEffect(() => { useEffect(() => {
load_user_info();
getProfessions();
getCities(); getCities();
getProfessions();
}, []); }, []);
const getProfessions = async () => { const getProfessions = async () => {
@@ -93,34 +93,34 @@ const EditProfilePage: React.FC = () => {
}; };
// 加载用户信息 // 加载用户信息
const load_user_info = async () => { // const load_user_info = async () => {
try { // try {
setLoading(true); // setLoading(true);
const user_data = await UserService.get_user_info(); // const user_data = await UserService.get_user_info();
setUserInfo(user_data); // setUserInfo(user_data);
setFormData({ // setFormData({
nickname: user_data.nickname || "", // nickname: user_data.nickname || "",
personal_profile: user_data.personal_profile || "", // personal_profile: user_data.personal_profile || "",
occupation: user_data.occupation || "", // occupation: user_data.occupation || "",
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: user_data.birthday || "", // birthday: user_data.birthday || "",
country: user_data.country || "", // country: user_data.country || "",
province: user_data.province || "", // province: user_data.province || "",
city: user_data.city || "", // city: user_data.city || "",
}); // });
} catch (error) { // } catch (error) {
console.error("加载用户信息失败:", error); // console.error("加载用户信息失败:", error);
Taro.showToast({ // Taro.showToast({
title: "加载用户信息失败", // title: "加载用户信息失败",
icon: "error", // icon: "error",
duration: 2000, // duration: 2000,
}); // });
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
// 处理头像上传 // 处理头像上传
const handle_avatar_upload = () => { const handle_avatar_upload = () => {
@@ -184,11 +184,10 @@ const EditProfilePage: React.FC = () => {
try { try {
// 调用更新用户信息接口,只传递修改的字段 // 调用更新用户信息接口,只传递修改的字段
const update_data = { [editing_field]: value }; const update_data = { [editing_field]: value };
await UserService.update_user_info(update_data); await updateUserInfo(update_data);
// 更新本地状态 // 更新表单状态store会自动更新
setFormData((prev) => ({ ...prev, [editing_field]: value })); setFormData((prev) => ({ ...prev, [editing_field]: value }));
setUserInfo((prev) => ({ ...prev, [editing_field]: value }));
// 关闭弹窗 // 关闭弹窗
setEditModalVisible(false); setEditModalVisible(false);
@@ -224,20 +223,18 @@ const EditProfilePage: React.FC = () => {
field !== null && field !== null &&
!Array.isArray(field) !Array.isArray(field)
) { ) {
await UserService.update_user_info({ ...field }); await updateUserInfo({ ...field });
// 更新本地状态
// 更新表单状态store会自动更新
setFormData((prev) => ({ ...prev, ...field })); setFormData((prev) => ({ ...prev, ...field }));
setUserInfo((prev) => ({ ...prev, ...field }));
} else { } else {
// 调用更新用户信息接口,只传递修改的字段 // 调用更新用户信息接口,只传递修改的字段
const update_data = { [field as string]: value }; const update_data = { [field as string]: value };
await UserService.update_user_info(update_data); await updateUserInfo(update_data);
// 更新本地状态 // 更新表单状态store会自动更新
setFormData((prev) => ({ ...prev, [field as string]: value })); setFormData((prev) => ({ ...prev, [field as string]: value }));
setUserInfo((prev) => ({ ...prev, [field as string]: value }));
} }
// 显示成功提示 // 显示成功提示
Taro.showToast({ Taro.showToast({
title: "保存成功", title: "保存成功",
@@ -364,7 +361,7 @@ const EditProfilePage: React.FC = () => {
<View className="avatar_container" onClick={handle_avatar_upload}> <View className="avatar_container" onClick={handle_avatar_upload}>
<Image <Image
className="avatar" className="avatar"
src={user_info.avatar} src={(user_info as UserInfoType)?.avatar_url || ""}
mode="aspectFill" mode="aspectFill"
/> />
<View className="avatar_overlay"> <View className="avatar_overlay">
@@ -393,7 +390,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.nickname || "188的王晨"} {form_data.nickname || ""}
</Text> </Text>
<Image <Image
className="arrow_icon" className="arrow_icon"
@@ -648,6 +645,8 @@ const EditProfilePage: React.FC = () => {
{/* 生日选择弹窗 */} {/* 生日选择弹窗 */}
{birthday_picker_visible && ( {birthday_picker_visible && (
<PopupPicker <PopupPicker
minYear={1970}
maxYear={new Date().getFullYear()}
showHeader={true} showHeader={true}
title="选择生日" title="选择生日"
confirmText="保存" confirmText="保存"
@@ -693,7 +692,7 @@ const EditProfilePage: React.FC = () => {
], ],
]} ]}
type="ntrp" type="ntrp"
img={user_info.avatar} img={(user_info as UserInfoType)?.avatar_url}
visible={ntrp_picker_visible} visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible} setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level]} value={[form_data.ntrp_level]}

View File

@@ -3,12 +3,14 @@ import { View, Text, Image, ScrollView } from "@tarojs/components";
import Taro, { useDidShow } 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 } from "@/components/UserInfo/index";
import { UserService } from "@/services/userService"; import { UserService, UserInfo } from "@/services/userService";
import ListContainer from "@/container/listContainer"; import ListContainer from "@/container/listContainer";
import { TennisMatch } from "@/../types/list/types"; import { TennisMatch } from "@/../types/list/types";
import { withAuth, NTRPTestEntryCard } from "@/components"; import { withAuth, NTRPTestEntryCard } from "@/components";
import { EvaluateScene } from "@/store/evaluateStore"; import { EvaluateScene } from "@/store/evaluateStore";
import { useUserInfo } from "@/store/userStore";
import { UserInfoType } from "@/services/userService";
const MyselfPage: React.FC = () => { const MyselfPage: React.FC = () => {
// 获取页面参数 // 获取页面参数
@@ -18,33 +20,14 @@ const MyselfPage: React.FC = () => {
// 判断是否为当前用户 // 判断是否为当前用户
const is_current_user = !user_id; const is_current_user = !user_id;
// 用户信息状态 // 直接从store获取用户信息确保响应式更新
const [user_info, set_user_info] = useState<UserInfo>({ const user_info = useUserInfo();
id: "",
nickname: "加载中...",
avatar: require("@/static/userInfo/default_avatar.svg"),
join_date: "加载中...",
stats: {
following: 0,
friends: 0,
hosted: 0,
participated: 0,
},
bio: "加载中...",
city: "加载中...",
occupation: "加载中...",
ntrp_level: "NTRP 3.0",
personal_profile: "加载中...",
gender: "",
country: "",
province: "",
});
// 球局记录状态 // 球局记录状态
const [game_records, set_game_records] = useState<TennisMatch[]>([]); const [game_records, set_game_records] = useState<TennisMatch[]>([]);
// 往期球局 // 往期球局
const [ended_game_records, setEndedGameRecords] = useState<TennisMatch[]>([]); const [ended_game_records, setEndedGameRecords] = useState<TennisMatch[]>([]);
const [loading, set_loading] = useState(true); const [loading, set_loading] = useState(false);
// 关注状态 // 关注状态
const [is_following, setIsFollowing] = useState(false); const [is_following, setIsFollowing] = useState(false);
@@ -55,37 +38,37 @@ const MyselfPage: React.FC = () => {
); );
// 加载用户数据 // 加载用户数据
const load_user_data = async () => { // const load_user_data = async () => {
try { // try {
set_loading(true); // set_loading(true);
// 获取用户信息(包含统计数据) // // 获取用户信息(包含统计数据)
const user_data = await UserService.get_user_info(); // const user_data = await UserService.get_user_info();
set_user_info(user_data); // set_user_info(user_data);
// let games_data; // // let games_data;
// if (active_tab === "hosted") { // // if (active_tab === "hosted") {
// games_data = await UserService.get_hosted_games(user_id); // // games_data = await UserService.get_hosted_games(user_id);
// } else { // // } else {
// games_data = await UserService.get_participated_games(user_id); // // games_data = await UserService.get_participated_games(user_id);
// // }
// // set_game_records(games_data);
// } catch (error) {
// console.error("加载用户数据失败:", error);
// Taro.showToast({
// title: "加载失败,请重试",
// icon: "error",
// duration: 2000,
// });
// } finally {
// set_loading(false);
// } // }
// set_game_records(games_data); // };
} catch (error) {
console.error("加载用户数据失败:", error);
Taro.showToast({
title: "加载失败,请重试",
icon: "error",
duration: 2000,
});
} finally {
set_loading(false);
}
};
useEffect(() => { // useEffect(() => {
if (user_info.id) { // if (user_info.id) {
load_game_data(); // 在 user_info 更新后调用 // load_game_data(); // 在 user_info 更新后调用
} // }
}, [user_info]); // }, [user_info]);
// 页面加载时获取数据 // 页面加载时获取数据
// useEffect(() => { // useEffect(() => {
@@ -93,7 +76,7 @@ const MyselfPage: React.FC = () => {
// }, [user_id]); // }, [user_id]);
useDidShow(() => { useDidShow(() => {
load_user_data(); // 确保从编辑页面返回时刷新数据 // set_user_info(useUserInfo()); // 确保从编辑页面返回时刷新数据
}); });
// 切换标签页时重新加载球局数据 // 切换标签页时重新加载球局数据
@@ -196,19 +179,12 @@ const MyselfPage: React.FC = () => {
<View className="main_content"> <View className="main_content">
{/* 用户信息区域 */} {/* 用户信息区域 */}
<View className="user_info_section"> <View className="user_info_section">
{loading ? (
<View className="loading_container">
<Text className="loading_text">...</Text>
</View>
) : (
<UserInfoCard <UserInfoCard
user_info={user_info} user_info={user_info}
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}
/> />
)}
{/* 球局订单和收藏功能 */} {/* 球局订单和收藏功能 */}
<View className="quick_actions_section"> <View className="quick_actions_section">
<View className="action_card"> <View className="action_card">

View File

@@ -9,10 +9,10 @@ import {
UserInfoCard, UserInfoCard,
// GameCard, // GameCard,
GameTabs, GameTabs,
UserInfo, // UserInfo,
GameRecord, GameRecord,
} from "@/components/UserInfo"; } from "@/components/UserInfo";
import { UserService } from "@/services/userService"; import { UserService, UserInfoType } from "@/services/userService";
import * as LoginService from "@/services/loginService"; import * as LoginService from "@/services/loginService";
const OtherUserPage: React.FC = () => { const OtherUserPage: React.FC = () => {
@@ -21,21 +21,22 @@ const OtherUserPage: React.FC = () => {
const user_id = instance.router?.params?.userid; const user_id = instance.router?.params?.userid;
// 模拟用户数据 // 模拟用户数据
const [user_info, setUserInfo] = useState<UserInfo>({ const [user_info, setUserInfo] = useState<Partial<UserInfoType>>({
id: user_id || "1", id: parseInt(user_id || "1") || 1,
gender: "", gender: "",
nickname: "网球爱好者", nickname: "网球爱好者",
avatar: require("@/static/userInfo/default_avatar.svg"), avatar_url: require("@/static/userInfo/default_avatar.svg"),
join_date: "2024年3月加入", join_date: "2024年3月加入",
stats: { stats: {
following: 0, following_count: 0,
friends: 0, followers_count: 0,
hosted: 0, hosted_games_count: 0,
participated: 0, participated_games_count: 0,
}, },
tags: ["北京朝阳", "金融从业者", "NTRP 3.5"], tags: ["北京朝阳", "金融从业者", "NTRP 3.5"],
bio: "热爱网球的金融从业者,周末喜欢约球\n技术还在提升中欢迎一起切磋\n平时在朝阳公园附近活动", bio: "热爱网球的金融从业者,周末喜欢约球\n技术还在提升中欢迎一起切磋\n平时在朝阳公园附近活动",
location: "北京朝阳", city: "北京",
district: "朝阳",
occupation: "金融从业者", occupation: "金融从业者",
ntrp_level: "NTRP 3.5", ntrp_level: "NTRP 3.5",
is_following: false, is_following: false,
@@ -68,22 +69,26 @@ const OtherUserPage: React.FC = () => {
const { data: userData } = res; const { data: userData } = res;
// setUserInfo({...res.data as UserInfo, avatar: data.avatar_url || require("@/static/userInfo/default_avatar.svg")}); // setUserInfo({...res.data as UserInfo, avatar: data.avatar_url || require("@/static/userInfo/default_avatar.svg")});
setUserInfo({ setUserInfo({
id: user_id || "", id: parseInt(user_id || "") || 0,
nickname: userData.nickname || "", nickname: userData.nickname || "",
avatar: userData.avatar_url || "", avatar_url: userData.avatar_url || "",
join_date: userData.subscribe_time join_date: userData.subscribe_time
? `${new Date(userData.subscribe_time).getFullYear()}${new Date(userData.subscribe_time).getMonth() + 1 ? `${new Date(userData.subscribe_time).getFullYear()}${
new Date(userData.subscribe_time).getMonth() + 1
}月加入` }月加入`
: "", : "",
stats: { stats: {
following: userData.stats?.following_count || 0, following_count: userData.stats?.following_count || 0,
friends: userData.stats?.followers_count || 0, followers_count: userData.stats?.followers_count || 0,
hosted: userData.stats?.hosted_games_count || 0, hosted_games_count: userData.stats?.hosted_games_count || 0,
participated: userData.stats?.participated_games_count || 0, participated_games_count:
userData.stats?.participated_games_count || 0,
}, },
personal_profile: userData.personal_profile || "", personal_profile: userData.personal_profile || "",
location: userData.city + userData.district || "", province: userData.province || "",
city: userData.city || "",
district: userData.district || "",
occupation: userData.occupation || "", occupation: userData.occupation || "",
ntrp_level: "", ntrp_level: "",
phone: userData.phone || "", phone: userData.phone || "",
@@ -132,7 +137,10 @@ const OtherUserPage: React.FC = () => {
active_tab active_tab
); );
const sorted_games = games_data.sort((a, b) => { const sorted_games = games_data.sort((a, b) => {
return new Date(a.original_start_time.replace(/\s/, 'T')).getTime() - new Date(b.original_start_time.replace(/\s/, 'T')).getTime(); return (
new Date(a.original_start_time.replace(/\s/, "T")).getTime() -
new Date(b.original_start_time.replace(/\s/, "T")).getTime()
);
}); });
const { notEndGames, finishedGames } = classifyGameRecords(sorted_games); const { notEndGames, finishedGames } = classifyGameRecords(sorted_games);
setGameRecords(notEndGames); setGameRecords(notEndGames);
@@ -157,7 +165,7 @@ const OtherUserPage: React.FC = () => {
const handle_follow = async () => { const handle_follow = async () => {
try { try {
const new_follow_status = await UserService.toggle_follow( const new_follow_status = await UserService.toggle_follow(
user_info.id, user_info.id || "",
is_following is_following
); );
setIsFollowing(new_follow_status); setIsFollowing(new_follow_status);
@@ -178,7 +186,9 @@ const OtherUserPage: React.FC = () => {
// 处理发送消息 // 处理发送消息
const handle_send_message = () => { const handle_send_message = () => {
Taro.navigateTo({ Taro.navigateTo({
url: `/mode_user/message/chat/index?user_id=${user_info.id}&nickname=${user_info.nickname}`, url: `/mode_user/message/chat/index?user_id=${
user_info.id || ""
}&nickname=${user_info.nickname || ""}`,
}); });
}; };