Quellcode durchsuchen

对AI批改页面进行了响应式设计处理,处理了一些细节,然后教师批改页面进行了一些改造

Jiang Pengyu vor 7 Monaten
Ursprung
Commit
f4ba9f349d

+ 173 - 17
src/views/AIEvaluationPage/AIEvaluationPage.vue

@@ -64,7 +64,14 @@
         <div class="rectangle">
           <template v-if="selected === 'trapezoid'">
             <el-scrollbar height="100%" class="evaluation-scrollbar">
-              <div class="overall-evaluation-content">
+              <!-- 整体评价加载状态 -->
+              <div v-if="isLoadingOverall" class="loading-container">
+                <el-icon class="is-loading" :size="40" color="#409EFF">
+                  <Loading />
+                </el-icon>
+                <div class="loading-text">AI 正在生成整体评价,请稍候...</div>
+              </div>
+              <div v-else class="overall-evaluation-content">
                 <div class="avatar-container">
                   <div class="avatar-wrapper">
                     <img src="@/image/seecoderLogo.png" width="32" >
@@ -81,11 +88,29 @@
           </template>
           <template v-else>
               <el-scrollbar height="100%" class="evaluation-scrollbar">
-                <div class="sentence-evaluation-content">
+                <!-- 逐句评价加载状态 -->
+                <div v-if="isLoadingSentence" class="loading-container">
+                  <el-icon class="is-loading" :size="40" color="#409EFF">
+                    <Loading />
+                  </el-icon>
+                  <div class="loading-text">AI 正在生成逐句评价,请稍候...</div>
+                  <div class="loading-tip">由于需要对每个句子进行详细分析,此过程可能需要较长时间</div>
+                </div>
+                <div v-else class="sentence-evaluation-content">
                   <template v-if="data.sentenceEvaluationList?.length == 0">
                     <div class="no-evaluation">暂无逐句评价</div>
                   </template>
+                  <template v-else-if="data.sentenceEvaluationList?.length > 0 && !data.sentenceEvaluationList[0].evaluation">
+                    <div class="no-evaluation">
+                      <div style="margin-bottom: 10px;">⚠️ 后端返回的逐句评价数据不完整</div>
+                      <div style="font-size: 12px; color: #666;">
+                        已收到 {{ data.sentenceEvaluationList.length }} 条评价记录,但缺少评价内容(evaluation)和分类(category)字段。<br/>
+                        请联系后端检查 AI 评价生成逻辑。
+                      </div>
+                    </div>
+                  </template>
                   <el-card v-for="sentenceEvaluation in data.sentenceEvaluationList"
+                           v-else
                            :key="sentenceEvaluation.id"
                            class="sentence-card" 
                            shadow="hover"
@@ -141,13 +166,13 @@
 </template>
 
 <script setup lang="ts">
-  import {onMounted, reactive, ref, watch} from "vue";
+  import {onMounted, onUnmounted, reactive, ref, watch} from "vue";
   import useApis from "@/apis";
   import MyComponent from "@/views/AIEvaluationPage/MyComponent.vue";
   import {ElMessage} from "element-plus";
   import {useRoute} from "vue-router";
   import {useStore} from "@/store";
-  import { ArrowRight } from "@element-plus/icons-vue";
+  import { ArrowRight, Loading } from "@element-plus/icons-vue";
 
   interface recordVersion {
     id: number,
@@ -171,6 +196,10 @@
     versionList: []
   })
   const selectedVersion = ref(1.2)
+  const isLoadingSentence = ref(false) // 逐句评价加载状态
+  const isLoadingOverall = ref(false) // 整体评价加载状态
+  let sentenceLoadingTimer: NodeJS.Timeout | null = null // 逐句评价延迟加载计时器
+  let sentencePollingInterval: NodeJS.Timeout | null = null // 逐句评价轮询计时器
 
   const toEvaluation = () => {
     apis.overAllEvaluation(assignmentId, store.user.id)
@@ -197,29 +226,111 @@
         })
   }
 
+  // 轮询获取逐句评价(在60秒内每5秒轮询一次)
+  const pollSentenceEvaluation = (version: number) => {
+    let pollCount = 0
+    const maxPolls = 12 // 60秒 / 5秒 = 12次
+    
+    sentencePollingInterval = setInterval(() => {
+      pollCount++
+      
+      // 如果已经轮询了12次(60秒),停止轮询
+      if (pollCount >= maxPolls) {
+        if (sentencePollingInterval) {
+          clearInterval(sentencePollingInterval)
+          sentencePollingInterval = null
+        }
+        isLoadingSentence.value = false
+        return
+      }
+      
+      // 请求逐句评价
+      apis.getSentence(assignmentId, store.user.id, version)
+        .then(res => {
+          if(res.data.code === 200 && res.data.data && res.data.data.length > 0) {
+            // 获取到数据了,停止轮询
+            data.sentenceEvaluationList = res.data.data
+            isLoadingSentence.value = false
+            
+            if (sentencePollingInterval) {
+              clearInterval(sentencePollingInterval)
+              sentencePollingInterval = null
+            }
+            
+            // 检查数据完整性
+            const hasEvaluation = res.data.data.some(item => item.evaluation)
+            if(!hasEvaluation) {
+              console.warn('⚠️ 后端返回的逐句评价数据缺少 evaluation 字段')
+            }
+          }
+        })
+        .catch(err => {
+          console.error('轮询逐句评价失败:', err)
+        })
+    }, 5000) // 每5秒轮询一次
+  }
+
   const getEvaluation = (version: number) => {
     selectedVersion.value = version
+    
+    // 清除之前的计时器和轮询
+    if (sentenceLoadingTimer) {
+      clearTimeout(sentenceLoadingTimer)
+      sentenceLoadingTimer = null
+    }
+    if (sentencePollingInterval) {
+      clearInterval(sentencePollingInterval)
+      sentencePollingInterval = null
+    }
+    
+    // 开始加载整体评价
+    isLoadingOverall.value = true
     apis.getOverall(assignmentId,store.user.id,version)
         .then(res => {
-          // console.log(res)
           data.overallEvaluation = res.data.data
         })
         .catch(err => {
           console.log(err)
           data.overallEvaluation = {} as OverallEvaluation
         })
+        .finally(() => {
+          isLoadingOverall.value = false
+        })
+    
+    // 开始加载逐句评价
+    isLoadingSentence.value = true
     apis.getSentence(assignmentId,store.user.id,version)
         .then(res => {
-          // console.log(res)
           if(res.data.code === 200){
-            data.sentenceEvaluationList = res.data.data
+            const responseData = res.data.data
+            
+            // 如果返回了数据,直接使用
+            if(responseData && responseData.length > 0) {
+              data.sentenceEvaluationList = responseData
+              isLoadingSentence.value = false
+              
+              // 检查数据完整性
+              const hasEvaluation = responseData.some(item => item.evaluation)
+              if(!hasEvaluation) {
+                console.warn('⚠️ 后端返回的逐句评价数据缺少 evaluation 字段,请检查后端 AI 评价生成逻辑')
+              }
+            } else {
+              // 如果返回空数据,开始轮询(每5秒一次,持续60秒)
+              data.sentenceEvaluationList = []
+              pollSentenceEvaluation(version)
+            }
           } else {
+            console.warn('后端返回非200状态码:', res.data.code, res.data.msg)
             data.sentenceEvaluationList = []
+            // 非200状态码也开始轮询
+            pollSentenceEvaluation(version)
           }
         })
         .catch(err => {
-          console.log(err)
+          console.error('逐句评价API请求失败:', err)
           data.sentenceEvaluationList = []
+          // 请求失败也尝试轮询
+          pollSentenceEvaluation(version)
         })
   }
 
@@ -276,6 +387,18 @@
     // getEvaluation()
     getEvaluationVersionList()
   })
+
+  // 组件卸载时清理定时器
+  onUnmounted(() => {
+    if (sentenceLoadingTimer) {
+      clearTimeout(sentenceLoadingTimer)
+      sentenceLoadingTimer = null
+    }
+    if (sentencePollingInterval) {
+      clearInterval(sentencePollingInterval)
+      sentencePollingInterval = null
+    }
+  })
 </script>
 
 <script lang="ts">
@@ -288,9 +411,10 @@ export default {
 /* 主容器 - 响应式布局 */
 .ai-evaluation-container {
   display: flex;
+  align-items: stretch; /* 让所有子元素高度一致 */
   gap: 10px;
   padding: 10px;
-  min-height: 90vh;
+  height: calc(100vh - 80px); /* 使用视口高度减去顶部导航栏等空间 */
   box-sizing: border-box;
 }
 
@@ -340,7 +464,7 @@ export default {
   white-space: pre-wrap;
   margin-top: 40px;
   line-height: 30px;
-  height: calc(90vh - 80px);
+  flex: 1; /* 自动填充剩余空间 */
   overflow: hidden;
 }
 
@@ -360,7 +484,6 @@ export default {
 .evaluation-section {
   flex: 0 0 40%;
   min-width: 300px;
-  height: 89vh;
   overflow: hidden;
 }
 
@@ -491,6 +614,32 @@ export default {
   font-size: 14px;
 }
 
+/* 加载状态样式 */
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-height: 300px;
+  padding: 40px 20px;
+}
+
+.loading-text {
+  margin-top: 20px;
+  font-size: 16px;
+  color: #409EFF;
+  font-weight: 500;
+}
+
+.loading-tip {
+  margin-top: 10px;
+  font-size: 12px;
+  color: #999;
+  text-align: center;
+  max-width: 80%;
+  line-height: 1.5;
+}
+
 .sentence-card {
   cursor: pointer;
   position: relative;
@@ -531,7 +680,6 @@ export default {
 .version-section {
   flex: 0 0 20%;
   min-width: 180px;
-  height: 90vh;
   border-left: 1px solid #e4e7ed;
   border-radius: 0 8px 8px 0;
   box-shadow: -2px 0 4px rgba(0,0,0,0.05);
@@ -634,17 +782,24 @@ export default {
 @media screen and (max-width: 1200px) {
   .ai-evaluation-container {
     flex-wrap: wrap;
+    height: auto; /* 允许换行时自动调整高度 */
+    min-height: calc(100vh - 80px);
   }
   
   .essay-section,
   .evaluation-section {
     flex: 0 0 calc(50% - 10px);
     min-width: 280px;
+    height: calc(50vh - 50px); /* 平板模式下各占一半视口 */
+  }
+  
+  .essay-content-wrapper {
+    height: calc(100% - 80px);
   }
   
   .version-section {
     flex: 0 0 100%;
-    height: auto;
+    height: 40vh;
     min-height: 300px;
     border-left: none;
     border-top: 1px solid #e4e7ed;
@@ -663,6 +818,7 @@ export default {
     flex-direction: column;
     padding: 5px;
     gap: 8px;
+    height: auto; /* 移动端垂直排列,高度自适应 */
   }
   
   .essay-section,
@@ -674,19 +830,19 @@ export default {
   }
   
   .essay-section {
-    height: 50vh;
+    height: 45vh;
   }
   
   .essay-content-wrapper {
-    height: calc(50vh - 80px);
+    height: calc(100% - 80px);
   }
   
   .evaluation-section {
-    height: 60vh;
+    height: 45vh;
   }
   
   .version-section {
-    height: 40vh;
+    height: 35vh;
   }
   
   .trapezoid,

+ 2 - 2
src/views/CorrectPage/CorrectPage.vue

@@ -49,8 +49,8 @@
   
     <!-- 右侧内容区域 -->
     <div class="right-content">
-      <!-- 作业描述区域 -->
-      <div class="content-panel">
+      <!-- 作业描述区域 - 仅学生可见 -->
+      <div v-if="isStudent" class="content-panel">
         <!-- 上半部分:作业描述 -->
         <div class="assignment-section">
           <div class="section-header">

+ 1 - 1
src/views/CorrectPage/index.scss

@@ -967,7 +967,7 @@
     }
   }
 
-  // 作业描述面板样式
+  // 作业描述面板样式(仅学生可见)
   .content-panel {
     width: 100%;
     height: 240px;

+ 5 - 8
src/views/CourseSquare/component/CourseDetails/component/CourseHomeworkTable.vue

@@ -257,11 +257,7 @@ const openEditor = (url: string, title: string) => {
        //如果被禁用则提示
       if((props.disabled || isDisabled(teacherId, endDate))&&throttleMessage()){
         if(props.role == 'student'){
-          if(props.disabled){
-            ElMessage.warning('请先加入课程')
-          } else {
-            ElMessage.warning('作业已经超过截止时间')
-          }
+          ElMessage.warning('请先加入课程')
         }else if(props.role == 'teachers'){
           ElMessage.warning('无权限')
         }
@@ -287,7 +283,9 @@ const openEditor = (url: string, title: string) => {
   }
 
   const handleWriting = (assignmentId:number) => {
-    router.push(`/home/homeworkDetail/${assignmentId}?fromCourse=1`);
+    router.push(`/home/homeworkDetail/${assignmentId}?fromCourse=1`).then(() => {
+      window.location.reload()
+    });
     // Location.reload()
   }
 
@@ -299,9 +297,8 @@ const openEditor = (url: string, title: string) => {
       if(+teacherId != store.user.id){
         return true
       }
-    } else { // 是学生且超时则禁用
-      if(new Date() > new Date(endDate)) return true
     }
+    // 学生不再因为超过截止日期而禁用按钮
     return false
   }
 

+ 5 - 0
src/views/EditPage/EditPage.vue

@@ -70,12 +70,17 @@
       )
           .then(() => {
             router.push(`/home/homeworkDetail/${assignmentId}`)
+          }).then(() => {
+            window.location.reload()
           })
           .catch(() => {
             return
           })
     }else {
       router.push(`/home/homeworkDetail/${assignmentId}`)
+      .then(() => {
+        window.location.reload()
+      })
     }
   }