add contest details.

This commit is contained in:
2023-03-30 18:49:32 +08:00
parent 301fc1be6d
commit eb652d1c86
10 changed files with 153 additions and 23 deletions

View File

@@ -1,7 +1,9 @@
import http from "utils/http"
import {
AdminProblem,
BlankContest,
BlankProblem,
Contest,
TestcaseUploadedReturns,
User,
} from "~/utils/types"
@@ -113,3 +115,17 @@ export function createProblem(problem: BlankProblem) {
export function createContestProblem(problem: BlankProblem) {
return http.post("admin/contest/problem", problem)
}
export function createContest(contest: BlankContest) {
return http.post("admin/contest", contest)
}
export function editContest(contest: BlankContest) {
return http.put("admin/contest", contest)
}
export function getContest(id: string) {
return http.get<Contest & { password: string }>("admin/contest", {
params: { id },
})
}

View File

@@ -29,13 +29,6 @@ function goEditProblems() {
<n-button size="small" type="info" secondary @click="goEditProblems">
题目
</n-button>
<n-button size="small" type="warning" secondary>公告</n-button>
<n-tooltip>
<template #trigger>
<n-button size="small" secondary>下载</n-button>
</template>
下载 AC 提交
</n-tooltip>
</n-space>
</template>
<style scoped></style>

View File

@@ -1,7 +1,117 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { formatISO } from "date-fns"
import TextEditor from "~/shared/TextEditor.vue"
import { CONTEST_TYPE } from "~/utils/constants"
import { BlankContest } from "~/utils/types"
import { createContest, editContest, getContest } from "../api"
interface Props {
contestID?: string
}
const after10mins = Date.now() + 1000 * 600
const after70mins = after10mins + 1000 * 3600
const route = useRoute()
const router = useRouter()
const message = useMessage()
const props = defineProps<Props>()
const [ready, toggleReady] = useToggle()
const startTime = ref(after10mins)
const endTime = ref(after70mins)
const contest = reactive<BlankContest & { id: number }>({
id: 0,
title: "",
description: "",
start_time: formatISO(after10mins),
end_time: formatISO(after70mins),
rule_type: "ACM",
password: "",
real_time_rank: true,
visible: true,
allowed_ip_ranges: [],
})
watch([startTime, endTime], (values) => {
contest.start_time = formatISO(values[0])
contest.end_time = formatISO(values[1])
})
async function getContestDetail() {
if (!props.contestID) {
toggleReady(true)
return
}
const { data } = await getContest(props.contestID)
toggleReady(true)
contest.id = data.id
contest.title = data.title
contest.description = data.description
contest.start_time = data.start_time
contest.end_time = data.end_time
contest.rule_type = "ACM"
contest.password = data.password
contest.real_time_rank = true
contest.visible = data.visible
contest.allowed_ip_ranges = []
// 显示
startTime.value = Date.parse(data.start_time)
endTime.value = Date.parse(data.end_time)
}
async function submit() {
const api = {
"admin contest create": createContest,
"admin contest edit": editContest,
}[<string>route.name]
try {
await api!(contest)
if (route.name === "admin contest create") {
message.success("成功创建比赛 💐")
} else {
message.success("修改已保存")
}
router.push({ name: "admin contest list" })
} catch (err: any) {
message.error(err.data)
}
}
onMounted(getContestDetail)
</script>
<template>
<div>contest detail</div>
<n-form inline>
<n-form-item label="标题">
<n-input class="title" v-model:value="contest.title" />
</n-form-item>
<n-form-item label="开始">
<n-date-picker v-model:value="startTime" type="datetime" />
</n-form-item>
<n-form-item label="结束">
<n-date-picker v-model:value="endTime" type="datetime" />
</n-form-item>
<n-form-item label="密码">
<n-input v-model:value="contest.password" />
</n-form-item>
<n-form-item label="可见">
<n-switch v-model:value="contest.visible" />
</n-form-item>
</n-form>
<TextEditor
v-if="ready"
title="描述"
v-model:value="contest.description"
:min-height="200"
/>
<n-button type="primary" @click="submit">保存</n-button>
</template>
<style scoped></style>
<style scoped>
.title {
width: 400px;
}
</style>

View File

@@ -58,7 +58,7 @@ const columns: DataTableColumn<Contest>[] = [
{
title: "选项",
key: "actions",
width: 260,
width: 140,
render: (row) => h(Actions, { contest: row }),
},
]

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { deleteContestProblem, deleteProblem } from "~/admin/api"
import download from "~/utils/download"
interface Props {
problemID: number
@@ -30,8 +31,8 @@ async function handleDeleteProblem() {
}
}
function download() {
console.log(props.problemID)
function downloads() {
download("test_case?problem_id=" + props.problemID)
}
function goEdit() {
@@ -60,7 +61,7 @@ function goCheck() {
</n-popconfirm>
<n-tooltip>
<template #trigger>
<n-button size="small" secondary @click="download">下载</n-button>
<n-button size="small" secondary @click="downloads">下载</n-button>
</template>
下载测试用例
</n-tooltip>

View File

@@ -91,7 +91,10 @@ const tagOptions = computed(() =>
)
async function getProblemDetail() {
if (!props.problemID) return
if (!props.problemID) {
toggleReady(true)
return
}
const { data } = await getProblem(props.problemID)
toggleReady(true)
problem.id = data.id
@@ -307,12 +310,6 @@ async function submit() {
}
onMounted(() => {
if (
route.name === "admin problem create" ||
route.name === "admin contest problem create"
) {
toggleReady(true)
}
listTags()
getProblemDetail()
})

1
src/components.d.ts vendored
View File

@@ -26,6 +26,7 @@ declare module '@vue/runtime-core' {
NCode: typeof import('naive-ui')['NCode']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDropdown: typeof import('naive-ui')['NDropdown']

View File

@@ -1,4 +1,4 @@
import { formatISO, getTime, intervalToDuration, parseISO } from "date-fns"
import { formatISO, getTime, parseISO } from "date-fns"
import { useUserStore } from "~/shared/store/user"
import { ContestStatus, ContestType } from "~/utils/constants"
import { duration } from "~/utils/functions"

View File

@@ -5,7 +5,7 @@ import Header from "../Header.vue"
</script>
<template>
<n-layout position="absolute" :native-scrollbar="false">
<n-layout position="absolute">
<n-layout-header bordered class="header">
<Header />
</n-layout-header>

View File

@@ -283,6 +283,18 @@ export interface Contest {
visible: boolean
}
export interface BlankContest {
title: string
description: string
start_time: string
end_time: string
rule_type: "ACM" | "OI"
password: string
real_time_rank: boolean
visible: boolean
allowed_ip_ranges: { value: string }[]
}
interface SubmissionInfo {
is_ac: boolean
ac_time: number