add contest problems.
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
44
src/admin/problem/components/AddButton.vue
Normal file
44
src/admin/problem/components/AddButton.vue
Normal 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>
|
||||
90
src/admin/problem/components/Modal.vue
Normal file
90
src/admin/problem/components/Modal.vue
Normal 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>
|
||||
@@ -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
3
src/components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -216,7 +216,7 @@ const columns = computed(() => {
|
||||
NButton,
|
||||
{
|
||||
size: "small",
|
||||
tertiary: true,
|
||||
type: "primary",
|
||||
onClick: () => rejudge(row.id),
|
||||
},
|
||||
() => "重新判题"
|
||||
|
||||
Reference in New Issue
Block a user