1057 lines
28 KiB
Vue
1057 lines
28 KiB
Vue
<template>
|
|
<div class="registration-records-page">
|
|
<a-card class="mb-4">
|
|
<template #title>
|
|
<a-breadcrumb>
|
|
<a-breadcrumb-item>
|
|
<router-link :to="{ name: 'ContestsRegistrations', params: { tenantCode } }">报名管理</router-link>
|
|
</a-breadcrumb-item>
|
|
<a-breadcrumb-item>{{ contestName || '报名记录' }}</a-breadcrumb-item>
|
|
</a-breadcrumb>
|
|
</template>
|
|
<template #extra>
|
|
<a-space>
|
|
<a-button
|
|
v-permission="'contest:update'"
|
|
type="primary"
|
|
:disabled="selectedRowKeys.length === 0"
|
|
@click="handleBatchReview"
|
|
>
|
|
批量审核
|
|
</a-button>
|
|
<a-button @click="handleExport" :loading="exportLoading">
|
|
<template #icon><DownloadOutlined /></template>
|
|
导出
|
|
</a-button>
|
|
</a-space>
|
|
</template>
|
|
</a-card>
|
|
|
|
<!-- 个人赛搜索表单 -->
|
|
<a-form
|
|
v-if="contestType === 'individual'"
|
|
:model="searchParams"
|
|
layout="inline"
|
|
class="search-form"
|
|
@finish="handleSearch"
|
|
>
|
|
<a-form-item label="机构">
|
|
<a-input
|
|
v-model:value="searchParams.tenantName"
|
|
placeholder="请输入机构名称"
|
|
allow-clear
|
|
style="width: 150px"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="姓名">
|
|
<a-input
|
|
v-model:value="searchParams.nickname"
|
|
placeholder="请输入姓名"
|
|
allow-clear
|
|
style="width: 120px"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="报名账号">
|
|
<a-input
|
|
v-model:value="searchParams.accountNo"
|
|
placeholder="请输入账号"
|
|
allow-clear
|
|
style="width: 150px"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="审核状态">
|
|
<a-select
|
|
v-model:value="searchParams.registrationState"
|
|
placeholder="请选择状态"
|
|
allow-clear
|
|
style="width: 120px"
|
|
>
|
|
<a-select-option value="pending">待审核</a-select-option>
|
|
<a-select-option value="passed">已通过</a-select-option>
|
|
<a-select-option value="rejected">已拒绝</a-select-option>
|
|
</a-select>
|
|
</a-form-item>
|
|
<a-form-item label="报名时间">
|
|
<a-range-picker
|
|
v-model:value="dateRange"
|
|
style="width: 240px"
|
|
@change="handleDateChange"
|
|
/>
|
|
</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-form-item>
|
|
</a-form>
|
|
|
|
<!-- 团队赛搜索表单 -->
|
|
<a-form
|
|
v-else
|
|
:model="searchParams"
|
|
layout="inline"
|
|
class="search-form"
|
|
@finish="handleSearch"
|
|
>
|
|
<a-form-item label="机构">
|
|
<a-input
|
|
v-model:value="searchParams.tenantName"
|
|
placeholder="请输入机构名称"
|
|
allow-clear
|
|
style="width: 150px"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="队伍名称">
|
|
<a-input
|
|
v-model:value="searchParams.teamName"
|
|
placeholder="请输入队伍名称"
|
|
allow-clear
|
|
style="width: 120px"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="报名账号">
|
|
<a-input
|
|
v-model:value="searchParams.accountNo"
|
|
placeholder="请输入账号"
|
|
allow-clear
|
|
style="width: 150px"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="审核状态">
|
|
<a-select
|
|
v-model:value="searchParams.registrationState"
|
|
placeholder="请选择状态"
|
|
allow-clear
|
|
style="width: 120px"
|
|
>
|
|
<a-select-option value="pending">待审核</a-select-option>
|
|
<a-select-option value="passed">已通过</a-select-option>
|
|
<a-select-option value="rejected">已拒绝</a-select-option>
|
|
</a-select>
|
|
</a-form-item>
|
|
<a-form-item label="报名时间">
|
|
<a-range-picker
|
|
v-model:value="dateRange"
|
|
style="width: 240px"
|
|
@change="handleDateChange"
|
|
/>
|
|
</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-form-item>
|
|
</a-form>
|
|
|
|
<!-- 个人赛表格 -->
|
|
<a-table
|
|
v-if="contestType === 'individual'"
|
|
:columns="individualColumns"
|
|
:data-source="dataSource"
|
|
:loading="loading"
|
|
:pagination="pagination"
|
|
:row-selection="rowSelection"
|
|
row-key="id"
|
|
@change="handleTableChange"
|
|
>
|
|
<template #bodyCell="{ column, record, index }">
|
|
<template v-if="column.key === 'index'">
|
|
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
|
|
</template>
|
|
<template v-else-if="column.key === 'tenant'">
|
|
<div>
|
|
<div>{{ record.user?.tenant?.name || record.tenant?.name || "-" }}</div>
|
|
<div v-if="record.user?.student?.class" class="org-detail">
|
|
{{ record.user.student.class.grade?.name || "" }}
|
|
{{ record.user.student.class.name || "" }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-else-if="column.key === 'nickname'">
|
|
{{ record.user?.nickname || record.accountName || "-" }}
|
|
</template>
|
|
<template v-else-if="column.key === 'accountNo'">
|
|
{{ record.accountNo || record.user?.username || "-" }}
|
|
</template>
|
|
<template v-else-if="column.key === 'registrationState'">
|
|
<a-tag :color="getStateColor(record.registrationState)">
|
|
{{ getStateText(record.registrationState) }}
|
|
</a-tag>
|
|
</template>
|
|
<template v-else-if="column.key === 'registrationTime'">
|
|
{{ formatDateTime(record.registrationTime) }}
|
|
</template>
|
|
<template v-else-if="column.key === 'action'">
|
|
<a-space>
|
|
<a-button
|
|
type="link"
|
|
size="small"
|
|
@click="handleViewDetail(record)"
|
|
>
|
|
详细信息
|
|
</a-button>
|
|
<a-button
|
|
v-if="record.registrationState !== 'passed'"
|
|
v-permission="'contest:update'"
|
|
type="link"
|
|
size="small"
|
|
@click="handlePass(record)"
|
|
>
|
|
通过
|
|
</a-button>
|
|
<a-button
|
|
v-if="record.registrationState !== 'rejected'"
|
|
v-permission="'contest:update'"
|
|
type="link"
|
|
size="small"
|
|
@click="handleReject(record)"
|
|
>
|
|
拒绝
|
|
</a-button>
|
|
<a-popconfirm
|
|
v-permission="'contest:delete'"
|
|
title="确定要删除该报名记录吗?"
|
|
@confirm="handleDelete(record.id)"
|
|
>
|
|
<a-button type="link" danger size="small">删除</a-button>
|
|
</a-popconfirm>
|
|
</a-space>
|
|
</template>
|
|
</template>
|
|
</a-table>
|
|
|
|
<!-- 团队赛表格 -->
|
|
<a-table
|
|
v-else
|
|
:columns="teamColumns"
|
|
:data-source="dataSource"
|
|
:loading="loading"
|
|
:pagination="pagination"
|
|
:row-selection="rowSelection"
|
|
row-key="id"
|
|
@change="handleTableChange"
|
|
>
|
|
<template #bodyCell="{ column, record, index }">
|
|
<template v-if="column.key === 'index'">
|
|
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
|
|
</template>
|
|
<template v-else-if="column.key === 'tenant'">
|
|
<div>
|
|
<div>{{ record.user?.tenant?.name || record.tenant?.name || "-" }}</div>
|
|
<div v-if="record.user?.student?.class" class="org-detail">
|
|
{{ record.user.student.class.grade?.name || "" }}
|
|
{{ record.user.student.class.name || "" }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-else-if="column.key === 'teamName'">
|
|
{{ record.team?.teamName || "-" }}
|
|
</template>
|
|
<template v-else-if="column.key === 'accountNo'">
|
|
{{ record.accountNo || record.user?.username || "-" }}
|
|
</template>
|
|
<template v-else-if="column.key === 'registrationState'">
|
|
<a-tag :color="getStateColor(record.registrationState)">
|
|
{{ getStateText(record.registrationState) }}
|
|
</a-tag>
|
|
</template>
|
|
<template v-else-if="column.key === 'registrationTime'">
|
|
{{ formatDateTime(record.registrationTime) }}
|
|
</template>
|
|
<template v-else-if="column.key === 'action'">
|
|
<a-space>
|
|
<a-button
|
|
type="link"
|
|
size="small"
|
|
@click="handleViewMembers(record)"
|
|
>
|
|
成员信息
|
|
</a-button>
|
|
<a-button
|
|
v-if="record.registrationState !== 'passed'"
|
|
v-permission="'contest:update'"
|
|
type="link"
|
|
size="small"
|
|
@click="handlePass(record)"
|
|
>
|
|
通过
|
|
</a-button>
|
|
<a-button
|
|
v-if="record.registrationState !== 'rejected'"
|
|
v-permission="'contest:update'"
|
|
type="link"
|
|
size="small"
|
|
@click="handleReject(record)"
|
|
>
|
|
拒绝
|
|
</a-button>
|
|
<a-popconfirm
|
|
v-permission="'contest:delete'"
|
|
title="确定要删除该报名记录吗?"
|
|
@confirm="handleDelete(record.id)"
|
|
>
|
|
<a-button type="link" danger size="small">删除</a-button>
|
|
</a-popconfirm>
|
|
</a-space>
|
|
</template>
|
|
</template>
|
|
</a-table>
|
|
|
|
<!-- 详细信息抽屉 -->
|
|
<a-drawer
|
|
v-model:open="detailDrawerVisible"
|
|
title="详细信息"
|
|
width="600"
|
|
placement="right"
|
|
>
|
|
<template v-if="currentDetail">
|
|
<a-descriptions :column="2" bordered>
|
|
<a-descriptions-item label="姓名">
|
|
{{ currentDetail.user?.nickname || currentDetail.accountName || "-" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="账号">
|
|
{{ currentDetail.accountNo || currentDetail.user?.username || "-" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="机构">
|
|
<div>{{ currentDetail.user?.tenant?.name || "-" }}</div>
|
|
<div v-if="currentDetail.user?.student?.class" class="org-detail">
|
|
{{ currentDetail.user.student.class.grade?.name || "" }}
|
|
{{ currentDetail.user.student.class.name || "" }}
|
|
</div>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="审核状态">
|
|
<a-tag :color="getStateColor(currentDetail.registrationState)">
|
|
{{ getStateText(currentDetail.registrationState) }}
|
|
</a-tag>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="报名时间" :span="2">
|
|
{{ formatDateTime(currentDetail.registrationTime) }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item v-if="currentDetail.reason" label="审核理由" :span="2">
|
|
{{ currentDetail.reason }}
|
|
</a-descriptions-item>
|
|
</a-descriptions>
|
|
</template>
|
|
</a-drawer>
|
|
|
|
<!-- 成员信息弹窗 -->
|
|
<a-modal
|
|
v-model:open="membersModalVisible"
|
|
:title="`团队成员 - ${currentTeam?.teamName || ''}`"
|
|
:footer="null"
|
|
width="700px"
|
|
>
|
|
<template v-if="currentTeam">
|
|
<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>
|
|
<a-table
|
|
:columns="memberColumns"
|
|
:data-source="teamMembers"
|
|
:loading="membersLoading"
|
|
:pagination="false"
|
|
row-key="id"
|
|
>
|
|
<template #bodyCell="{ column, record }">
|
|
<template v-if="column.key === 'role'">
|
|
<a-tag :color="getMemberRoleColor(record.role)">
|
|
{{ getMemberRoleText(record.role) }}
|
|
</a-tag>
|
|
</template>
|
|
</template>
|
|
</a-table>
|
|
</template>
|
|
</a-modal>
|
|
|
|
<!-- 批量审核弹窗 -->
|
|
<a-modal
|
|
v-model:open="batchReviewModalVisible"
|
|
title="批量审核"
|
|
:confirm-loading="batchReviewLoading"
|
|
@ok="handleBatchReviewSubmit"
|
|
>
|
|
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
|
<a-form-item label="审核结果" required>
|
|
<a-radio-group v-model:value="batchReviewForm.registrationState">
|
|
<a-radio value="passed">通过</a-radio>
|
|
<a-radio value="rejected">拒绝</a-radio>
|
|
</a-radio-group>
|
|
</a-form-item>
|
|
<a-form-item label="审核理由">
|
|
<a-textarea
|
|
v-model:value="batchReviewForm.reason"
|
|
placeholder="请输入审核理由"
|
|
:rows="4"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="已选择">
|
|
<span>{{ selectedRowKeys.length }} 条记录</span>
|
|
</a-form-item>
|
|
</a-form>
|
|
</a-modal>
|
|
|
|
<!-- 拒绝理由弹窗 -->
|
|
<a-modal
|
|
v-model:open="rejectModalVisible"
|
|
title="拒绝理由"
|
|
:confirm-loading="rejectLoading"
|
|
@ok="handleRejectSubmit"
|
|
>
|
|
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
|
<a-form-item label="拒绝理由">
|
|
<a-textarea
|
|
v-model:value="rejectReason"
|
|
placeholder="请输入拒绝理由"
|
|
:rows="4"
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
</a-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, onMounted, computed } from "vue"
|
|
import { useRouter, useRoute } from "vue-router"
|
|
import { message } from "ant-design-vue"
|
|
import type { TableProps } from "ant-design-vue"
|
|
import {
|
|
SearchOutlined,
|
|
ReloadOutlined,
|
|
DownloadOutlined,
|
|
} from "@ant-design/icons-vue"
|
|
import {
|
|
registrationsApi,
|
|
contestsApi,
|
|
teamsApi,
|
|
type ContestRegistration,
|
|
type QueryRegistrationParams,
|
|
type ContestTeam,
|
|
type ContestTeamMember,
|
|
} from "@/api/contests"
|
|
import dayjs from "dayjs"
|
|
import type { Dayjs } from "dayjs"
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const tenantCode = route.params.tenantCode as string
|
|
const contestId = Number(route.params.id)
|
|
|
|
// 比赛信息
|
|
const contestName = ref("")
|
|
const contestType = ref<"individual" | "team">("individual")
|
|
|
|
// 日期范围
|
|
const dateRange = ref<[Dayjs, Dayjs] | null>(null)
|
|
|
|
// 列表状态
|
|
const loading = ref(false)
|
|
const dataSource = ref<ContestRegistration[]>([])
|
|
const pagination = reactive({
|
|
current: 1,
|
|
pageSize: 10,
|
|
total: 0,
|
|
})
|
|
|
|
// 搜索参数
|
|
const searchParams = reactive<QueryRegistrationParams & { teamName?: string }>({
|
|
contestId,
|
|
tenantName: "",
|
|
nickname: "",
|
|
teamName: "",
|
|
accountNo: "",
|
|
registrationState: undefined,
|
|
startTime: undefined,
|
|
endTime: undefined,
|
|
})
|
|
|
|
// 选中的行
|
|
const selectedRowKeys = ref<number[]>([])
|
|
const rowSelection = computed<TableProps["rowSelection"]>(() => ({
|
|
selectedRowKeys: selectedRowKeys.value,
|
|
onChange: (keys: any) => {
|
|
selectedRowKeys.value = keys
|
|
},
|
|
}))
|
|
|
|
// 导出loading
|
|
const exportLoading = ref(false)
|
|
|
|
// 详情抽屉
|
|
const detailDrawerVisible = ref(false)
|
|
const currentDetail = ref<ContestRegistration | null>(null)
|
|
|
|
// 成员信息弹窗
|
|
const membersModalVisible = ref(false)
|
|
const currentTeam = ref<ContestTeam | null>(null)
|
|
const teamMembers = ref<ContestTeamMember[]>([])
|
|
const membersLoading = ref(false)
|
|
|
|
// 批量审核弹窗
|
|
const batchReviewModalVisible = ref(false)
|
|
const batchReviewLoading = ref(false)
|
|
const batchReviewForm = reactive({
|
|
registrationState: "passed" as "passed" | "rejected",
|
|
reason: "",
|
|
})
|
|
|
|
// 拒绝弹窗
|
|
const rejectModalVisible = ref(false)
|
|
const rejectLoading = ref(false)
|
|
const rejectReason = ref("")
|
|
const currentRejectId = ref<number | null>(null)
|
|
|
|
// 个人赛表格列
|
|
const individualColumns = [
|
|
{
|
|
title: "序号",
|
|
key: "index",
|
|
width: 70,
|
|
},
|
|
{
|
|
title: "机构",
|
|
key: "tenant",
|
|
width: 150,
|
|
},
|
|
{
|
|
title: "姓名",
|
|
key: "nickname",
|
|
width: 120,
|
|
},
|
|
{
|
|
title: "报名账号",
|
|
key: "accountNo",
|
|
width: 150,
|
|
},
|
|
{
|
|
title: "审核状态",
|
|
key: "registrationState",
|
|
width: 100,
|
|
},
|
|
{
|
|
title: "报名时间",
|
|
key: "registrationTime",
|
|
width: 160,
|
|
},
|
|
{
|
|
title: "操作",
|
|
key: "action",
|
|
width: 220,
|
|
fixed: "right" as const,
|
|
},
|
|
]
|
|
|
|
// 团队赛表格列
|
|
const teamColumns = [
|
|
{
|
|
title: "序号",
|
|
key: "index",
|
|
width: 70,
|
|
},
|
|
{
|
|
title: "机构",
|
|
key: "tenant",
|
|
width: 150,
|
|
},
|
|
{
|
|
title: "队伍名称",
|
|
key: "teamName",
|
|
width: 150,
|
|
},
|
|
{
|
|
title: "报名账号",
|
|
key: "accountNo",
|
|
width: 150,
|
|
},
|
|
{
|
|
title: "审核状态",
|
|
key: "registrationState",
|
|
width: 100,
|
|
},
|
|
{
|
|
title: "报名时间",
|
|
key: "registrationTime",
|
|
width: 160,
|
|
},
|
|
{
|
|
title: "操作",
|
|
key: "action",
|
|
width: 220,
|
|
fixed: "right" as const,
|
|
},
|
|
]
|
|
|
|
// 团队成员表格列
|
|
const memberColumns = [
|
|
{
|
|
title: "姓名",
|
|
dataIndex: ["user", "nickname"],
|
|
key: "nickname",
|
|
width: 120,
|
|
},
|
|
{
|
|
title: "账号",
|
|
dataIndex: ["user", "username"],
|
|
key: "username",
|
|
width: 150,
|
|
},
|
|
{
|
|
title: "角色",
|
|
dataIndex: "role",
|
|
key: "role",
|
|
width: 100,
|
|
},
|
|
{
|
|
title: "加入时间",
|
|
dataIndex: "createTime",
|
|
key: "createTime",
|
|
width: 160,
|
|
customRender: ({ record }: { record: ContestTeamMember }) => {
|
|
return record.createTime
|
|
? dayjs(record.createTime).format("YYYY-MM-DD HH:mm")
|
|
: "-"
|
|
},
|
|
},
|
|
]
|
|
|
|
// 格式化日期时间
|
|
const formatDateTime = (dateStr?: string) => {
|
|
if (!dateStr) return "-"
|
|
return dayjs(dateStr).format("YYYY-MM-DD HH:mm")
|
|
}
|
|
|
|
// 获取状态颜色
|
|
const getStateColor = (state?: string) => {
|
|
switch (state) {
|
|
case "passed":
|
|
return "success"
|
|
case "rejected":
|
|
return "error"
|
|
default:
|
|
return "processing"
|
|
}
|
|
}
|
|
|
|
// 获取状态文本
|
|
const getStateText = (state?: string) => {
|
|
switch (state) {
|
|
case "passed":
|
|
return "已通过"
|
|
case "rejected":
|
|
return "已拒绝"
|
|
default:
|
|
return "待审核"
|
|
}
|
|
}
|
|
|
|
// 获取成员角色颜色
|
|
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 fetchContestInfo = async () => {
|
|
try {
|
|
const contest = await contestsApi.getDetail(contestId)
|
|
contestName.value = contest.contestName
|
|
contestType.value = contest.contestType as "individual" | "team"
|
|
} catch (error) {
|
|
console.error("获取比赛信息失败", error)
|
|
}
|
|
}
|
|
|
|
// 获取列表数据
|
|
const fetchList = async () => {
|
|
loading.value = true
|
|
try {
|
|
const params: QueryRegistrationParams = {
|
|
contestId,
|
|
page: pagination.current,
|
|
pageSize: pagination.pageSize,
|
|
}
|
|
// 添加搜索条件
|
|
if (searchParams.tenantName) params.tenantName = searchParams.tenantName
|
|
if (searchParams.accountNo) params.accountNo = searchParams.accountNo
|
|
if (searchParams.registrationState) params.registrationState = searchParams.registrationState
|
|
if (searchParams.startTime) params.startTime = searchParams.startTime
|
|
if (searchParams.endTime) params.endTime = searchParams.endTime
|
|
|
|
if (contestType.value === 'individual') {
|
|
if (searchParams.nickname) params.nickname = searchParams.nickname
|
|
} else {
|
|
if (searchParams.teamName) params.teamName = searchParams.teamName
|
|
}
|
|
|
|
const response = await registrationsApi.getList(params)
|
|
dataSource.value = response.list
|
|
pagination.total = response.total
|
|
} catch (error) {
|
|
message.error("获取报名记录失败")
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 日期变化
|
|
const handleDateChange = (dates: [Dayjs, Dayjs] | null) => {
|
|
if (dates) {
|
|
searchParams.startTime = dates[0].format("YYYY-MM-DD")
|
|
searchParams.endTime = dates[1].format("YYYY-MM-DD")
|
|
} else {
|
|
searchParams.startTime = undefined
|
|
searchParams.endTime = undefined
|
|
}
|
|
}
|
|
|
|
// 搜索
|
|
const handleSearch = () => {
|
|
pagination.current = 1
|
|
fetchList()
|
|
}
|
|
|
|
// 重置搜索
|
|
const handleReset = () => {
|
|
searchParams.tenantName = ""
|
|
searchParams.nickname = ""
|
|
searchParams.teamName = ""
|
|
searchParams.accountNo = ""
|
|
searchParams.registrationState = undefined
|
|
searchParams.startTime = undefined
|
|
searchParams.endTime = undefined
|
|
dateRange.value = null
|
|
pagination.current = 1
|
|
fetchList()
|
|
}
|
|
|
|
// 表格变化
|
|
const handleTableChange = (pag: any) => {
|
|
pagination.current = pag.current
|
|
pagination.pageSize = pag.pageSize
|
|
fetchList()
|
|
}
|
|
|
|
// 查看详细信息
|
|
const handleViewDetail = async (record: ContestRegistration) => {
|
|
try {
|
|
const detail = await registrationsApi.getDetail(record.id)
|
|
currentDetail.value = detail
|
|
detailDrawerVisible.value = true
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "获取详情失败")
|
|
}
|
|
}
|
|
|
|
// 查看成员信息
|
|
const handleViewMembers = async (record: ContestRegistration) => {
|
|
if (!record.team) {
|
|
message.warning("暂无团队信息")
|
|
return
|
|
}
|
|
currentTeam.value = record.team
|
|
membersModalVisible.value = true
|
|
membersLoading.value = true
|
|
|
|
try {
|
|
const teamDetail = await teamsApi.getDetail(record.team.id)
|
|
teamMembers.value = teamDetail.members || []
|
|
} catch (error: any) {
|
|
message.error("获取团队成员失败")
|
|
teamMembers.value = []
|
|
} finally {
|
|
membersLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 通过
|
|
const handlePass = async (record: ContestRegistration) => {
|
|
try {
|
|
await registrationsApi.review(record.id, {
|
|
registrationState: "passed",
|
|
})
|
|
message.success("已通过")
|
|
fetchList()
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "操作失败")
|
|
}
|
|
}
|
|
|
|
// 拒绝
|
|
const handleReject = (record: ContestRegistration) => {
|
|
currentRejectId.value = record.id
|
|
rejectReason.value = ""
|
|
rejectModalVisible.value = true
|
|
}
|
|
|
|
// 提交拒绝
|
|
const handleRejectSubmit = async () => {
|
|
if (!currentRejectId.value) return
|
|
try {
|
|
rejectLoading.value = true
|
|
await registrationsApi.review(currentRejectId.value, {
|
|
registrationState: "rejected",
|
|
reason: rejectReason.value,
|
|
})
|
|
message.success("已拒绝")
|
|
rejectModalVisible.value = false
|
|
fetchList()
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "操作失败")
|
|
} finally {
|
|
rejectLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 删除
|
|
const handleDelete = async (id: number) => {
|
|
try {
|
|
await registrationsApi.delete(id)
|
|
message.success("删除成功")
|
|
fetchList()
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "删除失败")
|
|
}
|
|
}
|
|
|
|
// 批量审核
|
|
const handleBatchReview = () => {
|
|
batchReviewForm.registrationState = "passed"
|
|
batchReviewForm.reason = ""
|
|
batchReviewModalVisible.value = true
|
|
}
|
|
|
|
// 提交批量审核
|
|
const handleBatchReviewSubmit = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
message.warning("请先选择要审核的记录")
|
|
return
|
|
}
|
|
try {
|
|
batchReviewLoading.value = true
|
|
await Promise.all(
|
|
selectedRowKeys.value.map((id) =>
|
|
registrationsApi.review(id, {
|
|
registrationState: batchReviewForm.registrationState,
|
|
reason: batchReviewForm.reason,
|
|
})
|
|
)
|
|
)
|
|
message.success("批量审核成功")
|
|
batchReviewModalVisible.value = false
|
|
selectedRowKeys.value = []
|
|
fetchList()
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "批量审核失败")
|
|
} finally {
|
|
batchReviewLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 导出
|
|
const handleExport = async () => {
|
|
exportLoading.value = true
|
|
try {
|
|
const response = await registrationsApi.getList({
|
|
contestId,
|
|
page: 1,
|
|
pageSize: 100,
|
|
})
|
|
|
|
if (response.list.length === 0) {
|
|
message.warning("暂无数据")
|
|
return
|
|
}
|
|
|
|
let headers: string[]
|
|
let rows: any[][]
|
|
|
|
if (contestType.value === 'individual') {
|
|
headers = ["序号", "机构", "姓名", "报名账号", "审核状态", "报名时间"]
|
|
rows = response.list.map((item, index) => [
|
|
index + 1,
|
|
item.user?.tenant?.name || "",
|
|
item.user?.nickname || item.accountName || "",
|
|
item.accountNo || item.user?.username || "",
|
|
getStateText(item.registrationState),
|
|
formatDateTime(item.registrationTime),
|
|
])
|
|
} else {
|
|
headers = ["序号", "机构", "队伍名称", "报名账号", "审核状态", "报名时间"]
|
|
rows = response.list.map((item, index) => [
|
|
index + 1,
|
|
item.user?.tenant?.name || "",
|
|
item.team?.teamName || "",
|
|
item.accountNo || item.user?.username || "",
|
|
getStateText(item.registrationState),
|
|
formatDateTime(item.registrationTime),
|
|
])
|
|
}
|
|
|
|
const csvContent = [
|
|
headers.join(","),
|
|
...rows.map((row) => row.map((cell) => `"${cell}"`).join(",")),
|
|
].join("\n")
|
|
|
|
const blob = new Blob(["\ufeff" + csvContent], {
|
|
type: "text/csv;charset=utf-8",
|
|
})
|
|
const url = URL.createObjectURL(blob)
|
|
const link = document.createElement("a")
|
|
link.href = url
|
|
link.download = `报名记录_${contestName.value}_${dayjs().format("YYYYMMDD_HHmmss")}.csv`
|
|
link.click()
|
|
URL.revokeObjectURL(url)
|
|
|
|
message.success("导出成功")
|
|
} catch (error: any) {
|
|
message.error(error?.response?.data?.message || "导出失败")
|
|
} finally {
|
|
exportLoading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await fetchContestInfo()
|
|
fetchList()
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
// 主色调
|
|
$primary: #1890ff;
|
|
$primary-dark: #0958d9;
|
|
$gradient-primary: linear-gradient(135deg, $primary 0%, $primary-dark 100%);
|
|
|
|
.registration-records-page {
|
|
// 标题卡片样式
|
|
:deep(.ant-card) {
|
|
border: none;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
margin-bottom: 16px;
|
|
|
|
.ant-card-head {
|
|
border-bottom: none;
|
|
padding: 16px 24px;
|
|
|
|
.ant-card-head-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
}
|
|
}
|
|
|
|
.ant-card-body {
|
|
padding: 0;
|
|
}
|
|
}
|
|
|
|
// 渐变主按钮样式
|
|
:deep(.ant-btn-primary) {
|
|
background: $gradient-primary;
|
|
border: none;
|
|
box-shadow: 0 4px 12px rgba($primary, 0.35);
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
background: linear-gradient(135deg, $primary-dark 0%, darken($primary-dark, 8%) 100%);
|
|
box-shadow: 0 6px 16px rgba($primary, 0.45);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
&:active {
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
// 表格样式
|
|
:deep(.ant-table-wrapper) {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
overflow: hidden;
|
|
|
|
.ant-table {
|
|
.ant-table-thead > tr > th {
|
|
background: #fafafa;
|
|
font-weight: 600;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.ant-table-tbody > tr {
|
|
transition: all 0.2s ease;
|
|
|
|
&:hover > td {
|
|
background: rgba($primary, 0.04);
|
|
}
|
|
|
|
> td {
|
|
border-bottom: 1px solid #f5f5f5;
|
|
}
|
|
}
|
|
}
|
|
|
|
.ant-table-pagination {
|
|
padding: 16px;
|
|
margin: 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
.search-form {
|
|
margin-bottom: 16px;
|
|
padding: 20px 24px;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
// 自适应换行 - 使用 flex wrap
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: flex-start;
|
|
gap: 16px 24px;
|
|
|
|
:deep(.ant-form-item) {
|
|
margin-bottom: 0;
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
|
|
.org-detail {
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-top: 2px;
|
|
}
|
|
</style>
|