import React, { useState, useEffect, useRef } from "react"; import Taro, { useDidShow } from "@tarojs/taro"; import { View, Text, Image, Button } from "@tarojs/components"; import "./index.scss"; import { EditModal } from "@/components"; import { UserService, PickerOption } from "@/services/userService"; import { PopupPicker } from "@/components/Picker/index"; import { useUserActions, useNicknameChangeStatus, useLastTestResult, useUserInfo, } from "@/store/userStore"; import { UserInfoType } from "@/services/userService"; import { useCities, useProfessions, useNtrpLevels, } from "@/store/pickerOptionsStore"; import { formatNtrpDisplay } from "@/utils/helper"; import { useGlobalState } from "@/store/global"; // 用户信息接口 // export interface UserInfo { // id: string | number; // nickname: string; // avatar: string; // join_date: string; // stats: { // following: number; // friends: number; // hosted: number; // participated: number; // }; // personal_profile: string; // occupation: string; // ntrp_level: string; // phone?: string; // gender: string; // bio?: string; // latitude?: string; // longitude?: string; // birthday?: string; // is_following?: boolean; // tags?: string[]; // ongoing_games?: string[]; // country: string; // province: string; // city: string; // } // 用户信息卡片组件属性 interface UserInfoCardProps { editable: boolean; user_info: Partial; is_current_user: boolean; is_following?: boolean; collapseProfile?: boolean; setMarginBottom?: boolean; on_follow?: () => void; on_message?: () => void; on_share?: () => void; set_user_info?: (info: Partial) => void; onTab?: (tab) => void; } // 处理编辑用户信息 const on_edit = () => { Taro.navigateTo({ url: "/user_pages/edit/index", }); }; // 用户信息卡片组件 const UserInfoCardComponent: React.FC = ({ editable = true, user_info: user_info_prop, is_current_user, is_following = false, collapseProfile, setMarginBottom = true, on_follow, on_message, on_share, set_user_info, onTab, }) => { const global_user_info = useUserInfo(); // 查看别人页面时用传入的 user_info,个人页用全局 store const user_info = is_current_user ? global_user_info : (user_info_prop ?? global_user_info); const nickname_change_status = useNicknameChangeStatus(); const { setShowGuideBar } = useGlobalState(); const { updateUserInfo, updateNickname, fetchLastTestResult } = useUserActions(); const ntrpLevels = useNtrpLevels(); // 使用全局状态中的测试结果,避免重复调用接口 const lastTestResult = useLastTestResult(); // 使用 useRef 记录上一次的 user_info,只在真正变化时打印 const prevUserInfoRef = useRef>(); useEffect(() => { const prevStr = JSON.stringify(prevUserInfoRef.current); const currentStr = JSON.stringify(user_info); if (prevStr !== currentStr) { prevUserInfoRef.current = user_info; } // 仅当前用户才拉取 NTRP 测试结果 if (is_current_user && !lastTestResult && user_info?.id) { fetchLastTestResult(); } }, [user_info?.id, lastTestResult, fetchLastTestResult, is_current_user]); // 从全局状态中获取测试状态 const ntrpTested = lastTestResult?.has_test_in_last_month || false; // 编辑个人简介弹窗状态 const [edit_modal_visible, setEditModalVisible] = useState(false); const [editing_field, setEditingField] = useState(""); const [gender_picker_visible, setGenderPickerVisible] = useState(false); const [location_picker_visible, setLocationPickerVisible] = useState(false); const [ntrp_picker_visible, setNtrpPickerVisible] = useState(false); const [occupation_picker_visible, setOccupationPickerVisible] = useState(false); // 表单状态 const [form_data, set_form_data] = useState>({ ...user_info }); // useDidShow(() => { // set_form_data({ ...user_info }); // }); useEffect(() => { set_form_data({ ...user_info }); }, [user_info]) useEffect(() => { const visibles = [ gender_picker_visible, location_picker_visible, ntrp_picker_visible, occupation_picker_visible, edit_modal_visible, ]; const allPickersClosed = visibles.every((item) => !item); // 所有选择器都关闭时,显示 GuideBar;否则隐藏 setShowGuideBar(allPickersClosed); }, [ gender_picker_visible, location_picker_visible, ntrp_picker_visible, occupation_picker_visible, edit_modal_visible, ]); // 职业数据 const professions = useProfessions(); // 城市数据 const cities = useCities(); // 页面加载时初始化数据 // useEffect(() => { // getProfessions(); // getCities(); // }, []); // const getProfessions = async () => { // try { // const res = await UserService.getProfessions(); // setProfessions(res); // } catch (e) { // console.log("获取职业失败:", e); // } // }; // const getCities = async () => { // try { // const res = await UserService.getCities(); // setCities(res); // } catch (e) { // console.log("获取职业失败:", e); // } // }; // 处理编辑弹窗 const handle_open_edit_modal = (field: string) => { // 打开编辑弹窗时隐藏 GuideBar setShowGuideBar(false); if (field === "gender") { setGenderPickerVisible(true); return; } if (field === "location") { setLocationPickerVisible(true); return; } if (field === "ntrp_level") { setNtrpPickerVisible(true); return; } if (field === "occupation") { setOccupationPickerVisible(true); return; } if (field === "nickname") { if (!is_current_user) return; if (!nickname_change_status.can_change) { return Taro.showToast({ title: `30天内仅可修改4次昵称,${nickname_change_status.next_period_start_date}后可修改`, icon: "none", duration: 2000, }); } // 手动输入 setShowGuideBar(false); setEditingField(field); setEditModalVisible(true); } else { if (!is_current_user) return; setShowGuideBar(false); 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); editing_field === "nickname" ? await updateNickname(value) : await updateUserInfo(update_data); set_form_data((prev) => { return { ...prev, ...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.warn("保存失败:", error); Taro.showToast({ title: "保存失败", icon: "error", }); } }; // 处理字段编辑 const handle_field_edit = async ( field: string | { [key: string]: string }, value?: string ) => { try { if ( typeof field === "object" && field !== null && !Array.isArray(field) ) { await updateUserInfo({ ...field }); set_form_data((prev) => { return { ...prev, ...field }; }); // 更新本地状态 // setFormData((prev) => ({ ...prev, ...field })); // setUserInfo((prev) => ({ ...prev, ...field })); } else { // 调用更新用户信息接口,只传递修改的字段 const update_data = { [field as string]: value }; await updateUserInfo(update_data); set_form_data((prev) => { return { ...prev, ...update_data }; }); // 更新本地状态 // setFormData((prev) => ({ ...prev, [field as string]: value })); // setUserInfo((prev) => ({ ...prev, [field as string]: value })); } // 显示成功提示 Taro.showToast({ title: "保存成功", icon: "success", }); } catch (error) { console.warn("保存失败:", error); Taro.showToast({ title: "保存失败", icon: "error", }); } }; // 处理性别选择 const handle_gender_change = (e: any) => { const gender_value = e[0]; handle_field_edit("gender", gender_value); }; // 处理地区选择 const handle_location_change = (e: any) => { const [province, city, district] = e; handle_field_edit({ province, city, district }); }; // 处理NTRP水平选择 const handle_ntrp_level_change = (e: any) => { const ntrp_level_value = e[0]; handle_field_edit("ntrp_level", ntrp_level_value); }; // 处理职业选择 const handle_occupation_change = (e: any) => { const [firstVal, secondVal, thirdVal] = e; handle_field_edit("occupation", `${firstVal} ${secondVal} ${thirdVal}`); }; const handle_edit_modal_cancel = () => { // 关闭编辑弹窗时显示 GuideBar setShowGuideBar(true); setEditModalVisible(false); setEditingField(""); }; // 处理统计项点击 const handle_stats_click = ( type: "following" | "friends" | "hosted" | "participated" ) => { // 只有当前用户才能查看关注相关页面 if (!is_current_user) { Taro.showToast({ title: "暂不支持查看他人关注信息", icon: "none", }); return; } if (type === "following") { // 跳转到关注列表页面 Taro.navigateTo({ url: "/user_pages/follow/index?tab=following", }); } else if (type === "friends") { // 跳转到球友(粉丝)页面,显示粉丝标签 Taro.navigateTo({ url: "/user_pages/follow/index?tab=follower", }); } // 主办和参加暂时不处理,可以后续扩展 }; const getDefaultOption = (options) => { if (!Array.isArray(options) || options.length === 0) { return []; } const defaultOptions: string[] = []; let current = options[0]; while (current) { defaultOptions.push(current.text); current = current.children?.[0]; } return defaultOptions; }; const previewAvatar = (url) => { wx.previewImage({ urls: [url], }); }; return ( {/* 头像和基本信息 */} { previewAvatar(user_info.avatar_url || ""); }} /> { handle_open_edit_modal("nickname"); }} > {user_info.nickname || ""} {user_info.join_date || ""} {is_current_user && ( )} {/* 统计数据 */} handle_stats_click("following")} > {user_info.stats?.following_count || 0} 关注 handle_stats_click("friends")} > {user_info.stats?.followers_count || 0} 粉丝 onTab?.("hosted")} > {user_info.stats?.hosted_games_count || 0} 主办 onTab?.("participated")} > {user_info.stats?.participated_games_count || 0} 参加 {/* 只有非当前用户才显示关注按钮 */} {!is_current_user && on_follow && ( )} {/* 只有非当前用户才显示消息按钮 */} {/* {!is_current_user && on_message && ( )} */} {/* 只有当前用户才显示分享按钮 */} {is_current_user && on_share && ( )} {/* 标签和简介 */} {!collapseProfile ? ( {user_info.gender && user_info.gender !== "2" ? ( {user_info.gender === "0" && ( { editable && handle_open_edit_modal("gender"); }} /> )} {user_info.gender === "1" && ( { editable && handle_open_edit_modal("gender"); }} /> )} ) : is_current_user && user_info.gender !== "2" ? ( { handle_open_edit_modal("gender"); }} > 选择性别 ) : null} {user_info.ntrp_level !== "" ? ( { editable && handle_open_edit_modal("ntrp_level"); }} > {`NTRP ${formatNtrpDisplay( user_info.ntrp_level )}`} ) : is_current_user ? ( { handle_open_edit_modal("ntrp_level"); }} > 测测你的NTRP水平 ) : null} {user_info.occupation ? ( { editable && handle_open_edit_modal("occupation"); }} > {user_info.occupation.split(" ")[2]} ) : is_current_user ? ( { handle_open_edit_modal("occupation"); }} > 选择职业 ) : null} {user_info.province || user_info.city || user_info.district ? ( editable && handle_open_edit_modal("location")} > {`${user_info.city}${user_info.district}`} ) : is_current_user ? ( handle_open_edit_modal("location")} > 选择地区 ) : null} handle_open_edit_modal("personal_profile")} > {!collapseProfile ? ( user_info.personal_profile ? ( {user_info.personal_profile} ) : is_current_user ? ( 点击添加简介,让更多人了解你 ) : null ) : null} ) : null} {/* 编辑个人简介弹窗 */} /等无效字符。30 天内可修改 4 次昵称,${nickname_change_status.next_period_start_date} 前还可修改 ${nickname_change_status.remaining_count} 次。` : "请填写 2-100 个字符" } /> {/* */} {/* 性别选择弹窗 */} {gender_picker_visible && ( )} {/* 地区选择弹窗 */} {location_picker_visible && ( )} {/* NTRP水平选择弹窗 */} {ntrp_picker_visible && ( )} {/* 职业选择弹窗 */} {occupation_picker_visible && ( )} ); }; // 自定义比较函数:只在关键 props 变化时重新渲染 const arePropsEqual = ( prevProps: UserInfoCardProps, nextProps: UserInfoCardProps ) => { // 使用 JSON.stringify 进行深度比较(注意:对于复杂对象可能有性能问题) const prevUserInfoStr = JSON.stringify(prevProps.user_info); const nextUserInfoStr = JSON.stringify(nextProps.user_info); return ( prevUserInfoStr === nextUserInfoStr && prevProps.editable === nextProps.editable && prevProps.is_current_user === nextProps.is_current_user && prevProps.is_following === nextProps.is_following && prevProps.collapseProfile === nextProps.collapseProfile ); }; // 使用 React.memo 优化组件,减少不必要的重新渲染 // export const UserInfoCard = React.memo(UserInfoCardComponent, arePropsEqual); export const UserInfoCard = UserInfoCardComponent; // 球局记录接口 export interface GameRecord { id: string; title: string; date: string; time: string; duration: string; location: string; type: string; distance: string; participants: { avatar: string; nickname: string; }[]; max_participants: number; current_participants: number; level_range: string; game_type: string; image_list: string[]; deadline_hours: number; end_time: string; } // 球局卡片组件属性 interface GameCardProps { game: GameRecord; on_click: (game_id: string) => void; on_participant_click?: (participant_id: string) => void; } // 球局卡片组件 export const GameCard: React.FC = ({ game, on_click, on_participant_click, }) => { return ( on_click(game.id)}> {/* 球局标题和类型 */} {game.title} {/* 球局时间 */} {game.date} {game.time} {game.duration} {/* 球局地点和类型 */} {game.location} · {game.type} · {game.distance} {/* 球局图片 */} {game.image_list.map((image, index) => ( ))} {/* 球局信息标签 */} {game.participants?.map((participant, index) => ( { e.stopPropagation(); on_participant_click?.(participant.nickname); }} /> ))} 报名人数 {game.current_participants}/{game.max_participants} {game.level_range} {game.game_type} ); }; // 球局标签页组件属性 interface GameTabsProps { active_tab: "hosted" | "participated"; on_tab_change: (tab: "hosted" | "participated") => void; is_current_user: boolean; } // 球局标签页组件 export const GameTabs: React.FC = ({ active_tab, on_tab_change, is_current_user, }) => { const hosted_text = is_current_user ? "我主办的" : "主办球局"; const participated_text = is_current_user ? "我参与的" : "参与球局"; return ( on_tab_change("hosted")} > {hosted_text} on_tab_change("participated")} > {participated_text} ); };