Merge remote-tracking branch origin into feature/juguohong/20250816
This commit is contained in:
@@ -20,6 +20,12 @@ export interface GameDetail {
|
||||
updated_at: string,
|
||||
}
|
||||
|
||||
export enum MATCH_STATUS {
|
||||
NOT_STARTED = 0, // 未开始
|
||||
IN_PROGRESS = 1, //进行中
|
||||
FINISHED = 2 //已结束
|
||||
}
|
||||
|
||||
// 响应接口
|
||||
export interface Response {
|
||||
code: string
|
||||
|
||||
@@ -353,7 +353,7 @@ export const refresh_login_status = async (): Promise<boolean> => {
|
||||
// 获取用户详细信息
|
||||
export const fetchUserProfile = async (): Promise<ApiResponse<UserInfoType>> => {
|
||||
try {
|
||||
const response = await httpService.post('/user/detail');
|
||||
const response = await httpService.post('user/detail');
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
@@ -364,7 +364,7 @@ export const fetchUserProfile = async (): Promise<ApiResponse<UserInfoType>> =>
|
||||
// 更新用户信息
|
||||
export const updateUserProfile = async (payload: Partial<UserInfoType>) => {
|
||||
try {
|
||||
const response = await httpService.post('user/update', payload);
|
||||
const response = await httpService.post('/user/update', payload);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('更新用户信息失败:', error);
|
||||
|
||||
46
src/services/orderService.ts
Normal file
46
src/services/orderService.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import httpService from './httpService'
|
||||
import type { ApiResponse } from './httpService'
|
||||
import { requestPayment } from '@tarojs/taro'
|
||||
|
||||
export interface SignType {
|
||||
/** 仅在微信支付 v2 版本接口适用 */
|
||||
MD5
|
||||
/** 仅在微信支付 v2 版本接口适用 */
|
||||
'HMAC-SHA256'
|
||||
/** 仅在微信支付 v3 版本接口适用 */
|
||||
RSA
|
||||
}
|
||||
|
||||
export interface PayMentParams {
|
||||
order_id: number,
|
||||
order_no: string,
|
||||
status: number,
|
||||
appId: string,
|
||||
timeStamp: string,
|
||||
nonceStr: string,
|
||||
package: string,
|
||||
signType: keyof SignType,
|
||||
paySign: string
|
||||
}
|
||||
|
||||
// 用户接口
|
||||
export interface OrderResponse {
|
||||
participant_id: number,
|
||||
payment_required: boolean,
|
||||
payment_params: PayMentParams
|
||||
}
|
||||
|
||||
// 发布球局类
|
||||
class OrderService {
|
||||
// 用户登录
|
||||
async createOrder(game_id: number): Promise<ApiResponse<OrderResponse>> {
|
||||
return httpService.post('/payment/create_order', { game_id }, {
|
||||
showLoading: true,
|
||||
})
|
||||
}
|
||||
|
||||
// async getOrderInfo()
|
||||
}
|
||||
|
||||
// 导出认证服务实例
|
||||
export default new OrderService()
|
||||
549
src/services/userService.ts
Normal file
549
src/services/userService.ts
Normal file
@@ -0,0 +1,549 @@
|
||||
import { UserInfo } from '@/components/UserInfo';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { API_CONFIG } from '@/config/api';
|
||||
import httpService from './httpService';
|
||||
|
||||
|
||||
// 用户详情接口
|
||||
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;
|
||||
language: string;
|
||||
phone: string;
|
||||
is_subscribed: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
subscribe_time: string;
|
||||
last_login_time: string;
|
||||
create_time: string;
|
||||
last_modify_time: string;
|
||||
stats: {
|
||||
followers_count: number;
|
||||
following_count: number;
|
||||
hosted_games_count: number;
|
||||
participated_games_count: number;
|
||||
};
|
||||
}
|
||||
|
||||
// 更新用户信息参数接口
|
||||
interface UpdateUserParams {
|
||||
nickname: string;
|
||||
avatar_url: string;
|
||||
gender: string;
|
||||
phone: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
city: string;
|
||||
province: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
// 上传响应接口
|
||||
interface UploadResponseData {
|
||||
create_time: string;
|
||||
last_modify_time: string;
|
||||
duration: string;
|
||||
thumbnail_url: string;
|
||||
view_count: string;
|
||||
download_count: string;
|
||||
is_delete: number;
|
||||
id: number;
|
||||
user_id: number;
|
||||
resource_type: string;
|
||||
file_name: string;
|
||||
original_name: string;
|
||||
file_path: string;
|
||||
file_url: string;
|
||||
file_size: number;
|
||||
mime_type: string;
|
||||
description: string;
|
||||
tags: string;
|
||||
is_public: string;
|
||||
width: number;
|
||||
height: number;
|
||||
uploadInfo: {
|
||||
success: boolean;
|
||||
name: string;
|
||||
path: string;
|
||||
ossPath: string;
|
||||
fileType: string;
|
||||
fileSize: number;
|
||||
originalName: string;
|
||||
suffix: string;
|
||||
storagePath: 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?: number;
|
||||
longitude?: number;
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
// 用户服务类
|
||||
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);
|
||||
const date_time = this.format_date_time(start_time);
|
||||
|
||||
// 处理技能等级
|
||||
const skill_level = this.format_skill_level(game.skill_level_min, game.skill_level_max);
|
||||
|
||||
// 处理图片数组 - 兼容两种数据格式
|
||||
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 = game.latitude || 0;
|
||||
let longitude = game.longitude || 0;
|
||||
if (game.venue_dtl) {
|
||||
latitude = parseFloat(game.venue_dtl.latitude) || latitude;
|
||||
longitude = parseFloat(game.venue_dtl.longitude) || longitude;
|
||||
}
|
||||
const distance = this.calculate_distance(latitude, 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;
|
||||
|
||||
return {
|
||||
id: game.id,
|
||||
title: game.title || '未命名球局',
|
||||
dateTime: date_time,
|
||||
location: location,
|
||||
distance: distance,
|
||||
registeredCount: registered_count,
|
||||
maxCount: max_count,
|
||||
skillLevel: skill_level,
|
||||
matchType: game.play_type || '不限',
|
||||
images: images,
|
||||
shinei: game.court_type || '未知'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化时间显示
|
||||
private static format_date_time(start_time: Date): string {
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), 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 start_date = new Date(start_time.getFullYear(), start_time.getMonth(), start_time.getDate());
|
||||
|
||||
let date_str = '';
|
||||
if (start_date.getTime() === today.getTime()) {
|
||||
date_str = '今天';
|
||||
} else if (start_date.getTime() === tomorrow.getTime()) {
|
||||
date_str = '明天';
|
||||
} else if (start_date.getTime() === day_after_tomorrow.getTime()) {
|
||||
date_str = '后天';
|
||||
} else {
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
date_str = weekdays[start_time.getDay()];
|
||||
}
|
||||
|
||||
const time_str = `${start_time.getHours().toString().padStart(2, '0')}:${start_time.getMinutes().toString().padStart(2, '0')}`;
|
||||
return `${date_str} ${time_str}`;
|
||||
}
|
||||
|
||||
// 格式化技能等级
|
||||
private static format_skill_level(min: string, max: string): string {
|
||||
const min_num = parseInt(min) || 0;
|
||||
const max_num = parseInt(max) || 0;
|
||||
|
||||
if (min_num === 0 && max_num === 0) {
|
||||
return '不限';
|
||||
}
|
||||
|
||||
if (min_num === max_num) {
|
||||
return `${min_num}.0`;
|
||||
}
|
||||
|
||||
return `${min_num}.0-${max_num}.0`;
|
||||
}
|
||||
|
||||
// 计算距离(模拟实现,实际需要根据用户位置计算)
|
||||
private static calculate_distance(latitude: number, longitude: number): string {
|
||||
if (latitude === 0 && longitude === 0) {
|
||||
return '未知距离';
|
||||
}
|
||||
|
||||
// 这里应该根据用户当前位置计算实际距离
|
||||
// 暂时返回模拟距离
|
||||
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<UserInfo> {
|
||||
try {
|
||||
const response = await httpService.post<UserDetailData>(API_CONFIG.USER.DETAIL, user_id ? { user_id } : {}, {
|
||||
needAuth: false,
|
||||
showLoading: false
|
||||
});
|
||||
|
||||
if (response.code === 0) {
|
||||
const userData = response.data;
|
||||
return {
|
||||
id: userData.user_code || user_id || '1',
|
||||
nickname: userData.nickname || '用户',
|
||||
avatar: userData.avatar_url || require('../static/userInfo/default_avatar.svg'),
|
||||
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
|
||||
},
|
||||
tags: [
|
||||
userData.city || '未知地区',
|
||||
userData.province || '未知省份',
|
||||
'NTRP 3.0' // 默认等级,需要其他接口获取
|
||||
],
|
||||
bio: '这个人很懒,什么都没有写...',
|
||||
location: userData.city || '未知地区',
|
||||
occupation: '未知职业', // 需要其他接口获取
|
||||
ntrp_level: 'NTRP 3.0', // 需要其他接口获取
|
||||
phone: userData.phone || '',
|
||||
gender: userData.gender || ''
|
||||
};
|
||||
} else {
|
||||
throw new Error(response.message || '获取用户信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
// 返回默认用户信息
|
||||
return {
|
||||
id: user_id || '1',
|
||||
nickname: '用户',
|
||||
avatar: require('../static/userInfo/default_avatar.svg'),
|
||||
join_date: '未知时间加入',
|
||||
stats: {
|
||||
following: 0,
|
||||
friends: 0,
|
||||
hosted: 0,
|
||||
participated: 0
|
||||
},
|
||||
tags: ['未知地区', '未知职业', 'NTRP 3.0'],
|
||||
bio: '这个人很懒,什么都没有写...',
|
||||
location: '未知地区',
|
||||
occupation: '未知职业',
|
||||
ntrp_level: 'NTRP 3.0',
|
||||
phone: '',
|
||||
gender: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户主办的球局
|
||||
static async get_hosted_games(user_id: string): Promise<any[]> {
|
||||
try {
|
||||
const response = await httpService.post<any>(API_CONFIG.USER.HOSTED_GAMES, {
|
||||
user_id
|
||||
}, {
|
||||
needAuth: false,
|
||||
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 [
|
||||
{
|
||||
id: 1,
|
||||
title: '女生轻松双打',
|
||||
dateTime: '明天(周五) 下午5点',
|
||||
location: '仁恒河滨花园网球场',
|
||||
distance: '3.5km',
|
||||
registeredCount: 2,
|
||||
maxCount: 4,
|
||||
skillLevel: '2.0-2.5',
|
||||
matchType: '双打',
|
||||
images: [
|
||||
require('../static/userInfo/game1.svg'),
|
||||
require('../static/userInfo/game2.svg'),
|
||||
require('../static/userInfo/game3.svg')
|
||||
],
|
||||
shinei: '室外'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '新手友好局',
|
||||
dateTime: '周日 下午2点',
|
||||
location: '徐汇网球中心',
|
||||
distance: '1.8km',
|
||||
registeredCount: 4,
|
||||
maxCount: 6,
|
||||
skillLevel: '1.5-2.0',
|
||||
matchType: '双打',
|
||||
images: [
|
||||
require('../static/userInfo/game1.svg'),
|
||||
require('../static/userInfo/game2.svg')
|
||||
],
|
||||
shinei: '室外'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户参与的球局
|
||||
static async get_participated_games(user_id: string): Promise<any[]> {
|
||||
try {
|
||||
const response = await httpService.post<any>(API_CONFIG.USER.PARTICIPATED_GAMES, {
|
||||
user_id
|
||||
}, {
|
||||
needAuth: false,
|
||||
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 [
|
||||
{
|
||||
id: 2,
|
||||
title: '周末双打练习',
|
||||
dateTime: '后天(周六) 上午10点',
|
||||
location: '上海网球中心',
|
||||
distance: '5.2km',
|
||||
registeredCount: 6,
|
||||
maxCount: 8,
|
||||
skillLevel: '3.0-3.5',
|
||||
matchType: '双打',
|
||||
images: [
|
||||
require('../static/userInfo/game2.svg'),
|
||||
require('../static/userInfo/game3.svg')
|
||||
],
|
||||
shinei: '室内'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '晨练单打',
|
||||
dateTime: '明天(周五) 早上7点',
|
||||
location: '浦东网球俱乐部',
|
||||
distance: '2.8km',
|
||||
registeredCount: 1,
|
||||
maxCount: 2,
|
||||
skillLevel: '2.5-3.0',
|
||||
matchType: '单打',
|
||||
images: [
|
||||
require('../static/userInfo/game1.svg')
|
||||
],
|
||||
shinei: '室外'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '夜场混双',
|
||||
dateTime: '今晚 晚上8点',
|
||||
location: '虹桥网球中心',
|
||||
distance: '4.1km',
|
||||
registeredCount: 3,
|
||||
maxCount: 4,
|
||||
skillLevel: '3.5-4.0',
|
||||
matchType: '混双',
|
||||
images: [
|
||||
require('../static/userInfo/game1.svg'),
|
||||
require('../static/userInfo/game2.svg'),
|
||||
require('../static/userInfo/game3.svg')
|
||||
],
|
||||
shinei: '室内'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户球局记录(兼容旧方法)
|
||||
static async get_user_games(user_id: string, type: 'hosted' | 'participated'): Promise<any[]> {
|
||||
if (type === 'hosted') {
|
||||
return this.get_hosted_games(user_id);
|
||||
} else {
|
||||
return this.get_participated_games(user_id);
|
||||
}
|
||||
}
|
||||
|
||||
// 关注/取消关注用户
|
||||
static async toggle_follow(user_id: string, is_following: boolean): Promise<boolean> {
|
||||
try {
|
||||
const endpoint = is_following ? API_CONFIG.USER.UNFOLLOW : API_CONFIG.USER.FOLLOW;
|
||||
const response = await httpService.post<any>(endpoint, { user_id }, {
|
||||
needAuth: false,
|
||||
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<UserInfo> & { phone?: string; gender?: string }): Promise<boolean> {
|
||||
try {
|
||||
// 获取当前位置信息
|
||||
const location = await Taro.getLocation({
|
||||
type: 'wgs84'
|
||||
});
|
||||
|
||||
const updateParams: UpdateUserParams = {
|
||||
nickname: user_info.nickname || '',
|
||||
avatar_url: user_info.avatar || '',
|
||||
gender: user_info.gender || '',
|
||||
phone: user_info.phone || '',
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
city: user_info.location || '',
|
||||
province: '', // 需要从用户信息中获取
|
||||
country: '' // 需要从用户信息中获取
|
||||
};
|
||||
|
||||
const response = await httpService.post<any>(API_CONFIG.USER.UPDATE, updateParams, {
|
||||
needAuth: false,
|
||||
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<any[]> {
|
||||
try {
|
||||
const response = await httpService.post<any>('/user/activities', {
|
||||
user_id,
|
||||
page,
|
||||
limit
|
||||
}, {
|
||||
needAuth: false,
|
||||
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<string> {
|
||||
try {
|
||||
// 先上传文件到服务器
|
||||
const uploadResponse = await Taro.uploadFile({
|
||||
url: `${API_CONFIG.BASE_URL}${API_CONFIG.UPLOAD.AVATAR}`,
|
||||
filePath: file_path,
|
||||
name: 'file'
|
||||
});
|
||||
|
||||
const result = JSON.parse(uploadResponse.data) as { code: number; message: string; data: UploadResponseData };
|
||||
if (result.code === 0) {
|
||||
// 使用新的响应格式中的file_url字段
|
||||
return result.data.file_url;
|
||||
} else {
|
||||
throw new Error(result.message || '头像上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('头像上传失败:', error);
|
||||
// 如果上传失败,返回默认头像
|
||||
return require('../static/userInfo/default_avatar.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user