Pārlūkot izejas kodu

fix:冲突解决

廖彦舟 1 gadu atpakaļ
vecāks
revīzija
89bb5c4283
40 mainītis faili ar 6243 papildinājumiem un 394 dzēšanām
  1. 6 0
      package-lock.json
  2. 1 0
      package.json
  3. 17 1
      src/apis/assignment.ts
  4. 70 0
      src/apis/class.ts
  5. 8 0
      src/apis/course.ts
  6. 3 2
      src/apis/engagement.ts
  7. 3 1
      src/apis/file.ts
  8. 2 1
      src/apis/index.ts
  9. 1 0
      src/assets/add.svg
  10. 1 0
      src/assets/chevron-right.svg
  11. 1 0
      src/assets/people.svg
  12. 1 0
      src/assets/plus-square.svg
  13. 1 0
      src/assets/search.svg
  14. 1 0
      src/assets/time.svg
  15. 22 3
      src/layout/Header/Header.vue
  16. 5 3
      src/layout/Header/index.scss
  17. 35 3
      src/router/index.ts
  18. 19 0
      src/types/dto.ts
  19. 33 0
      src/types/vo.ts
  20. 44 10
      src/views/AiSpeakingPage/SpeakingPage.vue
  21. 68 15
      src/views/AiSpeakingPage/SpeakingRecord.vue
  22. 198 0
      src/views/ClassPage/ClassDetail.vue
  23. 574 0
      src/views/ClassPage/ClassHomework.vue
  24. 480 0
      src/views/ClassPage/ClassStudent.vue
  25. 421 0
      src/views/ClassPage/CourseClass.vue
  26. 12 0
      src/views/ClassPage/StudentClass.vue
  27. 531 0
      src/views/ClassPage/StudentDetail.vue
  28. 54 0
      src/views/ClassPage/components/classHeader.vue
  29. 50 0
      src/views/ClassPage/components/classTable.vue
  30. 62 0
      src/views/ClassPage/components/classToggle.vue
  31. 598 53
      src/views/CorrectPage/CorrectPage.vue
  32. 875 137
      src/views/CorrectPage/CoursePage.vue
  33. 996 1
      src/views/CorrectPage/index.scss
  34. 30 2
      src/views/CourseSquare/component/CourseDetails/index.vue
  35. 23 3
      src/views/EditPage/EditPage.vue
  36. 8 13
      src/views/Home/component/Edit/index.vue
  37. 13 4
      src/views/Home/component/Remark/Remark.vue
  38. 2 1
      src/views/Home/component/Silder/index.scss
  39. 112 12
      src/views/HomeworkPage/component/HomeworkDetail.vue
  40. 862 129
      src/views/HomeworkPage/index.vue

+ 6 - 0
package-lock.json

@@ -21,6 +21,7 @@
         "mitt": "^3.0.1",
         "pinia": "^2.1.7",
         "pinia-plugin-persistedstate": "^3.2.1",
+        "spark-md5": "^3.0.2",
         "vue": "^3.4.29",
         "vue-demi": "^0.14.10",
         "vue-router": "^4.3.3",
@@ -3953,6 +3954,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/spark-md5": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz",
+      "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
+    },
     "node_modules/ssf": {
       "version": "0.11.2",
       "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "mitt": "^3.0.1",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
+    "spark-md5": "^3.0.2",
     "vue": "^3.4.29",
     "vue-demi": "^0.14.10",
     "vue-router": "^4.3.3",

+ 17 - 1
src/apis/assignment.ts

@@ -33,6 +33,15 @@ const assignmentApis = {
             }
         })
     },
+    // 新增:获取学生在特定课程下的作业状态
+    getStudentAssignmentsByCourse(studentId: string, courseId: string) {
+        return axiosInstance.get("/api/course/student/assignments", {
+            params: {
+                studentId,
+                courseId
+            }
+        })
+    },
     updateAssignment(assignmentId:number, assignmentDTO:AssignmentDTO){
         return axiosInstance.post(`${ASSIGNMENT_PREFIX}/update`, assignmentDTO, {
             params: {
@@ -47,7 +56,14 @@ const assignmentApis = {
                 assignmentId
             }
         })
-
+    },
+    // 新增:查询某班级某作业的情况
+    getAssignmentStatusByClass: (classId: string, assignmentId: string) => {
+        return axiosInstance.get(`${ASSIGNMENT_PREFIX}/class/${classId}/status`, {
+            params: {
+                assignmentId
+            }
+        })
     }
 
 }

+ 70 - 0
src/apis/class.ts

@@ -0,0 +1,70 @@
+import axiosInstance from "@/apis/axios.config";
+//提交的后端路径
+const ENGAGE_PREFIX = "/api/class"
+
+const classApis = {
+    getClassOfCourse:(courseId: number) => {
+        return axiosInstance.get(ENGAGE_PREFIX+"/select",{
+            params:{
+                courseId
+            }
+        })
+    },
+    createClassOfCourse:(courseId:number,className:string,classTime:string,teacherId:number) => {
+        return axiosInstance.post(ENGAGE_PREFIX+"/create",{
+            courseId,
+            className,
+            classTime,
+            teacherId
+        })
+    },
+    deleteClass:(classId:number) => {
+        return axiosInstance.delete(ENGAGE_PREFIX+"/delete",{
+            params:{
+                classId
+            }
+        })
+    },
+    getStudentsOfClass:(classId:number) => {
+        return axiosInstance.get(ENGAGE_PREFIX+`/${classId}`)
+    },
+    addStudentOfClasss:(classId:number,officialNumber:number) => {
+        return axiosInstance.post(ENGAGE_PREFIX+`/${classId}/students`,{},{
+            params:{
+                officialNumber
+            }
+        })
+    },
+    deleteStudentOfClass:(classId:number,officialNumbers) => {
+        return axiosInstance.delete(ENGAGE_PREFIX+`/${classId}/students/batch`,{
+            headers:{
+                'Content-Type': 'application/json'
+            },
+            data: officialNumbers
+        })
+    },
+    getClassStatesOfCourse:(classId:number,assignmentId:number) => {
+        return axiosInstance.get(`/api/assignment/class/${classId}/status`,{
+            params:{
+                assignmentId
+            }
+        })
+    },
+    getHomeworkOfStudent:(courseId:number,studentId:number) => {
+        return axiosInstance.get(`/api/course/student/assignments`,{
+            params:{
+                studentId,
+                courseId
+            }
+        })
+    },
+    getClassByClassId:(classId:number) => {
+        return axiosInstance.get(`/api/class/find/${classId}`)
+    },
+    getClassNameOfStudent:(courseId:number,studentId:number) => {
+        return axiosInstance.get(ENGAGE_PREFIX+`/student/${studentId}/course/${courseId}`)
+    }
+}
+
+
+export default classApis

+ 8 - 0
src/apis/course.ts

@@ -82,6 +82,14 @@ const courseApis = {
             }
         })
     },
+    // 新增:查询某课程的所有班级
+    getClassesByCourse: (courseId: number) => {
+        return axiosInstance.get(`/api/class/select`, {
+            params: {
+                courseId
+            }
+        })
+    },
 }
 
 export default courseApis

+ 3 - 2
src/apis/engagement.ts

@@ -27,11 +27,12 @@ const engagementApis = {
         })
     },
     // 提交作业
-    engagementSubmit: (studentId: number, assignmentId: number) => {
+    engagementSubmit: (studentId: number, assignmentId: number,supplementarySubmission:boolean) => {
         return axiosInstance.put(ENGAGE_PREFIX+"/submit", {}, {
             params: {
                 studentId,
-                assignmentId
+                assignmentId,
+                supplementarySubmission
             }
         })
     },

+ 3 - 1
src/apis/file.ts

@@ -1,3 +1,4 @@
+
 import axiosInstance from "@/apis/axios.config";
 
 const FILE_PREFIX = "/api/file"
@@ -12,7 +13,8 @@ const fileApis = {
             },
             timeout:300000 // 添加超时时间等待
         });
-    },
+    }
 }
 
+
 export default fileApis

+ 2 - 1
src/apis/index.ts

@@ -11,7 +11,7 @@ import evaluationApis from "@/apis/evaluation";
 import emailApis from "@/apis/email";
 import authApis from "@/apis/auth";
 import bevaviorApis from "@/apis/behavior";
-
+import classApis from "@/apis/class";
 
 const apis = {
     ...authApis,
@@ -27,6 +27,7 @@ const apis = {
     ...evaluationApis,
     ...emailApis,
     ...bevaviorApis,
+    ...classApis,
 }
 
 // hook

+ 1 - 0
src/assets/add.svg

@@ -0,0 +1 @@
+<svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" viewBox="0 0 24 24" aria-labelledby="addIconTitle" stroke="#4a90c4" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#4a90c4"> <title id="addIconTitle">Add</title> <path d="M17 12L7 12M12 17L12 7"/> <circle cx="12" cy="12" r="10"/> </svg>

+ 1 - 0
src/assets/chevron-right.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#b8b8b8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>

+ 1 - 0
src/assets/people.svg

@@ -0,0 +1 @@
+<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24" aria-labelledby="peopleIconTitle" stroke="#4a90c4" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#4a90c4"> <title id="peopleIconTitle">People</title> <path d="M1 18C1 15.75 4 15.75 5.5 14.25 6.25 13.5 4 13.5 4 9.75 4 7.25025 4.99975 6 7 6 9.00025 6 10 7.25025 10 9.75 10 13.5 7.75 13.5 8.5 14.25 10 15.75 13 15.75 13 18M12.7918114 15.7266684C13.2840551 15.548266 13.6874862 15.3832994 14.0021045 15.2317685 14.552776 14.9665463 15.0840574 14.6659426 15.5 14.25 16.25 13.5 14 13.5 14 9.75 14 7.25025 14.99975 6 17 6 19.00025 6 20 7.25025 20 9.75 20 13.5 17.75 13.5 18.5 14.25 20 15.75 23 15.75 23 18"/> <path stroke-linecap="round" d="M12,16 C12.3662741,15.8763472 12.6302112,15.7852366 12.7918114,15.7266684"/> </svg>

+ 1 - 0
src/assets/plus-square.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4a90c4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-square"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>

+ 1 - 0
src/assets/search.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#606266" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>

+ 1 - 0
src/assets/time.svg

@@ -0,0 +1 @@
+<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24" aria-labelledby="timeIconTitle" stroke="#4a90c4" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#4a90c4"> <title id="timeIconTitle">Time</title> <circle cx="12" cy="12" r="10"/> <polyline points="12 5 12 12 16 16"/> </svg>

+ 22 - 3
src/layout/Header/Header.vue

@@ -57,7 +57,7 @@
 <script setup lang="ts">
 import {User} from "@element-plus/icons-vue";
 import router from "../../router/index.js";
-import { onMounted, ref } from "vue";
+import { onMounted, ref, watch } from "vue";
 import './index.scss';
 import { useStore } from "@/store";
 import emitter from "@/utils/emitter";
@@ -98,20 +98,39 @@ const handleCommand = (command: string) => {
 const route = useRoute();
 let activePage = ref('');
 
-onMounted(() => {
+// 更新活跃页面状态的函数
+const updateActivePage = () => {
   let path = route.path.split('/')[1];
   if (path == "profile") {
     activePage.value = null;
+    return;
   }
 
   if (route.path.split('/')[1] == "home") {
     path = route.path.split('/')[2].charAt(0).toUpperCase() + route.path.split('/')[2].slice(1);
-    if (path === 'Assignment') {
+    if (path === 'Assignment' || path === 'MyHomework') {
       activePage.value = 'MyWork';
+    } else if (path === 'MyCorrect') {
+      // 处理 myCorrect 路径,需要检查第三级路径
+      const thirdPath = route.path.split('/')[3];
+      if (thirdPath === 'course' || thirdPath === 'assignment') {
+        activePage.value = 'MyWork';
+      } else {
+        activePage.value = path;
+      }
     } else {
       activePage.value = path;
     }
   }
+};
+
+// 监听路由变化
+watch(() => route.path, () => {
+  updateActivePage();
+}, { immediate: true });
+
+onMounted(() => {
+  updateActivePage();
 });
 </script>
 

+ 5 - 3
src/layout/Header/index.scss

@@ -34,17 +34,19 @@
       vertical-align: middle;
       color: #000000;
       height: 40px;
+      font-size: 18px;
+      margin-right: 20px;
 
       &:hover {
         background-color: #fff;
         color: #000000;
-        border-bottom: 2px solid #597d3b;
+        border-bottom: 2px solid #4A90C4;
       }
 
       &.is-active {
         background-color: #fff;
-        color: #597d3b !important;
-        border-bottom: 2px solid #597d3b;
+        color: #4A90C4 !important;
+        border-bottom: 2px solid #4A90C4;
       }
     }
   }

+ 35 - 3
src/router/index.ts

@@ -19,8 +19,8 @@ const router = createRouter({
     {
       path: "/",
       redirect: () => {
-        // window.location.href = "https://p-nju.seec.seecoder.cn/login?from=https://air-nju.seec.seecoder.cn";
-        window.location.href = "http://localhost:8000/login?from=http://localhost:4000";
+        window.location.href = "https://p-nju.seec.seecoder.cn/login?from=https://air-nju.seec.seecoder.cn";
+        // window.location.href = "http://localhost:8000/login?from=http://localhost:4000";
         // {本地门户首页地址}?from={本地eai首页地址}
         return "/"; // 占位返回值,实际不会执行
     },
@@ -137,6 +137,38 @@ const router = createRouter({
           name: 'myCorrectWithAssignmentId',
           component: () => import("@/views/CorrectPage/CorrectAssignmentPage.vue")
         },
+        {
+          path: 'studentDetail',
+          name: 'studentDetail',
+          component: () => import("@/views/ClassPage/StudentDetail.vue")
+        },
+        {
+          path: 'courseClass/:courseId',
+          name: 'courseClass',
+          component: () => import("@/views/ClassPage/CourseClass.vue")
+        },
+        {
+          path: 'classDetail',
+          name: 'classDetail',
+          component: () => import("@/views/ClassPage/ClassDetail.vue"),
+          children:[
+            {
+              path: "",
+              name:"classDetail",
+              redirect: "/home/classDetail/student"
+            },
+            {
+              path: 'student',
+              name: 'classStudent',
+              component: () => import("@/views/ClassPage/ClassStudent.vue")
+            },
+            {
+              path: 'homework',
+              name: 'classHomework',
+              component: () => import("@/views/ClassPage/ClassHomework.vue")
+            },
+          ]
+        },
         {
           path:"behavior",
           name: "behavior",
@@ -228,7 +260,7 @@ const router = createRouter({
  */
   router.beforeEach(async (to) => {
     const store = useStore();
-    const publicPaths = [ '/register', '/passwordChange', '/admin/login'];
+    const publicPaths = [ '/register', '/passwordChange', '/admin/login','];
     // 1. 检查URL中的token
     const urlToken = new URLSearchParams(window.location.search).get('token');
     if (urlToken) {

+ 19 - 0
src/types/dto.ts

@@ -85,3 +85,22 @@ export interface PasswordChangeDTO {
     officialEmail: string; 
     verifyCode: string; 
 }
+
+// 新增:班级信息接口
+export interface ClassDTO {
+    classId: number;
+    className: string;
+    courseId: number;
+    stuNumber: number;
+    classTime: string;
+    teacherId: number;
+}
+
+// 新增:学生作业状态接口
+export interface StudentAssignmentStatusDTO {
+    studentId: number;
+    officialNumber: string;
+    stuName: string;
+    status: string; // "CORRECTED" | "NOT_SUBMITTED" | "SUBMITTED"
+    score: number;
+}

+ 33 - 0
src/types/vo.ts

@@ -140,3 +140,36 @@ export interface CourseDetailVO {
     description: string;
 }
 
+// 新增:学生作业状态接口
+export interface StudentAssignmentStatus {
+    assignmentId: number;
+    assignmentName: string;
+    status: 'CORRECTED' | 'SUBMITTED' | 'NOT_SUBMITTED'|'LATE_SUBMITTED_CORRECTED'|'LATE_SUBMITTED';
+    score: number;
+}
+
+// 新增:学生课程作业响应接口
+export interface StudentCourseAssignmentsResponse {
+    studentId: number;
+    officialNumber: string;
+    stuName: string;
+    assignments: StudentAssignmentStatus[];
+}
+
+export interface ClassVO {
+    classId: number;
+    className: string;
+    classTime: string;
+    teacherId: number;
+    courseId: number;
+    stuNumber: number;
+    classCode: number;
+}
+
+export interface ClassStudentVO {
+    officialNumber: number;
+    stuName: string;
+    stateCode: number;
+    stateDesc: string;
+    studentId: number;
+}

+ 44 - 10
src/views/AiSpeakingPage/SpeakingPage.vue

@@ -13,12 +13,14 @@
           <div class="end-time">截止时间:{{ formatDate(aiSpeakingAssignmentInfo.endTime) }}</div>
 
         </div>
-            <div class="data-item">
-              <span class="data-label">作业描述 </span>
-              <span class="data-value">{{ aiSpeakingAssignmentInfo.description !== null ? aiSpeakingAssignmentInfo.description : '未设置' }}</span>
-            </div>
-            <div class="data-item">
-              <span v-if="aiSpeakingAssignmentInfo.descriptionFile" class="data-label">作业附件</span>
+        <div class="data-item">
+          <span class="data-label">作业描述</span>
+          <div class="scrollable-description">
+            {{ aiSpeakingAssignmentInfo.description !== null ? aiSpeakingAssignmentInfo.description : '未设置' }}
+          </div>
+        </div>
+            <div class="data-item" v-if="aiSpeakingAssignmentInfo.descriptionFile">
+              <span class="data-label">作业附件</span>
               <el-icon  @click="openEditor(aiSpeakingAssignmentInfo.descriptionFile, aiSpeakingAssignmentInfo.assignmentName)"><Document /></el-icon>
             </div>
             <div class="data-item">
@@ -620,6 +622,9 @@ let homeworkStartTime = null;
 const cameraPreview = ref(null);
 const recordingDuration = ref(0); // 录制时长(秒)
 let recordingTimer = null; // 录制计时器
+
+const lastSavePromptTime = ref(0); // 记录上次提示保存的时间点
+const SAVE_REMINDER_INTERVAL = 180; // 3分钟(180秒)提醒一次
 // 开始作业
 const startHomework = async () => {
   scrollToBottom();
@@ -652,6 +657,13 @@ const startHomework = async () => {
       // 启动录制时长计时器
     recordingTimer = setInterval(() => {
       recordingDuration.value += 1;
+
+      // 每3分钟提醒保存一次(180秒)
+      if (recordingDuration.value % SAVE_REMINDER_INTERVAL === 0 && 
+          recordingDuration.value !== lastSavePromptTime.value) {
+        lastSavePromptTime.value = recordingDuration.value;
+        ElMessage.warning('录制时长已超过3分钟,建议暂存当前进度');
+  }
     }, 1000);
      // 将摄像头画面绑定到 video 元素
      if (cameraPreview.value) {
@@ -702,6 +714,7 @@ const saveHomework = async () => {
         const videoBlob = new Blob([...recordedChunks.value], { type: "video/webm" });
         const fileName = `user_${userId}_${assignmentId}_${Date.now()}`;
         let renameFile =new File([videoBlob], fileName +'.webm', { type: 'video/webm' })
+
         try {
           doubaoApis
           .saveSigment(userId, assignmentId,renameFile, duration)
@@ -712,6 +725,7 @@ const saveHomework = async () => {
             isHomeworkStarted.value = false;
             ElMessage.success("视频片段暂存成功");
             loadingInstance.close();
+            lastSavePromptTime.value = 0; // 重置提示计时
           })
         } catch (error) {
           console.error("视频片段上传失败:", error);
@@ -795,18 +809,20 @@ const resetHomework = async() => {
     return;
   }
   if (isHomeworkStarted.value) {
-    if (mediaRecorder.value && mediaRecorder.value.state === "recording") {
-      mediaRecorder.value.stop();
-    }
     // 停止录制时长计时器
     if (recordingTimer) {
       clearInterval(recordingTimer);
       recordingTimer = null;
+
     }
     // 清除摄像头画面
     if (cameraPreview.value) {
       cameraPreview.value.srcObject = null;
     }
+     // 关闭摄像头和麦克风
+     if (mediaRecorder.value && mediaRecorder.value.stream) {
+              mediaRecorder.value.stream.getTracks().forEach((track) => track.stop());
+      }
   }
 
    // 显示加载状态
@@ -823,8 +839,10 @@ const resetHomework = async() => {
       isHomeworkSaved.value = false;
       isHomeworkStarted.value = false;
       initRecognitionValue()
+      recordingDuration.value = 0;
       ElMessage.success("作业内容已清空");
       getHistory()
+      lastSavePromptTime.value = 0; // 重置提示计时
 
     } else {
       ElMessage.error("清除失败,请稍后尝试")
@@ -1005,6 +1023,7 @@ onBeforeUnmount(() => {
   if (cameraPreview.value) {
     cameraPreview.value.srcObject = null;
   }
+  lastSavePromptTime.value = 0; // 清理计时
 });
 
 
@@ -1124,6 +1143,20 @@ onMounted(() => {
     color: #777;
   }
 
+
+  .scrollable-description {
+  max-height: 12em; /* 10行高度(假设1em=1行) */
+  line-height: 1.3em; /* 行高设定 */
+  overflow-y: auto; /* 垂直滚动 */
+  white-space: pre-wrap; /* 保留换行符 */
+  color: #777;
+  margin-top: 10px;
+  scrollbar-width: none; /* Firefox */
+  -ms-overflow-style: none; /* IE/Edge */
+}
+
+
+
 .homework-button-group {
   display: flex;
   flex-direction: column;
@@ -1369,7 +1402,7 @@ progress {
 }
 
 .wechat-audio-button {
-  margin-left: 20px;
+  margin-left: 25px;
   display: flex;
   align-items: center;
   background-color: #90caf9;
@@ -1378,6 +1411,7 @@ progress {
   padding: 8px 15px;
   cursor: pointer;
   transition: background-color 0.3s;
+  margin-bottom:8px;
 }
 
 .wechat-audio-button:hover {

+ 68 - 15
src/views/AiSpeakingPage/SpeakingRecord.vue

@@ -2,7 +2,7 @@
   <div class="container">
     <div class="header-container">
       <!-- 返回按钮 -->
-      <el-button style="color:#597D3B" class="back-button" type="text" @click="goBack">
+      <el-button style="color:#4A90C4" class="back-button" type="text" @click="goBack">
         <el-icon><Back /></el-icon>
       </el-button>
       <!-- 标题 -->
@@ -255,7 +255,7 @@
             <div class="button-container">
               <div class="left-buttons">
                 <el-button 
-                  style="background-color:#597D3B;color:white;width:200px" 
+                  style="background-color:#4A90C4;color:white;width:200px" 
                   @click="submitComment" 
                   class="submit-button"
                   :loading="submitting"
@@ -264,7 +264,7 @@
                 </el-button>
                 
                 <el-button
-                  style="background-color:#597D3B;color:white;width:200px;margin-top:50px"
+                  style="background-color:#4A90C4;color:white;width:200px;margin-top:50px"
                   @click="handleNext"
                   :disabled="isAllCorrected"
                 >
@@ -272,14 +272,14 @@
                 </el-button>
                 <!-- 新增:评语模板 -->
                 <el-button
-                  style="background-color:#597D3B;color:white;width:200px;margin-top:50px;"
+                  style="background-color:#4A90C4;color:white;width:200px;margin-top:50px;"
                   @click="showTemplateDialog"
                 >
                   评语模板
                 </el-button>
 
                 <el-button
-                  style="background-color:#597D3B;color:white;width:200px;margin-top:50px;"
+                  style="background-color:#4A90C4;color:white;width:200px;margin-top:50px;"
                   @click="goBack"
                 >
                   返回
@@ -341,12 +341,18 @@ import engagementApis from "@/apis/engagement";
 import mediaApis from "@/apis/media";
 import * as echarts from "echarts";
 import { ElEmpty, ElMessage } from "element-plus";
-import { Back, Loading } from "@element-plus/icons-vue"; // 引入 Element UI
+import { Back } from "@element-plus/icons-vue"; // 引入 Element UI
 import { useRouter } from "vue-router";
 import {useStore} from "@/store";
+import useApis from "@/apis";
 
 const router = useRouter();
 const store = useStore();
+const apis = useApis();
+
+// 添加courseId ref
+const courseId = ref(0);
+
 const props = defineProps({
   userId: {
     type: Number,
@@ -403,11 +409,11 @@ const correctionStats = ref({
   correctNumber: 0
 });
 
-// 计算批改进度百分比
-const progressPercentage = computed(() => {
-  if (correctionStats.value.engageNumber === 0) return 0;
-  return Math.round((correctionStats.value.correctNumber / correctionStats.value.engageNumber) * 100);
-});
+// 计算批改进度百分比(暂时不使用,保留以防将来需要)
+// const progressPercentage = computed(() => {
+//   if (correctionStats.value.engageNumber === 0) return 0;
+//   return Math.round((correctionStats.value.correctNumber / correctionStats.value.engageNumber) * 100);
+// });
 
 // 计算未批改数量
 const uncorrectedNumber = computed(() => {
@@ -431,6 +437,30 @@ const fetchCorrectionStats = async () => {
   }
 };
 
+// 获取作业详情以获取courseId
+const fetchAssignmentDetails = async () => {
+  try {
+    const response = await apis.getAssignmentById(props.assignmentId);
+    if (response.data.code === 200) {
+      const assignmentData = response.data.data;
+      courseId.value = assignmentData.courseId || 0;
+      console.log('获取到AI口语作业courseId:', courseId.value);
+    }
+  } catch (error) {
+    console.error('获取AI口语作业详情失败:', error);
+    // 如果普通作业API失败,尝试AI口语作业API
+    try {
+      const aiResponse = await apis.getAiSpeakingAssignmentById(props.assignmentId);
+      if (aiResponse.data.code === 200 && aiResponse.data.data.courseId) {
+        courseId.value = aiResponse.data.data.courseId;
+        console.log('从AI口语API获取到courseId:', courseId.value);
+      }
+    } catch (aiError) {
+      console.error('获取AI口语作业详情也失败:', aiError);
+    }
+  }
+};
+
 // 获取下一个学生作业ID
 const getNextStudentAssignment = async () => {
   try {
@@ -937,10 +967,30 @@ onBeforeUnmount(() => {
 });
 
 const goBack = () => {
-  router.push({
-    name: 'myCorrectWithAssignmentId',
-    params: { assignmentId: props.assignmentId }
-  });
+  // 根据用户角色决定返回页面
+  if (ROLE === 'STUDENT') {
+    // 学生返回 myHomework 页面,并传递courseId
+    if (!courseId.value) {
+      ElMessage.warning('获取课程信息失败,将返回作业列表');
+      router.push({ name: 'myHomework' });
+      return;
+    }
+    
+    console.log('AI口语作业返回,courseId:', courseId.value);
+    router.push({ 
+      name: 'myHomework',
+      query: { 
+        courseId: courseId.value.toString(),
+        fromAssignment: 'true' // 标记是从作业页面返回
+      }
+    });
+  } else {
+    // 老师保持原有逻辑,返回批改页面
+    router.push({
+      name: 'myCorrectWithAssignmentId',
+      params: { assignmentId: props.assignmentId }
+    });
+  }
 };
 
 const ROLE = store.user.role;
@@ -987,6 +1037,9 @@ watch(templateDialogVisible, (val) => {
 // 在数据加载完成后渲染图表
 onMounted(async () => {
   try {
+    // 先获取作业详情以获取courseId
+    await fetchAssignmentDetails();
+    
     const response = await doubaoApis.getRecord(
       props.userId,
       props.assignmentId

+ 198 - 0
src/views/ClassPage/ClassDetail.vue

@@ -0,0 +1,198 @@
+<template>
+    <div class="course-class" v-show="DataLoading">
+        <classHeader>{{Coursedata.courseName}}:{{ Classdata.className }}</classHeader>
+        <div class="content">
+            <router-view></router-view>
+            <!-- <classTable></classTable> -->
+        </div>
+        <!-- <classDetailFooter :classId=classId :courseId=courseId :flag=true></classDetailFooter> -->
+        <div class="class-footer">
+            <button 
+                :class="{'active':choose,'unactive':!choose,'inhover':(!choose && hoverid===1)}"
+                :disabled="choose"
+                @click="{choose = true;$router.replace(`/home/classDetail/student?classId=${classId}&courseId=${courseId}`);}"
+                @mouseenter="hoverid = 1"
+                @mouseleave="hoverid = 0"
+            >
+            班级学生管理
+            </button>
+            <button 
+                :class="{'active':!choose,'unactive':choose,'inhover':(choose && hoverid===2)}"
+                :disabled="!choose"
+                @click="{choose = false;$router.replace(`/home/classDetail/homework?classId=${classId}&courseId=${courseId}`);}"
+                @mouseenter="hoverid = 2"
+                @mouseleave="hoverid = 0"
+            >
+            班级作业管理
+            </button>
+        </div>
+    </div>
+    <div class="loading" v-show="!DataLoading"> Loading...</div>
+</template>
+
+<script setup lang="ts">
+import {onMounted, reactive, ref, computed} from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import classHeader from './components/classHeader.vue';
+import type {CourseDetailVO, ClassVO} from "@/types/vo";
+import { useStore } from "@/store"
+import useApis from "@/apis";
+
+const store = useStore()
+const route = useRoute()
+const router = useRouter()
+const apis = useApis()
+const classId = +route.query.classId;
+const courseId = +route.query.courseId;
+
+const choose = ref(true)
+let hoverid = ref(0) // 0 没有 1 学生 2 作业 
+
+const CourseDataLoading = ref(false)
+const ClassDataLoading = ref(false)
+
+const Coursedata = reactive<CourseDetailVO>({
+  courseId: null,
+  courseName: '',
+  teacherIds: [],
+  teacherNames: [],
+  courseImages: [''],
+  targetGrade: '',
+  startTime: '',
+  endTime: '',
+  createTime: '',
+  description: '',
+})
+
+const Classdata = reactive<ClassVO>({
+  classId: null,
+  className: '',
+  courseId: null,
+  stuNumber: null,
+  classTime: '',
+  teacherId: null,
+})
+
+const identify = () => {
+    if(store.user.role === "STUDENT"){
+        router.push('/404')
+    }
+}
+const getClass = () => {
+    console.log("classId:",classId);
+    Classdata.classId = classId
+    Classdata.courseId = courseId
+    Classdata.className = "读写1班"
+    Classdata.stuNumber = 30
+    Classdata.classTime = "Mon 10:10"
+    Classdata.teacherId = 4
+
+    ClassDataLoading.value = true
+}
+const getCourse = () => {
+  // 获取课程信息
+  apis.getCourseByCourseId(courseId)
+      .then((res: any) => {
+        let data = res.data.data.records[0]
+        Object.assign(Coursedata, {...data})
+        CourseDataLoading.value = true
+      })
+      .catch((err: any) => {
+        console.error(err);
+      })
+}
+const setchoose = () => {
+    const currentPath = route.path
+    const parts = currentPath.split('/')
+    if(parts[parts.length-1]=="homework")
+        choose.value=false
+    else
+        choose.value=true
+}
+const DataLoading = computed(() => {
+  return CourseDataLoading.value && ClassDataLoading.value
+})
+
+
+onMounted(() => {
+    identify()
+    setchoose()
+    getClass()
+    getCourse()
+})
+</script>
+
+<script lang="ts">
+export default {
+  name: "ClassDetail"
+}
+</script>
+
+<style scoped>
+.course-class {
+    width: 66vw;
+    height: 65vh;
+    background-color: #fff;
+    border-radius: 15px;
+    margin: 0 auto;
+    margin-top: 4.6vh;
+    border-bottom: 1px solid #70bbdb;
+    padding:  7vh 5.5vw;
+    .content {
+        height: 55vh;
+        width: 100%;
+        /* border:1px solid; */
+        margin: 0 auto;
+        display: flex;
+        align-items: center;
+        flex-direction: row;
+        flex-wrap: wrap;
+        justify-content: start;
+        padding: 10px;
+        gap: 3.6vw;
+    }
+    .class-footer {
+        width: '100%';
+        height: 7.5vh;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+        gap: 250px;
+        .active {
+            font-size: 24px;
+            font-weight: 600;
+            color: #fff;
+            border-radius: 50px;
+            padding: 2px 10px;
+            background-color: #70bbdb;
+            border:none;
+            border-bottom: 5px solid #4FA3C5;
+        }
+        .unactive {
+            font-size: 24px;
+            font-weight: 600;
+            color: #fff;
+            border-radius: 50px;
+            padding: 2px 10px;
+            background-color: rgb(112, 187, 219, 0.5);
+            border:none;
+            border-bottom: 5px solid #4FA3C5;
+            box-shadow: -5px 7px 7px 0 rgba(0, 0, 0, 0.25);
+        }
+        .inhover{
+            box-shadow: -10px 7px 20px #70bbdb;
+            border-bottom: 5px solid rgb(112, 187, 219, 0.5);
+        }
+    }
+}
+.loading{
+    height: 100%;
+    width: 100%;
+    text-align: center;
+    line-height: 100vh;
+    font-size: 44px;
+    font-weight: 700;
+    color: #70bbdb;
+}
+</style>

+ 574 - 0
src/views/ClassPage/ClassHomework.vue

@@ -0,0 +1,574 @@
+<template>
+    <div class="main">
+        <div class="search">
+            <img src="@/assets/search.svg" width="20px" height="20px"></img> 
+            <div class="searchInput">
+                <input type="text" v-model.trim="searchInput" placeholder="输入姓名或学号" :disabled="!isTable">
+                <div class="fix">
+                    <span>搜索</span>
+                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#b8b8b8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
+                </div>
+            </div>
+            <div class="Myselect">
+                <select id="selectHomework" v-model="TheHomework" @change="changeHomework" :disabled="!isTable"> 
+                    <option value=-1>选择作业</option>
+                    <option v-for="item in Homeworksdata" :value="item.assignmentId">{{item.assignmentName}}</option>
+                </select>
+                <div class="show">
+                        {{ ShowTheHomework }}
+                </div>
+            </div>
+            <div class="Myselect">
+                <select id="selectSort" v-model="TheSort" :disabled="!isTable"> 
+                    <option value="无">排序方式</option>
+                    <option v-for="item in sortMethods" :value="item">{{item}}</option>
+                </select>
+                <div class="show">
+                        {{ ShowTheSort }}
+                </div>
+            </div>
+            <classToggle v-model:isLeft="isTable">
+                <template #left>
+                    table
+                </template>
+                <template #right>
+                    chart
+                </template>
+            </classToggle>
+        </div>
+        <div class="content">
+            <div class="Loading" v-show="!DataLoading">Loading...</div>
+            <div class="half-table" v-show="DataLoading">
+                <classTable v-if="isTable">
+                <template #head>
+                    <tr>
+                        <th>序号</th>
+                        <th>学生姓名</th>
+                        <th>学号</th>
+                        <th>成绩</th>
+                    </tr>
+                </template>
+                <template #body>
+                    <tr v-for="(item,index) in showData.slice(0,5)" :key="index+item.officialNumber">
+                        <template v-if="item.officialNumber!==0">
+                            <td>{{ (currentPage-1)*10+index+1 }}</td>
+                            <td
+                                @click="$router.push(`/home/studentDetail?classId=${classId}&courseId=${courseId}&stuId=${item.studentId}`)"
+                                @mouseenter="hoverStuId=item.studentId"
+                                @mouseleave="hoverStuId=-1"
+                                :class="{Gethover:hoverStuId===item.studentId,Blue:true}"
+                                >{{ item.stuName }}</td>
+                            <td>{{ item.officialNumber }}</td>
+                            <td  @click="$router.push(`/home/assignment/${ShowTheHomeworkId}/correct?studentId=${item.studentId}`)">
+                                <div class="highScore" v-if="item.score>=80"> {{ item.score }}</div>
+                                <div class="midScore" v-else-if="item.score>=60"> {{ item.score }}</div>
+                                <div class="lowScore" v-else-if="item.score>0"> {{ item.score }}</div>
+                                <div class="uncorrected" v-else-if="item.status==='SUBMITTED'"> 未批改</div>
+                                <div class="unsubmitted" v-else-if="item.status==='NOT_SUBMITTED'"> 未提交</div>             
+                            </td>
+                        </template>
+                        <template v-else>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                        </template>
+                    </tr>
+                </template>
+                </classTable>
+                <div ref="chartRef1" class="chart" v-else></div>
+            </div>
+            <div class="half-table" v-show="DataLoading">
+                <classTable v-if="isTable">
+                <template #head>
+                    <tr>
+                        <th>序号</th>
+                        <th>学生姓名</th>
+                        <th>学号</th>
+                        <th>成绩</th>
+                    </tr>
+                </template>
+                <template #body>
+                    <tr v-for="(item,index) in showData.slice(5,10)" :key="index+item.officialNumber">
+                        <template v-if="item.officialNumber!==0">
+                            <td>{{ (currentPage-1)*10+index+6 }}</td>
+                            <td
+                                @click="$router.push(`/home/studentDetail?classId=${classId}&courseId=${courseId}&stuId=${item.studentId}`)"
+                                @mouseenter="hoverStuId=item.studentId"
+                                @mouseleave="hoverStuId=-1"
+                                :class="{Gethover:hoverStuId===item.studentId,Blue:true}"
+                                >{{ item.stuName }}</td>
+                            <td>{{ item.officialNumber }}</td>
+                            <td @click="$router.push(`/home/assignment/${ShowTheHomeworkId}/correct?studentId=${item.studentId}`)">
+                                <div class="highScore" v-if="item.score>=80"> {{ item.score }}</div>
+                                <div class="midScore" v-else-if="item.score>=60"> {{ item.score }}</div>
+                                <div class="lowScore" v-else-if="item.score>0"> {{ item.score }}</div>
+                                <div class="uncorrected" v-else-if="item.status==='SUBMITTED'"> 未批改</div>
+                                <div class="unsubmitted" v-else-if="item.status==='NOT_SUBMITTED'"> 未提交</div>                
+                            </td>
+                        </template>
+                        <template v-else>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                        </template>
+                    </tr>
+                </template>
+                </classTable>
+                <div ref="chartRef2" class="chart chart2" v-else></div>
+            </div>
+        </div>
+        <div class="pages" v-show="DataLoading && isTable">
+            <el-pagination
+                v-model:current-page="currentPage"
+                :page-size="10"
+                :pager-count="5"
+                layout="total, prev, pager, next"
+                :total="searchData.length"
+            />
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import classTable from './components/classTable.vue';
+import classToggle from './components/classToggle.vue'
+import {onMounted, ref, computed, watch, nextTick } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { useStore } from "@/store"
+import * as echarts from 'echarts'
+import useApis from "@/apis";
+
+const apis = useApis()
+const store = useStore()
+const route = useRoute()
+const router = useRouter()
+const classId = +route.query.classId;
+const courseId = +route.query.courseId;
+
+const currentPage = ref(1)
+const searchInput = ref("")
+const TheHomework = ref(-1)
+const ShowTheHomework = ref("")
+const ShowTheHomeworkId = ref(-1)
+const TheSort = ref("无")
+const ShowTheSort = ref("默认")
+const hoverStuId = ref(-1)
+
+const StudentsDataLoading = ref(false)
+const HomeworkDataLoading = ref(false)
+const isTable = ref(true)
+
+const Studentsdata = ref([])
+const Homeworksdata = ref([])
+
+
+const sortMethods = ["默认","成绩从低到高","成绩从高到低","未批改优先","未提交优先"]
+
+
+const chartRef1 = ref(null)
+const chartRef2 = ref(null)
+const setChart = () => {
+
+    const chartInstance = echarts.init(chartRef1.value)
+    const chartInstance2 = echarts.init(chartRef2.value)
+    let excellentnum=0, goodnum=0 , failnum=0 , uncorrectednum=0 , unsubmittednum=0
+    Studentsdata.value.forEach((item)=>{
+        if(item.status === "NOT_SUBMITTED"){
+            unsubmittednum++
+        }
+        else if(item.status === "SUBMITTED"){
+            uncorrectednum++
+        }
+        else if(item.score<60){
+            failnum++
+        }
+        else if(item.score<80){
+            goodnum++
+        }
+        else{
+            excellentnum++
+        }
+    })
+    
+    const option = {
+        title: {
+        text: ShowTheHomework.value+'详情',
+        // subtext: '纯属虚构',
+        left: 'center'
+        },
+        tooltip: {
+        trigger: 'item',
+        formatter: '{b}: {d}%'
+        },
+        legend: {
+        orient: 'vertical',
+        left: 'left'
+        },
+        series: [
+        {
+            name: '作业组成',
+            type: 'pie',
+            radius: '50%',
+            data: [
+                { value: excellentnum, name: '优秀', itemStyle: { color: '#7ADBB0' } },
+                { value: goodnum, name: '良好', itemStyle: { color: '#F0C05A' } },
+                { value: failnum, name: '不及格', itemStyle: { color: '#F4A261' } },
+                { value: unsubmittednum, name: '未提交', itemStyle: { color: '#C45C4A' } },
+                { value: uncorrectednum, name: '未批改', itemStyle: { color: '#D9D9D9' } }
+            ],
+            emphasis: {
+            itemStyle: {
+                shadowBlur: 10,
+                shadowOffsetX: 0,
+                shadowColor: 'rgba(0, 0, 0, 0.5)'
+            }
+            }
+        }
+        ]
+    }
+    const option2 = {
+        title: {
+        text: ShowTheHomework.value+'详情',
+        // subtext: '纯属虚构',
+        left: 'center'
+        },
+        tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+            type: 'shadow' // 悬停时显示阴影效果
+        },
+        formatter: '{b}: {c}'
+        },
+        xAxis: {
+            type: 'category',
+            data: ['优秀', '良好', '不及格', '未提交', '未批改']
+        },
+        yAxis: {
+            type: 'value',
+            name: '人数'
+        },
+        series: [
+        {
+            name: '作业组成',
+            type: 'bar',
+            radius: '50%',
+            data: [
+                { value: excellentnum, name: '优秀', itemStyle: { color: '#7ADBB0' } },
+                { value: goodnum, name: '良好', itemStyle: { color: '#F0C05A' } },
+                { value: failnum, name: '不及格', itemStyle: { color: '#F4A261' } },
+                { value: unsubmittednum, name: '未提交', itemStyle: { color: '#C45C4A' } },
+                { value: uncorrectednum, name: '未批改', itemStyle: { color: '#D9D9D9' } }
+            ],
+            barWidth: '40%',
+            emphasis: {
+            itemStyle: {
+                shadowBlur: 10,
+                shadowOffsetX: 0,
+                shadowColor: 'rgba(0, 0, 0, 0.5)'
+            }
+            }
+        }
+        ]
+    }
+
+    chartInstance.setOption(option)
+    chartInstance2.setOption(option2)
+}
+
+
+const identify = () => {
+    if(store.user.role === "STUDENT"){
+        router.push('/404')
+    }
+}
+const getHomeworks = async () => {
+    // console.log(courseId,classId);
+    console.log(ShowTheHomeworkId.value);
+    await apis.getAssignmentByCourseId(courseId)
+        .then((res:any)=>{
+            // console.log('----------------------');
+            // console.log(res.data.data.records);
+            res.data.data.records.forEach(item => Homeworksdata.value.push(item))
+            ShowTheHomework.value = res.data.data.records[0].assignmentName
+            ShowTheHomeworkId.value = res.data.data.records[0].assignmentId
+        })
+    // t.forEach(item => Homeworksdata.value.push(item))
+    HomeworkDataLoading.value = true
+    console.log(ShowTheHomeworkId.value);
+}
+const getStudents = () => {
+    StudentsDataLoading.value=false
+    console.log('+++++++++++++++++++++++');
+    console.log(classId,ShowTheHomeworkId.value);
+    apis.getClassStatesOfCourse(classId,ShowTheHomeworkId.value)
+        .then((res:any)=>{
+            console.log(res.data.data);
+            Studentsdata.value = res.data.data
+            StudentsDataLoading.value = true
+        })
+        .catch((err:any)=>{
+            console.log('-------------------------');
+            console.log(err);
+        })
+}
+const changeHomework = () => {
+    console.log(ShowTheHomework.value)
+    getStudents()
+}
+
+const searchData = computed(()=>{
+    if(searchInput.value===""){
+        return Studentsdata.value
+    }
+    else{
+        return Studentsdata.value.filter(student => {
+          return (
+            student.stuName.includes(searchInput.value) ||
+            student.officialNumber.toString().includes(searchInput.value)
+          )
+        })
+    }
+})
+
+
+const showData = computed(() => {
+    const showdata = searchData.value.slice((currentPage.value-1)*10,currentPage.value*10)
+    while(showdata.length<10){
+        showdata.push(    {
+        "officialNumber": 0,
+        "stuName": "",
+        "status": "",
+        "score": 0,
+        "studentId": -1
+        })
+    }
+    return showdata
+})
+
+const DataLoading = computed(() => {
+  return StudentsDataLoading.value && HomeworkDataLoading.value
+})
+
+watch(TheHomework, () => {
+    nextTick(() => {
+        // console.log(ShowTheHomework.value,TheHomework.value);
+        if(TheHomework.value != -1){
+            let t = Homeworksdata.value.find(item=>item.assignmentId==TheHomework.value)
+            ShowTheHomework.value = t.assignmentName
+            ShowTheHomeworkId.value = t.assignmentId
+            TheHomework.value = -1
+        }
+        // console.log(ShowTheHomework.value,TheHomework.value);
+    });
+});
+watch(TheSort, () => {
+    nextTick(() => {
+        // console.log(ShowTheSort.value,TheSort.value);
+        if(TheSort.value != "无"){
+            ShowTheSort.value = TheSort.value
+            TheSort.value = "无"
+        }
+        // console.log(ShowTheSort.value,TheSort.value);
+    });
+});
+watch(ShowTheSort,(newData) =>{
+    if(newData==="未提交优先"){
+        Studentsdata.value = Studentsdata.value.sort((a,b)=>{
+            return a.status === "NOT_SUBMITTED" ? -1 : b.status === "NOT_SUBMITTED" ? 1 : 0;
+        })
+        console.log("未批改已排序");
+        console.log(Studentsdata.value);
+        
+    }
+    else if(newData==="未批改优先"){
+        Studentsdata.value = Studentsdata.value.sort((a,b)=>{
+            return a.status === "SUBMITTED" ? -1 : b.status === "SUBMITTED" ? 1 : 0;
+        })
+    }
+    else if(newData==="成绩从低到高"){
+        Studentsdata.value = Studentsdata.value.sort((a,b)=>{
+            if (a.status === "CORRECTED" && b.status !== "CORRECTED") return -1;
+            if (a.status !== "CORRECTED" && b.status === "CORRECTED") return 1;
+            return a.score - b.score;
+        })
+    }
+    else if(newData==="成绩从高到低"){
+        Studentsdata.value = Studentsdata.value.sort((a,b)=>{
+            if (a.status === "CORRECTED" && b.status !== "CORRECTED") return -1;
+            if (a.status !== "CORRECTED" && b.status === "CORRECTED") return 1;
+            return b.score - a.score;
+        })
+    }
+})
+watch(isTable, (newVal) => {
+    if(newVal === false){
+        nextTick(()=>{
+            console.log('chartRef1 dom:', chartRef1.value)
+            setChart()
+        })
+    }
+})
+onMounted(async () => {
+    await getHomeworks(); 
+    if (ShowTheHomeworkId.value) {
+        await getStudents();
+    } else {
+        console.warn('未获取到作业 ID,无法加载学生数据');
+    }
+});
+</script>
+
+<style scoped>
+.main{
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: 100%;
+    .search{
+    height: 50px;
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: center;
+    margin-top: 20px;
+    .searchInput{
+        height: 100%;
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-left: 20px;
+        input{
+            border: 2px solid #e4e7ed;
+            border-right: none;
+            height: 27px;
+            border-radius: 7px 0 0 7px;
+            padding: 0 10px;
+            color: #606266;
+        }
+        input::placeholder {
+            color:#c2c4c9;
+        }
+        .fix{
+            background-color: #f5f7fa;
+            padding: 2px 10px;
+            color:#b8b8b8;
+            font-weight: 600;
+            border: 2px solid #e4e7ed;
+            border-radius: 0 7px 7px 0;
+        }
+    }
+    .Myselect{
+        margin-left: 15px;
+        display: flex;
+        flex-direction: row;
+        select {
+            border: none;
+            background-color: #4a90c4;
+            font-size: 14px;
+            font-weight: 600;
+            color:#fff;
+            padding: 3px 10px;
+            padding-right: 0;
+            border-radius: 5px 0 0 5px;
+            max-width: 100px;
+        }
+        select:focus {
+            outline: none;
+            border: none;
+            background-color: #FFF;
+            border: 3px solid #4a90c4;
+            border-right: 0;
+            color:#4a90c4;
+        }
+        .show{
+            width: fit-content;
+            color: #4a90c4;
+            border: 3px solid #4a90c4;
+            font-weight: 600;
+            padding-left: 6px;
+            padding-right: 5px;
+            border-radius: 0 5px 5px 0;
+        }
+    }
+}
+.content{
+    width: 100%;
+    flex-grow: 1;
+    display: flex;
+    flex-direction: row;
+    gap: 40px;
+    .half-table{
+        width: 50%;
+        height: 100%;
+        .highScore{
+            padding: 2px 10px;
+            background-color: #7ADBB0;   
+            color: #fff;
+            border-radius: 20px;
+            width: fit-content;
+            margin: auto;
+        }
+        .midScore{
+            padding: 2px 10px;
+            background-color: #F0C05A;   
+            color: #fff;
+            border-radius: 20px;
+            width: fit-content;
+            margin: auto;
+        }
+        .lowScore{
+            padding: 2px 10px;
+            background-color: #F4A261;   
+            color: #fff;
+            border-radius: 20px;
+            width: fit-content;
+            margin: auto;
+        }
+        .unsubmitted{
+            padding: 2px 10px;
+            background-color: #C45C4A;   
+            color: #fff;
+            border-radius: 20px;
+            width: fit-content;
+            margin: auto;
+        }
+        .uncorrected{
+            padding: 2px 10px;
+            background-color: #D9D9D9;   
+            color: #fff;
+            border-radius: 20px;
+            width: fit-content;
+            margin: auto;
+        }
+        .chart{
+            margin-top: 70px;
+            margin-left: 30px;
+            height: 300px;
+            width: 100%;
+        }
+        .chart2{
+            margin-left: 0;
+            margin-right: 30px;
+            padding-bottom: 50px;
+        }
+    }
+    .Loading{
+        font-size: 40px;
+        color: #70bbdb;
+        font-weight: 700;
+        height: 100%;
+        width: 100%;
+        text-align: center;
+        align-content: center;
+    }
+}
+.pages{
+    height: 30px;
+    margin-top: 10px;
+}
+}
+</style>

+ 480 - 0
src/views/ClassPage/ClassStudent.vue

@@ -0,0 +1,480 @@
+<template>
+    <div class="main">
+        <div class="search">
+            <img src="@/assets/search.svg" width="20px" height="20px"></img> 
+            <div class="searchInput">
+                <input type="text" v-model.trim="searchInput" placeholder="输入姓名或学号">
+                <div class="fix">
+                    <span>搜索</span>
+                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#b8b8b8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
+                </div>
+            </div>
+
+            <!-- <button @click="visible_add=true"> 添加学生 </button> -->
+
+            <classToggle v-model:isLeft="isSingleDelete">
+                <template #left>
+                    单个删除
+                </template>
+                <template #right>
+                    批量删除
+                </template>
+            </classToggle>
+
+            <button v-show="!isSingleDelete" class="Del" @click="DeleteStudents()">确认删除</button>
+        </div>
+        <div class="content">
+            <div class="Loading" v-show="!DataLoading">Loading...</div>
+            <classTable v-show="DataLoading">
+                <template #head>
+                    <tr>
+                        <th>序号</th>
+                        <th>学生姓名</th>
+                        <th>学号</th>
+                        <!-- <th>是否加入班级</th> -->
+                        <th>
+                                <span v-show="!isSingleDelete" style="margin-right: 5px;">全选</span>
+                                <input 
+                                    type="checkbox"
+                                    v-show="!isSingleDelete"
+                                    v-model="allDeleted"
+                                >
+                        </th>
+                    </tr>
+                </template>
+                <template #body>
+                    <tr v-for="(item,index) in showData" :key="index+item.officialNumber">
+                        <template v-if="item.officialNumber!==0">
+                            <td>{{ (currentPage-1)*5+index+1 }}</td>
+                            <td  
+                                @click="$router.push(`/home/studentDetail?classId=${classId}&courseId=${courseId}&stuId=${item.studentId}`)"
+                                @mouseenter="hoverStuId=item.officialNumber" 
+                                @mouseleave="hoverStuId=-1"
+                                :class="{Gethover:hoverStuId===item.officialNumber,Blue:true}"
+                                >{{ item.stuName }}</td>
+                            <td>{{ item.officialNumber }}</td>
+                            <!-- <td>
+                                <div class="added" v-if="item.stateCode===1">已加入 </div>
+                                <div class="notadd" v-else> 未加入</div>                        
+                            </td> -->
+                            <td>
+                                <button v-show="isSingleDelete" class="Del" @click="handleDelete(item.officialNumber)">DEL</button>
+                                <input 
+                                    type="checkbox"
+                                    v-show="!isSingleDelete" 
+                                    :value="item.officialNumber" 
+                                    v-model="deleteIds"
+                                    @change="console.log(deleteIds)"
+                                >
+                            </td>
+                        </template>
+                        <template v-else>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                            <td>&nbsp;</td>
+                        </template>
+                    </tr>
+                </template>
+            </classTable>
+        </div>
+        <div class="pages">
+            <el-pagination
+                v-model:current-page="currentPage"
+                :page-size="5"
+                :pager-count="5"
+                layout="total, prev, pager, next"
+                :total="searchData.length"
+            />
+        </div>
+    </div>
+
+    <div v-if="visible_del" class="modal-mask" @click.self="isloading || (visible_del=false)" >
+        <div class="modal-container">
+            <div class="modal-header">
+                <h2>{{isloading?"删除中...":"你确定要删除 "+deleteName+":"+deleteId+" 吗?"}}</h2>
+            </div>
+            <div class="modal-footer">
+                <button class="yes-button" @click.stop="DeleteStudent" :disabled="isloading">确定</button>
+                <button class="no-button" @click.stop="visible_del=false;" :disabled="isloading">取消</button>
+            </div>
+        </div>
+    </div>
+
+    <div v-if="visible_add" class="modal-mask" @click.self="isloading_add || (visible_add=false)" >
+        <div class="modal-container">
+            <div class="add1" v-show="!isloading_add">
+                <h2>学生姓名:</h2> <input type="text" v-model.trim="addStudentName" ></input>
+            </div>
+            <div class="add1" v-show="!isloading_add">
+                <h2>学生学号:</h2> <input type="text" v-model.trim="addOfficalNumber" ></input>
+            </div>
+            <div class="add1" v-show="isloading_add" 
+            :style="{'height': '66%', 'margin-left':'210px'}">
+                <h2>上传中...</h2>
+            </div>
+            <div class="modal-footer add2">
+                <button class="yes-button" @click.stop="AddStudent" :disabled="isloading_add">确定</button>
+                <button class="no-button" @click.stop="visible_add=false" :disabled="isloading_add">取消</button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import classTable from './components/classTable.vue';
+import classToggle from './components/classToggle.vue'
+import {onMounted, reactive, ref, computed} from 'vue';
+import { useRoute } from 'vue-router';
+import type {ClassStudentVO} from "@/types/vo";
+import useApis from "@/apis";
+import { get } from 'http';
+
+const route = useRoute()
+const apis = useApis()
+const classId = +route.query.classId;
+const courseId = +route.query.courseId;
+
+const currentPage = ref(1)
+const searchInput = ref("")
+const visible_add = ref(false)
+const visible_del = ref(false)
+const isloading = ref(false)
+const isloading_add = ref(false)
+const addStudentName = ref("")
+const addOfficalNumber = ref("")
+const deleteId = ref(-1)
+const deleteIds = ref<number[]>([])
+const deleteName = ref("")
+const hoverStuId = ref(-1)
+const isSingleDelete = ref(true)
+
+const StudentsDataLoading = ref(false)
+
+const Studentsdata = reactive<ClassStudentVO[]>([])
+
+const handleDelete = (sid) => {
+    visible_del.value = true
+    deleteId.value = sid
+    deleteName.value = Studentsdata.find((item)=>{
+        return item.officialNumber === sid
+    }).stuName
+}
+const DeleteStudent = async () =>{
+    isloading.value = true
+    await apis.deleteStudentOfClass(classId,[deleteId.value])
+        .then((res:any)=>{console.log(res);
+        })
+    visible_del.value=false
+    isloading.value = false
+    getStudents()
+}
+const DeleteStudents = async () => {
+    isloading.value = true
+    visible_del.value = true
+    await apis.deleteStudentOfClass(classId,deleteIds.value)
+        .then((res:any)=>{console.log(res);
+        })
+    deleteIds.value=[]
+    visible_del.value=false
+    isloading.value = false
+    getStudents()
+}
+const AddStudent = async () => {
+    isloading_add.value = true
+    console.log(addStudentName.value)
+    console.log(+addOfficalNumber.value)
+    console.log(classId);
+    await apis.addStudentOfClasss(classId,+addOfficalNumber.value)
+    isloading_add.value = false
+    visible_add.value=false
+    addStudentName.value = ""
+    addOfficalNumber.value = "" 
+    console.log("添加成功");
+    getStudents()
+    // setTimeout(()=>{
+    //     isloading_add.value = false
+    //     visible_add.value=false
+    //     addStudentName.value = ""
+    //     addOfficalNumber.value = "" 
+    // }, 1000)
+}
+
+const getStudents = () => {
+    apis.getStudentsOfClass(classId)
+        .then((res:any) => {
+            Studentsdata.length=0
+            console.log(res.data.data);
+            res.data.data.forEach(item => Studentsdata.push(item))
+            console.log(res.data);
+            StudentsDataLoading.value = true
+        })
+    
+}
+
+const searchData = computed(()=>{
+    if(searchInput.value===""){
+        return Studentsdata
+    }
+    else{
+        return Studentsdata.filter(student => {
+          return (
+            student.stuName.includes(searchInput.value) ||
+            student.officialNumber.toString().includes(searchInput.value)
+          )
+        })
+    }
+})
+
+
+const showData = computed(() => {
+    const showdata = searchData.value.slice((currentPage.value-1)*5,currentPage.value*5)
+    while(showdata.length<5){
+        showdata.push(    {
+        "officialNumber": 0,
+        "stuName": "",
+        "stateCode": -1,
+        "stateDesc": "",
+        "studentId": -1
+        })
+    }
+    return showdata
+})
+const DataLoading = computed(() => {
+  return StudentsDataLoading.value
+})
+
+const allDeleted = computed({
+    get() {
+        return deleteIds.value.length === Studentsdata.length
+    },
+    set(newValue) {
+        if(newValue === true){
+            deleteIds.value = <number[]>[]
+            Studentsdata.forEach((item)=>{
+                deleteIds.value.push(+item.officialNumber)
+            })
+        }
+        else{
+            deleteIds.value = <number[]>[]
+        }
+    }
+})
+
+onMounted(() => {
+    getStudents()
+})
+</script>
+
+<style scoped>
+.main{
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: 100%;
+}
+.search{
+    height: 50px;
+    width: 100%;
+    margin-top: 20px;
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: center;
+    .searchInput{
+        height: 100%;
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-left: 20px;
+        input{
+            border: 2px solid #e4e7ed;
+            border-right: none;
+            height: 27px;
+            border-radius: 7px 0 0 7px;
+            padding: 0 10px;
+            color: #606266;
+        }
+        input::placeholder {
+            color:#c2c4c9;
+        }
+        .fix{
+            background-color: #f5f7fa;
+            padding: 2px 10px;
+            color:#b8b8b8;
+            font-weight: 600;
+            border: 2px solid #e4e7ed;
+            border-radius: 0 7px 7px 0;
+        }
+        
+    }
+    button{
+        background-color: #4a90c4;
+        font-size: 14px;
+        font-weight: 700;
+        border: none;
+        padding: 4px 10px;
+        color: #fff;
+        margin-left: 30px;
+        border-radius: 5px;
+        cursor: pointer;
+    }
+}
+.content{
+    width: 100%;
+    flex-grow: 1;
+    .Loading{
+        font-size: 40px;
+        color: #70bbdb;
+        font-weight: 700;
+        height: 100%;
+        text-align: center;
+        align-content: center;
+    }
+}
+.Del{
+    font-size: 14px;
+    padding: 1px 12px;
+    background-color: #c45c4a;
+    color: #fff;
+    border: none;
+    border-radius: 20px;
+    cursor: pointer;
+}
+.pages{
+    height: 30px;
+    margin-top: 10px;
+}
+.added{
+    color: #fff;
+    background-color: #7adbb0;
+    width: 62px;
+    height: 20px;
+    margin: auto;
+    border-radius: 20px;
+    font-weight: 600;
+}
+.notadd{
+    color: #fff;
+    background-color: #d9d9d9;
+    width: 62px;
+    height: 20px;
+    margin: auto;
+    border-radius: 20px;
+    font-weight: 600;
+}
+input[type="checkbox"]{
+    margin-bottom: -2px;
+    appearance: none; /* 隐藏默认样式 */
+    -webkit-appearance: none;
+    width: 15px;
+    height: 15px;
+    border: 2px solid #4a90c4; /* 边框 */
+    border-radius: 4px;    
+}
+input[type="checkbox"]:checked {
+  background-color: #4a90c4; /* 绿色 */
+  border-color: #4a90c4;
+}
+.modal-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 9999;
+    .modal-container {
+        border-radius: 30px;
+        border: 7px solid #70BBDB;
+        background: #FFF;
+        box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25);
+        width: 35vw;
+        height: 25vh;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+        .modal-header {
+            padding: 16px 20px;
+            height: 50%;
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+            color:#70bbdb
+        }
+        .modal-footer {
+            padding: 12px 20px;
+            height: 50%;
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+            gap: 50px;
+            .yes-button{
+                background-color: #c45c4a;
+                color: #fff;
+                border: 0;
+                padding: 3px 25px;
+                font-size: 22px;
+                font-weight: 600;
+                border-radius: 20px;
+                margin-right: 20px;
+            }
+            .no-button{
+                background-color: #4a90c4;
+                color: #fff;
+                border: 0;
+                padding: 3px 25px;
+                font-size: 22px;
+                font-weight: 600;
+                border-radius: 20px;
+                margin-right: 20px;
+            }
+            button{
+                cursor: pointer;
+            }
+            button:disabled{
+                background-color: #d9d9d9;
+            }
+        }
+        .add1 {
+            height: 33%;
+            display: flex;
+            flex-direction: row;
+            justify-content: start;
+            align-items: center;
+            color:#70bbdb;
+            margin-left: 80px;
+            input,select {
+                border: none;
+                border-bottom: 3px solid #70bbdb;
+                font-size: 16px;
+                font-weight: 600;
+                color:#70bbdb
+            }
+            input::placeholder {
+                color:#b2d7e7
+            }
+            input:focus,select:focus {
+                outline: none;
+                border: none;
+                outline: 5px solid #70bbdb;
+            }
+            span{
+                font-size: 16px;
+                font-weight: 600;
+                margin: 0 15px;
+            }
+        }
+        .add2 {
+            height: 33%;
+        }
+    }   
+}
+</style>

+ 421 - 0
src/views/ClassPage/CourseClass.vue

@@ -0,0 +1,421 @@
+<template>
+    <div class="course-class" v-show="DataLoading">
+        <classHeader>{{Coursedata.courseName}}</classHeader>
+        <div class="content scroll">
+            <a v-for="(item) in data" :key="item.classId" 
+                class="content-item" 
+                @mouseenter="activeId=item.classId" 
+                @mouseleave="activeId=-1" 
+                :class="{'shadow_unactive':item.classId!==activeId,'shadow_active':item.classId===activeId}"
+                @click="{$event.preventDefault();$router.push(`/home/classDetail?classId=${item.classId}&courseId=${item.courseId}`)}"
+            >
+                <h2>{{ item.className }}</h2>
+                <div class="svg-text">
+                    <img src="@/assets/people.svg" width="24px" height="24px"></img> 
+                    <span>:</span>
+                    <span>{{ item.stuNumber }}</span>
+                </div>
+                <div class="svg-text">
+                    <img src="@/assets/time.svg" width="24px" height="24px"></img> 
+                    <span>:</span>
+                    <span>{{ item.classTime }}</span>
+                </div>
+                <div class="svg-text">
+                    <img src="@/assets/plus-square.svg" width="24px" height="24px"></img> 
+                    <span>:</span>
+                    <span>{{item.classCode}}</span>
+                </div>
+                <div class="del">
+                    <button :disabled="item.stuNumber!==0"
+                            @click.stop="handleDelete(item.classId)"
+                            :title="item.stuNumber!==0 ? '仍有学生剩余无法直接删除班级' : ''"
+                    >
+                     Del
+                    </button>
+                </div>
+            </a>
+            <a class="plus"
+                @mouseenter="activeId=-2" 
+                @mouseleave="activeId=-1" 
+                @click="visible_add=true"
+                :class="{'shadow_unactive':-2!==activeId,'shadow_active':-2===activeId}">
+                <img src="@/assets/add.svg"></img> 
+            </a>
+        </div>
+    </div>
+
+    <div v-if="visible_del" class="modal-mask" @click.self="isloading || (visible_del=false)" >
+        <div class="modal-container">
+            <div class="modal-header">
+                <h2>{{isloading?"删除中...":"你确定要删除"+deleteName+"吗?"}}</h2>
+            </div>
+            <div class="modal-footer">
+                <button class="yes-button" @click.stop="DeleteClass" :disabled="isloading">确定</button>
+                <button class="no-button" @click.stop="visible_del=false;" :disabled="isloading">取消</button>
+            </div>
+        </div>
+    </div>
+
+    <div v-if="visible_add" class="modal-mask" @click.self="isloading_add || (visible_add=false)" >
+        <div class="modal-container">
+            <div class="add1" v-show="!isloading_add">
+                <h2>班级名称:</h2> <input type="text" v-model.trim="addClassName" placeholder="班级名称建议小于6个字"></input>
+            </div>
+            <div class="add1" v-show="isloading_add" 
+            :style="{'height': '66%', 'margin-left':'210px'}">
+                <h2>上传中...</h2>
+            </div>
+            <div class="add1" v-show="!isloading_add">
+                <h2>上课时间:</h2>
+                <select id="addWeekdays" v-model="addClassDay">
+                    <option value="">Weekdays</option>
+                    <option value="Mon">Mon</option>
+                    <option value="Tue">Tue</option>
+                    <option value="Wed">Wed</option>
+                    <option value="Thu">Thu</option>
+                    <option value="Tue">Fri</option>
+                    <option value="Wed">Sat</option>
+                    <option value="Thu">Sun</option>
+                </select>
+                <span>-</span>
+                <select id="addTime" v-model="addClassClock">
+                    <option value="">Time</option>
+                    <option value="08:00">08:00</option>
+                    <option value="10:10">10:10</option>
+                    <option value="14:00">14:00</option>
+                    <option value="16:10">16:10</option> 
+                </select>
+            </div>
+            <div class="modal-footer add2">
+                <button class="yes-button" @click.stop="AddClass" :disabled="isloading_add">确定</button>
+                <button class="no-button" @click.stop="visible_add=false" :disabled="isloading_add">取消</button>
+            </div>
+        </div>
+    </div>
+
+
+</template>
+
+<script setup lang="ts">
+import {onMounted, reactive, ref, computed } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { useStore } from "@/store"
+import classHeader from './components/classHeader.vue';
+import type {CourseDetailVO, ClassVO} from "@/types/vo";
+import useApis from "@/apis";
+
+const route = useRoute()
+const store = useStore()
+const router = useRouter()
+const apis = useApis()
+const courseId = +route.params.courseId;
+
+const data = reactive<ClassVO[]>([])
+const Coursedata = reactive<CourseDetailVO>({
+  courseId: null,
+  courseName: '',
+  teacherIds: [],
+  teacherNames: [],
+  courseImages: [''],
+  targetGrade: '',
+  startTime: '',
+  endTime: '',
+  createTime: '',
+  description: '',
+})
+let activeId = ref(-1)
+let isloading = ref(false)
+let isloading_add = ref(false)
+let visible_del = ref(false)
+let visible_add = ref(false)
+let deleteName = ref("")
+let deleteId = ref(-1)
+let addClassName = ref("")
+let addClassDay = ref("")
+let addClassClock = ref("")
+
+let CourseDataLoading = ref(false)
+let ClassDataLoading = ref(false)
+
+const handleDelete = (cid) => {
+    visible_del.value = true
+    deleteId.value = cid
+    deleteName.value = data.find((item)=>{
+        return item.classId === cid
+    }).className
+}
+const DeleteClass = async () =>{
+    isloading.value = true
+    console.log(deleteId.value);
+    await apis.deleteClass(deleteId.value)
+    visible_del.value=false
+    isloading.value = false
+    getClass()
+}
+const AddClass = async () =>{
+    if(addClassName.value!==""&&addClassDay.value!=""&&addClassClock.value!=""){
+        isloading_add.value = true
+        await apis.createClassOfCourse(courseId,addClassName.value,addClassDay.value+" "+addClassClock.value,Coursedata.teacherIds[0])
+        isloading_add.value = false
+        visible_add.value=false
+        addClassName.value = ""
+        addClassDay.value = ""
+        addClassClock.value = ""    
+        getClass()
+    }
+    else{
+        alert("名称或时间为空,添加失败")
+    }
+}
+const identify = () => {
+    if(store.user.role === "STUDENT"){
+        router.push('/404')
+    }
+}
+const getClass = () => {
+    // console.log(courseId);
+    data.length=0
+    apis.getClassOfCourse(courseId)
+        .then((res: any)=>{
+            console.log("getclass:",res.data.data)
+            res.data.data.forEach(item => data.push(item))
+            ClassDataLoading.value = true
+        })
+        .catch((err:any) => {
+            console.error(err)
+        })
+}
+const getCourse = () => {
+  // 获取课程信息
+  apis.getCourseByCourseId(courseId)
+      .then((res: any) => {
+        let data = res.data.data.records[0]
+        Object.assign(Coursedata, {...data})
+        CourseDataLoading.value = true
+      })
+      .catch((err: any) => {
+        console.error(err);
+      })
+}
+const DataLoading = computed(() => {
+  return CourseDataLoading.value && ClassDataLoading.value
+})
+
+onMounted(() => {
+    identify()
+    getClass()
+    getCourse()
+})
+</script>
+
+<script lang="ts">
+export default {
+  name: "CourseClass"
+}
+</script>
+
+<style scoped>
+.course-class {
+    width: 66vw;
+    height: 65vh;
+    background-color: #fff;
+    border-radius: 15px;
+    flex-shrink: 0;
+    margin: 0 auto;
+    margin-top: 4.6vh;
+    border-bottom: 1px solid #70bbdb;
+    padding:  7vh 5.5vw;
+}
+.content {
+    height: 50vh;
+    width: 100%;
+    /* border:1px solid; */
+    margin: 0 auto;
+    margin-top: 30px;
+    margin-bottom: 10vh;
+    display: flex;
+    align-items: center;
+    flex-direction: row;
+    flex-wrap: wrap;
+    justify-content: start;
+    padding: 10px;
+    gap: 30px;
+    overflow: auto;
+    .content-item{
+        width: 160px;
+        height: 260px;
+        border-radius: 25px;
+        border: 20px solid #70bbdb; 
+        cursor: pointer;
+        transition: all 0.3s ease;
+        margin-left: 20px;
+        margin-bottom: 20px;
+        h2 {
+            color: #4a90c4;
+            margin-left: 15px;
+            margin-right: 15px;
+            margin-top: 25px;
+            white-space: nowrap;      /* 强制不换行 */
+            overflow: hidden;        /* 隐藏超出部分 */
+            text-overflow: ellipsis;
+        }
+        .svg-text{
+            display: flex;
+            align-items: center;
+            font-size: 16px;
+            color:#4a90c4;
+            font-weight: 600;
+            margin:20px 10px;
+            margin-left: 15px;
+            span {
+                margin-left: 10px;
+            }
+        }
+        .del{
+            display: flex;
+            flex-direction: row;
+            justify-content: end;
+            button{
+                background-color: #c45c4a;
+                color: #fff;
+                border: 0;
+                padding: 3px 20px;
+                font-size: 18px;
+                font-weight: 600;
+                border-radius: 20px;
+                margin-right: 20px;
+            }
+            button:disabled{
+                background-color: #d9d9d9;
+            }
+        }
+    }
+    a:active{
+        box-shadow: none;
+        border: 25px solid #4a90c4; 
+    }
+    .plus{
+        width: 160px;
+        height: 260px;
+        border-radius: 25px;
+        border: 20px solid #70bbdb; 
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        margin-left: 30px;
+        margin-bottom: 20px;
+    }
+}
+.shadow_unactive{
+    box-shadow: -20px 20px 20px 0 rgba(0, 0, 0, 0.25);
+}
+.shadow_active{
+    box-shadow: -20px 20px 20px 0 #B9DDF5;
+}
+.scroll{
+    scrollbar-width: thin;
+    scrollbar-color: rgba(0, 123, 255, 0.2) transparent;
+}
+
+.modal-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 9999;
+    .modal-container {
+        border-radius: 30px;
+        border: 7px solid #70BBDB;
+        background: #FFF;
+        box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25);
+        width: 35vw;
+        height: 25vh;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+        .modal-header {
+            padding: 16px 20px;
+            height: 50%;
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+            color:#70bbdb
+        }
+        .modal-footer {
+            padding: 12px 20px;
+            height: 50%;
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+            gap: 50px;
+            .yes-button{
+                background-color: #c45c4a;
+                color: #fff;
+                border: 0;
+                padding: 3px 25px;
+                font-size: 22px;
+                font-weight: 600;
+                border-radius: 20px;
+                margin-right: 20px;
+            }
+            .no-button{
+                background-color: #4a90c4;
+                color: #fff;
+                border: 0;
+                padding: 3px 25px;
+                font-size: 22px;
+                font-weight: 600;
+                border-radius: 20px;
+                margin-right: 20px;
+            }
+            button{
+                cursor: pointer;
+            }
+            button:disabled{
+                background-color: #d9d9d9;
+            }
+        }
+        .add1 {
+            height: 33%;
+            display: flex;
+            flex-direction: row;
+            justify-content: start;
+            align-items: center;
+            color:#70bbdb;
+            margin-left: 80px;
+            input,select {
+                border: none;
+                border-bottom: 3px solid #70bbdb;
+                font-size: 16px;
+                font-weight: 600;
+                color:#70bbdb
+            }
+            input::placeholder {
+                color:#b2d7e7
+            }
+            input:focus,select:focus {
+                outline: none;
+                border: none;
+                outline: 5px solid #70bbdb;
+            }
+            span{
+                font-size: 16px;
+                font-weight: 600;
+                margin: 0 15px;
+            }
+        }
+        .add2 {
+            height: 33%;
+        }
+    }   
+}
+</style>

+ 12 - 0
src/views/ClassPage/StudentClass.vue

@@ -0,0 +1,12 @@
+<template>
+    <div>
+        <h1>学生班级</h1>
+    </div>
+</template>
+
+<script setup lang="ts">
+
+</script>
+
+<style scoped>
+</style>

+ 531 - 0
src/views/ClassPage/StudentDetail.vue

@@ -0,0 +1,531 @@
+<template>
+    <div class="course-class" v-show="DataLoading">
+        <classHeader>{{store.user.role === 'STUDENT'?Coursedata.courseName:Studentdata.stuName}}:{{store.user.role === 'STUDENT'?Classdata.className: Studentdata.officialNumber }} 
+            <template #TeacherName v-if="store.user.role === 'STUDENT'">{{ Coursedata.teacherNames[0] }}</template>
+        </classHeader>
+        <div class="content">
+            <div class="search">
+                <img src="@/assets/search.svg" width="20px" height="20px"></img> 
+                <div class="searchInput">
+                    <input type="text" v-model.trim="searchInput" placeholder="输入作业序号或作业名称">
+                    <div class="fix">
+                        <span>搜索</span>
+                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#b8b8b8" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
+                    </div>
+                </div>
+                <div class="Myselect">
+                    <select id="selectSort" v-model="TheSort" > 
+                        <option value="无">排序方式</option>
+                        <option v-for="item in sortMethods" :value="item">{{item}}</option>
+                    </select>
+                    <div class="show">
+                            {{ ShowTheSort }}
+                    </div>
+                </div>
+            </div>
+            <div class="table">
+                <div class="table-half left-half">
+                    <classTable>
+                        <template #head>
+                            <tr>
+                                <th>序号</th>
+                                <th>作业列表</th>
+                                <th>成绩</th>
+                            </tr>
+                        </template>
+                        <template #body>
+                            <tr v-for="(item,index) in showData.slice(0,5)" :key="index+item.assignmentName">
+                                <template v-if="item.status!==''">
+                                    <td>{{ (currentPage-1)*10+index+1 }}</td>
+                                    <td>{{ item.assignmentName }}</td>
+                                    <td @click="$router.push(`/home/assignment/${item.assignmentId}/correct?studentId=${studentId}`)">
+                                        <div class="highScore" v-if="item.score>=80"> {{ item.score }}</div>
+                                        <div class="midScore" v-else-if="item.score>=60"> {{ item.score }}</div>
+                                        <div class="lowScore" v-else-if="item.score>0"> {{ item.score }}</div>
+                                        <div class="uncorrected" v-else-if="item.status==='SUBMITTED'"> 未批改</div>
+                                        <div class="unsubmitted" v-else-if="item.status==='NOT_SUBMITTED'"> 未提交</div>      
+                                    </td>
+                                </template>
+                                <template v-else>
+                                    <td>&nbsp;</td>
+                                    <td>&nbsp;</td>
+                                    <td>&nbsp;</td>
+                                </template>
+                            </tr>
+                        </template>
+                    </classTable>
+                </div>
+                <div class="table-half right-half">
+                    <classTable>
+                        <template #head>
+                            <tr>
+                                <th>序号</th>
+                                <th>作业列表</th>
+                                <th>成绩</th>
+                            </tr>
+                        </template>
+                        <template #body>
+                            <tr v-for="(item,index) in showData.slice(5,10)" :key="index+item.assignmentName">
+                                <template v-if="item.status!==''">
+                                    <td>{{ (currentPage-1)*10+index+6 }}</td>
+                                    <td>{{ item.assignmentName }}</td>
+                                    <td @click="$router.push(`/home/assignment/${item.assignmentId}/correct?studentId=${studentId}`)">
+                                        <div class="highScore" v-if="item.score>=80"> {{ item.score }}</div>
+                                        <div class="midScore" v-else-if="item.score>=60"> {{ item.score }}</div>
+                                        <div class="lowScore" v-else-if="item.score>0"> {{ item.score }}</div>
+                                        <div class="uncorrected" v-else-if="item.status==='SUBMITTED'"> 未批改</div>
+                                        <div class="unsubmitted" v-else-if="item.status==='NOT_SUBMITTED'"> 未提交</div>      
+                                    </td>
+                                </template>
+                                <template v-else>
+                                    <td>&nbsp;</td>
+                                    <td>&nbsp;</td>
+                                    <td>&nbsp;</td>
+                                </template>
+                            </tr>
+                        </template>
+                    </classTable>
+                </div>
+            </div>
+            <div class="pages">
+                <el-pagination
+                    v-model:current-page="currentPage"
+                    :page-size="10"
+                    :pager-count="5"
+                    layout="total, prev, pager, next"
+                    :total="searchData.length"
+                />
+            </div>
+        </div>
+    </div>
+    <div class="loading" v-show="!DataLoading"> Loading...</div>
+</template>
+
+<script setup lang="ts">
+import classTable from './components/classTable.vue';
+import {onMounted, reactive, ref, computed, watch, nextTick} from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { useStore } from "@/store"
+import classHeader from './components/classHeader.vue';
+import type {CourseDetailVO, ClassVO, ClassStudentVO} from "@/types/vo";
+import useApis from "@/apis";
+
+const route = useRoute()
+const store = useStore()
+const router = useRouter()
+const apis = useApis()
+const classId = +route.query.classId
+const courseId = +route.query.courseId
+const studentId = +route.query.stuId
+
+const currentPage = ref(1)
+const searchInput = ref("")
+const TheSort = ref("无")
+const ShowTheSort = ref("默认")
+
+const StudentdataLoading = ref(false)
+const CourseDataLoading = ref(false)
+const ClassDataLoading = ref(false)
+
+const Coursedata = reactive<CourseDetailVO>({
+  courseId: null,
+  courseName: '',
+  teacherIds: [],
+  teacherNames: [],
+  courseImages: [''],
+  targetGrade: '',
+  startTime: '',
+  endTime: '',
+  createTime: '',
+  description: '',
+})
+
+const Classdata = ref<ClassVO>({
+  classId: null,
+  className: '',
+  courseId: null,
+  stuNumber: null,
+  classTime: '',
+  teacherId: null,
+})
+const sortMethods = ["默认","成绩从低到高","成绩从高到低","未批改优先","未提交优先"]
+// const Studentdata = ref({
+//     "officialNumber": 522025312092,
+//     "stuName": "张伟",
+//     "studentId": studentId,
+//     "assignments": [
+//         {
+//             "assignmentId": 73,
+//             "assignmentName":"第一次作业",
+//             "status": "CORRECTED",
+//             "score": 88
+//         },
+//         {
+//             "assignmentId": 73,
+//             "assignmentName":"第二次作业",
+//             "status": "CORRECTED",
+//             "score": 91
+//         },
+//         {
+//             "assignmentId": 73,
+//             "assignmentName":"第三次作业",
+//             "status": "CORRECTED",
+//             "score": 94
+//         },
+//         {
+//             "assignmentId": 73,
+//             "assignmentName":"期中作业",
+//             "status": "CORRECTED",
+//             "score": 59
+//         },
+//         {
+//             "assignmentId": 73,
+//             "assignmentName":"第五次作业",
+//             "status": "CORRECTED",
+//             "score": 79
+//         },
+//         {
+//             "assignmentId": 73,
+//             "assignmentName":"第六次作业",
+//             "status": "NOT_SUBMITTED",
+//             "score": 0
+//         },
+//         {
+//             "assignmentId": 73,
+//             "assignmentName":"期末考试",
+//             "status": "SUBMITTED",
+//             "score": 0
+//         }
+//     ]
+// })
+
+// const identify = () => {
+//     if(store.user.role === "STUDENT"){
+//         router.push('/404')
+//     }
+// }
+const Studentdata = ref({
+    "officialNumber": 0,
+    "stuName": "",
+    "studentId": 0,
+    "assignments":[]
+})
+const getStudent = () => {
+    StudentdataLoading.value = false
+    console.log(courseId)
+    console.log(studentId)
+    apis.getHomeworkOfStudent(courseId,studentId)
+        .then((res:any)=>{
+            Studentdata.value = res.data.data
+            console.log(res.data.data);
+            StudentdataLoading.value = true
+        })
+}
+const getClass = () => {
+    console.log("classId:",classId)
+    apis.getClassByClassId(classId)
+        .then((res:any)=>{
+            console.log(res.data.data);
+            Classdata.value = res.data.data
+            console.log("Classdata",Classdata.value);
+            
+        })
+
+    ClassDataLoading.value = true
+}
+
+const getCourse = () => {
+  // 获取课程信息
+  apis.getCourseByCourseId(courseId)
+      .then((res: any) => {
+        let data = res.data.data.records[0]
+        Object.assign(Coursedata, {...data})
+        CourseDataLoading.value = true
+      })
+      .catch((err: any) => {
+        console.error(err);
+      })
+}
+
+const DataLoading = computed(() => {
+//   return CourseDataLoading.value && ClassDataLoading.value
+    if(store.user.role === "STUDENT"){
+        return StudentdataLoading.value && CourseDataLoading.value && ClassDataLoading.value
+    }
+    else{
+        return StudentdataLoading.value
+    }
+})
+const searchData = computed(()=>{
+    if(searchInput.value===""){
+        return Studentdata.value.assignments
+    }
+    else{
+        return Studentdata.value.assignments.filter(student => {
+          return (
+            student.assignmentName.includes(searchInput.value)
+          )
+        })
+    }
+})
+const showData = computed(() => {
+    const showdata = searchData.value.slice((currentPage.value-1)*10,currentPage.value*10)
+    while(showdata.length<10){
+        showdata.push(    {
+        "assignmentId": -1,
+        "assignmentName": "",
+        "status": "",
+        "score": 0
+        })
+    }
+    return showdata
+})
+watch(TheSort, () => {
+    nextTick(() => {
+        // console.log(ShowTheSort.value,TheSort.value);
+        if(TheSort.value != "无"){
+            ShowTheSort.value = TheSort.value
+            TheSort.value = "无"
+        }
+        // console.log(ShowTheSort.value,TheSort.value);
+    });
+});
+watch(ShowTheSort,(newData) =>{
+    if(newData==="未批改优先"){
+        Studentdata.value.assignments = Studentdata.value.assignments.sort((a,b)=>{
+            return a.status === "SUBMITTED" ? -1 : b.status === "SUBMITTED" ? 1 : 0;
+        })
+    }
+    else if(newData==="未提交优先"){
+        Studentdata.value.assignments = Studentdata.value.assignments.sort((a,b)=>{
+            return a.status === "NOT_SUBMITTED" ? -1 : b.status === "NOT_SUBMITTED" ? 1 : 0;
+        })
+    }
+    else if(newData==="成绩从低到高"){
+        Studentdata.value.assignments = Studentdata.value.assignments.sort((a,b)=>{
+            if (a.status === "CORRECTED" && b.status !== "CORRECTED") return -1;
+            if (a.status !== "CORRECTED" && b.status === "CORRECTED") return 1;
+            return a.score - b.score;
+        })
+    }
+    else if(newData==="成绩从高到低"){
+        Studentdata.value.assignments = Studentdata.value.assignments.sort((a,b)=>{
+            if (a.status === "CORRECTED" && b.status !== "CORRECTED") return -1;
+            if (a.status !== "CORRECTED" && b.status === "CORRECTED") return 1;
+            return b.score - a.score;
+        })
+    }
+})
+
+onMounted(() => {
+    // identify()
+    if(store.user.role === "STUDENT"){
+        getCourse()
+        getClass()
+    }
+    getStudent()
+    
+})
+</script>
+
+<script lang="ts">
+export default {
+  name: "ClassDetail"
+}
+</script>
+
+<style scoped>
+.course-class {
+    width: 66vw;
+    height: 65vh;
+    background-color: #fff;
+    border-radius: 15px;
+    margin: 0 auto;
+    margin-top: 4.6vh;
+    border-bottom: 1px solid #70bbdb;
+    padding:  7vh 5.5vw;
+    .content {
+        height: 55vh;
+        width: 100%;
+        /* border:1px solid; */
+        margin: 0 auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: start;
+        padding: 10px;
+        .search{
+            height: 50px;
+            width: 100%;
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-start;
+            align-items: center;
+            margin-top: 20px;
+            .searchInput{
+                height: 100%;
+                display: flex;
+                flex-direction: row;
+                justify-content: flex-start;
+                align-items: center;
+                margin-left: 20px;
+                input{
+                    border: 2px solid #e4e7ed;
+                    border-right: none;
+                    height: 27px;
+                    border-radius: 7px 0 0 7px;
+                    padding: 0 10px;
+                    color: #606266;
+                }
+                input::placeholder {
+                    color:#c2c4c9;
+                }
+                .fix{
+                    background-color: #f5f7fa;
+                    padding: 2px 10px;
+                    color:#b8b8b8;
+                    font-weight: 600;
+                    border: 2px solid #e4e7ed;
+                    border-radius: 0 7px 7px 0;
+                }
+            }
+            .Myselect{
+                margin-left: 15px;
+                display: flex;
+                flex-direction: row;
+                select {
+                    border: none;
+                    background-color: #4a90c4;
+                    font-size: 14px;
+                    font-weight: 600;
+                    color:#fff;
+                    padding: 3px 10px;
+                    padding-right: 0;
+                    border-radius: 5px 0 0 5px;
+                    max-width: 100px;
+                }
+                select:focus {
+                    outline: none;
+                    border: none;
+                    background-color: #FFF;
+                    border: 3px solid #4a90c4;
+                    border-right: 0;
+                    color:#4a90c4;
+                }
+                .show{
+                    width: fit-content;
+                    color: #4a90c4;
+                    border: 3px solid #4a90c4;
+                    font-weight: 600;
+                    padding-left: 6px;
+                    padding-right: 5px;
+                    border-radius: 0 5px 5px 0;
+                }
+            }
+            .toggle{
+                margin-left: 20px;
+                .left{
+                    border: 3px solid #4A90C4;
+                    border-radius: 20px 0 0 20px;
+                    border-right: 0;
+                    padding: 2px 7px;
+                    font-weight: 700;
+                    cursor: pointer;
+                }
+                .right{
+                    border: 3px solid #4A90C4;
+                    border-radius: 0 20px 20px 0;
+                    padding: 2px 7px;
+                    font-weight: 700;
+                    cursor: pointer;
+                }
+                .unactive{
+                    color: #70BBDB;
+                    background-color: #fff;
+                }
+                .active{
+                    color: #fff;
+                    background-color:#4A90C4;
+                }
+            }
+        }
+        .table{
+            flex-grow: 1;
+            width: 100%;
+            display: flex;
+            flex-direction: row;
+            margin-top: 20px;
+            .table-half{
+                width: 50%;
+                height: 100%;
+                border: 5px solid #70bbdb;
+                padding: 15px;
+                padding-top: 0;
+                td{
+                    height:45px;
+                }
+                .highScore{
+                    padding: 2px 10px;
+                    background-color: #7ADBB0;   
+                    color: #fff;
+                    border-radius: 20px;
+                    width: fit-content;
+                    margin: auto;
+                }
+                .midScore{
+                    padding: 2px 10px;
+                    background-color: #F0C05A;   
+                    color: #fff;
+                    border-radius: 20px;
+                    width: fit-content;
+                    margin: auto;
+                }
+                .lowScore{
+                    padding: 2px 10px;
+                    background-color: #F4A261;   
+                    color: #fff;
+                    border-radius: 20px;
+                    width: fit-content;
+                    margin: auto;
+                }
+                .unsubmitted{
+                    padding: 2px 10px;
+                    background-color: #C45C4A;   
+                    color: #fff;
+                    border-radius: 20px;
+                    width: fit-content;
+                    margin: auto;
+                }
+                .uncorrected{
+                    padding: 2px 10px;
+                    background-color: #D9D9D9;   
+                    color: #fff;
+                    border-radius: 20px;
+                    width: fit-content;
+                    margin: auto;
+                }
+            }
+            .left-half{
+                border-right: 0;
+                border-radius: 10px 0 0 10px;
+            }
+            .right-half{
+                border-left: 5px solid #B9DDF5;
+                border-radius: 0 10px 10px 0;
+            }
+        }
+        .pages{
+            height: 30px;
+            margin-top: 40px;
+        }
+    }
+}
+.loading{
+    height: 100%;
+    width: 100%;
+    text-align: center;
+    line-height: 100vh;
+    font-size: 44px;
+    font-weight: 700;
+    color: #70bbdb;
+}
+</style>

+ 54 - 0
src/views/ClassPage/components/classHeader.vue

@@ -0,0 +1,54 @@
+<template>
+    <div class="class-header">
+        <div class="title title_border">
+            <slot></slot>
+        </div>
+        <div class="title title_border"style="margin-left:-200px;">
+            <slot name="TeacherName"></slot>
+        </div>
+        <div class="title" @click="$router.go(-1)" style="padding-right: 4.1vw;cursor: pointer;">
+            <el-icon style="vertical-align: bottom; margin-right: 6px;"><ArrowLeftBold/></el-icon>
+            返回
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ArrowLeftBold } from '@element-plus/icons-vue'
+</script>
+
+<script lang="ts">
+export default {
+  name: "classHeader"
+}
+</script>
+
+<style scoped>
+.class-header {
+    width: '100%';
+    height: 7.5vh;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    padding-left: 0.5vw;
+    margin-top: -15px;
+}
+.title{
+    /* 字体设置 一级标题 */
+    color:#70BBDB;
+    font-family: Inter;
+    font-size: 28px;
+    font-style: normal;
+    font-weight: 700;
+    line-height: 24px;
+    height:28px;
+    padding-bottom: 10px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+}
+.title_border{
+    border-bottom: 5px solid #4FA3C5;
+}
+</style>

+ 50 - 0
src/views/ClassPage/components/classTable.vue

@@ -0,0 +1,50 @@
+<template>
+    <div class="class-table-zcx">
+        <table>
+            <slot name="head"></slot>
+            <slot name="body"></slot>
+        </table>
+    </div>
+</template>
+
+<script setup lang="ts">
+
+</script>
+
+<script lang="ts">
+export default {
+  name: "classTable"
+}
+</script>
+
+<style>
+.class-table-zcx {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+    table{
+      width: 100%;
+      height: 100%;
+      th{
+        font-size: 16px;
+        font-weight: 700;
+        color: #70bbdb;
+        border: none;
+        border-bottom: 3px solid #70bbdb;
+      }
+      td{
+        font-size: 14px;
+        color: #606266;
+        border: none;
+        border-bottom: 2px solid #e4e7ed;
+        text-align: center;
+      }
+      .Gethover {
+        background-color: #f0f2f5;
+      }
+      .Blue {
+        color: #4a90c4;
+      }
+    }
+}
+</style>

+ 62 - 0
src/views/ClassPage/components/classToggle.vue

@@ -0,0 +1,62 @@
+<template>
+    <div class="toggle">
+        <button class="left"
+                :class="{'active':isLeft,'unactive':!isLeft}"
+                @click="toggle(true)"
+        ><slot name="left"></slot></button>
+        <button class="right"
+                :class="{'active':!isLeft,'unactive':isLeft}"
+                @click="toggle(false)"
+        ><slot name="right"></slot></button>
+    </div>
+</template>
+
+<script setup lang="ts">
+const props = defineProps({
+  isLeft: {
+    type: Boolean,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:isLeft'])
+
+const toggle = (value) => {
+    emit('update:isLeft', value)
+}
+</script>
+
+<script lang="ts">
+export default {
+  name: "classToggle"
+}
+</script>
+
+<style>
+.toggle{
+    margin-left: 20px;
+    .left{
+        border: 3px solid #4A90C4;
+        border-radius: 20px 0 0 20px;
+        border-right: 0;
+        padding: 2px 7px;
+        font-weight: 700;
+        cursor: pointer;
+    }
+    .right{
+        border: 3px solid #4A90C4;
+        border-radius: 0 20px 20px 0;
+        padding: 2px 7px;
+        font-weight: 700;
+        cursor: pointer;
+    }
+    .unactive{
+        color: #70BBDB;
+        background-color: #fff;
+    }
+    .active{
+        color: #fff;
+        background-color:#4A90C4;
+    }
+}
+</style>

+ 598 - 53
src/views/CorrectPage/CorrectPage.vue

@@ -4,36 +4,229 @@
   Created Date: 2024-7-24
 -->
 <template>
+  <div class="correct-page">
+    <!-- 最左侧AI对话区域 -->
+    <div class="ai-chat-panel">
+      <div class="chat-header">
+        <h3 class="chat-title">学生与AI对话记录</h3>
+      </div>
+      
+      <div class="chat-messages">
+        <div 
+          v-for="(message, index) in formattedMessages" 
+          :key="index"
+          class="message-group"
+        >
+          <!-- 用户消息 -->
+          <div v-if="message.role === 'user'" class="user-message">
+            <div class="message-info">
+              <div class="user-name">Student</div>
+              <div class="message-bubble user-bubble">
+              {{ message.content }}
+              </div>
+            </div>
+            <div class="message-avatar user-avatar">
+              <span>S</span>
+            </div>
+          </div>
+          
+          <!-- AI消息 -->
+          <div v-if="message.role === 'assistant'" class="ai-message">
+            <div class="message-avatar ai-avatar">
+              <span>C</span>
+            </div>
+            <div class="message-info">
+              <div class="ai-name">ChatGLM4</div>
+              <div class="message-bubble ai-bubble">
+                {{ message.content }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+       <div class="chat-footer"></div>
+    </div>
+  
+    <!-- 右侧内容区域 -->
+    <div class="right-content">
+      <!-- 作业描述区域 -->
+      <div class="content-panel">
+        <!-- 上半部分:作业描述 -->
+        <div class="assignment-section">
+          <div class="section-header">
+            <h2>英语写作练习</h2>
+          </div>
+          <div class="assignment-content">
+            <StudentWritingRequirements />
+          </div>
+        </div>
+      </div>
 
-  <div class="my-correct">
-    <!-- 写作题目 及 作文内容 -->
-
-    <div class="my-correct-header">
-      <el-card>
-        <StudentWritingRequirements />
-      </el-card>
-      <div style="height: 10px"></div>
-      <el-card>
-        <StudentEssay :studentId="studentId" :assignmentId="assignmentId" />
-      </el-card>
+      <!-- 学生信息和回答区域 -->
+      <div class="student-info-section">
+        <div class="student-header">
+          <span class="student-label">学生姓名:</span>
+          <span class="student-name">{{ studentInfo.name }}</span>
+          <span class="class-label">所属班级:</span>
+          <span class="class-name">{{ studentInfo.className || '信息与计算科学(强基计划)' }}</span>
+          <span 
+            v-if="!isStudent && isLateSubmission" 
+            class="late-submission-badge"
+          >
+            补交
+          </span>
+        </div>
+        <div class="student-answer-content">
+          {{ studentAnswer }}
+        </div>
+      </div>
     </div>
 
-    <div class="right-side">
-      <!-- update: 仅保留AI历史对话 -->
-      <div class="my-correct-record">
-        <h2>学生与AI对话历史记录</h2>
-        <AIChatter :dialogueId="dataForm.dialogueId" :messages="dataForm.messages" :is-teacher="true" />
-        <el-button @click="showDialog = true">查看行为记录</el-button>
+    <!-- 教师功能区域 -->
+    <div v-if="!isStudent" class="teacher-function-area">
+      <!-- 查看行为记录按钮 -->
+      <el-button 
+        type="primary" 
+        class="behavior-record-btn"
+        @click="handleBehaviorRecord"
+      >
+        查看行为记录
+      </el-button>
+      
+      <!-- 评分区域 -->
+      <div class="rating-section">
+        <div class="rating-component">
+          <div class="rating-input-wrapper">
+            <span style="font-size: 22px; font-weight: 500; margin-left: -20px;">评分:&nbsp;&nbsp;</span>
+            <div class="score-counter">
+              <button 
+                class="score-btn decrease-btn" 
+                @click="decreaseScore"
+                :disabled="correctionData.score <= 1"
+              >
+                −
+              </button>
+              <input 
+                type="number" 
+                class="score-input" 
+                :min="1" 
+                :max="100" 
+                placeholder="暂无分数" 
+                title="请输入1~100间的数字"
+                v-model="correctionData.score" 
+                @input="validateScore" 
+              />
+              <button 
+                class="score-btn increase-btn" 
+                @click="increaseScore"
+                :disabled="correctionData.score >= 100"
+              >
+                +
+              </button>
+            </div>
+          </div>
+          <div v-if="scoreError" class="error-message">{{ scoreError }}</div>
+        </div>
+      </div>
+      
+      <!-- 评语区域 -->
+      <div class="remark-section">
+        <div class="textarea-container">
+          <textarea 
+            v-model="correctionData.remark" 
+            placeholder="请输入评语" 
+            class="remark-textarea"
+          ></textarea>
+        </div>
+        
+        <!-- 提交批改和评语模板按钮 -->
+        
       </div>
 
-      <!-- 教师评分、修改意见及评价 -->
-      <div class="my-correct-remark">
-        <Remark :studentId="studentId" :assignmentId="assignmentId" />
+      <div class="button-section">
+        <!-- 第一行按钮 -->
+        <div class="button-row">
+          <el-button 
+            class="action-btn submit-btn"
+            @click="handleCorrect"
+            :loading="submitting"
+          >
+            提交评语
+          </el-button>
+          <el-button 
+            class="action-btn next-btn"
+            @click="handleNext"
+            :disabled="isAllCorrected"
+          >
+            下一个
+          </el-button>
+        </div>
+        
+        <!-- 第二行按钮 -->
+        <div class="button-row">
+          <el-button 
+            class="action-btn template-btn"
+            @click="showTemplateDialog"
+          >
+            评语模板
+          </el-button>
+          <el-button 
+            class="action-btn back-btn"
+            @click="goBack"
+          >
+            返回
+          </el-button>
+        </div>
+        
+        <!-- 进度条 -->
+        <div class="progress-section">
+          <el-progress 
+            :percentage="progressPercentage" 
+            :stroke-width="20"
+            color="#7FD67F"
+            class="progress-bar"
+          />
+        </div>
+        
+        <!-- 进度文字 -->
+        <div class="progress-text">
+          已批改/未批改:{{ correctionStats.correctNumber }}/{{ uncorrectedNumber }}
+        </div>
       </div>
+      
+      <!-- 预留区域:后续可在此添加更多教师功能 -->
     </div>
-  </div>
-
-  <el-dialog v-model="showDialog" title="文件信息">
+    
+    <!-- 学生功能区域 -->
+    <div v-if="isStudent" class="student-function-area">
+      <!-- 老师批改分数展示 -->
+      <div class="student-score-display">
+        <div class="score-label">评分:</div>
+        <div class="score-container">
+          <div class="score-value">{{ correctionData.score || '暂无评分' }}</div>
+        </div>
+      </div>
+      
+      <!-- 老师评语展示 -->
+      <div class="student-remark-display">
+        <div class="remark-content">
+          {{ correctionData.remark || '老师暂未给出评语' }}
+        </div>
+      </div>
+      
+      <!-- 学生返回按钮区域 -->
+      <div class="student-button-area">
+        <el-button 
+          class="student-back-btn"
+          @click="goBackStudent"
+        >
+          返回
+        </el-button>
+      </div>
+    </div>
+    
+    <!-- 行为记录对话框 -->
+        <el-dialog v-model="showDialog" title="文件信息">
 
       <el-table :data="fileList" stripe>
         <el-table-column prop="id" label="ID"></el-table-column>
@@ -52,31 +245,53 @@
         <el-table-column prop="studentId" label="学生 ID"></el-table-column>
       </el-table>
 
-    <template #footer>
+      <template #footer>
       <el-button @click="showDialog = false">取消</el-button>
       <el-button type="primary" @click="downloadAllFiles">导出</el-button>
-    </template>
-  </el-dialog>
-
+      </template>
+      </el-dialog>
+      
+    <!-- 评语模板对话框 -->
+    <el-dialog
+      v-model="templateDialogVisible"
+      title="评语模板"
+      width="50%"
+      :close-on-click-modal="false"
+      class="template-dialog"
+    >
+      <div class="template-list">
+        <div 
+          v-for="(template, index) in remarkTemplates" 
+          :key="index"
+          class="template-item"
+          @click="selectTemplate(template)"
+        >
+          <div class="template-content">
+            {{ index + 1 }}. {{ template }}
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
 </template>
 
 <script setup lang="ts">
-import AIChatter from "@/components/AIChatter/AIChatter.vue";
-import Dictionaries from "@/views/Home/component/Dictionaries/index.vue";
+
 import useApis from "@/apis";
 import { useStore } from "@/store";
 import router from "@/router"
-import { onMounted, reactive, ref, watchEffect } from "vue";
+import { onMounted, reactive, ref, computed } from "vue";
 import type { engagementVO } from "@/types/vo";
 import type { IDialogue } from "@/types/dialogue.ts";
-import Edit from "@/views/Home/component/Edit/index.vue";
-import Slider from '@/views/Home/component/Silder/index.vue'
+import { ElMessage } from 'element-plus';
+
 import "./index.scss"
 import { useRoute } from 'vue-router'
-import StudentEssay from "../Home/component/StudentEssay/StudentEssay.vue";
-import StudentWritingRequirements from "../Home/component/StudentEssay/StudentWritingRequirements.vue";
-import Remark from "../Home/component/Remark/Remark.vue";
-import dayjs from 'dayjs';
+
+// 导入所需组件
+import StudentWritingRequirements from "@/views/Home/component/StudentEssay/StudentWritingRequirements.vue";
+
+import * as dayjs from 'dayjs';
 const formatDateTime = (isoString: string): string => {
   return dayjs(isoString).format('YYYY-MM-DD HH:mm:ss');
 };
@@ -89,10 +304,59 @@ const studentId = Number(route.query.studentId)
 const apis = useApis()
 const store = useStore()
 
+// 弹窗相关变量
 const fileList = reactive([]);
-
 const showDialog = ref(false);
 
+// 评分和评语相关变量
+const correctionData = reactive({
+  score: 0,
+  remark: ''
+});
+const scoreError = ref('');
+const submitting = ref(false);
+
+// 评语模板相关变量
+const templateDialogVisible = ref(false);
+const remarkTemplates = [
+  '希望广大青年坚定理想信念,厚植家国情怀,练就过硬本领,发扬奋斗精神,到祖国和人民最需要的地方发光发热,为中国式现代化建设贡献青春力量',
+  '文章结构清晰,论点明确,语言表达流畅,体现了较好的写作水平。',
+  '内容充实,观点鲜明,但在语法和拼写方面还需要进一步完善。',
+  '思路清晰,表达准确,展现了良好的语言运用能力。',
+  '文章内容丰富,但需要注意段落间的逻辑连接和过渡。'
+];
+
+// 批改进度统计
+const correctionStats = reactive({
+  engageNumber: 0,
+  correctNumber: 0
+});
+
+// 计算批改进度百分比
+const progressPercentage = computed(() => {
+  if (correctionStats.engageNumber === 0) return 0;
+  return Math.round((correctionStats.correctNumber / correctionStats.engageNumber) * 100);
+});
+
+// 计算未批改数量
+const uncorrectedNumber = computed(() => {
+  return correctionStats.engageNumber - correctionStats.correctNumber;
+});
+
+// 判断是否全部批改完成
+const isAllCorrected = computed(() => {
+  return uncorrectedNumber.value === 0;
+});
+
+// 判断当前用户是否为学生
+const isStudent = computed(() => store.user.role === 'STUDENT');
+
+// 判断是否为补交状态
+const isLateSubmission = computed(() => {
+  const status = dataForm.engagement?.status;
+  return status === 'LATE_SUBMITTED' || status === 'LATE_SUBMITTED_CORRECTED';
+});
+
 const dataForm = reactive<{
   messages: IDialogue[],
   dialogueId: string;
@@ -103,6 +367,71 @@ const dataForm = reactive<{
   engagement: {} as engagementVO
 })
 
+// 学生信息
+const studentInfo = reactive({
+  name: '',
+  className: ''
+})
+
+// 学生回答内容
+const studentAnswer = ref('')
+
+// 课程ID(用于返回时保持菜单选中状态)
+const courseId = ref(0)
+
+// 格式化消息数据用于显示
+const formattedMessages = ref<IDialogue[]>([])
+
+// 更新格式化消息
+const updateFormattedMessages = () => {
+  if (!dataForm.messages || dataForm.messages.length === 0) {
+    formattedMessages.value = []
+    return
+  }
+  formattedMessages.value = [...dataForm.messages]
+}
+
+// 获取学生信息
+async function fetchStudentInfo() {
+  try {
+    const response = await apis.findUserById(studentId)
+    if (response.data.code === 200) {
+      const userData = response.data.data.records[0]
+      studentInfo.name = userData.name
+      studentInfo.className = userData.className || ''
+    }
+  } catch (error) {
+    console.error('获取学生信息失败:', error)
+  }
+}
+
+// 获取评分和评语数据
+async function fetchCorrectionData() {
+  try {
+    const response = await apis.getEngagement(studentId, assignmentId)
+    if (response.data.code === 200) {
+      const engagementData = response.data.data
+      correctionData.score = engagementData.score || 0
+      correctionData.remark = engagementData.remark || ''
+    }
+  } catch (error) {
+    console.error('获取评分数据失败:', error)
+  }
+}
+
+// 获取作业详情以获取课程ID
+async function fetchAssignmentDetails() {
+  try {
+    const response = await apis.getAssignmentById(assignmentId)
+    if (response.data.code === 200) {
+      const assignmentData = response.data.data
+      courseId.value = assignmentData.courseId || 0
+    }
+  } catch (error) {
+    console.error('获取作业详情失败:', error)
+  }
+}
+
 // 获取数据
 async function fetchData() {
   await apis.getAIDialogues(assignmentId, studentId)
@@ -113,48 +442,263 @@ async function fetchData() {
         messages: data?.messages.content,
         engagement: dataForm.engagement
       })
+      updateFormattedMessages()
     })
 
   await apis.getEngagement(studentId, assignmentId)
     .then((_res: any) => {
+      const engagementData = _res.data.data
       Object.assign(dataForm, {
         dialogueId: dataForm.dialogueId,
         messages: dataForm.messages,
-        engagement: _res.data.data
+        engagement: engagementData
       })
+      // 获取学生回答内容
+      if (engagementData.textContent) {
+        studentAnswer.value = engagementData.textContent
+      }
     }).catch((_err: any) => {
       console.log(_err)
       // router.push("/login");
       router.go(-1);
     })
+  
+  // 获取学生信息
+  await fetchStudentInfo()
+  
+  // 获取现有的评分和评语
+  await fetchCorrectionData()
+  
+  // 获取作业详情(获取课程ID)
+  await fetchAssignmentDetails()
+  
+  // 获取批改进度统计
+  await fetchCorrectionStats()
+}
+
+// 弹窗相关函数
+const handleBehaviorRecord = () => {
+  showDialog.value = true
+  // 如果数据为空,重新获取
+  if (fileList.length === 0) {
+    getBehaviorRecords()
+  }
 }
 
+// 获取行为记录数据
 const getBehaviorRecords = () => {
   apis.selectBehaviorRecord(studentId, assignmentId)
-      .then(res => {
-        Object.assign(fileList, res.data.data)
-        // fileList.push(res.data.data)
-        // fileList.value = res.data.data
-      })
-      .catch(err => {
-        console.log(err)
-      })
+    .then(res => {
+      console.log('API返回的原始数据:', res.data)
+      const records = res.data.data || []
+      console.log('解析后的记录数组:', records)
+      console.log('记录数量:', records.length)
+      
+      // 清空并重新填充数据(确保响应式更新)
+      fileList.length = 0
+      if (Array.isArray(records)) {
+        records.forEach(record => {
+          fileList.push(record)
+        })
+      } else {
+        console.warn('返回的数据不是数组:', records)
+      }
+      
+      console.log('fileList 最终内容:', fileList)
+      console.log('fileList 长度:', fileList.length)
+    })
+    .catch(err => {
+      console.error('获取行为记录失败:', err)
+      ElMessage.error('获取行为记录失败')
+      fileList.length = 0
+    })
 }
 
+
+
+// 下载单个文件
+const downloadSingleFile = (fileUrl: string) => {
+  if (!fileUrl) {
+    ElMessage.warning('文件链接不存在')
+    return
+  }
+  const link = document.createElement('a')
+  link.href = fileUrl
+  link.download = ''
+  link.target = '_blank'
+  document.body.appendChild(link)
+  link.click()
+  document.body.removeChild(link)
+}
+
+// 批量下载所有文件(参考old文件的实现)
 const downloadAllFiles = () => {
+  if (fileList.length === 0) {
+    ElMessage.warning('暂无文件可下载')
+    return
+  }
+  
+  let downloadCount = 0
   fileList.forEach((item) => {
-    const link = document.createElement('a');
-    link.href = item.recordFileUrl;
-    link.download = '';
-    link.click();
-    link.remove();
+    if (item.recordFileUrl) {
+      downloadSingleFile(item.recordFileUrl)
+      downloadCount++
+    }
+  })
+  
+  if (downloadCount > 0) {
+    ElMessage.success(`开始下载 ${downloadCount} 个文件`)
+  } else {
+    ElMessage.warning('没有可下载的文件链接')
+  }
+  
+  showDialog.value = false
+}
+
+// 验证评分
+const validateScore = () => {
+  const score = Number(correctionData.score)
+  if (isNaN(score) || score < 1 || score > 100) {
+    scoreError.value = '评分需要在1-100之间'
+  } else {
+    scoreError.value = ''
+  }
+}
+
+// 增加分数
+const increaseScore = () => {
+  if (correctionData.score < 100) {
+    correctionData.score++
+    validateScore()
+  }
+}
+
+// 减少分数
+const decreaseScore = () => {
+  if (correctionData.score > 1) {
+    correctionData.score--
+    validateScore()
+  }
+}
+
+// 提交批改
+const handleCorrect = async () => {
+  if (!correctionData.score) {
+    ElMessage.error("请先评分")
+    return
+  }
+  if (!correctionData.remark) {
+    ElMessage.error("请先输入评语")
+    return
+  }
+  
+  submitting.value = true
+  try {
+    const response = await apis.engagementCorrect(studentId, assignmentId, correctionData)
+    if (response.data.code === 200) {
+      ElMessage.success("批改成功")
+      await fetchCorrectionStats() // 刷新进度统计
+    } else {
+      ElMessage.error("批改异常: " + response.data.msg)
+    }
+  } catch (error) {
+    console.error('批改失败:', error)
+    ElMessage.error("批改失败,请重试")
+  } finally {
+    submitting.value = false
+  }
+}
+
+// 显示评语模板对话框
+const showTemplateDialog = () => {
+  templateDialogVisible.value = true
+}
+
+// 选择评语模板
+const selectTemplate = (template: string) => {
+  correctionData.remark = template
+  templateDialogVisible.value = false
+}
+
+// 获取批改进度统计
+const fetchCorrectionStats = async () => {
+  try {
+    const response = await apis.getAssignmentCorrectionStats(assignmentId);
+    if (response.data.code === 200) {
+      Object.assign(correctionStats, response.data.data);
+    }
+  } catch (error) {
+    console.error('获取批改进度失败:', error);
+  }
+}
+
+// 获取下一个学生作业ID
+const getNextStudentAssignment = async () => {
+  try {
+    const response = await apis.getNextStudentAssignment(assignmentId, studentId);
+    if (response.data.code === 200) {
+      return response.data.data;
+    }
+  } catch (error) {
+    console.error('获取下一个学生失败:', error);
+  }
+  return null;
+}
+
+// 处理下一个学生
+const handleNext = async () => {
+  const nextStudentId = await getNextStudentAssignment();
+  if (nextStudentId) {
+    router.push({
+      name: 'correct',
+      params: { assignmentId: assignmentId },
+      query: { studentId: nextStudentId }
+    }).then(() => {
+      // 强制刷新页面以重新加载数据
+      window.location.reload();
+    });
+    ElMessage.success("已自动跳转到下一个学生");
+  } else {
+    ElMessage.info("没有下一个学生");
+  }
+}
+
+// 返回上一页(教师版)
+const goBack = () => {
+  router.push({
+    name: 'myCorrectWithCourse',
+    query: {
+      courseId: courseId.value.toString(),
+      assignmentId: assignmentId.toString(),
+      fromCorrect: 'true'
+    }
+  }).then(() => {
+    window.location.reload();
+  });
+}
+
+// 返回上一页(学生版)
+const goBackStudent = () => {
+  // 如果没有获取到课程ID,则正常返回
+  if (!courseId.value) {
+    ElMessage.warning('获取课程信息失败,将返回作业列表');
+    router.push({ name: 'myHomework' });
+    return;
+  }
+  
+  // 返回时传递课程ID,让myHomework页面保持对应课程的选中状态
+  router.push({ 
+    name: 'myHomework',
+    query: { 
+      courseId: courseId.value.toString(),
+      fromAssignment: 'true' // 标记是从作业页面返回
+    }
   });
-  showDialog.value = false;
 };
 
 onMounted(() => {
   fetchData()
-  getBehaviorRecords()
+  getBehaviorRecords() // 页面加载时获取行为记录数据
 })
 
 </script>
@@ -170,3 +714,4 @@ export default {
   name: "CorrectPage"
 }
 </script>
+

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 875 - 137
src/views/CorrectPage/CoursePage.vue


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 996 - 1
src/views/CorrectPage/index.scss


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

@@ -32,6 +32,7 @@
           </template>
           <template v-if="showEdit && role == 'teachers'">
             <el-button color="#597d3b" @click="visible = true; fileList[0] = {name: '壁纸', url: newCourseInfo.courseImages[0]}">编辑课程</el-button>
+            <el-button color="#4a90c4" style="color: #fff" @click="$router.push(`/home/courseClass/${courseId}`)">班级管理</el-button>
           </template>
         </div>
       </div>
@@ -44,6 +45,15 @@
               {{dataForm.description}}
             </div>
           </template>
+          <template v-else-if="activeTab == '班级列表'">
+            <div>
+                <button 
+                style="color: #fff;background-color: #4a90c4;border: none;padding: 5px 10px;border-radius: 5px;cursor: pointer;" 
+                @click="$router.push(`/home/studentDetail?classId=${classId}&courseId=${courseId}&stuId=${store.user.id}`)">
+                查看班级列表
+              </button>
+            </div>
+          </template>
           <template v-else>
             <CourseHomeworkTable :disabled="isDisabled()" :columns="columns" :tableData="homeworkList" :role="role" :courseId="dataForm.courseId" :isShowNewHomework="showEdit && role == 'teachers'"/>
           </template>
@@ -123,7 +133,7 @@ import {
   type UploadFiles, type UploadFile, type FormRules, type FormInstance
 } from 'element-plus';
 import './index.scss'
-import type {CourseDetailVO, CourseVO, IAssignment, IAssignmentTableColumn, IAssignmentTableItem} from "@/types/vo";
+import type {CourseDetailVO, CourseVO, IAssignment, IAssignmentTableColumn, IAssignmentTableItem, ClassVO} from "@/types/vo";
 import {useRoute} from "vue-router";
 import useApis from "@/apis";
 import CourseHomeworkTable from "@/views/CourseSquare/component/CourseDetails/component/CourseHomeworkTable.vue";
@@ -139,6 +149,7 @@ const store = useStore()
 const route = useRoute()
 const role:'teachers' | 'student' = store.user.role === 'STUDENT' ?   'student':'teachers';
 
+let classId
 const courseId = +route.params.courseId
 
 let showCode = ref(true)
@@ -147,6 +158,8 @@ let showEdit = ref(false)
 let visible = ref(false)
 let uploadRef = ref()
 
+// classId = 2
+
 // 用于展示课程信息
 let dataForm = reactive<CourseDetailVO>({
   courseId: null,
@@ -317,7 +330,14 @@ const handleChange = (file,fileList) => {
 const processCourses = () => {
   dataForm.courseImages[0] = dataForm.courseImages[0].split("?")[0]
 }
-
+const getClassId = () => {
+  
+  apis.getClassNameOfStudent(courseId,store.user.id)
+    .then((res:any)=>{
+      console.log('NEWRES',res);
+      classId = res.data.data.classId
+    })
+}
 const getCourse = () => {
   // 获取课程信息
   apis.getCourseByCourseId(courseId)
@@ -558,9 +578,17 @@ function isDisabled(){
   if(role == "teachers") return !showEdit.value;
 }
 
+const ShowStudentClass = () => {
+  if(role == "student"){
+    items.value.push({label: "班级列表", key: "班级列表"})
+  }
+}
+
 onMounted(() => {
+  ShowStudentClass()
   getCourse()
   getHomeworkList()
+  getClassId()
 
   if(role == 'student') isShowCode()
   if(role == 'teachers') isShowEdit()

+ 23 - 3
src/views/EditPage/EditPage.vue

@@ -8,7 +8,7 @@
   <div class="my-work" >
 
     <div class="my-work-slider">
-      <el-button class="back" color="#597D3B" @click="handleBack">&lt;返回</el-button>
+      <el-button class="back" color="#4A90C4" style="color: #FFFFFF;" @click="handleBack">返回</el-button>
       <AIChatter :dialogueId="dataForm.dialogueId" :messages="dataForm.messages" :is-teacher="false" />
       <Dictionaries />
     </div>
@@ -25,16 +25,17 @@
   import useApis from "@/apis";
   import {useStore} from "@/store";
   import router from "@/router"
-  import {onMounted, onUnmounted, reactive, ref, watchEffect} from "vue";
+  import {onMounted, onUnmounted, reactive} from "vue";
   import type {engagementVO} from "@/types/vo";
   import type {IDialogue} from "@/types/dialogue.ts";
   import Edit from "@/views/Home/component/Edit/index.vue";
   import Slider from '@/views/Home/component/Silder/index.vue'
   import "./index.scss"
   import {useRoute} from 'vue-router'
-  import {ElMessage, ElMessageBox} from "element-plus";
+  import {ElMessageBox} from "element-plus";
   import IndexedDB from "@/utils/indexedDBUtil";
   import {isModified, registerAllEventListeners, removeAllEventListeners} from "@/views/EditPage/userWritingRecord";
+  import emitter from "@/utils/emitter";
 
   // 获得路由中的assignmentId
   const route = useRoute()
@@ -104,6 +105,23 @@
         })
   }
 
+  // 保存AI对话内容
+  const saveAIDialogue = async () => {
+    if (dataForm.dialogueId && dataForm.messages.length > 0) {
+      try {
+        await apis.requestAI(dataForm.dialogueId, 'ChatGLM4', dataForm.messages)
+        console.log('AI对话内容已保存')
+      } catch (error) {
+        console.error('保存AI对话失败:', error)
+      }
+    }
+  }
+
+  // 监听提交前的保存对话事件
+  emitter.on('save-ai-dialogue-before-submit', async () => {
+    await saveAIDialogue()
+  })
+
   onMounted( () => {
     fetchData()
     // indexedDB.initDB("eventId")
@@ -115,6 +133,8 @@
     // indexedDB.closeDB()
     // indexedDB.deleteDBAll()
     removeAllEventListeners();
+    // 清理事件监听器
+    emitter.off('save-ai-dialogue-before-submit');
   })
 
 </script>

+ 8 - 13
src/views/Home/component/Edit/index.vue

@@ -11,8 +11,8 @@
         <QuillEditor/>
       </div>
       <div class="button-right">
-        <el-button type="primary" @click="handleSubmit" :disabled="useBtn" color="#597D3B">提交</el-button>
-        <el-button type="primary" @click="handleStage" :disabled="useBtn" color="#597D3B">暂存</el-button>
+        <el-button type="primary" @click="handleSubmit"  color="#4A90C4" style="color: #FFFFFF; width: 70px;">提交</el-button>
+        <el-button type="primary" @click="handleStage"  color="#4A90C4" style="color: #FFFFFF; width: 70px; margin-left: 15px;">暂存</el-button>
       </div>
     </div>
   </div>
@@ -91,17 +91,18 @@
     }
     const endTime = dayjs(data.assignment.endTime); // 假设截止时间在 assignment 的 endTime 属性中
     const currentTime = dayjs();
+    let supplementarySubmission = false
     if (currentTime.isAfter(endTime)) {
-      ElMessage.error('作业已截止,无法提交!');
-      return;
+      supplementarySubmission = true
     }
-    apis.engagementSubmit(store.user.id, props.engagement.assignmentId, )
+    apis.engagementSubmit(store.user.id, props.engagement.assignmentId, supplementarySubmission)
     .then((_res: any) => {
       success(_res.data.data);
-      router.push("/home/myHomework")
+      router.push("/home/myHomework").then(() => {
+        window.location.reload()
+      })
     })
     .catch((error: any) => {
-      console.error('提交失败', error);
       console.error('提交失败', error);
       error('提交失败');
     });
@@ -109,12 +110,6 @@
 
   // 暂存操作
   const handleStage = async () => {
-    const endTime = dayjs(data.assignment.endTime); // 假设截止时间在 assignment 的 endTime 属性中
-    const currentTime = dayjs();
-    if (currentTime.isAfter(endTime)) {
-      ElMessage.error('作业已截止,无法暂存!');
-      return;
-    }
     try {
       const _res = await apis.engagementStage(store.user.id, props.engagement.assignmentId, JSON.stringify(getQuillContent), getText.value, getHTML.value);
       emitter.emit('click-stage');

+ 13 - 4
src/views/Home/component/Remark/Remark.vue

@@ -260,10 +260,19 @@ export default defineComponent({
     };
 
     const goBack = () => {
-      router.push({
-        name: 'myCorrectWithAssignmentId',
-        params: { assignmentId: props.assignmentId }
-      });
+      // 根据用户角色决定返回页面
+      if (isStudent.value) {
+        // 学生返回 myHomework 页面
+        router.push({
+          name: 'myHomework'
+        });
+      } else {
+        // 老师保持原有逻辑,返回批改页面
+        router.push({
+          name: 'myCorrectWithAssignmentId',
+          params: { assignmentId: props.assignmentId }
+        });
+      }
     };
     //新增:验证评分是否在1-100之间
     const validateScore = () => {

+ 2 - 1
src/views/Home/component/Silder/index.scss

@@ -48,7 +48,8 @@
     gap: 10px;
 
     .slider-tag-item {
-      background-color: #add94f;
+      background-color: #4A90C4;
+      color: #FFFFFF;
       padding: 8px 0;
       display: flex;
       justify-content: center;

+ 112 - 12
src/views/HomeworkPage/component/HomeworkDetail.vue

@@ -16,6 +16,10 @@
           <span style="font-weight: 600">发布老师: </span>
           {{data.teacherName}}
         </div>
+        <div class="item">
+          <span style="font-weight: 600">开始时间: </span>
+          {{new Date(Date.parse(data.startTime)).toLocaleString()}}
+        </div>
         <div class="item">
           <span style="font-weight: 600">截止日期: </span>
           {{new Date(Date.parse(data.endTime)).toLocaleString()}}
@@ -23,8 +27,14 @@
         <div class="item">
           <el-link type="primary" @click="downloadFile(data.descriptionFile, `${data.assignmentName}-描述文件`)">相关文件下载</el-link>
           <div class="button-container">
-            <el-button style="margin-left: 550px" color="#597D3B" @click="handleBack">返回</el-button>
-            <el-button color="#597D3B" @click="handleStart()" :disabled="new Date() > new Date(data.endTime)">
+            <el-button style="margin-left: 550px; color: #FFFFFF;" color="#7EB5E1" @click="handleBack">返回</el-button>
+            <el-button
+              style="color: #FFFFFF;"
+              color="#7ADBB0"
+              @click="handleStart()"
+              :disabled="isAssignmentDisabled"
+              :title="getButtonTooltip"
+            >
               开始{{ data.type }}
             </el-button>
           </div>
@@ -54,8 +64,8 @@
 <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 {onMounted, reactive, computed, ref} from "vue";
+import type {IAssignment, engagementVO} from "@/types/vo";
 import './homeworkDetail.scss'
 import {useStore} from "@/store";
 import router from "@/router";
@@ -82,14 +92,70 @@ let data = reactive<IAssignment>({
   type:""
 })
 
+// 学生作业参与状态
+const engagementStatus = ref<engagementVO | null>(null)
+
+// 计算属性:判断作业是否应该禁用
+const isAssignmentDisabled = computed(() => {
+  const now = new Date()
+  const startTime = new Date(data.startTime)
+  const endTime = new Date(data.endTime)
+
+  // 如果当前时间小于开始时间,则禁用
+  if (now < startTime) {
+    return true
+  }
+
+  // 如果当前时间大于结束时间
+  if (now > endTime) {
+    // 超过截止日期时,只有未完成状态的学生可以点击
+    if (engagementStatus.value) {
+      return engagementStatus.value.status !== 'NOT_SUBMITTED'
+    }
+    // 如果没有参与状态信息,允许点击(首次参与)
+    return false
+  }
+
+  // 在作业时间范围内,不禁用
+  return false
+})
+
+// 计算属性:按钮提示文本
+const getButtonTooltip = computed(() => {
+  const now = new Date()
+  const startTime = new Date(data.startTime)
+  const endTime = new Date(data.endTime)
+
+  if (now < startTime) {
+    return `作业尚未开始,开始时间:${startTime.toLocaleString()}`
+  } else if (now > endTime) {
+    if (engagementStatus.value) {
+      const status = engagementStatus.value.status
+      if (status === 'NOT_SUBMITTED') {
+        return '作业已截止,但您可以继续完成作业'
+      } else if (status === 'SUBMITTED') {
+        return '作业已截止且已提交,无法再次编辑'
+      } else if (status === 'CORRECTED') {
+        return '作业已截止且已批改,无法再次编辑'
+      }
+    }
+    return `作业已截止,截止时间:${endTime.toLocaleString()}`
+  } else {
+    return '点击开始作业'
+  }
+})
+
 function handleStart(){
   getEngagement()
   console.log(data.type)
   if(data.type == 'writing' || data.type == '写作'){
-    console.log('开始写作')
-    router.push(`/home/assignment/${assignmentId}/edit`)
+    router.push(`/home/assignment/${assignmentId}/edit`).then(() => {
+      window.location.reload()
+    })
   } else if(data.type == 'speaking' || data.type == 'AI口语'){
-    router.push(`/home/assignment/${assignmentId}/aispeaking`)
+    router.push(`/home/assignment/${assignmentId}/aispeaking`).then(() => {
+      window.location.reload()
+    })
   }
 }
 
@@ -102,6 +168,21 @@ function downloadFile(url:string, fileName:string) {
 }
 
 
+// 获取学生作业参与状态(仅查询,不自动加入)
+async function fetchEngagementStatus() {
+  try {
+    const response = await apis.getEngagement(store.user.id, assignmentId)
+    if (response.data.code === 200 && response.data.data) {
+      engagementStatus.value = response.data.data
+    } else {
+      engagementStatus.value = null
+    }
+  } catch (error) {
+    console.log('获取作业参与状态失败:', error)
+    engagementStatus.value = null
+  }
+}
+
 async function getEngagement(){
   apis.getEngagement(store.user.id, assignmentId)
       .then((_res:any) => {
@@ -113,6 +194,8 @@ async function getEngagement(){
                   message: '参加作业成功',
                   type: 'success',
                 })
+                // 加入作业后重新获取状态
+                fetchEngagementStatus()
               })
               .catch((_err:any) => {
                 console.log("参加做业的错误",_err)
@@ -132,12 +215,19 @@ const fetchData = () => {
 }
 
 const handleBack = () => {
-   // 跳转到目标页面
-   router.go(-1)
+   // 跳转到我的作业页面,携带课程ID参数以便自动选中对应课程
+   router.push({
+     path: '/home/myHomework',
+     query: {
+       courseId: data.courseId,
+       fromAssignment: 'true'
+     }
+   }).then(() => {window.location.reload()})
 }
 
 onMounted(() => {
   fetchData()
+  fetchEngagementStatus()
 })
 </script>
 
@@ -150,11 +240,21 @@ export default {
 <style scoped>
   .button-container {
     display: flex;
-
   }
+
   .el-button {
-  margin-right:0px;
-}
+    margin-right: 0px;
+  }
+
+  /* 禁用状态的开始按钮样式 */
+  .el-button:disabled {
+    opacity: 0.6;
+    cursor: not-allowed;
+  }
+
+  .el-button:disabled:hover {
+    opacity: 0.6;
+  }
 </style>
 
 

+ 862 - 129
src/views/HomeworkPage/index.vue

@@ -4,91 +4,208 @@
   Created Date: 2024-7-3
 -->
 <template>
-  <el-card style="margin: 10px auto 0;height: 100%; width: 80%" shadow="never">
-    <el-button :icon="Refresh" circle @click="doRefresh"></el-button>
-    <el-select
-        v-model="state"
-        placeholder="状态"
-        size="default"
-        style="width: 150px; margin-left: 10px;"
-        @change="handleSearch"
-    >
-      <el-option
-          v-for="item in stateOptions"
-          :key="item.value"
-          :label="item.label"
-          :value="item.value"
-      />
-    </el-select>
-    <el-input v-model="assignmentName" placeholder="请输入作业名称" clearable style="width: 180px;"/>
-    <el-button @click="handleSearch" color="#597D3B">
-      <el-icon style="margin-right: 3px" ><Search/></el-icon>
-      查询
-    </el-button>
-
-    <el-table :data="pageInfo.tableData" style="width: 100%" height="100%" show-overflow-tooltip>
-      <template #empty>
-        暂无作业
-      </template>
-      <el-table-column type="index"/>
-      <el-table-column prop="assignmentName" label="课程名称" width="400"/>
-      <el-table-column prop="type" label="类型" width="100"/>
-<!--      <el-table-column prop="description" label="描述" width="400"/>-->
-      <el-table-column prop="endTime" label="截止时间" sortable/>
-      <el-table-column prop="status" label="状态" sortable>
-        <template #default="{row}">
-          <el-tag v-if="row.status === AssignmentStatus.NOT_STARTED" type="info">未发布</el-tag>
-          <el-tag v-else-if="row.status === AssignmentStatus.PROCEEDING" type="success">进行中</el-tag>
-          <el-tag v-else-if="row.status === AssignmentStatus.FINISHED" type="danger">已截止</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" width="100">
-        <template #default="scope">
-          <el-button @click="handleShowDetail(scope.row.assignmentId)" style="margin-right: 0; margin-left: 0;padding-left: 10px; padding-right: 10px" color="#597D3B">
-            <el-icon style="margin-right: 3px"><ZoomIn/></el-icon>
-            详情
+  <div class="homework-page">
+    <!-- 左侧课程菜单栏 -->
+    <div class="left-sidebar">
+      <div class="course-list">
+        <div 
+          v-for="course in courseList" 
+          :key="course.courseId"
+          class="course-item"
+          :class="{ 'active': selectedCourseId === course.courseId }"
+          @click="selectCourse(course.courseId)"
+        >
+          <div class="course-name">{{ course.courseName }}</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 右侧内容区域 -->
+    <div class="right-content">
+      <!-- 空白状态 -->
+      <div v-if="!selectedCourseId" class="empty-state">
+        <div class="empty-content">
+          <div class="empty-text">请选择一门课程查看作业</div>
+        </div>
+      </div>
+
+      <!-- 作业内容区域 -->
+      <div v-else class="assignment-content">
+        <!-- 作业列表标题 -->
+        <h2 class="assignment-title">作业列表</h2>
+        
+        <!-- 搜索框 -->
+        <div class="search-section">
+          <el-input 
+            v-model="searchQuery" 
+            placeholder="Please enter" 
+            class="search-input"
+            @input="handleSearch"
+          >
+            <template #suffix>
+              <el-icon class="search-icon"><Search /></el-icon>
+            </template>
+          </el-input>
+          <el-button class="reset-btn" @click="resetSearch">Reset</el-button>
+          <el-button class="query-btn" @click="handleSearch">Query</el-button>
+        </div>
+
+        <!-- 筛选栏 -->
+        <div class="filter-section">
+          <el-button 
+            v-for="status in statusFilters"
+            :key="status.value"
+            :class="['filter-btn', { 'active': selectedStatus === status.value }]"
+            @click="selectStatus(status.value)"
+          >
+            {{ status.label }}
           </el-button>
-        </template>
-      </el-table-column>
-      <el-table-column label="查看" width="150">
-        <template #default="scope">
-          <el-button @click="viewCorrection(scope.row.assignmentId,store.user.id,scope.row.type, scope.row.assignmentName)" style="margin-right: 0; margin-left: 0;padding-left: 10px; padding-right: 10px" color="#597D3B">
-            <el-icon style="margin-right: 3px"><ZoomIn/></el-icon>
-            批改结果
+        </div>
+
+        <!-- 第二个筛选栏 -->
+        <div class="filter-section">
+          <el-button 
+            v-for="status in secondStatusFilters"
+            :key="status.value"
+            :class="['filter-btn', { 'active': selectedSecondStatus === status.value }]"
+            @click="selectSecondStatus(status.value)"
+          >
+            {{ status.label }}
           </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <el-pagination
-        small
-        style="font-size: 16px;margin-top: 10px;margin-left: 450px"
-        @current-change="handleCurrentChange"
-        @size-change="handleSizeChange"
-        :page-sizes="[5, 10, 15]"
-        :current-page="pageInfo.pages"
-        :page-size="pageInfo.size"
-        layout="total, sizes, prev, pager, next, jumper"
-        :total="pageInfo.total">
-    </el-pagination>
-  </el-card>
+        </div>
+
+                 <!-- 作业卡片列表 -->
+         <div class="assignment-cards">
+           <div 
+             v-for="assignment in filteredAssignments" 
+             :key="assignment.assignmentId"
+             class="assignment-card"
+             @click="handleShowDetail(assignment.assignmentId)"
+           >
+             <!-- 左侧状态装饰条和圆点 -->
+             <div class="card-accent">
+               <div class="accent-line" :class="getStatusClass(assignment)"></div>
+               <div class="accent-dot" :class="getStatusClass(assignment)"></div>
+             </div>
+             
+             <!-- 卡片内容 -->
+             <div class="card-content">
+               <!-- 左侧信息 -->
+               <div class="left-info">
+                 <div class="assignment-name">{{ assignment.assignmentName }}</div>
+                 <div class="assignment-type">{{ assignment.type }}</div>
+               </div>
+               
+               <!-- 中间状态信息 -->
+               <div class="middle-info">
+                 <div class="status-badge" :class="getSecondStatusClass(assignment)">
+                   <el-icon class="status-icon" v-if="getSecondStatusText(assignment) === '已批改'"><Check /></el-icon>
+                   <el-icon class="status-icon" v-else-if="getSecondStatusText(assignment) === '(补交)已批改'"><Check /></el-icon>
+                   <el-icon class="status-icon" v-else-if="getSecondStatusText(assignment) === '已提交'"><Calendar /></el-icon>
+                   <el-icon class="status-icon" v-else-if="getSecondStatusText(assignment) === '(补交)未批改'"><Calendar /></el-icon>
+                   <el-icon class="status-icon" v-else><Close /></el-icon>
+                   <span>{{ getSecondStatusText(assignment) }}</span>
+                 </div>
+               </div>
+               
+               <!-- 右侧时间信息 -->
+               <div class="right-info">
+                 <div class="time-row">
+                   <span class="time-label">开始时间:</span>
+                   <span class="time-value">{{ assignment.startTime || '未设置' }}</span>
+                 </div>
+                 <div class="time-row">
+                   <span class="time-label">截止时间:</span>
+                   <span class="time-value">{{ assignment.endTime }}</span>
+                 </div>
+               </div>
+               
+               <!-- 操作按钮 -->
+               <div class="card-actions" @click.stop>
+                 <el-button 
+                   class="detail-btn" 
+                   size="small"
+                   @click="handleShowDetail(assignment.assignmentId)"
+                 >
+                   详情
+                 </el-button>
+                 <el-button 
+                   class="result-btn" 
+                   size="small"
+                   type="primary"
+                   :disabled="getSecondStatusText(assignment) !== '已批改' && getSecondStatusText(assignment) !== '(补交)已批改'"
+                   @click="viewCorrection(assignment.assignmentId, store.user.id, assignment.type, assignment.assignmentName)"
+                 >
+                   批改结果
+                 </el-button>
+               </div>
+             </div>
+           </div>
+         </div>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script lang="ts" setup>
-  import {Refresh, ZoomIn, Search} from "@element-plus/icons-vue";
-  import {inject, onMounted, reactive, ref} from "vue";
+  import {Search} from "@element-plus/icons-vue";
+  import {Calendar,Check,Close} from "@element-plus/icons-vue";
+  import {onMounted, reactive, ref, computed, watch} from "vue";
   import useApis from "@/apis";
   import {useStore} from "@/store";
-  import type {CourseVO, IAssignment} from "@/types/vo";
+  import type {CourseVO, IAssignment, StudentCourseAssignmentsResponse, StudentAssignmentStatus} from "@/types/vo";
   import router from "@/router";
-  import {AssignmentStatus} from "@/types/enums";
+  import {useRoute} from "vue-router";
+
   import {useFormatDate} from "@/hooks/useFormatDate";
 
   const apis = useApis()
   const store = useStore()
-  const doRefresh:Function = inject("reload")
   const {formatDate} = useFormatDate()
   const role:'teachers' | 'student' = store.user.role === 'STUDENT' ?   'student':'teachers';
+  
+  // 获取路由信息
+  const route = useRoute()
+
+  // 课程列表
+  const courseList = ref<CourseVO[]>([])
+  
+  // 选中的课程ID
+  const selectedCourseId = ref<number | null>(null)
+  
+  // 搜索查询
+  const searchQuery = ref('')
+  
+  // 选中的状态
+  const selectedStatus = ref('全部')
+  
+  // 状态筛选选项
+  const statusFilters = [
+    { label: '全部', value: '全部' },
+    { label: '未发布', value: '未发布' },
+    { label: '进行中', value: '进行中' },
+    { label: '已截止', value: '已截止' }
+  ]
+
+  // 选中的第二个状态
+  const selectedSecondStatus = ref('全部')
+  
+  // 第二个状态筛选选项
+  const secondStatusFilters = [
+    { label: '全部', value: '全部' },
+    { label: '已批改', value: '已批改' },
+    { label: '已提交', value: '已提交' },
+    { label: '未完成', value: '未完成' }
+  ]
+  
+  // 当前课程的作业列表
+  const currentCourseAssignments = ref<IAssignment[]>([])
+  
+  // 所有作业数据
+  const allAssignments = ref<IAssignment[]>([])
+  
+  // 学生作业状态数据
+  const studentAssignmentStatus = ref<Map<number, StudentAssignmentStatus[]>>(new Map())
 
   // 获取的所有作业
   let homeworkList = reactive<{
@@ -119,7 +236,7 @@
   //  查看教师批改结果 跳转至批改页面
   const viewCorrection = (assignmentId:number,studentId:number, type:string, assignmentName:string) => {
     if(type === '写作' || type ==='writing'){
-      router.push(`/home/assignment/${assignmentId}/correct?studentId=${studentId}`)
+      router.push(`/home/assignment/${assignmentId}/correct?studentId=${studentId}`).then(() => {window.location.reload()})
     } else if(type === 'AI口语' || type === 'speaking'){
       router.push({
       path: `/home/assignment/${assignmentId}/airecord`,
@@ -127,33 +244,18 @@
         userId: studentId,
         assignmentId: assignmentId,
         assignmentName: assignmentName
-      }});
+      }}).then(() => {window.location.reload()});
     }
    
 
   }
 
   const handleShowDetail = (assignmentId:number) => {
-    router.push(`/home/homeworkDetail/${assignmentId}`)
+    router.push(`/home/homeworkDetail/${assignmentId}`).then(() => {window.location.reload()})
 
   }
 
-  const handleCurrentChange = (pageNum: number) => {
-    Object.assign(pageInfo, {
-      tableData: getSlice(pageNum, pageInfo.size),
-      size: pageInfo.size,
-      pages: pageNum,
-      total: processedHomeworkList.tableData.length,
-    })
-  }
-  const handleSizeChange = (pageSize: number) => {
-    Object.assign(pageInfo, {
-      tableData: getSlice(1, pageSize),
-      size: pageSize,
-      pages: 1,
-      total: processedHomeworkList.tableData.length,
-    })
-  }
+
 
 
   const fetchData = () => {
@@ -164,9 +266,10 @@
             let total = res.data.data.total;
             apis.getCourseByStudentId(1, total)
                 .then((res: any) => {
-                  let data:CourseVO[] = res.data.data.records
-                  for(let i = 0; i < data.length; i++){
-                    fetchHomeworkListByCourseId(data[i].courseId)
+                  courseList.value = res.data.data.records
+                  // 为每个课程获取作业数据
+                  for(let i = 0; i < courseList.value.length; i++){
+                    fetchHomeworkListByCourseId(courseList.value[i].courseId)
                   }
                 })
           })
@@ -177,26 +280,269 @@
             let total = res.data.data.total;
             apis.getCourseByTeacherId(1, total)
                 .then((res: any) => {
-                  let data:CourseVO[] = res.data.data.records
-                  for(let i = 0; i < data.length; i++){
-                    fetchHomeworkListByCourseId(data[i].courseId)
+                  courseList.value = res.data.data.records
+                  // 为每个课程获取作业数据
+                  for(let i = 0; i < courseList.value.length; i++){
+                    fetchHomeworkListByCourseId(courseList.value[i].courseId)
                   }
                 })
           })
     }
   }
 
+  // 选择课程
+  const selectCourse = (courseId: number) => {
+    console.log('selectCourse 被调用,课程ID:', courseId)
+    selectedCourseId.value = courseId
+    // 筛选当前课程的作业
+    currentCourseAssignments.value = allAssignments.value.filter(
+      assignment => assignment.courseId === courseId
+    )
+    
+    console.log('当前role:', role)
+    console.log('studentAssignmentStatus.value.has(courseId):', studentAssignmentStatus.value.has(courseId))
+    
+    // 如果是学生角色且还没有该课程的作业状态数据,主动获取
+    if (role === 'student' && !studentAssignmentStatus.value.has(courseId)) {
+      console.log('没有找到课程状态数据,主动获取...')
+      fetchStudentAssignmentStatus(courseId)
+    } else if (role === 'student') {
+      console.log('已存在课程状态数据:', studentAssignmentStatus.value.get(courseId))
+    }
+    console.log('选中课程后,当前课程作业数量:', currentCourseAssignments.value.length)
+  }
+  
+  // 处理路由参数 - 从其他页面返回时自动选中课程
+  const handleRouteParams = () => {
+    const { courseId, fromAssignment } = route.query
+    
+    
+    // 如果有课程ID参数且是从作业页面返回的
+    if (courseId && fromAssignment === 'true') {
+      const targetCourseId = Number(courseId)
+      
+      // 检查目标课程是否存在
+      const targetCourse = courseList.value.find(course => course.courseId === targetCourseId)
+      if (targetCourse) {
+        selectCourse(targetCourseId)
+        
+        // 清除路由参数,避免重复处理
+        router.replace({ 
+          name: 'myHomework',
+          query: {} 
+        })
+      } else {
+        console.log('未找到目标课程')
+      }
+    }
+  }
+
+  // 重置搜索
+  const resetSearch = () => {
+    searchQuery.value = ''
+    selectedStatus.value = '全部'
+    selectedSecondStatus.value = '全部'
+  }
+
+  // 选择状态
+  const selectStatus = (status: string) => {
+    selectedStatus.value = status
+  }
+
+  // 选择第二个状态
+  const selectSecondStatus = (status: string) => {
+    selectedSecondStatus.value = status
+  }
+
+  // 筛选后的作业列表
+  const filteredAssignments = computed(() => {
+    let filtered = [...currentCourseAssignments.value]
+    
+    // 按搜索关键词筛选
+    if (searchQuery.value) {
+      filtered = filtered.filter(assignment => 
+        assignment.assignmentName.toLowerCase().includes(searchQuery.value.toLowerCase())
+      )
+    }
+    
+    // 按状态筛选
+    if (selectedStatus.value !== '全部') {
+      filtered = filtered.filter(assignment => getActualStatus(assignment) === selectedStatus.value)
+    }
+    
+    // 按第二个状态筛选
+    if (selectedSecondStatus.value !== '全部') {
+      filtered = filtered.filter(assignment => getSecondStatusText(assignment) === selectedSecondStatus.value)
+    }
+    
+    return filtered
+  })
+
+  // 获取作业的实际状态(基于开始时间和截止时间)
+  const getActualStatus = (assignment: IAssignment) => {
+    const now = new Date()
+    
+    // 确保能正确解析结束日期,支持多种格式
+    let endTime: Date
+    
+    // 如果endTime是字符串,尝试解析
+    if (typeof assignment.endTime === 'string') {
+      // 处理 "YYYY-MM-DD HH:mm:ss" 格式
+      endTime = new Date(assignment.endTime.replace(' ', 'T'))
+    } else {
+      endTime = new Date(assignment.endTime)
+    }
+    
+    // 检查结束日期是否有效
+    if (isNaN(endTime.getTime())) {
+      console.warn('Invalid end date format:', assignment.endTime)
+      return '进行中' // 默认返回进行中
+    }
+
+    // 解析开始时间(如果存在)
+    let startTime: Date | null = null
+    if (assignment.startTime) {
+      if (typeof assignment.startTime === 'string') {
+        startTime = new Date(assignment.startTime.replace(' ', 'T'))
+      } else {
+        startTime = new Date(assignment.startTime)
+      }
+      
+      // 检查开始日期是否有效
+      if (isNaN(startTime.getTime())) {
+        console.warn('Invalid start date format:', assignment.startTime)
+        startTime = null
+      }
+    }
+    
+    // 判断状态优先级:未发布 > 已截止 > 进行中
+    if (startTime && now < startTime) {
+      return '未发布'
+    } else if (now > endTime) {
+      return '已截止'
+    } else {
+      return '进行中'
+    }
+  }
+
+  // 获取状态样式类
+  const getStatusClass = (assignment: IAssignment) => {
+    const actualStatus = getActualStatus(assignment)
+    switch(actualStatus) {
+      case '未发布':
+        return 'status-unpublished'
+      case '进行中':
+        return 'status-proceeding'
+      case '已截止':
+        return 'status-finished'
+      default:
+        return 'status-unknown'
+    }
+  }
+
+
+
+  // 获取第二个状态的文本
+  const getSecondStatusText = (assignment: IAssignment) => {
+    if (role === 'student') {
+      // 从真实API数据中获取状态
+      const courseStatus = studentAssignmentStatus.value.get(assignment.courseId)
+      console.log(2)
+      if (courseStatus) {
+        console.log(1)
+        const assignmentStatus = courseStatus.find(status => status.assignmentId === assignment.assignmentId)
+        if (assignmentStatus) {
+          switch (assignmentStatus.status) {
+            case 'CORRECTED':
+              return '已批改'
+            case 'SUBMITTED':
+              return '已提交'
+            case 'NOT_SUBMITTED':
+              return '未完成'
+            case 'LATE_SUBMITTED':
+              return '(补交)未批改'
+            case 'LATE_SUBMITTED_CORRECTED':
+              return '(补交)已批改'
+            default:
+              return '未完成'
+          }
+        }
+      }
+    }
+    return '未完成'
+  }
+
+  // 获取第二个状态的样式类
+  const getSecondStatusClass = (assignment: IAssignment) => {
+    const statusText = getSecondStatusText(assignment)
+    switch(statusText) {
+      case '已批改':
+      case '已提交':
+        return 'status-submitted'
+      case '未完成':
+        return 'status-incomplete'
+      case '(补交)已批改':
+      case '(补交)未批改':
+        return 'status-late-submitted'
+        default:
+        return 'status-incomplete'
+    }
+  }
+
+  // 获取学生作业状态数据
+  const fetchStudentAssignmentStatus = (courseId: number) => {
+    if (role === 'student') {
+      console.log('开始获取学生作业状态数据,课程ID:', courseId)
+      apis.getStudentAssignmentsByCourse(store.user.id.toString(), courseId.toString())
+        .then((res: any) => {
+          console.log('API响应:', res)
+          if (res.data.code === 200) {
+            const responseData: StudentCourseAssignmentsResponse = res.data.data
+            console.log('获取到学生作业状态数据:', responseData)
+            console.log('存储到Map中,courseId:', courseId, 'assignments:', responseData.assignments)
+            studentAssignmentStatus.value.set(courseId, responseData.assignments)
+            console.log('存储后的Map:', studentAssignmentStatus.value)
+          } else {
+            console.log('API返回错误代码:', res.data.code, res.data.msg)
+          }
+        })
+        .catch((error: any) => {
+          console.error('获取学生作业状态数据失败:', error)
+        })
+    }
+  }
+
   const fetchHomeworkListByCourseId = (courseId:number) => {
+    // console.log('开始获取课程', courseId, '的作业数据')
     apis.getAssignmentByCourseId(courseId)
         .then((res: any) => {
           let total = res.data.data.total
+          // console.log('课程', courseId, '总作业数:', total)
           apis.getAssignmentByCourseId(courseId, 1, total)
               .then((res: any) => {
                 let data = res.data.data.records
-                for(let i = 0; i < data.length; i++) {
-                  data[i].endTime = formatDate(data[i].endTime)
-                  homeworkList.tableData.push(data[i])
+                // console.log('课程', courseId, '获取到', data.length, '个作业')
+                                 for(let i = 0; i < data.length; i++) {
+                   data[i].endTime = formatDate(data[i].endTime)
+                   data[i].startTime = data[i].startTime ? formatDate(data[i].startTime) : null
+                   data[i].courseId = courseId // 添加课程ID
+                   homeworkList.tableData.push(data[i])
+                   allAssignments.value.push(data[i]) // 添加到所有作业数组
+                   // console.log('添加作业:', data[i].assignmentName, 'courseId:', data[i].courseId)
+                 }
+                // console.log('课程', courseId, '作业加载完成,当前allAssignments总数:', allAssignments.value.length)
+                
+                // 如果是学生角色,获取作业状态数据
+                if (role === 'student') {
+                  fetchStudentAssignmentStatus(courseId)
+                }
+                
+                // 如果有选中的课程且正好是这个课程ID,立即重新筛选
+                if (selectedCourseId.value === courseId) {
+                  // console.log('检测到当前选中课程', courseId, '的作业已加载,重新筛选')
+                  selectCourse(courseId)
                 }
+                
                 processedHomeworkList.tableData = homeworkList.tableData
                 Object.assign(pageInfo, {
                   tableData: getSlice(pageInfo.pages, pageInfo.size),
@@ -208,21 +554,9 @@
         })
   }
 
+  // 旧的handleSearch方法保留用于兼容性
   const handleSearch = () => {
-    processedHomeworkList.tableData = []
-    for(let i = 0; i < homeworkList.tableData.length; i++){
-      let assignmentItem = homeworkList.tableData[i]
-      let input = true;
-      if(state.value != null && state.value != assignmentItem.status) input = false;
-      if(assignmentName.value != '' && !assignmentItem.assignmentName.includes(assignmentName.value)) input = false;
-      if(input) processedHomeworkList.tableData.push(assignmentItem);
-      Object.assign(pageInfo, {
-        tableData: getSlice(pageInfo.pages, pageInfo.size),
-        size: pageInfo.size,
-        pages: pageInfo.pages,
-        total: processedHomeworkList.tableData.length,
-      })
-    }
+    // 新版本中不需要实现,因为使用了响应式筛选
   }
 
   //用于分页展示,截取数据
@@ -230,27 +564,46 @@
     return processedHomeworkList.tableData.slice((page-1)*size, page*size)
   }
 
+  // 监听课程列表变化,数据加载完成后处理路由参数
+  watch(courseList, (newCourseList) => {
+    // console.log('courseList 变化,课程数量:', newCourseList.length)
+    if (newCourseList.length > 0) {
+      handleRouteParams()
+    }
+  }, { immediate: false })
+
+  // 监听作业数据变化,确保作业数据也加载完成
+  watch(allAssignments, (newAssignments) => {
+    // console.log('allAssignments 变化,作业数量:', newAssignments.length)
+    
+    // 如果有选中的课程且作业数据更新了,重新筛选作业
+    if (selectedCourseId.value && newAssignments.length > 0) {
+      // console.log('重新筛选选中课程的作业,课程ID:', selectedCourseId.value)
+      selectCourse(selectedCourseId.value)
+    }
+    
+    // 如果作业数据加载完成,再次尝试处理路由参数
+    if (newAssignments.length > 0) {
+      handleRouteParams()
+    }
+  }, { immediate: false })
+
+  // 监听学生作业状态数据变化
+  watch(studentAssignmentStatus, (newStatus) => {
+    console.log('studentAssignmentStatus 变化,课程数量:', newStatus.size)
+    // 当状态数据更新时,重新筛选当前选中课程的作业
+    if (selectedCourseId.value) {
+      currentCourseAssignments.value = allAssignments.value.filter(
+        assignment => assignment.courseId === selectedCourseId.value
+      )
+    }
+  }, { immediate: false, deep: true })
+
   onMounted(() => {
     fetchData()
   })
 
-  let state = ref(null)
-  const stateOptions = [
-    {
-      value: AssignmentStatus.NOT_STARTED,
-      label: "未发布"
-    },
-    {
-      value: AssignmentStatus.PROCEEDING,
-      label: "进行中"
-    },
-    {
-      value: AssignmentStatus.FINISHED,
-      label: "已截止"
-    },
-  ]
 
-  let assignmentName = ref('')
 
 </script>
 
@@ -259,3 +612,383 @@ export default {
   name: "HomeworkPage"
 }
 </script>
+
+<style scoped>
+.homework-page {
+  display: flex;
+  height: 95.5vh;
+  font-family: "Noto Sans SC", sans-serif;
+}
+
+/* 左侧菜单栏 */
+.left-sidebar {
+  width: 250px;
+  min-width: 250px;
+  max-width: 250px;
+  background: linear-gradient(180deg, #89CDF1 0%, #1890FF 100%);
+  display: flex;
+  flex-direction: column;
+  flex-shrink: 0;
+}
+
+.course-list {
+  padding: 20px 0;
+}
+
+.course-item {
+  padding: 15px 25px;
+  color: white;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  border-left: 4px solid transparent;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.course-item:hover {
+  background-color: rgba(255, 255, 255, 0.1);
+  border-left-color: white;
+}
+
+.course-item.active {
+  background-color: rgba(255, 255, 255, 0.2);
+  border-left-color: white;
+  font-weight: 600;
+}
+
+.course-name {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 右侧内容区域 */
+.right-content {
+  flex: 1;
+  background-color: white;
+  padding: 30px;
+  overflow-y: auto;
+}
+
+/* 空白状态 */
+.empty-state {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.empty-content {
+  text-align: center;
+  color: #999;
+}
+
+.empty-text {
+  font-size: 16px;
+  color: #999;
+}
+
+/* 作业内容区域 */
+.assignment-content {
+  max-width: 1200px;
+}
+
+.assignment-title {
+  font-size: 24px;
+  font-weight: 600;
+  color: #2c3e50;
+  margin-bottom: 0px;
+  margin-top: 0;
+}
+
+/* 搜索区域 */
+.search-section {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 0px;
+  padding: 20px 0;
+}
+
+.search-input {
+  flex: 1;
+  max-width: 400px;
+}
+
+.reset-btn, .query-btn {
+  padding: 8px 20px;
+  border-radius: 6px;
+  font-weight: 500;
+}
+
+.reset-btn {
+  background-color: #f5f5f5;
+  border-color: #d9d9d9;
+  color: #666;
+}
+
+.query-btn {
+  background-color: #1890ff;
+  border-color: #1890ff;
+  color: white;
+}
+
+/* 筛选区域 */
+.filter-section {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 20px;
+}
+
+.filter-btn {
+  padding: 8px 16px;
+  border: 1px solid #d9d9d9;
+  background-color: white;
+  color: #666;
+  border-radius: 6px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+}
+
+.filter-btn:hover {
+  border-color: #1890ff;
+  color: #1890ff;
+}
+
+.filter-btn.active {
+  background-color: #1890ff;
+  border-color: #1890ff;
+  color: white;
+}
+
+/* 作业卡片区域 */
+.assignment-cards {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.assignment-card {
+  background: white;
+  border-radius: 12px;
+  padding: 0;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+  border: 1px solid #f0f0f0;
+  min-height: 80px;
+  position: relative;
+  cursor: pointer;
+  overflow: hidden;
+}
+
+.assignment-card:hover {
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+  transform: translateY(-1px);
+}
+
+/* 左侧状态装饰条和圆点 */
+.card-accent {
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 8px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.accent-line {
+  width: 100%;
+  height: 100%;
+}
+
+.accent-line.status-proceeding {
+  background-color: #52c41a; /* 绿色 - 进行中 */
+}
+
+.accent-line.status-unpublished {
+  background-color: #faad14; /* 黄色 - 未发布 */
+}
+
+.accent-line.status-finished {
+  background-color: #ff4d4f; /* 红色 - 已截止 */
+}
+
+.accent-line.status-unknown {
+  background-color: #d9d9d9; /* 灰色 - 未知状态 */
+}
+
+.accent-dot {
+  position: absolute;
+  left: 20px;
+  top: 20px;
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;
+}
+
+.accent-dot.status-proceeding {
+  background-color: #52c41a; /* 绿色 - 进行中 */
+}
+
+.accent-dot.status-unpublished {
+  background-color: #faad14; /* 黄色 - 未发布 */
+}
+
+.accent-dot.status-finished {
+  background-color: #ff4d4f; /* 红色 - 已截止 */
+}
+
+.accent-dot.status-unknown {
+  background-color: #d9d9d9; /* 灰色 - 未知状态 */
+}
+
+/* 卡片内容 */
+.card-content {
+  display: flex;
+  align-items: center;
+  padding: 16px 20px 16px 30px;
+  gap: 20px;
+}
+
+/* 左侧信息 */
+.left-info {
+  margin-left: 15px;
+  flex: 0 0 auto;
+  min-width: 200px;
+}
+
+.assignment-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #2c3e50;
+  margin: 0 0 6px 0;
+  line-height: 1.3;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 180px;
+}
+
+.assignment-type {
+  font-size: 14px;
+  width: 50px;
+  text-align: center;
+  background-color: #e6f7ff;
+  color: #1890ff;
+  font-weight: 400;
+  border-radius: 10px;
+}
+
+/* 中间状态信息 */
+.middle-info {
+  margin-left: 20px;
+  flex: 0 0 auto;
+  display: flex;
+  justify-content: center;
+  min-width: 120px;
+}
+
+.status-badge {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 6px 12px;
+  border-radius: 16px;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.status-badge.status-submitted {
+  background-color: #e6f7ff;
+  color: #1890ff;
+}
+
+.status-badge.status-late-submitted {
+  background-color: #fff2e8;
+  color: #fa8c16;
+}
+
+.status-badge.status-incomplete {
+  background-color: #fff2e8;
+  color: #ff4d4f;
+}
+
+.status-icon {
+  font-size: 16px;
+}
+
+/* 右侧时间信息 */
+.right-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  align-items: flex-end;
+}
+
+.time-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 13px;
+}
+
+.time-label {
+  color: #666;
+  white-space: nowrap;
+}
+
+.time-value {
+  color: #333;
+  font-weight: 500;
+  white-space: nowrap;
+}
+
+/* 操作按钮 */
+.card-actions {
+  flex: 0 0 auto;
+  display: flex;
+  gap: 8px;
+  margin-left: auto;
+}
+
+.detail-btn {
+  background-color: #5397F8;
+  opacity: 1;
+  border-radius: 4px;
+  color: #ffffff;
+}
+
+.result-btn {
+  background-color: #1890ff;
+  border-color: #1890ff;
+  color: white;
+}
+
+.detail-btn:hover {
+  background-color: #5397F8;
+  opacity: 0.8;
+}
+
+.result-btn:hover {
+  background-color: #40a9ff;
+  border-color: #40a9ff;
+}
+
+/* 禁用状态的批改结果按钮样式 */
+.result-btn:disabled {
+  background-color: #f5f5f5;
+  border-color: #d9d9d9;
+  color: #bfbfbf;
+  opacity: 1;
+  cursor: not-allowed;
+}
+
+.result-btn:disabled:hover {
+  background-color: #f5f5f5;
+  border-color: #d9d9d9;
+  color: #bfbfbf;
+}
+</style>

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels