修复团队赛事相关功能

1. 修复团队成员无法在"我参与的赛事"中看到团队赛事的问题
2. 修复教师作为指导老师无法看到团队赛事的问题
3. 修复上传作品/参赛作品/我的队伍按钮500错误(userId获取方式错误)
4. 修复管理端成员弹框队长名称和成员数显示问题
5. 新增getMyRegistration接口支持团队成员查询报名状态
6. 优化赛事卡片按钮布局

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
zhangxiaohua 2026-01-22 15:27:06 +08:00
parent 0cdc5d1ceb
commit ac0c38c04a
11 changed files with 390 additions and 94 deletions

View File

@ -363,6 +363,7 @@ export class ContestsService {
contestIds = judgeRecords.map((r) => r.contestId); contestIds = judgeRecords.map((r) => r.contestId);
} else if (role === 'teacher') { } else if (role === 'teacher') {
// 教师:查询作为指导老师参与的赛事 // 教师:查询作为指导老师参与的赛事
// 1. 从报名指导老师关联表查询(个人赛)
const teacherRecords = await this.prisma.contestRegistrationTeacher.findMany({ const teacherRecords = await this.prisma.contestRegistrationTeacher.findMany({
where: { where: {
userId, 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 { } else {
// 学生/默认:查询报名的赛事 // 学生/默认:查询报名的赛事
// 1. 从报名记录查询(个人赛报名或团队赛队长)
const registrationWhere: any = { const registrationWhere: any = {
userId, userId,
}; };
@ -393,7 +414,32 @@ export class ContestsService {
}, },
distinct: ['contestId'], 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) { if (contestIds.length === 0) {

View File

@ -10,6 +10,7 @@ import {
UseGuards, UseGuards,
Request, Request,
ParseIntPipe, ParseIntPipe,
BadRequestException,
} from '@nestjs/common'; } from '@nestjs/common';
import { RegistrationsService } from './registrations.service'; import { RegistrationsService } from './registrations.service';
import { CreateRegistrationDto } from './dto/create-registration.dto'; import { CreateRegistrationDto } from './dto/create-registration.dto';
@ -28,9 +29,9 @@ export class RegistrationsController {
create(@Body() createRegistrationDto: CreateRegistrationDto, @Request() req) { create(@Body() createRegistrationDto: CreateRegistrationDto, @Request() req) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new Error('无法确定租户信息'); throw new BadRequestException('无法确定租户信息');
} }
const creatorId = req.user?.id; const creatorId = req.user?.userId;
return this.registrationsService.create( return this.registrationsService.create(
createRegistrationDto, createRegistrationDto,
tenantId, tenantId,
@ -45,6 +46,22 @@ export class RegistrationsController {
return this.registrationsService.findAll(queryDto, tenantId); 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') @Get(':id')
@RequirePermission('contest:read') @RequirePermission('contest:read')
findOne(@Param('id', ParseIntPipe) id: number, @Request() req) { findOne(@Param('id', ParseIntPipe) id: number, @Request() req) {
@ -60,7 +77,7 @@ export class RegistrationsController {
@Request() req, @Request() req,
) { ) {
const tenantId = req.tenantId || req.user?.tenantId; 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); return this.registrationsService.review(id, reviewDto, operatorId, tenantId);
} }
@ -73,9 +90,9 @@ export class RegistrationsController {
) { ) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new Error('无法确定租户信息'); throw new BadRequestException('无法确定租户信息');
} }
const creatorId = req.user?.id; const creatorId = req.user?.userId;
return this.registrationsService.addTeacher( return this.registrationsService.addTeacher(
id, id,
body.teacherUserId, body.teacherUserId,
@ -93,7 +110,7 @@ export class RegistrationsController {
) { ) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new Error('无法确定租户信息'); throw new BadRequestException('无法确定租户信息');
} }
return this.registrationsService.removeTeacher(id, teacherUserId, tenantId); return this.registrationsService.removeTeacher(id, teacherUserId, tenantId);
} }

View File

@ -626,5 +626,111 @@ export class RegistrationsService {
where: { id: teacherRecord.id }, 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;
}
}
} }

View File

@ -9,6 +9,7 @@ import {
UseGuards, UseGuards,
Request, Request,
ParseIntPipe, ParseIntPipe,
BadRequestException,
} from '@nestjs/common'; } from '@nestjs/common';
import { TeamsService } from './teams.service'; import { TeamsService } from './teams.service';
import { CreateTeamDto } from './dto/create-team.dto'; import { CreateTeamDto } from './dto/create-team.dto';
@ -27,9 +28,9 @@ export class TeamsController {
create(@Body() createTeamDto: CreateTeamDto, @Request() req) { create(@Body() createTeamDto: CreateTeamDto, @Request() req) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!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); return this.teamsService.create(createTeamDto, tenantId, creatorId);
} }
@ -59,9 +60,9 @@ export class TeamsController {
) { ) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!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); return this.teamsService.update(id, updateTeamDto, tenantId, modifierId);
} }
@ -74,9 +75,9 @@ export class TeamsController {
) { ) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new Error('无法确定租户信息'); throw new BadRequestException('无法确定租户信息');
} }
const creatorId = req.user?.id; const creatorId = req.user?.userId;
return this.teamsService.inviteMember( return this.teamsService.inviteMember(
teamId, teamId,
inviteMemberDto, inviteMemberDto,
@ -94,7 +95,7 @@ export class TeamsController {
) { ) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new Error('无法确定租户信息'); throw new BadRequestException('无法确定租户信息');
} }
return this.teamsService.removeMember(teamId, userId, tenantId); return this.teamsService.removeMember(teamId, userId, tenantId);
} }
@ -104,7 +105,7 @@ export class TeamsController {
remove(@Param('id', ParseIntPipe) id: number, @Request() req) { remove(@Param('id', ParseIntPipe) id: number, @Request() req) {
const tenantId = req.tenantId || req.user?.tenantId; const tenantId = req.tenantId || req.user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new Error('无法确定租户信息'); throw new BadRequestException('无法确定租户信息');
} }
return this.teamsService.remove(id, tenantId); return this.teamsService.remove(id, tenantId);
} }

View File

@ -206,7 +206,7 @@ export class TeamsService {
where.tenantId = tenantId; where.tenantId = tenantId;
} }
return this.prisma.contestTeam.findMany({ const teams = await this.prisma.contestTeam.findMany({
where, where,
orderBy: { orderBy: {
createTime: 'desc', createTime: 'desc',
@ -230,6 +230,13 @@ export class TeamsService {
}, },
}, },
}, },
registrations: {
select: {
id: true,
registrationState: true,
},
take: 1,
},
_count: { _count: {
select: { select: {
members: true, 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 = { const where: any = {
id, id,
validState: 1, validState: 1,
}; };
if (tenantId) { // 只有明确要求严格租户检查时才限制 tenantId
// 通过 ID 查询单个团队时ID 已经是唯一的,不需要再限制 tenantId
if (tenantId && strictTenantCheck) {
where.tenantId = tenantId; where.tenantId = tenantId;
} }

View File

@ -257,6 +257,8 @@ export interface ContestTeam {
createTime?: string; createTime?: string;
modifyTime?: string; modifyTime?: string;
validState?: number; validState?: number;
registrationState?: string; // 报名状态
registrationId?: number; // 报名记录ID
leader?: { leader?: {
id: number; id: number;
username: string; username: string;
@ -730,6 +732,16 @@ export const registrationsApi = {
return response; 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 ( create: async (
data: CreateRegistrationForm data: CreateRegistrationForm

View File

@ -105,37 +105,36 @@
<!-- 底部区域 --> <!-- 底部区域 -->
<div class="card-footer"> <div class="card-footer">
<span <div class="status-row">
class="status-dot" <span
:class="{ 'status-ongoing': contest.status === 'ongoing' }" class="status-dot"
></span> :class="{ 'status-ongoing': contest.status === 'ongoing' }"
<span class="status-text">{{ getStatusText(contest) }}</span> ></span>
<span class="status-text">{{ getStatusText(contest) }}</span>
</div>
<!-- 操作按钮区域 - 我的赛事tab显示 --> <!-- 操作按钮区域 - 我的赛事tab显示 -->
<div v-if="activeTab === 'my'" class="card-actions" @click.stop> <div v-if="activeTab === 'my'" class="card-actions" @click.stop>
<!-- 学生角色按钮 --> <!-- 学生角色按钮 -->
<template v-if="userRole === 'student'"> <template v-if="userRole === 'student'">
<template v-if="contest.contestType === 'individual'"> <a-button
<a-button v-if="isSubmitting(contest)"
v-if="isSubmitting(contest)" type="primary"
type="primary" size="small"
size="small" @click="handleUploadWork(contest.id)"
@click="handleUploadWork(contest.id)" >
> 上传作品
上传作品 </a-button>
</a-button> <a-button size="small" @click="handleViewWorks(contest.id)">
<a-button size="small" @click="handleViewWorks(contest.id)"> 参赛作品
参赛作品 </a-button>
</a-button> <a-button
</template> v-if="contest.contestType === 'team'"
<template v-else> size="small"
<a-button size="small" @click="handleViewWorks(contest.id)"> @click="handleViewTeam(contest)"
参赛作品 >
</a-button> 我的队伍
<a-button size="small" @click="handleViewTeam(contest.id)"> </a-button>
我的队伍
</a-button>
</template>
</template> </template>
<!-- 教师角色按钮 --> <!-- 教师角色按钮 -->
@ -199,6 +198,52 @@
v-model:open="viewWorkDrawerVisible" v-model:open="viewWorkDrawerVisible"
:contest-id="currentContestIdForView" :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> </div>
</template> </template>
@ -216,8 +261,10 @@ import {
import dayjs from "dayjs" import dayjs from "dayjs"
import { import {
contestsApi, contestsApi,
registrationsApi,
type Contest, type Contest,
type QueryContestParams, type QueryContestParams,
type ContestTeam,
} from "@/api/contests" } from "@/api/contests"
import { useAuthStore } from "@/stores/auth" import { useAuthStore } from "@/stores/auth"
import SubmitWorkDrawer from "./components/SubmitWorkDrawer.vue" import SubmitWorkDrawer from "./components/SubmitWorkDrawer.vue"
@ -374,9 +421,59 @@ const handleViewWorks = (id: number) => {
} }
// //
const handleViewTeam = (id: number) => { const teamModalVisible = ref(false)
// TODO: const teamLoading = ref(false)
message.info("查看我的队伍功能开发中") 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 { .card-footer {
display: flex; display: flex;
align-items: center; flex-direction: column;
gap: 12px;
padding-top: 12px; padding-top: 12px;
border-top: 1px solid #f5f5f5; border-top: 1px solid #f5f5f5;
margin-top: auto; margin-top: auto;
.status-row {
display: flex;
align-items: center;
}
.status-dot { .status-dot {
width: 8px; width: 8px;
height: 8px; height: 8px;
@ -823,16 +926,15 @@ $primary-light: #40a9ff;
.card-actions { .card-actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 6px;
margin-left: auto;
// - // -
:deep(.ant-btn-primary) { :deep(.ant-btn-primary) {
border: none; border: none;
border-radius: 16px; border-radius: 14px;
padding: 6px 16px; padding: 4px 12px;
height: auto; height: auto;
font-size: 13px; font-size: 12px;
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
$primary 0%, $primary 0%,
@ -860,10 +962,10 @@ $primary-light: #40a9ff;
// //
:deep(.ant-btn-default) { :deep(.ant-btn-default) {
border: none; border: none;
border-radius: 16px; border-radius: 14px;
padding: 6px 16px; padding: 4px 12px;
height: auto; height: auto;
font-size: 13px; font-size: 12px;
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf0 100%); background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf0 100%);
color: rgba(0, 0, 0, 0.75); color: rgba(0, 0, 0, 0.75);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06); 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) { @media (max-width: 768px) {
.contests-activities-page { .contests-activities-page {

View File

@ -360,23 +360,13 @@ const fetchRegistrationId = async () => {
} }
try { try {
const response = await registrationsApi.getList({ //
contestId: props.contestId, const registration = await registrationsApi.getMyRegistration(props.contestId)
userId: authStore.user.id,
page: 1,
pageSize: 1,
})
if (response.list && response.list.length > 0) { if (registration) {
const registration = response.list[0] registrationIdRef.value = registration.id
if (registration.registrationState === "passed") {
registrationIdRef.value = registration.id
} else {
message.warning("您的报名尚未通过审核,无法上传作品")
visible.value = false
}
} else { } else {
message.warning("您尚未报名该赛事,无法上传作品") message.warning("您尚未报名该赛事或报名未通过,无法上传作品")
visible.value = false visible.value = false
} }
} catch (error: any) { } catch (error: any) {

View File

@ -205,23 +205,16 @@ const fetchUserWork = async () => {
return return
} }
// //
const registrationResponse = await registrationsApi.getList({ const registration = await registrationsApi.getMyRegistration(props.contestId)
contestId: props.contestId,
userId: userId,
registrationType: "individual",
registrationState: "passed",
page: 1,
pageSize: 10,
})
if (registrationResponse.list.length === 0) { if (!registration) {
message.warning("您尚未报名该赛事或报名未通过,无法查看作品") message.warning("您尚未报名该赛事或报名未通过,无法查看作品")
visible.value = false visible.value = false
return return
} }
const registrationId = registrationResponse.list[0].id const registrationId = registration.id
// //
const works = await worksApi.getVersions(registrationId) const works = await worksApi.getVersions(registrationId)

View File

@ -355,7 +355,7 @@
<a-descriptions :column="3" bordered style="margin-bottom: 16px"> <a-descriptions :column="3" bordered style="margin-bottom: 16px">
<a-descriptions-item label="团队名称">{{ currentTeam.teamName }}</a-descriptions-item> <a-descriptions-item label="团队名称">{{ currentTeam.teamName }}</a-descriptions-item>
<a-descriptions-item label="队长">{{ currentTeam.leader?.nickname || "-" }}</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-descriptions>
<a-table <a-table
:columns="memberColumns" :columns="memberColumns"
@ -778,15 +778,19 @@ const handleViewMembers = async (record: ContestRegistration) => {
message.warning("暂无团队信息") message.warning("暂无团队信息")
return return
} }
currentTeam.value = record.team
membersModalVisible.value = true membersModalVisible.value = true
membersLoading.value = true membersLoading.value = true
currentTeam.value = null
teamMembers.value = []
try { try {
const teamDetail = await teamsApi.getDetail(record.team.id) const teamDetail = await teamsApi.getDetail(record.team.id)
// leader
currentTeam.value = teamDetail
teamMembers.value = teamDetail.members || [] teamMembers.value = teamDetail.members || []
} catch (error: any) { } catch (error: any) {
message.error("获取团队成员失败") message.error("获取团队成员失败")
currentTeam.value = record.team // 使
teamMembers.value = [] teamMembers.value = []
} finally { } finally {
membersLoading.value = false membersLoading.value = false

View File

@ -74,7 +74,7 @@
</div> </div>
<div class="loading-info"> <div class="loading-info">
<div class="loading-title"> <div class="loading-title">
{{ task?.status === 'pending' ? '排队中' : 'AI 生成中' }} {{ task?.status === "pending" ? "排队中" : "AI 生成中" }}
</div> </div>
<div v-if="task?.status === 'pending'" class="loading-text"> <div v-if="task?.status === 'pending'" class="loading-text">
<p> <p>
@ -83,9 +83,9 @@
</p> </p>
<p> <p>
预计时间: 预计时间:
<span class="highlight" <span class="highlight">{{
>{{ formatEstimatedTime(queueInfo.estimatedTime) }}</span formatEstimatedTime(queueInfo.estimatedTime)
> }}</span>
</p> </p>
</div> </div>
<div v-else class="loading-text"> <div v-else class="loading-text">
@ -290,10 +290,7 @@ const handleCardClick = (index: number) => {
// sessionStorageURL // sessionStorageURL
if (allResultUrls.length > 1) { if (allResultUrls.length > 1) {
sessionStorage.setItem( sessionStorage.setItem("model-viewer-urls", JSON.stringify(allResultUrls))
"model-viewer-urls",
JSON.stringify(allResultUrls)
)
sessionStorage.setItem("model-viewer-index", String(index)) sessionStorage.setItem("model-viewer-index", String(index))
// URL // URL
sessionStorage.removeItem("model-viewer-url") sessionStorage.removeItem("model-viewer-url")
@ -777,7 +774,7 @@ $gradient-card: linear-gradient(
} }
.progress-bar { .progress-bar {
width: 120px; width: 200px;
height: 4px; height: 4px;
background: rgba($primary, 0.2); background: rgba($primary, 0.2);
border-radius: 2px; border-radius: 2px;