This commit is contained in:
2025-10-06 19:38:44 +08:00
parent 96adf39cba
commit 0d19529632
2 changed files with 109 additions and 27 deletions

View File

@@ -1,8 +1,9 @@
<template> <template>
<n-grid :cols="5" :x-gap="16" v-if="tutorial.id"> <div>
<n-gi :span="1"> <!-- 桌面端布局 -->
<n-card title="目录" :bordered="false" size="small"> <n-grid :cols="5" :x-gap="16" v-if="tutorial.id && isDesktop">
<n-scrollbar :style="{ maxHeight: 'calc(100vh - 180px)' }"> <n-gi :span="1">
<n-card title="教程目录" :bordered="false" size="small">
<n-list hoverable clickable> <n-list hoverable clickable>
<n-list-item <n-list-item
v-for="(item, index) in titles" v-for="(item, index) in titles"
@@ -17,30 +18,86 @@
</n-text> </n-text>
</n-list-item> </n-list-item>
</n-list> </n-list>
</n-scrollbar> </n-card>
</n-card> </n-gi>
</n-gi>
<n-gi :span="tutorial.code ? 2 : 4"> <n-gi :span="tutorial.code ? 2 : 4">
<n-card <n-card
:title="`第 ${step} 课:${titles[step - 1]?.title}`" :title="`第 ${step} 课:${titles[step - 1]?.title}`"
:bordered="false" :bordered="false"
size="small" size="small"
> >
<MdPreview <MdPreview
preview-theme="vuepress" preview-theme="vuepress"
:theme="isDark ? 'dark' : 'light'" :theme="isDark ? 'dark' : 'light'"
:model-value="tutorial.content" :model-value="tutorial.content"
/> />
</n-card> </n-card>
</n-gi> </n-gi>
<n-gi :span="2" v-if="tutorial.code"> <n-gi :span="2" v-if="tutorial.code">
<n-card title="示例代码" :bordered="false" size="small"> <n-card title="示例代码" :bordered="false" size="small">
<CodeEditor language="Python3" v-model="tutorial.code" /> <CodeEditor language="Python3" v-model="tutorial.code" />
</n-card> </n-card>
</n-gi> </n-gi>
</n-grid> </n-grid>
<!-- 手机端布局 -->
<template v-if="tutorial.id && !isDesktop">
<n-tabs type="line" animated v-model:value="activeTab">
<n-tab-pane name="catalog" tab="目录">
<n-list hoverable clickable>
<n-list-item
v-for="(item, index) in titles"
:key="item.id"
@click="goToLesson(index + 1)"
>
<n-text
:type="step === index + 1 ? 'primary' : undefined"
:strong="step === index + 1"
>
{{ index + 1 }}. {{ item.title }}
</n-text>
</n-list-item>
</n-list>
</n-tab-pane>
<n-tab-pane name="content" :tab="`第 ${step} 课`">
<MdPreview
preview-theme="vuepress"
:theme="isDark ? 'dark' : 'light'"
:model-value="tutorial.content"
/>
</n-tab-pane>
<n-tab-pane name="code" tab="示例代码" v-if="tutorial.code">
<CodeEditor language="Python3" v-model="tutorial.code" />
</n-tab-pane>
</n-tabs>
<n-divider style="margin: 12px 0" />
<n-flex align="center" justify="space-between">
<n-button
secondary
type="primary"
:disabled="isFirstLesson"
@click="goToPrevLesson"
>
上一课
</n-button>
<n-text>{{ step }} / {{ titles.length }}</n-text>
<n-button
secondary
type="primary"
:disabled="isLastLesson"
@click="goToNextLesson"
>
下一课
</n-button>
</n-flex>
</template>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -48,6 +105,7 @@ import { MdPreview } from "md-editor-v3"
import "md-editor-v3/lib/preview.css" import "md-editor-v3/lib/preview.css"
import { Tutorial } from "utils/types" import { Tutorial } from "utils/types"
import { getTutorial, getTutorials } from "../api" import { getTutorial, getTutorials } from "../api"
import { isDesktop } from "shared/composables/breakpoints"
const CodeEditor = defineAsyncComponent( const CodeEditor = defineAsyncComponent(
() => import("shared/components/CodeEditor.vue"), () => import("shared/components/CodeEditor.vue"),
@@ -72,12 +130,29 @@ const tutorial = ref<Partial<Tutorial>>({
}) })
const titles = ref<{ id: number; title: string }[]>([]) const titles = ref<{ id: number; title: string }[]>([])
const activeTab = ref("content")
const isFirstLesson = computed(() => step.value === 1)
const isLastLesson = computed(() => step.value === titles.value.length)
function goToLesson(lessonNumber: number) { function goToLesson(lessonNumber: number) {
activeTab.value = "content"
const dest = lessonNumber.toString().padStart(2, "0") const dest = lessonNumber.toString().padStart(2, "0")
router.push("/learn/" + dest) router.push("/learn/" + dest)
} }
function goToPrevLesson() {
if (step.value > 1) {
goToLesson(step.value - 1)
}
}
function goToNextLesson() {
if (step.value < titles.value.length) {
goToLesson(step.value + 1)
}
}
async function init() { async function init() {
const res1 = await getTutorials() const res1 = await getTutorials()
titles.value = res1.data titles.value = res1.data
@@ -101,4 +176,12 @@ watch(
:deep(.md-editor-preview .md-editor-code .md-editor-code-head) { :deep(.md-editor-preview .md-editor-code .md-editor-code-head) {
z-index: 100; z-index: 100;
} }
:deep(.md-editor-preview h1) {
font-size: 1.6em;
}
:deep(.md-editor-preview h2) {
font-size: 1.4em;
}
</style> </style>

View File

@@ -86,7 +86,6 @@ const menus = computed<MenuOption[]>(() => [
label: () => h(RouterLink, { to: "/learn/01" }, { default: () => "自学" }), label: () => h(RouterLink, { to: "/learn/01" }, { default: () => "自学" }),
key: "learn", key: "learn",
icon: renderIcon("streamline-emojis:snake"), icon: renderIcon("streamline-emojis:snake"),
show: isDesktop.value,
}, },
{ {
label: () => h(RouterLink, { to: "/" }, { default: () => "题库" }), label: () => h(RouterLink, { to: "/" }, { default: () => "题库" }),