diff --git a/src/components/Picker/PopupPicker.tsx b/src/components/Picker/PopupPicker.tsx index 94359fa..1d524a7 100644 --- a/src/components/Picker/PopupPicker.tsx +++ b/src/components/Picker/PopupPicker.tsx @@ -17,6 +17,8 @@ interface PickerOption { } interface PickerProps { + minYear?: number; + maxYear?: number; title?: string; showHeader?: boolean; confirmText?: string; @@ -28,10 +30,12 @@ interface PickerProps { img?: string; onConfirm?: (options: PickerOption[], values: (string | number)[]) => void; onChange?: (value: (string | number)[]) => void; - style?: React.CSSProperties + style?: React.CSSProperties; } const PopupPicker = ({ + minYear, + maxYear, confirmText, title, showHeader, @@ -87,7 +91,7 @@ const PopupPicker = ({ if (type === "month") { setDefaultOptions(renderYearMonth()); } else if (type === "day") { - setDefaultOptions(renderYearMonthDay()); + setDefaultOptions(renderYearMonthDay(minYear, maxYear)); } else if (type === "hour") { setDefaultOptions(renderHourMinute()); } else { diff --git a/src/components/UserInfo/index.tsx b/src/components/UserInfo/index.tsx index d666430..4088ff2 100644 --- a/src/components/UserInfo/index.tsx +++ b/src/components/UserInfo/index.tsx @@ -6,45 +6,47 @@ import "./index.scss"; import { EditModal } from "@/components"; import { UserService, PickerOption } from "@/services/userService"; import { PopupPicker } from "@/components/Picker/index"; +import { useUserActions } from "@/store/userStore"; +import { UserInfoType } 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; - 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; -} +// 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 { - user_info: UserInfo; + user_info: Partial; is_current_user: boolean; is_following?: boolean; on_follow?: () => void; on_message?: () => void; on_share?: () => void; - set_user_info?: (info: UserInfo) => void; + set_user_info?: (info: Partial) => void; } // 处理编辑用户信息 @@ -63,6 +65,7 @@ export const UserInfoCard: React.FC = ({ on_share, set_user_info, }) => { + const { updateUserInfo } = useUserActions(); console.log("UserInfoCard 用户信息:", user_info); // 编辑个人简介弹窗状态 const [edit_modal_visible, setEditModalVisible] = useState(false); @@ -74,7 +77,9 @@ export const UserInfoCard: React.FC = ({ useState(false); // 表单状态 - const [form_data, setFormData] = useState({ ...user_info }); + const [form_data, setFormData] = useState>({ + ...user_info, + }); // 职业数据 const [professions, setProfessions] = useState([]); @@ -172,15 +177,14 @@ export const UserInfoCard: React.FC = ({ field !== null && !Array.isArray(field) ) { - await UserService.update_user_info({ ...field }); + await updateUserInfo({ ...field }); // 更新本地状态 setFormData((prev) => ({ ...prev, ...field })); // setUserInfo((prev) => ({ ...prev, ...field })); } else { // 调用更新用户信息接口,只传递修改的字段 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 })); // setUserInfo((prev) => ({ ...prev, [field as string]: value })); @@ -259,11 +263,15 @@ export const UserInfoCard: React.FC = ({ {/* 头像和基本信息 */} - + - {user_info.nickname} - {user_info.join_date} + {user_info.nickname || ""} + {user_info.join_date || ""} {is_current_user && ( @@ -282,22 +290,30 @@ export const UserInfoCard: React.FC = ({ className="stat_item clickable" onClick={() => handle_stats_click("following")} > - {user_info.stats.following} + + {user_info.stats?.following_count || 0} + 关注 handle_stats_click("friends")} > - {user_info.stats.friends} + + {user_info.stats?.followers_count || 0} + 球友 - {user_info.stats.hosted} + + {user_info.stats?.hosted_games_count || 0} + 主办 - {user_info.stats.participated} + + {user_info.stats?.participated_games_count || 0} + 参加 @@ -455,7 +471,7 @@ export const UserInfoCard: React.FC = ({ ]} visible={gender_picker_visible} setvisible={setGenderPickerVisible} - value={[form_data.gender]} + value={[form_data.gender || ""]} onChange={handle_gender_change} /> )} @@ -467,7 +483,11 @@ export const UserInfoCard: React.FC = ({ options={cities} visible={location_picker_visible} 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} /> )} @@ -488,10 +508,10 @@ export const UserInfoCard: React.FC = ({ ], ]} type="ntrp" - img={user_info.avatar} + img={user_info.avatar_url || ""} visible={ntrp_picker_visible} setvisible={setNtrpPickerVisible} - value={[form_data.ntrp_level]} + value={[form_data.ntrp_level || ""]} onChange={handle_ntrp_level_change} /> )} @@ -503,7 +523,7 @@ export const UserInfoCard: React.FC = ({ options={professions} visible={occupation_picker_visible} setvisible={setOccupationPickerVisible} - value={[...form_data.occupation.split(" ")]} + value={[...(form_data.occupation || "").split(" ")]} onChange={handle_occupation_change} /> )} @@ -644,8 +664,9 @@ export const GameTabs: React.FC = ({ {hosted_text} on_tab_change("participated")} > {participated_text} diff --git a/src/services/userService.ts b/src/services/userService.ts index 7e07a28..c5f5d4c 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -1,12 +1,11 @@ -import { UserInfo } from '@/components/UserInfo'; -import { API_CONFIG } from '@/config/api'; -import httpService, { ApiResponse } from './httpService'; -import uploadFiles from './uploadFiles'; -import Taro from '@tarojs/taro'; -import getCurrentConfig from '@/config/env'; +import { UserInfo } from "@/components/UserInfo"; +import { API_CONFIG } from "@/config/api"; +import httpService, { ApiResponse } from "./httpService"; +import uploadFiles from "./uploadFiles"; +import Taro from "@tarojs/taro"; +import getCurrentConfig from "@/config/env"; import { clear_login_state } from "@/services/loginService"; - // 用户详情接口 interface UserDetailData { id: number; @@ -33,7 +32,7 @@ interface UserDetailData { personal_profile: string; occupation: string; birthday: string; - ntrp_level: string, + ntrp_level: string; stats: { followers_count: number; following_count: number; @@ -55,27 +54,42 @@ export interface Profession { // 用户详细信息接口(从 loginService 移过来) export interface UserInfoType { - id: number - openid: string - unionid: string - session_key: string - nickname: string - avatar_url: string - gender: string - country: string - province: string - city: string - district: string - language: string - phone: string - is_subscribed: string - latitude: number - longitude: number - subscribe_time: string - last_login_time: string + id: number; + openid: string; + unionid: string; + session_key: string; + nickname: string; + avatar_url: string; + gender: string; + country: string; + province: string; + city: string; + district: string; + language: string; + phone: string; + is_subscribed: string; + latitude: number; + longitude: number; + subscribe_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 { id: number; @@ -138,37 +152,43 @@ const formatOptions = (data: Profession[]): PickerOption[] => { const itm: PickerOption = { text, value: text, - children: children ? formatOptions(children) : [] - } + children: children ? formatOptions(children) : [], + }; if (!itm.children!.length) { - delete itm.children + delete itm.children; } - return itm - }) -} + return itm; + }); +}; // 用户服务类 export class UserService { // 数据转换函数:将后端数据转换为ListContainer期望的格式 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); // 处理图片数组 - 兼容两种数据格式 let images: string[] = []; 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) { images = game.venue_image_list - .filter(img => img && img.url && img.url.trim() !== '') - .map(img => img.url); + .filter((img) => img && img.url && img.url.trim() !== "") + .map((img) => img.url); } // 处理距离 - 优先使用venue_dtl中的坐标,其次使用game中的坐标 - let latitude: number = 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; + let latitude: number = + 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) { latitude = parseFloat(game.venue_dtl.latitude) || latitude; longitude = parseFloat(game.venue_dtl.longitude) || longitude; @@ -176,33 +196,34 @@ export class UserService { const distance = this.calculate_distance(latitude, longitude); // 处理地点信息 - 优先使用venue_dtl中的信息 - let location = game.location_name || game.location || '未知地点'; + let location = game.location_name || game.location || "未知地点"; if (game.venue_dtl && 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; // 转换为 ListCard 期望的格式 return { id: game.id, - title: game.title || '未命名球局', + title: game.title || "未命名球局", start_time: date_time, original_start_time: game.start_time, - end_time: game.end_time || '', + end_time: game.end_time || "", location: location, - distance_km: parseFloat(distance.replace('km', '')) || 0, + distance_km: parseFloat(distance.replace("km", "")) || 0, current_players: registered_count, max_players: max_count, skill_level_min: parseInt(game.skill_level_min) || 0, skill_level_max: parseInt(game.skill_level_max) || 0, - play_type: game.play_type || '不限', + play_type: game.play_type || "不限", image_list: images, - court_type: game.court_type || '未知', - matchType: game.play_type || '不限', - shinei: game.court_type || '未知', + court_type: game.court_type || "未知", + matchType: game.play_type || "不限", + shinei: game.court_type || "未知", participants: game.participants || [], }; }); @@ -210,8 +231,12 @@ export class UserService { private static is_date_in_this_week(date: Date): boolean { const today = new Date(); - const firstDayOfWeek = new Date(today.setDate(today.getDate() - today.getDay())); - const lastDayOfWeek = new Date(firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 6)); + const firstDayOfWeek = new Date( + today.setDate(today.getDate() - today.getDay()) + ); + const lastDayOfWeek = new Date( + firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 6) + ); return date >= firstDayOfWeek && date <= lastDayOfWeek; } @@ -219,17 +244,27 @@ export class UserService { // 格式化时间显示 private static format_date_time(start_time: Date): string { 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 day_after_tomorrow = new Date(today.getTime() + 2 * 24 * 60 * 60 * 1000); - const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + const day_after_tomorrow = new Date( + 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()]; - let date_str = ''; + let date_str = ""; if (start_date.getTime() === today.getTime()) { - date_str = '今天'; + date_str = "今天"; } else if (start_date.getTime() === tomorrow.getTime()) { date_str = `明天(${weekday})`; } 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)) { date_str = weekday; } 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}`; } // 计算距离(模拟实现,实际需要根据用户位置计算) - private static calculate_distance(latitude: number, longitude: number): string { + private static calculate_distance( + latitude: number, + longitude: number + ): string { 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)]; } // 获取用户信息 static async get_user_info(user_id?: string): Promise { try { - const response = await httpService.post(API_CONFIG.USER.DETAIL, user_id ? { user_id } : {}, { - - showLoading: false - }); + const response = await httpService.post( + API_CONFIG.USER.DETAIL, + user_id ? { user_id } : {}, + { + showLoading: false, + } + ); if (response.code === 0) { const userData = response.data; return { - id: userData.id || '', - nickname: userData.nickname || '', - avatar: userData.avatar_url || '', - join_date: userData.subscribe_time ? `${new Date(userData.subscribe_time).getFullYear()}年${new Date(userData.subscribe_time).getMonth() + 1}月加入` : '', + id: userData.id || "", + nickname: userData.nickname || "", + avatar: userData.avatar_url || "", + join_date: userData.subscribe_time + ? `${new Date(userData.subscribe_time).getFullYear()}年${ + new Date(userData.subscribe_time).getMonth() + 1 + }月加入` + : "", stats: { following: userData.stats?.following_count || 0, friends: userData.stats?.followers_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 || '', - occupation: userData.occupation || '', - ntrp_level: userData.ntrp_level || '', - phone: userData.phone || '', - gender: userData.gender || '', - birthday: userData.birthday || '', - country: userData.country || '', - province: userData.province || '', - city: userData.city || '', + personal_profile: userData.personal_profile || "", + occupation: userData.occupation || "", + ntrp_level: userData.ntrp_level || "", + phone: userData.phone || "", + gender: userData.gender || "", + birthday: userData.birthday || "", + country: userData.country || "", + province: userData.province || "", + city: userData.city || "", }; } else { - throw new Error(response.message || '获取用户信息失败'); + throw new Error(response.message || "获取用户信息失败"); } } 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 = {}; - Object.keys(update_data).forEach(key => { + Object.keys(update_data).forEach((key) => { const value = update_data[key as keyof UserInfo]; // 只添加非空且非空字符串的字段 - if (value !== null && value !== undefined && value !== '') { - if (typeof value === 'string' && value.trim() !== '') { + if (value !== null && value !== undefined && value !== "") { + if (typeof value === "string" && value.trim() !== "") { filtered_data[key] = value.trim(); - } else if (typeof value !== 'string') { + } else if (typeof value !== "string") { filtered_data[key] = value; } } }); // 如果没有需要更新的字段,直接返回 if (Object.keys(filtered_data).length === 0) { - console.log('没有需要更新的字段'); + console.log("没有需要更新的字段"); return; } - const response = await httpService.post(API_CONFIG.USER.UPDATE, filtered_data, { - showLoading: true - }); + const response = await httpService.post( + API_CONFIG.USER.UPDATE, + filtered_data, + { + showLoading: true, + } + ); if (response.code !== 0) { - throw new Error(response.message || '更新用户信息失败'); + throw new Error(response.message || "更新用户信息失败"); } } catch (error) { - console.error('更新用户信息失败:', error); + console.error("更新用户信息失败:", error); throw error; } } @@ -336,53 +396,61 @@ export class UserService { // 获取用户主办的球局 static async get_hosted_games(userId: string | number): Promise { try { - const response = await httpService.post(API_CONFIG.USER.HOSTED_GAMES, { - userId - }, { - - showLoading: false - }); + const response = await httpService.post( + API_CONFIG.USER.HOSTED_GAMES, + { + userId, + }, + { + showLoading: false, + } + ); if (response.code === 0) { // 使用数据转换函数将后端数据转换为ListContainer期望的格式 return this.transform_game_data(response.data.rows || []); } else { - throw new Error(response.message || '获取主办球局失败'); + throw new Error(response.message || "获取主办球局失败"); } } catch (error) { - console.error('获取主办球局失败:', error); + console.error("获取主办球局失败:", error); // 返回符合ListContainer data格式的模拟数据 - return [] + return []; } } // 获取用户参与的球局 static async get_participated_games(userId: string | number): Promise { try { - const response = await httpService.post(API_CONFIG.USER.PARTICIPATED_GAMES, { - userId - }, { - - showLoading: false - }); + const response = await httpService.post( + API_CONFIG.USER.PARTICIPATED_GAMES, + { + userId, + }, + { + showLoading: false, + } + ); if (response.code === 0) { // 使用数据转换函数将后端数据转换为ListContainer期望的格式 return this.transform_game_data(response.data.rows || []); } else { - throw new Error(response.message || '获取参与球局失败'); + throw new Error(response.message || "获取参与球局失败"); } } catch (error) { - console.error('获取参与球局失败:', error); + console.error("获取参与球局失败:", error); // 返回符合ListContainer data格式的模拟数据 return []; - } } // 获取用户球局记录(兼容旧方法) - static async get_user_games(user_id: string | number, type: 'hosted' | 'participated'): Promise { - if (type === 'hosted') { + static async get_user_games( + user_id: string | number, + type: "hosted" | "participated" + ): Promise { + if (type === "hosted") { return this.get_hosted_games(user_id); } else { 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 { + static async toggle_follow( + following_id: string | number, + is_following: boolean + ): Promise { try { - const endpoint = is_following ? API_CONFIG.USER.UNFOLLOW : API_CONFIG.USER.FOLLOW; - const response = await httpService.post(endpoint, { following_id }, { - - showLoading: true, - loadingText: is_following ? '取消关注中...' : '关注中...' - }); + const endpoint = is_following + ? API_CONFIG.USER.UNFOLLOW + : API_CONFIG.USER.FOLLOW; + const response = await httpService.post( + endpoint, + { following_id }, + { + showLoading: true, + loadingText: is_following ? "取消关注中..." : "关注中...", + } + ); if (response.code === 0) { return !is_following; } else { - throw new Error(response.message || '操作失败'); + throw new Error(response.message || "操作失败"); } } catch (error) { - console.error('关注操作失败:', error); + console.error("关注操作失败:", error); throw error; } } // 保存用户信息 - static async save_user_info(user_info: Partial & { phone?: string; gender?: string }): Promise { + static async save_user_info( + user_info: Partial & { phone?: string; gender?: string } + ): Promise { try { // 字段映射配置 const field_mapping: Record = { - nickname: 'nickname', - avatar: 'avatar_url', - gender: 'gender', - phone: 'phone', - latitude: 'latitude', - longitude: 'longitude', - province: 'province', + nickname: "nickname", + avatar: "avatar_url", + gender: "gender", + phone: "phone", + latitude: "latitude", + longitude: "longitude", + province: "province", country: "country", city: "city", - personal_profile: 'personal_profile', - occupation: 'occupation', - ntrp_level: 'ntrp_level' + personal_profile: "personal_profile", + occupation: "occupation", + ntrp_level: "ntrp_level", }; // 构建更新参数,只包含非空字段 const updateParams: Record = {}; // 循环处理所有字段 - Object.keys(field_mapping).forEach(key => { + Object.keys(field_mapping).forEach((key) => { 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(); } }); - // 如果没有需要更新的字段,直接返回成功 if (Object.keys(updateParams).length === 0) { - console.log('没有需要更新的字段'); + console.log("没有需要更新的字段"); return true; } - const response = await httpService.post(API_CONFIG.USER.UPDATE, updateParams, { - showLoading: true, - loadingText: '保存中...' - }); + const response = await httpService.post( + API_CONFIG.USER.UPDATE, + updateParams, + { + showLoading: true, + loadingText: "保存中...", + } + ); if (response.code === 0) { return true; } else { - throw new Error(response.message || '更新用户信息失败'); + throw new Error(response.message || "更新用户信息失败"); } } catch (error) { - console.error('保存用户信息失败:', error); + console.error("保存用户信息失败:", error); throw error; } } // 获取用户动态 - static async get_user_activities(user_id: string, page: number = 1, limit: number = 10): Promise { + static async get_user_activities( + user_id: string, + page: number = 1, + limit: number = 10 + ): Promise { try { - const response = await httpService.post('/user/activities', { - user_id, - page, - limit - }, { - - showLoading: false - }); + const response = await httpService.post( + "/user/activities", + { + user_id, + page, + limit, + }, + { + showLoading: false, + } + ); if (response.code === 0) { return response.data.activities || []; } else { - throw new Error(response.message || '获取用户动态失败'); + throw new Error(response.message || "获取用户动态失败"); } } catch (error) { - console.error('获取用户动态失败:', error); + console.error("获取用户动态失败:", error); return []; } } @@ -490,34 +578,38 @@ export class UserService { static async upload_avatar(file_path: string): Promise { 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字段 return result.ossPath; } 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 { try { - const response = await httpService.post<{ phone: string }>(API_CONFIG.USER.PARSE_PHONE, { phone_code }, { - showLoading: true, - loadingText: '获取手机号中...' - }); + 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 || ''; + return response.data.phone || ""; } else { - throw new Error(response.message || '获取手机号失败'); + throw new Error(response.message || "获取手机号失败"); } } catch (error) { - console.error('获取手机号失败:', error); - return ''; + console.error("获取手机号失败:", error); + return ""; } } @@ -529,10 +621,10 @@ export class UserService { if (code === 0) { return formatOptions(data || []); } else { - throw new Error(message || '获取职业树失败'); + throw new Error(message || "获取职业树失败"); } } catch (error) { - console.error('获取职业树失败:', error); + console.error("获取职业树失败:", error); return []; } } @@ -545,10 +637,10 @@ export class UserService { if (code === 0) { return formatOptions(data || []); } else { - throw new Error(message || '获取城市树失败'); + throw new Error(message || "获取城市树失败"); } } catch (error) { - console.error('获取职业树失败:', error); + console.error("获取职业树失败:", error); return []; } } @@ -566,10 +658,10 @@ export class UserService { url: "/login_pages/index/index", }); } else { - throw new Error(message || '注销账户失败'); + throw new Error(message || "注销账户失败"); } } catch (error) { - console.error('注销账户失败:', error); + console.error("注销账户失败:", error); } } } @@ -577,12 +669,14 @@ export class UserService { // 从 loginService 移过来的用户相关方法 // 获取用户详细信息 -export const fetchUserProfile = async (): Promise> => { +export const fetchUserProfile = async (): Promise< + ApiResponse +> => { try { - const response = await httpService.post('user/detail'); + const response = await httpService.post("user/detail"); return response; } catch (error) { - console.error('获取用户信息失败:', error); + console.error("获取用户信息失败:", error); throw error; } }; @@ -590,24 +684,27 @@ export const fetchUserProfile = async (): Promise> => // 更新用户信息 export const updateUserProfile = async (payload: Partial) => { try { - const response = await httpService.post('/user/update', payload); + const response = await httpService.post("/user/update", payload); return response; } catch (error) { - console.error('更新用户信息失败:', error); + console.error("更新用户信息失败:", error); throw error; } }; // 更新用户坐标位置 -export const updateUserLocation = async (latitude: number, longitude: number) => { +export const updateUserLocation = async ( + latitude: number, + longitude: number +) => { try { - const response = await httpService.post('/user/update_location', { + const response = await httpService.post("/user/update_location", { latitude, longitude, }); return response; } catch (error) { - console.error('更新用户坐标位置失败:', error); + console.error("更新用户坐标位置失败:", error); throw error; } }; @@ -615,9 +712,9 @@ export const updateUserLocation = async (latitude: number, longitude: number) => // 获取用户信息(从本地存储) export const get_user_info = (): any | null => { try { - let userinfo = Taro.getStorageSync('user_info') + let userinfo = Taro.getStorageSync("user_info"); if (userinfo) { - return JSON.parse(userinfo) + return JSON.parse(userinfo); } return null; } catch (error) { @@ -632,25 +729,25 @@ export const handleCustomerService = async (): Promise => { const config = getCurrentConfig; const { customerService } = config; - console.log('打开客服中心,配置信息:', customerService); + console.log("打开客服中心,配置信息:", customerService); // 使用微信官方客服能力 await Taro.openCustomerServiceChat({ extInfo: { - url: customerService.serviceUrl + url: customerService.serviceUrl, }, corpId: customerService.corpId, success: (res) => { - console.log('打开客服成功:', res); + console.log("打开客服成功:", res); }, fail: (error) => { - console.error('打开客服失败:', error); + console.error("打开客服失败:", error); // 如果官方客服不可用,显示备用联系方式 showCustomerServiceFallback(customerService); - } + }, }); } catch (error) { - console.error('客服功能异常:', error); + console.error("客服功能异常:", error); // 备用方案:显示联系信息 showCustomerServiceFallback(); } @@ -658,14 +755,14 @@ export const handleCustomerService = async (): Promise => { // 客服备用方案 const showCustomerServiceFallback = (customerInfo?: any) => { - const options = ['拨打客服电话', '复制邮箱地址']; + const options = ["拨打客服电话", "复制邮箱地址"]; // 如果没有客服信息,只显示通用提示 if (!customerInfo?.phoneNumber && !customerInfo?.email) { Taro.showModal({ - title: '联系客服', - content: '如需帮助,请通过其他方式联系我们', - showCancel: false + title: "联系客服", + content: "如需帮助,请通过其他方式联系我们", + showCancel: false, }); return; } @@ -677,33 +774,33 @@ const showCustomerServiceFallback = (customerInfo?: any) => { // 拨打客服电话 try { await Taro.makePhoneCall({ - phoneNumber: customerInfo.phoneNumber + phoneNumber: customerInfo.phoneNumber, }); } catch (error) { - console.error('拨打电话失败:', error); + console.error("拨打电话失败:", error); Taro.showToast({ - title: '拨打电话失败', - icon: 'none' + title: "拨打电话失败", + icon: "none", }); } } else if (res.tapIndex === 1 && customerInfo?.email) { // 复制邮箱地址 try { await Taro.setClipboardData({ - data: customerInfo.email + data: customerInfo.email, }); Taro.showToast({ - title: '邮箱地址已复制', - icon: 'success' + title: "邮箱地址已复制", + icon: "success", }); } catch (error) { - console.error('复制邮箱失败:', error); + console.error("复制邮箱失败:", error); Taro.showToast({ - title: '复制失败', - icon: 'none' + title: "复制失败", + icon: "none", }); } } - } + }, }); }; diff --git a/src/store/userStore.ts b/src/store/userStore.ts index 70c4ca6..005af59 100644 --- a/src/store/userStore.ts +++ b/src/store/userStore.ts @@ -11,18 +11,31 @@ export interface UserState { updateUserInfo: (userInfo: Partial) => void; } +const fetchUserInfo = async (set) => { + try { + const res = await fetchUserProfile(); + set({ user: res.data }); + return res.data; + } catch {} +}; + export const useUser = create()((set) => ({ user: {}, - fetchUserInfo: async () => { - try { - const res = await fetchUserProfile(); - set({ user: res.data }); - return res.data - } catch { } - }, + fetchUserInfo: fetchUserInfo.bind(null, set), updateUserInfo: async (userInfo: Partial) => { - const res = await updateUserProfile(userInfo); - set({ user: res.data }); + try { + // 先更新后端 + await updateUserProfile(userInfo); + // 然后立即更新本地状态 + set((state) => ({ + user: { ...state.user, ...userInfo }, + })); + // 最后重新获取完整用户信息确保数据一致性 + await fetchUserInfo(set); + } catch (error) { + console.error("更新用户信息失败:", error); + throw error; + } }, })); diff --git a/src/user_pages/edit/index.tsx b/src/user_pages/edit/index.tsx index 634ca43..dd10a6d 100644 --- a/src/user_pages/edit/index.tsx +++ b/src/user_pages/edit/index.tsx @@ -3,53 +3,36 @@ import { View, Text, Image, ScrollView, Button } from "@tarojs/components"; import { PopupPicker } from "@/components/Picker/index"; import Taro from "@tarojs/taro"; import "./index.scss"; -import { UserInfo } from "@/components/UserInfo"; import { UserService, PickerOption } from "@/services/userService"; import { clear_login_state } from "@/services/loginService"; import { convert_db_gender_to_display } from "@/utils/genderUtils"; import { EditModal } from "@/components"; import img from "@/config/images"; import CommonDialog from "@/components/CommonDialog"; +import { useUserActions, useUserInfo } from "@/store/userStore"; +import { UserInfoType } from "@/services/userService"; const EditProfilePage: React.FC = () => { - // 用户信息状态 - const [user_info, setUserInfo] = useState({ - id: "1", - 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: "", - }); + const { updateUserInfo } = useUserActions(); + // 直接从store获取用户信息,确保响应式更新 + const user_info = useUserInfo(); - // 表单状态 + // 表单状态,基于store中的用户信息初始化 const [form_data, setFormData] = useState({ - nickname: "", - personal_profile: "", - occupation: "", - ntrp_level: "4.0", - phone: "", - gender: "", - birthday: "2000-01-01", - country: "", - province: "", - city: "", + 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 || "", }); // 加载状态 - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [showLogoutDialog, setShowLogoutDialog] = useState(false); // 编辑弹窗状态 @@ -68,11 +51,28 @@ const EditProfilePage: React.FC = () => { // 城市数据 const [cities, setCities] = useState([]); + // 监听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(() => { - load_user_info(); - getProfessions(); getCities(); + getProfessions(); }, []); const getProfessions = async () => { @@ -93,34 +93,34 @@ const EditProfilePage: React.FC = () => { }; // 加载用户信息 - const load_user_info = async () => { - try { - setLoading(true); - const user_data = await UserService.get_user_info(); - setUserInfo(user_data); - setFormData({ - nickname: user_data.nickname || "", - personal_profile: user_data.personal_profile || "", - occupation: user_data.occupation || "", - ntrp_level: user_data.ntrp_level || "NTRP 4.0", - phone: user_data.phone || "", - gender: user_data.gender || "", - birthday: user_data.birthday || "", - country: user_data.country || "", - province: user_data.province || "", - city: user_data.city || "", - }); - } catch (error) { - console.error("加载用户信息失败:", error); - Taro.showToast({ - title: "加载用户信息失败", - icon: "error", - duration: 2000, - }); - } finally { - setLoading(false); - } - }; + // const load_user_info = async () => { + // try { + // setLoading(true); + // const user_data = await UserService.get_user_info(); + // setUserInfo(user_data); + // setFormData({ + // nickname: user_data.nickname || "", + // personal_profile: user_data.personal_profile || "", + // occupation: user_data.occupation || "", + // ntrp_level: user_data.ntrp_level || "NTRP 4.0", + // phone: user_data.phone || "", + // gender: user_data.gender || "", + // birthday: user_data.birthday || "", + // country: user_data.country || "", + // province: user_data.province || "", + // city: user_data.city || "", + // }); + // } catch (error) { + // console.error("加载用户信息失败:", error); + // Taro.showToast({ + // title: "加载用户信息失败", + // icon: "error", + // duration: 2000, + // }); + // } finally { + // setLoading(false); + // } + // }; // 处理头像上传 const handle_avatar_upload = () => { @@ -184,11 +184,10 @@ const EditProfilePage: React.FC = () => { try { // 调用更新用户信息接口,只传递修改的字段 const update_data = { [editing_field]: value }; - await UserService.update_user_info(update_data); + await updateUserInfo(update_data); - // 更新本地状态 + // 更新表单状态(store会自动更新) setFormData((prev) => ({ ...prev, [editing_field]: value })); - setUserInfo((prev) => ({ ...prev, [editing_field]: value })); // 关闭弹窗 setEditModalVisible(false); @@ -224,20 +223,18 @@ const EditProfilePage: React.FC = () => { field !== null && !Array.isArray(field) ) { - await UserService.update_user_info({ ...field }); - // 更新本地状态 + await updateUserInfo({ ...field }); + + // 更新表单状态(store会自动更新) setFormData((prev) => ({ ...prev, ...field })); - setUserInfo((prev) => ({ ...prev, ...field })); } else { // 调用更新用户信息接口,只传递修改的字段 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 })); - setUserInfo((prev) => ({ ...prev, [field as string]: value })); } - // 显示成功提示 Taro.showToast({ title: "保存成功", @@ -364,7 +361,7 @@ const EditProfilePage: React.FC = () => { @@ -393,7 +390,7 @@ const EditProfilePage: React.FC = () => { - {form_data.nickname || "188的王晨"} + {form_data.nickname || ""} { {/* 生日选择弹窗 */} {birthday_picker_visible && ( { ], ]} type="ntrp" - img={user_info.avatar} + img={(user_info as UserInfoType)?.avatar_url} visible={ntrp_picker_visible} setvisible={setNtrpPickerVisible} value={[form_data.ntrp_level]} diff --git a/src/user_pages/myself/index.tsx b/src/user_pages/myself/index.tsx index ee4cfff..e8c8ad9 100644 --- a/src/user_pages/myself/index.tsx +++ b/src/user_pages/myself/index.tsx @@ -3,12 +3,14 @@ import { View, Text, Image, ScrollView } from "@tarojs/components"; import Taro, { useDidShow } from "@tarojs/taro"; import "./index.scss"; import GuideBar from "@/components/GuideBar"; -import { UserInfoCard, UserInfo } from "@/components/UserInfo/index"; -import { UserService } from "@/services/userService"; +import { UserInfoCard } from "@/components/UserInfo/index"; +import { UserService, UserInfo } from "@/services/userService"; import ListContainer from "@/container/listContainer"; import { TennisMatch } from "@/../types/list/types"; import { withAuth, NTRPTestEntryCard } from "@/components"; import { EvaluateScene } from "@/store/evaluateStore"; +import { useUserInfo } from "@/store/userStore"; +import { UserInfoType } from "@/services/userService"; const MyselfPage: React.FC = () => { // 获取页面参数 @@ -18,33 +20,14 @@ const MyselfPage: React.FC = () => { // 判断是否为当前用户 const is_current_user = !user_id; - // 用户信息状态 - const [user_info, set_user_info] = useState({ - 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: "", - }); + // 直接从store获取用户信息,确保响应式更新 + const user_info = useUserInfo(); // 球局记录状态 const [game_records, set_game_records] = useState([]); // 往期球局 const [ended_game_records, setEndedGameRecords] = useState([]); - const [loading, set_loading] = useState(true); + const [loading, set_loading] = useState(false); // 关注状态 const [is_following, setIsFollowing] = useState(false); @@ -55,37 +38,37 @@ const MyselfPage: React.FC = () => { ); // 加载用户数据 - const load_user_data = async () => { - try { - set_loading(true); + // const load_user_data = async () => { + // try { + // set_loading(true); - // 获取用户信息(包含统计数据) - const user_data = await UserService.get_user_info(); - set_user_info(user_data); - // let games_data; - // if (active_tab === "hosted") { - // games_data = await UserService.get_hosted_games(user_id); - // } else { - // 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); - } - }; + // // 获取用户信息(包含统计数据) + // const user_data = await UserService.get_user_info(); + // set_user_info(user_data); + // // let games_data; + // // if (active_tab === "hosted") { + // // games_data = await UserService.get_hosted_games(user_id); + // // } else { + // // 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); + // } + // }; - useEffect(() => { - if (user_info.id) { - load_game_data(); // 在 user_info 更新后调用 - } - }, [user_info]); + // useEffect(() => { + // if (user_info.id) { + // load_game_data(); // 在 user_info 更新后调用 + // } + // }, [user_info]); // 页面加载时获取数据 // useEffect(() => { @@ -93,7 +76,7 @@ const MyselfPage: React.FC = () => { // }, [user_id]); useDidShow(() => { - load_user_data(); // 确保从编辑页面返回时刷新数据 + // set_user_info(useUserInfo()); // 确保从编辑页面返回时刷新数据 }); // 切换标签页时重新加载球局数据 @@ -196,19 +179,12 @@ const MyselfPage: React.FC = () => { {/* 用户信息区域 */} - {loading ? ( - - 加载中... - - ) : ( - - )} + {/* 球局订单和收藏功能 */} diff --git a/src/user_pages/other/index.tsx b/src/user_pages/other/index.tsx index 360fbf7..aae0f21 100644 --- a/src/user_pages/other/index.tsx +++ b/src/user_pages/other/index.tsx @@ -9,10 +9,10 @@ import { UserInfoCard, // GameCard, GameTabs, - UserInfo, + // UserInfo, GameRecord, } from "@/components/UserInfo"; -import { UserService } from "@/services/userService"; +import { UserService, UserInfoType } from "@/services/userService"; import * as LoginService from "@/services/loginService"; const OtherUserPage: React.FC = () => { @@ -21,21 +21,22 @@ const OtherUserPage: React.FC = () => { const user_id = instance.router?.params?.userid; // 模拟用户数据 - const [user_info, setUserInfo] = useState({ - id: user_id || "1", + const [user_info, setUserInfo] = useState>({ + id: parseInt(user_id || "1") || 1, gender: "", nickname: "网球爱好者", - avatar: require("@/static/userInfo/default_avatar.svg"), + avatar_url: require("@/static/userInfo/default_avatar.svg"), join_date: "2024年3月加入", stats: { - following: 0, - friends: 0, - hosted: 0, - participated: 0, + following_count: 0, + followers_count: 0, + hosted_games_count: 0, + participated_games_count: 0, }, tags: ["北京朝阳", "金融从业者", "NTRP 3.5"], bio: "热爱网球的金融从业者,周末喜欢约球\n技术还在提升中,欢迎一起切磋\n平时在朝阳公园附近活动", - location: "北京朝阳", + city: "北京", + district: "朝阳", occupation: "金融从业者", ntrp_level: "NTRP 3.5", is_following: false, @@ -68,22 +69,26 @@ const OtherUserPage: React.FC = () => { const { data: userData } = res; // setUserInfo({...res.data as UserInfo, avatar: data.avatar_url || require("@/static/userInfo/default_avatar.svg")}); setUserInfo({ - id: user_id || "", + id: parseInt(user_id || "") || 0, nickname: userData.nickname || "", - avatar: userData.avatar_url || "", + avatar_url: userData.avatar_url || "", 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: { - following: userData.stats?.following_count || 0, - friends: userData.stats?.followers_count || 0, - hosted: userData.stats?.hosted_games_count || 0, - participated: userData.stats?.participated_games_count || 0, + following_count: userData.stats?.following_count || 0, + followers_count: userData.stats?.followers_count || 0, + hosted_games_count: userData.stats?.hosted_games_count || 0, + participated_games_count: + userData.stats?.participated_games_count || 0, }, personal_profile: userData.personal_profile || "", - location: userData.city + userData.district || "", + province: userData.province || "", + city: userData.city || "", + district: userData.district || "", occupation: userData.occupation || "", ntrp_level: "", phone: userData.phone || "", @@ -132,7 +137,10 @@ const OtherUserPage: React.FC = () => { active_tab ); 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); setGameRecords(notEndGames); @@ -157,7 +165,7 @@ const OtherUserPage: React.FC = () => { const handle_follow = async () => { try { const new_follow_status = await UserService.toggle_follow( - user_info.id, + user_info.id || "", is_following ); setIsFollowing(new_follow_status); @@ -178,7 +186,9 @@ const OtherUserPage: React.FC = () => { // 处理发送消息 const handle_send_message = () => { 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 || ""}`, }); }; @@ -244,7 +254,7 @@ const OtherUserPage: React.FC = () => { error={null} errorImg="ICON_LIST_EMPTY" emptyText="暂无消息,去互动看看吧" - loadMoreMatches={() => { }} + loadMoreMatches={() => {}} /> {/* */} @@ -280,7 +290,7 @@ const OtherUserPage: React.FC = () => { error={null} errorImg="ICON_LIST_EMPTY" emptyText="暂无消息,去互动看看吧" - loadMoreMatches={() => { }} + loadMoreMatches={() => {}} /> {/* */}