出题人
This commit is contained in:
17
package-lock.json
generated
17
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@codemirror/lang-cpp": "^6.0.3",
|
"@codemirror/lang-cpp": "^6.0.3",
|
||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
|
"@vueuse/router": "^13.9.0",
|
||||||
"@wangeditor-next/editor": "^5.6.46",
|
"@wangeditor-next/editor": "^5.6.46",
|
||||||
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
@@ -1530,6 +1531,22 @@
|
|||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/router": {
|
||||||
|
"version": "13.9.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vueuse/router/-/router-13.9.0.tgz",
|
||||||
|
"integrity": "sha512-7AYay8Pv/0fC4D0eygbIyZuLyVs+9D7dsnO5D8aqat9qcOz91v/XFWR667WE1+p+OkU0ib+FjQUdnTVBNoIw8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vueuse/shared": "13.9.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0",
|
||||||
|
"vue-router": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vueuse/shared": {
|
"node_modules/@vueuse/shared": {
|
||||||
"version": "13.9.0",
|
"version": "13.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@codemirror/lang-cpp": "^6.0.3",
|
"@codemirror/lang-cpp": "^6.0.3",
|
||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
|
"@vueuse/router": "^13.9.0",
|
||||||
"@wangeditor-next/editor": "^5.6.46",
|
"@wangeditor-next/editor": "^5.6.46",
|
||||||
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { AdminProblemFiltered } from "~/utils/types"
|
|||||||
import { getProblemList, toggleProblemVisible } from "../api"
|
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"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contestID?: string
|
contestID?: string
|
||||||
@@ -38,7 +39,7 @@ interface ProblemQuery {
|
|||||||
|
|
||||||
// 使用分页 composable
|
// 使用分页 composable
|
||||||
const { query, clearQuery } = usePagination<ProblemQuery>({
|
const { query, clearQuery } = usePagination<ProblemQuery>({
|
||||||
keyword: "",
|
keyword: useRouteQuery("keyword", "").value,
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
const columns: DataTableColumn<AdminProblemFiltered>[] = [
|
||||||
@@ -152,7 +153,7 @@ watch(
|
|||||||
从题库中选择
|
从题库中选择
|
||||||
</n-button>
|
</n-button>
|
||||||
<div>
|
<div>
|
||||||
<n-input v-model:value="query.keyword" placeholder="输入标题关键字" />
|
<n-input v-model:value="query.keyword" placeholder="输入标题关键字" clearable @clear="clearQuery" />
|
||||||
</div>
|
</div>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ export async function getProblemList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAuthors() {
|
||||||
|
return http.get("problem/author")
|
||||||
|
}
|
||||||
|
|
||||||
export function getRandomProblemID() {
|
export function getRandomProblemID() {
|
||||||
return http.get("pickone")
|
return http.get("pickone")
|
||||||
}
|
}
|
||||||
@@ -84,7 +88,7 @@ export function submitCode(data: SubmitCodePayload) {
|
|||||||
return http.post("submission", data)
|
return http.post("submission", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubmissions(params: SubmissionListPayload) {
|
export function getSubmissions(params: Partial<SubmissionListPayload>) {
|
||||||
const endpoint = !!params.contest_id ? "contest_submissions" : "submissions"
|
const endpoint = !!params.contest_id ? "contest_submissions" : "submissions"
|
||||||
return http.get(endpoint, { params })
|
return http.get(endpoint, { params })
|
||||||
}
|
}
|
||||||
@@ -249,13 +253,10 @@ export function getAIDetailData(start: string, end: string) {
|
|||||||
return http.get("ai/detail", { params: { start, end } })
|
return http.get("ai/detail", { params: { start, end } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAIWeeklyData(
|
export function getAIWeeklyData(end: string, duration: string) {
|
||||||
end: string,
|
|
||||||
duration: string,
|
|
||||||
) {
|
|
||||||
return http.get("ai/weekly", { params: { end, duration } })
|
return http.get("ai/weekly", { params: { end, duration } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAIHeatmapData() {
|
export function getAIHeatmapData() {
|
||||||
return http.get("ai/heatmap")
|
return http.get("ai/heatmap")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRouteQuery } from "@vueuse/router"
|
||||||
import { NTag } from "naive-ui"
|
import { NTag } from "naive-ui"
|
||||||
import { getContestList } from "oj/api"
|
import { getContestList } from "oj/api"
|
||||||
import { duration, parseTime } from "utils/functions"
|
import { duration, parseTime } from "utils/functions"
|
||||||
@@ -22,9 +23,9 @@ interface ContestQuery {
|
|||||||
|
|
||||||
// 使用分页 composable
|
// 使用分页 composable
|
||||||
const { query, clearQuery } = usePagination<ContestQuery>({
|
const { query, clearQuery } = usePagination<ContestQuery>({
|
||||||
keyword: "",
|
keyword: useRouteQuery("keyword", "").value,
|
||||||
status: "",
|
status: useRouteQuery("status", "").value,
|
||||||
tag: "",
|
tag: useRouteQuery("tag", "").value,
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = ref<Contest[]>([])
|
const data = ref<Contest[]>([])
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ watch(query, listSubmissions)
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-alert class="tip" type="error" :show-icon="false" v-else>
|
<n-alert class="tip" type="error" :show-icon="false" v-if="rank === -1 && class_ac_count > 0">
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-flex>
|
<n-flex>
|
||||||
<div>
|
<div>
|
||||||
@@ -212,7 +212,7 @@ watch(query, listSubmissions)
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-alert class="tip" type="error" :show-icon="false" v-else>
|
<n-alert class="tip" type="error" :show-icon="false" v-if="rank === -1 && all_ac_count > 0">
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { NSpace, NTag } from "naive-ui"
|
import { NSpace, NTag } from "naive-ui"
|
||||||
import { getProblemList } from "oj/api"
|
import { useRouteQuery } from "@vueuse/router"
|
||||||
|
import { getAuthors, 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"
|
||||||
@@ -23,6 +24,7 @@ interface ProblemQuery {
|
|||||||
keyword: string
|
keyword: string
|
||||||
difficulty: string
|
difficulty: string
|
||||||
tag: string
|
tag: string
|
||||||
|
author: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const difficultyOptions = [
|
const difficultyOptions = [
|
||||||
@@ -32,6 +34,8 @@ 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()
|
||||||
@@ -42,11 +46,22 @@ const [showTag, toggleShowTag] = useToggle(isDesktop.value)
|
|||||||
|
|
||||||
// 使用分页 composable
|
// 使用分页 composable
|
||||||
const { query, clearQuery } = usePagination<ProblemQuery>({
|
const { query, clearQuery } = usePagination<ProblemQuery>({
|
||||||
keyword: "",
|
keyword: useRouteQuery("keyword", "").value,
|
||||||
difficulty: "",
|
difficulty: useRouteQuery("difficulty", "").value,
|
||||||
tag: "",
|
tag: useRouteQuery("tag", "").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
|
||||||
const offset = (query.page - 1) * query.limit
|
const offset = (query.page - 1) * query.limit
|
||||||
@@ -54,6 +69,7 @@ async function listProblems() {
|
|||||||
keyword: query.keyword,
|
keyword: query.keyword,
|
||||||
tag: query.tag,
|
tag: query.tag,
|
||||||
difficulty: query.difficulty,
|
difficulty: query.difficulty,
|
||||||
|
author: query.author,
|
||||||
})
|
})
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
problems.value = res.results
|
problems.value = res.results
|
||||||
@@ -67,11 +83,6 @@ async function listTags() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function search(value: string) {
|
|
||||||
query.keyword = value
|
|
||||||
}
|
|
||||||
|
|
||||||
function chooseTag(tag: Tag) {
|
function chooseTag(tag: Tag) {
|
||||||
query.tag = tag.checked ? "" : tag.name
|
query.tag = tag.checked ? "" : tag.name
|
||||||
tags.value = tags.value.map((t) => {
|
tags.value = tags.value.map((t) => {
|
||||||
@@ -84,26 +95,21 @@ function chooseTag(tag: Tag) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
async function getRandom() {
|
||||||
clearQuery()
|
const res = await getRandomProblemID()
|
||||||
|
router.push("/problem/" + res.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function getRandom() {
|
|
||||||
// const res = await getRandomProblemID()
|
|
||||||
// router.push("/problem/" + res.data)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 监听搜索关键词变化(防抖)
|
// 监听搜索关键词变化(防抖)
|
||||||
watchDebounced(
|
watchDebounced(() => query.keyword, listProblems, {
|
||||||
() => query.keyword,
|
debounce: 500,
|
||||||
listProblems,
|
maxWait: 1000,
|
||||||
{ debounce: 500, maxWait: 1000 }
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// 监听其他查询条件变化
|
// 监听其他查询条件变化
|
||||||
watch(
|
watch(
|
||||||
() => [query.tag, query.difficulty, query.limit, query.page],
|
() => [query.tag, query.difficulty, query.limit, query.page, query.author],
|
||||||
listProblems
|
listProblems,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听标签变化,更新标签选中状态
|
// 监听标签变化,更新标签选中状态
|
||||||
@@ -117,14 +123,13 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听用户认证状态变化,只在认证完成且已登录时刷新问题列表
|
|
||||||
watch(
|
watch(
|
||||||
() => userStore.isFinished && userStore.isAuthed,
|
() => userStore.isFinished && userStore.isAuthed,
|
||||||
(isAuthenticatedAndFinished) => {
|
(isAuthenticatedAndFinished) => {
|
||||||
if (isAuthenticatedAndFinished) {
|
if (isAuthenticatedAndFinished) {
|
||||||
listProblems()
|
listProblems()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -212,12 +217,22 @@ function rowProps(row: ProblemFiltered) {
|
|||||||
:options="difficultyOptions"
|
:options="difficultyOptions"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
<n-form-item label="出题者">
|
||||||
|
<n-select
|
||||||
|
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-item>
|
||||||
<n-input
|
<n-input
|
||||||
clearable
|
clearable
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
v-model:value="query.keyword"
|
v-model:value="query.keyword"
|
||||||
placeholder="题号或者标题"
|
placeholder="编号或者标题"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
@@ -226,9 +241,8 @@ function rowProps(row: ProblemFiltered) {
|
|||||||
<n-form :show-feedback="false" inline label-placement="left">
|
<n-form :show-feedback="false" inline label-placement="left">
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
<n-button @click="search(query.keyword)">搜索</n-button>
|
<n-button @click="clearQuery" quaternary>重置</n-button>
|
||||||
<n-button @click="clear" quaternary>重置</n-button>
|
<n-button @click="getRandom" quaternary>试试手气</n-button>
|
||||||
<!-- <n-button @click="getRandom" quaternary>试试手气</n-button> -->
|
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NButton, NH2, NText } from "naive-ui"
|
import { NButton, NH2, NText } from "naive-ui"
|
||||||
|
import { useRouteQuery } from "@vueuse/router"
|
||||||
import { adminRejudge, getSubmissions, getTodaySubmissionCount } from "oj/api"
|
import { adminRejudge, getSubmissions, getTodaySubmissionCount } from "oj/api"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { LANGUAGE, SubmissionListItem } from "utils/types"
|
import { LANGUAGE, SubmissionListItem } from "utils/types"
|
||||||
@@ -18,7 +19,7 @@ import SubmissionDetail from "./detail.vue"
|
|||||||
interface SubmissionQuery {
|
interface SubmissionQuery {
|
||||||
username: string
|
username: string
|
||||||
result: string
|
result: string
|
||||||
myself: boolean
|
myself: "0" | "1"
|
||||||
problem: string
|
problem: string
|
||||||
language: LANGUAGE | ""
|
language: LANGUAGE | ""
|
||||||
}
|
}
|
||||||
@@ -34,11 +35,11 @@ const todayCount = ref(0)
|
|||||||
|
|
||||||
// 使用分页 composable
|
// 使用分页 composable
|
||||||
const { query, clearQuery } = usePagination<SubmissionQuery>({
|
const { query, clearQuery } = usePagination<SubmissionQuery>({
|
||||||
username: "",
|
username: useRouteQuery("username", "").value,
|
||||||
result: "",
|
result: useRouteQuery("result", "").value,
|
||||||
myself: false,
|
myself: useRouteQuery("myself", "0").value,
|
||||||
problem: "",
|
problem: useRouteQuery("problem", "").value,
|
||||||
language: "",
|
language: useRouteQuery("language", "").value,
|
||||||
})
|
})
|
||||||
const submissionID = ref("")
|
const submissionID = ref("")
|
||||||
const problemDisplayID = ref("")
|
const problemDisplayID = ref("")
|
||||||
@@ -65,7 +66,6 @@ async function listSubmissions() {
|
|||||||
try {
|
try {
|
||||||
const res = await getSubmissions({
|
const res = await getSubmissions({
|
||||||
...query,
|
...query,
|
||||||
myself: query.myself ? "1" : "0",
|
|
||||||
offset,
|
offset,
|
||||||
problem_id: query.problem,
|
problem_id: query.problem,
|
||||||
contest_id: <string>route.params.contestID ?? "",
|
contest_id: <string>route.params.contestID ?? "",
|
||||||
@@ -92,7 +92,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
function search(username: string, problem: string) {
|
function search(username: string, problem: string) {
|
||||||
query.username = username
|
query.username = username
|
||||||
query.problem = problem
|
query.problem = problem
|
||||||
@@ -129,11 +128,10 @@ function showCodePanel(id: string, problem: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 监听用户名和题号变化(防抖)
|
// 监听用户名和题号变化(防抖)
|
||||||
watchDebounced(
|
watchDebounced(() => [query.username, query.problem], listSubmissions, {
|
||||||
() => [query.username, query.problem],
|
debounce: 500,
|
||||||
listSubmissions,
|
maxWait: 1000,
|
||||||
{ debounce: 500, maxWait: 1000 },
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// 监听其他查询条件变化
|
// 监听其他查询条件变化
|
||||||
watch(
|
watch(
|
||||||
@@ -272,7 +270,11 @@ const columns = computed(() => {
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item v-if="userStore.isAuthed" label="只看自己">
|
<n-form-item v-if="userStore.isAuthed" label="只看自己">
|
||||||
<n-switch v-model:value="query.myself" />
|
<n-switch
|
||||||
|
v-model:value="query.myself"
|
||||||
|
checked-value="1"
|
||||||
|
unchecked-value="0"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
<n-form :show-feedback="false" inline label-placement="left">
|
<n-form :show-feedback="false" inline label-placement="left">
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ async function receive() {
|
|||||||
onMounted(receive)
|
onMounted(receive)
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="hitokoto" @click="receive">
|
<div class="hitokoto" @click="receive" v-if="hitokoto.sentence">
|
||||||
<div class="sentence">{{ hitokoto.sentence }}</div>
|
<div class="sentence">{{ hitokoto.sentence }}</div>
|
||||||
<div class="from">{{ "from " + hitokoto.from }}</div>
|
<div class="from">{{ "来自 " + hitokoto.from }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user