重构自学模块

This commit is contained in:
2025-06-15 14:40:47 +08:00
parent 70dd3540c9
commit 73b86c644a
36 changed files with 1118 additions and 848 deletions

View File

@@ -232,3 +232,11 @@ export function refreshUserProblemDisplayIds() {
export function getMetrics(userid: number) {
return http.get("metrics", { params: { userid } })
}
export function getTutorial(id: number) {
return http.get("tutorial", { params: { id } })
}
export function getTutorials() {
return http.get("tutorials")
}

144
src/oj/learn/index.vue Normal file
View File

@@ -0,0 +1,144 @@
<template>
<n-grid :cols="2" :x-gap="24" v-if="!!tutorial.id">
<n-gi :span="1">
<n-flex vertical>
<n-flex align="center">
<n-button text :disabled="step == 1" @click="prev">
<Icon :width="30" icon="pepicons-pencil:arrow-left"></Icon>
</n-button>
<n-dropdown size="large" :options="menu" trigger="click">
<n-button tertiary style="flex: 1" size="large">
<n-flex align="center">
<span style="font-weight: bold">
{{ title }}
</span>
<Icon :width="24" icon="proicons:chevron-down"></Icon>
</n-flex>
</n-button>
</n-dropdown>
<n-button text :disabled="step == titles.length" @click="next">
<Icon :width="30" icon="pepicons-pencil:arrow-right"></Icon>
</n-button>
</n-flex>
<n-flex vertical size="large">
<MdPreview
:theme="isDark ? 'dark' : 'light'"
:model-value="tutorial.content"
/>
<n-flex justify="space-between">
<div style="flex: 1">
<n-button
block
style="height: 60px"
v-if="step !== 1"
@click="prev"
>
上一课时
</n-button>
</div>
<div style="flex: 1">
<n-button
block
style="height: 60px"
v-if="step !== titles.length"
@click="next"
>
下一课时
</n-button>
</div>
</n-flex>
</n-flex>
</n-flex>
</n-gi>
<n-gi :span="1">
<n-flex vertical>
<CodeEditor
v-show="!!tutorial.code"
language="Python3"
v-model="tutorial.code"
/>
</n-flex>
</n-gi>
</n-grid>
</template>
<script setup lang="ts">
import { Icon } from "@iconify/vue"
import { MdPreview } from "md-editor-v3"
import "md-editor-v3/lib/preview.css"
import { Tutorial } from "~/utils/types"
import { getTutorial, getTutorials } from "../api"
const CodeEditor = defineAsyncComponent(
() => import("~/shared/components/CodeEditor.vue"),
)
const isDark = useDark()
const route = useRoute()
const router = useRouter()
const step = computed(() => {
if (!route.params.step || !route.params.step.length) return 1
else {
return parseInt(route.params.step[0])
}
})
const tutorial = ref<Partial<Tutorial>>({
id: 0,
title: "",
content: "",
code: "",
})
const titles = ref<{ id: number; title: string }[]>([])
const title = computed(
() => `${step.value} 课:${titles.value[step.value - 1].title}`,
)
const menu = computed<DropdownOption[]>(() => {
return titles.value.map((item, index) => {
const id = (index + 1).toString().padStart(2, "0")
const prefix = `${index + 1} 课:`
return {
key: id,
label: prefix + item.title,
props: {
onClick: () => router.push(`/learn/${id}`),
},
}
})
})
async function init() {
const res1 = await getTutorials()
titles.value = res1.data
const id = titles.value[step.value - 1].id
const res2 = await getTutorial(id)
tutorial.value = res2.data
}
function next() {
if (step.value === titles.value.length) return
const dest = (step.value + 1).toString().padStart(2, "0")
router.push("/learn/" + dest)
}
function prev() {
if (step.value === 1) return
const dest = (step.value - 1).toString().padStart(2, "0")
router.push("/learn/" + dest)
}
watch(
() => route.params.step,
async () => {
if (route.name !== "learn") return
init()
},
{ immediate: true },
)
</script>
<style></style>