<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>