后台出题人
This commit is contained in:
@@ -25,8 +25,8 @@ export async function getProblemList(
|
|||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
keyword: string,
|
keyword: string,
|
||||||
|
author?: string,
|
||||||
contestID?: string,
|
contestID?: string,
|
||||||
ruleType?: "ACM" | "OI",
|
|
||||||
) {
|
) {
|
||||||
const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem"
|
const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem"
|
||||||
const res = await http.get(endpoint, {
|
const res = await http.get(endpoint, {
|
||||||
@@ -35,8 +35,8 @@ export async function getProblemList(
|
|||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
keyword,
|
keyword,
|
||||||
|
author,
|
||||||
contest_id: contestID,
|
contest_id: contestID,
|
||||||
rule_type: ruleType,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getProblemList, toggleProblemVisible } from "../api"
|
|||||||
import Actions from "./components/Actions.vue"
|
import Actions from "./components/Actions.vue"
|
||||||
import Modal from "./components/Modal.vue"
|
import Modal from "./components/Modal.vue"
|
||||||
import { useRouteQuery } from "@vueuse/router"
|
import { useRouteQuery } from "@vueuse/router"
|
||||||
|
import AuthorSelect from "~/shared/components/AuthorSelect.vue"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contestID?: string
|
contestID?: string
|
||||||
@@ -35,11 +36,13 @@ const problems = ref<AdminProblemFiltered[]>([])
|
|||||||
|
|
||||||
interface ProblemQuery {
|
interface ProblemQuery {
|
||||||
keyword: string
|
keyword: string
|
||||||
|
author: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用分页 composable
|
// 使用分页 composable
|
||||||
const { query, clearQuery } = usePagination<ProblemQuery>({
|
const { query, clearQuery } = usePagination<ProblemQuery>({
|
||||||
keyword: useRouteQuery("keyword", "").value,
|
keyword: useRouteQuery("keyword", "").value,
|
||||||
|
author: useRouteQuery("author", "").value,
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
||||||
@@ -77,7 +80,6 @@ const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async function listProblems() {
|
async function listProblems() {
|
||||||
if (query.page < 1) query.page = 1
|
if (query.page < 1) query.page = 1
|
||||||
const offset = (query.page - 1) * query.limit
|
const offset = (query.page - 1) * query.limit
|
||||||
@@ -85,6 +87,7 @@ async function listProblems() {
|
|||||||
offset,
|
offset,
|
||||||
query.limit,
|
query.limit,
|
||||||
query.keyword,
|
query.keyword,
|
||||||
|
query.author,
|
||||||
props.contestID,
|
props.contestID,
|
||||||
)
|
)
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
@@ -116,17 +119,13 @@ async function selectProblems() {
|
|||||||
onMounted(listProblems)
|
onMounted(listProblems)
|
||||||
|
|
||||||
// 监听搜索关键词变化(防抖)
|
// 监听搜索关键词变化(防抖)
|
||||||
watchDebounced(
|
watchDebounced(() => query.keyword, listProblems, {
|
||||||
() => query.keyword,
|
debounce: 500,
|
||||||
listProblems,
|
maxWait: 1000,
|
||||||
{ debounce: 500, maxWait: 1000 },
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// 监听其他查询条件变化
|
// 监听其他查询条件变化
|
||||||
watch(
|
watch(() => [query.page, query.limit, query.author], listProblems)
|
||||||
() => [query.page, query.limit],
|
|
||||||
listProblems,
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -152,8 +151,17 @@ watch(
|
|||||||
>
|
>
|
||||||
从题库中选择
|
从题库中选择
|
||||||
</n-button>
|
</n-button>
|
||||||
|
<n-flex align="center" v-if="!props.contestID">
|
||||||
|
<span>出题人</span>
|
||||||
|
<AuthorSelect v-model:value="query.author" all />
|
||||||
|
</n-flex>
|
||||||
<div>
|
<div>
|
||||||
<n-input v-model:value="query.keyword" placeholder="输入标题关键字" clearable @clear="clearQuery" />
|
<n-input
|
||||||
|
v-model:value="query.keyword"
|
||||||
|
placeholder="输入标题关键字"
|
||||||
|
clearable
|
||||||
|
@clear="clearQuery"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|||||||
@@ -56,8 +56,12 @@ export async function getProblemList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAuthors() {
|
export function getAuthors(all = false) {
|
||||||
return http.get("problem/author")
|
return http.get("problem/author", {
|
||||||
|
params: {
|
||||||
|
all: all ? "1" : "0",
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomProblemID() {
|
export function getRandomProblemID() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { NSpace, NTag } from "naive-ui"
|
import { NSpace, NTag } from "naive-ui"
|
||||||
import { useRouteQuery } from "@vueuse/router"
|
import { useRouteQuery } from "@vueuse/router"
|
||||||
import { getAuthors, getProblemList, getRandomProblemID } from "oj/api"
|
import { getProblemList, getRandomProblemID } from "oj/api"
|
||||||
import { getTagColor } from "utils/functions"
|
import { getTagColor } from "utils/functions"
|
||||||
import { ProblemFiltered } from "utils/types"
|
import { ProblemFiltered } from "utils/types"
|
||||||
import { getProblemTagList } from "~/shared/api"
|
import { getProblemTagList } from "~/shared/api"
|
||||||
@@ -13,6 +13,7 @@ import { usePagination } from "~/shared/composables/pagination"
|
|||||||
import { useUserStore } from "~/shared/store/user"
|
import { useUserStore } from "~/shared/store/user"
|
||||||
import { renderTableTitle } from "~/utils/renders"
|
import { renderTableTitle } from "~/utils/renders"
|
||||||
import ProblemStatus from "./components/ProblemStatus.vue"
|
import ProblemStatus from "./components/ProblemStatus.vue"
|
||||||
|
import AuthorSelect from "~/shared/components/AuthorSelect.vue"
|
||||||
|
|
||||||
interface Tag {
|
interface Tag {
|
||||||
id: number
|
id: number
|
||||||
@@ -34,8 +35,6 @@ const difficultyOptions = [
|
|||||||
{ label: "困难", value: "High" },
|
{ label: "困难", value: "High" },
|
||||||
]
|
]
|
||||||
|
|
||||||
const authorOptions = ref([{ label: "全部", value: "" }])
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@@ -52,17 +51,6 @@ const { query, clearQuery } = usePagination<ProblemQuery>({
|
|||||||
author: useRouteQuery("author", "").value,
|
author: useRouteQuery("author", "").value,
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getAuthorOptions() {
|
|
||||||
authorOptions.value = [{ label: "全部", value: "" }]
|
|
||||||
const res = await getAuthors()
|
|
||||||
const remotes = res.data.map(
|
|
||||||
(item: { username: string; problem_count: number }) => ({
|
|
||||||
label: `${item.username} (${item.problem_count})`,
|
|
||||||
value: item.username,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
authorOptions.value = [...authorOptions.value, ...remotes]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listProblems() {
|
async function listProblems() {
|
||||||
if (query.page < 1) query.page = 1
|
if (query.page < 1) query.page = 1
|
||||||
@@ -220,14 +208,7 @@ function rowProps(row: ProblemFiltered) {
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="出题者">
|
<n-form-item label="出题者">
|
||||||
<n-select
|
<AuthorSelect v-model:value="query.author" />
|
||||||
style="width: 160px"
|
|
||||||
v-model:value="query.author"
|
|
||||||
remote
|
|
||||||
@update:show="getAuthorOptions"
|
|
||||||
@update:value="(val) => (query.author = val)"
|
|
||||||
:options="authorOptions"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -242,22 +223,24 @@ function rowProps(row: ProblemFiltered) {
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-flex align="center">
|
<n-button @click="clearQuery" quaternary>重置</n-button>
|
||||||
<n-button @click="clearQuery" quaternary>重置</n-button>
|
</n-form-item>
|
||||||
<n-button @click="getRandom" quaternary>试试手气</n-button>
|
<!-- <n-form-item>
|
||||||
</n-flex>
|
<n-button @click="getRandom" quaternary>随机</n-button>
|
||||||
|
</n-form-item> -->
|
||||||
|
<n-form-item>
|
||||||
|
<n-button
|
||||||
|
@click="toggleShowTag()"
|
||||||
|
quaternary
|
||||||
|
icon-placement="right"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon v-if="showTag" icon="ph:caret-down"></Icon>
|
||||||
|
<Icon v-else icon="ph:caret-up"></Icon>
|
||||||
|
</template>
|
||||||
|
标签
|
||||||
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-button
|
|
||||||
@click="toggleShowTag()"
|
|
||||||
quaternary
|
|
||||||
icon-placement="right"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<Icon v-if="showTag" icon="ph:caret-down"></Icon>
|
|
||||||
<Icon v-else icon="ph:caret-up"></Icon>
|
|
||||||
</template>
|
|
||||||
标签
|
|
||||||
</n-button>
|
|
||||||
</n-form>
|
</n-form>
|
||||||
</div>
|
</div>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|||||||
35
src/shared/components/AuthorSelect.vue
Normal file
35
src/shared/components/AuthorSelect.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<n-select
|
||||||
|
style="width: 140px"
|
||||||
|
v-model:value="author"
|
||||||
|
remote
|
||||||
|
@update:show="getAuthorOptions"
|
||||||
|
:options="authorOptions"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getAuthors } from "~/oj/api"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
all?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const { all = false } = defineProps<Props>()
|
||||||
|
|
||||||
|
const author = defineModel<string>("value")
|
||||||
|
|
||||||
|
const authorOptions = ref([{ label: "全部", value: "" }])
|
||||||
|
|
||||||
|
async function getAuthorOptions() {
|
||||||
|
authorOptions.value = [{ label: "全部", value: "" }]
|
||||||
|
const res = await getAuthors(all)
|
||||||
|
const remotes = res.data.map(
|
||||||
|
(item: { username: string; problem_count: number }) => ({
|
||||||
|
label: `${item.username} (${item.problem_count})`,
|
||||||
|
value: item.username,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
authorOptions.value = [...authorOptions.value, ...remotes]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -13,7 +13,6 @@ http.interceptors.response.use(
|
|||||||
if (res.data.error) {
|
if (res.data.error) {
|
||||||
if (res.data.data && res.data.data.startsWith("Please login")) {
|
if (res.data.data && res.data.data.startsWith("Please login")) {
|
||||||
storage.remove(STORAGE_KEY.AUTHED)
|
storage.remove(STORAGE_KEY.AUTHED)
|
||||||
window.location.reload()
|
|
||||||
}
|
}
|
||||||
return Promise.reject(res.data)
|
return Promise.reject(res.data)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user