feat: 更新导航结构,重构课程准备页面及其子页面,添加新组件

This commit is contained in:
Timothy Yin 2025-04-19 17:19:16 +08:00
parent 6221602d5e
commit b03b2d9273
Signed by: HoshinoSuzumi
GPG Key ID: 4052E565F04B122A
11 changed files with 217 additions and 43 deletions

View File

@ -10,7 +10,7 @@
--card-foreground: 0 0% 3.9%; --card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%; --popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%; --primary: 238.73deg 83.53% 66.67%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%; --secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%; --secondary-foreground: 0 0% 9%;
@ -46,7 +46,7 @@
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%; --popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 98%;
--primary: 0 0% 98%; --primary: 234.45deg 89.47% 73.92%;
--primary-foreground: 0 0% 9%; --primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%; --secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 98%;

View File

@ -1,7 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { SubNavItem } from '../nav/Secondary.vue' import { twMerge } from 'tailwind-merge'
import type { NavSecondaryItem } from '../nav/Secondary.vue'
defineProps<{ subnavs?: SubNavItem[] }>() defineProps<{
navSecondary?: NavSecondaryItem[]
contentClass?: string
}>()
</script> </script>
<template> <template>
@ -9,12 +13,13 @@ defineProps<{ subnavs?: SubNavItem[] }>()
<!-- <h1 class="pl-2 text-xl font-medium">外部标题</h1> --> <!-- <h1 class="pl-2 text-xl font-medium">外部标题</h1> -->
<slot name="subnav"> <slot name="subnav">
<NavSecondary <NavSecondary
v-if="subnavs && subnavs.length" v-if="navSecondary && navSecondary.length"
:navs="subnavs" :navs="navSecondary"
/> />
</slot> </slot>
<div <div
class="bg-white h-full rounded-lg shadow-sm p-8 dark:bg-neutral-900 z-20" class="h-full rounded-lg shadow-sm overflow-hidden"
:class="twMerge('bg-white dark:bg-neutral-900 p-8 z-20', contentClass)"
> >
<slot /> <slot />
</div> </div>

View File

@ -0,0 +1,7 @@
<script lang="ts" setup></script>
<template>
<div>教案设计</div>
</template>
<style scoped></style>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
export interface SubNavItem { export interface NavSecondaryItem {
label: string label: string
to: string to: string
} }
@ -7,7 +7,7 @@ export interface SubNavItem {
<script lang="ts" setup> <script lang="ts" setup>
defineProps<{ defineProps<{
navs: SubNavItem[] navs: NavSecondaryItem[]
}>() }>()
const route = useRoute() const route = useRoute()

View File

@ -0,0 +1,49 @@
<script lang="ts">
export interface NavTertiaryItem {
label: string
to?: string
component?: string | Component
}
</script>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
navs: NavTertiaryItem[]
modelValue?: number
}>(),
{
modelValue: 0,
},
)
const emit = defineEmits<{
(e: 'update:modelValue', idx: number): void
}>()
const isActiveItem = (idx: number) => {
return props.modelValue === idx
}
const onClickItem = (idx: number) => {
emit('update:modelValue', idx)
}
</script>
<template>
<div class="flex flex-col gap-2">
<div
v-for="(nav, i) in navs"
:key="i"
class="flex justify-center items-center gap-2 p-2.5 rounded-sm cursor-pointer select-none transition-colors duration-75"
:class="`${isActiveItem(i) ? 'bg-primary text-primary-foreground' : 'bg-accent text-foreground'}`"
@click="onClickItem(i)"
>
<span class="text-sm font-medium">
{{ nav.label }}
</span>
</div>
</div>
</template>
<style scoped></style>

View File

@ -65,7 +65,7 @@ watch(
<template> <template>
<AppContainer <AppContainer
:subnavs="[ :nav-secondary="[
{ {
label: '课程章节', label: '课程章节',
to: `/course/${id}/chapters`, to: `/course/${id}/chapters`,

View File

@ -1,26 +1,20 @@
export const nav = [ import type { NavSecondaryItem } from '~/components/nav/Secondary.vue'
export const nav: NavSecondaryItem[] = [
{ {
items: [ label: '教学设计',
{ to: '/course/prep/teach',
title: 'AI 教学设计',
url: '/course/prep/teach',
icon: 'tabler:calendar-cog',
},
{
title: 'AI 课件设计',
url: '/course/prep/deck',
icon: 'tabler:book-2',
},
{
title: 'AI 出题',
url: '/course/prep/quiz',
icon: 'tabler:notebook',
},
{
title: '微视频制作',
url: '/course/prep/test',
icon: 'tabler:video',
},
],
}, },
{
label: '课件设计',
to: '/course/prep/deck',
},
{
label: 'AI 出题',
to: '/course/prep/quiz',
},
// {
// label: '微视频制作',
// to: '/course/prep/test',
// },
] ]

View File

@ -3,13 +3,32 @@ import { nav } from './config'
definePageMeta({ definePageMeta({
requiresAuth: true, requiresAuth: true,
hideSidebar: true,
})
useHead({
title: 'AI 课件设计 | 备课',
})
const { setBreadcrumbs } = useBreadcrumbs()
onMounted(() => {
setBreadcrumbs([
{
label: 'AI 备课',
path: '/course/prep',
},
{
label: 'AI 课件设计',
},
])
}) })
</script> </script>
<template> <template>
<AppPageWithSidebar :sidebar-nav="nav"> <AppContainer :nav-secondary="nav">
<h1>deck</h1> <h1>AI 课件设计</h1>
</AppPageWithSidebar> </AppContainer>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -3,13 +3,32 @@ import { nav } from './config'
definePageMeta({ definePageMeta({
requiresAuth: true, requiresAuth: true,
hideSidebar: true,
})
useHead({
title: 'AI 出题 | 备课',
})
const { setBreadcrumbs } = useBreadcrumbs()
onMounted(() => {
setBreadcrumbs([
{
label: 'AI 备课',
path: '/course/prep',
},
{
label: 'AI 出题',
},
])
}) })
</script> </script>
<template> <template>
<AppPageWithSidebar :sidebar-nav="nav"> <AppContainer :nav-secondary="nav">
<h1>quiz</h1> <h1>AI 出题</h1>
</AppPageWithSidebar> </AppContainer>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -1,19 +1,98 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nav } from './config' import { nav } from './config'
import { FnTeachLessonPlan } from '#components'
import type { NavTertiaryItem } from '~/components/nav/Tertiary.vue'
definePageMeta({ definePageMeta({
requiresAuth: true, requiresAuth: true,
hideSidebar: true,
}) })
useHead({ useHead({
title: 'AI 教学设计 | 备课', title: 'AI 教学设计 | 备课',
}) })
const route = useRoute()
const router = useRouter()
const { setBreadcrumbs } = useBreadcrumbs()
const tertiaryNavs: NavTertiaryItem[] = [
{ label: '教案设计', component: FnTeachLessonPlan },
{ label: '案例设计' },
{ label: '课程标准' },
{ label: '知识图谱' },
{ label: '课程章节' },
{ label: '教研计划' },
]
const currentNav = ref(0)
watch(currentNav, (val) => {
router.replace({
query: {
fn: val,
},
})
})
onMounted(() => {
if (route.query.fn && !isNaN(Number(route.query.fn))) {
currentNav.value = Number(route.query.fn)
}
setBreadcrumbs([
{
label: 'AI 备课',
path: '/course/prep',
},
{
label: 'AI 教学设计',
},
])
})
</script> </script>
<template> <template>
<AppPageWithSidebar :sidebar-nav="nav"> <AppContainer
<h1>teach</h1> :nav-secondary="nav"
</AppPageWithSidebar> content-class="flex items-start p-0"
>
<div class="w-[188px] h-full border-r shadow-xl">
<div class="flex justify-center items-center h-16 border-b gap-2">
<Icon
name="fluent-color:design-ideas-24"
class="text-2xl"
/>
<h1 class="text-base font-medium text-foreground">AI 教学设计</h1>
</div>
<div class="p-2">
<NavTertiary
v-model="currentNav"
:navs="tertiaryNavs"
/>
</div>
</div>
<div class="flex-1 h-full p-6">
<Suspense>
<component
:is="tertiaryNavs[currentNav].component"
v-if="tertiaryNavs[currentNav].component"
/>
<div
v-else
class="flex flex-col items-center justify-center w-full h-full gap-2"
>
<Icon
name="tabler:mood-sad"
class="text-6xl text-muted-foreground"
/>
<p class="text text-muted-foreground">
该功能暂不可用
</p>
</div>
</Suspense>
</div>
</AppContainer>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -5,7 +5,9 @@ definePageMeta({
</script> </script>
<template> <template>
<div>Resources</div> <AppContainer>
<div>课程资源</div>
</AppContainer>
</template> </template>
<style scoped></style> <style scoped></style>