contest list.
This commit is contained in:
@@ -40,6 +40,12 @@ export function deleteUsers(userIDs: number[]) {
|
|||||||
return http.delete("admin/user", { params: { id: userIDs.join(",") } })
|
return http.delete("admin/user", { params: { id: userIDs.join(",") } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function editUser(user: User & { password: string }) {
|
export function editUser(user: User) {
|
||||||
return http.put("admin/user", user)
|
return http.put("admin/user", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getContestList(offset = 0, limit = 10, keyword: string) {
|
||||||
|
return http.get("admin/contest", {
|
||||||
|
params: { paging: true, offset, limit, keyword },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
41
src/admin/contest/components/Actions.vue
Normal file
41
src/admin/contest/components/Actions.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Contest } from "~/utils/types"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contest: Contest
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function goEdit() {
|
||||||
|
router.push({
|
||||||
|
name: "admin contest edit",
|
||||||
|
params: { contestID: props.contest.id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function goEditProblems() {
|
||||||
|
router.push({
|
||||||
|
name: "admin contest problems",
|
||||||
|
params: { contestID: props.contest.id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,7 +1,90 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { DataTableColumn, NSwitch, NTag } from "naive-ui"
|
||||||
|
import Pagination from "~/shared/Pagination.vue"
|
||||||
|
import { Contest } from "~/utils/types"
|
||||||
|
import { getContestList } from "../api"
|
||||||
|
import ContestType from "~/shared/ContestType.vue"
|
||||||
|
import ContestTitle from "~/shared/ContestTitle.vue"
|
||||||
|
import { CONTEST_STATUS } from "~/utils/constants"
|
||||||
|
import Actions from "./components/Actions.vue"
|
||||||
|
|
||||||
|
const contests = ref<Contest[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const query = reactive({
|
||||||
|
limit: 10,
|
||||||
|
page: 1,
|
||||||
|
keyword: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggleVisible(id: number) {}
|
||||||
|
|
||||||
|
const columns: DataTableColumn<Contest>[] = [
|
||||||
|
{ title: "ID", key: "id", width: 60 },
|
||||||
|
{
|
||||||
|
title: "比赛",
|
||||||
|
key: "title",
|
||||||
|
minWidth: 250,
|
||||||
|
render: (row) => h(ContestTitle, { contest: row }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "类型",
|
||||||
|
key: "contest_type",
|
||||||
|
width: 80,
|
||||||
|
render: (row) => h(ContestType, { contest: row, size: "small" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "状态",
|
||||||
|
key: "status",
|
||||||
|
width: 80,
|
||||||
|
render: (row) =>
|
||||||
|
h(
|
||||||
|
NTag,
|
||||||
|
{ type: CONTEST_STATUS[row.status]["type"], size: "small" },
|
||||||
|
() => CONTEST_STATUS[row.status]["name"]
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "可见",
|
||||||
|
key: "visible",
|
||||||
|
width: 60,
|
||||||
|
render: (row) =>
|
||||||
|
h(NSwitch, {
|
||||||
|
value: row.visible,
|
||||||
|
size: "small",
|
||||||
|
rubberBand: false,
|
||||||
|
onUpdateValue: () => toggleVisible(row.id),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "选项",
|
||||||
|
key: "actions",
|
||||||
|
width: 260,
|
||||||
|
render: (row) => h(Actions, { contest: row }),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async function listContests() {
|
||||||
|
const offset = (query.page - 1) * query.limit
|
||||||
|
const res = await getContestList(offset, query.limit, query.keyword)
|
||||||
|
contests.value = res.data.results
|
||||||
|
total.value = res.data.total
|
||||||
|
}
|
||||||
|
onMounted(listContests)
|
||||||
|
watch(query, listContests, { deep: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>contest list</div>
|
<n-form>
|
||||||
|
<n-form-item>
|
||||||
|
<n-input v-model:value="query.keyword" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<n-data-table :columns="columns" :data="contests" size="small" />
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:limit="query.limit"
|
||||||
|
v-model:page="query.page"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "edit",
|
title: "选项",
|
||||||
|
key: "actions",
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (row) => h(Actions, { problemID: row.id, onDeleted: listProblems }),
|
render: (row) => h(Actions, { problemID: row.id, onDeleted: listProblems }),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const columns: DataTableColumn<User>[] = [
|
|||||||
{ title: "真名", key: "real_name", width: 100 },
|
{ title: "真名", key: "real_name", width: 100 },
|
||||||
{ title: "邮箱", key: "email", width: 200 },
|
{ title: "邮箱", key: "email", width: 200 },
|
||||||
{
|
{
|
||||||
key: "edit",
|
key: "actions",
|
||||||
title: "选项",
|
title: "选项",
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (row) =>
|
render: (row) =>
|
||||||
|
|||||||
8
src/components.d.ts
vendored
8
src/components.d.ts
vendored
@@ -18,12 +18,10 @@ declare module '@vue/runtime-core' {
|
|||||||
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
|
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
|
||||||
IEpSunny: typeof import('~icons/ep/sunny')['default']
|
IEpSunny: typeof import('~icons/ep/sunny')['default']
|
||||||
NAlert: typeof import('naive-ui')['NAlert']
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NAvatar: typeof import("naive-ui")["NAvatar"]
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
|
||||||
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
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']
|
||||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||||
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
||||||
@@ -55,7 +53,7 @@ declare module '@vue/runtime-core' {
|
|||||||
NTabs: typeof import('naive-ui')['NTabs']
|
NTabs: typeof import('naive-ui')['NTabs']
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
NUpload: typeof import("naive-ui")["NUpload"]
|
NUpload: typeof import('naive-ui')['NUpload']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { useContestStore } from "oj/store/contest"
|
import { useContestStore } from "oj/store/contest"
|
||||||
import ContestTypeVue from "./ContestType.vue"
|
import ContestTypeVue from "~/shared/ContestType.vue"
|
||||||
|
|
||||||
const contestStore = useContestStore()
|
const contestStore = useContestStore()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DataTableColumn, NTag, SelectOption } from "naive-ui"
|
import { DataTableColumn, NTag, SelectOption } from "naive-ui"
|
||||||
import { getContestList } from "oj/api"
|
import { getContestList } from "oj/api"
|
||||||
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 { ContestType, CONTEST_STATUS } from "~/utils/constants"
|
import { ContestType, CONTEST_STATUS } from "~/utils/constants"
|
||||||
import ContestTitle from "./components/ContestTitle.vue"
|
import ContestTitle from "~/shared/ContestTitle.vue"
|
||||||
|
import Pagination from "~/shared/Pagination.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"
|
||||||
import { isDesktop } from "~/shared/composables/breakpoints"
|
import { isDesktop } from "~/shared/composables/breakpoints"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ContestType } from "~/utils/constants"
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contest: Contest
|
contest: Contest
|
||||||
|
size?: "small"
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
@@ -14,7 +15,7 @@ const isPrivate = computed(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-tag :type="isPrivate ? 'error' : 'info'">
|
<n-tag :type="isPrivate ? 'error' : 'info'" :size="props.size">
|
||||||
{{ isPrivate ? "需要密码" : "公开" }}
|
{{ isPrivate ? "需要密码" : "公开" }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</template>
|
</template>
|
||||||
@@ -45,6 +45,7 @@ export interface User {
|
|||||||
two_factor_auth: boolean
|
two_factor_auth: boolean
|
||||||
open_api: boolean
|
open_api: boolean
|
||||||
is_disabled: boolean
|
is_disabled: boolean
|
||||||
|
password?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LANGUAGE =
|
export type LANGUAGE =
|
||||||
@@ -236,6 +237,7 @@ export interface Contest {
|
|||||||
create_time: string
|
create_time: string
|
||||||
now: string
|
now: string
|
||||||
last_update_time: string
|
last_update_time: string
|
||||||
|
visible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubmissionInfo {
|
interface SubmissionInfo {
|
||||||
|
|||||||
Reference in New Issue
Block a user