瀏覽代碼

Merge branch 'feature/class' of FanYanPeng/EAIFrontend into host-nju

LiuLuLin 1 年之前
父節點
當前提交
6472e84029

+ 1 - 1
package-lock.json

@@ -2153,7 +2153,7 @@
     },
     "node_modules/echarts": {
       "version": "5.6.0",
-      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
       "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
       "dependencies": {
         "tslib": "2.3.0",

+ 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

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

+ 32 - 0
src/router/index.ts

@@ -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")
+            },
+          ]
+        },
       ]
     },
 

+ 17 - 0
src/types/vo.ts

@@ -139,4 +139,21 @@ export interface CourseDetailVO {
     createTime: string;
     description: string;
 }
+//班级模块
+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;
+}

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

+ 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()