Compare commits
2
Commits
d4aadc7722
...
ff3fdc0b1d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff3fdc0b1d | ||
|
|
e8a20d12f9 |
No files matched your search
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getAuthors } from "oj/api"
|
import { getAuthors } from "shared/api"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
all?: boolean
|
all?: boolean
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EditorView } from "@codemirror/view"
|
import { EditorView } from "@codemirror/view"
|
||||||
import { PROBLEM_TAG_MAX_LENGTH, TEST_CASE_MAX_CASES } from "@oj2/contract"
|
import { TEST_CASE_MAX_CASES } from "@oj2/contract"
|
||||||
import Markdown from "shared/components/Markdown.vue"
|
import Markdown from "shared/components/Markdown.vue"
|
||||||
import { StorageSerializers } from "@vueuse/core"
|
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import PageHeader from "admin/components/PageHeader.vue"
|
import PageHeader from "admin/components/PageHeader.vue"
|
||||||
import ProblemTypeTag from "oj/problem/components/ProblemTypeTag.vue"
|
import ProblemTypeTag from "oj/problem/components/ProblemTypeTag.vue"
|
||||||
@@ -24,6 +23,15 @@ import {
|
|||||||
previewSQLTestcase,
|
previewSQLTestcase,
|
||||||
uploadTestcases,
|
uploadTestcases,
|
||||||
} from "../api"
|
} from "../api"
|
||||||
|
import {
|
||||||
|
cleanTags,
|
||||||
|
difficultyOptions,
|
||||||
|
isBlankHtml,
|
||||||
|
problemListRoute,
|
||||||
|
saveErrorText,
|
||||||
|
tagTooLongText,
|
||||||
|
useCreateDraft,
|
||||||
|
} from "./components/problemForm"
|
||||||
import {
|
import {
|
||||||
explainSqlError,
|
explainSqlError,
|
||||||
groupSummary,
|
groupSummary,
|
||||||
@@ -94,13 +102,6 @@ const blankForm = (): SqlForm => ({
|
|||||||
|
|
||||||
const form = reactive<SqlForm>(blankForm())
|
const form = reactive<SqlForm>(blankForm())
|
||||||
|
|
||||||
/** 富文本编辑器清空之后留下的是一个空段落,不是空串 */
|
|
||||||
const isBlankHtml = (html: string) => !html.trim() || html === "<p><br></p>"
|
|
||||||
|
|
||||||
const difficultyOptions = (["Low", "Mid", "High"] as const).map((value) => ({
|
|
||||||
label: DIFFICULTY[value],
|
|
||||||
value,
|
|
||||||
}))
|
|
||||||
const testCase = reactive({ id: "", score: [] as Testcase[] })
|
const testCase = reactive({ id: "", score: [] as Testcase[] })
|
||||||
|
|
||||||
let nextKey = 0
|
let nextKey = 0
|
||||||
@@ -118,28 +119,17 @@ const showMore = ref(false)
|
|||||||
const ready = ref(false)
|
const ready = ref(false)
|
||||||
|
|
||||||
// 新建时存草稿:出一道 SQL 题要写好几段脚本,误关页面全没了太亏
|
// 新建时存草稿:出一道 SQL 题要写好几段脚本,误关页面全没了太亏
|
||||||
// 初值是 null 时 VueUse 推不出类型,会拿 String() 存成 "[object Object]",得显式指定
|
const draft = useCreateDraft<{ form: SqlForm; scripts: string[] }>(STORAGE_KEY.ADMIN_SQL_PROBLEM, {
|
||||||
const draft = useLocalStorage<{ form: SqlForm; scripts: string[] } | null>(
|
source: () => [form, groups.value.map((g) => g.sql)] as const,
|
||||||
STORAGE_KEY.ADMIN_SQL_PROBLEM,
|
enabled: () => ready.value && isCreate.value,
|
||||||
null,
|
empty: () =>
|
||||||
{ serializer: StorageSerializers.object },
|
!form._id &&
|
||||||
)
|
!form.title &&
|
||||||
|
isBlankHtml(form.description) &&
|
||||||
watchDebounced(
|
!form.answer &&
|
||||||
() => [form, groups.value.map((g) => g.sql)] as const,
|
!groups.value.some((g) => g.sql),
|
||||||
() => {
|
take: () => ({ form: { ...form }, scripts: groups.value.map((g) => g.sql) }),
|
||||||
if (!ready.value || !isCreate.value) return
|
})
|
||||||
const scripts = groups.value.map((g) => g.sql)
|
|
||||||
const empty =
|
|
||||||
!form._id &&
|
|
||||||
!form.title &&
|
|
||||||
isBlankHtml(form.description) &&
|
|
||||||
!form.answer &&
|
|
||||||
!scripts.some(Boolean)
|
|
||||||
draft.value = empty ? null : { form: { ...form }, scripts }
|
|
||||||
},
|
|
||||||
{ debounce: 800, deep: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
function clearDraft() {
|
function clearDraft() {
|
||||||
draft.value = null
|
draft.value = null
|
||||||
@@ -212,12 +202,9 @@ const tagOptions = computed(() =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
function onTagsChange(tags: string[]) {
|
function onTagsChange(tags: string[]) {
|
||||||
const tooLong = tags.find((t) => t.length > PROBLEM_TAG_MAX_LENGTH)
|
const cleaned = cleanTags(tags)
|
||||||
if (tooLong) {
|
if ("tooLong" in cleaned) message.error(tagTooLongText(cleaned.tooLong))
|
||||||
message.error(`标签最多 ${PROBLEM_TAG_MAX_LENGTH} 个字:${tooLong}`)
|
else form.tags = cleaned.tags
|
||||||
return
|
|
||||||
}
|
|
||||||
form.tags = unique(tags.map((t) => t.trim()).filter(Boolean))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- 测试数据:自动重跑
|
// ---------------------------------------------------------------- 测试数据:自动重跑
|
||||||
@@ -415,25 +402,15 @@ async function save() {
|
|||||||
} else {
|
} else {
|
||||||
message.success("保存好了")
|
message.success("保存好了")
|
||||||
}
|
}
|
||||||
router.push(
|
router.push(problemListRoute(props.contestID))
|
||||||
props.contestID
|
|
||||||
? { name: "admin contest problem list", params: { contestID: props.contestID } }
|
|
||||||
: { name: "admin problem list" },
|
|
||||||
)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error(
|
message.error(saveErrorText(err))
|
||||||
errorCode(err) === "display-id-exists" ? "显示编号重复了,换一个" : errorMessage(err),
|
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const backTo = computed(() =>
|
const backTo = computed(() => problemListRoute(props.contestID))
|
||||||
props.contestID
|
|
||||||
? { name: "admin contest problem list", params: { contestID: props.contestID } }
|
|
||||||
: { name: "admin problem list" },
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getProblemTagList().then((res) => (tagList.value = res))
|
getProblemTagList().then((res) => (tagList.value = res))
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getTagColor } from "utils/functions"
|
|||||||
import type { AstRules, BlankProblem, LANGUAGE } from "utils/types"
|
import type { AstRules, BlankProblem, LANGUAGE } from "utils/types"
|
||||||
import type { AstCheckState, FlowchartMode, PreviewTab } from "./editorTypes"
|
import type { AstCheckState, FlowchartMode, PreviewTab } from "./editorTypes"
|
||||||
import type { TemplateParts } from "./codeTemplate"
|
import type { TemplateParts } from "./codeTemplate"
|
||||||
|
import { isBlankHtml } from "./problemForm"
|
||||||
|
|
||||||
const CodeEditor = defineAsyncComponent(() => import("shared/components/CodeEditor.vue"))
|
const CodeEditor = defineAsyncComponent(() => import("shared/components/CodeEditor.vue"))
|
||||||
|
|
||||||
@@ -41,8 +42,6 @@ const emit = defineEmits<{ renderState: [ok: boolean]; collapse: [] }>()
|
|||||||
|
|
||||||
const theme = useThemeVars()
|
const theme = useThemeVars()
|
||||||
|
|
||||||
const isBlankHtml = (html: string) => !html.trim() || html === "<p><br></p>"
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- 语法要求
|
// ---------------------------------------------------------------- 语法要求
|
||||||
|
|
||||||
const KIND_TAG_TYPE = { require: "success", forbid: "error", count: "info" } as const
|
const KIND_TAG_TYPE = { require: "success", forbid: "error", count: "info" } as const
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { PROBLEM_TAG_MAX_LENGTH } from "@oj2/contract"
|
||||||
|
import { StorageSerializers, useLocalStorage, watchDebounced } from "@vueuse/core"
|
||||||
|
import type { WatchSource } from "vue"
|
||||||
|
import { errorCode, errorMessage } from "utils/api"
|
||||||
|
import { DIFFICULTY } from "utils/constants"
|
||||||
|
import { unique } from "utils/functions"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编程题(detail.vue)、SQL 题(SqlDetail.vue)两个出题页共用的零件。两页要填的东西几乎
|
||||||
|
* 不重合,所以没合成一页(见 SqlDetail.vue 顶部),但这几样原来是逐字抄了两三份。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 富文本编辑器清空之后留下的是一个空段落,不是空串 */
|
||||||
|
export const isBlankHtml = (html: string) => !html.trim() || html === "<p><br></p>"
|
||||||
|
|
||||||
|
export const difficultyOptions = (["Low", "Mid", "High"] as const).map((value) => ({
|
||||||
|
label: DIFFICULTY[value],
|
||||||
|
value,
|
||||||
|
}))
|
||||||
|
|
||||||
|
/** 出题页的「返回」:比赛题回这场比赛的题目列表,公开题回题库 */
|
||||||
|
export function problemListRoute(contestID: string | undefined) {
|
||||||
|
return contestID
|
||||||
|
? { name: "admin contest problem list", params: { contestID } }
|
||||||
|
: { name: "admin problem list" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存失败给老师看的话。编号撞了是最常见的一种,单独说清楚 */
|
||||||
|
export function saveErrorText(err: unknown) {
|
||||||
|
return errorCode(err) === "display-id-exists" ? "显示编号重复了,换一个" : errorMessage(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下拉里新打的标签:去空格、去重。有超长的就返回它(调用方提示、这次改动不收),
|
||||||
|
* 后端也卡这个长度,先在这儿拦省一次往返
|
||||||
|
*/
|
||||||
|
export function cleanTags(tags: string[]): { tags: string[] } | { tooLong: string } {
|
||||||
|
const tooLong = tags.find((t) => t.length > PROBLEM_TAG_MAX_LENGTH)
|
||||||
|
if (tooLong) return { tooLong }
|
||||||
|
return { tags: unique(tags.map((t) => t.trim()).filter(Boolean)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tagTooLongText = (tag: string) => `标签最多 ${PROBLEM_TAG_MAX_LENGTH} 个字:${tag}`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建页的草稿:改动停 0.8 秒存一次,什么都没填就清掉。编辑页不存 —— 原来编辑页也往
|
||||||
|
* 同一个 key 里写,没保存就离开的话,那道题的 id 会跟着草稿漂到新建页。
|
||||||
|
*
|
||||||
|
* `enabled` 在页面数据摆好之前要返回 false,不然加载时那一下会把空表单存进去。
|
||||||
|
*/
|
||||||
|
export function useCreateDraft<T>(
|
||||||
|
key: string,
|
||||||
|
options: { source: WatchSource; enabled: () => boolean; empty: () => boolean; take: () => T },
|
||||||
|
) {
|
||||||
|
// 初值是 null 时 VueUse 推不出类型,会拿 String() 存成 "[object Object]",得显式指定
|
||||||
|
const draft = useLocalStorage<T | null>(key, null, { serializer: StorageSerializers.object })
|
||||||
|
watchDebounced(
|
||||||
|
options.source,
|
||||||
|
() => {
|
||||||
|
if (!options.enabled()) return
|
||||||
|
draft.value = options.empty() ? null : options.take()
|
||||||
|
},
|
||||||
|
{ debounce: 800, deep: true },
|
||||||
|
)
|
||||||
|
return draft
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PROBLEM_TAG_MAX_LENGTH, TEST_CASE_MAX_CASES, type RunnableLanguage } from "@oj2/contract"
|
import { TEST_CASE_MAX_CASES, type RunnableLanguage } from "@oj2/contract"
|
||||||
import { StorageSerializers } from "@vueuse/core"
|
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import PageHeader from "admin/components/PageHeader.vue"
|
import PageHeader from "admin/components/PageHeader.vue"
|
||||||
import { getProblemTagList } from "shared/api"
|
import { getProblemTagList } from "shared/api"
|
||||||
import TextEditor from "shared/components/TextEditor.vue"
|
import TextEditor from "shared/components/TextEditor.vue"
|
||||||
import { errorCode, errorMessage } from "utils/api"
|
import { errorMessage } from "utils/api"
|
||||||
import { DIFFICULTY, STORAGE_KEY } from "utils/constants"
|
import { DIFFICULTY, STORAGE_KEY } from "utils/constants"
|
||||||
import download from "utils/download"
|
import download from "utils/download"
|
||||||
import { createZipBlob, parseTime, unique } from "utils/functions"
|
import { createZipBlob, parseTime, unique } from "utils/functions"
|
||||||
@@ -24,6 +23,15 @@ import CaseTable from "./components/CaseTable.vue"
|
|||||||
import CodePreview from "./components/CodePreview.vue"
|
import CodePreview from "./components/CodePreview.vue"
|
||||||
import PreviewStrip from "./components/PreviewStrip.vue"
|
import PreviewStrip from "./components/PreviewStrip.vue"
|
||||||
import { usePreviewCollapsed } from "./components/previewCollapse"
|
import { usePreviewCollapsed } from "./components/previewCollapse"
|
||||||
|
import {
|
||||||
|
cleanTags,
|
||||||
|
difficultyOptions,
|
||||||
|
isBlankHtml,
|
||||||
|
problemListRoute,
|
||||||
|
saveErrorText,
|
||||||
|
tagTooLongText,
|
||||||
|
useCreateDraft,
|
||||||
|
} from "./components/problemForm"
|
||||||
import {
|
import {
|
||||||
caseFiles,
|
caseFiles,
|
||||||
caseStatus,
|
caseStatus,
|
||||||
@@ -135,14 +143,6 @@ const blankForm = (): CodeForm => ({
|
|||||||
|
|
||||||
const form = reactive<CodeForm>(blankForm())
|
const form = reactive<CodeForm>(blankForm())
|
||||||
|
|
||||||
/** 富文本编辑器清空之后留下的是一个空段落,不是空串 */
|
|
||||||
const isBlankHtml = (html: string) => !html.trim() || html === "<p><br></p>"
|
|
||||||
|
|
||||||
const difficultyOptions = (["Low", "Mid", "High"] as const).map((value) => ({
|
|
||||||
label: DIFFICULTY[value],
|
|
||||||
value,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const NO_INPUT = "<p>没有输入</p>"
|
const NO_INPUT = "<p>没有输入</p>"
|
||||||
|
|
||||||
// ---------------------------------------------------------------- 标签
|
// ---------------------------------------------------------------- 标签
|
||||||
@@ -176,13 +176,12 @@ function toggleKnowledge(name: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onOtherTagsChange(tags: string[]) {
|
function onOtherTagsChange(tags: string[]) {
|
||||||
const tooLong = tags.find((t) => t.length > PROBLEM_TAG_MAX_LENGTH)
|
const cleaned = cleanTags(tags)
|
||||||
if (tooLong) {
|
if ("tooLong" in cleaned) {
|
||||||
message.error(`标签最多 ${PROBLEM_TAG_MAX_LENGTH} 个字:${tooLong}`)
|
message.error(tagTooLongText(cleaned.tooLong))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const others = unique(tags.map((t) => t.trim()).filter(Boolean))
|
form.tags = unique([...form.tags.filter((t) => knowledgeSet.value.has(t)), ...cleaned.tags])
|
||||||
form.tags = unique([...form.tags.filter((t) => knowledgeSet.value.has(t)), ...others])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- 标准答案
|
// ---------------------------------------------------------------- 标准答案
|
||||||
@@ -525,11 +524,6 @@ interface Draft {
|
|||||||
rows: { input: string; output: string; example: boolean }[]
|
rows: { input: string; output: string; example: boolean }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新建时存草稿(编辑页不存:原来编辑页也往同一个 key 里写,没保存就离开的话,
|
|
||||||
// 那道题的 id 会跟着草稿漂到新建页)。初值 null 时 VueUse 推不出类型,得显式指定
|
|
||||||
const draft = useLocalStorage<Draft | null>(STORAGE_KEY.ADMIN_CODE_PROBLEM, null, {
|
|
||||||
serializer: StorageSerializers.object,
|
|
||||||
})
|
|
||||||
const ready = ref(false)
|
const ready = ref(false)
|
||||||
const draftRestored = ref(false)
|
const draftRestored = ref(false)
|
||||||
|
|
||||||
@@ -543,25 +537,21 @@ function snapshot() {
|
|||||||
}
|
}
|
||||||
let baseline = ""
|
let baseline = ""
|
||||||
|
|
||||||
watchDebounced(
|
// 新建时存草稿(编辑页不存,见 useCreateDraft)
|
||||||
() => [form, rows.value.map((r) => [r.input, r.output, r.example])] as const,
|
const draft = useCreateDraft<Draft>(STORAGE_KEY.ADMIN_CODE_PROBLEM, {
|
||||||
() => {
|
source: () => [form, rows.value.map((r) => [r.input, r.output, r.example])] as const,
|
||||||
if (!ready.value || !isCreate.value) return
|
enabled: () => ready.value && isCreate.value,
|
||||||
const empty =
|
empty: () =>
|
||||||
!form._id &&
|
!form._id &&
|
||||||
!form.title &&
|
!form.title &&
|
||||||
isBlankHtml(form.description) &&
|
isBlankHtml(form.description) &&
|
||||||
!RUNNABLE.some((l) => form.answers[l].trim()) &&
|
!RUNNABLE.some((l) => form.answers[l].trim()) &&
|
||||||
!rows.value.some((r) => r.input.trim() || r.output.trim())
|
!rows.value.some((r) => r.input.trim() || r.output.trim()),
|
||||||
draft.value = empty
|
take: () => ({
|
||||||
? null
|
form: JSON.parse(JSON.stringify(form)),
|
||||||
: {
|
rows: rows.value.map((r) => ({ input: r.input, output: r.output, example: r.example })),
|
||||||
form: JSON.parse(JSON.stringify(form)),
|
}),
|
||||||
rows: rows.value.map((r) => ({ input: r.input, output: r.output, example: r.example })),
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
{ debounce: 800, deep: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
function clearDraft() {
|
function clearDraft() {
|
||||||
draft.value = null
|
draft.value = null
|
||||||
@@ -841,9 +831,7 @@ async function save() {
|
|||||||
: { name: "admin problem edit", params: { problemID: created.id } },
|
: { name: "admin problem edit", params: { problemID: created.id } },
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error(
|
message.error(saveErrorText(err))
|
||||||
errorCode(err) === "display-id-exists" ? "显示编号重复了,换一个" : errorMessage(err),
|
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@@ -866,11 +854,7 @@ onBeforeRouteLeave(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const backTo = computed(() =>
|
const backTo = computed(() => problemListRoute(props.contestID))
|
||||||
props.contestID
|
|
||||||
? { name: "admin contest problem list", params: { contestID: props.contestID } }
|
|
||||||
: { name: "admin problem list" },
|
|
||||||
)
|
|
||||||
|
|
||||||
const authorLine = computed(() =>
|
const authorLine = computed(() =>
|
||||||
props.initial
|
props.initial
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { batchSetProblemVisible, getProblemList, toggleProblemVisible } from "..
|
|||||||
import Actions from "./components/Actions.vue"
|
import Actions from "./components/Actions.vue"
|
||||||
import AddFromLibraryModal from "./components/AddFromLibraryModal.vue"
|
import AddFromLibraryModal from "./components/AddFromLibraryModal.vue"
|
||||||
import { useRouteQuery } from "@vueuse/router"
|
import { useRouteQuery } from "@vueuse/router"
|
||||||
import AuthorSelect from "shared/components/AuthorSelect.vue"
|
import AuthorSelect from "admin/components/AuthorSelect.vue"
|
||||||
import type { DataTableRowKey } from "naive-ui"
|
import type { DataTableRowKey } from "naive-ui"
|
||||||
import BatchTagModal from "./components/BatchTagModal.vue"
|
import BatchTagModal from "./components/BatchTagModal.vue"
|
||||||
import CreateProblemMenu from "./components/CreateProblemMenu.vue"
|
import CreateProblemMenu from "./components/CreateProblemMenu.vue"
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
import type { MarkAchievementsRead } from "@oj2/contract"
|
|
||||||
import api from "utils/api"
|
import api from "utils/api"
|
||||||
import type { AchievementList, PendingAchievement } from "utils/types"
|
import type { AchievementList } from "utils/types"
|
||||||
|
|
||||||
export function getAchievements(name?: string) {
|
export function getAchievements(name?: string) {
|
||||||
return api.get<AchievementList>("achievements", {
|
return api.get<AchievementList>("achievements", {
|
||||||
params: name ? { username: name } : {},
|
params: name ? { username: name } : {},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPendingAchievements() {
|
|
||||||
return api.get<PendingAchievement[]>("achievements/pending")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function markAchievementsRead(ids: number[]) {
|
|
||||||
return api.post("achievements/pending/read", { ids } satisfies MarkAchievementsRead)
|
|
||||||
}
|
|
||||||
+2
-29
@@ -23,7 +23,6 @@ import {
|
|||||||
type ProfileOverview,
|
type ProfileOverview,
|
||||||
type TutorialSummary,
|
type TutorialSummary,
|
||||||
type Tutorial,
|
type Tutorial,
|
||||||
type ClassActivity,
|
|
||||||
type ClassToday,
|
type ClassToday,
|
||||||
type LastVisit,
|
type LastVisit,
|
||||||
type ClassBoard,
|
type ClassBoard,
|
||||||
@@ -40,10 +39,8 @@ import {
|
|||||||
type WeeklyRank,
|
type WeeklyRank,
|
||||||
type ProblemRank,
|
type ProblemRank,
|
||||||
type CreateSubmissionResponse,
|
type CreateSubmissionResponse,
|
||||||
type ProblemAuthor,
|
|
||||||
type ProblemListItem,
|
type ProblemListItem,
|
||||||
type ProblemStats,
|
type ProblemStats,
|
||||||
type ProblemList,
|
|
||||||
type ProblemProgress,
|
type ProblemProgress,
|
||||||
type CreateFlowchartResponse,
|
type CreateFlowchartResponse,
|
||||||
type FlowchartScores,
|
type FlowchartScores,
|
||||||
@@ -64,7 +61,7 @@ import {
|
|||||||
} from "@oj2/contract"
|
} from "@oj2/contract"
|
||||||
import api from "utils/api"
|
import api from "utils/api"
|
||||||
import { contract } from "utils/contract"
|
import { contract } from "utils/contract"
|
||||||
import { toProblemRow } from "oj/transforms"
|
import { toProblemRow } from "utils/transforms"
|
||||||
import type {
|
import type {
|
||||||
Announcement,
|
Announcement,
|
||||||
AnnouncementListItem,
|
AnnouncementListItem,
|
||||||
@@ -82,26 +79,12 @@ import type {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 题目详情 / 提交详情的 schema 按需加载,和请求并行发出。静态 import 会把 zod
|
* 题目详情 / 提交详情的 schema 按需加载,和请求并行发出。静态 import 会把 zod
|
||||||
* 运行时带进首页 —— 题目列表的 getProblemList 也在这个文件里。
|
* 运行时带进首页 —— 首页、题目列表都 import 这个文件。
|
||||||
* 闸门本身仍是同步的(等 schema 到了再返回),submissionDetailSchema 有 `.catch()`,
|
* 闸门本身仍是同步的(等 schema 到了再返回),submissionDetailSchema 有 `.catch()`,
|
||||||
* 放行原文和解析结果不等价,所以不能像 /me 那样改成 contractDeferred。
|
* 放行原文和解析结果不等价,所以不能像 /me 那样改成 contractDeferred。
|
||||||
*/
|
*/
|
||||||
const loadDetailSchemas = () => import("./detailSchemas")
|
const loadDetailSchemas = () => import("./detailSchemas")
|
||||||
|
|
||||||
export async function getProblemList(
|
|
||||||
offset = 0,
|
|
||||||
limit = 10,
|
|
||||||
searchParams: Record<string, unknown> = {},
|
|
||||||
) {
|
|
||||||
const res = await api.get<ProblemList>("problems", {
|
|
||||||
params: { paging: true, offset, limit, ...searchParams },
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
results: res.results.map(toProblemRow),
|
|
||||||
total: res.total,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 题目列表顶上那行和左栏的进度;没登录是 null */
|
/** 题目列表顶上那行和左栏的进度;没登录是 null */
|
||||||
export function getProblemProgress() {
|
export function getProblemProgress() {
|
||||||
return api.get<ProblemProgress | null>("problems/progress")
|
return api.get<ProblemProgress | null>("problems/progress")
|
||||||
@@ -115,12 +98,6 @@ export function getRandomProblem(params: { tag: string; type: string; language:
|
|||||||
return api.get<string>("problems/random", { params })
|
return api.get<string>("problems/random", { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAuthors(all = false) {
|
|
||||||
return api.get<ProblemAuthor[]>("problem-authors", {
|
|
||||||
params: { all: all ? "1" : "0" },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getProblem(problemID: string, contestID: string) {
|
export async function getProblem(problemID: string, contestID: string) {
|
||||||
const endpoint = contestID
|
const endpoint = contestID
|
||||||
? `contests/${encodeURIComponent(contestID)}/problems/${encodeURIComponent(problemID)}`
|
? `contests/${encodeURIComponent(contestID)}/problems/${encodeURIComponent(problemID)}`
|
||||||
@@ -316,10 +293,6 @@ export function getKnowledgeMap() {
|
|||||||
return api.get<KnowledgeMap>("me/knowledge")
|
return api.get<KnowledgeMap>("me/knowledge")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClassActivity() {
|
|
||||||
return api.get<ClassActivity>("me/class-activity")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 老师首页「今天的课」:今天在上课的每个班 */
|
/** 老师首页「今天的课」:今天在上课的每个班 */
|
||||||
export function getClassToday() {
|
export function getClassToday() {
|
||||||
return api.get<ClassToday>("classroom/today")
|
return api.get<ClassToday>("classroom/today")
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { LANGUAGE_SHOW_VALUE } from "utils/constants"
|
|||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import type { ClassBoard, ClassBoardCell, ClassBoardProblem, ClassBoardStudent } from "utils/types"
|
import type { ClassBoard, ClassBoardCell, ClassBoardProblem, ClassBoardStudent } from "utils/types"
|
||||||
import ExplainModal, { type ExplainPerson } from "./components/ExplainModal.vue"
|
import ExplainModal, { type ExplainPerson } from "./components/ExplainModal.vue"
|
||||||
import { errorKind, type ErrorKind } from "./errorKind"
|
import { errorKind, type ErrorKind } from "utils/errorKind"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 课堂看板(老师,设计稿「课堂看板重设计」A+B):这个班、今天、这节课的几道题 × 全班学生。
|
* 课堂看板(老师,设计稿「课堂看板重设计」A+B):这个班、今天、这节课的几道题 × 全班学生。
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { continueStep, doneCount, lessonPath, splitTitle, type TutorialType } fr
|
|||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import {
|
import {
|
||||||
getAnnouncementList,
|
getAnnouncementList,
|
||||||
getClassActivity,
|
|
||||||
getKnowledgeMap,
|
getKnowledgeMap,
|
||||||
getContestList,
|
getContestList,
|
||||||
getLastVisit,
|
getLastVisit,
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
getTutorials,
|
getTutorials,
|
||||||
getWeeklyRank,
|
getWeeklyRank,
|
||||||
} from "oj/api"
|
} from "oj/api"
|
||||||
|
import { getClassActivity } from "shared/api"
|
||||||
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
||||||
import { pickHeadline, type Headline } from "oj/user/knowledge"
|
import { pickHeadline, type Headline } from "oj/user/knowledge"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { errorMessage } from "utils/api"
|
|||||||
import { SubmissionStatus } from "utils/constants"
|
import { SubmissionStatus } from "utils/constants"
|
||||||
import { trialRun } from "utils/judge"
|
import { trialRun } from "utils/judge"
|
||||||
import { explainCCompileError } from "oj/problem/utils/cError"
|
import { explainCCompileError } from "oj/problem/utils/cError"
|
||||||
import { explainPythonCompileError } from "oj/problem/utils/pythonError"
|
import { explainPythonCompileError } from "utils/pythonError"
|
||||||
import { explainRuntimeError } from "oj/problem/utils/runtimeError"
|
import { explainRuntimeError } from "oj/problem/utils/runtimeError"
|
||||||
import { useAuthModalStore } from "shared/store/authModal"
|
import { useAuthModalStore } from "shared/store/authModal"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
showPunctuationMarks,
|
showPunctuationMarks,
|
||||||
} from "oj/problem/utils/errorMark"
|
} from "oj/problem/utils/errorMark"
|
||||||
import { explainCCompileError } from "oj/problem/utils/cError"
|
import { explainCCompileError } from "oj/problem/utils/cError"
|
||||||
import { explainPythonCompileError } from "oj/problem/utils/pythonError"
|
import { explainPythonCompileError } from "utils/pythonError"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编译错误的中文说明卡片:Python 的语法错误走 pythonError.ts,C / C++ 的 gcc 报错走
|
* 编译错误的中文说明卡片:Python 的语法错误走 pythonError.ts,C / C++ 的 gcc 报错走
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { RouteLocationRaw } from "vue-router"
|
|||||||
import { getProblemSetDetail, getProblemSetProblems, joinProblemSet } from "oj/api"
|
import { getProblemSetDetail, getProblemSetProblems, joinProblemSet } from "oj/api"
|
||||||
import { useContestStore } from "oj/store/contest"
|
import { useContestStore } from "oj/store/contest"
|
||||||
import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart"
|
import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart"
|
||||||
import { useLessonStore } from "oj/store/lesson"
|
import { useLessonStore } from "shared/store/lesson"
|
||||||
import { useProblemStore } from "oj/store/problem"
|
import { useProblemStore } from "oj/store/problem"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import { useLessonStore } from "oj/store/lesson"
|
import { useLessonStore } from "shared/store/lesson"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过之后告诉学生「这节课的下一题是哪道」。
|
* 通过之后告诉学生「这节课的下一题是哪道」。
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useCollabHere, useTeacherCollab } from "../composables/teacherCollab"
|
|||||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||||
import CollabBar from "./CollabBar.vue"
|
import CollabBar from "./CollabBar.vue"
|
||||||
import { useFlowchartStore } from "oj/store/flowchart"
|
import { useFlowchartStore } from "oj/store/flowchart"
|
||||||
import { useLessonStore } from "oj/store/lesson"
|
import { useLessonStore } from "shared/store/lesson"
|
||||||
|
|
||||||
const FlowchartEditor = defineAsyncComponent(
|
const FlowchartEditor = defineAsyncComponent(
|
||||||
() => import("shared/components/FlowchartEditor/index.vue"),
|
() => import("shared/components/FlowchartEditor/index.vue"),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { getReaction } from "oj/api"
|
|||||||
import { useProblemStore } from "oj/store/problem"
|
import { useProblemStore } from "oj/store/problem"
|
||||||
import { useSubmissionStore } from "oj/store/submission"
|
import { useSubmissionStore } from "oj/store/submission"
|
||||||
import { useFireworks } from "oj/problem/composables/useFireworks"
|
import { useFireworks } from "oj/problem/composables/useFireworks"
|
||||||
import { useLessonStore } from "oj/store/lesson"
|
import { useLessonStore } from "shared/store/lesson"
|
||||||
import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart"
|
import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart"
|
||||||
import { SubmissionStatus } from "utils/constants"
|
import { SubmissionStatus } from "utils/constants"
|
||||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import WrongAnswerExplain from "./WrongAnswerExplain.vue"
|
|||||||
import LessonNext from "./LessonNext.vue"
|
import LessonNext from "./LessonNext.vue"
|
||||||
import ResultHeader from "./ResultHeader.vue"
|
import ResultHeader from "./ResultHeader.vue"
|
||||||
import SimilarProblems from "./SimilarProblems.vue"
|
import SimilarProblems from "./SimilarProblems.vue"
|
||||||
import { useLessonStore } from "oj/store/lesson"
|
import { useLessonStore } from "shared/store/lesson"
|
||||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||||
import Markdown from "shared/components/Markdown.vue"
|
import Markdown from "shared/components/Markdown.vue"
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,7 @@ import { useThemeVars } from "naive-ui"
|
|||||||
import { useProblemStore } from "oj/store/problem"
|
import { useProblemStore } from "oj/store/problem"
|
||||||
import { markdownToText } from "oj/problem/utils/plainText"
|
import { markdownToText } from "oj/problem/utils/plainText"
|
||||||
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
||||||
import {
|
import { classifyDiff, explainDiff, firstDifferentLine, type SampleCheck } from "utils/wrongAnswer"
|
||||||
classifyDiff,
|
|
||||||
explainDiff,
|
|
||||||
firstDifferentLine,
|
|
||||||
type SampleCheck,
|
|
||||||
} from "oj/problem/utils/wrongAnswer"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 答案错误时的样例对比(数据见后端 judge/run.ts 的 checkSamples)。
|
* 答案错误时的样例对比(数据见后端 judge/run.ts 的 checkSamples)。
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { Icon } from "@iconify/vue"
|
|||||||
import type { ProblemProgress, ProblemTypeFilter, Tag } from "@oj2/contract"
|
import type { ProblemProgress, ProblemTypeFilter, Tag } from "@oj2/contract"
|
||||||
import { useRouteQuery } from "@vueuse/router"
|
import { useRouteQuery } from "@vueuse/router"
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import { getAuthors, getProblemList, getProblemProgress, getRandomProblem } from "oj/api"
|
import { getProblemProgress, getRandomProblem } from "oj/api"
|
||||||
|
import { getAuthors, getProblemList } from "shared/api"
|
||||||
import { useCodeStore } from "oj/store/code"
|
import { useCodeStore } from "oj/store/code"
|
||||||
import { useTone } from "shared/composables/tone"
|
import { useTone } from "shared/composables/tone"
|
||||||
import { STORAGE_KEY } from "utils/constants"
|
import { STORAGE_KEY } from "utils/constants"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { findChinesePunctuation } from "./chinesePunctuation"
|
import { findChinesePunctuation } from "./chinesePunctuation"
|
||||||
import { PUNCTUATION_NAMES, type PythonErrorExplanation } from "./pythonError"
|
import { PUNCTUATION_NAMES, type PythonErrorExplanation } from "utils/pythonError"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把 C / C++ 的编译错误(gcc 的原文)翻成中文,和 pythonError.ts 同一个思路:
|
* 把 C / C++ 的编译错误(gcc 的原文)翻成中文,和 pythonError.ts 同一个思路:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PUNCTUATION_MAP } from "./pythonError"
|
import { PUNCTUATION_MAP } from "utils/pythonError"
|
||||||
|
|
||||||
export interface PunctuationFix {
|
export interface PunctuationFix {
|
||||||
from: number
|
from: number
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { RankRow, WeeklyChampion } from "@oj2/contract"
|
|||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import UserName from "shared/components/UserName.vue"
|
import UserName from "shared/components/UserName.vue"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import RankAvatar from "./RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
|
|
||||||
/** 本班最近几周的每周冠军,底下再挂这张榜上「进步最大」的那个人 */
|
/** 本班最近几周的每周冠军,底下再挂这张榜上「进步最大」的那个人 */
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useBackClose } from "shared/composables/backClose"
|
|||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { useRankPalette } from "../palette"
|
import { useRankPalette } from "../palette"
|
||||||
import RankAvatar from "./RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
|
|
||||||
// Markdown 连着整套代码高亮(两百多 KB),这里要等点开 / AI 写完才用得上,别让它拖慢页面
|
// Markdown 连着整套代码高亮(两百多 KB),这里要等点开 / AI 写完才用得上,别让它拖慢页面
|
||||||
const Markdown = defineAsyncComponent(() => import("shared/components/Markdown.vue"))
|
const Markdown = defineAsyncComponent(() => import("shared/components/Markdown.vue"))
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useThemeVars } from "naive-ui"
|
|||||||
import UserName from "shared/components/UserName.vue"
|
import UserName from "shared/components/UserName.vue"
|
||||||
import { gapText, toPass } from "../utils"
|
import { gapText, toPass } from "../utils"
|
||||||
import MoodPopover from "./MoodPopover.vue"
|
import MoodPopover from "./MoodPopover.vue"
|
||||||
import RankAvatar from "./RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
|
|
||||||
export interface MiniRank {
|
export interface MiniRank {
|
||||||
label: string
|
label: string
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useThemeVars } from "naive-ui"
|
|||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import UserName from "shared/components/UserName.vue"
|
import UserName from "shared/components/UserName.vue"
|
||||||
import { useRankPalette } from "../palette"
|
import { useRankPalette } from "../palette"
|
||||||
import RankAvatar from "./RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 领奖台:前三名站在台阶上,第一名戴皇冠。赛道从第 4 名接着排,前三不再出现第二遍。
|
* 领奖台:前三名站在台阶上,第一名戴皇冠。赛道从第 4 名接着排,前三不再出现第二遍。
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import UserName from "shared/components/UserName.vue"
|
|||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { useRankPalette } from "../palette"
|
import { useRankPalette } from "../palette"
|
||||||
import MoodPopover from "./MoodPopover.vue"
|
import MoodPopover from "./MoodPopover.vue"
|
||||||
import RankAvatar from "./RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 赛道:每人一根条,条越长做对越多,一样长的先做到的排前面(设计稿 G1 / G2)。
|
* 赛道:每人一根条,条越长做对越多,一样长的先做到的排前面(设计稿 G1 / G2)。
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { RankRow } from "@oj2/contract"
|
|||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import UserName from "shared/components/UserName.vue"
|
import UserName from "shared/components/UserName.vue"
|
||||||
import { useRankPalette } from "../palette"
|
import { useRankPalette } from "../palette"
|
||||||
import RankAvatar from "./RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
|
|
||||||
/** 这周本班前 5 名(做对过新题的),每周一清零 */
|
/** 这周本班前 5 名(做对过新题的),每周一清零 */
|
||||||
const props = defineProps<{ rows: RankRow[]; meId?: number; loaded: boolean }>()
|
const props = defineProps<{ rows: RankRow[]; meId?: number; loaded: boolean }>()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { explainCCompileError } from "oj/problem/utils/cError"
|
import { explainCCompileError } from "oj/problem/utils/cError"
|
||||||
import { explainPythonCompileError } from "oj/problem/utils/pythonError"
|
import { explainPythonCompileError } from "utils/pythonError"
|
||||||
import { explainRuntimeError } from "oj/problem/utils/runtimeError"
|
import { explainRuntimeError } from "oj/problem/utils/runtimeError"
|
||||||
import { SubmissionStatus } from "utils/constants"
|
import { SubmissionStatus } from "utils/constants"
|
||||||
import type { Submission } from "utils/types"
|
import type { Submission } from "utils/types"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import { resetAvatar, uploadAvatar } from "oj/api"
|
import { resetAvatar, uploadAvatar } from "oj/api"
|
||||||
import RankAvatar from "oj/rank/components/RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { MOOD_MAX, STUDENT_ROLES, type ProfileOverview } from "@oj2/contract"
|
|||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import { clearMood, setRankHidden, updateProfile } from "oj/api"
|
import { clearMood, setRankHidden, updateProfile } from "oj/api"
|
||||||
import RankAvatar from "oj/rank/components/RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
import { classLabel } from "utils/classes"
|
import { classLabel } from "utils/classes"
|
||||||
import UserName from "shared/components/UserName.vue"
|
import UserName from "shared/components/UserName.vue"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import type { LoginRequest, RegisterRequest } from "@oj2/contract"
|
import type { LoginRequest, RegisterRequest } from "@oj2/contract"
|
||||||
import type { Quote, WebsiteConfig } from "@oj2/contract"
|
import type {
|
||||||
|
ClassActivity,
|
||||||
|
MarkAchievementsRead,
|
||||||
|
ProblemAuthor,
|
||||||
|
ProblemList,
|
||||||
|
Quote,
|
||||||
|
WebsiteConfig,
|
||||||
|
} from "@oj2/contract"
|
||||||
import api from "utils/api"
|
import api from "utils/api"
|
||||||
import { contractDeferred } from "utils/contract"
|
import { contractDeferred } from "utils/contract"
|
||||||
import type { Profile, Tag } from "utils/types"
|
import { toProblemRow } from "utils/transforms"
|
||||||
|
import type { PendingAchievement, Profile, Tag } from "utils/types"
|
||||||
|
|
||||||
export function login(data: { username: string; password: string }) {
|
export function login(data: { username: string; password: string }) {
|
||||||
return api.post("auth/login", data satisfies LoginRequest)
|
return api.post("auth/login", data satisfies LoginRequest)
|
||||||
@@ -51,3 +59,35 @@ export function getClassUsernames(classroom: string) {
|
|||||||
export function getWebsiteConfig() {
|
export function getWebsiteConfig() {
|
||||||
return api.get<WebsiteConfig>("site")
|
return api.get<WebsiteConfig>("site")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getProblemList(
|
||||||
|
offset = 0,
|
||||||
|
limit = 10,
|
||||||
|
searchParams: Record<string, unknown> = {},
|
||||||
|
) {
|
||||||
|
const res = await api.get<ProblemList>("problems", {
|
||||||
|
params: { paging: true, offset, limit, ...searchParams },
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
results: res.results.map(toProblemRow),
|
||||||
|
total: res.total,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAuthors(all = false) {
|
||||||
|
return api.get<ProblemAuthor[]>("problem-authors", {
|
||||||
|
params: { all: all ? "1" : "0" },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getClassActivity() {
|
||||||
|
return api.get<ClassActivity>("me/class-activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPendingAchievements() {
|
||||||
|
return api.get<PendingAchievement[]>("achievements/pending")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markAchievementsRead(ids: number[]) {
|
||||||
|
return api.post("achievements/pending/read", { ids } satisfies MarkAchievementsRead)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { errorKind } from "oj/classroom/errorKind"
|
import { errorKind } from "utils/errorKind"
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { useTeacherHelp } from "shared/composables/teacherHelp"
|
import { useTeacherHelp } from "shared/composables/teacherHelp"
|
||||||
import { useCollabStore } from "shared/store/collab"
|
import { useCollabStore } from "shared/store/collab"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import RankAvatar from "oj/rank/components/RankAvatar.vue"
|
import RankAvatar from "shared/components/RankAvatar.vue"
|
||||||
import { classLabel, groupClassOptions } from "utils/classes"
|
import { classLabel, groupClassOptions } from "utils/classes"
|
||||||
import { errorCode } from "utils/api"
|
import { errorCode } from "utils/api"
|
||||||
import { STORAGE_KEY } from "utils/constants"
|
import { STORAGE_KEY } from "utils/constants"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import { useLessonStore } from "oj/store/lesson"
|
import { useLessonStore } from "shared/store/lesson"
|
||||||
import { useProblemJump } from "shared/composables/problemJump"
|
import { useProblemJump } from "shared/composables/problemJump"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import type { ClassActivityProblem } from "utils/types"
|
import type { ClassActivityProblem } from "utils/types"
|
||||||
|
|||||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
|||||||
import { getProblemList } from "oj/api"
|
import { getProblemList } from "shared/api"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按题号直达。课上老师报一个题号、全班去找,是非比赛提交最主要的来路,
|
* 按题号直达。课上老师报一个题号、全班去找,是非比赛提交最主要的来路,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getPendingAchievements, markAchievementsRead } from "oj/achievement/api"
|
import { getPendingAchievements, markAchievementsRead } from "shared/api"
|
||||||
import type { QueuedAchievement } from "utils/types"
|
import type { QueuedAchievement } from "utils/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { getClassActivity } from "oj/api"
|
import { getClassActivity } from "shared/api"
|
||||||
import { LANGUAGE_SHOW_VALUE } from "utils/constants"
|
import { LANGUAGE_SHOW_VALUE } from "utils/constants"
|
||||||
import type { ClassActivity, ClassActivityProblem } from "utils/types"
|
import type { ClassActivity, ClassActivityProblem } from "utils/types"
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { classifyDiff, type DiffKind } from "oj/problem/utils/wrongAnswer"
|
import { classifyDiff, type DiffKind } from "utils/wrongAnswer"
|
||||||
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
||||||
import type { ClassBoardCell } from "utils/types"
|
import type { ClassBoardCell } from "utils/types"
|
||||||
|
|
||||||
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,5 @@
|
|||||||
import type { StatisticInfo } from "@oj2/contract"
|
import type { StatisticInfo } from "@oj2/contract"
|
||||||
import { PUNCTUATION_MAP, PUNCTUATION_NAMES } from "./pythonError"
|
import { PUNCTUATION_MAP, PUNCTUATION_NAMES } from "utils/pythonError"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 答案错误时,比较学生在**公开样例**上的输出和正确输出,归一类、给一句中文提示。
|
* 答案错误时,比较学生在**公开样例**上的输出和正确输出,归一类、给一句中文提示。
|
||||||
Reference in new issue
Block a user