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 http from "utils/http"
import { import {
AdminProblem, AdminProblem,
BlankContest,
BlankProblem, BlankProblem,
Contest,
TestcaseUploadedReturns, TestcaseUploadedReturns,
User, User,
} from "~/utils/types" } from "~/utils/types"
@@ -113,3 +115,17 @@ export function createProblem(problem: BlankProblem) {
export function createContestProblem(problem: BlankProblem) { export function createContestProblem(problem: BlankProblem) {
return http.post("admin/contest/problem", problem) 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 size="small" type="info" secondary @click="goEditProblems">
题目 题目
</n-button> </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> </n-space>
</template> </template>
<style scoped></style> <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> <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> </template>
<style scoped></style> <style scoped>
.title {
width: 400px;
}
</style>

View File

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

View File

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

View File

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

1
src/components.d.ts vendored
View File

@@ -26,6 +26,7 @@ declare module '@vue/runtime-core' {
NCode: typeof import('naive-ui')['NCode'] NCode: typeof import('naive-ui')['NCode']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable'] NDataTable: typeof import('naive-ui')['NDataTable']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDescriptions: typeof import('naive-ui')['NDescriptions'] NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem'] NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDropdown: typeof import('naive-ui')['NDropdown'] 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 { useUserStore } from "~/shared/store/user"
import { ContestStatus, ContestType } from "~/utils/constants" import { ContestStatus, ContestType } from "~/utils/constants"
import { duration } from "~/utils/functions" import { duration } from "~/utils/functions"

View File

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

View File

@@ -283,6 +283,18 @@ export interface Contest {
visible: boolean 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 { interface SubmissionInfo {
is_ac: boolean is_ac: boolean
ac_time: number ac_time: number