stupig-zcx hace 4 meses
padre
commit
88c4de2bd6

+ 9 - 0
src/apis/behavior.ts

@@ -63,6 +63,15 @@ const bevaviorApis = {
             }
             }
         })
         })
     },
     },
+    downLoadAllBehaviorDataByClass:(classId :number,assignmentId :number)=>{
+        return axiosInstance.get(`/api/export/behaviorOverview/excel/class/assignment`, {
+            responseType: 'blob',
+            params: {
+                classId,
+                assignmentId
+            }
+        })
+    },
 }
 }
 
 
 export default bevaviorApis;
 export default bevaviorApis;

+ 9 - 0
src/apis/class.ts

@@ -35,6 +35,15 @@ const classApis = {
             }
             }
         })
         })
     },
     },
+    importStudentsOfClass:(classId:number,file:File) => {
+        const formData = new FormData();
+        formData.append("file", file);
+        return axiosInstance.post(ENGAGE_PREFIX+`/${classId}/students/batch`, formData, {
+            headers: {
+                'Content-Type': 'multipart/form-data'
+            }
+        })
+    },
     deleteStudentOfClass:(classId:number,officialNumbers) => {
     deleteStudentOfClass:(classId:number,officialNumbers) => {
         return axiosInstance.delete(ENGAGE_PREFIX+`/${classId}/students/batch`,{
         return axiosInstance.delete(ENGAGE_PREFIX+`/${classId}/students/batch`,{
             headers:{
             headers:{

+ 0 - 1
src/components/QuillEditor/Editor.vue

@@ -35,7 +35,6 @@ import {ElMessage} from "element-plus";
         toolbar: [
         toolbar: [
           ['bold', 'italic', 'underline', 'strike'],
           ['bold', 'italic', 'underline', 'strike'],
           [{ 'size': ['small', false, 'large', 'huge'] }],
           [{ 'size': ['small', false, 'large', 'huge'] }],
-          [{ 'font': [] }],
           [{ 'align': [] }],
           [{ 'align': [] }],
           [{ 'list': 'ordered' }, { 'list': 'bullet' }],
           [{ 'list': 'ordered' }, { 'list': 'bullet' }],
           [{ 'indent': '-1' }, { 'indent': '+1' }],
           [{ 'indent': '-1' }, { 'indent': '+1' }],

+ 15 - 15
src/router/index.ts

@@ -16,24 +16,24 @@ 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";
-        // {本地门户首页地址}?from={本地eai首页地址}
-        return "/"; // 占位返回值,实际不会执行
-    },
-    },
     // {
     // {
     //   path: "/",
     //   path: "/",
-    //   redirect: "/login"
+    //   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";
+    //     // {本地门户首页地址}?from={本地eai首页地址}
+    //     return "/"; // 占位返回值,实际不会执行
     // },
     // },
-    // {
-    //   path: '/login',
-    //   name: 'login',
-    //   component: () => import('../views/LoginPage/LoginPage.vue')
     // },
     // },
+    {
+      path: "/",
+      redirect: "/login"
+    },
+    {
+      path: '/login',
+      name: 'login',
+      component: () => import('../views/LoginPage/LoginPage.vue')
+    },
     {
     {
       path: '/register',
       path: '/register',
       name: 'register',
       name: 'register',
@@ -260,7 +260,7 @@ const router = createRouter({
  */
  */
   router.beforeEach(async (to) => {
   router.beforeEach(async (to) => {
     const store = useStore();
     const store = useStore();
-    const publicPaths = [ '/register', '/passwordChange', '/admin/login'];
+    const publicPaths = [ '/register', '/passwordChange', '/admin/login','/login' ];
     // 1. 检查URL中的token
     // 1. 检查URL中的token
     const urlToken = new URLSearchParams(window.location.search).get('token');
     const urlToken = new URLSearchParams(window.location.search).get('token');
     if (urlToken) {
     if (urlToken) {

+ 26 - 0
src/views/BehaviorPage/BehaviorBoard.vue

@@ -31,6 +31,7 @@ const loadingStatus = ref({
 provide('loadingStatus',loadingStatus)
 provide('loadingStatus',loadingStatus)
 
 
 const selectedCourse  = ref({courseName: "", courseId: -1});
 const selectedCourse  = ref({courseName: "", courseId: -1});
+const selectedClass  = ref({courseName: "", classId: -1});
 const courseList:Ref<CourseVO[]> = ref([]);
 const courseList:Ref<CourseVO[]> = ref([]);
 let courseData:any[]= []
 let courseData:any[]= []
 const selectedAssignment  = ref({assignmentName: "", assignmentId: -1});
 const selectedAssignment  = ref({assignmentName: "", assignmentId: -1});
@@ -365,6 +366,30 @@ function downLoadBehaviorData(){
           ElMessage.success('下载失败')
           ElMessage.success('下载失败')
         })
         })
   }
   }
+  // else if (selectedType.value === 'class'){
+  //     apis.downLoadAllBehaviorDataByClass(selectedClass.value.classId,selectedAssignment.value.assignmentId)
+  //       .then((res:any) => {
+  //         if (res && res.status === 200){
+  //           let blob = res.data as Blob;
+  //           const fileName = '行为数据.xlsx';
+  //           if ('download' in document.createElement('a')) {
+  //             const link = document.createElement('a');
+  //             link.download = fileName;
+  //             link.style.display = 'none';
+  //             link.href = URL.createObjectURL(blob);
+  //             document.body.appendChild(link);
+  //             link.click();
+  //             ElMessage.success('下载成功')
+  //             URL.revokeObjectURL(link.href); // 释放URL对象
+  //             document.body.removeChild(link);
+  //           }
+  //         }
+  //       })
+  //       .catch((err:any) => {
+  //         console.log(err)
+  //         ElMessage.success('下载失败')
+  //       })
+  // }
   else{
   else{
     ElMessage.error("请选择下载类型")
     ElMessage.error("请选择下载类型")
   }
   }
@@ -467,6 +492,7 @@ onMounted(()=>{
       <el-radio-group v-model="selectedType">
       <el-radio-group v-model="selectedType">
         <el-radio value="single">当前学生行为数据</el-radio>
         <el-radio value="single">当前学生行为数据</el-radio>
         <el-radio value="all">所有学生行为数据</el-radio>
         <el-radio value="all">所有学生行为数据</el-radio>
+        <!-- <el-radio value="class">班级行为数据</el-radio> -->
       </el-radio-group>
       </el-radio-group>
       <template #footer>
       <template #footer>
         <div class="dialog-footer">
         <div class="dialog-footer">

+ 49 - 1
src/views/ClassPage/ClassHomework.vue

@@ -35,6 +35,9 @@
                     chart
                     chart
                 </template>
                 </template>
             </classToggle>
             </classToggle>
+            <div class="exportButton">
+                    <button @click="exportData">导出数据</button>
+            </div>
         </div>
         </div>
         <div class="content">
         <div class="content">
             <div class="Loading" v-show="!DataLoading">Loading...</div>
             <div class="Loading" v-show="!DataLoading">Loading...</div>
@@ -137,8 +140,10 @@ import classToggle from './components/classToggle.vue'
 import {onMounted, ref, computed, watch, nextTick } from 'vue';
 import {onMounted, ref, computed, watch, nextTick } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { useRoute, useRouter } from 'vue-router';
 import { useStore } from "@/store"
 import { useStore } from "@/store"
+import {ElLoading, ElMessage} from "element-plus";
 import * as echarts from 'echarts'
 import * as echarts from 'echarts'
 import useApis from "@/apis";
 import useApis from "@/apis";
+import { log } from 'console';
 
 
 const apis = useApis()
 const apis = useApis()
 const store = useStore()
 const store = useStore()
@@ -166,7 +171,6 @@ const Homeworksdata = ref([])
 
 
 const sortMethods = ["默认","成绩从低到高","成绩从高到低","未批改优先","未提交优先"]
 const sortMethods = ["默认","成绩从低到高","成绩从高到低","未批改优先","未提交优先"]
 
 
-
 const chartRef1 = ref(null)
 const chartRef1 = ref(null)
 const chartRef2 = ref(null)
 const chartRef2 = ref(null)
 const setChart = () => {
 const setChart = () => {
@@ -277,6 +281,33 @@ const setChart = () => {
     chartInstance2.setOption(option2)
     chartInstance2.setOption(option2)
 }
 }
 
 
+const exportData = () => {
+    console.log('exportData',classId,ShowTheHomeworkId.value)
+    // apis.downLoadAllBehaviorDataByClass(classId,ShowTheHomeworkId.value)
+    // apis.downLoadAllBehaviorData(ShowTheHomeworkId.value)
+    apis.downLoadAllBehaviorDataByClass(classId,ShowTheHomeworkId.value)
+    .then((res:any) => {
+        if (res && res.status === 200){
+        let blob = res.data as Blob;
+        const fileName = `${ShowTheHomework.value}作业行为数据.xlsx`;
+        if ('download' in document.createElement('a')) {
+            const link = document.createElement('a');
+            link.download = fileName;
+            link.style.display = 'none';
+            link.href = URL.createObjectURL(blob);
+            document.body.appendChild(link);
+            link.click();
+            ElMessage.success('下载成功')
+            URL.revokeObjectURL(link.href); // 释放URL对象
+            document.body.removeChild(link);
+        }
+        }
+    })
+    .catch((err:any) => {
+        console.log(err)
+        ElMessage.success('下载失败')
+    })
+}
 
 
 const identify = () => {
 const identify = () => {
     if(store.user.role === "STUDENT"){
     if(store.user.role === "STUDENT"){
@@ -436,6 +467,23 @@ onMounted(async () => {
     justify-content: flex-start;
     justify-content: flex-start;
     align-items: center;
     align-items: center;
     margin-top: 20px;
     margin-top: 20px;
+    .exportButton{
+        height: 100%;
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-left: 20px;
+        button{
+            height: 27px;
+            padding: 0 10px;
+            border: 2px solid #e4e7ed;
+            border-radius: 7px;
+            background-color: #4a90c4;
+            color: #fff;
+            font-weight: 600;
+        }
+    }
     .searchInput{
     .searchInput{
         height: 100%;
         height: 100%;
         display: flex;
         display: flex;

+ 37 - 1
src/views/ClassPage/ClassStudent.vue

@@ -10,7 +10,9 @@
                 </div>
                 </div>
             </div>
             </div>
 
 
-            <!-- <button @click="visible_add=true"> 添加学生 </button> -->
+            <button @click="visible_add=true"> 添加学生 </button>
+
+            <button class="import_class" @click="importData">批量导入学生</button>
 
 
             <classToggle v-model:isLeft="isSingleDelete">
             <classToggle v-model:isLeft="isSingleDelete">
                 <template #left>
                 <template #left>
@@ -161,6 +163,31 @@ const handleDelete = (sid) => {
         return item.officialNumber === sid
         return item.officialNumber === sid
     }).stuName
     }).stuName
 }
 }
+const importData = async () => {
+    console.log("importData");
+    const input = document.createElement('input')
+    input.type = 'file'
+    input.accept = '.xlsx,.xls,.csv'
+    input.addEventListener('change', async (e) => {
+        const target = e.target as HTMLInputElement
+        const selectedFile = target.files?.[0]
+        if (!selectedFile) {
+            isloading_add.value = false
+            return
+        }
+        isloading_add.value = true
+        try {
+            const res = await apis.importStudentsOfClass(classId, selectedFile)
+            console.log('Import response:', res)
+            getStudents()
+        } catch (error) {
+            console.error('Import failed:', error)
+        } finally {
+            isloading_add.value = false
+        }
+    })
+    input.click()
+}
 const DeleteStudent = async () =>{
 const DeleteStudent = async () =>{
     isloading.value = true
     isloading.value = true
     await apis.deleteStudentOfClass(classId,[deleteId.value])
     await apis.deleteStudentOfClass(classId,[deleteId.value])
@@ -274,6 +301,15 @@ onMounted(() => {
     width: 100%;
     width: 100%;
     height: 100%;
     height: 100%;
 }
 }
+.import_class{
+    font-size: 14px;
+    padding: 1px 12px;
+    background-color: #c45c4a;
+    color: #fff;
+    border: none;
+    border-radius: 20px;
+    cursor: pointer;
+}
 .search{
 .search{
     height: 50px;
     height: 50px;
     width: 100%;
     width: 100%;

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

@@ -151,61 +151,7 @@ const Classdata = ref<ClassVO>({
   classCode: null,
   classCode: null,
 })
 })
 const sortMethods = ["默认","成绩从低到高","成绩从高到低","未批改优先","未提交优先"]
 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({
 const Studentdata = ref({
     "officialNumber": 0,
     "officialNumber": 0,
     "stuName": "",
     "stuName": "",

+ 17 - 0
src/views/Home/component/Silder/index.scss

@@ -97,6 +97,23 @@
       &:active {
       &:active {
         transform: translateY(0);
         transform: translateY(0);
       }
       }
+      
+      &.disabled {
+        background-color: #cccccc;
+        color: #999999;
+        cursor: not-allowed;
+        opacity: 0.6;
+        
+        &:hover {
+          background-color: #cccccc;
+          transform: none;
+          box-shadow: none;
+        }
+        
+        &:active {
+          transform: none;
+        }
+      }
     }
     }
   }
   }
 
 

+ 16 - 1
src/views/Home/component/Silder/index.vue

@@ -35,7 +35,22 @@
               <div class="slider-tag-item" @click="openEditor(data.assignment.descriptionFile, data.assignment.assignmentName)">作业要求</div>
               <div class="slider-tag-item" @click="openEditor(data.assignment.descriptionFile, data.assignment.assignmentName)">作业要求</div>
             </template>
             </template>
             <template v-if="data.assignment.attachments">
             <template v-if="data.assignment.attachments">
-              <div v-for="(item, index) in data.assignment.attachments" :key="index" class="slider-tag-item" @click="openEditor(item, '附件' + index)">作业描述文件下载</div>
+              <!-- <div v-for="(item, index) in data.assignment.attachments" :key="index" class="slider-tag-item" @click="openEditor(item, '附件' + index)">作业描述文件下载</div> -->
+              <el-tooltip 
+                v-for="(item, index) in data.assignment.attachments" 
+                :key="index" 
+                :content="!item || item.trim() === '' ? '暂无描述文件' : ''"
+                :disabled="item && item.trim() !== ''"
+                placement="top"
+              >
+                <div 
+                  class="slider-tag-item" 
+                  :class="{ 'disabled': !item || item.trim() === '' }"
+                  @click="item && item.trim() !== '' && openEditor(item, '附件' + index)"
+                >
+                  作业描述文件下载
+                </div>
+              </el-tooltip>
             </template>
             </template>
           </div>
           </div>
         </template>
         </template>