Ver código fonte

fix:合并seec-x86分支修改

廖彦舟 1 ano atrás
pai
commit
bbd5b67d75

Diferenças do arquivo suprimidas por serem muito extensas
+ 831 - 39
package-lock.json


+ 1 - 0
src/api/course/course.js

@@ -4,6 +4,7 @@ import courseSelectionAPIs from "@/api/course/courseSelection";
 const prefix = "/course";
 
 function createCourse(data) {
+  console.log(data);
   return axios.post(`${prefix}/create`, data);
 }
 

+ 52 - 0
src/api/homework.js

@@ -0,0 +1,52 @@
+import {axios} from "@/requests/axios.config";
+import userAPIs from "@/api/user";
+function createWritingHomework(courseId, data){
+    // return axios.post(`/eai/exam/${courseId}/create/writing`,data);
+    return axios.post(`/eai/exam/${courseId}/create/writing`,data);
+}
+
+function createSpeakingHomework(courseId, data){
+    // return axios.post(`/eai/exam/${courseId}/create/writing`,data);
+    return axios.post(`/eai/exam/${courseId}/create/speaking`,data);
+}
+function updateHomework(assignmentId,data){
+    return axios.put(`/eai/exam/${assignmentId}/update/writing`,data);
+}
+function updateSpeakingHomework(assignmentId,data){
+    return axios.put(`/eai/exam/${assignmentId}/update/speaking`,data);
+}
+function getHomeworkDetail(assignmentId,type){
+    return axios.get(`/eai/exam/${assignmentId}/detail`,{
+        params:{
+            type
+        }
+    });
+}
+function deleteHomework(teacherId,assignmentId){
+    return axios.delete("/eai/assignment/delete",{
+        params:{
+            teacherId,
+            assignmentId
+        }
+    });
+}
+
+function getHomeworks(courseId){
+    return axios.get("/eai/exam/teacher/course",{
+        params:{
+            courseId
+        }
+    });
+}
+
+const homeworkAPIs = {
+     createWritingHomework,
+    getHomeworks,
+    deleteHomework,
+    updateHomework,
+    createSpeakingHomework,
+    getHomeworkDetail,
+    updateSpeakingHomework
+
+};
+export default homeworkAPIs;

+ 3 - 1
src/api/index.js

@@ -10,6 +10,7 @@ import groupAPIS from  "@/api/group";
 import projectAPIs from "@/api/project";
 import courseWareAPIs from "@/api/courseWare";
 import communicationAPIs from "@/api/communication";
+import homeworkAPIs from "@/api/homework";
 
 const api = {
   ...userAPIs,
@@ -23,7 +24,8 @@ const api = {
   ...groupAPIS,
   ...projectAPIs,
   ...courseWareAPIs,
-  ...communicationAPIs
+  ...communicationAPIs,
+  ...homeworkAPIs
 };
 
 export default api;

BIN
src/assets/course-SE1.png


+ 1 - 1
src/components/GlobalHeader/GlobalHeader.vue

@@ -15,7 +15,7 @@
       <el-menu-item index="5">
         任务
       </el-menu-item>
-      <el-menu-item index="4">
+      <el-menu-item index="4" v-if="false">
         项目
       </el-menu-item>
       <el-menu-item v-if="isTeacher" index="7">

+ 7 - 2
src/global/register-element.js

@@ -40,7 +40,7 @@ import {
   ElContainer, ElHeader, ElMain,
   ElBadge, ElOptionGroup, ElTooltip,
   ElAlert,
-  ElProgress, ElCheckboxGroup
+  ElProgress, ElCheckboxGroup, ElEmpty, ElSlider, ElLoading, ElInputNumber
 } from "element-plus";
 
 // NOTE 使用到的组件列表,请确保 import 和 component 的一致性
@@ -93,7 +93,12 @@ const components = [
   ElMain,
   ElAlert,
   ElProgress,
-  ElCheckboxGroup
+  ElCheckboxGroup,
+  ElEmpty,
+  ElSlider,
+    ElPopover,
+    ElLoading,
+    ElInputNumber
 ];
 
 export default function (app) {

+ 10 - 5
src/router/index.js

@@ -29,16 +29,21 @@ const routes = [
     name: "Task",
     component: () => import("../views/TaskPage/TaskPage.vue"),
   },
-  {
-    path: "/project",
-    name: "Project",
-    component: () => import("../views/ProjectPage/ProjectPage.vue"),
-  },
+  // {
+  //   path: "/project",
+  //   name: "Project",
+  //   component: () => import("../views/ProjectPage/ProjectPage.vue"),
+  // },
   {
     path: "/home",
     name: "Home",
     component: () => import("../views/HomePage/HomePage.vue"),
   },
+  {
+    path: "/homework/:homeworkId/:teacherId/:type/:courseId",
+    name: "Homework",
+    component: () => import("../views/CoursePage/HomeworkPage.vue"),
+  },
   {
     path: "/message",
     name: "Message",

+ 19 - 3
src/views/CourseDetailPage/CourseDetailPage.vue

@@ -56,6 +56,16 @@
         <el-tabs v-model="tabName"
                  type="card"
                  @tab-click="handleTabsClick">
+          <el-tab-pane label="作业" name="homework" v-if="courseInfo.eaiId">
+            <homework-tab ref="homeworkTab"
+                          :course-name="courseInfo.name"
+                          :coder-id="courseInfo.coderId"
+                          :eval-id="courseInfo.evalId"
+                          :eai-id="courseInfo.eaiId"
+                          :teacher-id="courseInfo.teacher.id"
+                          :course-id="courseInfo.id"
+            />
+          </el-tab-pane>
           <el-tab-pane label="任务" name="task">
             <task-tab ref="taskTab"
                       v-if="taskTabVisible"
@@ -83,13 +93,13 @@
                          :course-id="courseInfo.id"
                          :teacher-id="courseInfo.teacher.id"/>
           </el-tab-pane>
-          <el-tab-pane label="项目" name="project">
+          <el-tab-pane label="项目" name="project" v-if="false">
             <project-tab ref="projectTab"
                          v-if="projectTabVisible && courseInfo.eaiId == null"
                          :course-id="courseInfo.id"
                          :teacher-id="courseInfo.teacher.id"/>
           </el-tab-pane>
-          <el-tab-pane label="团队" name="group">
+          <el-tab-pane label="团队" name="group" v-if="false">
             <group-tab ref="groupTab"
                        v-if="groupTabVisible"
                        :course-id="courseInfo.id"
@@ -110,10 +120,12 @@
   import AnnouncementTab from "@/views/CourseDetailPage/tabs/AnnouncementTab.vue";
   import MessageTab from "@/views/CourseDetailPage/tabs/MessageTab.vue";
   import portal from "@/utils/portal/portal";
+  import HomeworkTab from "@/views/CourseDetailPage/tabs/HomeworkTab.vue";
 
   export default {
     name: "CourseDetail",
     components: {
+      HomeworkTab,
       MessageTab,
       AnnouncementTab,
       CoursewareTab,
@@ -129,7 +141,7 @@
         jump_from_message: null,
         jump_from_message_id: null,
 
-        tabName: "task",
+        tabName: "homework",
         taskTabVisible: true,
         coursewareTabVisible: true,
         announcementTabVisible: true,
@@ -183,6 +195,7 @@
         this.$apis.getCourseById(this.$route.params.id)
                 .then(res => {
                   this.courseInfo = res.data.data;
+                  console.log(res);
                   this.$apis.checkCourseSelection(this.$route.params.id, this.$store.state.user.id)
                           .then(res => {
                             this.isCourseSelected = res.data.data;
@@ -258,6 +271,9 @@
 
       refreshTab() {
         switch (this.tabName) {
+          case "homework":
+            this.$refs.homeworkTab.init();
+            break;
           case "task":
             this.$refs.taskTab.init();
             break;

+ 690 - 0
src/views/CourseDetailPage/tabs/HomeworkTab.vue

@@ -0,0 +1,690 @@
+<script>
+import {ElMessage, ElMessageBox} from "element-plus";
+import {Close, UploadFilled} from "@element-plus/icons-vue";
+
+export default {
+  name: "HomeworkTab",
+  components: { UploadFilled},
+  props: {
+    coderId: {
+      required: true,
+    },
+    evalId: {
+      required: true,
+    },
+    eaiId: {
+      require:true,
+    },
+    courseId: {
+      required: true,
+    },
+    courseName:{
+      required:true,
+    },
+    teacherId: {
+      required: true,
+    },
+  },
+  data(){
+    return{
+      homeworkList: [],
+      inputValue:"",
+      dialog:{
+        delete:false,
+        create:false,
+        update:false,
+      },
+      speakingDialog:{
+        delete:false,
+        create:false,
+        update:false,
+      },
+      writingHomeworkForm:{
+        assignmentName:"",
+        description:"",
+        type:"writing",
+        descriptionFile:"",
+        attachments:[],
+        timeRange:[]
+      },
+      inputVisible:false,
+      speakingHomeworkForm:{
+        assignmentName:"",
+        description:"",
+        type:"speaking",
+        descriptionFile:"",
+        attachments:[],
+        timeRange:[],
+        minDuration:null,
+        minInteraction:null,
+        surveyLinks:[],
+        surveyDuration:null
+      },
+      rules:{
+        assignmentName:[
+          { required: true, message: "请输入作业名", trigger: "blur" },
+          { min: 3, max: 50, message: "长度应大于3小于50", trigger: "blur" },
+        ],
+        description:[
+          { required: true, message: "请输入作业描述", trigger: "blur" },
+          { min: 10, max: 200, message: "长度应大于10小于200", trigger: "blur" },
+        ],
+        timeRange:[
+          { required: true, message: "请选择时间范围", trigger: "blur" },
+        ],
+      },
+      speakingRules:{
+        assignmentName:[
+          { required: true, message: "请输入作业名", trigger: "blur" },
+          { min: 3, max: 50, message: "长度应大于3小于50", trigger: "blur" },
+        ],
+        description:[
+          { required: true, message: "请输入作业描述", trigger: "blur" },
+          { min: 10, max: 200, message: "长度应大于10小于200", trigger: "blur" },
+        ],
+        timeRange:[
+          { required: true, message: "请选择时间范围", trigger: "blur" },
+        ],
+        minDuration:[
+          { validator: this.checkDuration, trigger: "blur" },
+        ],
+        minInteraction:[
+          { validator: this.checkInteraction, trigger: "blur" },
+        ],
+        surveyDuration:[
+          { validator: this.checkInteraction, trigger: "blur" },
+        ],
+      }
+    };
+  },
+  mounted() {
+    this.init();
+  },
+  computed:{
+    isTeacher() {
+      return this.$store.getters.isTeacher;
+    },
+  },
+  methods:{
+    init(){
+      const this_ = this;
+      this.$apis.getHomeworks(this.eaiId)
+          .then(res => {
+            if (res){
+              console.log(res);
+              this.homeworkList = res.data.data;
+            }
+          })
+          .catch(error => {
+            ElMessage.error("获取作业失败");
+          });
+    },
+    /**
+     * 根据eaiID创建写作作业
+     */
+    createHomework(){
+      this.$refs.homeworkFormRef.validate((valid)=>{
+        if (valid){
+          // console.log(this.dateTimeFormatter(this.writingHomeworkForm.timeRange[0]));
+          // console.log(this.dateTimeFormatter(this.writingHomeworkForm.timeRange[1]));
+          let homeworkVO = {
+            assignmentName:this.writingHomeworkForm.assignmentName,
+            description:this.writingHomeworkForm.description,
+            type:this.writingHomeworkForm.type,
+            descriptionFile:this.writingHomeworkForm.descriptionFile,
+            attachments:this.writingHomeworkForm.attachments,
+            startTime: this.dateTimeFormatter(this.writingHomeworkForm.timeRange[0]),
+            endTime: this.dateTimeFormatter(this.writingHomeworkForm.timeRange[1]),
+          };
+          this.$apis.createWritingHomework(this.eaiId,homeworkVO)
+              .then(res => {
+                console.log( res);
+                if (res && res.data.code === 200){
+                  ElMessage.success("作业创建成功");
+                  this.dialog.create = false;
+                  this.init();
+                }
+                else{
+                  ElMessage.error("作业创建失败");
+                }
+              })
+              .catch(error => {
+                ElMessage.error("创建失败");
+              });
+        }
+      });
+    },
+    /**
+     * 根据eaiID创建对话作业
+     */
+    createSpeakingHomework(){
+      this.$refs.speakingHomeworkFormRef.validate((valid)=>{
+        if (valid){
+          let homeworkVO = {
+            assignmentName:this.speakingHomeworkForm.assignmentName,
+            description:this.speakingHomeworkForm.description,
+            type:this.speakingHomeworkForm.type,
+            descriptionFile:this.speakingHomeworkForm.descriptionFile,
+            attachments:this.speakingHomeworkForm.attachments,
+            startTime: this.dateTimeFormatter(this.speakingHomeworkForm.timeRange[0]),
+            endTime: this.dateTimeFormatter(this.speakingHomeworkForm.timeRange[1]),
+            minDuration:this.speakingHomeworkForm.minDuration,
+            minInteraction:this.speakingHomeworkForm.minInteraction,
+            surveyLinks:this.speakingHomeworkForm.surveyLinks,
+            surveyDuration:this.speakingHomeworkForm.surveyDuration
+          };
+          this.$apis.createSpeakingHomework(this.eaiId,homeworkVO)
+              .then(res => {
+                console.log( res);
+                if (res && res.data.code === 200){
+                  ElMessage.success("作业创建成功");
+                  this.dialog.create = false;
+                  this.speakingDialog.create = false;
+                  this.init();
+                }
+                else{
+                  ElMessage.error("作业创建失败");
+                }
+              })
+              .catch(error => {
+                ElMessage.error("创建失败");
+              });
+        }
+      });
+    },
+    handleClose(){
+      ElMessageBox.confirm("确认吗?你的修改将不会保存。",{
+        confirmButtonText:"确认",
+        cancelButtonText:"取消"
+      })
+          .then(() => {
+            this.dialog.create = false;
+            this.speakingDialog.create = false;
+            this.$refs.homeworkFormRef.resetFields();
+            this.$refs.speakingHomeworkFormRef.resetFields();
+            done();
+          })
+          .catch(() => {
+            done();
+          });
+    },
+    toHomeworkDetail(id,type){
+      this.$router.push("/homework/"+id+"/"+this.teacherId+"/"+type+"/"+this.eaiId);
+    },
+    statusInfo(status){
+      if (status === "NOT_STARTED"){
+        return {text:"未开始",type:"info",color:"#909399"};
+      }
+      else if (status === "PROCEEDING"){
+        return {text:"进行中",type:"success",color: "#67C23A"};
+      }
+      else if (status === "FINISHED"){
+        return {text:"已结束",type:"info",color: "#909399"};
+      }
+      else{
+        return {text:"未知",type:"info",color: "#909399"};
+      }
+    },
+    handleAddEaiWritingClick(){
+      this.dialog.create = true;
+    },
+    handleAddEaiSpeakingClick(){
+      this.speakingDialog.create = true;
+    },
+    dateTimeFormatter(date){
+      return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
+    },
+    handleLinkClose(link){
+      this.speakingHomeworkForm.surveyLinks.splice(this.speakingHomeworkForm.surveyLinks.indexOf(link), 1);
+    },
+    handleInputConfirm(){
+      if (this.inputValue) {
+        this.speakingHomeworkForm.surveyLinks.push(this.inputValue);
+      }
+      this.inputVisible = false;
+      this.inputValue = "";
+    },
+    showInput(){
+      this.inputVisible = true;
+      this.$nextTick(() => {
+        this.$refs.InputRef.input.focus();
+      });
+    },
+    checkNumberRange(rule,value,callback,min,max){
+      if (!value){
+        callback();
+        return;
+      }
+      if (Number.isNaN(value)){
+        callback(new Error("请输入整数"));
+        return;
+      }
+      if (Number.isInteger(Number(value))){
+        if (Number(value)>=min && Number(value)<=max){
+          callback();
+        }
+        else{
+          callback(new Error(`请输入${min}~${max}的整数`));
+        }
+      }
+      else{
+        callback(new Error("请输入整数"));
+      }
+    },
+    checkDuration(rule,value,callback){
+      this.checkNumberRange(rule,value,callback,1,120);
+    },
+    checkInteraction(rule,value,callback){
+      this.checkNumberRange(rule,value,callback,1,50);
+    }
+  }
+};
+</script>
+
+<template>
+  <div class="homework-tab">
+    <div class="homework-tab-bar" v-if="isTeacher">
+      <el-button
+                 @click="handleAddEaiWritingClick"
+                 icon="el-icon-circle-plus-outline"
+                 style="color: var(--el-color-primary);">写作作业
+      </el-button>
+      <el-button
+                 @click="handleAddEaiSpeakingClick"
+                 icon="el-icon-circle-plus-outline"
+                 style="color: #50A879;">口语作业
+      </el-button>
+    </div>
+    <el-timeline
+        style="width: auto;margin-top: 20px">
+      <el-timeline-item
+          v-for="(homework,index) in homeworkList"
+          :timestamp="(homework.startTime.substring(0, 16) + ' — ' + homework.endTime.substring(0, 16)).replaceAll('T',' ')"
+          :color="statusInfo(homework.status).color"
+          @click="toHomeworkDetail(homework.assignmentId,homework.type)"
+          :key="index">
+        <el-row>
+          <el-tag v-if="homework['type'] ==='writing'" size="small" color="#409EFFFF" effect="dark"
+                  id="homework-tag">W
+          </el-tag>
+          <el-tag v-else-if="homework['type'] ==='speaking'" size="small" color="#50A879" effect="dark"
+                  id="homework-tag" round>S
+          </el-tag>
+          <el-tag v-else class="timeline-content" size="small" color="#8dc9e2" effect="dark"
+                  style="border: none">?
+          </el-tag>
+          <el-row class="timeline-content">
+            <el-tag :type="statusInfo(homework.status).type" size="small">{{statusInfo(homework.status).text}}</el-tag>
+          </el-row>
+          <el-row class="task-title timeline-content">
+            {{ homework.assignmentName }}
+          </el-row>
+        </el-row>
+      </el-timeline-item>
+<!--      <el-timeline-item-->
+<!--          @click="toHomeworkDetail(activity.id)"-->
+<!--          v-for="(activity, index) in homeworkList"-->
+<!--          :key="index"-->
+<!--          :timestamp="activity.startTime+'~'+activity.endTime"-->
+<!--      >-->
+<!--        {{ activity.assignmentName }}-->
+<!--      </el-timeline-item>-->
+    </el-timeline>
+    <el-empty
+        v-if="homeworkList.length===0"
+        description="暂无作业"
+    />
+  </div>
+  <el-dialog
+      v-model="dialog.create"
+      title="创建写作作业"
+      width="40%"
+      v-if="isTeacher"
+      :before-close="handleClose"
+  >
+    <el-form :model="writingHomeworkForm"
+             label-width="130px"
+             ref="homeworkFormRef"
+             class="homework-form"
+             label-position="right"
+             :rules="rules"
+             >
+      <el-form-item label="作业名"
+                    prop="assignmentName"
+                    class="homework-form-item">
+        <el-input class="form-input"
+                  maxlength="50"
+                  show-word-limit
+            v-model="writingHomeworkForm.assignmentName" />
+      </el-form-item>
+      <el-form-item label="作业详细描述"
+                    required
+                    prop="description"
+                    class="homework-form-item">
+        <el-input type="textarea"
+                  resize="none"
+                  style="font-size: 20px"
+                  maxlength="200"
+                  show-word-limit
+                  :autosize="{ minRows: 1, maxRows: 10 }"
+            v-model="writingHomeworkForm.description" />
+      </el-form-item>
+      <el-form-item label="作业类型"
+                    prop="type"
+                    class="homework-form-item">
+        <span>
+          写作
+        </span>
+      </el-form-item>
+      <el-form-item label="作业描述文件"
+                    prop="descriptionFile"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="1"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业附件"
+                    prop="attachment"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="5"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业期限"
+                    prop="timeRange"
+                    class="homework-form-item">
+        <el-date-picker
+            v-model="writingHomeworkForm.timeRange"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="dialog.create = false;">取消</el-button>
+        <el-button type="primary" @click="createHomework">
+          创建
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+  <el-dialog
+      v-model="speakingDialog.create"
+      title="创建口语作业"
+      width="40%"
+      v-if="isTeacher"
+      :before-close="handleClose"
+  >
+    <el-form :model="speakingHomeworkForm"
+             ref="speakingHomeworkFormRef"
+             class="homework-form"
+             label-width="130px"
+             label-position="right"
+             :rules="speakingRules"
+    >
+      <el-form-item label="作业名"
+                    prop="assignmentName"
+                    class="homework-form-item">
+        <el-input class="form-input"
+                  maxlength="50"
+                  show-word-limit
+                  v-model="speakingHomeworkForm.assignmentName" />
+      </el-form-item>
+      <el-form-item label="作业详细描述"
+                    prop="description"
+                    class="homework-form-item">
+        <el-input type="textarea"
+                  resize="none"
+                  style="font-size: 20px"
+                  maxlength="200"
+                  show-word-limit
+                  :autosize="{ minRows: 1, maxRows: 10 }"
+                  v-model="speakingHomeworkForm.description" />
+      </el-form-item>
+      <el-form-item label="作业类型"
+                    prop="type"
+                    class="homework-form-item">
+        <span>
+          AI口语
+        </span>
+      </el-form-item>
+      <el-form-item label="作业描述文件"
+                    prop="descriptionFile"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="1"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业附件"
+                    prop="attachment"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="5"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业期限"
+                    prop="timeRange"
+                    class="homework-form-item">
+        <el-date-picker
+            v-model="speakingHomeworkForm.timeRange"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+        />
+      </el-form-item>
+      <el-form-item label="最少时长"
+                    class="homework-form-item"
+                    prop="minDuration">
+        <div style="width: 100%;height: 100%;">
+          <el-input v-model="speakingHomeworkForm.minDuration"
+                    style="width: 100%"
+                    placeholder="请输入最少分钟数">
+            <template #append>
+              分钟
+            </template>
+          </el-input>
+        </div>
+      </el-form-item>
+      <el-form-item label="最少交流"
+                    class="homework-form-item"
+                    prop="minInteraction">
+        <el-input v-model="speakingHomeworkForm.minInteraction"
+                  style="width: 100%"
+                  placeholder="请输入最少交流数">
+          <template #append>
+            次
+          </template>
+        </el-input>
+      </el-form-item>
+      <el-form-item label="问卷链接"
+                    class="homework-form-item"
+                    prop="surveyLinks">
+        <div style="display: flex;flex-direction: column;">
+          <transition-group name="list" tag="links"
+                            style="
+                            display: flex;
+                            gap: 5px;
+                            margin-bottom: 5px;
+                            max-height: 200px;
+                            overflow-y: auto;
+                            flex-direction: column;">
+
+            <el-tag
+                id="links"
+                style="width: fit-content;height: auto"
+                v-for="link in speakingHomeworkForm.surveyLinks"
+                :key="link"
+                closable
+                :disable-transitions="true"
+                @close="handleLinkClose(link)"
+            >
+              <el-tooltip
+                  :content="link"
+                  :disabled="link.length<=40"
+                  :hide-after="150"
+                  placement="top">
+                <span>{{link.substring(0,40)+(link.length>40?'...':'')}}</span>
+              </el-tooltip>
+            </el-tag>
+          </transition-group>
+          <el-input
+              v-if="inputVisible"
+              ref="InputRef"
+              v-model="inputValue"
+              class="w-20"
+              size="small"
+              @keyup.enter="handleInputConfirm"
+              @blur="handleInputConfirm"
+          />
+          <el-button v-else class="button-new-tag" size="small" plain type="primary" @click="showInput">
+            + 添加问卷链接
+          </el-button>
+        </div>
+      </el-form-item>
+      <el-form-item label="问卷投放间隔"
+                    class="homework-form-item"
+                    prop="surveyDuration">
+
+        <el-input v-model="speakingHomeworkForm.surveyDuration"
+                  style="width: 100%"
+                  placeholder="请输入问卷投放间隔">
+          <template #append>
+            分钟
+          </template>
+        </el-input>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="speakingDialog.create = false;">取消</el-button>
+        <el-button type="primary" @click="createSpeakingHomework">
+          创建
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<style scoped>
+#homework-tag{
+  border: none;border-radius: 1000px;width: 25px;height: 25px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.homework-tab{
+  width: 100%;
+  height: auto;
+  display: flex;
+  flex-direction: column;
+}
+.homework-tab-bar{
+  height: auto;
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  justify-content: start;
+  align-items: center;
+}
+.homework-form{
+  width: 100%;
+  height: auto;
+}
+.homework-form-item{
+  width: 100%;
+  max-width: 100%;
+  height: auto;
+}
+.form-input{
+  font-size: 16px;
+  width: 100%;
+}
+.timeline-content{
+  margin-left: 5px;
+}
+.list-enter-active,
+.list-leave-active {
+  transition: all 0.5s ease;
+}
+.list-enter-from,
+.list-leave-to {
+  opacity: 0;
+}
+</style>

+ 14 - 24
src/views/CourseDetailPage/tabs/TaskTab.vue

@@ -11,16 +11,6 @@
                  icon="el-icon-circle-plus-outline"
                  style="color: #f5b86e;">Eval测试
       </el-button>
-      <el-button v-if="eaiId"
-                 @click="handleAddEaiWritingClick"
-                 icon="el-icon-circle-plus-outline"
-                 style="color: #50A879;">写作任务
-      </el-button>
-      <el-button v-if="eaiId"
-                 @click="handleAddEaiSpeakingClick"
-                 icon="el-icon-circle-plus-outline"
-                 style="color: #50A879;">口语任务
-      </el-button>
       <el-timeline>
         <el-timeline-item
             v-for="task in courseTaskList"
@@ -488,17 +478,17 @@ export default {
         if (_this.coderId !== null) {
           _this.coderExamList = (await _this.$apis.getTeacherCoderExamsByCourseId(_this.coderId)).data.data || [];
         }
-        if(this.eaiId !== null) {
-          const result = (await _this.$apis.getTeacherEaiExamsByCourseId(_this.eaiId)).data.data || [];
-          _this.eaiExamList = result.map(item => ({
-            eaiId: item.assignmentId,
-            assignmentName:item.assignmentName,
-            startTime:item.startTime,
-            endTime:item.endTime,
-            type:item.type,
-            status:item.status
-          }));
-        }
+        // if(this.eaiId !== null) {
+        //   const result = (await _this.$apis.getTeacherEaiExamsByCourseId(_this.eaiId)).data.data || [];
+        //   _this.eaiExamList = result.map(item => ({
+        //     eaiId: item.assignmentId,
+        //     assignmentName:item.assignmentName,
+        //     startTime:item.startTime,
+        //     endTime:item.endTime,
+        //     type:item.type,
+        //     status:item.status
+        //   }));
+        // }
       } else if (this.isStudent) {
         if (_this.evalId !== null) {
           _this.evalExamList = (await _this.$apis.getStudentEvalExamsByCourseId(_this.evalId)).data.data || [];
@@ -702,7 +692,7 @@ export default {
       this.eaiExamDialog = true;
     },
 
-    
+
 
     handleAddCoderClick() {
       this.coderExamDialog = true;
@@ -825,8 +815,8 @@ export default {
       });
     }
   },
-  
-  
+
+
 };
 </script>
 

+ 774 - 0
src/views/CoursePage/HomeworkPage.vue

@@ -0,0 +1,774 @@
+<script>
+import {ElLoading, ElMessage, ElMessageBox} from "element-plus";
+import {Back, UploadFilled} from "@element-plus/icons-vue";
+
+export default {
+  name: "HomeworkPage",
+  components: {UploadFilled, Back},
+  data(){
+    return{
+      homeworkInfo:{
+        assignmentName:"  ",
+        description:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+        type:"写作",
+        descriptionFile:"",
+        attachments:[],
+        startTime:"2025-06-15 09:00:00",
+        endTime:"2025-06-30 09:00:00"
+      },
+      inputVisible:false,
+      speakingHomeworkInfo:{
+        assignmentName:"",
+        description:"",
+        type:"speaking",
+        descriptionFile:"",
+        attachments:[],
+        startTime:"2025-06-15 09:00:00",
+        endTime:"2025-06-30 09:00:00",
+        minDuration:null,
+        minInteraction:null,
+        surveyLinks:[],
+        surveyDuration:null,
+
+      },
+      speakingHomeworkForm:{
+        assignmentName:"",
+        description:"",
+        type:"speaking",
+        descriptionFile:"",
+        attachments:[],
+        timeRange:[],
+        minDuration:null,
+        minInteraction:null,
+        surveyLinks:[],
+        surveyDuration:null
+      },
+      dialog:{
+        delete:false,
+        create:false,
+        update:false,
+      },
+      inputValue:"",
+      speakingDialog:{
+        delete:false,
+        create:false,
+        update:false,
+      },
+      isLoading:true,
+      homeworkList:[],
+      homeworkType:"",
+      assignmentId:0,
+      homeworkForm:{
+        assignmentName:"作业2",
+        description:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+        type:"写作",
+        descriptionFile:"",
+        attachments:[],
+        timeRange:[]
+      },
+      rules:{
+        assignmentName:[
+          { required: true, message: "请输入作业名", trigger: "blur" },
+          { min: 3, max: 50, message: "长度应大于3小于50", trigger: "blur" },
+        ],
+        description:[
+          { required: true, message: "请输入作业描述", trigger: "blur" },
+          { min: 10, max: 200, message: "长度应大于10小于200", trigger: "blur" },
+        ],
+        timeRange:[
+          { required: true, message: "请选择时间范围", trigger: "blur" },
+        ],
+      },
+      speakingRules:{
+        assignmentName:[
+          { required: true, message: "请输入作业名", trigger: "blur" },
+          { min: 3, max: 50, message: "长度应大于3小于50", trigger: "blur" },
+        ],
+        description:[
+          { required: true, message: "请输入作业描述", trigger: "blur" },
+          { min: 10, max: 200, message: "长度应大于10小于200", trigger: "blur" },
+        ],
+        timeRange:[
+          { required: true, message: "请选择时间范围", trigger: "blur" },
+        ],
+        minDuration:[
+          { validator: this.checkDuration, trigger: "blur" },
+        ],
+        minInteraction:[
+          { validator: this.checkInteraction, trigger: "blur" },
+        ],
+        surveyDuration:[
+          { validator: this.checkInteraction, trigger: "blur" },
+        ],
+      }
+    };
+  },
+  mounted() {
+    this.init();
+  },
+  methods:{
+    init() {
+      this.assignmentId = this.$route.params.homeworkId;
+      this.homeworkType = this.$route.params.type === "speaking"?"口语":"写作";
+      const temp = ElLoading.service();
+      this.$apis.getHomeworkDetail(this.assignmentId,this.homeworkType)
+          .then(res => {
+            console.log(res);
+            if (this.homeworkType === "写作"){
+              this.homeworkInfo = res.data.data;
+              this.homeworkForm.assignmentName = this.homeworkInfo.assignmentName;
+              this.homeworkForm.description = this.homeworkInfo.description;
+              this.homeworkForm.type = this.homeworkInfo.type;
+              this.homeworkForm.descriptionFile = this.homeworkInfo.descriptionFile;
+              this.homeworkForm.attachments = this.homeworkInfo.attachments;
+              this.homeworkForm.timeRange = [this.homeworkInfo.startTime,this.homeworkInfo.endTime];
+            }
+            else{
+              this.speakingHomeworkInfo = res.data.data;
+              this.speakingHomeworkForm.assignmentName = this.speakingHomeworkInfo.assignmentName;
+              this.speakingHomeworkForm.description = this.speakingHomeworkInfo.description;
+              this.speakingHomeworkForm.type = this.speakingHomeworkInfo.type;
+              this.speakingHomeworkForm.descriptionFile = this.speakingHomeworkInfo.descriptionFile;
+              this.speakingHomeworkForm.attachments = this.speakingHomeworkInfo.attachments;
+              this.speakingHomeworkForm.timeRange = [this.speakingHomeworkInfo.startTime,this.speakingHomeworkInfo.endTime];
+              this.speakingHomeworkForm.minDuration = this.speakingHomeworkInfo.minDuration;
+              this.speakingHomeworkForm.minInteraction = this.speakingHomeworkInfo.minInteraction;
+              this.speakingHomeworkForm.surveyLinks = [...this.speakingHomeworkInfo.surveyLinks];
+              this.speakingHomeworkForm.surveyDuration = this.speakingHomeworkInfo.surveyDuration;
+              if (this.speakingHomeworkForm.surveyLinks.length!==0&&
+                  this.speakingHomeworkForm.surveyLinks[0] === ""){
+                this.speakingHomeworkForm.surveyLinks.splice(0,1);
+                this.speakingHomeworkInfo.surveyLinks.splice(0,1);
+              }
+            }
+            temp.close();
+          })
+          .catch(_err => {
+            ElMessage.error("获取作业信息失败");
+          });
+    },
+    deleteHomework(){
+      this.$apis.deleteHomework(this.$route.params.teacherId,this.$route.params.homeworkId)
+          .then(res => {
+            ElMessage.success("作业已删除");
+
+            this.$router.go(-1);
+          })
+          .catch(_err => {
+            ElMessage.error("删除作业失败");
+          });
+    },
+    edithomework(){
+      if (this.homeworkType === "写作"){
+        this.dialog.update = true;
+      }
+      else{
+        this.speakingDialog.update = true;
+      }
+    },
+    goBack(){
+      this.$router.go(-1);
+    },
+    dateTimeFormatter(da){
+      let date = new Date(da);
+      return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
+    },
+    toEAI(){
+      window.location.href = "http://localhost:4000/";
+    },
+    contentToAHTML(content){
+      return "<a href=\""+ content +"\" target=\"_blank\">"+content+"</a>";
+    },
+    handleClose(){
+      ElMessageBox.confirm("确认吗?你的修改将不会保存。",{
+        confirmButtonText:"确认",
+        cancelButtonText:"取消"
+      })
+          .then(() => {
+            this.dialog.update = false;
+            this.speakingDialog.update = false;
+            this.recoverForm();
+            this.$refs.homeworkFormRef.resetFields();
+            this.$refs.speakingHomeworkFormRef.resetFields();
+            done();
+          })
+          .catch(() => {
+            done();
+          });
+    },
+    recoverForm(){
+      this.homeworkForm.assignmentName = this.homeworkInfo.assignmentName;
+      this.homeworkForm.description = this.homeworkInfo.description;
+      this.homeworkForm.type = this.homeworkInfo.type;
+      this.homeworkForm.descriptionFile = this.homeworkInfo.descriptionFile;
+      this.homeworkForm.attachments = this.homeworkInfo.attachments;
+      this.homeworkForm.timeRange = [this.homeworkInfo.startTime,this.homeworkInfo.endTime];
+
+      this.speakingHomeworkForm.assignmentName = this.speakingHomeworkInfo.assignmentName;
+      this.speakingHomeworkForm.description = this.speakingHomeworkInfo.description;
+      this.speakingHomeworkForm.type = this.speakingHomeworkInfo.type;
+      this.speakingHomeworkForm.descriptionFile = this.speakingHomeworkInfo.descriptionFile;
+      this.speakingHomeworkForm.attachments = this.speakingHomeworkInfo.attachments;
+      this.speakingHomeworkForm.timeRange = [this.speakingHomeworkInfo.startTime,this.speakingHomeworkInfo.endTime];
+      this.speakingHomeworkForm.minDuration = this.speakingHomeworkInfo.minDuration;
+      this.speakingHomeworkForm.minInteraction = this.speakingHomeworkInfo.minInteraction;
+      this.speakingHomeworkForm.surveyLinks = [...this.speakingHomeworkInfo.surveyLinks];
+      this.speakingHomeworkForm.surveyDuration = this.speakingHomeworkInfo.surveyDuration;
+    },
+    updateHomework(){
+      this.$refs.homeworkFormRef.validate((valid)=>{
+        if (valid){
+          // console.log(valid);
+          let homeworkVO = {
+            assignmentName:this.homeworkForm.assignmentName,
+            description:this.homeworkForm.description,
+            type:this.homeworkForm.type,
+            descriptionFile:this.homeworkForm.descriptionFile,
+            attachments:this.homeworkForm.attachments,
+            startTime: this.dateTimeFormatter(this.homeworkForm.timeRange[0]),
+            endTime: this.dateTimeFormatter(this.homeworkForm.timeRange[1]),
+          };
+          // console.log(homeworkVO);
+          this.$apis.updateHomework(this.assignmentId,homeworkVO)
+              .then(res => {
+                console.log( res);
+                if (res && res.data.code === 200){
+                  ElMessage.success("作业修改成功");
+                  this.dialog.update = false;
+                  this.init();
+                }
+                else{
+                  ElMessage.error("作业修改失败");
+                }
+              })
+              .catch(error => {
+                ElMessage.error("作业修改失败");
+              });
+        }
+      });
+    },
+    updateSpeakingHomework(){
+      this.$refs.speakingHomeworkFormRef.validate((valid)=>{
+        if (valid){
+          // console.log(valid);
+          let homeworkVO = {
+            assignmentName:this.speakingHomeworkForm.assignmentName,
+            description:this.speakingHomeworkForm.description,
+            type:this.speakingHomeworkForm.type,
+            descriptionFile:this.speakingHomeworkForm.descriptionFile,
+            attachments:this.speakingHomeworkForm.attachments,
+            startTime: this.dateTimeFormatter(this.speakingHomeworkForm.timeRange[0]),
+            endTime: this.dateTimeFormatter(this.speakingHomeworkForm.timeRange[1]),
+            minDuration:this.speakingHomeworkForm.minDuration,
+            minInteraction:this.speakingHomeworkForm.minInteraction,
+            surveyLinks:this.speakingHomeworkForm.surveyLinks,
+            surveyDuration:this.speakingHomeworkForm.surveyDuration
+          };
+          if (this.speakingHomeworkForm.surveyLinks.length===0){
+            homeworkVO.surveyLinks = [""];
+          }
+          // console.log(homeworkVO);
+          this.$apis.updateSpeakingHomework(this.assignmentId,homeworkVO)
+              .then(res => {
+                console.log( res);
+                if (res && res.data.code === 200){
+                  ElMessage.success("作业修改成功");
+                  this.speakingDialog.update = false;
+                  this.init();
+                }
+                else{
+                  ElMessage.error("作业修改失败");
+                }
+              })
+              .catch(error => {
+                ElMessage.error("作业修改失败");
+              });
+        }
+      });
+    },
+    handleLinkClose(link){
+      this.speakingHomeworkForm.surveyLinks.splice(this.speakingHomeworkForm.surveyLinks.indexOf(link), 1);
+    },
+    handleInputConfirm(){
+      if (this.inputValue) {
+        this.speakingHomeworkForm.surveyLinks.push(this.inputValue);
+      }
+      this.inputVisible = false;
+      this.inputValue = "";
+    },
+    showInput(){
+      this.inputVisible = true;
+      this.$nextTick(() => {
+        this.$refs.InputRef.input.focus();
+      });
+    },
+    checkNumberRange(rule,value,callback,min,max){
+      if (!value){
+        callback();
+        return;
+      }
+      if (Number.isNaN(value)){
+        callback(new Error("请输入整数"));
+        return;
+      }
+      if (Number.isInteger(Number(value))){
+        if (Number(value)>=min && Number(value)<=max){
+          callback();
+        }
+        else{
+          callback(new Error(`请输入${min}~${max}的整数`));
+        }
+      }
+      else{
+        callback(new Error("请输入整数"));
+      }
+    },
+    checkDuration(rule,value,callback){
+      this.checkNumberRange(rule,value,callback,1,120);
+    },
+    checkInteraction(rule,value,callback){
+      this.checkNumberRange(rule,value,callback,1,50);
+    }
+  },
+  computed:{
+    isTeacher() {
+      return this.$store.getters.isTeacher;
+    },
+    isWritingType(){
+      return this.homeworkType === "写作";
+    }
+  },
+};
+</script>
+
+<template>
+  <div style="width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;flex-direction: column">
+    <div style="width: 60%;
+    background-color: #FFFFFF;
+    margin-top: 50px;
+    padding: 20px;
+    border:1px solid var(--el-border-color-light);
+    border-radius: 20px;" >
+      <div
+          class="homework-page-header">
+        <el-icon color="#606266" style="width: 25px;height: 25px;cursor: pointer" @click="goBack">
+          <Back style="width: 25px;height: 25px;"/>
+        </el-icon>
+        <span id="back-title" @click="goBack">
+          返回
+        </span>
+        <el-divider direction="vertical"/>
+        <span style="font-weight: bold">
+          {{isWritingType?homeworkInfo.assignmentName:speakingHomeworkInfo.assignmentName}}
+        </span>
+      </div>
+      <el-descriptions  column="1" >
+        <template v-if="isWritingType">
+          <el-descriptions-item label="作业类型">{{homeworkInfo.type==="speaking"?"AI对话":"写作"}}</el-descriptions-item>
+          <el-descriptions-item label="作业描述">{{homeworkInfo.description}}</el-descriptions-item>
+          <el-descriptions-item label="作业描述文件">无</el-descriptions-item>
+          <el-descriptions-item label="附件">无</el-descriptions-item>
+          <el-descriptions-item label="作业期限">
+            {{(homeworkInfo.startTime.substring(0, 16) + ' ~ ' + homeworkInfo.endTime.substring(0, 16)).replaceAll("T"," ")}}
+          </el-descriptions-item>
+        </template>
+        <template v-else>
+          <el-descriptions-item label="作业类型">{{speakingHomeworkInfo.type==="speaking"?"AI对话":"写作"}}</el-descriptions-item>
+          <el-descriptions-item label="作业描述">{{speakingHomeworkInfo.description}}</el-descriptions-item>
+          <el-descriptions-item label="作业描述文件">无</el-descriptions-item>
+          <el-descriptions-item label="附件">无</el-descriptions-item>
+          <el-descriptions-item label="作业期限">
+            {{(speakingHomeworkInfo.startTime.substring(0, 16) + ' ~ ' + speakingHomeworkInfo.endTime.substring(0, 16)).replaceAll("T"," ")}}
+          </el-descriptions-item>
+          <el-descriptions-item label="最小时长">{{speakingHomeworkInfo.minDuration+"分钟"}}</el-descriptions-item>
+          <el-descriptions-item label="最小交流">{{speakingHomeworkInfo.minInteraction+"分钟"}}</el-descriptions-item>
+          <el-descriptions-item label="问卷链接">
+            <span v-if="speakingHomeworkInfo.surveyLinks.length===0">无</span>
+            <div
+                style="
+              display: flex;
+              gap: 5px;
+              margin-bottom: 5px;
+              max-height: 200px;
+              overflow-y: auto;
+              flex-direction: column;">
+              <el-tag
+                  id="links"
+                  style="width: fit-content;height: auto;margin-left: 70px"
+                  v-for="link in speakingHomeworkInfo.surveyLinks"
+                  :key="link"
+              >
+                <el-tooltip
+                    :content="link"
+                    :disabled="link.length<=90"
+                    :hide-after="150"
+                    placement="top">
+                  <a :href="link" target="_blank">
+                    {{link.substring(0,90)+(link.length>90?'...':'')}}
+                  </a>
+                </el-tooltip>
+              </el-tag>
+            </div>
+          </el-descriptions-item>
+          <el-descriptions-item label="投放间隔">{{speakingHomeworkInfo.surveyDuration?(speakingHomeworkInfo.surveyDuration+"分钟"):"暂未设置"}}</el-descriptions-item>
+        </template>
+      </el-descriptions>
+      <div style="display: flex;flex-direction: row;justify-content: end">
+        <el-button type="success"
+                   title="点击跳转至EAI平台"
+                   @click="toEAI">
+          <div style="height: 100%;display: flex;justify-content: center;align-items: center">
+            EAI平台<el-icon ><Link /></el-icon>
+          </div>
+        </el-button>
+        <el-button type="primary" @click="edithomework" v-if="isTeacher">编辑作业</el-button>
+        <el-button type="danger" @click="dialog.delete = true" v-if="isTeacher">删除作业</el-button>
+      </div>
+    </div>
+  </div>
+  <el-dialog
+      v-model="dialog.update"
+      title="编辑写作作业"
+      width="40%"
+      v-if="isTeacher"
+      :before-close="handleClose"
+  >
+    <el-form :model="homeworkForm"
+             label-width="130px"
+             ref="homeworkFormRef"
+             class="homework-form"
+             label-position="right"
+             :rules="rules"
+    >
+      <el-form-item label="作业名"
+                    prop="assignmentName"
+                    class="homework-form-item">
+        <div style="width: 100%;height: 100%">
+          <el-input class="form-input"
+                    maxlength="50"
+                    show-word-limit
+                    style="width: 100%;"
+                    v-model="homeworkForm.assignmentName" />
+        </div>
+      </el-form-item>
+      <el-form-item label="作业详细描述"
+                    required
+                    prop="description"
+                    class="homework-form-item">
+        <el-input type="textarea"
+                  resize="none"
+                  style="font-size: 20px"
+                  maxlength="200"
+                  show-word-limit
+                  :autosize="{ minRows: 1, maxRows: 10 }"
+                  v-model="homeworkForm.description" />
+      </el-form-item>
+      <el-form-item label="作业类型"
+                    prop="type"
+                    class="homework-form-item">
+        <span>
+          写作
+        </span>
+      </el-form-item>
+      <el-form-item label="作业描述文件"
+                    prop="descriptionFile"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="1"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业附件"
+                    prop="attachment"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="5"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业期限"
+                    prop="timeRange"
+                    class="homework-form-item">
+        <el-date-picker
+            v-model="homeworkForm.timeRange"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="dialog.create = false;this.recoverForm()">取消</el-button>
+        <el-button type="primary" @click="updateHomework">
+          保存
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+  <el-dialog
+      v-model="speakingDialog.update"
+      title="编辑口语作业"
+      width="40%"
+      v-if="isTeacher"
+      :before-close="handleClose"
+  >
+    <el-form :model="speakingHomeworkForm"
+             ref="speakingHomeworkFormRef"
+             class="homework-form"
+             label-width="130px"
+             label-position="right"
+
+             :rules="speakingRules"
+    >
+      <el-form-item label="作业名"
+                    prop="assignmentName"
+                    class="homework-form-item">
+        <div style="width: 100%;height: 100%">
+          <el-input class="form-input"
+                    maxlength="50"
+                    show-word-limit
+                    style="width: 100%"
+                    v-model="speakingHomeworkForm.assignmentName" />
+        </div>
+      </el-form-item>
+      <el-form-item label="作业详细描述"
+                    prop="description"
+                    class="homework-form-item">
+        <el-input type="textarea"
+                  resize="none"
+                  style="font-size: 20px"
+                  maxlength="200"
+                  show-word-limit
+                  :autosize="{ minRows: 1, maxRows: 10 }"
+                  v-model="speakingHomeworkForm.description" />
+      </el-form-item>
+      <el-form-item label="作业类型"
+                    prop="type"
+                    class="homework-form-item">
+        <span>
+          AI口语
+        </span>
+      </el-form-item>
+      <el-form-item label="作业描述文件"
+                    prop="descriptionFile"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="1"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业附件"
+                    prop="attachment"
+                    class="homework-form-item">
+        <el-upload
+            drag
+            action=""
+            show-file-list
+            :auto-upload="false"
+            multiple
+            :limit="5"
+            ref="attachmentUploadRef"
+        >
+          <el-icon :size="50" style="padding-top: 30px" >
+            <upload-filled />
+          </el-icon>
+          <div class="el-upload__text">
+            拖动文件到此处或者 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="作业期限"
+                    prop="timeRange"
+                    class="homework-form-item">
+        <el-date-picker
+            v-model="speakingHomeworkForm.timeRange"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+        />
+      </el-form-item>
+      <el-form-item label="最少时长"
+                    class="homework-form-item"
+                    prop="minDuration">
+        <div style="width: 100%;height: 100%;">
+          <el-input v-model="speakingHomeworkForm.minDuration"
+                    style="width: 100%"
+                    placeholder="请输入最少分钟数">
+            <template #append>
+              分钟
+            </template>
+          </el-input>
+        </div>
+      </el-form-item>
+      <el-form-item label="最少交流"
+                    class="homework-form-item"
+                    prop="minInteraction">
+        <el-input v-model="speakingHomeworkForm.minInteraction"
+                  style="width: 100%"
+                  placeholder="请输入最少交流数">
+          <template #append>
+            次
+          </template>
+        </el-input>
+      </el-form-item>
+      <el-form-item label="问卷链接"
+                    class="homework-form-item"
+                    prop="surveyLinks">
+        <div style="display: flex;flex-direction: column;">
+          <transition-group name="list" tag="links"
+                            style="
+                            display: flex;
+                            gap: 5px;
+                            margin-bottom: 5px;
+                            max-height: 200px;
+                            overflow-y: auto;
+                            flex-direction: column;">
+
+            <el-tag
+                id="links"
+                style="width: fit-content;height: auto"
+                v-for="link in speakingHomeworkForm.surveyLinks"
+                :key="link"
+                closable
+                :disable-transitions="true"
+                @close="handleLinkClose(link)"
+            >
+              <el-tooltip
+                  :content="link"
+                  :disabled="link.length<=40"
+                  :hide-after="150"
+                  placement="top">
+                <span>{{link.substring(0,40)+(link.length>40?'...':'')}}</span>
+              </el-tooltip>
+            </el-tag>
+          </transition-group>
+          <el-input
+              v-if="inputVisible"
+              ref="InputRef"
+              v-model="inputValue"
+              class="w-20"
+              size="small"
+              @keyup.enter="handleInputConfirm"
+              @blur="handleInputConfirm"
+          />
+          <el-button v-else class="button-new-tag" size="small" plain type="primary" @click="showInput">
+            + 添加问卷链接
+          </el-button>
+        </div>
+      </el-form-item>
+      <el-form-item label="问卷投放间隔"
+                    class="homework-form-item"
+                    prop="surveyDuration">
+
+        <el-input v-model="speakingHomeworkForm.surveyDuration"
+                  style="width: 100%"
+                  placeholder="请输入问卷投放间隔">
+          <template #append>
+            分钟
+          </template>
+        </el-input>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="speakingDialog.update = false;this.recoverForm()">取消</el-button>
+        <el-button type="primary" @click="updateSpeakingHomework">
+          保存
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+  <el-dialog
+      width="400px"
+      title="警告"
+
+      v-model="dialog.delete">
+    <span style="font-size: 15px">确认删除该作业吗</span>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="dialog.delete = false" type="danger">取消</el-button>
+        <el-button type="primary" @click="dialog.delete = false;deleteHomework()">
+          确认
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<style scoped>
+.homework-page-header{
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  justify-content: start;
+  align-items: center;
+  margin-bottom: 20px;
+  gap: 5px
+}
+#back-title{
+  color: var(--el-text-color-regular)
+}
+#back-title:hover{
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+</style>

+ 6 - 5
src/views/ManagePage/ManagePage.vue

@@ -249,9 +249,9 @@
         <el-form-item label="Eval课程">
           <el-switch v-model="form.evalLink" :disabled="switchDisabled" active-text="创建" inactive-text="不创建"/>
         </el-form-item>
-        <el-form-item label="Eai课程">
-          <el-switch v-model="form.eaiLink" :disabled="switchDisabled" active-text="创建" inactive-text="不创建"/>
-        </el-form-item>
+<!--        <el-form-item label="Eai课程">-->
+<!--          <el-switch v-model="form.eaiLink" :disabled="switchDisabled" active-text="创建" inactive-text="不创建"/>-->
+<!--        </el-form-item>-->
       </el-form>
       <div style="margin: auto;width: fit-content">
         <el-button type="primary" @click="handleFormConfirm">确定</el-button>
@@ -540,7 +540,6 @@ export default {
         eaiLink: false,
         coderLink: false,
         evalLink: false,
-        eaiLink:false
       },
       examForm: {
         examType: "coder",
@@ -705,6 +704,7 @@ export default {
       const _this = this;
       _this.$refs.formRef.validate((valid) => {
         if (valid) {
+          console.log(_this.form);
           if (_this.form.id === null) {
             // 首先创建课程
             // 之后上传课程图片
@@ -725,9 +725,10 @@ export default {
               eaiId: _this.form.eaiLink ? -1 : null,
               coderId: _this.form.coderLink ? -1 : null,
               evalId: _this.form.evalLink ? -1 : null,
-              eaiId:_this.form.eaiLink ? -1 : null
+              eaiId: _this.form.eaiLink ? -1 : null,
             }).then(async (res) => {
               const createdCourse = res.data.data;
+              console.log(res);
               if (createdCourse) {
                 if (_this.form.fileList.length !== 0 && this.form.fileList[0].raw) {
                   const imageUrl = (await _this.$apis.uploadCourseImage(createdCourse.name, this.form.fileList[0].raw)).data.data;

+ 26 - 7
src/views/PortalPage/PortalPage.css

@@ -1,10 +1,10 @@
 #portal-body {
-    height: 100%;
-    display: grid;
-    grid-template-columns: 1fr 2.5fr;
-    grid-template-rows: 210px auto auto;
-    grid-gap: 12px 12px;
-    padding: 10px 10vw 80px 10vw;
+    height: fit-content;
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 10px;
 }
 
 .portal-card-title {
@@ -55,4 +55,23 @@
     font-size: small;
     font-weight: bold;
     color: #666666;
-}
+}
+
+#portal-banner {
+    width: 100%;
+}
+
+#hot-course-title {
+    font-size: 25px;
+    color: var(--el-text-color-primary);
+    font-weight: bold;
+}
+.hot-course-container {
+    width: 65%;
+    height: auto;
+    display: flex;
+    justify-content: space-between;
+    flex-direction: row;
+    align-items: center;
+    margin-bottom: 500px;
+}

+ 208 - 125
src/views/PortalPage/PortalPage.vue

@@ -1,7 +1,8 @@
 <template>
 
-  <PhotoChange></PhotoChange>
+  <!--  <PhotoChange></PhotoChange>-->
   <div id="portal-body">
+<<<<<<< HEAD
 
     <!-- 去除轮播组件 -->
     <!--<CarouselCard style="grid-column-start: 1; grid-column-end: 3;"/>-->
@@ -54,116 +55,178 @@
           </div>
         </div>
       </el-card>
+=======
+    <!--  <img src="@/assets/seec-banner.png" id="portal-banner" alt="contain"/>-->
+    <span id="hot-course-title">
+    热门课程
+  </span>
+    <div class="hot-course-container">
+      <div  v-for="course in hotCourseList" :key="course.title">
+        <HotCourseCard :courseVO="course"/>
+      </div>
+>>>>>>> 1094909 (feat:作业模块增删查改)
     </div>
+    <!-- 去除轮播组件 -->
+    <!--    <CarouselCard style="grid-column-start: 1; grid-column-end: 3;"/>-->
 
-    <div>
-      <el-card id="portal-course-card-main" :style="{height: this.courseRowHeight + 'px'}">
-        <div class="portal-card-title">
-          <div class="portal-card-title-main">精选课程</div>
-          <el-pagination v-if="isExpanding && exampleCourseList.length > 6"
-                         :page-size="6"
-                         :total="exampleCourseList.length"
-                         @current-change="handleCourseCurrentChange"
-                         layout="prev, pager, next"
-                         style="width: 200px; margin: 0 auto 0 auto"/>
-          <ElButton plain round type="primary" @click="toCourse">前往课程广场</ElButton>
-        </div>
-        <el-divider class="portal-card-divider"/>
-        <div class="portal-card-content" v-if="showExampleCourseList.length !== 0">
-          <CourseCard v-for="item in showExampleCourseList" :key="item.id" :courseVO="item"/>
-        </div>
-        <div v-else :style="{marginTop: (this.courseRowHeight / 2 - 100) + 'px'}" style="margin-left: calc(50% - 36px)">
-          <el-icon :size="72" color="#bfbfbf">
-            <FolderOpened/>
-          </el-icon>
-          <div class="tip">暂无数据~</div>
-        </div>
-      </el-card>
-    </div>
+    <!--    <div>-->
+    <!--      <el-card :style="{height: this.courseRowHeight + 'px'}">-->
+    <!--        <div class="portal-card-title">-->
+    <!--          <div class="portal-card-title-main">我的课程</div>-->
+    <!--          <div style="font-size: 10px; color: #777777" v-if="myCourseList.length>6">可滚动查看课程列表</div>-->
+    <!--        </div>-->
+    <!--        <el-divider class="portal-card-divider"/>-->
+    <!--        <div>-->
 
-    <div>
-      <el-card :style="{height: this.testRowHeight + 'px'}">
-        <div class="portal-card-title">
-          <div class="portal-card-title-main">我的测试</div>
-          <div style="font-size: 10px; color: #777777" v-if="myTestList.length>6">可滚动查看测试列表</div>
-        </div>
-        <el-divider class="portal-card-divider"/>
-        <div>
-          <div v-if="!isLogin"
-               :style="{height: (this.testRowHeight - 63) + 'px', marginTop: (this.testRowHeight / 2 - 100) + 'px'}"
-               class="portal-card-main" style="cursor: pointer"
-               @click="toAuth">
-            <el-icon :size="72" color="#bfbfbf">
-              <Lock/>
-            </el-icon>
-            <div class="tip">登录以查看</div>
-          </div>
+    <!--          <div v-if="!isLogin"-->
+    <!--               :style="{height: (this.courseRowHeight - 63) + 'px', marginTop: (this.courseRowHeight / 2 - 100) + 'px'}"-->
+    <!--               class="portal-card-main" style="cursor: pointer"-->
+    <!--               @click="toAuth">-->
+    <!--            <el-icon :size="72" color="#bfbfbf">-->
+    <!--              <Lock/>-->
+    <!--            </el-icon>-->
+    <!--            <div class="tip">登录以查看</div>-->
+    <!--          </div>-->
 
-          <div v-if="isLogin && isStudent && myTestList!==null && myTestList.length!==0"
-               :style="{height: (this.testRowHeight - 63) + 'px'}"
-               class="portal-card-main">
-            <div class="portal-my-card-main">
-              <MyTestCard v-for="test in myTestList" :key="test.id" :testVO="test"/>
-            </div>
-          </div>
+    <!--          <div v-if="isLogin && isStudent && myCourseList.length!==0"-->
+    <!--               :style="{height: (this.courseRowHeight - 63) + 'px'}"-->
+    <!--               class="portal-card-main">-->
+    <!--            <div class="portal-my-card-main" ref="a">-->
+    <!--              <MyCourseCard v-for="course in myCourseList" :key="course.id" :courseVO="course"/>-->
+    <!--            </div>-->
+    <!--          </div>-->
 
-          <div v-if="isLogin && isStudent && myTestList!==null && myTestList.length===0"
-               :style="{height: (this.testRowHeight - 63) + 'px', marginTop: (this.testRowHeight / 2 - 100) + 'px'}"
-               class="portal-card-main" style="cursor: pointer"
-               @click="toTest">
-            <el-icon :size="72" color="#bfbfbf">
-              <ShoppingCart/>
-            </el-icon>
-            <div class="tip">去参加测试</div>
-          </div>
+    <!--          <div v-if="isLogin && isStudent && myCourseList.length===0"-->
+    <!--               :style="{height: (this.courseRowHeight - 63) + 'px', marginTop: (this.courseRowHeight / 2 - 100) + 'px'}"-->
+    <!--               class="portal-card-main" style="cursor: pointer"-->
+    <!--               @click="toCourse">-->
+    <!--            <el-icon :size="72" color="#bfbfbf">-->
+    <!--              <ShoppingCart/>-->
+    <!--            </el-icon>-->
+    <!--            <div class="tip">去选择课程</div>-->
+    <!--          </div>-->
 
-          <div v-show="isLogin && isTeacher"
-               :style="{height: (this.testRowHeight - 63) + 'px', marginTop: (this.testRowHeight / 2 - 100) + 'px'}"
-               class="portal-card-main" style="cursor: pointer"
-               @click="toManage">
-            <el-icon :size="72" color="#bfbfbf">
-              <SetUp/>
-            </el-icon>
-            <div class="tip">去管理测试</div>
-          </div>
-        </div>
+    <!--          <div v-show="isLogin && isTeacher"-->
+    <!--               :style="{height: (this.courseRowHeight - 63) + 'px', marginTop: (this.courseRowHeight / 2 - 100) + 'px'}"-->
+    <!--               class="portal-card-main" style="cursor: pointer"-->
+    <!--               @click="toManage">-->
+    <!--            <el-icon :size="72" color="#bfbfbf">-->
+    <!--              <SetUp/>-->
+    <!--            </el-icon>-->
+    <!--            <div class="tip">去管理课程</div>-->
+    <!--          </div>-->
+    <!--        </div>-->
+    <!--      </el-card>-->
+    <!--    </div>-->
 
-      </el-card>
-    </div>
+    <!--    <div>-->
+    <!--      <el-card id="portal-course-card-main" :style="{height: this.courseRowHeight + 'px'}">-->
+    <!--        <div class="portal-card-title">-->
+    <!--          <div class="portal-card-title-main">精选课程</div>-->
+    <!--          <el-pagination v-if="isExpanding && exampleCourseList.length > 6"-->
+    <!--                         :page-size="6"-->
+    <!--                         :total="exampleCourseList.length"-->
+    <!--                         @current-change="handleCourseCurrentChange"-->
+    <!--                         layout="prev, pager, next"-->
+    <!--                         style="width: 200px; margin: 0 auto 0 auto"/>-->
+    <!--          <ElButton plain round type="primary" @click="toCourse">前往课程广场</ElButton>-->
+    <!--        </div>-->
+    <!--        <el-divider class="portal-card-divider"/>-->
+    <!--        <div class="portal-card-content" v-if="showExampleCourseList.length !== 0">-->
+    <!--          <CourseCard v-for="item in showExampleCourseList" :key="item.id" :courseVO="item"/>-->
+    <!--        </div>-->
+    <!--        <div v-else :style="{marginTop: (this.courseRowHeight / 2 - 100) + 'px'}" style="margin-left: calc(50% - 36px)">-->
+    <!--          <el-icon :size="72" color="#bfbfbf">-->
+    <!--            <FolderOpened/>-->
+    <!--          </el-icon>-->
+    <!--          <div class="tip">暂无数据~</div>-->
+    <!--        </div>-->
+    <!--      </el-card>-->
+    <!--    </div>-->
 
-    <div>
-      <el-card id="portal-test-card-main" ref="portalTestCardBodyMain">
-        <div class="portal-card-title">
-          <div class="portal-card-title-main">智能测试</div>
-          <ElButton plain round type="primary" @click="toTest">前往测试广场</ElButton>
-        </div>
-        <el-divider class="portal-card-divider"/>
-        <el-tabs v-model="activeTestPaneName" @tab-click="changeTestPane" style="min-height: 400px;">
-          <el-tab-pane label="评测题" name="eval">
-            <div class="portal-card-content" v-if="showExampleEvalTestList.length !== 0">
-              <TestCard v-for="item in showExampleEvalTestList" :key="item.id" :testVO="item" :isPublicTest="true"/>
-            </div>
-            <div v-else style="margin: 100px 0 0 calc(50% - 36px);">
-              <el-icon :size="72" color="#bfbfbf">
-                <FolderOpened/>
-              </el-icon>
-              <div class="tip">暂无数据~</div>
-            </div>
-          </el-tab-pane>
-          <el-tab-pane label="代码题" name="coder">
-            <div class="portal-card-content" v-if="showExampleCoderTestList.length !== 0">
-              <TestCard v-for="item in showExampleCoderTestList" :key="item.id" :testVO="item" :isPublicTest="true"/>
-            </div>
-            <div v-else style="margin: 100px 0 0 calc(50% - 36px);">
-              <el-icon :size="72" color="#bfbfbf">
-                <FolderOpened/>
-              </el-icon>
-              <div class="tip">暂无数据~</div>
-            </div>
-          </el-tab-pane>
-        </el-tabs>
-      </el-card>
-    </div>
+    <!--    <div>-->
+    <!--      <el-card :style="{height: this.testRowHeight + 'px'}">-->
+    <!--        <div class="portal-card-title">-->
+    <!--          <div class="portal-card-title-main">我的测试</div>-->
+    <!--          <div style="font-size: 10px; color: #777777" v-if="myTestList.length>6">可滚动查看测试列表</div>-->
+    <!--        </div>-->
+    <!--        <el-divider class="portal-card-divider"/>-->
+    <!--        <div>-->
+    <!--          <div v-if="!isLogin"-->
+    <!--               :style="{height: (this.testRowHeight - 63) + 'px', marginTop: (this.testRowHeight / 2 - 100) + 'px'}"-->
+    <!--               class="portal-card-main" style="cursor: pointer"-->
+    <!--               @click="toAuth">-->
+    <!--            <el-icon :size="72" color="#bfbfbf">-->
+    <!--              <Lock/>-->
+    <!--            </el-icon>-->
+    <!--            <div class="tip">登录以查看</div>-->
+    <!--          </div>-->
+
+    <!--          <div v-if="isLogin && isStudent && myTestList!==null && myTestList.length!==0"-->
+    <!--               :style="{height: (this.testRowHeight - 63) + 'px'}"-->
+    <!--               class="portal-card-main">-->
+    <!--            <div class="portal-my-card-main">-->
+    <!--              <MyTestCard v-for="test in myTestList" :key="test.id" :testVO="test"/>-->
+    <!--            </div>-->
+    <!--          </div>-->
+
+    <!--          <div v-if="isLogin && isStudent && myTestList!==null && myTestList.length===0"-->
+    <!--               :style="{height: (this.testRowHeight - 63) + 'px', marginTop: (this.testRowHeight / 2 - 100) + 'px'}"-->
+    <!--               class="portal-card-main" style="cursor: pointer"-->
+    <!--               @click="toTest">-->
+    <!--            <el-icon :size="72" color="#bfbfbf">-->
+    <!--              <ShoppingCart/>-->
+    <!--            </el-icon>-->
+    <!--            <div class="tip">去参加测试</div>-->
+    <!--          </div>-->
+
+    <!--          <div v-show="isLogin && isTeacher"-->
+    <!--               :style="{height: (this.testRowHeight - 63) + 'px', marginTop: (this.testRowHeight / 2 - 100) + 'px'}"-->
+    <!--               class="portal-card-main" style="cursor: pointer"-->
+    <!--               @click="toManage">-->
+    <!--            <el-icon :size="72" color="#bfbfbf">-->
+    <!--              <SetUp/>-->
+    <!--            </el-icon>-->
+    <!--            <div class="tip">去管理测试</div>-->
+    <!--          </div>-->
+    <!--        </div>-->
+
+    <!--      </el-card>-->
+    <!--    </div>-->
+
+    <!--    <div>-->
+    <!--      <el-card id="portal-test-card-main" ref="portalTestCardBodyMain">-->
+    <!--        <div class="portal-card-title">-->
+    <!--          <div class="portal-card-title-main">智能测试</div>-->
+    <!--          <ElButton plain round type="primary" @click="toTest">前往测试广场</ElButton>-->
+    <!--        </div>-->
+    <!--        <el-divider class="portal-card-divider"/>-->
+    <!--        <el-tabs v-model="activeTestPaneName" @tab-click="changeTestPane" style="min-height: 400px;">-->
+    <!--          <el-tab-pane label="评测题" name="eval">-->
+    <!--            <div class="portal-card-content" v-if="showExampleEvalTestList.length !== 0">-->
+    <!--              <TestCard v-for="item in showExampleEvalTestList" :key="item.id" :testVO="item" :isPublicTest="true"/>-->
+    <!--            </div>-->
+    <!--            <div v-else style="margin: 100px 0 0 calc(50% - 36px);">-->
+    <!--              <el-icon :size="72" color="#bfbfbf">-->
+    <!--                <FolderOpened/>-->
+    <!--              </el-icon>-->
+    <!--              <div class="tip">暂无数据~</div>-->
+    <!--            </div>-->
+    <!--          </el-tab-pane>-->
+    <!--          <el-tab-pane label="代码题" name="coder">-->
+    <!--            <div class="portal-card-content" v-if="showExampleCoderTestList.length !== 0">-->
+    <!--              <TestCard v-for="item in showExampleCoderTestList" :key="item.id" :testVO="item" :isPublicTest="true"/>-->
+    <!--            </div>-->
+    <!--            <div v-else style="margin: 100px 0 0 calc(50% - 36px);">-->
+    <!--              <el-icon :size="72" color="#bfbfbf">-->
+    <!--                <FolderOpened/>-->
+    <!--              </el-icon>-->
+    <!--              <div class="tip">暂无数据~</div>-->
+    <!--            </div>-->
+    <!--          </el-tab-pane>-->
+    <!--        </el-tabs>-->
+    <!--      </el-card>-->
+    <!--    </div>-->
   </div>
 </template>
 
@@ -175,21 +238,24 @@ import MyTestCard from "@/views/PortalPage/components/MyTestCard";
 import { FolderOpened, Lock, SetUp, ShoppingCart } from "@element-plus/icons-vue";
 import TestCard from "@/components/TestCard";
 import PhotoChange from "@/views/PortalPage/components/PhotoChange";
+import HotCourseCard from "@/views/PortalPage/components/HotCourseCard.vue";
 
 export default {
   //数据
   name: "PortalPage",
   components: {
-    FolderOpened,
-    TestCard,
-    SetUp,
-    ShoppingCart,
-    Lock,
-    MyTestCard,
-    CourseCard,
-    // CarouselCard,
-    MyCourseCard,
-    PhotoChange
+    HotCourseCard
+    // HotCourseCard
+    // FolderOpened,
+    // TestCard,
+    // SetUp,
+    // ShoppingCart,
+    // Lock,
+    // MyTestCard,
+    // CourseCard,
+    // // CarouselCard,
+    // MyCourseCard,
+    // PhotoChange
   },
   data() {
     return {
@@ -204,6 +270,23 @@ export default {
       myCoderExamList: [],
       courseRowHeight: 360,
       testRowHeight: 300,
+      hotCourseList:[{
+        title:"基于Java的面向对象编程范式",
+        img:"1",
+        belong:"南京大学",
+        desc:"了解编程基本思想,养成编程基本习惯,理解面向对象思想。"
+      },{
+        title:"软件工程与计算Ⅰ",
+        img:"2",
+        belong:"南京大学",
+        desc: "通过Scheme、Python和Java语言,让你分别体会函\n" +
+            "数式编程范式、结构化编程范式和面向对象编程范式"
+      },{
+        title:"软件工程与计算Ⅱ",
+        img:"3",
+        belong:"南京大学",
+        desc: "基于DevOps培养团队开发中小规模软件系统的能力"
+      }]
     };
   },
   computed: {
@@ -221,26 +304,26 @@ export default {
     },
     myTestList() {
       return []
-        .concat(this.myEvalExamList)
-        .concat(this.myCoderExamList)
-        .sort((a, b) => {
-          let a_time = a["startAt"] * 1000 || new Date(a.startTime).getTime();
-          let b_time = b["startAt"] * 1000 || new Date(b.startTime).getTime();
-          return a_time - b_time;
-        });
+          .concat(this.myEvalExamList)
+          .concat(this.myCoderExamList)
+          .sort((a, b) => {
+            let a_time = a["startAt"] * 1000 || new Date(a.startTime).getTime();
+            let b_time = b["startAt"] * 1000 || new Date(b.startTime).getTime();
+            return a_time - b_time;
+          });
     },
     showExampleCourseList() {
       return this.exampleCourseList.slice((this.showCourseList_current - 1) * 6, this.showCourseList_current * 6);
     },
     showExampleCoderTestList() {
       return this.exampleCoderTestList
-        // .filter(i => this.state(i) === "RUNNING")
-        .slice(0, 6);
+          // .filter(i => this.state(i) === "RUNNING")
+          .slice(0, 6);
     },
     showExampleEvalTestList() {
       return this.exampleEvalTestList
-        // .filter(i => this.state(i) === "RUNNING")
-        .slice(0, 6);
+          // .filter(i => this.state(i) === "RUNNING")
+          .slice(0, 6);
     },
   },
 
@@ -250,6 +333,7 @@ export default {
   },
   beforeUnmount() {
     window.removeEventListener("scroll", this.handleResize,);
+    this.hotCourseList.push("test");
   },
   async mounted() {
     const that = this;
@@ -257,7 +341,6 @@ export default {
     this.exampleCourseList = (await this.$apis.getExampleCourses()).data.data;
     this.exampleEvalTestList = (await this.$apis.getRecommendEvalExams()).data.data;
     this.exampleCoderTestList = (await this.$apis.getRecommendCoderExams({})).data.data;
-
     if (this.isLogin && this.isStudent) {
       const res = await this.$apis.getStudentCourses();
       console.log(res);

+ 85 - 0
src/views/PortalPage/components/HotCourseCard.vue

@@ -0,0 +1,85 @@
+<script>
+ import javaImg from "@/assets/carousel-java.png";
+ import se1Img from "@/assets/course-SE1.png";
+ import se2Img from "@/assets/carousel-SE2.png";
+ import router from "@/router";
+ export default {
+   name:"HotCourseCard",
+   props:{
+     courseVO:Object
+   },
+   data() {
+     return {
+       imgMap: {
+         "1": javaImg,
+         "2":se1Img,
+         "3":se2Img,
+       }
+     };
+   },
+ };
+</script>
+<template>
+  <div id="hot-course-card"
+       @click="this.$router.push('/course');">
+    <img :src="imgMap[courseVO.img]"
+         alt="fill"
+         id="course-img"/>
+    <span id="course-title">
+      {{courseVO.title}}
+    </span>
+    <span id="course-info">
+      {{courseVO.belong}}
+    </span>
+    <span id="course-info" style="margin-top: 5px;color: var(--el-text-color-primary)">
+      {{courseVO.desc}}
+    </span>
+  </div>
+</template>
+
+<style scoped>
+#course-img {
+  width: 100%;
+  height: 125px;
+}
+
+#hot-course-card {
+  width: 250px;
+  aspect-ratio: 1;
+  border-radius: 0;
+  padding: unset;
+  display: flex;
+  flex-direction: column;
+  justify-content: start;
+  background-color: white;
+  border: 1px solid var(--el-border-color-light);
+  transition: all 0.4s ease-in-out;
+}
+
+#hot-course-card:hover {
+  transform: scale(1.04);
+  box-shadow: var(--el-box-shadow-light);
+  cursor: pointer;
+}
+
+#course-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: var(--el-text-color-primary);
+  padding: 10px 10px 0 10px;
+}
+
+#course-title:hover {
+  color: var(--el-color-primary);
+}
+
+#course-title:active {
+  color: var(--el-color-primary-light-3);
+}
+
+#course-info {
+  font-size: 14px;
+  color: var(--el-text-color-secondary);
+  padding: 0 10px 0 10px;
+}
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 409 - 317
yarn.lock


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff