修复团队赛事相关功能
1. 修复团队成员无法在"我参与的赛事"中看到团队赛事的问题 2. 修复教师作为指导老师无法看到团队赛事的问题 3. 修复上传作品/参赛作品/我的队伍按钮500错误(userId获取方式错误) 4. 修复管理端成员弹框队长名称和成员数显示问题 5. 新增getMyRegistration接口支持团队成员查询报名状态 6. 优化赛事卡片按钮布局 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0cdc5d1ceb
commit
ac0c38c04a
@ -363,6 +363,7 @@ export class ContestsService {
|
||||
contestIds = judgeRecords.map((r) => r.contestId);
|
||||
} else if (role === 'teacher') {
|
||||
// 教师:查询作为指导老师参与的赛事
|
||||
// 1. 从报名指导老师关联表查询(个人赛)
|
||||
const teacherRecords = await this.prisma.contestRegistrationTeacher.findMany({
|
||||
where: {
|
||||
userId,
|
||||
@ -375,9 +376,29 @@ export class ContestsService {
|
||||
},
|
||||
},
|
||||
});
|
||||
contestIds = Array.from(new Set(teacherRecords.map((r) => r.registration.contestId as number)));
|
||||
const contestIdsFromRegistration = teacherRecords.map((r) => r.registration.contestId as number);
|
||||
|
||||
// 2. 从团队成员表查询(团队赛,role='mentor')
|
||||
const mentorRecords = await this.prisma.contestTeamMember.findMany({
|
||||
where: {
|
||||
userId,
|
||||
role: 'mentor',
|
||||
},
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
contestId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const contestIdsFromTeam = mentorRecords.map((r) => r.team.contestId);
|
||||
|
||||
// 合并去重
|
||||
contestIds = Array.from(new Set([...contestIdsFromRegistration, ...contestIdsFromTeam]));
|
||||
} else {
|
||||
// 学生/默认:查询报名的赛事
|
||||
// 1. 从报名记录查询(个人赛报名或团队赛队长)
|
||||
const registrationWhere: any = {
|
||||
userId,
|
||||
};
|
||||
@ -393,7 +414,32 @@ export class ContestsService {
|
||||
},
|
||||
distinct: ['contestId'],
|
||||
});
|
||||
contestIds = registrations.map((r) => r.contestId);
|
||||
const contestIdsFromRegistration = registrations.map((r) => r.contestId);
|
||||
|
||||
// 2. 从团队成员表查询(团队赛成员,role='leader' 或 'member')
|
||||
const teamMemberWhere: any = {
|
||||
userId,
|
||||
role: { in: ['leader', 'member'] },
|
||||
};
|
||||
|
||||
if (tenantId) {
|
||||
teamMemberWhere.tenantId = tenantId;
|
||||
}
|
||||
|
||||
const teamMembers = await this.prisma.contestTeamMember.findMany({
|
||||
where: teamMemberWhere,
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
contestId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const contestIdsFromTeam = teamMembers.map((r) => r.team.contestId);
|
||||
|
||||
// 合并去重
|
||||
contestIds = Array.from(new Set([...contestIdsFromRegistration, ...contestIdsFromTeam]));
|
||||
}
|
||||
|
||||
if (contestIds.length === 0) {
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
UseGuards,
|
||||
Request,
|
||||
ParseIntPipe,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { RegistrationsService } from './registrations.service';
|
||||
import { CreateRegistrationDto } from './dto/create-registration.dto';
|
||||
@ -28,9 +29,9 @@ export class RegistrationsController {
|
||||
create(@Body() createRegistrationDto: CreateRegistrationDto, @Request() req) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
const creatorId = req.user?.id;
|
||||
const creatorId = req.user?.userId;
|
||||
return this.registrationsService.create(
|
||||
createRegistrationDto,
|
||||
tenantId,
|
||||
@ -45,6 +46,22 @@ export class RegistrationsController {
|
||||
return this.registrationsService.findAll(queryDto, tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户在某比赛中的报名记录(包括作为团队成员的情况)
|
||||
*/
|
||||
@Get('my/:contestId')
|
||||
@RequirePermission('contest:read')
|
||||
getMyRegistration(
|
||||
@Param('contestId', ParseIntPipe) contestId: number,
|
||||
@Request() req,
|
||||
) {
|
||||
const userId = req.user?.userId;
|
||||
if (!userId) {
|
||||
throw new BadRequestException('用户未登录');
|
||||
}
|
||||
return this.registrationsService.getMyRegistration(contestId, userId);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RequirePermission('contest:read')
|
||||
findOne(@Param('id', ParseIntPipe) id: number, @Request() req) {
|
||||
@ -60,7 +77,7 @@ export class RegistrationsController {
|
||||
@Request() req,
|
||||
) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
const operatorId = req.user?.id;
|
||||
const operatorId = req.user?.userId;
|
||||
return this.registrationsService.review(id, reviewDto, operatorId, tenantId);
|
||||
}
|
||||
|
||||
@ -73,9 +90,9 @@ export class RegistrationsController {
|
||||
) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
const creatorId = req.user?.id;
|
||||
const creatorId = req.user?.userId;
|
||||
return this.registrationsService.addTeacher(
|
||||
id,
|
||||
body.teacherUserId,
|
||||
@ -93,7 +110,7 @@ export class RegistrationsController {
|
||||
) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
return this.registrationsService.removeTeacher(id, teacherUserId, tenantId);
|
||||
}
|
||||
|
||||
@ -626,5 +626,111 @@ export class RegistrationsService {
|
||||
where: { id: teacherRecord.id },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户在某比赛中的报名记录(包括作为团队成员的情况)
|
||||
* @param contestId 比赛ID
|
||||
* @param userId 用户ID
|
||||
* @returns 报名记录或null
|
||||
*/
|
||||
async getMyRegistration(contestId: number, userId: number) {
|
||||
try {
|
||||
// 1. 先查询个人赛报名(直接按 userId 匹配)
|
||||
const individualReg = await this.prisma.contestRegistration.findFirst({
|
||||
where: {
|
||||
contestId,
|
||||
userId,
|
||||
registrationType: 'individual',
|
||||
registrationState: 'passed',
|
||||
},
|
||||
include: {
|
||||
contest: {
|
||||
select: {
|
||||
id: true,
|
||||
contestName: true,
|
||||
contestType: true,
|
||||
},
|
||||
},
|
||||
works: {
|
||||
where: { validState: 1, isLatest: true },
|
||||
orderBy: { submitTime: 'desc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (individualReg) {
|
||||
return individualReg;
|
||||
}
|
||||
|
||||
// 2. 查询用户所属的团队(该比赛的)
|
||||
const teamMemberships = await this.prisma.contestTeamMember.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
select: {
|
||||
teamId: true,
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
contestId: true,
|
||||
validState: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 过滤出该比赛且有效的团队
|
||||
const validTeamIds = teamMemberships
|
||||
.filter((m) => m.team?.contestId === contestId && m.team?.validState === 1)
|
||||
.map((m) => m.teamId);
|
||||
|
||||
if (validTeamIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 通过团队ID查询团队报名记录
|
||||
const teamReg = await this.prisma.contestRegistration.findFirst({
|
||||
where: {
|
||||
contestId,
|
||||
teamId: { in: validTeamIds },
|
||||
registrationType: 'team',
|
||||
registrationState: 'passed',
|
||||
},
|
||||
include: {
|
||||
contest: {
|
||||
select: {
|
||||
id: true,
|
||||
contestName: true,
|
||||
contestType: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
include: {
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
nickname: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
works: {
|
||||
where: { validState: 1, isLatest: true },
|
||||
orderBy: { submitTime: 'desc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return teamReg;
|
||||
} catch (error) {
|
||||
console.error('getMyRegistration error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
UseGuards,
|
||||
Request,
|
||||
ParseIntPipe,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { TeamsService } from './teams.service';
|
||||
import { CreateTeamDto } from './dto/create-team.dto';
|
||||
@ -27,9 +28,9 @@ export class TeamsController {
|
||||
create(@Body() createTeamDto: CreateTeamDto, @Request() req) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
const creatorId = req.user?.id;
|
||||
const creatorId = req.user?.userId;
|
||||
return this.teamsService.create(createTeamDto, tenantId, creatorId);
|
||||
}
|
||||
|
||||
@ -59,9 +60,9 @@ export class TeamsController {
|
||||
) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
const modifierId = req.user?.id;
|
||||
const modifierId = req.user?.userId;
|
||||
return this.teamsService.update(id, updateTeamDto, tenantId, modifierId);
|
||||
}
|
||||
|
||||
@ -74,9 +75,9 @@ export class TeamsController {
|
||||
) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
const creatorId = req.user?.id;
|
||||
const creatorId = req.user?.userId;
|
||||
return this.teamsService.inviteMember(
|
||||
teamId,
|
||||
inviteMemberDto,
|
||||
@ -94,7 +95,7 @@ export class TeamsController {
|
||||
) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
return this.teamsService.removeMember(teamId, userId, tenantId);
|
||||
}
|
||||
@ -104,7 +105,7 @@ export class TeamsController {
|
||||
remove(@Param('id', ParseIntPipe) id: number, @Request() req) {
|
||||
const tenantId = req.tenantId || req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('无法确定租户信息');
|
||||
throw new BadRequestException('无法确定租户信息');
|
||||
}
|
||||
return this.teamsService.remove(id, tenantId);
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ export class TeamsService {
|
||||
where.tenantId = tenantId;
|
||||
}
|
||||
|
||||
return this.prisma.contestTeam.findMany({
|
||||
const teams = await this.prisma.contestTeam.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
createTime: 'desc',
|
||||
@ -230,6 +230,13 @@ export class TeamsService {
|
||||
},
|
||||
},
|
||||
},
|
||||
registrations: {
|
||||
select: {
|
||||
id: true,
|
||||
registrationState: true,
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
members: true,
|
||||
@ -238,15 +245,24 @@ export class TeamsService {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 将报名状态扁平化到团队对象上
|
||||
return teams.map((team) => ({
|
||||
...team,
|
||||
registrationState: team.registrations?.[0]?.registrationState || 'pending',
|
||||
registrationId: team.registrations?.[0]?.id,
|
||||
}));
|
||||
}
|
||||
|
||||
async findOne(id: number, tenantId?: number) {
|
||||
async findOne(id: number, tenantId?: number, strictTenantCheck = false) {
|
||||
const where: any = {
|
||||
id,
|
||||
validState: 1,
|
||||
};
|
||||
|
||||
if (tenantId) {
|
||||
// 只有明确要求严格租户检查时才限制 tenantId
|
||||
// 通过 ID 查询单个团队时,ID 已经是唯一的,不需要再限制 tenantId
|
||||
if (tenantId && strictTenantCheck) {
|
||||
where.tenantId = tenantId;
|
||||
}
|
||||
|
||||
|
||||
@ -257,6 +257,8 @@ export interface ContestTeam {
|
||||
createTime?: string;
|
||||
modifyTime?: string;
|
||||
validState?: number;
|
||||
registrationState?: string; // 报名状态
|
||||
registrationId?: number; // 报名记录ID
|
||||
leader?: {
|
||||
id: number;
|
||||
username: string;
|
||||
@ -730,6 +732,16 @@ export const registrationsApi = {
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取当前用户在某比赛中的报名记录(包括作为团队成员的情况)
|
||||
getMyRegistration: async (
|
||||
contestId: number
|
||||
): Promise<ContestRegistration | null> => {
|
||||
const response = await request.get<any, ContestRegistration | null>(
|
||||
`/contests/registrations/my/${contestId}`
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 创建报名
|
||||
create: async (
|
||||
data: CreateRegistrationForm
|
||||
|
||||
@ -105,37 +105,36 @@
|
||||
|
||||
<!-- 底部区域 -->
|
||||
<div class="card-footer">
|
||||
<span
|
||||
class="status-dot"
|
||||
:class="{ 'status-ongoing': contest.status === 'ongoing' }"
|
||||
></span>
|
||||
<span class="status-text">{{ getStatusText(contest) }}</span>
|
||||
<div class="status-row">
|
||||
<span
|
||||
class="status-dot"
|
||||
:class="{ 'status-ongoing': contest.status === 'ongoing' }"
|
||||
></span>
|
||||
<span class="status-text">{{ getStatusText(contest) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区域 - 我的赛事tab显示 -->
|
||||
<div v-if="activeTab === 'my'" class="card-actions" @click.stop>
|
||||
<!-- 学生角色按钮 -->
|
||||
<template v-if="userRole === 'student'">
|
||||
<template v-if="contest.contestType === 'individual'">
|
||||
<a-button
|
||||
v-if="isSubmitting(contest)"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleUploadWork(contest.id)"
|
||||
>
|
||||
上传作品
|
||||
</a-button>
|
||||
<a-button size="small" @click="handleViewWorks(contest.id)">
|
||||
参赛作品
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-button size="small" @click="handleViewWorks(contest.id)">
|
||||
参赛作品
|
||||
</a-button>
|
||||
<a-button size="small" @click="handleViewTeam(contest.id)">
|
||||
我的队伍
|
||||
</a-button>
|
||||
</template>
|
||||
<a-button
|
||||
v-if="isSubmitting(contest)"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleUploadWork(contest.id)"
|
||||
>
|
||||
上传作品
|
||||
</a-button>
|
||||
<a-button size="small" @click="handleViewWorks(contest.id)">
|
||||
参赛作品
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="contest.contestType === 'team'"
|
||||
size="small"
|
||||
@click="handleViewTeam(contest)"
|
||||
>
|
||||
我的队伍
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- 教师角色按钮 -->
|
||||
@ -199,6 +198,52 @@
|
||||
v-model:open="viewWorkDrawerVisible"
|
||||
:contest-id="currentContestIdForView"
|
||||
/>
|
||||
|
||||
<!-- 我的队伍弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="teamModalVisible"
|
||||
:title="`我的队伍 - ${currentTeamContest?.contestName || ''}`"
|
||||
:footer="null"
|
||||
width="600px"
|
||||
>
|
||||
<a-spin :spinning="teamLoading">
|
||||
<div v-if="myTeamInfo" class="team-info">
|
||||
<a-descriptions :column="2" bordered style="margin-bottom: 16px">
|
||||
<a-descriptions-item label="团队名称">
|
||||
{{ myTeamInfo.teamName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="成员数">
|
||||
{{ myTeamInfo.members?.length || 0 }}人
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<div class="team-members">
|
||||
<div class="members-title">团队成员</div>
|
||||
<a-table
|
||||
:columns="memberColumns"
|
||||
:data-source="myTeamInfo.members || []"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
{{ record.user?.nickname || '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'username'">
|
||||
{{ record.user?.username || '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'role'">
|
||||
<a-tag :color="getMemberRoleColor(record.role)">
|
||||
{{ getMemberRoleText(record.role) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-else description="暂无团队信息" />
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -216,8 +261,10 @@ import {
|
||||
import dayjs from "dayjs"
|
||||
import {
|
||||
contestsApi,
|
||||
registrationsApi,
|
||||
type Contest,
|
||||
type QueryContestParams,
|
||||
type ContestTeam,
|
||||
} from "@/api/contests"
|
||||
import { useAuthStore } from "@/stores/auth"
|
||||
import SubmitWorkDrawer from "./components/SubmitWorkDrawer.vue"
|
||||
@ -374,9 +421,59 @@ const handleViewWorks = (id: number) => {
|
||||
}
|
||||
|
||||
// 查看我的队伍
|
||||
const handleViewTeam = (id: number) => {
|
||||
// TODO: 跳转到我的队伍页面或打开抽屉
|
||||
message.info("查看我的队伍功能开发中")
|
||||
const teamModalVisible = ref(false)
|
||||
const teamLoading = ref(false)
|
||||
const currentTeamContest = ref<Contest | null>(null)
|
||||
const myTeamInfo = ref<ContestTeam | null>(null)
|
||||
|
||||
// 团队成员表格列
|
||||
const memberColumns = [
|
||||
{ title: "姓名", key: "nickname", width: 120 },
|
||||
{ title: "账号", key: "username", width: 150 },
|
||||
{ title: "角色", key: "role", width: 100 },
|
||||
]
|
||||
|
||||
// 获取成员角色颜色
|
||||
const getMemberRoleColor = (role?: string) => {
|
||||
switch (role) {
|
||||
case "leader":
|
||||
return "gold"
|
||||
case "mentor":
|
||||
return "purple"
|
||||
default:
|
||||
return "blue"
|
||||
}
|
||||
}
|
||||
|
||||
// 获取成员角色文本
|
||||
const getMemberRoleText = (role?: string) => {
|
||||
switch (role) {
|
||||
case "leader":
|
||||
return "队长"
|
||||
case "mentor":
|
||||
return "指导老师"
|
||||
default:
|
||||
return "成员"
|
||||
}
|
||||
}
|
||||
|
||||
const handleViewTeam = async (contest: Contest) => {
|
||||
currentTeamContest.value = contest
|
||||
teamModalVisible.value = true
|
||||
teamLoading.value = true
|
||||
myTeamInfo.value = null
|
||||
|
||||
try {
|
||||
// 获取用户在该比赛的报名记录(包括团队信息)
|
||||
const registration = await registrationsApi.getMyRegistration(contest.id)
|
||||
if (registration?.team) {
|
||||
myTeamInfo.value = registration.team
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "获取团队信息失败")
|
||||
} finally {
|
||||
teamLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 教师相关操作 =====
|
||||
@ -797,11 +894,17 @@ $primary-light: #40a9ff;
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f5f5f5;
|
||||
margin-top: auto;
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@ -823,16 +926,15 @@ $primary-light: #40a9ff;
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
gap: 6px;
|
||||
|
||||
// 渐变主要按钮 - 蓝色系
|
||||
:deep(.ant-btn-primary) {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 14px;
|
||||
padding: 4px 12px;
|
||||
height: auto;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
$primary 0%,
|
||||
@ -860,10 +962,10 @@ $primary-light: #40a9ff;
|
||||
// 渐变次要按钮
|
||||
:deep(.ant-btn-default) {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 14px;
|
||||
padding: 4px 12px;
|
||||
height: auto;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf0 100%);
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
|
||||
@ -895,6 +997,18 @@ $primary-light: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
// 团队信息弹窗样式
|
||||
.team-info {
|
||||
.members-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式适配
|
||||
@media (max-width: 768px) {
|
||||
.contests-activities-page {
|
||||
|
||||
@ -360,23 +360,13 @@ const fetchRegistrationId = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await registrationsApi.getList({
|
||||
contestId: props.contestId,
|
||||
userId: authStore.user.id,
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
})
|
||||
// 获取用户的报名记录(包括作为团队成员的情况)
|
||||
const registration = await registrationsApi.getMyRegistration(props.contestId)
|
||||
|
||||
if (response.list && response.list.length > 0) {
|
||||
const registration = response.list[0]
|
||||
if (registration.registrationState === "passed") {
|
||||
registrationIdRef.value = registration.id
|
||||
} else {
|
||||
message.warning("您的报名尚未通过审核,无法上传作品")
|
||||
visible.value = false
|
||||
}
|
||||
if (registration) {
|
||||
registrationIdRef.value = registration.id
|
||||
} else {
|
||||
message.warning("您尚未报名该赛事,无法上传作品")
|
||||
message.warning("您尚未报名该赛事或报名未通过,无法上传作品")
|
||||
visible.value = false
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
||||
@ -205,23 +205,16 @@ const fetchUserWork = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 先获取用户的报名记录
|
||||
const registrationResponse = await registrationsApi.getList({
|
||||
contestId: props.contestId,
|
||||
userId: userId,
|
||||
registrationType: "individual",
|
||||
registrationState: "passed",
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
// 获取用户的报名记录(包括作为团队成员的情况)
|
||||
const registration = await registrationsApi.getMyRegistration(props.contestId)
|
||||
|
||||
if (registrationResponse.list.length === 0) {
|
||||
if (!registration) {
|
||||
message.warning("您尚未报名该赛事或报名未通过,无法查看作品")
|
||||
visible.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const registrationId = registrationResponse.list[0].id
|
||||
const registrationId = registration.id
|
||||
|
||||
// 获取该报名的所有作品版本,取最新版本
|
||||
const works = await worksApi.getVersions(registrationId)
|
||||
|
||||
@ -355,7 +355,7 @@
|
||||
<a-descriptions :column="3" bordered style="margin-bottom: 16px">
|
||||
<a-descriptions-item label="团队名称">{{ currentTeam.teamName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="队长">{{ currentTeam.leader?.nickname || "-" }}</a-descriptions-item>
|
||||
<a-descriptions-item label="成员数">{{ currentTeam._count?.members || 0 }}人</a-descriptions-item>
|
||||
<a-descriptions-item label="成员数">{{ teamMembers.length || currentTeam._count?.members || 0 }}人</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<a-table
|
||||
:columns="memberColumns"
|
||||
@ -778,15 +778,19 @@ const handleViewMembers = async (record: ContestRegistration) => {
|
||||
message.warning("暂无团队信息")
|
||||
return
|
||||
}
|
||||
currentTeam.value = record.team
|
||||
membersModalVisible.value = true
|
||||
membersLoading.value = true
|
||||
currentTeam.value = null
|
||||
teamMembers.value = []
|
||||
|
||||
try {
|
||||
const teamDetail = await teamsApi.getDetail(record.team.id)
|
||||
// 更新完整的团队信息(包含 leader 和成员数)
|
||||
currentTeam.value = teamDetail
|
||||
teamMembers.value = teamDetail.members || []
|
||||
} catch (error: any) {
|
||||
message.error("获取团队成员失败")
|
||||
currentTeam.value = record.team // 降级使用列表中的数据
|
||||
teamMembers.value = []
|
||||
} finally {
|
||||
membersLoading.value = false
|
||||
|
||||
@ -74,7 +74,7 @@
|
||||
</div>
|
||||
<div class="loading-info">
|
||||
<div class="loading-title">
|
||||
{{ task?.status === 'pending' ? '排队中' : 'AI 生成中' }}
|
||||
{{ task?.status === "pending" ? "排队中" : "AI 生成中" }}
|
||||
</div>
|
||||
<div v-if="task?.status === 'pending'" class="loading-text">
|
||||
<p>
|
||||
@ -83,9 +83,9 @@
|
||||
</p>
|
||||
<p>
|
||||
预计时间:
|
||||
<span class="highlight"
|
||||
>{{ formatEstimatedTime(queueInfo.estimatedTime) }}</span
|
||||
>
|
||||
<span class="highlight">{{
|
||||
formatEstimatedTime(queueInfo.estimatedTime)
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="loading-text">
|
||||
@ -290,10 +290,7 @@ const handleCardClick = (index: number) => {
|
||||
|
||||
// 存储到 sessionStorage(避免URL过长)
|
||||
if (allResultUrls.length > 1) {
|
||||
sessionStorage.setItem(
|
||||
"model-viewer-urls",
|
||||
JSON.stringify(allResultUrls)
|
||||
)
|
||||
sessionStorage.setItem("model-viewer-urls", JSON.stringify(allResultUrls))
|
||||
sessionStorage.setItem("model-viewer-index", String(index))
|
||||
// 清除单URL存储
|
||||
sessionStorage.removeItem("model-viewer-url")
|
||||
@ -777,7 +774,7 @@ $gradient-card: linear-gradient(
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 120px;
|
||||
width: 200px;
|
||||
height: 4px;
|
||||
background: rgba($primary, 0.2);
|
||||
border-radius: 2px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user