This commit is contained in:
2023-01-31 16:29:58 +08:00
parent d5d6491d6d
commit 7b0163885a
3 changed files with 113 additions and 82 deletions

View File

@@ -1,82 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { Contest, Problem } from "utils/types"
import { CONTEST_STATUS, ContestStatus, ContestType } from "utils/constants" import { CONTEST_STATUS, ContestStatus, ContestType } from "utils/constants"
import { parseTime } from "utils/functions" import { parseTime } from "utils/functions"
import {
getContest,
getContestAccess,
getContestProblem,
checkContestPassword,
} from "../api"
import { isDesktop } from "~/shared/composables/breakpoints" import { isDesktop } from "~/shared/composables/breakpoints"
import ContestTypeVue from "./components/ContestType.vue" import ContestTypeVue from "./components/ContestType.vue"
import { useUserStore } from "~/shared/store/user" import { useContestStore } from "../store/contest"
const props = defineProps<{ const props = defineProps<{
contestID: string contestID: string
}>() }>()
const message = useMessage() const contestStore = useContestStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const userStore = useUserStore()
const contest = ref<Contest>()
const problems = ref<Problem[]>([])
const password = ref("") const password = ref("")
const [access, toggleAccsess] = useToggle()
async function init() { onMounted(() => contestStore.init(props.contestID))
const res = await getContest(props.contestID)
contest.value = res.data const contestMenuVisible = computed(() => {
if (contest.value?.contest_type === ContestType.private) { if (contestStore.isContestAdmin) return true
const res = await getContestAccess(props.contestID) if (contestStore.contest?.contest_type === ContestType.public) {
toggleAccsess(res.data.access) // TODO:这里没有完成
} }
getProblems() return contestStore.access
} })
onMounted(init)
async function getProblems() {
try {
problems.value = await getContestProblem(props.contestID)
} catch (err) {
toggleAccsess(false)
}
}
async function checkPassword() {
try {
const res = await checkContestPassword(props.contestID, password.value)
toggleAccsess(res.data)
if (res.data) {
getProblems()
}
} catch (err) {
toggleAccsess(false)
message.error("密码错误")
}
}
const isContestAdmin = computed(
() =>
userStore.isSuperAdmin ||
(userStore.isAuthed && contest.value?.created_by.id === userStore.user.id)
)
const passwordFormVisible = computed( const passwordFormVisible = computed(
() => () =>
contest.value?.contest_type === ContestType.private && contestStore.contest?.contest_type === ContestType.private &&
!access.value && !contestStore.access &&
!isContestAdmin.value !contestStore.isContestAdmin
) )
const contestMenuVisible = computed(() => {
if (isContestAdmin) return true
if (contest.value?.contest_type === ContestType.public) {
}
return access.value
})
function goto(name: string) { function goto(name: string) {
router.push({ name: "contest " + name }) router.push({ name: "contest " + name })
} }
@@ -88,31 +41,35 @@ function getCurrentType(name: string): "primary" | "default" {
</script> </script>
<template> <template>
<div v-if="contest"> <div v-if="contestStore.contest">
<n-space align="center"> <n-space align="center">
<n-tag :type="CONTEST_STATUS[contest.status]['type']"> <n-tag :type="CONTEST_STATUS[contestStore.contest.status]['type']">
{{ CONTEST_STATUS[contest.status]["name"] }} {{ CONTEST_STATUS[contestStore.contest.status]["name"] }}
</n-tag> </n-tag>
<h2 class="contestTitle">{{ contest.title }}</h2> <h2 class="contestTitle">{{ contestStore.contest.title }}</h2>
<n-icon <n-icon
v-if="contest.contest_type === ContestType.private" v-if="contestStore.contest.contest_type === ContestType.private"
class="lockIcon" class="lockIcon"
> >
<i-ep-lock /> <i-ep-lock />
</n-icon> </n-icon>
</n-space> </n-space>
<div v-html="contest.description"></div> <div v-html="contestStore.contest.description"></div>
<n-form :inline="isDesktop" label-placement="left"> <n-form :inline="isDesktop" label-placement="left">
<n-form-item v-if="passwordFormVisible" label="需要输入密码才能看到题目"> <n-form-item v-if="passwordFormVisible" label="需要输入密码才能看到题目">
<n-input <n-input
name="ContestPassword" name="ContestPassword"
type="password" type="password"
v-model:value="password" v-model:value="password"
@change="checkPassword"
/> />
</n-form-item> </n-form-item>
<n-form-item v-if="passwordFormVisible"> <n-form-item v-if="passwordFormVisible">
<n-button @click="checkPassword" :disabled="!password">确认</n-button> <n-button
@click="contestStore.checkPassword(contestID, password)"
:disabled="!password"
>
确认
</n-button>
</n-form-item> </n-form-item>
<n-form-item v-if="contestMenuVisible"> <n-form-item v-if="contestMenuVisible">
<n-space> <n-space>
@@ -137,39 +94,43 @@ function getCurrentType(name: string): "primary" | "default" {
</n-space> </n-space>
</n-form-item> </n-form-item>
</n-form> </n-form>
<n-descriptions class="margin" bordered :column="isDesktop ? 5 : 2"> <n-descriptions class="bottom" bordered :column="isDesktop ? 5 : 2">
<n-descriptions-item <n-descriptions-item
:span="isDesktop ? 1 : 2" :span="isDesktop ? 1 : 2"
v-if="contest.status !== ContestStatus.finished" v-if="contestStore.contest.status !== ContestStatus.finished"
> >
<template #label> <template #label>
<span v-if="contest.status === ContestStatus.not_started"> <span
v-if="contestStore.contest.status === ContestStatus.not_started"
>
距离比赛开始还有 距离比赛开始还有
</span> </span>
<span v-if="contest.status === ContestStatus.underway"> <span v-if="contestStore.contest.status === ContestStatus.underway">
距离比赛结束还有 距离比赛结束还有
</span> </span>
</template> </template>
<n-space align="center"> <n-space align="center">
<n-tag :type="CONTEST_STATUS[contest.status]['type']"> <n-tag :type="CONTEST_STATUS[contestStore.contest.status]['type']">
<n-countdown :duration="500000" /> <n-countdown :duration="500000" />
</n-tag> </n-tag>
</n-space> </n-space>
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="开始时间"> <n-descriptions-item label="开始时间">
{{ parseTime(contest.start_time, "YYYY年M月D日 hh:mm:ss") }} {{
parseTime(contestStore.contest.start_time, "YYYY年M月D日 hh:mm:ss")
}}
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="结束时间"> <n-descriptions-item label="结束时间">
{{ parseTime(contest.end_time, "YYYY年M月D日 hh:mm:ss") }} {{ parseTime(contestStore.contest.end_time, "YYYY年M月D日 hh:mm:ss") }}
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="比赛类型"> <n-descriptions-item label="比赛类型">
<ContestTypeVue :contest="contest" /> <ContestTypeVue :contest="contestStore.contest" />
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="发起人"> <n-descriptions-item label="发起人">
{{ contest.created_by.username }} {{ contestStore.contest.created_by.username }}
</n-descriptions-item> </n-descriptions-item>
</n-descriptions> </n-descriptions>
<router-view :problems="problems"></router-view> <router-view></router-view>
</div> </div>
</template> </template>
@@ -182,7 +143,7 @@ function getCurrentType(name: string): "primary" | "default" {
transform: translateY(2px); transform: translateY(2px);
} }
.margin { .bottom {
margin-bottom: 24px; margin-bottom: 24px;
} }
</style> </style>

View File

@@ -2,13 +2,14 @@
import { DataTableColumn } from "naive-ui" import { DataTableColumn } from "naive-ui"
import { ProblemFiltered } from "utils/types" import { ProblemFiltered } from "utils/types"
import ProblemStatus from "~/oj/problem/components/ProblemStatus.vue" import ProblemStatus from "~/oj/problem/components/ProblemStatus.vue"
import { useContestStore } from "~/oj/store/contest"
const props = defineProps<{ const props = defineProps<{
contestID: string contestID: string
problems: ProblemFiltered[]
}>() }>()
const router = useRouter() const router = useRouter()
const contestStore = useContestStore()
const problemsColumns: DataTableColumn<ProblemFiltered>[] = [ const problemsColumns: DataTableColumn<ProblemFiltered>[] = [
{ {
title: "状态", title: "状态",
@@ -37,10 +38,10 @@ function rowProps(row: ProblemFiltered) {
striped striped
size="small" size="small"
class="problems" class="problems"
:data="problems" :data="contestStore.problems"
:columns="problemsColumns" :columns="problemsColumns"
:row-props="rowProps" :row-props="rowProps"
v-if="problems?.length" v-if="contestStore.problems?.length"
/> />
</template> </template>

69
src/oj/store/contest.ts Normal file
View File

@@ -0,0 +1,69 @@
import { useUserStore } from "~/shared/store/user"
import { ContestType } from "~/utils/constants"
import { Contest, Problem } from "~/utils/types"
import {
getContest,
getContestAccess,
getContestProblem,
checkContestPassword,
} from "../api"
export const useContestStore = defineStore("contest", () => {
const userStore = useUserStore()
const contest = ref<Contest>()
const [access, toggleAccsess] = useToggle()
const problems = ref<Problem[]>([])
const message = useMessage()
const contestStatus = computed(() => {
return false
})
const isContestAdmin = computed(
() =>
userStore.isSuperAdmin ||
(userStore.isAuthed && contest.value?.created_by.id === userStore.user.id)
)
async function init(contestID: string) {
const res = await getContest(contestID)
contest.value = res.data
if (contest.value?.contest_type === ContestType.private) {
const res = await getContestAccess(contestID)
toggleAccsess(res.data.access)
}
_getProblems(contestID)
}
async function checkPassword(contestID: string, password: string) {
try {
const res = await checkContestPassword(contestID, password)
toggleAccsess(res.data)
if (res.data) {
_getProblems(contestID)
}
} catch (err) {
toggleAccsess(false)
message.error("密码错误")
}
}
async function _getProblems(contestID: string) {
try {
problems.value = await getContestProblem(contestID)
} catch (err) {
problems.value = []
toggleAccsess(false)
}
}
return {
contest,
contestStatus,
isContestAdmin,
access,
problems,
init,
checkPassword,
}
})