feat(challenges): 添加自定义挑战功能和多语言支持
- 新增自定义挑战创建页面,支持设置挑战类型、时间范围、目标值等 - 实现挑战邀请码系统,支持通过邀请码加入自定义挑战 - 完善挑战详情页面的多语言翻译支持 - 优化用户认证状态检查逻辑,使用token作为主要判断依据 - 添加阿里字体文件支持,提升UI显示效果 - 改进确认弹窗组件,支持Liquid Glass效果和自定义内容 - 优化应用启动流程,直接读取onboarding状态而非预加载用户数据
This commit is contained in:
@@ -2,11 +2,24 @@ import { api } from './api';
|
||||
|
||||
export type ChallengeStatus = 'upcoming' | 'ongoing' | 'expired';
|
||||
|
||||
export enum ChallengeSource {
|
||||
SYSTEM = 'system',
|
||||
CUSTOM = 'custom',
|
||||
}
|
||||
|
||||
export enum ChallengeState {
|
||||
DRAFT = 'draft',
|
||||
ACTIVE = 'active',
|
||||
ARCHIVED = 'archived',
|
||||
}
|
||||
|
||||
export type ChallengeProgressDto = {
|
||||
completed: number;
|
||||
target: number;
|
||||
remaining: number
|
||||
checkedInToday: boolean;
|
||||
lastProgressAt?: string;
|
||||
last_progress_at?: string;
|
||||
};
|
||||
|
||||
export type RankingItemDto = {
|
||||
@@ -38,7 +51,7 @@ export type ChallengeListItemDto = {
|
||||
durationLabel: string;
|
||||
requirementLabel: string;
|
||||
unit?: string;
|
||||
status: ChallengeStatus;
|
||||
status?: ChallengeStatus;
|
||||
participantsCount: number;
|
||||
rankingDescription?: string;
|
||||
highlightTitle: string;
|
||||
@@ -50,12 +63,23 @@ export type ChallengeListItemDto = {
|
||||
endAt?: string;
|
||||
minimumCheckInDays: number; // 最小打卡天数
|
||||
type: ChallengeType;
|
||||
shareCode?: string | null;
|
||||
source?: ChallengeSource;
|
||||
creatorId?: string | null;
|
||||
isCreator?: boolean;
|
||||
isPublic?: boolean;
|
||||
maxParticipants?: number | null;
|
||||
challengeState?: ChallengeState;
|
||||
progressUnit?: string;
|
||||
targetValue?: number;
|
||||
summary?: string | null;
|
||||
};
|
||||
|
||||
export type ChallengeDetailDto = ChallengeListItemDto & {
|
||||
summary?: string;
|
||||
rankings: RankingItemDto[];
|
||||
summary?: string | null;
|
||||
rankings?: RankingItemDto[];
|
||||
userRank?: number;
|
||||
challengeState?: ChallengeState;
|
||||
};
|
||||
|
||||
export type ChallengeRankingsDto = {
|
||||
@@ -65,6 +89,31 @@ export type ChallengeRankingsDto = {
|
||||
items: RankingItemDto[];
|
||||
};
|
||||
|
||||
export type ChallengeListResponse = {
|
||||
items: ChallengeListItemDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
|
||||
export type CreateCustomChallengePayload = {
|
||||
title: string;
|
||||
type: ChallengeType;
|
||||
image?: string | null;
|
||||
startAt: number;
|
||||
endAt: number;
|
||||
targetValue: number;
|
||||
minimumCheckInDays: number;
|
||||
durationLabel: string;
|
||||
requirementLabel: string;
|
||||
summary?: string | null;
|
||||
progressUnit?: string;
|
||||
periodLabel?: string | null;
|
||||
rankingDescription?: string | null;
|
||||
isPublic?: boolean;
|
||||
maxParticipants?: number | null;
|
||||
};
|
||||
|
||||
export async function listChallenges(): Promise<ChallengeListItemDto[]> {
|
||||
return api.get<ChallengeListItemDto[]>('/challenges');
|
||||
}
|
||||
@@ -101,3 +150,43 @@ export async function getChallengeRankings(
|
||||
const url = `/challenges/${encodeURIComponent(id)}/rankings${query ? `?${query}` : ''}`;
|
||||
return api.get<ChallengeRankingsDto>(url);
|
||||
}
|
||||
|
||||
export async function listMyCustomChallenges(
|
||||
params?: { page?: number; pageSize?: number; state?: ChallengeState }
|
||||
): Promise<ChallengeListResponse> {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) {
|
||||
searchParams.append('page', String(params.page));
|
||||
}
|
||||
if (params?.pageSize) {
|
||||
searchParams.append('pageSize', String(params.pageSize));
|
||||
}
|
||||
if (params?.state) {
|
||||
searchParams.append('state', params.state);
|
||||
}
|
||||
const query = searchParams.toString();
|
||||
const url = `/challenges/my/created${query ? `?${query}` : ''}`;
|
||||
return api.get<ChallengeListResponse>(url);
|
||||
}
|
||||
|
||||
export async function createCustomChallenge(
|
||||
payload: CreateCustomChallengePayload
|
||||
): Promise<ChallengeDetailDto> {
|
||||
return api.post<ChallengeDetailDto>('/challenges/custom', payload);
|
||||
}
|
||||
|
||||
export async function joinChallengeByCode(shareCode: string): Promise<ChallengeProgressDto> {
|
||||
return api.post<ChallengeProgressDto>('/challenges/join-by-code', { shareCode });
|
||||
}
|
||||
|
||||
export async function getChallengeByShareCode(shareCode: string): Promise<ChallengeDetailDto> {
|
||||
return api.get<ChallengeDetailDto>(`/challenges/share/${encodeURIComponent(shareCode)}`);
|
||||
}
|
||||
|
||||
export async function regenerateChallengeShareCode(
|
||||
id: string
|
||||
): Promise<{ shareCode: string }> {
|
||||
return api.post<{ shareCode: string }>(
|
||||
`/challenges/custom/${encodeURIComponent(id)}/regenerate-code`
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user