add contest detail.
This commit is contained in:
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -16,6 +16,7 @@ declare module '@vue/runtime-core' {
|
|||||||
NCard: typeof import('naive-ui')['NCard']
|
NCard: typeof import('naive-ui')['NCard']
|
||||||
NCode: typeof import('naive-ui')['NCode']
|
NCode: typeof import('naive-ui')['NCode']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
|
NCountdown: typeof import('naive-ui')['NCountdown']
|
||||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||||
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
||||||
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
||||||
|
|||||||
@@ -105,3 +105,16 @@ export function getContestList(query: {
|
|||||||
export function getContest(id: string) {
|
export function getContest(id: string) {
|
||||||
return http.get("contest", { params: { id } })
|
return http.get("contest", { params: { id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getContestAccess(id: string) {
|
||||||
|
return http.get("contest/access", { params: { contest_id: id } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkContestPassword(contestID: string, password: string) {
|
||||||
|
return http.post("contest/password", {
|
||||||
|
data: {
|
||||||
|
contest_id: contestID,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Contest } from "~/utils/types"
|
import { Contest } from "~/utils/types"
|
||||||
|
import { ContestType } from "~/utils/constants"
|
||||||
|
|
||||||
defineProps<{ contest: Contest }>()
|
defineProps<{ contest: Contest }>()
|
||||||
</script>
|
</script>
|
||||||
@@ -8,7 +9,7 @@ defineProps<{ contest: Contest }>()
|
|||||||
<span>{{ contest.title }}</span>
|
<span>{{ contest.title }}</span>
|
||||||
<n-icon
|
<n-icon
|
||||||
class="lockIcon"
|
class="lockIcon"
|
||||||
v-if="contest.contest_type === 'Password Protected'"
|
v-if="contest.contest_type === ContestType.private"
|
||||||
>
|
>
|
||||||
<i-ep-lock />
|
<i-ep-lock />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
|
|||||||
@@ -1,7 +1,79 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const route = useRoute()
|
import { Contest } from "utils/types"
|
||||||
|
import { CONTEST_STATUS, ContestStatus, ContestType } from "utils/constants"
|
||||||
|
import { parseTime } from "utils/functions"
|
||||||
|
import { getContest, getContestAccess } from "../api"
|
||||||
|
import { isDesktop } from "~/shared/composables/breakpoints"
|
||||||
|
const props = defineProps<{
|
||||||
|
contestID: string
|
||||||
|
}>()
|
||||||
|
const contest = ref<Contest>()
|
||||||
|
async function init() {
|
||||||
|
const res = await getContest(props.contestID)
|
||||||
|
contest.value = res.data
|
||||||
|
if (contest.value?.contest_type === ContestType.private) {
|
||||||
|
const res = await getContestAccess(props.contestID)
|
||||||
|
// TODO: 这里 access 的逻辑不清楚
|
||||||
|
console.log(res.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(init)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template></template>
|
<template>
|
||||||
|
<n-card v-if="contest">
|
||||||
|
<template #header>
|
||||||
|
<n-space align="center">
|
||||||
|
<n-tag :type="CONTEST_STATUS[contest.status]['type']">
|
||||||
|
{{ CONTEST_STATUS[contest.status]["name"] }}
|
||||||
|
</n-tag>
|
||||||
|
<span>{{ contest.title }}</span>
|
||||||
|
<n-icon
|
||||||
|
v-if="contest.contest_type === ContestType.private"
|
||||||
|
class="lockIcon"
|
||||||
|
>
|
||||||
|
<i-ep-lock />
|
||||||
|
</n-icon>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
<div v-html="contest.description"></div>
|
||||||
|
<n-descriptions bordered :column="isDesktop ? 5 : 2">
|
||||||
|
<n-descriptions-item
|
||||||
|
:span="isDesktop ? 1 : 2"
|
||||||
|
v-if="contest.status !== ContestStatus.finished"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span v-if="contest.status === ContestStatus.not_started">
|
||||||
|
距离比赛开始还有
|
||||||
|
</span>
|
||||||
|
<span v-if="contest.status === ContestStatus.underway">
|
||||||
|
距离比赛结束还有
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<n-space align="center">
|
||||||
|
<n-tag :type="CONTEST_STATUS[contest.status]['type']">
|
||||||
|
<n-countdown :duration="500000" />
|
||||||
|
</n-tag>
|
||||||
|
</n-space>
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="开始时间">
|
||||||
|
{{ parseTime(contest.start_time, "YYYY年M月D日 hh:mm:ss") }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="结束时间">
|
||||||
|
{{ parseTime(contest.end_time, "YYYY年M月D日 hh:mm:ss") }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="比赛类型">
|
||||||
|
{{ contest.contest_type === ContestType.private ? "需要密码" : "公开" }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="发起人">
|
||||||
|
{{ contest.created_by.username }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.lockIcon {
|
||||||
|
transform: translateY(2px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getContestList } from "oj/api"
|
|||||||
import Pagination from "~/shared/Pagination.vue"
|
import Pagination from "~/shared/Pagination.vue"
|
||||||
import { filterEmptyValue, parseTime, duration } from "utils/functions"
|
import { filterEmptyValue, parseTime, duration } from "utils/functions"
|
||||||
import { Contest } from "utils/types"
|
import { Contest } from "utils/types"
|
||||||
import { CONTEST_STATUS } from "~/utils/constants"
|
import { ContestType, CONTEST_STATUS } from "~/utils/constants"
|
||||||
import ContestTitle from "./components/ContestTitle.vue"
|
import ContestTitle from "./components/ContestTitle.vue"
|
||||||
import { useUserStore } from "~/shared/store/user"
|
import { useUserStore } from "~/shared/store/user"
|
||||||
import { toggleLogin } from "~/shared/composables/modal"
|
import { toggleLogin } from "~/shared/composables/modal"
|
||||||
@@ -108,7 +108,7 @@ function rowProps(row: Contest) {
|
|||||||
return {
|
return {
|
||||||
style: "cursor: pointer",
|
style: "cursor: pointer",
|
||||||
onClick() {
|
onClick() {
|
||||||
if (!userStore.isAuthed && row.contest_type === "Password Protected") {
|
if (!userStore.isAuthed && row.contest_type === ContestType.private) {
|
||||||
toggleLogin(true)
|
toggleLogin(true)
|
||||||
} else {
|
} else {
|
||||||
router.push("/contest/" + row.id)
|
router.push("/contest/" + row.id)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
import { RouterLink } from "vue-router"
|
import { RouterLink } from "vue-router"
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
@@ -30,6 +31,8 @@ function handleDropdown(key: string) {
|
|||||||
|
|
||||||
onMounted(userStore.getMyProfile)
|
onMounted(userStore.getMyProfile)
|
||||||
|
|
||||||
|
const defaultValue = computed(() => route.path.split("/")[1] || "problem")
|
||||||
|
|
||||||
const menus: MenuOption[] = [
|
const menus: MenuOption[] = [
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () =>
|
||||||
@@ -47,7 +50,7 @@ const menus: MenuOption[] = [
|
|||||||
{
|
{
|
||||||
label: () =>
|
label: () =>
|
||||||
h(RouterLink, { to: "/submission" }, { default: () => "提交" }),
|
h(RouterLink, { to: "/submission" }, { default: () => "提交" }),
|
||||||
key: "status",
|
key: "submission",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () => h(RouterLink, { to: "/rank" }, { default: () => "排名" }),
|
label: () => h(RouterLink, { to: "/rank" }, { default: () => "排名" }),
|
||||||
@@ -67,7 +70,11 @@ const options = computed<Array<DropdownOption | DropdownDividerOption>>(() => [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-space align="center">
|
<n-space align="center">
|
||||||
<n-menu mode="horizontal" :options="menus" default-value="problem"></n-menu>
|
<n-menu
|
||||||
|
mode="horizontal"
|
||||||
|
:options="menus"
|
||||||
|
:default-value="defaultValue"
|
||||||
|
></n-menu>
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button circle @click="toggleDark()">
|
<n-button circle @click="toggleDark()">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ export enum SubmissionStatus {
|
|||||||
submitting = 9,
|
submitting = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ContestStatus {
|
||||||
|
not_started = "1",
|
||||||
|
underway = "0",
|
||||||
|
finished = "-1",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContestType {
|
||||||
|
public = "Public",
|
||||||
|
private = "Password Protected",
|
||||||
|
}
|
||||||
|
|
||||||
export const JUDGE_STATUS: {
|
export const JUDGE_STATUS: {
|
||||||
[key in SUBMISSION_RESULT]: {
|
[key in SUBMISSION_RESULT]: {
|
||||||
name: string
|
name: string
|
||||||
@@ -71,7 +82,7 @@ export const JUDGE_STATUS: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CONTEST_STATUS: {
|
export const CONTEST_STATUS: {
|
||||||
[key in "1" | "-1" | "0"]: {
|
[key in ContestStatus]: {
|
||||||
name: string
|
name: string
|
||||||
type: "error" | "success" | "warning"
|
type: "error" | "success" | "warning"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ContestStatus, ContestType } from "./constants"
|
||||||
|
|
||||||
export interface Profile {
|
export interface Profile {
|
||||||
id: number
|
id: number
|
||||||
user: User
|
user: User
|
||||||
@@ -206,8 +208,8 @@ export interface Contest {
|
|||||||
username: string
|
username: string
|
||||||
real_name: null
|
real_name: null
|
||||||
}
|
}
|
||||||
status: "-1" | "0" | "1"
|
status: ContestStatus
|
||||||
contest_type: "Password Protected" | "Public"
|
contest_type: ContestType
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
real_time_rank: boolean
|
real_time_rank: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user