import { UserInfo } from "@/components/UserInfo"; import { API_CONFIG } from "@/config/api"; import httpService, { ApiResponse } from "./httpService"; import uploadFiles from "./uploadFiles"; import * as Taro from "@tarojs/taro"; import getCurrentConfig from "@/config/env"; import { clear_login_state } from "@/services/loginService"; // 用户详情接口 interface UserDetailData { id: number; openid: string; user_code: string | null; 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: string; longitude: string; subscribe_time: string; last_login_time: string; create_time: string; last_modify_time: string; personal_profile: string; occupation: string; birthday: string; ntrp_level: string; last_location_province: string; last_location_city: string; stats: { followers_count: number; following_count: number; hosted_games_count: number; participated_games_count: number; }; } export interface PickerOption { text: string | number; value: string | number; children?: PickerOption[]; } export interface Profession { name: string; children: 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; 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; last_location_province?: string; last_location_city?: string; bio?: string; birthday?: string; is_following?: boolean; tags?: string[]; ongoing_games?: string[]; } export interface NicknameChangeStatus { can_change: boolean; remaining_count: number; period_start_time: string; next_period_start_time: string; days_until_next_period: number; next_period_start_date?: string; } // 后端球局数据接口 interface BackendGameData { id: number; title: string; description: string; game_type?: string; play_type: string; publisher_id?: string; venue_id?: string; max_players?: number; current_players?: number; price: string; price_mode: string; court_type: string; court_surface: string; gender_limit?: string; skill_level_min: string; skill_level_max: string; start_time: string; end_time: string; location_name: string | null; location: string; latitude?: string; longitude?: string; image_list?: string[]; description_tag?: string[]; venue_description_tag?: string[]; venue_image_list?: Array<{ id: string; url: string }>; participant_count: number; max_participants: number; participant_info?: { id: number; status: string; payment_status: string; joined_at: string; deposit_amount: number; join_message: string; skill_level: string; contact_info: string; }; venue_dtl?: { id: number; name: string; address: string; latitude: string; longitude: string; venue_type: string; surface_type: string; distance_km: string; }; participants: { user: { avatar_url: string; }; }[]; } const formatOptions = (data: Profession[]): PickerOption[] => { return data.map((item: Profession) => { const { name: text, children } = item; const itm: PickerOption = { text, value: text, children: children ? formatOptions(children) : [], }; if (!itm.children!.length) { delete itm.children; } return itm; }); }; // 用户服务类 export class UserService { // 数据转换函数:将后端数据转换为ListContainer期望的格式 private static transform_game_data(backend_data: BackendGameData[]): any[] { return backend_data.map((game) => { // 处理时间格式 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() !== ""); } 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); } // 处理距离 - 优先使用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; if (game.venue_dtl) { latitude = parseFloat(game.venue_dtl.latitude) || latitude; longitude = parseFloat(game.venue_dtl.longitude) || longitude; } // 处理地点信息 - 优先使用venue_dtl中的信息 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 max_count = game.max_players || game.max_participants || 0; // 转换为 ListCard 期望的格式 return { id: game.id, title: game.title || "未命名球局", start_time: date_time, original_start_time: game.start_time, end_time: game.end_time || "", location: location, distance_km: game.venue_dtl?.distance_km , 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 || "不限", image_list: images, court_type: game.court_type || "未知", matchType: game.play_type || "不限", shinei: game.court_type || "未知", participants: game.participants || [], }; }); } 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) ); return date >= firstDayOfWeek && date <= lastDayOfWeek; } // 格式化时间显示 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 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 start_date = new Date( start_time.getFullYear(), start_time.getMonth() + 1, start_time.getDate() ); const weekday = weekdays[start_time.getDay()]; let date_str = ""; if (start_date.getTime() === today.getTime()) { date_str = "今天"; } else if (start_date.getTime() === tomorrow.getTime()) { date_str = `明天(${weekday})`; } else if (start_date.getTime() === day_after_tomorrow.getTime()) { date_str = `后天(${weekday})`; } 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})`; } const time_str = `${start_time .getHours() .toString() .padStart(2, "0")}:${start_time .getMinutes() .toString() .padStart(2, "0")}`; return `${date_str} ${time_str}`; } // 获取用户信息 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, } ); 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 }月加入` : "", 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, }, 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 || "", last_location_province: userData.last_location_province || "", last_location_city: userData.last_location_city || "", }; } else { throw new Error(response.message || "获取用户信息失败"); } } catch (error) { console.error("获取用户信息失败:", error); // 返回默认用户信息 return {} as UserInfo; } } // 更新用户信息(简化版本,具体逻辑在 userStore 中处理) static async update_user_info(update_data: Partial): Promise { try { // 过滤掉空字段 const filtered_data: Record = {}; 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() !== "") { filtered_data[key] = value.trim(); } else if (typeof value !== "string") { filtered_data[key] = value; } } }); // 如果没有需要更新的字段,直接返回 if (Object.keys(filtered_data).length === 0) { console.log("没有需要更新的字段"); return; } const response = await httpService.post( API_CONFIG.USER.UPDATE, filtered_data, { showLoading: true, } ); if (response.code !== 0) { throw new Error(response.message || "更新用户信息失败"); } } catch (error) { console.error("更新用户信息失败:", error); throw error; } } // 获取用户主办的球局 static async get_hosted_games(userId: string | number): Promise { try { 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 || "获取主办球局失败"); } } catch (error) { console.error("获取主办球局失败:", error); // 返回符合ListContainer data格式的模拟数据 return []; } } // 获取用户参与的球局 static async get_participated_games(userId: string | number): Promise { try { 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 || "获取参与球局失败"); } } catch (error) { console.error("获取参与球局失败:", error); // 返回符合ListContainer data格式的模拟数据 return []; } } // 获取用户球局记录(兼容旧方法) 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); } } // 关注/取消关注用户 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 ? "取消关注中..." : "关注中...", } ); if (response.code === 0) { return !is_following; } else { throw new Error(response.message || "操作失败"); } } catch (error) { console.error("关注操作失败:", error); throw error; } } // 保存用户信息 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", country: "country", city: "city", personal_profile: "personal_profile", occupation: "occupation", ntrp_level: "ntrp_level", }; // 构建更新参数,只包含非空字段 const updateParams: Record = {}; // 循环处理所有字段 Object.keys(field_mapping).forEach((key) => { const value = user_info[key as keyof typeof user_info]; if (value && typeof value === "string" && value.trim() !== "") { updateParams[field_mapping[key]] = value.trim(); } }); // 如果没有需要更新的字段,直接返回成功 if (Object.keys(updateParams).length === 0) { console.log("没有需要更新的字段"); return true; } 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 || "更新用户信息失败"); } } catch (error) { console.error("保存用户信息失败:", error); throw error; } } // 获取用户动态 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, } ); if (response.code === 0) { return response.data.activities || []; } else { throw new Error(response.message || "获取用户动态失败"); } } catch (error) { console.error("获取用户动态失败:", error); return []; } } // 上传头像 static async upload_avatar(file_path: string): Promise { try { // 先上传文件到服务器 const result = await uploadFiles.upload_oss_img(file_path); await this.save_user_info({ avatar: result.ossPath }); // 使用新的响应格式中的file_url字段 return result.ossPath; } catch (error) { console.error("头像上传失败:", error); // 如果上传失败,返回默认头像 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: "获取手机号中...", } ); if (response.code === 0) { return response.data.phone || ""; } else { throw new Error(response.message || "获取手机号失败"); } } catch (error) { console.error("获取手机号失败:", error); return ""; } } // 获取职业树 static async getProfessions(): Promise<[] | PickerOption[]> { try { const response = await httpService.post(API_CONFIG.PROFESSIONS); const { code, data, message } = response; if (code === 0) { return formatOptions(data || []); } else { throw new Error(message || "获取职业树失败"); } } catch (error) { console.error("获取职业树失败:", error); return []; } } // 获取城市树 static async getCities(): Promise<[] | PickerOption[]> { try { const response = await httpService.post(API_CONFIG.CITIS); const { code, data, message } = response; if (code === 0) { return formatOptions(data || []); } else { throw new Error(message || "获取城市树失败"); } } catch (error) { console.error("获取职业树失败:", error); return []; } } // 注销账户 static async logout(): Promise { try { const response = await httpService.post(API_CONFIG.USER.LOGOUT); const { code, message } = response; if (code === 0) { // 清除用户数据 clear_login_state(); Taro.reLaunch({ url: "/login_pages/index/index", }); } else { throw new Error(message || "注销账户失败"); } } catch (error) { console.error("注销账户失败:", error); } } } // 从 loginService 移过来的用户相关方法 // 获取用户详细信息 export const fetchUserProfile = async (): Promise< ApiResponse > => { try { const response = await httpService.post("user/detail"); return response; } catch (error) { console.error("获取用户信息失败:", error); throw error; } }; // 获取昵称修改状态 export const checkNicknameChangeStatus = async (): Promise< ApiResponse > => { try { const response = await httpService.post( "/user/check_nickname_change_status" ); return response; } catch (error) { console.error("获取昵称修改状态失败:", error); throw error; } }; // 修改昵称 export const updateNickname = async (nickname: string) => { try { const response = await httpService.post("/user/update_nickname", { nickname, }); return response; } catch (error) { console.error("昵称修改失败:", error); throw error; } }; // 更新用户信息 export const updateUserProfile = async (payload: Partial) => { try { const response = await httpService.post("/user/update", payload); return response; } catch (error) { console.error("更新用户信息失败:", error); throw error; } }; // 更新用户坐标位置 export const updateUserLocation = async ( latitude: number, longitude: number, force: boolean = false ) => { try { const response = await httpService.post("/user/update_location", { latitude, longitude, force, }); return response; } catch (error) { console.error("更新用户坐标位置失败:", error); throw error; } }; // 获取用户信息(从本地存储) export const get_user_info = (): any | null => { try { let userinfo = Taro.getStorageSync("user_info"); if (userinfo) { return JSON.parse(userinfo); } return null; } catch (error) { return null; } }; // 客服中心处理函数 export const handleCustomerService = async (): Promise => { try { // 获取当前环境的客服配置 const config = getCurrentConfig; const { customerService } = config; console.log("打开客服中心,配置信息:", customerService); // 使用微信官方客服能力 await Taro.openCustomerServiceChat({ extInfo: { url: customerService.serviceUrl, }, corpId: customerService.corpId, success: (res) => { console.log("打开客服成功:", res); }, fail: (error) => { console.error("打开客服失败:", error); // 如果官方客服不可用,显示备用联系方式 showCustomerServiceFallback(customerService); }, }); } catch (error) { console.error("客服功能异常:", error); // 备用方案:显示联系信息 showCustomerServiceFallback(); } }; // 客服备用方案 const showCustomerServiceFallback = (customerInfo?: any) => { const options = ["拨打客服电话", "复制邮箱地址"]; // 如果没有客服信息,只显示通用提示 if (!customerInfo?.phoneNumber && !customerInfo?.email) { Taro.showModal({ title: "联系客服", content: "如需帮助,请通过其他方式联系我们", showCancel: false, }); return; } Taro.showActionSheet({ itemList: options, success: async (res) => { if (res.tapIndex === 0 && customerInfo?.phoneNumber) { // 拨打客服电话 try { await Taro.makePhoneCall({ phoneNumber: customerInfo.phoneNumber, }); } catch (error) { console.error("拨打电话失败:", error); Taro.showToast({ title: "拨打电话失败", icon: "none", }); } } else if (res.tapIndex === 1 && customerInfo?.email) { // 复制邮箱地址 try { await Taro.setClipboardData({ data: customerInfo.email, }); Taro.showToast({ title: "邮箱地址已复制", icon: "success", }); } catch (error) { console.error("复制邮箱失败:", error); Taro.showToast({ title: "复制失败", icon: "none", }); } } }, }); };