add contest problems.

This commit is contained in:
2023-03-30 23:18:31 +08:00
parent 451e8d7c70
commit 70a4b67b58
10 changed files with 378 additions and 176 deletions

View File

@@ -16,11 +16,19 @@ export async function getProblemList(
offset = 0,
limit = 10,
keyword: string,
contestID?: string
contestID?: string,
ruleType?: "ACM" | "OI"
) {
const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem"
const res = await http.get(endpoint, {
params: { paging: true, offset, limit, keyword, contest_id: contestID },
params: {
paging: true,
offset,
limit,
keyword,
contest_id: contestID,
rule_type: ruleType,
},
})
return {
results: res.data.results.map((result: AdminProblem) => ({
@@ -129,3 +137,15 @@ export function getContest(id: string) {
params: { id },
})
}
export function addProblemForContest(
contestID: string,
problemID: number,
displayID: string
) {
return http.post("admin/contest/add_problem_from_public", {
contest_id: contestID,
problem_id: problemID,
display_id: displayID,
})
}

View File

@@ -23,12 +23,12 @@ function goEditProblems() {
</script>
<template>
<n-space>
<n-button size="small" type="primary" secondary @click="goEdit">
编辑
</n-button>
<n-button size="small" type="info" secondary @click="goEditProblems">
<n-button size="small" type="primary" secondary @click="goEditProblems">
题目
</n-button>
<n-button size="small" type="info" secondary @click="goEdit">
编辑
</n-button>
</n-space>
</template>
<style scoped></style>

View File

@@ -41,7 +41,16 @@ function goEdit() {
}
function goCheck() {
const data = router.resolve("/problem/" + props.problemDisplayID)
let data = router.resolve("/problem/" + props.problemDisplayID)
if (route.name === "admin contest problem list") {
data = router.resolve({
name: "contest problem",
params: {
contestID: route.params.contestID,
problemID: props.problemDisplayID,
},
})
}
window.open(data.href, "_blank")
}
</script>

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import { addProblemForContest } from "~/admin/api"
interface Props {
problemID: number
contestID: string
}
const props = defineProps<Props>()
const emit = defineEmits(["added"])
const message = useMessage()
const displayID = ref("")
async function addProblem() {
if (!displayID.value) return
try {
await addProblemForContest(
props.contestID,
props.problemID,
displayID.value
)
emit("added")
} catch (err: any) {
if (err.data === "Duplicate display id in this contest") {
message.error("显示编号重复了,请重新写一个")
} else {
message.error(err.data)
}
}
}
</script>
<template>
<n-popconfirm :show-icon="false" @positive-click="addProblem">
<template #trigger>
<n-button secondary size="small" type="primary">+</n-button>
</template>
<n-space vertical>
<span>请输入在这场比赛中的显示编号</span>
<n-input autofocus v-model:value="displayID" />
</n-space>
</n-popconfirm>
</template>
<style scoped></style>

View File

@@ -0,0 +1,90 @@
<script lang="ts" setup>
import { DataTableColumn } from "naive-ui"
import { getProblemList } from "~/admin/api"
import { AdminProblemFiltered } from "~/utils/types"
import Pagination from "~/shared/Pagination.vue"
import AddButton from "./AddButton.vue"
interface Props {
show: boolean
count: number
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: "update:show", value: boolean): void
(e: "change"): void
}>()
const route = useRoute()
const query = reactive({
page: 1,
limit: 10,
keyword: "",
})
const total = ref(0)
const problems = shallowRef<AdminProblemFiltered[]>([])
const columns: DataTableColumn<AdminProblemFiltered>[] = [
{ title: "编号", key: "_id", width: 80 },
{ title: "标题", key: "title" },
{
title: "选项",
key: "add",
render: (row) =>
h(AddButton, {
problemID: row.id,
contestID: <string>route.params.contestID,
onAdded: () => emit("change"),
}),
width: 60,
},
]
async function getList() {
const offset = (query.page - 1) * query.limit
const res = await getProblemList(
offset,
query.limit,
query.keyword,
"",
"ACM"
)
total.value = res.total
problems.value = res.results
}
watch(
() => props.show,
(value) => {
if (value) getList()
}
)
watch(query, getList, { deep: true })
</script>
<template>
<n-modal
:mask-closable="false"
:show="props.show"
preset="card"
style="width: 600px"
title="从题库中添加"
@close="$emit('update:show', false)"
>
<n-input
class="search"
v-model:value="query.keyword"
placeholder="搜索标题或编号"
/>
<n-data-table size="small" :columns="columns" :data="problems" />
<Pagination
:total="total"
v-model:limit="query.limit"
v-model:page="query.page"
/>
</n-modal>
</template>
<style scoped>
.search {
margin-bottom: 20px;
}
</style>

View File

@@ -5,12 +5,29 @@ import { DataTableColumn, NSwitch } from "naive-ui"
import { AdminProblemFiltered } from "~/utils/types"
import { parseTime } from "~/utils/functions"
import Actions from "./components/Actions.vue"
import Modal from "./components/Modal.vue"
interface Props {
contestID?: string
}
const props = defineProps<Props>()
const route = useRoute()
const router = useRouter()
const title = computed(
() =>
({
"admin problem list": "题目列表",
"admin contest problem list": "比赛题目列表",
}[<string>route.name])
)
const isContestProblemList = computed(
() => route.name === "admin contest problem list"
)
const [show, toggleShow] = useToggle()
const { count, inc } = useCounter(0)
const total = ref(0)
const problems = ref<AdminProblemFiltered[]>([])
const query = reactive({
@@ -78,14 +95,34 @@ async function toggleVisible(problemID: number) {
})
}
function createContestProblem() {
router.push({
name: "admin contest problem create",
params: { contestID: props.contestID },
})
}
async function selectProblems() {
toggleShow(true)
inc()
}
onMounted(listProblems)
watch(query, listProblems, { deep: true })
</script>
<template>
<n-space class="titleWrapper" justify="space-between">
<h2 class="title">题目列表</h2>
<n-input v-model:value="query.keyword" placeholder="输入标题关键字" />
<h2 class="title">{{ title }}</h2>
<n-space>
<n-button v-if="isContestProblemList" @click="createContestProblem">
新建比赛题目
</n-button>
<n-button v-if="isContestProblemList" @click="selectProblems">
从题库中选择
</n-button>
<n-input v-model:value="query.keyword" placeholder="输入标题关键字" />
</n-space>
</n-space>
<n-data-table striped size="small" :columns="columns" :data="problems" />
<Pagination
@@ -93,6 +130,7 @@ watch(query, listProblems, { deep: true })
v-model:limit="query.limit"
v-model:page="query.page"
/>
<Modal v-model:show="show" :count="count" @change="listProblems" />
</template>
<style scoped>

3
src/components.d.ts vendored
View File

@@ -16,6 +16,7 @@ declare module '@vue/runtime-core' {
IEpMenu: typeof import('~icons/ep/menu')['default']
IEpMoon: typeof import('~icons/ep/moon')['default']
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
IEpPlus: typeof import('~icons/ep/plus')['default']
IEpSunny: typeof import('~icons/ep/sunny')['default']
NAlert: typeof import('naive-ui')['NAlert']
NAvatar: typeof import('naive-ui')['NAvatar']
@@ -23,7 +24,7 @@ declare module '@vue/runtime-core' {
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NCode: typeof import('naive-ui')['NCode']
NCode: typeof import("naive-ui")["NCode"]
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDatePicker: typeof import('naive-ui')['NDatePicker']

View File

@@ -216,7 +216,7 @@ const columns = computed(() => {
NButton,
{
size: "small",
tertiary: true,
type: "primary",
onClick: () => rejudge(row.id),
},
() => "重新判题"