2025-12-09 11:10:36 +08:00
|
|
|
import request from "@/utils/request";
|
|
|
|
|
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
|
|
|
|
|
|
|
|
|
// ==================== 比赛相关类型 ====================
|
|
|
|
|
export interface Contest {
|
|
|
|
|
id: number;
|
|
|
|
|
contestName: string;
|
|
|
|
|
contestType: "individual" | "team";
|
|
|
|
|
contestState: "unpublished" | "published";
|
2026-01-08 09:17:46 +08:00
|
|
|
status: "ongoing" | "finished"; // 赛事进度状态
|
2025-12-09 11:10:36 +08:00
|
|
|
startTime: string;
|
|
|
|
|
endTime: string;
|
|
|
|
|
address?: string;
|
|
|
|
|
content?: string;
|
|
|
|
|
contestTenants?: number[];
|
|
|
|
|
coverUrl?: string;
|
|
|
|
|
posterUrl?: string;
|
|
|
|
|
contactName?: string;
|
|
|
|
|
contactPhone?: string;
|
|
|
|
|
contactQrcode?: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
organizers?: string;
|
|
|
|
|
coOrganizers?: string;
|
|
|
|
|
sponsors?: string;
|
|
|
|
|
// 报名配置
|
2025-12-09 11:10:36 +08:00
|
|
|
registerStartTime: string;
|
|
|
|
|
registerEndTime: string;
|
|
|
|
|
registerState?: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
requireAudit: boolean; // 是否需要审核
|
|
|
|
|
allowedGrades?: number[]; // 允许报名的年级
|
|
|
|
|
allowedClasses?: number[]; // 允许报名的班级
|
|
|
|
|
teamMinMembers?: number; // 团队最少人数
|
|
|
|
|
teamMaxMembers?: number; // 团队最多人数
|
|
|
|
|
// 作品配置
|
2025-12-09 11:10:36 +08:00
|
|
|
submitRule: "once" | "resubmit";
|
|
|
|
|
submitStartTime: string;
|
|
|
|
|
submitEndTime: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
workType?: "image" | "video" | "document" | "code" | "other"; // 作品类型
|
|
|
|
|
workRequirement?: string; // 作品要求
|
|
|
|
|
// 评审配置
|
2025-12-09 11:10:36 +08:00
|
|
|
reviewRuleId?: number;
|
|
|
|
|
reviewStartTime: string;
|
|
|
|
|
reviewEndTime: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
// 赛果配置
|
|
|
|
|
resultState: "unpublished" | "published"; // 赛果发布状态
|
2025-12-09 11:10:36 +08:00
|
|
|
resultPublishTime?: string;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
|
|
|
|
attachments?: ContestAttachment[];
|
|
|
|
|
reviewRule?: ContestReviewRule;
|
|
|
|
|
_count?: {
|
|
|
|
|
registrations: number;
|
|
|
|
|
works: number;
|
|
|
|
|
teams: number;
|
|
|
|
|
judges: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateContestForm {
|
2026-01-08 09:17:46 +08:00
|
|
|
id?: number; // 编辑模式时使用
|
2025-12-09 11:10:36 +08:00
|
|
|
contestName: string;
|
|
|
|
|
contestType: "individual" | "team";
|
|
|
|
|
startTime: string;
|
|
|
|
|
endTime: string;
|
|
|
|
|
address?: string;
|
|
|
|
|
content?: string;
|
|
|
|
|
contestTenants?: number[];
|
|
|
|
|
coverUrl?: string;
|
|
|
|
|
posterUrl?: string;
|
|
|
|
|
contactName?: string;
|
|
|
|
|
contactPhone?: string;
|
|
|
|
|
contactQrcode?: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
organizers?: string;
|
|
|
|
|
coOrganizers?: string;
|
|
|
|
|
sponsors?: string;
|
|
|
|
|
// 报名配置
|
2025-12-09 11:10:36 +08:00
|
|
|
registerStartTime: string;
|
|
|
|
|
registerEndTime: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
requireAudit?: boolean;
|
|
|
|
|
allowedGrades?: number[];
|
|
|
|
|
allowedClasses?: number[];
|
|
|
|
|
teamMinMembers?: number;
|
|
|
|
|
teamMaxMembers?: number;
|
|
|
|
|
// 作品配置
|
2025-12-09 11:10:36 +08:00
|
|
|
submitRule?: "once" | "resubmit";
|
|
|
|
|
submitStartTime: string;
|
|
|
|
|
submitEndTime: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
workType?: "image" | "video" | "document" | "code" | "other";
|
|
|
|
|
workRequirement?: string;
|
|
|
|
|
// 评审配置
|
2025-12-09 11:10:36 +08:00
|
|
|
reviewStartTime: string;
|
|
|
|
|
reviewEndTime: string;
|
|
|
|
|
resultPublishTime?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface UpdateContestForm extends Partial<CreateContestForm> {
|
|
|
|
|
contestState?: "unpublished" | "published";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface QueryContestParams extends PaginationParams {
|
|
|
|
|
contestName?: string;
|
|
|
|
|
contestState?: "unpublished" | "published";
|
2026-01-08 09:17:46 +08:00
|
|
|
status?: "ongoing" | "finished";
|
2025-12-09 11:10:36 +08:00
|
|
|
contestType?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 附件相关类型 ====================
|
|
|
|
|
export interface ContestAttachment {
|
|
|
|
|
id: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
fileName: string;
|
|
|
|
|
fileUrl: string;
|
|
|
|
|
format?: string;
|
|
|
|
|
fileType?: string;
|
|
|
|
|
size?: string;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateAttachmentForm {
|
|
|
|
|
contestId: number;
|
|
|
|
|
fileName: string;
|
|
|
|
|
fileUrl: string;
|
|
|
|
|
format?: string;
|
|
|
|
|
fileType?: string;
|
|
|
|
|
size?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 评审规则相关类型 ====================
|
2026-01-08 09:17:46 +08:00
|
|
|
export interface ReviewDimension {
|
|
|
|
|
name: string;
|
|
|
|
|
percentage: number;
|
|
|
|
|
description?: string;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
export interface ContestReviewRule {
|
|
|
|
|
id: number;
|
2026-01-08 09:17:46 +08:00
|
|
|
tenantId: number;
|
2025-12-09 11:10:36 +08:00
|
|
|
ruleName: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
ruleDescription?: string;
|
|
|
|
|
judgeCount: number;
|
|
|
|
|
dimensions: ReviewDimension[];
|
|
|
|
|
calculationRule: "average" | "remove_max_min" | "remove_min";
|
2025-12-09 11:10:36 +08:00
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
2026-01-08 09:17:46 +08:00
|
|
|
contests?: Array<{
|
|
|
|
|
id: number;
|
|
|
|
|
contestName: string;
|
|
|
|
|
}>;
|
2025-12-09 11:10:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateReviewRuleForm {
|
|
|
|
|
ruleName: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
ruleDescription?: string;
|
|
|
|
|
judgeCount: number;
|
|
|
|
|
dimensions: ReviewDimension[];
|
|
|
|
|
calculationRule?: "average" | "remove_max_min" | "remove_min";
|
2025-12-09 11:10:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 报名相关类型 ====================
|
|
|
|
|
export interface ContestRegistration {
|
|
|
|
|
id: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
tenantId: number;
|
|
|
|
|
registrationType?: "individual" | "team";
|
|
|
|
|
teamId?: number;
|
|
|
|
|
teamName?: string;
|
|
|
|
|
userId: number;
|
|
|
|
|
accountNo: string;
|
|
|
|
|
accountName: string;
|
|
|
|
|
role?: string;
|
|
|
|
|
registrationState: "pending" | "passed" | "rejected" | "withdrawn";
|
|
|
|
|
registrant?: number;
|
|
|
|
|
registrationTime: string;
|
|
|
|
|
reason?: string;
|
|
|
|
|
operator?: number;
|
|
|
|
|
operationDate?: string;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
contest?: Contest;
|
|
|
|
|
user?: {
|
|
|
|
|
id: number;
|
|
|
|
|
username: string;
|
|
|
|
|
nickname: string;
|
|
|
|
|
};
|
|
|
|
|
team?: ContestTeam;
|
|
|
|
|
_count?: {
|
|
|
|
|
works: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateRegistrationForm {
|
|
|
|
|
contestId: number;
|
|
|
|
|
registrationType: "individual" | "team";
|
|
|
|
|
teamId?: number;
|
|
|
|
|
userId: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ReviewRegistrationForm {
|
|
|
|
|
registrationState: "pending" | "passed" | "rejected" | "withdrawn";
|
|
|
|
|
reason?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface QueryRegistrationParams extends PaginationParams {
|
|
|
|
|
contestId?: number;
|
|
|
|
|
registrationState?: "pending" | "passed" | "rejected" | "withdrawn";
|
|
|
|
|
registrationType?: string;
|
|
|
|
|
userId?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 团队相关类型 ====================
|
|
|
|
|
export interface ContestTeam {
|
|
|
|
|
id: number;
|
|
|
|
|
tenantId: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
teamName: string;
|
|
|
|
|
leaderUserId: number;
|
|
|
|
|
maxMembers?: number;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
|
|
|
|
leader?: {
|
|
|
|
|
id: number;
|
|
|
|
|
username: string;
|
|
|
|
|
nickname: string;
|
|
|
|
|
};
|
|
|
|
|
members?: ContestTeamMember[];
|
|
|
|
|
contest?: Contest;
|
|
|
|
|
_count?: {
|
|
|
|
|
members: number;
|
|
|
|
|
registrations: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ContestTeamMember {
|
|
|
|
|
id: number;
|
|
|
|
|
tenantId: number;
|
|
|
|
|
teamId: number;
|
|
|
|
|
userId: number;
|
|
|
|
|
role: "leader" | "member" | "mentor";
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
user?: {
|
|
|
|
|
id: number;
|
|
|
|
|
username: string;
|
|
|
|
|
nickname: string;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateTeamForm {
|
|
|
|
|
contestId: number;
|
|
|
|
|
teamName: string;
|
|
|
|
|
leaderUserId: number;
|
|
|
|
|
maxMembers?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface InviteMemberForm {
|
|
|
|
|
userId: number;
|
|
|
|
|
role?: "leader" | "member" | "mentor";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 作品相关类型 ====================
|
|
|
|
|
export interface ContestWork {
|
|
|
|
|
id: number;
|
|
|
|
|
tenantId: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
registrationId: number;
|
|
|
|
|
workNo?: string;
|
|
|
|
|
title: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
files?: string[];
|
|
|
|
|
version: number;
|
|
|
|
|
isLatest: boolean;
|
|
|
|
|
status: "submitted" | "locked" | "reviewing" | "rejected" | "accepted";
|
|
|
|
|
submitTime: string;
|
|
|
|
|
submitterUserId?: number;
|
|
|
|
|
submitterAccountNo?: string;
|
|
|
|
|
submitSource: string;
|
|
|
|
|
previewUrl?: string;
|
|
|
|
|
aiModelMeta?: any;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
|
|
|
|
contest?: Contest;
|
|
|
|
|
registration?: ContestRegistration;
|
|
|
|
|
attachments?: ContestWorkAttachment[];
|
|
|
|
|
_count?: {
|
|
|
|
|
scores: number;
|
|
|
|
|
assignments: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ContestWorkAttachment {
|
|
|
|
|
id: number;
|
|
|
|
|
tenantId: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
workId: number;
|
|
|
|
|
fileName: string;
|
|
|
|
|
fileUrl: string;
|
|
|
|
|
format?: string;
|
|
|
|
|
fileType?: string;
|
|
|
|
|
size?: string;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SubmitWorkForm {
|
|
|
|
|
registrationId: number;
|
|
|
|
|
title: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
files?: string[];
|
|
|
|
|
previewUrl?: string;
|
|
|
|
|
aiModelMeta?: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface QueryWorkParams extends PaginationParams {
|
|
|
|
|
contestId?: number;
|
|
|
|
|
registrationId?: number;
|
|
|
|
|
status?: "submitted" | "locked" | "reviewing" | "rejected" | "accepted";
|
|
|
|
|
title?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 评审相关类型 ====================
|
|
|
|
|
export interface ContestWorkJudgeAssignment {
|
|
|
|
|
id: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
workId: number;
|
|
|
|
|
judgeId: number;
|
|
|
|
|
assignmentTime: string;
|
|
|
|
|
status: "assigned" | "reviewing" | "completed";
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
work?: ContestWork;
|
|
|
|
|
judge?: {
|
|
|
|
|
id: number;
|
|
|
|
|
username: string;
|
|
|
|
|
nickname: string;
|
|
|
|
|
};
|
|
|
|
|
scores?: ContestWorkScore[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ContestWorkScore {
|
|
|
|
|
id: number;
|
|
|
|
|
tenantId: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
workId: number;
|
|
|
|
|
assignmentId: number;
|
|
|
|
|
judgeId: number;
|
|
|
|
|
judgeName: string;
|
|
|
|
|
dimensionScores: any;
|
|
|
|
|
totalScore: number;
|
|
|
|
|
comments?: string;
|
|
|
|
|
scoreTime: string;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
|
|
|
|
work?: ContestWork;
|
|
|
|
|
judge?: {
|
|
|
|
|
id: number;
|
|
|
|
|
username: string;
|
|
|
|
|
nickname: string;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface AssignWorkForm {
|
|
|
|
|
workId: number;
|
|
|
|
|
judgeIds: number[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateScoreForm {
|
|
|
|
|
workId: number;
|
|
|
|
|
assignmentId: number;
|
|
|
|
|
dimensionScores: any;
|
|
|
|
|
totalScore: number;
|
|
|
|
|
comments?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 公告相关类型 ====================
|
|
|
|
|
export interface ContestNotice {
|
|
|
|
|
id: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
title: string;
|
|
|
|
|
content: string;
|
|
|
|
|
noticeType: "system" | "manual" | "urgent";
|
|
|
|
|
priority: number;
|
|
|
|
|
publishTime?: string;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
|
|
|
|
contest?: Contest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateNoticeForm {
|
|
|
|
|
contestId: number;
|
|
|
|
|
title: string;
|
|
|
|
|
content: string;
|
|
|
|
|
noticeType?: "system" | "manual" | "urgent";
|
|
|
|
|
priority?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 评委相关类型 ====================
|
|
|
|
|
export interface ContestJudge {
|
|
|
|
|
id: number;
|
|
|
|
|
contestId: number;
|
|
|
|
|
judgeId: number;
|
|
|
|
|
specialty?: string;
|
|
|
|
|
weight?: number;
|
|
|
|
|
description?: string;
|
|
|
|
|
creator?: number;
|
|
|
|
|
modifier?: number;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
validState?: number;
|
|
|
|
|
contest?: Contest;
|
|
|
|
|
judge?: {
|
|
|
|
|
id: number;
|
|
|
|
|
username: string;
|
|
|
|
|
nickname: string;
|
|
|
|
|
email?: string;
|
2026-01-08 09:17:46 +08:00
|
|
|
phone?: string;
|
|
|
|
|
gender?: 'male' | 'female';
|
|
|
|
|
status?: 'enabled' | 'disabled';
|
|
|
|
|
tenantId?: number;
|
|
|
|
|
tenant?: {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
};
|
|
|
|
|
contestJudges?: Array<{
|
|
|
|
|
contest: {
|
|
|
|
|
id: number;
|
|
|
|
|
contestName: string;
|
|
|
|
|
status: string;
|
|
|
|
|
};
|
|
|
|
|
}>;
|
2025-12-09 11:10:36 +08:00
|
|
|
};
|
|
|
|
|
_count?: {
|
|
|
|
|
assignedContestWorks: number;
|
|
|
|
|
scoredContestWorks: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CreateJudgeForm {
|
|
|
|
|
contestId: number;
|
|
|
|
|
judgeId: number;
|
|
|
|
|
specialty?: string;
|
|
|
|
|
weight?: number;
|
|
|
|
|
description?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== API 函数 ====================
|
|
|
|
|
|
|
|
|
|
// 比赛管理
|
|
|
|
|
export const contestsApi = {
|
|
|
|
|
// 获取比赛列表
|
|
|
|
|
getList: async (
|
|
|
|
|
params: QueryContestParams
|
|
|
|
|
): Promise<PaginationResponse<Contest>> => {
|
|
|
|
|
const response = await request.get<any, PaginationResponse<Contest>>(
|
|
|
|
|
"/contests",
|
|
|
|
|
{ params }
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-08 09:17:46 +08:00
|
|
|
// 获取我参与的赛事列表
|
|
|
|
|
getMyContests: async (
|
|
|
|
|
params: QueryContestParams
|
|
|
|
|
): Promise<PaginationResponse<Contest>> => {
|
|
|
|
|
const response = await request.get<any, PaginationResponse<Contest>>(
|
|
|
|
|
"/contests/my-contests",
|
|
|
|
|
{ params }
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
// 获取比赛详情
|
|
|
|
|
getDetail: async (id: number): Promise<Contest> => {
|
|
|
|
|
const response = await request.get<any, Contest>(`/contests/${id}`);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 创建比赛
|
|
|
|
|
create: async (data: CreateContestForm): Promise<Contest> => {
|
|
|
|
|
const response = await request.post<any, Contest>("/contests", data);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 更新比赛
|
|
|
|
|
update: async (id: number, data: UpdateContestForm): Promise<Contest> => {
|
|
|
|
|
const response = await request.patch<any, Contest>(`/contests/${id}`, data);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 发布/撤回比赛
|
|
|
|
|
publish: async (
|
|
|
|
|
id: number,
|
|
|
|
|
contestState: "unpublished" | "published"
|
|
|
|
|
): Promise<Contest> => {
|
|
|
|
|
const response = await request.patch<any, Contest>(
|
|
|
|
|
`/contests/${id}/publish`,
|
|
|
|
|
{ contestState }
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 删除比赛
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(`/contests/${id}`);
|
|
|
|
|
},
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
|
|
|
// 标记比赛完结
|
|
|
|
|
finish: async (id: number): Promise<Contest> => {
|
|
|
|
|
const response = await request.patch<any, Contest>(`/contests/${id}/finish`);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 重新开启已完结的比赛
|
|
|
|
|
reopen: async (id: number): Promise<Contest> => {
|
|
|
|
|
const response = await request.patch<any, Contest>(`/contests/${id}/reopen`);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
2025-12-09 11:10:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 附件管理
|
|
|
|
|
export const attachmentsApi = {
|
|
|
|
|
// 获取比赛附件列表
|
|
|
|
|
getList: async (contestId: number): Promise<ContestAttachment[]> => {
|
|
|
|
|
const response = await request.get<any, ContestAttachment[]>(
|
|
|
|
|
`/contests/attachments/contest/${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 创建附件
|
|
|
|
|
create: async (data: CreateAttachmentForm): Promise<ContestAttachment> => {
|
|
|
|
|
const response = await request.post<any, ContestAttachment>(
|
|
|
|
|
"/contests/attachments",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 删除附件
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(`/contests/attachments/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 评审规则
|
2026-01-08 09:17:46 +08:00
|
|
|
export interface ReviewRule {
|
|
|
|
|
id: number;
|
|
|
|
|
tenantId: number;
|
|
|
|
|
ruleName: string;
|
|
|
|
|
ruleDescription?: string;
|
|
|
|
|
judgeCount: number;
|
|
|
|
|
dimensions: ReviewDimension[];
|
|
|
|
|
calculationRule: "average" | "remove_max_min" | "remove_min";
|
|
|
|
|
validState: number;
|
|
|
|
|
contests?: Array<{
|
|
|
|
|
id: number;
|
|
|
|
|
contestName: string;
|
|
|
|
|
}>;
|
|
|
|
|
createTime?: string;
|
|
|
|
|
modifyTime?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用于选择器的简化评审规则
|
|
|
|
|
export interface ReviewRuleForSelect {
|
|
|
|
|
id: number;
|
|
|
|
|
ruleName: string;
|
|
|
|
|
ruleDescription?: string;
|
|
|
|
|
judgeCount: number;
|
|
|
|
|
calculationRule: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface QueryReviewRuleParams {
|
|
|
|
|
ruleName?: string;
|
|
|
|
|
page?: number;
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
export const reviewRulesApi = {
|
2026-01-08 09:17:46 +08:00
|
|
|
// 获取评审规则列表
|
|
|
|
|
getList: async (params?: QueryReviewRuleParams): Promise<{
|
|
|
|
|
list: ReviewRule[];
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
pageSize: number;
|
|
|
|
|
}> => {
|
|
|
|
|
const response = await request.get<any, {
|
|
|
|
|
list: ReviewRule[];
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
pageSize: number;
|
|
|
|
|
}>("/contests/review-rules", { params });
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取所有可用的评审规则(用于赛事创建时选择)
|
|
|
|
|
getForSelect: async (): Promise<ReviewRuleForSelect[]> => {
|
|
|
|
|
const response = await request.get<any, ReviewRuleForSelect[]>(
|
|
|
|
|
"/contests/review-rules/select"
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取评审规则详情
|
|
|
|
|
getDetail: async (id: number): Promise<ReviewRule> => {
|
|
|
|
|
const response = await request.get<any, ReviewRule>(
|
|
|
|
|
`/contests/review-rules/${id}`
|
2025-12-09 11:10:36 +08:00
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 创建评审规则
|
2026-01-08 09:17:46 +08:00
|
|
|
create: async (data: CreateReviewRuleForm): Promise<ReviewRule> => {
|
|
|
|
|
const response = await request.post<any, ReviewRule>(
|
2025-12-09 11:10:36 +08:00
|
|
|
"/contests/review-rules",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 更新评审规则
|
|
|
|
|
update: async (
|
2026-01-08 09:17:46 +08:00
|
|
|
id: number,
|
2025-12-09 11:10:36 +08:00
|
|
|
data: Partial<CreateReviewRuleForm>
|
2026-01-08 09:17:46 +08:00
|
|
|
): Promise<ReviewRule> => {
|
|
|
|
|
const response = await request.patch<any, ReviewRule>(
|
|
|
|
|
`/contests/review-rules/${id}`,
|
2025-12-09 11:10:36 +08:00
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
|
|
|
// 删除评审规则
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
await request.delete(`/contests/review-rules/${id}`);
|
|
|
|
|
},
|
2025-12-09 11:10:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 报名管理
|
|
|
|
|
export const registrationsApi = {
|
|
|
|
|
// 获取报名列表
|
|
|
|
|
getList: async (
|
|
|
|
|
params: QueryRegistrationParams
|
|
|
|
|
): Promise<PaginationResponse<ContestRegistration>> => {
|
|
|
|
|
const response = await request.get<
|
|
|
|
|
any,
|
|
|
|
|
PaginationResponse<ContestRegistration>
|
|
|
|
|
>("/contests/registrations", { params });
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取报名详情
|
|
|
|
|
getDetail: async (id: number): Promise<ContestRegistration> => {
|
|
|
|
|
const response = await request.get<any, ContestRegistration>(
|
|
|
|
|
`/contests/registrations/${id}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 创建报名
|
|
|
|
|
create: async (
|
|
|
|
|
data: CreateRegistrationForm
|
|
|
|
|
): Promise<ContestRegistration> => {
|
|
|
|
|
const response = await request.post<any, ContestRegistration>(
|
|
|
|
|
"/contests/registrations",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 审核报名
|
|
|
|
|
review: async (
|
|
|
|
|
id: number,
|
|
|
|
|
data: ReviewRegistrationForm
|
|
|
|
|
): Promise<ContestRegistration> => {
|
|
|
|
|
const response = await request.patch<any, ContestRegistration>(
|
|
|
|
|
`/contests/registrations/${id}/review`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 删除报名
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(`/contests/registrations/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 团队管理
|
|
|
|
|
export const teamsApi = {
|
|
|
|
|
// 获取团队列表
|
|
|
|
|
getList: async (contestId: number): Promise<ContestTeam[]> => {
|
|
|
|
|
const response = await request.get<any, ContestTeam[]>(
|
|
|
|
|
`/contests/teams/contest/${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取团队详情
|
|
|
|
|
getDetail: async (id: number): Promise<ContestTeam> => {
|
|
|
|
|
const response = await request.get<any, ContestTeam>(
|
|
|
|
|
`/contests/teams/${id}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 创建团队
|
|
|
|
|
create: async (data: CreateTeamForm): Promise<ContestTeam> => {
|
|
|
|
|
const response = await request.post<any, ContestTeam>(
|
|
|
|
|
"/contests/teams",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 更新团队
|
|
|
|
|
update: async (
|
|
|
|
|
id: number,
|
|
|
|
|
data: Partial<CreateTeamForm>
|
|
|
|
|
): Promise<ContestTeam> => {
|
|
|
|
|
const response = await request.patch<any, ContestTeam>(
|
|
|
|
|
`/contests/teams/${id}`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 邀请成员
|
|
|
|
|
inviteMember: async (
|
|
|
|
|
teamId: number,
|
|
|
|
|
data: InviteMemberForm
|
|
|
|
|
): Promise<ContestTeamMember> => {
|
|
|
|
|
const response = await request.post<any, ContestTeamMember>(
|
|
|
|
|
`/contests/teams/${teamId}/members`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 移除成员
|
|
|
|
|
removeMember: async (teamId: number, userId: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(
|
|
|
|
|
`/contests/teams/${teamId}/members/${userId}`
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 删除团队
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(`/contests/teams/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 作品管理
|
|
|
|
|
export const worksApi = {
|
|
|
|
|
// 获取作品列表
|
|
|
|
|
getList: async (
|
|
|
|
|
params: QueryWorkParams
|
|
|
|
|
): Promise<PaginationResponse<ContestWork>> => {
|
|
|
|
|
const response = await request.get<any, PaginationResponse<ContestWork>>(
|
|
|
|
|
"/contests/works",
|
|
|
|
|
{ params }
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取作品详情
|
|
|
|
|
getDetail: async (id: number): Promise<ContestWork> => {
|
|
|
|
|
const response = await request.get<any, ContestWork>(
|
|
|
|
|
`/contests/works/${id}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 提交作品
|
|
|
|
|
submit: async (data: SubmitWorkForm): Promise<ContestWork> => {
|
|
|
|
|
const response = await request.post<any, ContestWork>(
|
|
|
|
|
"/contests/works/submit",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取作品版本列表
|
|
|
|
|
getVersions: async (registrationId: number): Promise<ContestWork[]> => {
|
|
|
|
|
const response = await request.get<any, ContestWork[]>(
|
|
|
|
|
`/contests/works/registration/${registrationId}/versions`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 删除作品
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(`/contests/works/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-08 09:17:46 +08:00
|
|
|
// ==================== 评审进度相关类型 ====================
|
|
|
|
|
export interface ReviewProgress {
|
|
|
|
|
contest: {
|
|
|
|
|
id: number;
|
|
|
|
|
contestName: string;
|
|
|
|
|
reviewStartTime: string;
|
|
|
|
|
reviewEndTime: string;
|
|
|
|
|
reviewRule: ContestReviewRule | null;
|
|
|
|
|
};
|
|
|
|
|
summary: {
|
|
|
|
|
totalWorks: number;
|
|
|
|
|
assignedWorksCount: number;
|
|
|
|
|
scoredWorksCount: number;
|
|
|
|
|
unassignedWorksCount: number;
|
|
|
|
|
totalJudges: number;
|
|
|
|
|
totalAssignments: number;
|
|
|
|
|
totalScores: number;
|
|
|
|
|
pendingScoresCount: number;
|
|
|
|
|
};
|
|
|
|
|
progress: {
|
|
|
|
|
assignmentProgress: number;
|
|
|
|
|
scoringProgress: number;
|
|
|
|
|
overallProgress: number;
|
|
|
|
|
};
|
|
|
|
|
judgeProgress: JudgeProgressItem[];
|
|
|
|
|
unassignedWorks: UnassignedWork[];
|
|
|
|
|
pendingAssignments: PendingAssignment[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface JudgeProgressItem {
|
|
|
|
|
judgeId: number;
|
|
|
|
|
judgeName: string;
|
|
|
|
|
specialty: string | null;
|
|
|
|
|
weight: number | null;
|
|
|
|
|
assignedCount: number;
|
|
|
|
|
scoredCount: number;
|
|
|
|
|
pendingCount: number;
|
|
|
|
|
progress: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface UnassignedWork {
|
|
|
|
|
id: number;
|
|
|
|
|
workNo: string | null;
|
|
|
|
|
title: string;
|
|
|
|
|
createTime: string;
|
|
|
|
|
registration: {
|
|
|
|
|
user: { id: number; nickname: string; username: string } | null;
|
|
|
|
|
team: { id: number; teamName: string } | null;
|
|
|
|
|
} | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PendingAssignment {
|
|
|
|
|
id: number;
|
|
|
|
|
workId: number;
|
|
|
|
|
workNo: string | null;
|
|
|
|
|
workTitle: string;
|
|
|
|
|
judgeId: number;
|
|
|
|
|
judgeName: string;
|
|
|
|
|
status: string;
|
|
|
|
|
assignmentTime: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface WorkStatusStats {
|
|
|
|
|
submitted: number;
|
|
|
|
|
reviewing: number;
|
|
|
|
|
reviewed: number;
|
|
|
|
|
awarded: number;
|
|
|
|
|
total: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BatchAssignForm {
|
|
|
|
|
workIds: number[];
|
|
|
|
|
judgeIds: number[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BatchAssignResult {
|
|
|
|
|
created: number;
|
|
|
|
|
skipped: number;
|
|
|
|
|
assignments: ContestWorkJudgeAssignment[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface AutoAssignResult {
|
|
|
|
|
message: string;
|
|
|
|
|
worksCount?: number;
|
|
|
|
|
created: number;
|
|
|
|
|
judgesPerWork?: number;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
// 评审管理
|
|
|
|
|
export const reviewsApi = {
|
|
|
|
|
// 分配作品给评委
|
|
|
|
|
assignWork: async (
|
|
|
|
|
contestId: number,
|
|
|
|
|
data: AssignWorkForm
|
|
|
|
|
): Promise<ContestWorkJudgeAssignment[]> => {
|
|
|
|
|
const response = await request.post<any, ContestWorkJudgeAssignment[]>(
|
|
|
|
|
`/contests/reviews/assign?contestId=${contestId}`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-08 09:17:46 +08:00
|
|
|
// 批量分配作品给评委
|
|
|
|
|
batchAssignWorks: async (
|
|
|
|
|
contestId: number,
|
|
|
|
|
data: BatchAssignForm
|
|
|
|
|
): Promise<BatchAssignResult> => {
|
|
|
|
|
const response = await request.post<any, BatchAssignResult>(
|
|
|
|
|
`/contests/reviews/batch-assign?contestId=${contestId}`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 自动分配作品给评委
|
|
|
|
|
autoAssignWorks: async (contestId: number): Promise<AutoAssignResult> => {
|
|
|
|
|
const response = await request.post<any, AutoAssignResult>(
|
|
|
|
|
`/contests/reviews/auto-assign?contestId=${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
// 评分
|
|
|
|
|
score: async (data: CreateScoreForm): Promise<ContestWorkScore> => {
|
|
|
|
|
const response = await request.post<any, ContestWorkScore>(
|
|
|
|
|
"/contests/reviews/score",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 更新评分
|
|
|
|
|
updateScore: async (
|
|
|
|
|
scoreId: number,
|
|
|
|
|
data: Partial<CreateScoreForm>
|
|
|
|
|
): Promise<ContestWorkScore> => {
|
|
|
|
|
const response = await request.patch<any, ContestWorkScore>(
|
|
|
|
|
`/contests/reviews/score/${scoreId}`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取分配给当前评委的作品
|
|
|
|
|
getAssignedWorks: async (
|
|
|
|
|
contestId: number
|
|
|
|
|
): Promise<ContestWorkJudgeAssignment[]> => {
|
|
|
|
|
const response = await request.get<any, ContestWorkJudgeAssignment[]>(
|
|
|
|
|
`/contests/reviews/assigned?contestId=${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-08 09:17:46 +08:00
|
|
|
// 获取评审进度统计
|
|
|
|
|
getReviewProgress: async (contestId: number): Promise<ReviewProgress> => {
|
|
|
|
|
const response = await request.get<any, ReviewProgress>(
|
|
|
|
|
`/contests/reviews/progress/${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取作品状态统计
|
|
|
|
|
getWorkStatusStats: async (contestId: number): Promise<WorkStatusStats> => {
|
|
|
|
|
const response = await request.get<any, WorkStatusStats>(
|
|
|
|
|
`/contests/reviews/work-status/${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
// 获取作品评分列表
|
|
|
|
|
getWorkScores: async (workId: number): Promise<ContestWorkScore[]> => {
|
|
|
|
|
const response = await request.get<any, ContestWorkScore[]>(
|
|
|
|
|
`/contests/reviews/work/${workId}/scores`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 计算最终得分
|
|
|
|
|
calculateFinalScore: async (
|
|
|
|
|
workId: number
|
|
|
|
|
): Promise<{
|
|
|
|
|
finalScore: number;
|
|
|
|
|
scoreCount: number;
|
|
|
|
|
calculationRule: string;
|
|
|
|
|
}> => {
|
|
|
|
|
const response = await request.get<
|
|
|
|
|
any,
|
|
|
|
|
{ finalScore: number; scoreCount: number; calculationRule: string }
|
|
|
|
|
>(`/contests/reviews/work/${workId}/final-score`);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 公告管理
|
|
|
|
|
export const noticesApi = {
|
2026-01-08 09:17:46 +08:00
|
|
|
// 获取公告列表(按赛事)
|
2025-12-09 11:10:36 +08:00
|
|
|
getList: async (contestId: number): Promise<ContestNotice[]> => {
|
|
|
|
|
const response = await request.get<any, ContestNotice[]>(
|
|
|
|
|
`/contests/notices/contest/${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-08 09:17:46 +08:00
|
|
|
// 获取所有公告(支持搜索和分页)
|
|
|
|
|
getAll: async (params?: {
|
|
|
|
|
title?: string;
|
|
|
|
|
publishDate?: string;
|
|
|
|
|
page?: number;
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
}): Promise<PaginationResponse<ContestNotice>> => {
|
|
|
|
|
const response = await request.get<any, PaginationResponse<ContestNotice>>(
|
|
|
|
|
'/contests/notices',
|
|
|
|
|
{ params }
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
// 获取公告详情
|
|
|
|
|
getDetail: async (id: number): Promise<ContestNotice> => {
|
|
|
|
|
const response = await request.get<any, ContestNotice>(
|
|
|
|
|
`/contests/notices/${id}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 创建公告
|
|
|
|
|
create: async (data: CreateNoticeForm): Promise<ContestNotice> => {
|
|
|
|
|
const response = await request.post<any, ContestNotice>(
|
|
|
|
|
"/contests/notices",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 更新公告
|
|
|
|
|
update: async (
|
|
|
|
|
id: number,
|
|
|
|
|
data: Partial<CreateNoticeForm>
|
|
|
|
|
): Promise<ContestNotice> => {
|
|
|
|
|
const response = await request.patch<any, ContestNotice>(
|
|
|
|
|
`/contests/notices/${id}`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 删除公告
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(`/contests/notices/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-08 09:17:46 +08:00
|
|
|
// ==================== 赛果相关类型 ====================
|
|
|
|
|
export interface ContestResult {
|
|
|
|
|
id: number;
|
|
|
|
|
workNo: string | null;
|
|
|
|
|
title: string;
|
|
|
|
|
finalScore: number | null;
|
|
|
|
|
rank: number | null;
|
|
|
|
|
awardLevel: string | null;
|
|
|
|
|
awardName: string | null;
|
|
|
|
|
certificateUrl: string | null;
|
|
|
|
|
registration: {
|
|
|
|
|
user: { id: number; username: string; nickname: string } | null;
|
|
|
|
|
team: { id: number; teamName: string } | null;
|
|
|
|
|
} | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ResultsResponse {
|
|
|
|
|
contest: {
|
|
|
|
|
id: number;
|
|
|
|
|
contestName: string;
|
|
|
|
|
resultState: string;
|
|
|
|
|
resultPublishTime: string | null;
|
|
|
|
|
reviewRule: ContestReviewRule | null;
|
|
|
|
|
};
|
|
|
|
|
list: ContestResult[];
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
pageSize: number;
|
|
|
|
|
awardDistribution: Record<string, number>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ResultsSummary {
|
|
|
|
|
contest: {
|
|
|
|
|
id: number;
|
|
|
|
|
contestName: string;
|
|
|
|
|
resultState: string;
|
|
|
|
|
resultPublishTime: string | null;
|
|
|
|
|
};
|
|
|
|
|
summary: {
|
|
|
|
|
totalWorks: number;
|
|
|
|
|
scoredWorks: number;
|
|
|
|
|
rankedWorks: number;
|
|
|
|
|
awardedWorks: number;
|
|
|
|
|
unscoredWorks: number;
|
|
|
|
|
};
|
|
|
|
|
awardDistribution: Record<string, number>;
|
|
|
|
|
scoreStats: {
|
|
|
|
|
avgScore: string | null;
|
|
|
|
|
maxScore: string | null;
|
|
|
|
|
minScore: string | null;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SetAwardForm {
|
|
|
|
|
awardLevel: 'first' | 'second' | 'third' | 'excellent' | 'none';
|
|
|
|
|
awardName?: string;
|
|
|
|
|
certificateUrl?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BatchSetAwardsForm {
|
|
|
|
|
awards: Array<{
|
|
|
|
|
workId: number;
|
|
|
|
|
awardLevel: 'first' | 'second' | 'third' | 'excellent' | 'none';
|
|
|
|
|
awardName?: string;
|
|
|
|
|
}>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface AutoSetAwardsForm {
|
|
|
|
|
first?: number;
|
|
|
|
|
second?: number;
|
|
|
|
|
third?: number;
|
|
|
|
|
excellent?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 赛果管理
|
|
|
|
|
export const resultsApi = {
|
|
|
|
|
// 计算所有作品的最终得分
|
|
|
|
|
calculateScores: async (contestId: number): Promise<{ message: string; calculatedCount: number; calculationRule: string }> => {
|
|
|
|
|
const response = await request.post<any, any>(
|
|
|
|
|
`/contests/results/${contestId}/calculate-scores`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 计算排名
|
|
|
|
|
calculateRankings: async (contestId: number): Promise<{ message: string; rankedCount: number }> => {
|
|
|
|
|
const response = await request.post<any, any>(
|
|
|
|
|
`/contests/results/${contestId}/calculate-rankings`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 设置单个作品奖项
|
|
|
|
|
setAward: async (workId: number, data: SetAwardForm): Promise<ContestResult> => {
|
|
|
|
|
const response = await request.patch<any, ContestResult>(
|
|
|
|
|
`/contests/results/work/${workId}/award`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 批量设置奖项
|
|
|
|
|
batchSetAwards: async (contestId: number, data: BatchSetAwardsForm): Promise<any> => {
|
|
|
|
|
const response = await request.post<any, any>(
|
|
|
|
|
`/contests/results/${contestId}/batch-set-awards`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 根据排名自动设置奖项
|
|
|
|
|
autoSetAwards: async (contestId: number, data: AutoSetAwardsForm): Promise<any> => {
|
|
|
|
|
const response = await request.post<any, any>(
|
|
|
|
|
`/contests/results/${contestId}/auto-set-awards`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 发布赛果
|
|
|
|
|
publish: async (contestId: number): Promise<any> => {
|
|
|
|
|
const response = await request.post<any, any>(
|
|
|
|
|
`/contests/results/${contestId}/publish`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 撤回发布
|
|
|
|
|
unpublish: async (contestId: number): Promise<any> => {
|
|
|
|
|
const response = await request.post<any, any>(
|
|
|
|
|
`/contests/results/${contestId}/unpublish`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取比赛结果列表
|
|
|
|
|
getResults: async (contestId: number, page = 1, pageSize = 20): Promise<ResultsResponse> => {
|
|
|
|
|
const response = await request.get<any, ResultsResponse>(
|
|
|
|
|
`/contests/results/${contestId}`,
|
|
|
|
|
{ params: { page, pageSize } }
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取比赛结果统计摘要
|
|
|
|
|
getSummary: async (contestId: number): Promise<ResultsSummary> => {
|
|
|
|
|
const response = await request.get<any, ResultsSummary>(
|
|
|
|
|
`/contests/results/${contestId}/summary`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-09 11:10:36 +08:00
|
|
|
// 评委管理
|
|
|
|
|
export const judgesApi = {
|
|
|
|
|
// 获取评委列表
|
|
|
|
|
getList: async (contestId: number): Promise<ContestJudge[]> => {
|
|
|
|
|
const response = await request.get<any, ContestJudge[]>(
|
|
|
|
|
`/contests/judges/contest/${contestId}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取评委详情
|
|
|
|
|
getDetail: async (id: number): Promise<ContestJudge> => {
|
|
|
|
|
const response = await request.get<any, ContestJudge>(
|
|
|
|
|
`/contests/judges/${id}`
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 添加评委
|
|
|
|
|
create: async (data: CreateJudgeForm): Promise<ContestJudge> => {
|
|
|
|
|
const response = await request.post<any, ContestJudge>(
|
|
|
|
|
"/contests/judges",
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 更新评委
|
|
|
|
|
update: async (
|
|
|
|
|
id: number,
|
|
|
|
|
data: Partial<CreateJudgeForm>
|
|
|
|
|
): Promise<ContestJudge> => {
|
|
|
|
|
const response = await request.patch<any, ContestJudge>(
|
|
|
|
|
`/contests/judges/${id}`,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 删除评委
|
|
|
|
|
delete: async (id: number): Promise<void> => {
|
|
|
|
|
return await request.delete<any, void>(`/contests/judges/${id}`);
|
|
|
|
|
},
|
|
|
|
|
};
|