feat: 成果发布列表评审未完成时禁用发布成果入口

Made-with: Cursor
This commit is contained in:
zhonghua 2026-04-15 14:21:43 +08:00
parent 6c3b2f4a1d
commit ea40131084

View File

@ -8,12 +8,8 @@
<!-- 统计卡片 -->
<div class="stats-row">
<div
v-for="item in statsItems"
:key="item.key"
:class="['stat-card', { active: activeFilter === item.key }]"
@click="handleStatClick(item.key)"
>
<div v-for="item in statsItems" :key="item.key" :class="['stat-card', { active: activeFilter === item.key }]"
@click="handleStatClick(item.key)">
<div class="stat-icon" :style="{ background: item.bgColor }">
<component :is="item.icon" :style="{ color: item.color }" />
</div>
@ -28,46 +24,31 @@
<div class="filter-bar">
<a-form layout="inline" :model="superSearch" @finish="handleSuperSearch">
<a-form-item label="所属活动">
<a-select
v-model:value="superSearch.contestId"
placeholder="全部活动"
allow-clear
show-search
:filter-option="filterContestOption"
style="width: 200px"
:options="contestOptions"
/>
<a-select v-model:value="superSearch.contestId" placeholder="全部活动" allow-clear show-search
:filter-option="filterContestOption" style="width: 200px" :options="contestOptions" />
</a-form-item>
<a-form-item label="发布状态">
<a-select
v-model:value="superSearch.resultState"
placeholder="全部"
allow-clear
style="width: 110px"
>
<a-select v-model:value="superSearch.resultState" placeholder="全部" allow-clear style="width: 110px">
<a-select-option value="published">已发布</a-select-option>
<a-select-option value="unpublished">未发布</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="主办机构">
<a-select
v-model:value="superSearch.creatorTenantId"
placeholder="全部机构"
allow-clear
show-search
:filter-option="filterTenantOption"
style="width: 160px"
:options="tenantOptions"
/>
<a-select v-model:value="superSearch.creatorTenantId" placeholder="全部机构" allow-clear show-search
:filter-option="filterTenantOption" style="width: 160px" :options="tenantOptions" />
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" html-type="submit">
<template #icon><SearchOutlined /></template>
<template #icon>
<SearchOutlined />
</template>
搜索
</a-button>
<a-button @click="handleSuperReset">
<template #icon><ReloadOutlined /></template>
<template #icon>
<ReloadOutlined />
</template>
重置
</a-button>
</a-space>
@ -76,26 +57,15 @@
</div>
<!-- 表格 -->
<a-table
:columns="superColumns"
:data-source="superDataSource"
:loading="superLoading"
:pagination="superPagination"
row-key="id"
@change="handleSuperTableChange"
class="data-table"
>
<a-table :columns="superColumns" :data-source="superDataSource" :loading="superLoading"
:pagination="superPagination" row-key="id" @change="handleSuperTableChange" class="data-table">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">
{{ (superPagination.current - 1) * superPagination.pageSize + index + 1 }}
</template>
<template v-else-if="column.key === 'contestName'">
<a-button
type="link"
size="small"
class="contest-link"
@click="router.push(`/${tenantCode}/contests/${record.id}/overview`)"
>
<a-button type="link" size="small" class="contest-link"
@click="router.push(`/${tenantCode}/contests/${record.id}/overview`)">
{{ record.contestName }}
</a-button>
</template>
@ -137,7 +107,8 @@
<!-- 统计概览 -->
<div class="stats-row">
<div v-for="item in orgStatsItems" :key="item.key" :class="['stat-card', { active: orgActiveFilter === item.key }]" @click="handleOrgStatClick(item.key)">
<div v-for="item in orgStatsItems" :key="item.key"
:class="['stat-card', { active: orgActiveFilter === item.key }]" @click="handleOrgStatClick(item.key)">
<div class="stat-icon" :style="{ background: item.bgColor }">
<component :is="item.icon" :style="{ color: item.color }" />
</div>
@ -155,25 +126,33 @@
<a-input v-model:value="searchParams.contestName" placeholder="请输入活动名称" allow-clear style="width: 200px" />
</a-form-item>
<a-form-item label="活动类型">
<a-select v-model:value="searchParams.contestType" placeholder="全部" allow-clear style="width: 120px" @change="handleSearch">
<a-select v-model:value="searchParams.contestType" placeholder="全部" allow-clear style="width: 120px"
@change="handleSearch">
<a-select-option value="individual">个人参与</a-select-option>
<a-select-option value="team">团队参与</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="发布状态">
<a-select v-model:value="orgResultState" placeholder="全部" allow-clear style="width: 110px" @change="handleSearch">
<a-select v-model:value="orgResultState" placeholder="全部" allow-clear style="width: 110px"
@change="handleSearch">
<a-select-option value="published">已发布</a-select-option>
<a-select-option value="unpublished">未发布</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit"><template #icon><SearchOutlined /></template> 搜索</a-button>
<a-button style="margin-left: 8px" @click="handleReset"><template #icon><ReloadOutlined /></template> 重置</a-button>
<a-button type="primary" html-type="submit"><template #icon>
<SearchOutlined />
</template> 搜索</a-button>
<a-button style="margin-left: 8px" @click="handleReset"><template #icon>
<ReloadOutlined />
</template>
重置</a-button>
</a-form-item>
</a-form>
</div>
<a-table :columns="orgColumns" :data-source="dataSource" :loading="loading" :pagination="pagination" row-key="id" @change="handleTableChange" class="data-table">
<a-table :columns="orgColumns" :data-source="dataSource" :loading="loading" :pagination="pagination" row-key="id"
@change="handleTableChange" class="data-table">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
@ -182,7 +161,8 @@
<a @click="handleViewDetail(record)">{{ record.contestName }}</a>
</template>
<template v-else-if="column.key === 'contestType'">
<a-tag :color="record.contestType === 'individual' ? 'blue' : 'green'">{{ record.contestType === 'individual' ? '个人' : '团队' }}</a-tag>
<a-tag :color="record.contestType === 'individual' ? 'blue' : 'green'">{{ record.contestType ===
'individual' ? '个人' : '团队' }}</a-tag>
</template>
<template v-else-if="column.key === 'registrationCount'">
{{ record._count?.registrations || 0 }}
@ -195,8 +175,17 @@
<a-tag v-else color="default">未发布</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<a-button v-if="record.resultState === 'published'" type="link" size="small" @click="handleViewDetail(record)">查看成果</a-button>
<a-button v-else type="link" size="small" style="color: #10b981" @click="handleViewDetail(record)">发布成果</a-button>
<a-button v-if="record.resultState === 'published'" type="link" size="small"
@click="handleViewDetail(record)">查看成果</a-button>
<template v-else-if="canPublishResults(record)">
<a-button type="link" size="small" style="color: #10b981"
@click="handleViewDetail(record)">发布成果</a-button>
</template>
<a-tooltip v-else placement="top" title="评审未完成,请待全部作品完成评审后再发布成果">
<span class="publish-results-btn-wrap">
<a-button type="link" size="small" style="color: #777" disabled>发布成果</a-button>
</span>
</a-tooltip>
</template>
</template>
</a-table>
@ -232,6 +221,16 @@ const isSuperAdmin = computed(() => authStore.hasAnyRole(['super_admin']))
const formatDate = (d?: string) => d ? dayjs(d).format("YYYY-MM-DD HH:mm") : "-"
/**
* 是否允许进入成果发布详情发布成果入口
* 与活动列表接口的 reviewedCount / totalWorksCount 一致有作品且已完成评审的作品数 作品总数
*/
function canPublishResults(record: Contest): boolean {
const totalWorks = Number(record.totalWorksCount ?? record._count?.works ?? 0)
const reviewed = Number(record.reviewedCount ?? 0)
return totalWorks > 0 && reviewed >= totalWorks
}
// =============================================
//
// =============================================
@ -455,11 +454,26 @@ $primary: #6366f1;
border: none;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
:deep(.ant-card-head) { border-bottom: none; .ant-card-head-title { font-size: 18px; font-weight: 600; } }
:deep(.ant-card-body) { padding: 0; }
:deep(.ant-card-head) {
border-bottom: none;
.ant-card-head-title {
font-size: 18px;
font-weight: 600;
}
}
.stats-row { display: flex; gap: 12px; margin-bottom: 16px; }
:deep(.ant-card-body) {
padding: 0;
}
}
.stats-row {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
.stat-card {
flex: 1;
@ -473,10 +487,43 @@ $primary: #6366f1;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.2s;
&:hover { box-shadow: 0 4px 16px rgba($primary, 0.12); }
&.active { border-color: $primary; background: rgba($primary, 0.02); }
.stat-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.stat-info { display: flex; flex-direction: column; .stat-count { font-size: 18px; font-weight: 700; color: #1e1b4b; line-height: 1.2; } .stat-label { font-size: 12px; color: #9ca3af; } }
&:hover {
box-shadow: 0 4px 16px rgba($primary, 0.12);
}
&.active {
border-color: $primary;
background: rgba($primary, 0.02);
}
.stat-icon {
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.stat-info {
display: flex;
flex-direction: column;
.stat-count {
font-size: 18px;
font-weight: 700;
color: #1e1b4b;
line-height: 1.2;
}
.stat-label {
font-size: 12px;
color: #9ca3af;
}
}
}
.filter-bar {
@ -493,13 +540,35 @@ $primary: #6366f1;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
overflow: hidden;
.ant-table-thead > tr > th { background: #fafafa; font-weight: 600; }
.ant-table-tbody > tr:hover > td { background: rgba($primary, 0.03); }
.ant-table-pagination { padding: 16px; margin: 0; }
.ant-table-thead>tr>th {
background: #fafafa;
font-weight: 600;
}
.ant-table-tbody>tr:hover>td {
background: rgba($primary, 0.03);
}
.ant-table-pagination {
padding: 16px;
margin: 0;
}
}
}
.contest-link { padding: 0; text-align: left; }
.text-muted { color: #d1d5db; }
.contest-link {
padding: 0;
text-align: left;
}
.text-muted {
color: #d1d5db;
}
/* 包裹禁用链接按钮,便于 Tooltip 触发且布局不塌缩 */
.publish-results-btn-wrap {
display: inline-block;
cursor: not-allowed;
}
</style>