Sfoglia il codice sorgente

fit:add new listening assigment

lalala 1 anno fa
parent
commit
59ca39fe47

+ 5 - 1
src/apis/assignment.ts

@@ -19,7 +19,7 @@ const assignmentApis = {
         })
     },
     createAssignment(courseId:number, assignmentDTO: AssignmentDTO){
-        return axiosInstance.post(`${ASSIGNMENT_PREFIX}`, assignmentDTO, {
+        return axiosInstance.post(ASSIGNMENT_PREFIX, assignmentDTO, {
             params: {
                 courseId
             }
@@ -34,6 +34,10 @@ const assignmentApis = {
         })
     },
     updateAssignment(assignmentId:number, assignmentDTO:AssignmentDTO){
+        let url = ASSIGNMENT_PREFIX;
+        if(assignmentDTO.type == '听力'){
+            url = ASSIGNMENT_PREFIX + '/listening'
+        } 
         return axiosInstance.put(`${ASSIGNMENT_PREFIX}`, assignmentDTO, {
             params: {
                 assignmentId

+ 2 - 2
src/apis/axios.config.ts

@@ -1,9 +1,9 @@
 import axios from "axios";
 
 // const BASEURL = 'http://47.111.23.171:8081'
-// const BASEURL = 'http://localhost:8080'
+const BASEURL = 'http://localhost:8081'
 // const BASEURL = 'http://8.130.126.86:8080'
-const BASEURL = 'https://air.seec.seecoder.cn/'
+// const BASEURL = 'https://air.seec.seecoder.cn/'
 
 const axiosInstance = axios.create({
     baseURL: BASEURL,

+ 230 - 0
src/components/AudioUpload.vue

@@ -0,0 +1,230 @@
+<template>
+  <el-form-item label="音频">
+    <el-upload
+      ref="uploadAudioRef"
+      action="/api/audio"
+      multiple
+      v-model:file-list="audioFileList"
+      :limit="5"
+      :on-success="handleAudioSuccess"
+      :on-error="handleAudioError"
+      :on-exceed="handleAudioExceed"
+      :on-change="handleAudioChange"
+      accept=".mp3, .wav"
+      :auto-upload="false"
+    >
+      <el-button type="primary">点击上传音频</el-button>
+      <template #tip>
+        <div class="el-upload__tip">支持 mp3/wav 文件,大小不超过 10MB</div>
+      </template>
+    </el-upload>
+  </el-form-item>
+  <el-form-item label="题目">
+    <el-upload
+      ref="uploadQuestionRef"
+      action="/api/file"
+      multiple
+      v-model:file-list="questionFileList"
+      :limit="5"
+      :on-success="handleQuestionSuccess"
+      :on-error="handleQuestionError"
+      :on-exceed="handleQuestionExceed"
+      :on-change="handleQuestionChange"
+      accept=".doc, .docx"
+      :auto-upload="false"
+    >
+      <el-button type="primary">点击上传题目</el-button>
+      <template #tip>
+        <div class="el-upload__tip">doc/docx 文件不大于 500kb</div>
+      </template>
+    </el-upload>
+  </el-form-item>
+  <el-form-item v-if="audioFileList.length > 0" label="播放次数">
+    <el-input-number
+      v-model="AssignmentInfo.playCount"
+      :min="1"
+      :max="10"
+    />
+  </el-form-item>
+  <el-form-item label="平台批改">
+    <el-radio-group v-model="AssignmentInfo.isCorrectRequired">
+      <el-radio :label="true">是</el-radio>
+      <el-radio :label="false">否</el-radio>
+    </el-radio-group>
+  </el-form-item>
+  <el-form-item v-if="AssignmentInfo.isCorrectRequired" label="题目数量">
+    <el-input-number
+      v-model="AssignmentInfo.questionCount"
+      :min="1"
+      :max="10"
+      label="题目数量"
+    />
+  </el-form-item>
+  <el-form-item v-if="AssignmentInfo.isCorrectRequired" label="题目答案">
+    <el-input
+      type="textarea"
+      v-model="AssignmentInfo.answers"
+      :placeholder="
+        '请输入 ' + AssignmentInfo.questionCount + ' 道题的答案,使用;分隔'
+      "
+      :rows="5"
+      :autosize="{ minRows: 5, maxRows: 10 }"
+    ></el-input>
+  </el-form-item>
+  <div class="dialog-footer" style="margin-left: 230px">
+    <el-button @click="dialogConfig.visible = false">取消</el-button>
+    <el-button type="primary" @click="handleSubmit()">确定</el-button>
+  </div>
+</template>
+  
+  <script lang="ts" setup>
+import { ref, defineProps, watch } from "vue";
+import useApis from "@/apis";
+import {
+  ElMessage,
+  ElNotification,
+  type UploadFile,
+  type UploadFiles,
+  type UploadProps,
+  type UploadUserFile,
+} from "element-plus";
+const apis = useApis();
+const props = defineProps({
+  newAssignmentInfo: {
+    type: Object,
+    required: true,
+  },
+  dialogConfig: {
+    type: Object,
+    required: true,
+  },
+});
+const emit = defineEmits(["update:dialogConfig"]);
+watch(
+  () => props.dialogConfig,
+  (newVal) => emit("update:dialogConfig", newVal),
+  { deep: true }
+);
+const AssignmentInfo = ref({
+  assignmentName: "",
+  description: "",
+  type: "",
+  descriptionFile: "",
+  attachments: [""],
+  startTime: "",
+  endTime: "",
+  audios: [],
+  questions: [], 
+  playCount: 1,
+  isCorrectRequired: true,
+  questionCount: 1,
+  answers: "",
+});
+
+watch(
+  () => props.newAssignmentInfo,
+  (newVal) => {
+    Object.assign(AssignmentInfo.value, newVal); // 将非响应式的 newAssignmentInfo 合并到响应式对象 AssignmentInfo
+  },
+  { immediate: true, deep: true } // immediate 表示组件初始化时触发,deep 表示深度监听对象
+);
+
+const audioFileList = ref([]);
+const questionFileList = ref([]);
+let uploadAudioRef = ref();
+let uploadQuestionRef = ref();
+
+// 上传成功处理函数
+const handleAudioSuccess = (response, file, fileList) => {
+  console.log("上传成功", response);
+};
+
+// 上传失败处理函数
+const handleAudioError = (error, file, fileList) => {
+  console.error("上传失败", error);
+};
+
+// 文件超出限制处理函数
+const handleAudioExceed = (files, fileList) => {
+  console.warn("文件超出限制", files, fileList);
+};
+
+const handleAudioChange = (file, fileList) => {
+  // 获取文件的扩展名
+  const fileAudioType = file.name.match(/\.([^\.]+)$/)[1].toLowerCase();
+  const validAudioTypes = ["mp3", "wav"]; // 支持的音频文件类型
+  const isAudioType = validAudioTypes.includes(fileAudioType);
+  const isLt10MB = file.size / 1024 / 1024 < 10; // 限制文件大小为 10MB
+  // 检查文件类型
+  if (!isAudioType) {
+    ElMessage.error("文件仅支持 mp3 和 wav 格式"); // 提示错误信息
+    uploadAudioRef.value.clearFiles(); // 清空上传文件
+    return false;
+  }
+  // 检查文件大小
+  if (!isLt10MB) {
+    ElMessage.error("文件不能超过 10MB!"); // 提示错误信息
+    uploadAudioRef.value.clearFiles(); // 清空上传文件
+    return false;
+  }
+  // 调用上传接口
+  apis
+    .uploadFile(file.raw)
+    .then((res) => {
+      AssignmentInfo.value.audio.push(res.data.data);
+      console.log(AssignmentInfo.value.audio);
+      ElMessage.success("音频上传成功");
+    })
+    .catch((err) => {
+      console.error("音频上传失败:", err);
+      ElMessage.error("音频上传失败");
+    });
+};
+
+const handleQuestionSuccess = (
+  response: any,
+  uploadFile: UploadFile,
+  uploadFiles: UploadFiles
+) => {
+  console.log("上传成功", response);
+};
+
+const handleQuestionError = (error, file, fileList) => {
+  console.error("上传失败", error);
+};
+
+// 文件超出限制处理函数
+const handleQuestionExceed = (files, fileList) => {
+  console.warn("文件超出限制", files, fileList);
+};
+
+const handleQuestionChange = (file, fileList) => {
+  const fileImgType = file.name.match(/\.([^\.]+)$/)[1]; //匹配文件格式(最后一个'.'后的格式)或者匹配file.raw.type
+  const isImageType = ["doc", "docx"];
+  const isLt500KB = file.size / 1024 < 500; //判断图片格式与大小
+  if (isImageType.indexOf(fileImgType) == -1) {
+    ElMessage.error("文件仅支持png、jpg格式"); //限制文件类型
+    uploadQuestionRef.value; //清空上传文件
+    return false;
+  }
+  if (!isLt500KB) {
+    ElMessage.error("文件不能超过500kb!"); //限制文件大小
+    uploadQuestionRef.value.clearFiles(); //清空上传文件
+    return false;
+  }
+  apis
+    .uploadFile(file.raw)
+    .then((res) => {
+      AssignmentInfo.value.descriptionFile = res.data.data;
+      ElMessage.success("题目上传成功");
+      console.log(AssignmentInfo.value.descriptionFile);
+    })
+    .catch((err) => {
+      console.log(err);
+    });
+};
+</script>
+  
+  <style scoped>
+</style>
+  

+ 2 - 0
src/types/dto.ts

@@ -3,6 +3,7 @@ import {AssignmentStatus, Role} from './enums';
 export interface AssignmentDTO {
     assignmentName: string; // 作业名称不能缺失
     description: string; // 作业描述不能缺失
+    type:string,//作业类型不能缺失
     descriptionFile?: string; // 作业要求,Word/Pdf文件 (可选)
     attachments?: string[]; // 作业附件 (可选)
     startTime: string; // 起始时间不能缺失 (ISO 8601 日期时间字符串)
@@ -15,6 +16,7 @@ export interface AssignmentQueryDTO {
     courseId?: number;
     assignmentName?: string;
     status?: AssignmentStatus;
+    type:string;
 }
 
 export interface EnrollDTO {

+ 7 - 2
src/types/vo.ts

@@ -42,6 +42,8 @@ export interface IAssignment {
 
     courseId: number; // 哪个课程下的assignment
 
+    type:string;
+
     startTime: string;
 
     endTime: string;
@@ -58,6 +60,8 @@ export interface AssignmentVO {
 
     description: string;
 
+    type:string;
+
     descriptionFile: string|null; // 作业要求,Word/Pdf文件
 
     attachments: string[]|null; // 作业附件
@@ -77,13 +81,14 @@ export interface AssignmentVO {
 
 export interface IAssignmentTableColumn {
     title: string;
-    dataIndex: 'desc' | 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId' | 'teacherId';
-    key: 'desc' | 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId' | 'teacherId';
+    dataIndex: 'desc' |'type'| 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId' | 'teacherId';
+    key: 'desc' | 'type'| 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId' | 'teacherId';
     width: number
 }
 
 export interface IAssignmentTableItem {
     desc: string;
+    type:string;
     publisher: string;
     startDate: string;
     endDate: string;

+ 1 - 1
src/views/CoursePage/CoursePage.vue

@@ -27,7 +27,7 @@
         <template #default="scope">
           <el-button @click="handleShowDetail(scope.row.courseId)" style="margin-right: 0; margin-left: 0;padding-left: 10px; padding-right: 10px" color="#597D3B">
             <el-icon style="margin-right: 3px" ><ZoomIn/></el-icon>
-            详情
+            描述
           </el-button>
         </template>
       </el-table-column>

+ 22 - 4
src/views/CourseSquare/component/CourseDetails/component/CourseHomeworkTable.vue

@@ -29,7 +29,7 @@
             </el-button>
           </template>
         </el-table-column>
-        <el-table-column :label="role == 'teachers'? '编辑':'写作'" width="60">
+        <el-table-column :label="role == 'teachers'? '编辑':'开始'" width="60">
           <template #default="scope">
             <el-button @mouseover="handleMouse('over', scope.row.teacherId, scope.row.endDate)" @mouseleave="handleMouse('leave', scope.row.teacherId, scope.row.endDate)"
                 color="#597d3b" circle @click="handleFunctionButton(scope.row.assignmentId)" :disabled="props.disabled || isDisabled(scope.row.teacherId, scope.row.endDate)">
@@ -55,13 +55,20 @@
       <el-form-item label="作业描述">
         <el-input v-model="newAssignmentInfo.description" placeholder="请输入描述"></el-input>
       </el-form-item>
+      <el-form-item label="作业类型">
+        <el-select v-model="newAssignmentInfo.type"  placeholder="请选择作业类型" :disabled="dialogConfig.type === 'edit'">
+          <el-option label="写作" value="写作"></el-option>
+          <el-option label="听力" value="听力"></el-option>
+          <el-option label="口语" value="口语"></el-option>
+        </el-select>
+      </el-form-item>
       <el-form-item label="开始时间">
         <el-date-picker v-model="newAssignmentInfo.startTime" type="datetime" placeholder="截止时间" style="width: 180px"/>
       </el-form-item>
       <el-form-item label="截止时间">
         <el-date-picker v-model="newAssignmentInfo.endTime" type="datetime" placeholder="截止时间" style="width: 180px"/>
       </el-form-item>
-      <el-form-item label="描述文件" v-if="dialogConfig.type == 'edit'">
+      <el-form-item label="描述文件" v-if="newAssignmentInfo.type=='写作'">
         <el-upload
             ref="uploadDescribeRef"
             action= "/api/file"
@@ -83,7 +90,7 @@
           </template>
         </el-upload>
       </el-form-item>
-      <el-form-item label="附件" v-if="dialogConfig.type == 'edit'">
+      <el-form-item label="附件" v-if="newAssignmentInfo.type=='写作'">
         <el-upload
             ref="uploadPictureRef"
             action= "/api/file"
@@ -105,6 +112,12 @@
           </template>
         </el-upload>
       </el-form-item>
+      <AudioUpload
+        v-if="newAssignmentInfo.type=='听力'"
+        :newAssignmentInfo="newAssignmentInfo"
+        :dialog-config="dialogConfig"
+      />
+
 <!--      <el-form-item label="作业图片" v-if="dialogConfig.type == 'edit'">-->
 <!--        <el-upload-->
 <!--            ref="uploadPictureRef"-->
@@ -133,7 +146,7 @@
 <!--        </el-upload>-->
 <!--      </el-form-item>-->
     </el-form>
-    <template #footer>
+    <template #footer v-if="newAssignmentInfo.type=='写作'">
       <div class="dialog-footer">
         <el-button @click="dialogConfig.visible = false; cleanNewAssignmentInfo()">取消</el-button>
         <el-button type="primary" @click="handleSubmit">确定</el-button>
@@ -158,6 +171,7 @@ import {defineProps, inject, onMounted, reactive, ref} from 'vue';
   type UploadProps,
   type UploadUserFile
 } from "element-plus";
+  import AudioUpload from '@/components/AudioUpload.vue'; // 引入组件
   import {useFormatDate} from "@/hooks/useFormatDate";
   import ViewsWordEditor from "@/components/ViewsWordEditor/ViewsWordEditor.vue";
   import {useStore} from "@/store";
@@ -187,6 +201,7 @@ import {defineProps, inject, onMounted, reactive, ref} from 'vue';
     Object.assign(newAssignmentInfo, {
       assignmentName: newAssignmentInfo.assignmentName,
       description: newAssignmentInfo.description,
+      type:newAssignmentInfo.type,
       descriptionFile: response.data,
       attachments: newAssignmentInfo.attachments == null? ['']:newAssignmentInfo.attachments,
       startTime: newAssignmentInfo.startTime,
@@ -331,6 +346,7 @@ const openEditor = (url: string, title: string) => {
   let newAssignmentInfo = reactive<AssignmentDTO>({
     assignmentName: '',
     description: '',
+    type:'',
     descriptionFile: '',
     attachments: [''],
     startTime: '',
@@ -341,6 +357,7 @@ const openEditor = (url: string, title: string) => {
     Object.assign(newAssignmentInfo, {
       assignmentName: '',
       description: '',
+      type:'',
       descriptionFile: '',
       attachments: [''],
       startTime: '',
@@ -352,6 +369,7 @@ const openEditor = (url: string, title: string) => {
     Object.assign(newAssignmentInfo, {
       assignmentName: assignment.assignmentName,
       description: assignment.description,
+      type:assignment.type,
       descriptionFile: assignment.descriptionFile,
       attachments: assignment.attachments == null? ['']:assignment.attachments,
       startTime: assignment.startTime,

+ 9 - 2
src/views/CourseSquare/component/CourseDetails/index.vue

@@ -242,7 +242,13 @@ const columns = ref<IAssignmentTableColumn[]>([
     title: '作业名称',
     dataIndex: 'desc',
     key: 'desc',
-    width: 250
+    width: 200
+  },
+  {
+    title: '作业类型',
+    dataIndex: 'type',
+    key: 'type',
+    width: 100
   },
   {
     title: '截止日期',
@@ -339,7 +345,7 @@ const getNewCourseInfo = (dataForm: CourseVO) => {
     startTime: dataForm.startTime,
     endTime: dataForm.endTime,
     description: dataForm.description,
-    enrollCode: ''
+    enrollCode: dataForm.enrollCode
   })
 }
 
@@ -444,6 +450,7 @@ const convertHomeworkList = (data:IAssignment[]) => {
   for(let i = 0; i < data.length; i++){
     let item:IAssignmentTableItem = {
       desc: data[i].assignmentName,
+      type: data[i].type,
       publisher: data[i].teacherId.toString(),
       startDate: formatDate(data[i].startTime),
       endDate: formatDate(data[i].endTime),

+ 28 - 2
src/views/HomeworkPage/component/HomeworkDetail.vue

@@ -22,7 +22,12 @@
         </div>
         <div class="item">
           <el-link type="primary" @click="downloadFile(data.descriptionFile, `${data.assignmentName}-描述文件`)">相关文件下载</el-link>
-          <el-button color="#597d3b" size="large" style="margin-left: 660px" @click="handleWriting()" :disabled="new Date() > new Date(data.endTime)">开始写作</el-button>
+          <div class="button-container">
+            <el-button style="margin-left: 600px" color="#597D3B" @click="handleBack">返回</el-button>
+            <el-button color="#597D3B" @click="handleWriting()" :disabled="new Date() > new Date(data.endTime)">
+              开始{{ data.type }}
+            </el-button>
+          </div>
         </div>
       </div>
     </div>
@@ -69,13 +74,13 @@ let data = reactive<IAssignment>({
   attachments: null,
   teacherId: null,
   teacherName: "",
+  type:"",
   courseId: null,
   startTime: "",
   endTime: "",
   createTime: "",
   status: ""
 })
-
 function handleWriting(){
   getEngagement()
   router.push(`/home/assignment/${assignmentId}/edit`)
@@ -89,7 +94,14 @@ function downloadFile(url:string, fileName:string) {
   link.click();
 }
 
+const handleBack = () => {
+   // 跳转到目标页面
+   router.push(`/home/courseDetail/${data.courseId}`).then(() => {
+    // 强制刷新页面
+    window.location.reload();
+  });
 
+}
 async function getEngagement(){
   apis.getEngagement(store.user.id, assignmentId)
       .then((_res:any) => {
@@ -117,9 +129,12 @@ const fetchData = () => {
   apis.getAssignmentById(assignmentId)
       .then((res: any) => {
         Object.assign(data, res.data.data)
+        
       })
 }
 
+
+
 onMounted(() => {
   fetchData()
 })
@@ -129,4 +144,15 @@ onMounted(() => {
 export default {
   name: "HomeworkDetail"
 }
+
 </script>
+<style scoped>
+  .button-container {
+    display: flex;
+    justify-content: space-between; /* 使按钮之间有空隙 */
+    align-items: center; /* 垂直居中对齐 */
+  }
+  .el-button {
+  margin: 0 10px; /* 可以调整按钮之间的间距 */
+}
+</style>

+ 143 - 0
src/views/HomeworkPage/component/ListeningHomeworkDetail.vue

@@ -0,0 +1,143 @@
+<template>
+    <el-button class="back" style="margin-left: 100px" color="#597D3B" @click="handleBack">&lt;返回</el-button>
+    <div class="homework-details">
+      <div class="homework-details-title">
+        作业详情
+      </div>
+      <div class="homework-details-info">
+        <div class="homework-details-text">
+          <div class="title">
+            {{data.assignmentName}}
+          </div>
+          <div class="item">
+            <span style="font-weight: 600">作业描述: </span>
+            {{data.description}}
+          </div>
+          <div class="item">
+            <span style="font-weight: 600">发布老师: </span>
+            {{data.teacherName}}
+          </div>
+          <div class="item">
+            <span style="font-weight: 600">截止日期: </span>
+            {{new Date(Date.parse(data.endTime)).toLocaleString()}}
+          </div>
+          <div class="item">
+            <el-link type="primary" @click="downloadFile(data.descriptionFile, `${data.assignmentName}-描述文件`)">相关文件下载</el-link>
+            <el-button color="#597d3b" size="large" style="margin-left: 660px" @click="handleWriting()" :disabled="new Date() > new Date(data.endTime)">开始写作</el-button>
+          </div>
+        </div>
+      </div>
+  <!--    <div class="homework-details-title">-->
+  <!--      评论区-->
+  <!--    </div>-->
+  <!--    <div class="homework-remark">-->
+  <!--      <div>-->
+  <!--        <el-skeleton style="width: 240px" animated >-->
+  <!--          <template #template>-->
+  <!--            <el-skeleton-item variant="circle" style="&#45;&#45;el-skeleton-circle-size: 40px"></el-skeleton-item>-->
+  <!--            <el-skeleton-item variant="text" style="height: 40px;width: 200px; margin-left: 30px"></el-skeleton-item>-->
+  <!--          </template>-->
+  <!--        </el-skeleton>-->
+  <!--      </div>-->
+  <!--      <div>-->
+  <!--        <el-input type="textarea" style="width: 100%;margin: 20px 0"></el-input>-->
+  <!--        <el-button color="#597d3b" size="large" style="float: right" :disabled="true">发布</el-button>-->
+  <!--      </div>-->
+  <!--    </div>-->
+    </div>
+  </template>
+  
+  <script lang="ts" setup>
+  import useApis from "@/apis";
+  import {useRoute} from "vue-router";
+  import {onMounted, reactive} from "vue";
+  import type {IAssignment} from "@/types/vo";
+  import './homeworkDetail.scss'
+  import {useStore} from "@/store";
+  import router from "@/router";
+  import {ElMessage} from "element-plus";
+  
+  const apis = useApis()
+  const store = useStore()
+  const route = useRoute()
+  const assignmentId = +route.params.assignmentId //string转number
+  
+  let data = reactive<IAssignment>({
+    assignmentId: null,
+    assignmentName: "",
+    description: "",
+    descriptionFile: null,
+    attachments: null,
+    teacherId: null,
+    teacherName: "",
+    courseId: null,
+    startTime: "",
+    endTime: "",
+    createTime: "",
+    status: ""
+  })
+  
+  function handleWriting(){
+    getEngagement()
+    router.push(`/home/assignment/${assignmentId}/edit`)
+  }
+  
+  function downloadFile(url:string, fileName:string) {
+    const link = document.createElement('a');
+    link.href = url;
+    //link.download = fileName;
+    //link.target = "_blank"; // 可选,如果希望在新窗口中下载文件,请取消注释此行
+    link.click();
+  }
+  
+  const handleBack = () => {
+     // 跳转到目标页面
+     router.push(`/home/courseDetail/${data.courseId}`).then(() => {
+      // 强制刷新页面
+      window.location.reload();
+    });
+  
+  }
+  async function getEngagement(){
+    apis.getEngagement(store.user.id, assignmentId)
+        .then((_res:any) => {
+          console.log(_res)
+          //还未参加作业,则先加入作业
+          if(_res.data.data == null){
+            apis.engageAssignment(assignmentId)
+                .then((res:any) => {
+                  ElMessage({
+                    message: '参加作业成功',
+                    type: 'success',
+                  })
+                })
+                .catch((_err:any) => {
+                  console.log("参加做业的错误",_err)
+                })
+          }
+        }).catch((_err:any) => {
+          console.log(_err)
+        })
+  }
+  
+  
+  const fetchData = () => {
+    apis.getAssignmentById(assignmentId)
+        .then((res: any) => {
+          Object.assign(data, res.data.data)
+        })
+  }
+  
+  
+  
+  onMounted(() => {
+    fetchData()
+  })
+  </script>
+  
+  <script lang="ts">
+  export default {
+    name: "HomeworkDetail"
+  }
+  </script>
+