Files
mini-programs/src/components/UserInfo/index.tsx
张成 721d0c10e4 1
2025-09-17 22:55:20 +08:00

450 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from "react";
import Taro from "@tarojs/taro";
import { View, Text, Image, Button } from "@tarojs/components";
import "./index.scss";
import { EditModal } from "@/components";
import { UserService } from "@/services/userService";
// 用户信息接口
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;
location: 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[];
}
// 用户信息卡片组件属性
interface UserInfoCardProps {
user_info: UserInfo;
is_current_user: boolean;
is_following?: boolean;
on_follow?: () => void;
on_message?: () => void;
on_share?: () => void;
set_user_info?: (info: UserInfo) => void;
}
// 处理编辑用户信息
const on_edit = () => {
Taro.navigateTo({
url: "/user_pages/edit/index",
});
};
// 用户信息卡片组件
export const UserInfoCard: React.FC<UserInfoCardProps> = ({
user_info,
is_current_user,
is_following = false,
on_follow,
on_message,
on_share,
set_user_info,
}) => {
console.log("UserInfoCard 用户信息:", 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("");
};
// 处理统计项点击
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'
});
}
// 主办和参加暂时不处理,可以后续扩展
};
return (
<View className="user_info_card">
{/* 头像和基本信息 */}
<View className="basic_info">
<View className="avatar_container">
<Image className="avatar" src={user_info.avatar} />
</View>
<View className="info_container">
<Text className="nickname">{user_info.nickname}</Text>
<Text className="join_date">{user_info.join_date}</Text>
</View>
{is_current_user && (
<View className="tag_item" onClick={on_edit}>
<Image
className="tag_icon"
src={require("../../static/userInfo/edit.svg")}
/>
</View>
)}
</View>
{/* 统计数据 */}
<View className="stats_section">
<View className="stats_container">
<View className="stat_item clickable" onClick={() => handle_stats_click('following')}>
<Text className="stat_number">{user_info.stats.following}</Text>
<Text className="stat_label"></Text>
</View>
<View className="stat_item clickable" onClick={() => handle_stats_click('friends')}>
<Text className="stat_number">{user_info.stats.friends}</Text>
<Text className="stat_label"></Text>
</View>
<View className="stat_item">
<Text className="stat_number">{user_info.stats.hosted}</Text>
<Text className="stat_label"></Text>
</View>
<View className="stat_item">
<Text className="stat_number">{user_info.stats.participated}</Text>
<Text className="stat_label"></Text>
</View>
</View>
<View className="action_buttons">
{/* 只有非当前用户才显示关注按钮 */}
{!is_current_user && on_follow && (
<Button
className={`follow_button ${is_following ? "following" : ""}`}
onClick={on_follow}
>
<Image
className="button_icon"
src={require(is_following
? "@/static/userInfo/following.svg"
: "@/static/userInfo/unfollow.svg")}
/>
<Text className={`button_text ${is_following ? "following" : ""}`}>
{is_following ? "已关注" : "关注"}
</Text>
</Button>
)}
{/* 只有非当前用户才显示消息按钮 */}
{/* {!is_current_user && on_message && (
<Button className="message_button" onClick={on_message}>
<Image
className="button_icon"
src={require("@/static/userInfo/chat.svg")}
/>
</Button>
)} */}
{/* 只有当前用户才显示分享按钮 */}
{is_current_user && on_share && (
<Button className="share_button" onClick={on_share}>
<Text className="button_text"></Text>
</Button>
)}
</View>
</View>
{/* 标签和简介 */}
<View className="tags_bio_section">
<View className="tags_container">
{user_info.gender ? (
<View className="tag_item">
{user_info.gender === "0" && (
<Image
className="tag_icon"
src={require("../../static/userInfo/male.svg")}
/>
)}
{user_info.gender === "1" && (
<Image
className="tag_icon"
src={require("../../static/userInfo/female.svg")}
/>
)}
</View>
) : is_current_user ? (
<View className="button_edit">
<Text></Text>
</View>
) : null}
{user_info.ntrp_level ? (
<View className="tag_item">
<Text className="tag_text">{user_info.ntrp_level}</Text>
</View>
) : is_current_user ? (
<View className="button_edit">
<Text>NTRP水平</Text>
</View>
) : null}
{user_info.occupation ? (
<View className="tag_item">
<Text className="tag_text">{user_info.occupation}</Text>
</View>
) : is_current_user ? (
<View className="button_edit">
<Text></Text>
</View>
) : null}
{user_info.location ? (
<View className="tag_item">
<Text className="tag_text">{user_info.location}</Text>
</View>
) : is_current_user ? (
<View className="button_edit">
<Text></Text>
</View>
) : null}
</View>
<View className="personal_profile">
{user_info.personal_profile ? (
<Text className="bio_text">{user_info.personal_profile}</Text>
) : is_current_user ? (
<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>
) : null}
</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>
);
};
// 球局记录接口
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<GameCardProps> = ({
game,
on_click,
on_participant_click,
}) => {
return (
<View className="game_card" onClick={() => on_click(game.id)}>
{/* 球局标题和类型 */}
<View className="game_header">
<Text className="game_title">{game.title}</Text>
<View className="game_type_icon">
<Image
className="type_icon"
src={require("../../static/userInfo/tennis.svg")}
/>
</View>
</View>
{/* 球局时间 */}
<View className="game_time">
<Text className="time_text">
{game.date} {game.time} {game.duration}
</Text>
</View>
{/* 球局地点和类型 */}
<View className="game_location">
<Text className="location_text">{game.location}</Text>
<Text className="separator">·</Text>
<Text className="type_text">{game.type}</Text>
<Text className="separator">·</Text>
<Text className="distance_text">{game.distance}</Text>
</View>
{/* 球局图片 */}
<View className="game_images">
{game.image_list.map((image, index) => (
<Image key={index} className="game_image" src={image} />
))}
</View>
{/* 球局信息标签 */}
<View className="game_tags">
<View className="participants_info">
<View className="avatars">
{game.participants?.map((participant, index) => (
<Image
key={index}
className="participant_avatar"
src={participant.avatar}
onClick={(e) => {
e.stopPropagation();
on_participant_click?.(participant.nickname);
}}
/>
))}
</View>
<View className="participants_count">
<Text className="count_text">
{game.current_participants}/{game.max_participants}
</Text>
</View>
</View>
<View className="game_info_tags">
<View className="info_tag">
<Text className="tag_text">{game.level_range}</Text>
</View>
<View className="info_tag">
<Text className="tag_text">{game.game_type}</Text>
</View>
</View>
</View>
</View>
);
};
// 球局标签页组件属性
interface GameTabsProps {
active_tab: "hosted" | "participated";
on_tab_change: (tab: "hosted" | "participated") => void;
is_current_user: boolean;
}
// 球局标签页组件
export const GameTabs: React.FC<GameTabsProps> = ({
active_tab,
on_tab_change,
is_current_user,
}) => {
const hosted_text = is_current_user ? "我主办的" : "主办球局";
const participated_text = is_current_user ? "我参与的" : "参与球局";
return (
<View className="game_tabs_section">
<View className="tab_container">
<View
className={`tab_item ${active_tab === "hosted" ? "active" : ""}`}
onClick={() => on_tab_change("hosted")}
>
<Text className="tab_text">{hosted_text}</Text>
</View>
<View
className={`tab_item ${
active_tab === "participated" ? "active" : ""
}`}
onClick={() => on_tab_change("participated")}
>
<Text className="tab_text">{participated_text}</Text>
</View>
</View>
</View>
);
};