- 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.
167 lines
5.3 KiB
Vue
167 lines
5.3 KiB
Vue
<script lang="ts" setup>
|
||
import { toast } from "vue-sonner";
|
||
import { createResource } from "~/api/course";
|
||
import { uploadFile } from "~/api/file";
|
||
import type { FetchError, IResource } from "~/types";
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
modelValue: boolean;
|
||
accept?: string;
|
||
}>(),
|
||
{
|
||
accept: ".docx, .pptx, .pdf, .png, .jpg, .mp4, .mp3",
|
||
}
|
||
);
|
||
|
||
const emit = defineEmits<{
|
||
"update:modelValue": [isOpen: boolean];
|
||
"on-create": [resource: IResource];
|
||
}>();
|
||
|
||
const isDialogOpen = computed({
|
||
get: () => props.modelValue,
|
||
set: (value: boolean) => emit("update:modelValue", value),
|
||
});
|
||
|
||
const loginState = useLoginState();
|
||
|
||
// const isDialogOpen = ref(false);
|
||
const loading = ref(false);
|
||
|
||
const selectedFile = ref<File | null>(null);
|
||
|
||
const onUpload = async () => {
|
||
if (!selectedFile.value) {
|
||
toast.error("请先选择文件", { id: "file-upload-error-no-file-selected" });
|
||
return;
|
||
}
|
||
loading.value = true;
|
||
|
||
toast.promise(
|
||
new Promise((resolve, reject) => {
|
||
uploadFile(selectedFile.value!, "resource")
|
||
.then((url) => {
|
||
createResource({
|
||
resourceName: selectedFile.value!.name,
|
||
resourceSize: selectedFile.value!.size,
|
||
resourceType: "resource",
|
||
resourceUrl: url,
|
||
allowDownload: true,
|
||
isRepo: false,
|
||
ownerId: loginState.user.userId,
|
||
})
|
||
.then((result) => {
|
||
if (result.code !== 200) {
|
||
reject(new Error(result.msg || "文件上传失败"));
|
||
} else {
|
||
emit("on-create", {
|
||
id: result.resourceId,
|
||
resourceName: selectedFile.value!.name,
|
||
resourceSize: selectedFile.value!.size,
|
||
resourceType: "resource",
|
||
resourceUrl: url,
|
||
allowDownload: true,
|
||
isRepo: false,
|
||
ownerId: loginState.user.userId,
|
||
});
|
||
resolve("文件上传成功");
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
reject(error);
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
reject(error);
|
||
});
|
||
}),
|
||
{
|
||
loading: "正在上传文件...",
|
||
success: () => {
|
||
isDialogOpen.value = false;
|
||
return "文件上传成功";
|
||
},
|
||
error: (error: FetchError) => {
|
||
return error.message || "文件上传失败,请稍后重试";
|
||
},
|
||
finally: () => {
|
||
loading.value = false;
|
||
},
|
||
}
|
||
);
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<Dialog v-model:open="isDialogOpen">
|
||
<DialogTrigger as-child>
|
||
<slot name="trigger" />
|
||
</DialogTrigger>
|
||
<DialogContent class="sm:max-w-[520px]">
|
||
<DialogHeader>
|
||
<DialogTitle>资源上传</DialogTitle>
|
||
<DialogDescription>
|
||
<p>上传资源文件并将其添加到课程中。</p>
|
||
<p>
|
||
支持的格式:<span class="font-medium">{{ accept }}</span>
|
||
</p>
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<div class="flex flex-col gap-4">
|
||
<FormField name="file">
|
||
<FormItem>
|
||
<FormLabel>选择文件</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
type="file"
|
||
:accept="accept"
|
||
@change="(e: any) => {
|
||
const files = e.target.files;
|
||
if (files && files.length > 0) {
|
||
selectedFile = files[0];
|
||
} else {
|
||
selectedFile = null;
|
||
}
|
||
}"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
</FormField>
|
||
<input type="hidden" name="courseId" />
|
||
<div class="text-xs text-muted-foreground space-y-2">
|
||
<p>
|
||
根据国家《出版管理条例》《网络出版服务管理规定》及教育部《职业教育专业教学资源库建设工作手册》等相关规定,上传的资源必须符合以下要求:
|
||
</p>
|
||
<ul
|
||
class="list-disc list-inside space-y-1 text-[11px] text-muted-foreground/80 text-justify"
|
||
>
|
||
<li>
|
||
没有法律、法规禁止出版的内容,没有政治性、道德性问题和科学性错误,不泄露国家秘密。
|
||
</li>
|
||
<li>
|
||
不含有侵犯他人著作权、肖像权、名誉权等权益的内容,资源具有原创性,引用需指明作者姓名、作品名称,使用他人作品应取得许可。
|
||
</li>
|
||
<li>
|
||
采用法定计量单位,名词、术语、符号等符合国家统一规定,尚无统一规定的,可采用习惯用法并保持一致。
|
||
</li>
|
||
<li>
|
||
地图具有严肃的政治性、严密的科学性和严格的法定性,使用的地图应根据《地图管理条例》的要求已送相关部门审核并标注审图号。
|
||
</li>
|
||
<li>不含有商业广告、商业性宣传内容。</li>
|
||
<li>不含有色情、赌博、迷信、暴力等内容。</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button :disabled="loading" @click="onUpload">
|
||
{{ loading ? "上传中..." : "上传" }}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</template>
|
||
|
||
<style scoped></style>
|