Jelajahi Sumber

添加导出学生行为数据的功能

廖彦舟 1 tahun lalu
induk
melakukan
3cbaa09a45

+ 24 - 0
src/apis/behavior.ts

@@ -37,6 +37,30 @@ const bevaviorApis = {
             }
         })
     },
+    getPauseStatisticData:(studentId:number,assignmentId :number,threshold:number) =>{
+        return axiosInstance.get(`${PREFIX}/pauseStatistics`, {
+            params: {
+                studentId,
+                assignmentId,
+                threshold
+            }
+        })
+    },
+    downLoadBehaviorData:(studentId:number,assignmentId :number)=>{
+        return axiosInstance.get(`/api/export/behaviorOverview/excel`, {
+            params: {
+                studentId,
+                assignmentId
+            }
+        })
+    },
+    downLoadAllBehaviorData:(assignmentId :number)=>{
+        return axiosInstance.get(`/api/export/behaviorOverview/excel/assignment`, {
+            params: {
+                assignmentId
+            }
+        })
+    },
 }
 
 export default bevaviorApis;

+ 98 - 8
src/views/BehaviorPage/BehaviorBoard.vue

@@ -15,6 +15,18 @@ interface AssignmentVO{
   assignmentName: string;
   assignmentId: number;
 }
+const dialogVisible = ref(false)
+const maxPauseTime = ref(2000)
+provide('maxPauseTime',maxPauseTime)
+
+const loadingStatus = ref({
+  edit:false,
+  pause:false,
+  process:false,
+  total:false
+})
+provide('loadingStatus',loadingStatus)
+
 const selectedCourse  = ref({courseName: "", courseId: -1});
 const courseList:Ref<CourseVO[]> = ref([]);
 let courseData:any[]= []
@@ -25,9 +37,18 @@ watch(selectedAssignment,()=>{
 const assignmentList:Ref<AssignmentVO[]> = ref([]);
 const dataIsLoading:Ref<boolean> = ref(true);
 let assignmentData:any[]= []
+
+const emptyText = computed(()=>{
+  if (selectedCourse.value.courseId === -1){
+    return "暂无课程"
+  }
+  else{
+    return "当前作业暂无学生提交"
+  }
+})
 function getCourseList(){
   console.log("获取课程列表")
-  let pageHTML = document.getElementById("page")
+  let pageHTML = document.getElementById("info-card")
   console.log(pageHTML)
   const loading = ElLoading.service({
     text: '获取数据中',
@@ -38,7 +59,7 @@ function getCourseList(){
       .then((res: any)=>{
         if (res&&res.data.code===200){
           courseData = res.data.data.records as [any];
-          console.log(courseData)
+          // console.log(courseData)
           courseList.value = courseData.map((item:any)=>{
             return {courseName: item.courseName, courseId: item.courseId}
           })
@@ -47,6 +68,8 @@ function getCourseList(){
           }
           else {
             selectedCourse.value = {courseName: "", courseId: -1}
+            loading.close();
+            return;
           }
           apis.getAssignmentByCourseId(courseData[0].courseId,0,1000)
             .then((res: any)=>{
@@ -139,10 +162,24 @@ function courseChanged(value:CourseVO){
 const engagements = ref([]);
 const currentEngagementIndex = ref(-1);
 const preButtonDisabled = computed(()=>{
-  return currentEngagementIndex.value<=0;
+  return currentEngagementIndex.value<=0
+      || loadingStatus.value.pause
+      || loadingStatus.value.process
+      || loadingStatus.value.total
+      || loadingStatus.value.edit
 });
 const nextButtonDisabled = computed(()=>{
-  return currentEngagementIndex.value>=engagements.value.length-1;
+  return currentEngagementIndex.value>=engagements.value.length-1
+      || loadingStatus.value.pause
+      || loadingStatus.value.process
+      || loadingStatus.value.total
+      || loadingStatus.value.edit
+})
+const isLoading = computed(()=>{
+  return loadingStatus.value.pause
+      || loadingStatus.value.process
+      || loadingStatus.value.total
+      || loadingStatus.value.edit
 })
 const currentEngagement = ref()
 watch(currentEngagementIndex,()=>{
@@ -186,6 +223,10 @@ function AssignmentChanged(value:AssignmentVO){
       })
 }
 
+/**
+ * 切换提交
+ * @param direction 1:下一个提交 -1:上一个提交 else:切换错误
+ */
 function assignmentSwitch(direction:number){
   if (direction===1){
     console.log("切换下一个作业")
@@ -199,6 +240,30 @@ function assignmentSwitch(direction:number){
     ElMessage.error("切换错误")
   }
 }
+const selectedType = ref('single');
+/**
+ * 下载行为数据
+ */
+function downLoadBehaviorData(){
+  dialogVisible.value = false
+  // ElMessage.success(selectedType.value)
+  if (selectedType.value === 'single'){
+    apis.downLoadBehaviorData(currentEngagement.value.studentId,currentEngagement.value.assignmentId)
+        .then((res:any) => {
+          console.log(res)
+        })
+  }
+  else if (selectedType.value === 'all'){
+    apis.downLoadAllBehaviorData(selectedAssignment.value.assignmentId)
+  }
+  else{
+    ElMessage.error("请选择下载类型")
+  }
+  // apis.downLoadBehaviorData(selectedAssignment.value.assignmentId,selectedCourse.value.courseId)
+}
+const handleClose = (done: () => void) => {
+  done()
+}
 onMounted(()=>{
   getCourseList();
 })
@@ -222,7 +287,7 @@ onMounted(()=>{
             <el-select v-model="selectedCourse"
                        placeholder="请选择课程"
                        value-key="courseId"
-                       :title="selectedCourse.courseName"
+                       :title="courseSelected?selectedCourse.courseName:'暂无课程'"
                        @change="courseChanged"
                        style="width: 130px;">
               <el-option
@@ -263,22 +328,47 @@ onMounted(()=>{
 <!--        </el-descriptions-item>-->
       </el-descriptions>
       <div v-if="currentEngagement" id="info-footer">
+        <el-button
+          :disabled="isLoading"
+          @click="dialogVisible = true" type="success">
+          导出行为数据
+        </el-button>
         <el-button
             :disabled="preButtonDisabled"
-            @change="assignmentSwitch(-1)" type="primary">
+            @click="assignmentSwitch(-1)" type="primary">
           上一个
         </el-button>
         <el-button
             :disabled="nextButtonDisabled"
-            @change="assignmentSwitch(1)" type="primary">
+            @click="assignmentSwitch(1)" type="primary">
           下一个
         </el-button>
       </div>
-      <el-empty v-else description="该作业暂无学生提交" style="height: 227px" />
+      <el-empty v-else :description="emptyText" style="height: 227px" />
     </el-card>
     <router-view :class="{hidden:!(!dataIsLoading&&currentEngagement)}" >
 
     </router-view>
+    <el-dialog
+        v-model="dialogVisible"
+        title="请选择导出的数据类型"
+        width="500"
+        :before-close="handleClose"
+    >
+      <el-radio-group v-model="selectedType">
+        <el-radio value="single">当前学生行为数据</el-radio>
+        <el-radio value="all">所有学生行为数据</el-radio>
+      </el-radio-group>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" type="danger">取消</el-button>
+          <el-button type="primary"
+                     @click="downLoadBehaviorData">
+            确认
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 

+ 38 - 13
src/views/BehaviorPage/component/EditCanvas.vue

@@ -4,6 +4,7 @@ import * as echarts from 'echarts';
 import {inject} from "vue-demi";
 import useApis from "@/apis";
 import {type EChartsType} from "echarts";
+import {ElMessage} from "element-plus";
 const props = defineProps<{
   assignmentId:number,
   splitNumber:number,
@@ -19,7 +20,12 @@ watch(currentEngagement,()=>{
   let myChart = echarts.getInstanceByDom(chartDom);
   getCanvasData(myChart);
 });
-
+const loadingStatus = inject('loadingStatus') as Ref<{
+  edit:boolean,
+  pause:boolean,
+  process:boolean,
+  total:boolean
+}>;
 function formatTimeWithMilliseconds(stamp:number ){
   const date = new Date(stamp);
   const hours = date.getHours().toString().padStart(2, '0');
@@ -36,23 +42,35 @@ function getCanvasData(chart:EChartsType){
   let studentId = currentEngagement.value.studentId;
   let assignmentId = currentEngagement.value.assignmentId;
   chart.showLoading({text:'加载中'});
+  loadingStatus.value.edit = true;
   apis.getBehaviorRecordAll(studentId,assignmentId)
   .then((res:any) => {
     if (res && res.data && res.data.code === 200){
       let rowData = res.data.data.events as any[];
       console.log("行为数据",rowData);
-      let count = 0;
-      let insertCount = 0;
       cursorData.length=0;
       productData.length = 0;
       processData.length = 0;
+      let processCount = 0;
       let zeroCount =0;
+      if (!rowData){
+        ElMessage.error('获取修改过程数据失败')
+        loadingStatus.value.edit = false;
+        chart.hideLoading();
+        return;
+      }
       rowData.forEach((item:any) => {
         // console.log("item",item)
         let startStamp = 0;
         if (item.startStamp){
           startStamp = item.startStamp;
         }
+        else {
+          return;
+        }
+        if (item.pauseTime <0){
+          return;
+        }
         if (startStamp == 0){
           zeroCount++;
         }
@@ -62,16 +80,22 @@ function getCanvasData(chart:EChartsType){
         }
         let clock = new Date(startStamp);
         clock.setHours(clock.getHours() + 16);
-        cursorData.push([clock,item.retain])
-        productData.push([clock,count])
-        processData.push([clock,insertCount])
-        if (item.type === 'insert'){
-          count++;
-          insertCount++;
+
+        cursorData.push([clock,Math.min(item.retain+1,item.docLength)])
+        productData.push([clock,item.docLength])
+        let temp = 0;
+        if (productData.length == 1){
+          temp = productData[0][1];
         }
-        if (item.type === 'delete'){
-          count--;
+        else{
+          let pre = productData[productData.length-2][1];
+          if (item.docLength > pre){
+            temp += item.docLength - pre;
+          }
         }
+        processCount+=temp;
+        processData.push([clock,processCount])
+
       })
       // console.log("productData",productData)
       // console.log("cursorData",cursorData)
@@ -88,9 +112,9 @@ function getCanvasData(chart:EChartsType){
               }
             },
             show: true,
-            type: "line"
+            type: "line",
           },
-          // splitNumber: props.splitNumber
+          splitNumber: props.splitNumber
         },
         yAxis: {
           type: 'value',
@@ -147,6 +171,7 @@ function getCanvasData(chart:EChartsType){
       };
       chart.setOption( option);
       chart.hideLoading();
+      loadingStatus.value.edit = false;
     }
   })
   //测试用数据

+ 1 - 1
src/views/BehaviorPage/component/HeatMap.vue

@@ -170,7 +170,7 @@ onMounted(async () => {
 // 计算属性 - 处理原始数据并格式化为多行显示
 const formattedLines = computed(() => {
   // 计算每行最大宽度(适应窗口大小)
-  const maxLineWidth = Math.min(1000, window.innerWidth - 40)
+  const maxLineWidth = Math.min(900, window.innerWidth - 40)
   // 使用Map存储每个位置的字符、第一次操作的startStamp、最后一次操作的startStamp和保留位置
   const positionMap = new Map()
   let lastStartStamp = 0;

+ 24 - 187
src/views/BehaviorPage/component/PauseCanvas.vue

@@ -4,6 +4,7 @@ import * as echarts from 'echarts';
 import {inject} from "vue-demi";
 import useApis from "@/apis";
 import type {EChartsType} from "echarts";
+import {ElMessage} from "element-plus";
 const props = defineProps<{
   assignmentId:number,
   splitNumber:number,
@@ -31,6 +32,14 @@ watch(currentEngagement,()=>{
   let myChart = echarts.getInstanceByDom(chartDom);
   getCanvasData(myChart);
 });
+
+const maxPauseTime = inject('maxPauseTime') as Ref<number>;
+const loadingStatus = inject('loadingStatus') as Ref<{
+  edit:boolean,
+  pause:boolean,
+  process:boolean,
+  total:boolean
+}>;
 function getCanvasData(chart:EChartsType){
   if (!currentEngagement.value){
     return;
@@ -38,6 +47,7 @@ function getCanvasData(chart:EChartsType){
   let studentId = currentEngagement.value.studentId;
   let assignmentId = currentEngagement.value.assignmentId;
   chart.showLoading({text:'加载中'});
+  loadingStatus.value.pause = true;
   apis.getBehaviorRecordAll(studentId,assignmentId)
       .then((res:any) => {
         if (res && res.data && res.data.code === 200){
@@ -46,6 +56,12 @@ function getCanvasData(chart:EChartsType){
           let zeroCount =0;
           pauseData.length = 0;
           chartData.length = 0;
+          if (!rowData){
+            ElMessage.error('获取停顿数据失败')
+            loadingStatus.value.pause = false;
+            chart.hideLoading();
+            return;
+          }
           if (rowData.length>0){
             limit.max = rowData[0].pauseTime;
           }
@@ -55,6 +71,12 @@ function getCanvasData(chart:EChartsType){
             if (item.startStamp){
               startStamp = item.startStamp;
             }
+            else {
+              return;
+            }
+            if (item.pauseTime < 0){
+              return;
+            }
             if (startStamp == 0){
               zeroCount++;
             }
@@ -68,7 +90,7 @@ function getCanvasData(chart:EChartsType){
             chartData.push([clock,item.pauseTime])
             limit.max = Math.max(limit.max,item.pauseTime);
           })
-
+          maxPauseTime.value = limit.max;
           let option = {
             xAxis: {
               type:'time',
@@ -123,6 +145,7 @@ function getCanvasData(chart:EChartsType){
           };
           chart.setOption( option);
           chart.hideLoading();
+          loadingStatus.value.pause = false;
           chart.on('datarangeselected', (params:any) => {
             let range = params.selected as [number,number];
             chartData = pauseData.filter((item:any) => {
@@ -133,193 +156,7 @@ function getCanvasData(chart:EChartsType){
           });
         }
       })
-  //测试用数据
 
-  // productData = [[new Date('2025-07-26 00:0:20'),0],
-  //   [new Date('2025-07-26 00:0:40'),15],
-  //   [new Date('2025-07-26 00:1:0'),15],
-  //   [new Date('2025-07-26 00:1:20'),17],
-  //   [new Date('2025-07-26 00:1:40'),21],
-  //   [new Date('2025-07-26 00:2:0'),39],
-  //   [new Date('2025-07-26 00:2:20'),49],
-  //   [new Date('2025-07-26 00:2:40'),61],
-  //   [new Date('2025-07-26 00:3:0'),71],
-  //   [new Date('2025-07-26 00:3:20'),89],
-  //   [new Date('2025-07-26 00:3:40'),103],
-  //   [new Date('2025-07-26 00:4:0'),109],
-  //   [new Date('2025-07-26 00:4:20'),109],
-  //   [new Date('2025-07-26 00:4:40'),128],
-  //   [new Date('2025-07-26 00:5:0'),143],
-  //   [new Date('2025-07-26 00:5:20'),143],
-  //   [new Date('2025-07-26 00:5:40'),145],
-  //   [new Date('2025-07-26 00:6:0'),160],
-  //   [new Date('2025-07-26 00:6:20'),177],
-  //   [new Date('2025-07-26 00:6:40'),177],
-  //   [new Date('2025-07-26 00:7:0'),196],
-  //   [new Date('2025-07-26 00:7:20'),199],
-  //   [new Date('2025-07-26 00:7:40'),204],
-  //   [new Date('2025-07-26 00:8:0'),209],
-  //   [new Date('2025-07-26 00:8:20'),213],
-  //   [new Date('2025-07-26 00:8:40'),229],
-  //   [new Date('2025-07-26 00:9:0'),245],
-  //   [new Date('2025-07-26 00:9:20'),250],
-  //   [new Date('2025-07-26 00:9:40'),250],
-  //   [new Date('2025-07-26 00:10:0'),265],
-  //   [new Date('2025-07-26 00:10:20'),266],
-  //   [new Date('2025-07-26 00:10:40'),282],
-  //   [new Date('2025-07-26 00:11:0'),291],
-  //   [new Date('2025-07-26 00:11:20'),308],
-  //   [new Date('2025-07-26 00:11:40'),322],
-  //   [new Date('2025-07-26 00:12:0'),335],
-  //   [new Date('2025-07-26 00:12:20'),343],
-  //   [new Date('2025-07-26 00:12:40'),350],
-  //   [new Date('2025-07-26 00:13:0'),354],
-  //   [new Date('2025-07-26 00:13:20'),367],
-  //   [new Date('2025-07-26 00:13:40'),386],
-  //   [new Date('2025-07-26 00:14:0'),392],
-  //   [new Date('2025-07-26 00:14:20'),411],
-  //   [new Date('2025-07-26 00:14:40'),414],
-  //   [new Date('2025-07-26 00:15:0'),431],
-  //   [new Date('2025-07-26 00:15:20'),437],
-  //   [new Date('2025-07-26 00:15:40'),450],
-  //   [new Date('2025-07-26 00:16:0'),453],
-  //   [new Date('2025-07-26 00:16:20'),468],
-  //   [new Date('2025-07-26 00:16:40'),469],
-  //   [new Date('2025-07-26 00:17:0'),486],
-  //   [new Date('2025-07-26 00:17:20'),486],
-  //   [new Date('2025-07-26 00:17:40'),501],
-  //   [new Date('2025-07-26 00:18:0'),508],
-  //   [new Date('2025-07-26 00:18:20'),515],
-  //   [new Date('2025-07-26 00:18:40'),523],
-  //   [new Date('2025-07-26 00:19:0'),524],
-  //   [new Date('2025-07-26 00:19:20'),529],
-  //   [new Date('2025-07-26 00:19:40'),536],
-  //   [new Date('2025-07-26 00:20:0'),554],
-  //   [new Date('2025-07-26 00:20:20'),563],
-  //   [new Date('2025-07-26 00:20:40'),570],
-  //   [new Date('2025-07-26 00:21:0'),589],
-  //   [new Date('2025-07-26 00:21:20'),600],
-  //   [new Date('2025-07-26 00:21:40'),602],
-  //   [new Date('2025-07-26 00:22:0'),602],
-  //   [new Date('2025-07-26 00:22:20'),618],
-  //   [new Date('2025-07-26 00:22:40'),620],
-  //   [new Date('2025-07-26 00:23:0'),636],
-  //   [new Date('2025-07-26 00:23:20'),650],
-  //   [new Date('2025-07-26 00:23:40'),653],
-  //   [new Date('2025-07-26 00:24:0'),668],
-  //   [new Date('2025-07-26 00:24:20'),670],
-  //   [new Date('2025-07-26 00:24:40'),680],
-  //   [new Date('2025-07-26 00:25:0'),680],
-  //   [new Date('2025-07-26 00:25:20'),695],
-  //   [new Date('2025-07-26 00:25:40'),709],
-  //   [new Date('2025-07-26 00:26:0'),714],
-  //   [new Date('2025-07-26 00:26:20'),714],
-  //   [new Date('2025-07-26 00:26:40'),729],
-  //   [new Date('2025-07-26 00:27:0'),743],
-  //   [new Date('2025-07-26 00:27:20'),747],
-  //   [new Date('2025-07-26 00:27:40'),764],
-  //   [new Date('2025-07-26 00:28:0'),783],
-  //   [new Date('2025-07-26 00:28:20'),792],
-  //   [new Date('2025-07-26 00:28:40'),806],
-  //   [new Date('2025-07-26 00:29:0'),817],
-  //   [new Date('2025-07-26 00:29:20'),823],
-  //   [new Date('2025-07-26 00:29:40'),837],
-  //   [new Date('2025-07-26 00:30:0'),854],];
-  // cursorData = [[new Date('2025-07-26 00:0:20'),0],
-  //   [new Date('2025-07-26 00:0:40'),15],
-  //   [new Date('2025-07-26 00:1:0'),15],
-  //   [new Date('2025-07-26 00:1:20'),17],
-  //   [new Date('2025-07-26 00:1:40'),21],
-  //   [new Date('2025-07-26 00:2:0'),39],
-  //   [new Date('2025-07-26 00:2:20'),49],
-  //   [new Date('2025-07-26 00:2:40'),61],
-  //   [new Date('2025-07-26 00:3:0'),71],
-  //   [new Date('2025-07-26 00:3:20'),89],
-  //   [new Date('2025-07-26 00:3:40'),103],
-  //   [new Date('2025-07-26 00:4:0'),109],
-  //   [new Date('2025-07-26 00:4:20'),109],
-  //   [new Date('2025-07-26 00:4:40'),128],
-  //   [new Date('2025-07-26 00:5:0'),143],
-  //   [new Date('2025-07-26 00:5:20'),143],
-  //   [new Date('2025-07-26 00:5:40'),145],
-  //   [new Date('2025-07-26 00:6:0'),160],
-  //   [new Date('2025-07-26 00:6:20'),177],
-  //   [new Date('2025-07-26 00:6:40'),177],
-  //   [new Date('2025-07-26 00:7:0'),196],
-  //   [new Date('2025-07-26 00:7:20'),199],
-  //   [new Date('2025-07-26 00:7:40'),204],
-  //   [new Date('2025-07-26 00:7:41'),150],
-  //   [new Date('2025-07-26 00:7:59'),158],
-  //   [new Date('2025-07-26 00:8:0'),212],
-  //   [new Date('2025-07-26 00:8:20'),213],
-  //   [new Date('2025-07-26 00:8:40'),229],
-  //   [new Date('2025-07-26 00:9:0'),245],
-  //   [new Date('2025-07-26 00:9:20'),250],
-  //   [new Date('2025-07-26 00:9:40'),250],
-  //   [new Date('2025-07-26 00:10:0'),265],
-  //   [new Date('2025-07-26 00:10:20'),266],
-  //   [new Date('2025-07-26 00:10:40'),282],
-  //   [new Date('2025-07-26 00:11:0'),291],
-  //   [new Date('2025-07-26 00:11:20'),308],
-  //   [new Date('2025-07-26 00:11:40'),322],
-  //   [new Date('2025-07-26 00:12:0'),335],
-  //   [new Date('2025-07-26 00:12:20'),343],
-  //   [new Date('2025-07-26 00:12:40'),350],
-  //   [new Date('2025-07-26 00:13:0'),354],
-  //   [new Date('2025-07-26 00:13:20'),367],
-  //   [new Date('2025-07-26 00:13:40'),386],
-  //   [new Date('2025-07-26 00:14:0'),392],
-  //   [new Date('2025-07-26 00:14:20'),411],
-  //   [new Date('2025-07-26 00:14:40'),414],
-  //   [new Date('2025-07-26 00:15:0'),431],
-  //   [new Date('2025-07-26 00:15:20'),437],
-  //   [new Date('2025-07-26 00:15:40'),450],
-  //   [new Date('2025-07-26 00:16:0'),453],
-  //   [new Date('2025-07-26 00:16:20'),468],
-  //   [new Date('2025-07-26 00:16:40'),469],
-  //   [new Date('2025-07-26 00:17:0'),486],
-  //   [new Date('2025-07-26 00:17:20'),486],
-  //   [new Date('2025-07-26 00:17:40'),501],
-  //   [new Date('2025-07-26 00:18:0'),508],
-  //   [new Date('2025-07-26 00:18:20'),515],
-  //   [new Date('2025-07-26 00:18:40'),523],
-  //   [new Date('2025-07-26 00:19:0'),524],
-  //   [new Date('2025-07-26 00:19:20'),529],
-  //   [new Date('2025-07-26 00:19:40'),536],
-  //   [new Date('2025-07-26 00:20:0'),554],
-  //   [new Date('2025-07-26 00:20:20'),563],
-  //   [new Date('2025-07-26 00:20:40'),570],
-  //   [new Date('2025-07-26 00:21:0'),589],
-  //   [new Date('2025-07-26 00:21:20'),600],
-  //   [new Date('2025-07-26 00:21:40'),602],
-  //   [new Date('2025-07-26 00:22:0'),602],
-  //   [new Date('2025-07-26 00:22:20'),618],
-  //   [new Date('2025-07-26 00:22:40'),620],
-  //   [new Date('2025-07-26 00:23:0'),636],
-  //   [new Date('2025-07-26 00:23:20'),650],
-  //   [new Date('2025-07-26 00:23:40'),653],
-  //   [new Date('2025-07-26 00:24:0'),668],
-  //   [new Date('2025-07-26 00:24:20'),670],
-  //   [new Date('2025-07-26 00:24:40'),680],
-  //   [new Date('2025-07-26 00:25:0'),680],
-  //   [new Date('2025-07-26 00:25:20'),695],
-  //   [new Date('2025-07-26 00:25:40'),709],
-  //   [new Date('2025-07-26 00:26:0'),714],
-  //   [new Date('2025-07-26 00:26:20'),714],
-  //   [new Date('2025-07-26 00:26:21'),500],
-  //   [new Date('2025-07-26 00:26:30'),515],
-  //   [new Date('2025-07-26 00:26:39'),515],
-  //   [new Date('2025-07-26 00:26:40'),729],
-  //   [new Date('2025-07-26 00:27:0'),743],
-  //   [new Date('2025-07-26 00:27:20'),747],
-  //   [new Date('2025-07-26 00:27:40'),764],
-  //   [new Date('2025-07-26 00:28:0'),783],
-  //   [new Date('2025-07-26 00:28:20'),792],
-  //   [new Date('2025-07-26 00:28:40'),806],
-  //   [new Date('2025-07-26 00:29:0'),817],
-  //   [new Date('2025-07-26 00:29:20'),823],
-  //   [new Date('2025-07-26 00:29:40'),837],
-  //   [new Date('2025-07-26 00:30:0'),854],];
 }
 onMounted(()=>{
   let chartDom = document.getElementById('pause-canvas');

+ 32 - 11
src/views/BehaviorPage/component/ProcessCanvas.vue

@@ -18,7 +18,12 @@ watch(currentEngagement,()=>{
   let myChart = echarts.getInstanceByDom(chartDom);
   getCanvasData(myChart);
 });
-
+const loadingStatus = inject('loadingStatus') as Ref<{
+  edit:boolean,
+  pause:boolean,
+  process:boolean,
+  total:boolean
+}>;
 function formatTimeWithMilliseconds(stamp:number ){
   const date = new Date(stamp);
   const hours = date.getHours().toString().padStart(2, '0');
@@ -42,6 +47,7 @@ function getCanvasData(chart:EChartsType){
     return;
   }
   chart.showLoading({text:'加载中'});
+  loadingStatus.value.process = true;
   let studentId = currentEngagement.value.studentId;
   let assignmentId = currentEngagement.value.assignmentId;
   apis.getBehaviorRecordAll(studentId,assignmentId)
@@ -49,21 +55,31 @@ function getCanvasData(chart:EChartsType){
         if (res && res.data && res.data.code === 200){
           let rowData = res.data.data.events as any[];
           console.log("行为数据",rowData);
-          let count = 0;
-          let insertCount = 0;
           //数组初始化
           cursorData.length=0;
           productData.length = 0;
           processData.length = 0;
           pauseData.length = 0;
           chartData.length = 0;
+          let processCount = 0;
           let zeroCount =0;
+          if (!rowData){
+            chart.hideLoading();
+            loadingStatus.value.process = false;
+            return
+          }
           rowData.forEach((item:any) => {
             // console.log("item",item)
             let startStamp = 0;
             if (item.startStamp){
               startStamp = item.startStamp;
             }
+            else{
+              return;
+            }
+            if (item.pauseTime < 0){
+              return;
+            }
             if (startStamp == 0){
               zeroCount++;
             }
@@ -76,20 +92,24 @@ function getCanvasData(chart:EChartsType){
             clock.setFullYear(2025)
             clock.setMonth(6)
             clock.setDate(26);
-            cursorData.push([clock,item.retain])
-            productData.push([clock,count])
-            processData.push([clock,insertCount])
+            cursorData.push([clock,item.retain+1])
+            productData.push([clock,item.docLength])
             pauseData.push([clock,item.pauseTime])
             chartData.push([clock,item.pauseTime])
 
             limit.max = Math.max(limit.max,item.pauseTime);
-            if (item.type === 'insert'){
-              count++;
-              insertCount++;
+            let temp = 0;
+            if (productData.length == 1){
+              temp = productData[0][1];
             }
-            if (item.type === 'delete'){
-              count--;
+            else{
+              let pre = productData[productData.length-2][1];
+              if (item.docLength > pre){
+                temp += item.docLength - pre;
+              }
             }
+            processCount+=temp;
+            processData.push([clock,processCount])
           })
           let option = {
             xAxis: {
@@ -191,6 +211,7 @@ function getCanvasData(chart:EChartsType){
           };
           chart.setOption( option);
           chart.hideLoading();
+          loadingStatus.value.process = false;
           chart.on('datarangeselected', (params:any) => {
             let range = params.selected as [number,number];
             chartData = pauseData.filter((item:any) => {

+ 14 - 0
src/views/BehaviorPage/component/TotalCanvas.vue

@@ -11,6 +11,14 @@ defineProps<{
   assignmentId:number
 }>()
 const currentEngagement = inject('currentEngagement') as Ref<any>;
+
+const loadingStatus = inject('loadingStatus') as Ref<{
+  edit:boolean,
+  pause:boolean,
+  process:boolean,
+  total:boolean
+}>;
+
 watch(currentEngagement,()=>{
   if (currentEngagement.value === null){
     return;
@@ -26,6 +34,10 @@ function drawChart(chart: EChartsType){
   if (!currentEngagement.value){
     return;
   }
+  chart.showLoading({
+    text: '加载中'
+  });
+  loadingStatus.value.total = true;
   apis.getOverview(currentEngagement.value.studentId,currentEngagement.value.assignmentId)
   .then((res:any) => {
     // console.log(res);
@@ -74,6 +86,8 @@ function drawChart(chart: EChartsType){
         ]
       };
       chart.setOption( option);
+      chart.hideLoading();
+      loadingStatus.value.total = false;
     }
   })
 }

+ 2 - 2
src/views/BehaviorPage/subPage/BehaviorHome.vue

@@ -78,7 +78,7 @@ function toDetail(index:number){
     </template>
     <div style="width: 100%;height: 350px">
       <EditCanvas
-          :split-number="5"
+          :split-number="4"
           :assignment-id="1"/>
     </div>
   </el-card>
@@ -97,7 +97,7 @@ function toDetail(index:number){
     </template>
     <div style="width: 100%;height: 350px">
       <ProcessCanvas
-          :split-number="5"
+          :split-number="4"
           :assignment-id="1"/>
     </div>
   </el-card>

+ 66 - 9
src/views/BehaviorPage/subPage/PauseAnalysis.vue

@@ -1,8 +1,11 @@
 <script setup lang="ts">
 import "./TotalAnalysis.scss"
 import PauseCanvas from "@/views/BehaviorPage/component/PauseCanvas.vue";
-import {ref,type Ref} from "vue";
+import {onMounted, provide, reactive, ref, type Ref, watch} from "vue";
 import HeatMap from "@/views/BehaviorPage/component/HeatMap.vue";
+import {inject} from "vue-demi";
+import useApis from "@/apis";
+import {ElMessage} from "element-plus";
 
 interface StatisticData{
   totalTime:number,
@@ -11,17 +14,51 @@ interface StatisticData{
   medianPauseTime:number,
   standardDeviation:number,
 }
+const currentEngagement = inject('currentEngagement') as Ref<any>;
+const maxPauseTime = inject('maxPauseTime') as Ref<number>;
+
+const threshold = ref(2000);
+watch(currentEngagement,()=>{
+  getStatisticData()
+});
+
 const data:Ref<StatisticData> = ref({
-  totalTime:56,
-  pauseTimes:856,
-  averagePauseTime:3600,
-  medianPauseTime:2500,
-  standardDeviation:1.8
+  totalTime:undefined,
+  pauseTimes:undefined,
+  averagePauseTime:undefined,
+  medianPauseTime:undefined,
+  standardDeviation:undefined
 })
+const dataLoading = ref(true);
+const apis = useApis();
 function getStatisticData(){
   //TODO
-  // data.value = xxxxx
+  if (!currentEngagement.value){
+    return;
+  }
+  // threshold.value = 0;
+  dataLoading.value = true;
+  apis.getPauseStatisticData(currentEngagement.value.studentId,currentEngagement.value.assignmentId,threshold.value)
+      .then((res:any) => {
+        console.log(res);
+        if (res && res.data &&res.data.code === 200){
+          data.value.averagePauseTime = res.data.data.averagePauseTime;
+          data.value.medianPauseTime = res.data.data.medianPauseTime;
+          data.value.pauseTimes = res.data.data.pauseCount;
+          data.value.standardDeviation = res.data.data.standardDeviation;
+          data.value.totalTime = res.data.data.totalPauseTime;
+          dataLoading.value = false;
+        }
+      })
+      .catch((err:any) => {
+        ElMessage.error("获取停顿统计数据失败");
+        dataLoading.value = false;
+      })
 }
+
+onMounted(()=>{
+  getStatisticData();
+})
 </script>
 
 <template>
@@ -33,13 +70,26 @@ function getStatisticData(){
         </span>
       </div>
     </template>
-    <div class="statistic-container">
+    <div id="threshold-container">
+      <span class="threshold-title">停顿阈值</span>
+      <el-slider v-model="threshold"
+                 :min="0"
+                 show-input
+                 :disabled="dataLoading"
+                 style="width: 50%;margin-left: 50px"
+                 :format-tooltip="(value:number)=>{
+                   return value+'ms';
+                 }"
+                 @change="getStatisticData"
+                 :max="maxPauseTime"/>
+    </div>
+    <div class="statistic-container" v-loading="dataLoading">
       <el-statistic
           :value="data.totalTime"
           suffix="min"
           title="停顿总时长">
         <template #suffix>
-          <span class="statistic-suffix" >min</span>
+          <span class="statistic-suffix" >ms</span>
         </template>
       </el-statistic>
       <el-divider direction="vertical" style="height: 50px;margin: 0"/>
@@ -86,4 +136,11 @@ function getStatisticData(){
   font-size: 12px;
   color: var(--el-text-color-regular);
 }
+#threshold-container{
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  margin: 20px 0 20px 0;
+}
 </style>