library-picturebook-activity/frontend/src/views/contests/results/Detail.vue

312 lines
7.2 KiB
Vue
Raw Normal View History

2026-01-15 16:35:00 +08:00
<template>
<div class="results-detail-page">
<!-- 顶部导航 -->
<div class="page-header">
<div class="header-left">
<a-button type="text" @click="handleBack">
<template #icon><ArrowLeftOutlined /></template>
返回
</a-button>
<span class="page-title">{{ contestInfo?.contestName || "赛果发布" }}</span>
</div>
<div class="header-right">
<a-button
type="primary"
:loading="publishLoading"
@click="handlePublish"
>
{{ contestInfo?.resultState === "published" ? "撤回发布" : "发布赛果" }}
</a-button>
</div>
</div>
<!-- 搜索表单 -->
<a-form
:model="searchParams"
layout="inline"
class="search-form"
@finish="handleSearch"
>
<a-form-item label="作品编号">
<a-input
v-model:value="searchParams.workNo"
placeholder="请输入作品编号"
allow-clear
style="width: 180px"
/>
</a-form-item>
<a-form-item label="报名账号">
<a-input
v-model:value="searchParams.accountNo"
placeholder="请输入报名账号"
allow-clear
style="width: 180px"
/>
</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
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
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 === 'finalScore'">
<span v-if="record.finalScore !== null" class="score">
{{ Number(record.finalScore).toFixed(2) }}
</span>
<span v-else>-</span>
</template>
<template v-else-if="column.key === 'nickname'">
{{ record.registration?.user?.nickname || "-" }}
</template>
<template v-else-if="column.key === 'username'">
{{ record.registration?.user?.username || "-" }}
</template>
<template v-else-if="column.key === 'org'">
<div>
<div>{{ record.registration?.user?.tenant?.name || "-" }}</div>
<div v-if="record.registration?.user?.student?.class" class="org-detail">
{{ record.registration.user.student.class.grade?.name || "" }}
{{ record.registration.user.student.class.name || "" }}
</div>
</div>
</template>
<template v-else-if="column.key === 'teachers'">
{{ formatTeachers(record.registration?.teachers) }}
</template>
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
import { useRoute, useRouter } from "vue-router"
import { message, Modal } from "ant-design-vue"
import {
ArrowLeftOutlined,
SearchOutlined,
ReloadOutlined,
} from "@ant-design/icons-vue"
import { resultsApi } from "@/api/contests"
const route = useRoute()
const router = useRouter()
const tenantCode = route.params.tenantCode as string
const contestId = Number(route.params.id)
// 赛事信息
const contestInfo = ref<{
id: number
contestName: string
resultState: string
} | null>(null)
// 列表状态
const loading = ref(false)
const publishLoading = ref(false)
const dataSource = ref<any[]>([])
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
})
// 搜索参数
const searchParams = reactive({
workNo: "",
accountNo: "",
})
// 表格列定义
const columns = [
{
title: "序号",
key: "index",
width: 70,
},
{
title: "作品编号",
dataIndex: "workNo",
key: "workNo",
width: 120,
},
{
title: "评委评分",
key: "finalScore",
width: 100,
},
{
title: "姓名",
key: "nickname",
width: 120,
},
{
title: "账号",
key: "username",
width: 120,
},
{
title: "机构信息",
key: "org",
width: 180,
},
{
title: "指导老师",
key: "teachers",
width: 150,
},
]
// 格式化指导老师
const formatTeachers = (teachers: any[] | undefined) => {
if (!teachers || teachers.length === 0) return "-"
return teachers
.map((t) => t.user?.nickname || t.user?.username)
.filter(Boolean)
.join("、") || "-"
}
// 获取列表数据
const fetchList = async () => {
loading.value = true
try {
const response = await resultsApi.getResults(contestId, {
page: pagination.current,
pageSize: pagination.pageSize,
workNo: searchParams.workNo || undefined,
accountNo: searchParams.accountNo || undefined,
})
contestInfo.value = response.contest
dataSource.value = response.list
pagination.total = response.total
} catch (error: any) {
message.error(error?.response?.data?.message || "获取列表失败")
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
pagination.current = 1
fetchList()
}
// 重置
const handleReset = () => {
searchParams.workNo = ""
searchParams.accountNo = ""
pagination.current = 1
fetchList()
}
// 表格变化
const handleTableChange = (pag: any) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchList()
}
// 返回
const handleBack = () => {
router.push(`/${tenantCode}/contests/results`)
}
// 发布/撤回赛果
const handlePublish = () => {
const isPublished = contestInfo.value?.resultState === "published"
Modal.confirm({
title: isPublished ? "确定撤回赛果发布吗?" : "确定发布赛果吗?",
content: isPublished
? "撤回后,赛果将不再对外公开显示"
: "发布后,比赛结果将公开显示",
onOk: async () => {
publishLoading.value = true
try {
if (isPublished) {
await resultsApi.unpublish(contestId)
message.success("已撤回发布")
} else {
await resultsApi.publish(contestId)
message.success("发布成功")
}
fetchList()
} catch (error: any) {
message.error(error?.response?.data?.message || "操作失败")
} finally {
publishLoading.value = false
}
},
})
}
onMounted(() => {
fetchList()
})
</script>
<style scoped>
.results-detail-page {
padding: 0;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: #fff;
border-radius: 8px;
margin-bottom: 16px;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.page-title {
font-size: 16px;
font-weight: 500;
}
.search-form {
margin-bottom: 16px;
padding: 16px;
background: #fff;
border-radius: 8px;
}
.org-detail {
font-size: 12px;
color: #666;
margin-top: 2px;
}
.score {
font-weight: bold;
color: #52c41a;
}
</style>