IntelliClass_FE/pages/course/[id]/chapters.vue
Timothy Yin 3a8b78ea7b
feat: update course resource types and interfaces, add resource uploader component
- Expanded CourseResourceType to include "resource" and "temp".
- Renamed ICourseResource to IResource and updated its properties for consistency.
- Introduced ICreateResource type for resource creation.
- Modified ICourseSection and ICourseChapter interfaces to use the new IResource type and updated property names for camelCase.
- Implemented uploadFile function in file API for handling file uploads.
- Created ResourceUploader component for uploading resources with validation and feedback.
- Developed Card component for displaying course class details and managing student enrollment.
- Added AlertDialog components for consistent alert dialog UI.
- Enhanced table components for better data presentation and management.
- Implemented preview page for displaying various resource types based on file extension.
2025-04-08 00:04:29 +08:00

198 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import { toTypedSchema } from "@vee-validate/zod";
import {
createCourseChatper,
deleteCourseChatper,
deleteCourseSection,
deleteResource,
getCourseChatpers,
} from "~/api/course";
import * as z from "zod";
import { useForm } from "vee-validate";
import { toast } from "vue-sonner";
definePageMeta({
requiresAuth: true,
});
const {
params: { id: courseId },
} = useRoute();
const { data: chapters, refresh: refreshChapters } = useAsyncData(() =>
getCourseChatpers(parseInt(courseId as string))
);
const createChatperDialogOpen = ref(false);
const createChatperSchema = toTypedSchema(
z.object({
title: z.string().min(2, "章节名称至少2个字符").max(32, "最大长度32个字符"),
courseId: z.number().min(1, "课程ID不能为空"),
})
);
const createChatperForm = useForm({
validationSchema: createChatperSchema,
initialValues: {
title: "",
courseId: Number(courseId),
},
});
const onCreateChapterSubmit = createChatperForm.handleSubmit((values) => {
toast.promise(createCourseChatper(values), {
loading: "正在创建章节...",
success: () => {
refreshChapters();
createChatperForm.resetForm();
createChatperDialogOpen.value = false;
return "创建章节成功";
},
error: () => {
return "创建章节失败";
},
});
});
const onDeleteChatper = (chapterId: number) => {
toast.promise(deleteCourseChatper(chapterId), {
loading: "正在删除章节...",
success: () => {
refreshChapters();
return "删除章节成功";
},
error: () => {
return "删除章节失败";
},
});
};
const onDeleteSection = (sectionId: number) => {
toast.promise(deleteCourseSection(sectionId), {
loading: "正在删除小节...",
success: () => {
refreshChapters();
return "删除小节成功";
},
error: () => {
return "删除小节失败";
},
});
};
const onDeleteResource = (resourceId: number) => {
toast.promise(deleteResource(resourceId), {
loading: "正在删除资源...",
success: () => {
refreshChapters();
return "删除资源成功";
},
error: () => {
return "删除资源失败";
},
});
};
</script>
<template>
<div class="flex flex-col gap-4 px-4 py-2">
<div class="flex justify-between items-start">
<h1 class="text-xl font-medium">课程章节管理</h1>
<div class="flex items-center gap-4">
<Tooltip :delay-duration="0">
<TooltipTrigger>
<Button
variant="ghost"
size="sm"
class="flex items-center gap-2 text-muted-foreground"
>
<div class="w-2 h-2 rounded-full bg-emerald-500" />
<span>训练完成</span>
</Button>
</TooltipTrigger>
<TooltipContent>点击进行召回测试</TooltipContent>
</Tooltip>
<Dialog v-model:open="createChatperDialogOpen">
<DialogTrigger as-child>
<Button
variant="secondary"
size="sm"
class="flex items-center gap-1"
>
<Icon name="tabler:plus" size="16px" />
<span>添加章节</span>
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>添加章节</DialogTitle>
</DialogHeader>
<form
id="create-chapter-form"
autocomplete="off"
class="space-y-2"
@submit="onCreateChapterSubmit"
>
<FormField v-slot="{ componentField }" name="title">
<FormItem v-auto-animate>
<FormLabel>章节名称</FormLabel>
<FormControl>
<Input
type="text"
placeholder="请输入章节名称"
v-bind="componentField"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<input type="hidden" name="courseId" />
</form>
<DialogFooter>
<Button type="submit" form="create-chapter-form">创建</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
<div
v-if="chapters?.rows && chapters.rows.length > 0"
class="flex flex-col gap-8"
>
<!-- chatpter -->
<CourseChapter
v-for="chapter in chapters?.rows"
:key="chapter.id"
:tag="`${chapter.id}`"
:chapter="chapter"
@refresh="refreshChapters"
@delete-chapter="onDeleteChatper"
@delete-section="onDeleteSection"
@delete-resource="onDeleteResource"
/>
</div>
<EmptyScreen v-else title="暂无章节" icon="fluent-color:document-add-24">
<div class="flex flex-col gap-2">
<p class="text-sm text-muted-foreground">
课程章节列表为空先创建章节吧
</p>
<Button
variant="outline"
size="sm"
@click="createChatperDialogOpen = true"
>
<Icon name="tabler:plus" size="16px" />
<span>添加章节</span>
</Button>
</div>
</EmptyScreen>
</div>
</template>
<style scoped></style>