Browse Source

Merge branch 'dev'

dengshaobo 2 weeks ago
parent
commit
0286f6d350

+ 2 - 1
src/App.vue

@@ -18,7 +18,7 @@ export default {
     },
     methods: {
         SubmitLogin() {
-            const username = '376050cy_xiyanzuo_015';
+            const username = '500072347220704';
             const password = '123456';
             // const type = '1';
             const schoolType = sessionStorage.getItem('schoolType');
@@ -29,6 +29,7 @@ export default {
             user.loginEmailPass({
                 username: username,
                 password: encrypt(password),
+                deviceType: "PC"
             }).then((res) => {
                 if (res.code === 200) {
                     this.$store.dispatch("user/CLEAR_LOCAL_STORAGE");//清空本地存储

BIN
src/assets/studentCase/banner.webp


BIN
src/assets/studentCase/question_1.webp


BIN
src/assets/studentCase/question_2.webp


BIN
src/assets/studentCase/question_3.webp


BIN
src/assets/studentCase/question_4.webp


+ 92 - 70
src/components/FiltersItem_ruoyan.vue

@@ -1,91 +1,113 @@
 <template>
-  <div class="filters_group">
-      <div v-for="(filterItem,index) in filtersData" :key="index">
-        <div class="tag_group" v-if="filterItem.list.length>0">
-          <div class="tag_group_title">{{ filterItem.name }}:</div>
-          <div class="tag_group_content">
-            <div class="tag_item" v-for="item in filterItem.list" :key="item.value" @click="selectItem(index, item.value, filterItem)" :class="item.value == filterItem.value?'active':''">
-              {{ item.label }}
+    <div class="filters_group">
+        <div v-for="(filterItem, index) in filtersData" :key="index">
+            <div class="tag_group" v-if="filterItem.list.length > 0">
+                <div class="tag_group_title">{{ filterItem.name }}:</div>
+                <div class="tag_group_content">
+                    <template v-for="item in filterItem.list">
+                        <div class="tag_item" @click="selectItem(index, item.value, filterItem)"
+                            :class="item.value == filterItem.value ? 'active' : ''">
+                            {{ item.label }}
+                        </div>
+                    </template>
+                </div>
             </div>
-          </div>
         </div>
-      </div>
     </div>
 </template>
 <script>
-  export default {
-    props:{
-      filtersData:{
-        type:Array,
-        default:()=>[]
-      },//筛选公共组件数据
+export default {
+    props: {
+        filtersData: {
+            type: Array,
+            default: () => []
+        },//筛选公共组件数据
     },
     data() {
-      return {
-      }
+        return {
+        }
     },
     mounted() {
     },
-    methods:{
+    methods: {
+
+        //筛选点击事件
+        selectItem(index, val, item) {
+            if (this.filtersData[index].value == val) return;
+            let d = { index, value: val }
+            this.$emit('selectItem', d, item)
+        },
+
+        // showTable(item) {
+        //     const showTotalScore = this.$store.state.report.showTotalScore
+
+        //     // isTotal:是否总分 0-不是 1-是
+        //     // subjectGroupType:学科分组类型 0-不分组 1-分组
+        //     const { isTotal, subjectGroupType } = item
 
-      //筛选点击事件
-      selectItem(index, val, item){
-        if(this.filtersData[index].value == val) return;
-        let d = {index, value: val}
-        this.$emit('selectItem', d, item)
-      }
+        //     //  当 isTotal 为 1 或 subjectGroupType 为 1 且 showTotalScore 为 false 的时候隐藏总分
+        //     if ((isTotal === 1 || subjectGroupType === 1) && !showTotalScore) {
+        //         return false
+        //     } else {
+        //         return true
+        //     }
+        // }
     }
-  }
+}
 </script>
 <style lang="scss" scoped>
 .filters_group {
-  background: #fff;
-  .tag_group{
-    padding:8px 0;
-    display: flex;
-    justify-content: flex-start;
-    .tag_group_title{
-      // width: 70px;
-      width: auto;
-      height: auto;
-      margin-right: 8px;
-      color:#333333;
-      font-size:14px;
-      text-align: left;
-      line-height: 30px;
-      font-weight: 600;
-      letter-spacing: 0em;
-    }
-    .tag_group_content
-    {
-      width: calc(100% - 80px);
-      display: flex;
-      flex-wrap: wrap;
-      gap: 8px;
-      .tag_item {
-        
-        padding:0 8px;
-        line-height:30px;
-        border-radius:2px;
-        font-size: 14px;
-        background: #fff;
-        color:#999999;
-        
-        cursor: pointer;
-        &.active {
-          // background: #2E64FA;
-          // color:white;
-          background: rgba(71,113,203,0.1);
-          color:#2E64FA;
+    background: #fff;
+
+    .tag_group {
+        padding: 8px 0;
+        display: flex;
+        justify-content: flex-start;
+
+        .tag_group_title {
+            // width: 70px;
+            width: auto;
+            height: auto;
+            margin-right: 8px;
+            color: #333333;
+            font-size: 14px;
+            text-align: left;
+            line-height: 30px;
+            font-weight: 600;
+            letter-spacing: 0em;
         }
-        &:hover
-        {
-          color:#2E64FA;
+
+        .tag_group_content {
+            width: calc(100% - 80px);
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+
+            .tag_item {
+
+                padding: 0 8px;
+                line-height: 30px;
+                border-radius: 2px;
+                font-size: 14px;
+                background: #fff;
+                color: #999999;
+
+                cursor: pointer;
+
+                &.active {
+                    // background: #2E64FA;
+                    // color:white;
+                    background: rgba(71, 113, 203, 0.1);
+                    color: #2E64FA;
+                }
+
+                &:hover {
+                    color: #2E64FA;
+                }
+            }
         }
-      }
+
+
     }
-    
-    
-  } 
 }
 </style>

+ 111 - 131
src/components/PaperImage.vue

@@ -6,11 +6,12 @@
         <img v-if="paperImgUrl" :src="paperImgUrl" >
       </div>
       <div class="no_paper_url" v-if="paperImgUrl==''">
+        <div>暂无答题卡</div>
         <!-- 暂无答题卡 -->
         <!-- 请先制作模版 -->
       </div>
       <div v-if="showContextMenu"  class="custom_context_menu"  :style="{ top: contextMenuY + 'px', left: contextMenuX + 'px' }" @click="hideContextMenu">
-        <div class="menu_item" @click="handleMenuAction('download')">图片另存为(放大后点另存可提高图片分率)</div>
+        <div class="menu_item" @click="handleMenuAction('download')">图片另存为</div>
         <div class="menu_item" @click="handleMenuAction('fitScreen')">适合屏幕</div>
         <div class="menu_item" @click="handleMenuAction('zoomIn')">放大(向上滚轮)</div>
         <div class="menu_item" @click="handleMenuAction('zoomOut')">缩小(向下滚轮)</div>
@@ -211,11 +212,11 @@ export default {
       deep: true // 如果需要深度监听数组内部对象的变化
     },//边框数据
 
-    currentId()
-    {
-      // console.log("当前选中项的id变化了",this.currentId);
-      this.drawImage();//更新边框数据并重新绘制
-    },
+    // currentId()
+    // {
+    //   // console.log("当前选中项的id变化了",this.currentId);
+    //   this.drawImage();//更新边框数据并重新绘制
+    // },
 
 
   },
@@ -371,7 +372,6 @@ export default {
         
         // 在canvas上按当前显示尺寸绘制所有试卷图片
         exportCtx.drawImage(this.image, 0, 0, this.paperImgInfo.width, this.paperImgInfo.height);
-
         // 重新绘制标注信息到导出canvas上(按原始图片尺寸)
         this.DrawDataInfoCommon(exportCtx,1,1);//导出按原试卷尺寸
           
@@ -669,10 +669,10 @@ export default {
               console.log("打印当前的批阅块数组",blockList);
               const drawLineData=JSON.parse(item.drawLineData);
               console.log("打印drawLineData",drawLineData);
-
               if(blockList.length>0)
               {
                 //跨页的
+                let jh=0;//打印需要减去的高度  跨页的
                 blockList.forEach((blockItem,index) => {
                   console.log("打印blockItem",blockItem);
                   if(blockItem.page==this.currentPage)
@@ -685,7 +685,7 @@ export default {
                       let drawItem=drawlineItem;
                       if(findIndex==index)
                       {
-                        drawItem=this.FindTargetObj(drawlineItem,blockList);
+                        // drawItem=this.FindTargetObj(drawlineItem,blockList);
                         blockPoint=blockList[findIndex];
       
         
@@ -723,9 +723,9 @@ export default {
                             console.log("打印blockList",blockList);
                             const coords = {
                               x: this.GetInteger(drawItem.x * zoomScale + offsetX),
-                              y: this.GetInteger(drawItem.y * zoomScale + offsetY - jHeight * zoomScale),
+                              y: this.GetInteger(drawItem.y * zoomScale + offsetY - jh * zoomScale),
                               endX: this.GetInteger(drawItem.endX * zoomScale + offsetX),
-                              endY: this.GetInteger(drawItem.endY * zoomScale + offsetY - jHeight * zoomScale)
+                              endY: this.GetInteger(drawItem.endY * zoomScale + offsetY - jh * zoomScale)
                             };
                             this.DrawHorizontalLine(coords.x, coords.y, coords.endX, coords.endY,ctx);
                             break;
@@ -735,9 +735,9 @@ export default {
                             // 绘制波浪线
                             const coords = {
                               startX: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
-                              startY: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2)),
+                              startY: parseFloat(((drawItem.y-jh) * zoomScale + offsetY).toFixed(2)),
                               endX: parseFloat((drawItem.endX * zoomScale + offsetX).toFixed(2)),
-                              endY: parseFloat((drawItem.endY * zoomScale + offsetY).toFixed(2))
+                              endY: parseFloat(((drawItem.endY-jh) * zoomScale + offsetY).toFixed(2))
                             };
                             this.DrawWaveLine(coords.startX, coords.startY, coords.endX, coords.endY,ctx);
                             break;
@@ -757,7 +757,25 @@ export default {
                             this.DrawText(coords.x, coords.y, drawItem.text,ctx);
                             break;
                           }
-                          
+                          case 6: 
+                          {
+                            // 绘制对号
+                            const coords = {
+                              x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
+                              y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
+                            };
+                            this.DrawText(coords.x, coords.y, '✔',ctx);
+                            break;
+                          }
+                          case 7: {
+                            // 绘制对号
+                            const coords = {
+                              x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
+                              y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
+                            };
+                            this.DrawText(coords.x, coords.y, '✘',ctx);
+                            break;
+                          }
                           default:
                             console.warn('未知的绘图类型:', drawItem.drawType);
                         }
@@ -765,18 +783,18 @@ export default {
                       
                     })
                   }
-                  
+                  jh=jh+blockItem.h;
                 });
               }
               else
               {
+                //非跨页的数据
                 for(const [index, drawItem] of drawLineData.entries()) {
                   console.log("打印drawItem",drawItem);
                   // 计算通用的缩放因子,避免重复计算
                   const zoomScale = zoomRate * scale;
                   const offsetX = blockPoint.x * zoomScale;
                   const offsetY = blockPoint.y * zoomScale;
-                  
                   // 扣分点 加分点 标记// 1:文字 (扣分留痕 显示扣分信息)  2:划线  3:波浪线   4:画笔 5:评语
                   switch(drawItem.drawType) {
                     case 1: {
@@ -839,6 +857,25 @@ export default {
                       this.DrawText(coords.x, coords.y, drawItem.text,ctx);
                       break;
                     }
+                    case 6: 
+                    {
+                      // 绘制对号
+                      const coords = {
+                        x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
+                        y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
+                      };
+                      this.DrawText(coords.x, coords.y, '✔',ctx);
+                      break;
+                    }
+                    case 7: {
+                      // 绘制对号
+                      const coords = {
+                        x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
+                        y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
+                      };
+                      this.DrawText(coords.x, coords.y, '✘',ctx);
+                      break;
+                    }
                     
                     default:
                       console.warn('未知的绘图类型:', drawItem.drawType);
@@ -846,82 +883,6 @@ export default {
                 }
               }
              
-
-              // for(const [index, drawItem] of drawLineData.entries()) {
-              //   console.log("打印drawItem",drawItem);
-              //   // 计算通用的缩放因子,避免重复计算
-              //   const zoomScale = zoomRate * scale;
-              //   const offsetX = blockPoint.x * zoomScale;
-              //   const offsetY = blockPoint.y * zoomScale;
-                
-              //   // 扣分点 加分点 标记// 1:文字 (扣分留痕 显示扣分信息)  2:划线  3:波浪线   4:画笔 5:评语
-              //   switch(drawItem.drawType) {
-              //     case 1: {
-              //       // 文字类型(扣分点/加分点标记)
-              //       const fontSize = Math.max(12, 40 * zoomScale);
-              //       ctx.font = `${fontSize}px Arial`;
-              //       ctx.fillStyle = '#D81E06';
-              //       ctx.textAlign = 'center';
-              //       ctx.textBaseline = 'middle';
-                    
-              //       const x = drawItem.x * zoomScale + offsetX;
-              //       const y = drawItem.y * zoomScale + offsetY;
-                    
-              //       if(drawItem.type==='reduce') {
-              //         ctx.fillText('-'+drawItem.score, x, y);  
-              //       } else if(drawItem.type==='bonus') {
-              //         ctx.fillText('+'+drawItem.score, x, y);  
-              //       }
-              //       break;
-              //     }
-                  
-              //     case 2: {
-              //       // 绘制横线
-              //       console.log("打印需要减去的高度",jHeight);
-              //       jHeight=0;
-              //       console.log("打印blockList",blockList);
-              //       const coords = {
-              //         x: this.GetInteger(drawItem.x * zoomScale + offsetX),
-              //         y: this.GetInteger(drawItem.y * zoomScale + offsetY - jHeight * zoomScale),
-              //         endX: this.GetInteger(drawItem.endX * zoomScale + offsetX),
-              //         endY: this.GetInteger(drawItem.endY * zoomScale + offsetY - jHeight * zoomScale)
-              //       };
-              //       this.DrawHorizontalLine(coords.x, coords.y, coords.endX, coords.endY);
-              //       break;
-              //     }
-                  
-              //     case 3: {
-              //       // 绘制波浪线
-              //       const coords = {
-              //         startX: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
-              //         startY: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2)),
-              //         endX: parseFloat((drawItem.endX * zoomScale + offsetX).toFixed(2)),
-              //         endY: parseFloat((drawItem.endY * zoomScale + offsetY).toFixed(2))
-              //       };
-              //       this.DrawWaveLine(coords.startX, coords.startY, coords.endX, coords.endY);
-              //       break;
-              //     }
-                  
-              //     case 4:
-              //       // 绘制画笔数据
-              //       this.DrawPenLine(drawItem,blockPoint);
-              //       break;
-                  
-              //     case 5: {
-              //       // 绘制评语
-              //       const coords = {
-              //         x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
-              //         y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
-              //       };
-              //       this.DrawText(coords.x, coords.y, drawItem.text);
-              //       break;
-              //     }
-                  
-              //     default:
-              //       console.warn('未知的绘图类型:', drawItem.drawType);
-              //   }
-              // }
-
               
             }
           
@@ -1552,6 +1513,24 @@ export default {
                     this.DrawText(coords.x, coords.y, drawItem.text);
                     break;
                   }
+                  case 6: {
+                    // 绘制对号
+                    const coords = {
+                      x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
+                      y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
+                    };
+                    this.DrawText(coords.x, coords.y, '✔');
+                    break;
+                  }
+                  case 7: {
+                    // 绘制错号
+                    const coords = {
+                      x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
+                      y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
+                    };
+                    this.DrawText(coords.x, coords.y, '✘');
+                    break;
+                  }
                   
                   default:
                     console.warn('未知的绘图类型:', drawItem.drawType);
@@ -1609,20 +1588,20 @@ export default {
     },
 
     //画横线
-      DrawHorizontalLine(x1,y1,x2,y2)
+      DrawHorizontalLine(x1,y1,x2,y2,ctx = null)
       {
         console.log("画横线起始坐标点",x1, y1, x2, y2);
-        const canvas = this.$refs.paperCanvas;
-        const ctx = canvas.getContext('2d');
-        ctx.strokeStyle = 'red';//设置红色
-        ctx.lineWidth = 2;//线条宽度
-        ctx.beginPath();
-        ctx.moveTo(x1, y1);
-        ctx.lineTo(x2, y2);
-        ctx.stroke();
+        // 如果没有传入上下文,则使用当前canvas的上下文
+        const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
+        canvasContext.strokeStyle = 'red';//设置红色
+        canvasContext.lineWidth = 2;//线条宽度
+        canvasContext.beginPath();
+        canvasContext.moveTo(x1, y1);
+        canvasContext.lineTo(x2, y2);
+        canvasContext.stroke();
       },
       //画波浪线
-      DrawWaveLine(startX, startY, endX, endY)
+      DrawWaveLine(startX, startY, endX, endY,ctx = null)
       {
         // console.log("画波浪线起始坐标点",startX, startY, endX, endY);
         const amplitude  = 2; // 波浪的宽度
@@ -1632,68 +1611,67 @@ export default {
         const dy=endY-startY;
 
 
-        // 开始绘制
-        const canvas = this.$refs.paperCanvas;
-        const ctx = canvas.getContext('2d');
-        ctx.strokeStyle = 'red';//设置红色
-        ctx.lineWidth = 2;//线条宽度
+         // 如果没有传入上下文,则使用当前canvas的上下文
+        const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
+        canvasContext.strokeStyle = 'red';//设置红色
+        canvasContext.lineWidth = 2;//线条宽度
         // 清除之前的线条
         // ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height);
-        ctx.beginPath();
-        ctx.moveTo(startX, startY);//绘制第一个点
+        canvasContext.beginPath();
+        canvasContext.moveTo(startX, startY);//绘制第一个点
         //绘制波浪
         // 绘制波浪线
         for (let x = startX; x <= endX; x += 1) {
           const y = startY + dy * (x - startX) / dx + amplitude * Math.sin(frequency * (x - startX));
-          ctx.lineTo(x, y);
+          canvasContext.lineTo(x, y);
         }
         // ctx.lineTo(endX, endY);//绘制最后一个点
         // ctx.lineWidth = 2; // 设置线条宽度
-        ctx.stroke();
+        canvasContext.stroke();
       },
 
       //绘制画笔数据
-      DrawPenLine(data,blockPoint)
+      DrawPenLine(data,blockPoint,zoomRate,scale, ctx = null)
       {
         
-        let linelist=data.drawlineData;
+        console.log("打印绘制画笔数据",data);
+         let linelist=data.drawlineData;
         if(linelist.length>0)
         {
-          const canvas = this.$refs.paperCanvas;
-          const ctx = canvas.getContext('2d');
-          ctx.strokeStyle = 'red';//设置红色
-          ctx.lineWidth = 2;//线条宽度
+          // 如果没有传入上下文,则使用当前canvas的上下文
+          const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
+          canvasContext.strokeStyle = 'red';//设置红色
+          canvasContext.lineWidth = 2;//线条宽度
           // 开始绘制
-          ctx.beginPath();
+          canvasContext.beginPath();
           
           // 绘制路径
           for (let i = 0; i < linelist.length; i++) {
             if(blockPoint)
             {
-              let x=parseFloat(((linelist[i].x + blockPoint.x)* this.zoomRate * this.scale).toFixed(2));
-              let y=parseFloat(((linelist[i].y + blockPoint.y)* this.zoomRate * this.scale).toFixed(2));
-              ctx.lineTo(x, y);
+              let x=parseFloat(((linelist[i].x + blockPoint.x)* zoomRate * scale).toFixed(2));
+              let y=parseFloat(((linelist[i].y + blockPoint.y)* zoomRate * scale).toFixed(2));
+              canvasContext.lineTo(x, y);
             }
            
           }
-          ctx.stroke();
-          ctx.closePath();
+          canvasContext.stroke();
+          canvasContext.closePath();
         }
       },
 
       //绘制文字
-      DrawText(startX, startY, text)
+      DrawText(startX, startY, text , ctx = null)
       {
-        // 开始绘制
-        const canvas = this.$refs.paperCanvas;
-        const ctx = canvas.getContext('2d');
+        // 如果没有传入上下文,则使用当前canvas的上下文
+        const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
         // 设置字体样式
-        ctx.font = '15px Arial';
-        ctx.fillStyle = 'red';
-        ctx.textAlign = 'left';
+        canvasContext.font = '15px Arial';
+        canvasContext.fillStyle = 'red';
+        canvasContext.textAlign = 'left';
         // ctx.textBaseline = 'middle';
         // 绘制文字
-        ctx.fillText(text,startX, startY);
+        canvasContext.fillText(text,startX, startY);
       },
 
     //下载图片绘制
@@ -2103,6 +2081,8 @@ export default {
     position: absolute;
     cursor: pointer;
     z-index: 9;
+    display: flex;
+    flex-direction: column;
     img
     {
       width: 100%;

+ 14 - 12
src/components/StudentPaper.vue

@@ -34,20 +34,19 @@
                 </el-table-column>
                 <el-table-column label="满分/答案" align="center">
                   <template slot-scope="scope">
-                    {{ scope.row.fullScore }}
-                    <span v-if="scope.row.questionAnswer">/{{ scope.row.questionAnswer }}</span>
+                    {{ scope.row.fullScore }}<span v-if="scope.row.questionAnswer">/{{ scope.row.questionAnswer }}</span>
                   </template>
                 </el-table-column>
                 <el-table-column label="得分/答案" align="center">
                   <template slot-scope="scope">
                     <div :class="scope.row.fullScore==scope.row.score?'':'question_score'">
-                    <template v-if="scope.row.displayType === 0 || scope.row.displayType === 2">{{ scope.row.displayName }}</template>
-                    <template v-else>
+                    <template v-if="questionDisplayType === 0">{{ scope.row.score }}</template>
+                    <template v-if="questionDisplayType === 1">
                       <img v-if="scope.row.correctType==0" src="@/assets/icon/icon_all_wrong.svg" style="width: 12px;" />
                       <img v-else-if="scope.row.correctType==2" src="@/assets/icon/icon_all_right.svg" style="width: 12px;" />
                       <img v-else src="@/assets/icon/icon_half_right.svg" style="width: 12px;" />
                     </template>
-                    <span v-if="scope.row.answer">/{{ scope.row.answer }}</span>
+                    <span v-if="scope.row.value">/{{ scope.row.value }}</span>
                     </div>
                   </template>
                 </el-table-column>
@@ -120,6 +119,7 @@ export default {
            currentDownLoadName: '',//当前学生试卷图片下载名称
            currentDrawData:[],//当前学生试卷答题标记数据
            questionList: [],//学生试卷题目列表
+           questionDisplayType:'',
            questionTableHeight: 0,//学生试卷题目列表高度
            usedCardType:null,//学生系统卡类型
            isLoading:false,//是否正在加载中
@@ -209,7 +209,7 @@ export default {
               console.log("打印学生试卷当前图片地址",this.currentPaperUrl);
 
               // 合并所有试卷图片中的题目列表
-              let allQuestions = [];
+              // let allQuestions = [];
               //先添加总分数据
               let totalScore = {
                 questionName: '总分',
@@ -225,13 +225,14 @@ export default {
               if (this.paperImageList.length > 0 && this.paperImageList[0].questionVOS) {
                 this.paperImageList[0].questionVOS.unshift(totalScore);
               }
-              this.paperImageList.forEach(item => {
-                if (item.questionVOS && item.questionVOS.length > 0) {
-                  allQuestions = allQuestions.concat(item.questionVOS);
-                }
-              });
+              // this.paperImageList.forEach(item => {
+              //   if (item.questionVOS && item.questionVOS.length > 0) {
+              //     allQuestions = allQuestions.concat(item.questionVOS);
+              //   }
+              // });
               
-              this.questionList = allQuestions;
+              this.questionList = res?.data?.studentAnswerBOS || [];
+              this.questionDisplayType = res?.data?.questionDisplayType;
               console.log("合并后的题目列表", this.questionList);
               
               this.CalculateTableHeight();//计算表格高度
@@ -246,6 +247,7 @@ export default {
               this.paperImageList = [];
               this.currentIndex = 0;
               this.questionList = [];
+              this.questionDisplayType = '';
               this.studentCode = '';
               this.questionTableHeight='';
               this.$nextTick(() => {

+ 14 - 0
src/http/api/reportStudent.js

@@ -86,6 +86,20 @@ const jointSchoolReport = {
             data
         );
     },
+    //查询学生一生一案数据
+    getOneStudentOneCase(data) {
+        return get(
+            base.prefix + "/api/v1/studentData/getOneStudentOneCase",
+            data
+        );
+    },
+    //编辑提交学生一生一案数据
+    changeOneStudentOneCase(data) {
+        return post(
+            base.prefix + "/api/v1/studentData/changeOneStudentOneCase",
+            data
+        );
+    },
 };
 //单校接口
 const schoolReport = {

+ 18 - 0
src/http/api/user.js

@@ -57,6 +57,24 @@ const user = {
     return get(base.prefix + '/api/v1/student_user/un_bind_wechat', data)
   },
 
+  // 个人中心-绑定手机号获取滑块验证
+  getBindPhoneSlider (data) {
+    return post(base.prefix + '/api/v1/student_user/obtain_slider_from_center', data)
+  },
+
+  //重新获取滑块验证码图片数据
+  obtainSliderImg(data)
+  {
+    return post(base.prefix + '/api/v1/student_user/re_obtain_slider',data)
+  },
+
+
+  //登录验证滑动验证码位置是否正确
+  checkSlideCaptcha(data)
+  {
+    return post(base.prefix + '/api/v1/student_user/validate_slider', data)
+  },
+
 
 }
 

+ 21 - 7
src/router/index.js

@@ -55,13 +55,13 @@ let studentAnalysisReport = {
             },
             component: () => import("@/views/analysisReport/studentPage/list/mainPage"),
         },
-        {
-            path: "/studentAnalysisReport/list", // 报告列表
-            meta: {
-                title: "校考分析报告",
-            },
-            component: () => import("@/views/analysisReport/studentPage/list/mainPage"),
-        },
+        // {
+        //     path: "/studentAnalysisReport/list", // 报告列表
+        //     meta: {
+        //         title: "校考分析报告",
+        //     },
+        //     component: () => import("@/views/analysisReport/studentPage/list/mainPage"),
+        // },
         {
             name: "reportDetails",
             path: "/studentAnalysisReport/reportDetails",
@@ -95,6 +95,20 @@ let studentAnalysisReport = {
                         title: "个人画像",
                     },
                 },
+                {
+                    path: "studentCase",
+                    component: () => import("@/views/analysisReport/studentPage/studentCase/mainPage"),
+                    meta: {
+                        title: "一生一案",
+                    },
+                },
+                {
+                    path: "studentReport",
+                    component: () => import("@/views/analysisReport/studentPage/downloadPdf/studentReport"),
+                    meta: {
+                        title: "一生一案",
+                    },
+                }
             ],
         },
     ],

+ 42 - 39
src/store/modules/report.js

@@ -3,50 +3,51 @@ let state = {
   scrollTop: 0,
   barMaxWidth: 40, //分析报告柱子最大宽度
   barMinWidth: 20, //分析报告柱子最小宽度
-  examId:localStorage.getItem('reportExamId') || '',//分析报告考试分析表主id
-  examCourseCode:localStorage.getItem('reportExamCourseCode') || '',//分析报告考试分析表选择的科目
-  examSelectItem:localStorage.getItem('reportExamItem') || {},//考试分析首页选择的考试item数据
+  examId: localStorage.getItem('reportExamId') || '',//分析报告考试分析表主id
+  examCourseCode: localStorage.getItem('reportExamCourseCode') || '',//分析报告考试分析表选择的科目
+  examSelectItem: localStorage.getItem('reportExamItem') || {},//考试分析首页选择的考试item数据
   examSelectCourseItem: localStorage.getItem('reportExamCourseItem') || {},//考试分析首页选择的科目item数据
-  filterData:[],//筛选数据
+  filterData: [],//筛选数据
   filterObject: {
-    examLevel:'',//1-联考 2-单校
-    contrastExamIds:[],//多次考试任务对比ID,不包含当前任务ID	
-    examId:'',//考试id	
+    examLevel: '',//1-联考 2-单校
+    contrastExamIds: [],//多次考试任务对比ID,不包含当前任务ID	
+    examId: '',//考试id	
     subjectCode: '', //科目code
     subjectGroupType: '', //是否为组合科目 1为组合科目 0为非组合科目
     subjectName: '',//科目名称
     isTotal: '' //是否为总分科目 1为总分 0为非总分
   },//分析报告顶部筛选数据对象  需要更多数据可以追加user_menuList
   isTotalScore: localStorage.getItem('reportIsTotalScore') || false,//考试分析选择的科目是否是总分
-  filterDataObject:{
-    graduatesList:[],//届别列表
-    levelsList:[],//学段列表
-    gradeList:[],//年级列表
-    examTypeList:[],//考试类型列表
+  filterDataObject: {
+    graduatesList: [],//届别列表
+    levelsList: [],//学段列表
+    gradeList: [],//年级列表
+    examTypeList: [],//考试类型列表
   },//分析报告考试筛选数据对象存储
-  isShowFilter:false,//是否显示顶部筛选数据
-  rateName:'四率',//考试的多率名称  三率还是四率还是五率  默认四率
-  student:{//学生分析
-    examWeight:[],//考试权重占比
+  isShowFilter: false,//是否显示顶部筛选数据
+  rateName: '四率',//考试的多率名称  三率还是四率还是五率  默认四率
+  student: {//学生分析
+    examWeight: [],//考试权重占比
     StudentRules: {
-        topNumber: 10, //大幅上升名次
-        bottomNumber: 10, //大幅下降名次
-        upNumber: 3, //平稳上升名次
-        lowNumber: 3, //平稳下降名次
+      topNumber: 10, //大幅上升名次
+      bottomNumber: 10, //大幅下降名次
+      upNumber: 3, //平稳上升名次
+      lowNumber: 3, //平稳下降名次
     },//需关注学生->学生判断规则设置
   },
-  lastExamIdList:[],// 考试综述或成绩单同步对比考试选择,保存最后选择的对比考试id
-  updateScrollTop:0,//更新分析报告页面滚动条位置 使其回到顶部
+  lastExamIdList: [],// 考试综述或成绩单同步对比考试选择,保存最后选择的对比考试id
+  updateScrollTop: 0,//更新分析报告页面滚动条位置 使其回到顶部
   lastExamRadioId: '', // 对标考试单选考试id
   lastExamRadioName: '', // 对标考试单选考试name
   lastExamSelectIds: [], // 历次对比考试list
-  examLevel: 2 // 任务级别 单校还是联考 1-联考 2-单校
+  examLevel: 2, // 任务级别 单校还是联考 1-联考 2-单校
+  showTotalScore: false, //是否显示总分(进入举一反三页面的时候,不显示总分)
 };
 // 同步
 let mutations = {
   //设置筛选数据
   SetFilterData(state, data) {
-    
+
     state.filterObject = data;
     // state.filterObject[data.key] = data.value;
     // if (data.key == "subjectInfo") {
@@ -54,52 +55,54 @@ let mutations = {
     // }
   },
 
+  SetShowTotalScore(state, data) {
+    state.showTotalScore = data;
+  },
+
   //设置分析报告store数据
   set_state(state, data) {
-    
+
     state[data.key] = data.value;
-   
+
   },
 
   // 设置读取本地存储的筛选数据
-  SetLocalFilterDataObject()
-  {
-    if(localStorage.getItem('filterDataObject')!=null)
-    {
-      state.filterDataObject=JSON.parse(localStorage.getItem('filterDataObject'));
+  SetLocalFilterDataObject() {
+    if (localStorage.getItem('filterDataObject') != null) {
+      state.filterDataObject = JSON.parse(localStorage.getItem('filterDataObject'));
     }
   },
   //设置 学生分析考试占比
-  SetExamWeight(state, data){
+  SetExamWeight(state, data) {
     state.student.examWeight = data;
   },
   //设置 需关注学生->学生判断规则设置
-  SetStudentRules(state, data){
+  SetStudentRules(state, data) {
     state.student.StudentRules = data;
   },
   //设置 同步对比考试id
-  SetLastExamIdList(state, data){
+  SetLastExamIdList(state, data) {
     state.lastExamIdList = data;
   },
 
   //回到顶部
-  GotoTop(state){
+  GotoTop(state) {
     state.updateScrollTop++;
   },
   //设置 同步对比考试id单选
-  SetLastExamRadioId(state, data){
+  SetLastExamRadioId(state, data) {
     state.lastExamRadioId = data;
   },
   //设置 同步对比考试id单选
-  SetLastExamRadioName(state, data){
+  SetLastExamRadioName(state, data) {
     state.lastExamRadioName = data;
   },
   //设置 同步对比考试id集合
-  SetLastExamSelectIds(state, data){
+  SetLastExamSelectIds(state, data) {
     state.lastExamSelectIds = data;
   },
   //设置任务级别
-  SetExamLevel(state, data){
+  SetExamLevel(state, data) {
     state.examLevel = data;
   },
 };

+ 8 - 1
src/styles/report.scss

@@ -2389,8 +2389,15 @@
   text-align: center;
   .web_mode{//网页模式
     width: 908px;
-    height: 1285px;
+    // height: 1285px;
     margin: 0 auto;
+    .download_btn{
+      position: fixed;
+      width: 160px;
+      height: 36px;
+      top: 148px;
+      right: 20px;
+    }
     .area_page
     {
       width:908px;

+ 6 - 26
src/views/analysisReport/components/dCharts/GaugeChart.vue

@@ -50,10 +50,10 @@ export default {
     return {
       myChart: null,
       colors: [
-        "#995FB3",
-        "#FAC858",
-        "#3BA272",
         "#5470C6",
+        "#3BA272",
+        "#FAC858",
+        "#995FB3",
         "#EE6666",
         "#72C0DD",
         "#90CB75",
@@ -70,30 +70,10 @@ export default {
         "#848BDC",
         "#D6AF83",
         "#84313D",//一组
-        "#995FB3",
-        "#FAC858",
-        "#3BA272",
         "#5470C6",
-        "#EE6666",
-        "#72C0DD",
-        "#90CB75",
-        "#EA7ACB",
-        "#FC8451",
-        "#65789B",
-        "#178BD3",
-        "#26A616",
-        "#A6129E",
-        "#D3A141",
-        "#234098",
-        "#047640",
-        "#B43535",
-        "#848BDC",
-        "#D6AF83",
-        "#84313D",//二组
-        "#995FB3",
-        "#FAC858",
         "#3BA272",
-        "#5470C6",
+        "#FAC858",
+        "#995FB3",
         "#EE6666",
         "#72C0DD",
         "#90CB75",
@@ -109,7 +89,7 @@ export default {
         "#B43535",
         "#848BDC",
         "#D6AF83",
-        "#84313D",//三
+        "#84313D"//一
       ]
     }
   },

+ 35 - 16
src/views/analysisReport/studentPage/downloadPdf/components/bookFlip.vue

@@ -1,5 +1,5 @@
 <template>
-    <div :class="['report_page', { 'full_screen': isShowFullScreen }]" style="position: absolute;top: -9999999px;z-index: -10;">
+    <div :class="['report_page', { 'full_screen': isShowFullScreen }]">
         <!-- 点击封面进入全屏 -->
         <div class="joint_print_area">
             <!-- 点击封面进入全屏预览模式 -->
@@ -63,6 +63,7 @@
             <!-- 下载模版 -->
             <!-- style="position: absolute;top: -9999999px;z-index: -10;" -->
             <div class="magazine-viewport web_mode">
+                <el-button class="download_btn" size="small" type="primary" :loading="pdfLoading" @click="DownloadPdfNew">下载PDF</el-button>
                 <!-- 封面 -->
                 <BookCover class="web_cover web_area_page" :showButton="false" :bookBg="`${bookBg}_bg`" :title="coverTitle" :subtitle="subtitle"></BookCover>
                 <slot type="web_mode"></slot>
@@ -70,6 +71,19 @@
                 <BookCover class="web_cover web_area_page" :showButton="false" :isCover="true" :bookBg="`${bookBg}_cover_bg`"></BookCover>
             </div>
         </div>
+        <div class="page_dialog">
+            <el-dialog title="下载PDF" class="page_dialog" :visible.sync="showReportLoading" width="500px" top="15%" append-to-body>
+                <div class="report_loading">
+                    <div class="report_loading_icon">
+                    <img src="@/assets/report/report_loading.png">
+                    </div>
+                    <div class="report_loading_title">正在努力生成PDF中,请稍等...</div> 
+                    <div class="report_loading_progress">
+                    <el-progress :text-inside="true" color="#2E64FA" text-color="#ffffff" :stroke-width="16" :percentage="targetProgress"></el-progress>
+                    </div>
+                </div>
+            </el-dialog>
+        </div>
     </div>
 </template>
 
@@ -124,7 +138,8 @@ export default {
             totalPages: 0, // 总页数(由 turn.js 计算)
             showReportLoading:false,
             targetProgress:0,
-            loading:false
+            loading:false,
+            pdfLoading:false,
         };
     },
     computed: {
@@ -568,10 +583,12 @@ export default {
         async DownloadPdfNew() {
             //获取所有的area_page 元素
             const elements = document.querySelectorAll(".web_mode .web_area_page");
-            const elLens = elements.length - 1;
-            this.loading = true;
-            // this.targetProgress = 1;
-            // this.showReportLoading = true;
+            const elLens = elements.length;
+            const elLensIndex = elLens - 1;
+            this.pdfLoading = true;
+            this.targetProgress = 0;
+            this.showReportLoading = true;
+            const itemProgress = elLens > 0 ? Math.ceil(100 / elLens) : 0; 
             const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
             const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
             const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
@@ -584,6 +601,7 @@ export default {
                     // 确保元素可见并已渲染,并设置为横向排列样式
                     element.style.visibility = 'visible';
                     element.offsetHeight; // 触发重排
+                    
                     // 等待元素完全渲染
                     await new Promise(resolve => setTimeout(resolve, 100));
                     
@@ -594,13 +612,11 @@ export default {
                         width: element.scrollWidth*2,
                         height: element.scrollHeight*2,
                         // 提高画布分辨率以获得更清晰的图像
-                        // canvasWidth: element.scrollWidth * 2,
-                        // canvasHeight: element.scrollHeight * 2,
-                        // 新增高清配置
-                        // pixelRatio: window.devicePixelRatio * 2,
+                        canvasWidth: element.scrollWidth * 2,
+                        canvasHeight: element.scrollHeight * 2,
                         style: {
                         'transform': 'scale(2)',
-                        'transform-origin': 'left top',
+                        'transform-origin': 'top left',
                         'background-color': '#ffffff',
                         // 关键:背景图尺寸适配原元素
                         'background-size': `${element.scrollWidth}px ${element.scrollHeight}px`, // 优先铺满且完整显示
@@ -613,10 +629,8 @@ export default {
                         },
                         bgcolor: '#ffffff'
                     });
-                    
                     // 获取图像属性并计算缩放比例
                     const imgProps = pdf.getImageProperties(dataUrl);
-
                     const imgWidth = imgProps.width;
                     const imgHeight = imgProps.height;
                     
@@ -628,6 +642,7 @@ export default {
                     // 居中放置图像
                     const xPosition = (pdfWidth - adjustWidth) / 2;
                     const yPosition = (pdfHeight - adjustHeight) / 2;
+
                     // console.log("打印pdf图片尺寸", imgWidth, imgHeight);
                     // console.log("打印pdf页面尺寸", pdfWidth, pdfHeight);
                     // console.log("打印pdf缩放比例", ratio);
@@ -640,16 +655,20 @@ export default {
                     }
                     //处理封面图铺满屏
                     let pdfAdjustHeight = adjustHeight;
-                    if(pageIndex == 0 || pageIndex == elLens){
+                    if(pageIndex == 0 || pageIndex == elLensIndex){
                         pdfAdjustHeight = adjustHeight + 6
                     }
                     pdf.addImage(dataUrl, "JPEG", xPosition, yPosition - 6, adjustWidth, pdfAdjustHeight);
+                    this.targetProgress += itemProgress;
+                    // 边界处理:进度值不超过100%
+                    this.targetProgress = Math.min(this.targetProgress, 100);
                 }
             }
             // 保存pdf文件
             pdf.save(`${this.reportTitle}_${this.coverTitle}.pdf`);
-            // this.showReportLoading = false; //关闭加载loading
-            this.loading = false;
+            this.showReportLoading = false; //关闭加载loading
+            this.targetProgress = 0;
+            this.pdfLoading = false;
             this.$emit('PdfLoadEnd');
         },
     }

+ 17 - 13
src/views/analysisReport/studentPage/downloadPdf/studentReport.vue

@@ -1,7 +1,6 @@
 <template>
     <!-- 学生报告 -->
-    <!-- v-loading="loading" element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.5)" :element-loading-text="loadingText" -->
-    <BookFlip :book-pages="bookPageImages" bookBg="student" :requestLoading="loading" :openLoading="openLoading" :coverTitle="`${stuClasName}${studentName}分析报告册`" subtitle="供参考学生使用" ref="bookFlipBox" @OpenBookImages="OpenBookImages" @PdfLoadEnd="PdfLoadEnd">
+    <BookFlip v-loading="loading" element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.5)" :element-loading-text="loadingText" :book-pages="bookPageImages" bookBg="student" :requestLoading="loading" :openLoading="openLoading" :coverTitle="`${stuClasName}${studentName}分析报告册`" subtitle="供参考学生使用" ref="bookFlipBox" @OpenBookImages="OpenBookImages" @PdfLoadEnd="PdfLoadEnd">
         <!-- 自定义每页内容 -->
         <template #default="slotProps">
             <template v-if="slotProps.type == 'web_mode'">
@@ -238,7 +237,7 @@
                                                             <span :class="GetDifficultyClass(scope.row[header.prop])"></span>
                                                             <span>{{ GetDifficultyName(scope.row[header.prop]) }}</span>
                                                         </template>
-                                                        <template v-else-if="header.prop == 'classGroupAvgScoreMap' || header.prop == 'classGroupScoreRateMap'">{{ scope.row?.[header.prop]?.[header.code] || '-' }}</template>
+                                                        <template v-else-if="header.prop == 'classGroupAvgScoreMap' || header.prop == 'classGroupScoreRateMap'">{{ scope.row?.[header.prop]?.[header.code] || '-' }}{{ scope.row?.[header.prop]?.[header.code]&&header.prop=='classGroupScoreRateMap'?'%':''}}</template>
                                                         <template v-else>{{ scope.row[header.prop] || '-' }}</template>
                                                     </template>
                                                 </el-table-column>
@@ -401,7 +400,7 @@ export default {
                 examLevel: this.$store.state.report.filterObject.examLevel, //1-联考 2-单校
                 contrastExamIds: this.$store.state.report.filterObject.contrastExamIds, //多次考试任务对比ID,不包含当前任务ID
                 examId: this.$store.state.report.filterObject.examId, //考试id
-                subjectCode: this.$store.state.report.filterObject.subjectCode, //科目code
+                subjectCode: 0, //this.$store.state.report.filterObject.subjectCode 科目code
                 subjectGroupType: this.$store.state.report.filterObject.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
                 isTotal: this.$store.state.report.filterObject.isTotal //是否为总分科目 1为总分 0为非总分
             }
@@ -478,7 +477,7 @@ export default {
             //多科成绩总览 科目标准分分析
             await this.QueryMultiSubjectData();
             //学生端查询总分,多科历次信息(联考)
-            // await this.QueryHistoryExamData();
+            await this.QueryHistoryExamData();
             // 学生端查询总分,多科总结建议信息(联考)
             await this.QuerySuggestionData();
             let index = 0;
@@ -510,7 +509,7 @@ export default {
                 await this.QueryOneSubjectGroupQuestionData(element.subjectCode,index);
                 await this.QueryOneSubjectCustomGroupQuestion(element.subjectCode,index);
                 //学生端查询单科-历次查询(联考)
-                // await this.QueryOneSubjectHistoryExamData(element.subjectCode,index);
+                await this.QueryOneSubjectHistoryExamData(element.subjectCode,index);
                 await this.QueryOneSubjectSuggestionData(element.subjectCode,index);
                 await this.FindStudentCard(element.subjectCode,index);
                 index++; // 每次循环后索引+1
@@ -674,7 +673,7 @@ export default {
                     this.$nextTick(()=>{
                         if((this.multiSubjectData.standardScoreAnalysisStatus === 0 && this.schoolType == 2) || this.schoolType == 1){
                             const desHeight = this.$refs?.standardScoreChartDes?.offsetHeight || 0;
-                            const divHeight = 404 + desHeight;
+                            const divHeight = 383 + desHeight;
                             this.multiSubjectData.chartPagesNum = this.SingleChartPage(this.multiSubjectData.datay.length > 0 ? divHeight : 0);
                         }else{
                             this.multiSubjectData.chartPagesNum = this.SingleChartPage(0);
@@ -723,7 +722,7 @@ export default {
                     })
                     this.historyExamData.datay = [datay];
                     this.historyExamData.tooltipData = [tooltipData];
-                    this.historyExamData.pageNum = this.SingleChartPage(this.historyExamData.chartData.length > 0 ? 363 : 0);
+                    this.historyExamData.pageNum = this.SingleChartPage(this.historyExamData.chartData.length > 0 && this.multiSubjectData.showHistory==1 ? 363 : 0);
                 } else {
                     this.historyExamData.chartData = [];
                     this.historyExamData.tooltipData = [];
@@ -797,7 +796,7 @@ export default {
                         prop:'score',
                         display:res?.data?.scoreStatus === 0 //0-显示,1-不显示
                     },{
-                        name:'赋分',
+                        name:`赋分${res?.data?.schoolId=='620212977630539776'?'(标准分)':''}`,
                         prop:'rateScore',
                         display:res?.data?.rateScoreStatus === 0 //0-显示,1-不显示
                     },{
@@ -816,10 +815,14 @@ export default {
                         name:'区排',
                         prop:'regionRank',
                         display:res?.data?.regionRankStatus === 0
+                    },{
+                        name:'实考人数',
+                        prop:'examNum',
+                        display:res?.data?.examNumStatus === 0
                     },{
                         name:'赋分等级',
                         prop:'rateScoreName',
-                        display:res?.data?.rateScoreStatus === 0
+                        display:res?.data?.rateScoreNameStatus === 0
                     },{
                         name:'标准分',
                         prop:'standardScore',
@@ -1002,7 +1005,7 @@ export default {
                     })
                     this.singleSubjectData[index].historyExamData.datay = [datay];
                     this.singleSubjectData[index].historyExamData.tooltipData = [tooltipData];
-                    this.singleSubjectData[index].historyExamData.pageNum = this.SingleChartPage(this.singleSubjectData[index].historyExamData.chartData.length > 0 ? 363 : 0);
+                    this.singleSubjectData[index].historyExamData.pageNum = this.SingleChartPage(this.singleSubjectData[index].historyExamData.chartData.length > 0 && this.singleSubjectData[index]?.showHistory==1 ? 363 : 0);
                 }
             })
         },
@@ -1109,8 +1112,9 @@ export default {
                 chartHeight = datay.length > 0 ? 393 : 0;//雷达图
             }
             const chartPagesNum = this.SingleChartPage(chartHeight);
-            const staticHeader = titleData.slice(0,1);//固定表头
-            const dynamicHeader = titleData.slice(1);//动态分组标头
+            const HeadData = titleData.filter(item=>item.prop!='smallQuestionNames');
+            const staticHeader = HeadData.slice(0,1);//固定表头
+            const dynamicHeader = HeadData.slice(1);//动态分组标头
             
             const pageTableData = this.TableRowAndColumnPage(dynamicHeader,tableData,7,1);//8列一个表
             const tablePagesNum = pageTableData.tablePagesNum;

+ 9 - 5
src/views/analysisReport/studentPage/scrolReport/transcript_single.vue

@@ -58,7 +58,7 @@
               </span>
             </div>
             <div class="score_item" v-if="subjectData?.data?.rateScoreStatus === 0">
-              <span class="title">赋分</span>
+              <span class="title">{{ `赋分${subjectData?.data?.schoolId=='620212977630539776'?'(标准分)':''}` }}</span>
               <span class="value">{{ subjectData?.data?.rateScore ?? '-' }}</span>
             </div>
             <div class="score_item" v-if="subjectData?.data?.classRankStatus === 0">
@@ -77,7 +77,11 @@
               <span class="title">区排</span>
               <span class="value">{{ subjectData?.data?.regionRank ?? '-' }}</span>
             </div>
-            <div class="score_item" v-if="subjectData?.data?.rateScoreStatus === 0">
+            <div class="score_item" v-if="subjectData?.data?.examNumStatus === 0">
+              <span class="title">实考人数</span>
+              <span class="value">{{ subjectData?.data?.examNum ?? '-' }}</span>
+            </div>
+            <div class="score_item" v-if="subjectData?.data?.rateScoreNameStatus === 0">
               <span class="title">赋分等级</span>
               <span class="value">{{ subjectData?.data?.rateScoreName ?? '-' }}</span>
             </div>
@@ -246,8 +250,8 @@
             align="left"
           >
             <el-table-column
-              v-for="title in groupData[group.value].titleData"
-              :key="title.prop"
+              v-for="(title,index) in groupData[group.value].titleData"
+              :key="`${title.prop}_${index}`"
               align="center"
               :label="title.name"
               show-overflow-tooltip
@@ -287,7 +291,7 @@
                   ><span :class="GetDifficultyClass(scope.row[title.prop])"></span
                   ><span>{{ GetDifficultyName(scope.row[title.prop]) }}</span></template
                 >
-                <template v-else-if="title.prop == 'classGroupAvgScoreMap' || title.prop == 'classGroupScoreRateMap'">{{ scope.row?.[title.prop]?.[title.code] || '-' }}</template>
+                <template v-else-if="title.prop == 'classGroupAvgScoreMap' || title.prop == 'classGroupScoreRateMap'">{{ scope.row?.[title.prop]?.[title.code] || '-' }}{{ scope.row?.[title.prop]?.[title.code]&&title.prop=='classGroupScoreRateMap'?'%':'' }}</template>
                 <template v-else>{{ scope.row[title.prop] || '-' }}</template>
               </template>
             </el-table-column>

+ 11 - 7
src/views/analysisReport/studentPage/scrolReport/transcript_total.vue

@@ -27,7 +27,7 @@
               {{ scope.row?.subjectName || '-' }}
             </template>
           </el-table-column>
-          <el-table-column v-for="title in middleTitleData" :key="title.prop" align="center" :label="title.name" min-width="100">
+          <el-table-column v-for="(title,index) in middleTitleData" :key="`${title.prop}_${index}`" align="center" :label="title.name" min-width="100">
             <template slot-scope="scope">
               <template v-if="title.prop == 'score'">
                 <!-- * 1-得分显示分数,小题分显示分数,2-得分显示分数,小题分显示对错
@@ -67,11 +67,12 @@
             </template>
           </el-table-column>
         </el-table>
-        <div class="module_describe">
+        <div class="module_describe" style="width: 100%;" v-if="gradeNameStatus===0">
           <ExpandableText>
-            说明:为了更好地反馈学习情况,将采用等级形式呈现学业结果。以下是学业等级划分的具体计算方式,请知悉。本次考试等级按照<span style="color: #2E64FA;">{{ this.academicLevelData.scoreType==1?'分数':'排名' }}占比区间</span>划分,<template v-if="academicLevelData.examAcademicLevelStr!=null">共分为{{ academicLevelData.num }}个等级:{{ academicLevelData.examAcademicLevelStr }}</template>等级是动态的,它只描述过去,却指引未来。你的每一次努力,都在为下一个等级重新定义!
+            说明:为了更好地反馈学习情况,将采用等级形式呈现学业结果。以下是学业等级划分的具体计算方式,请知悉。本次考试等级按照<span style="color: #2E64FA;">{{ academicLevelData.scoreType==1?'分数':'排名' }}占比区间</span>划分,<template v-if="academicLevelData.examAcademicLevelStr!=null">共分为{{ academicLevelData.num }}个等级:{{ academicLevelData.examAcademicLevelStr }}</template>等级是动态的,它只描述过去,却指引未来。你的每一次努力,都在为下一个等级重新定义!
           </ExpandableText>
         </div>
+        <div class="page_jg_20" v-else></div>
       </div>
     </div>
     <div class="report_module" v-if="suggestionData?.fullScore!='-' && suggestionData!=null">
@@ -239,7 +240,8 @@ export default {
       paperInfo: {},
       paperTitle: '', //答题卡弹框标题
       currentPageIndex: 0, //当前选中的答题卡第几页
-      showHistory:0
+      showHistory:0,
+      gradeNameStatus:1//学业等级文案
     }
   },
   watch: {
@@ -294,12 +296,11 @@ export default {
     },
     GetGroupClassValue(data,prop,code){
       const obj = data.find(item=>item.classGroupCode==code);
-      const val = prop=='groupClassRanks'?obj?.classGroupRankRate:obj?.groupClassResultScore;
+      const val = prop=='groupClassRanks'?obj?.classGroupRank:obj?.groupClassResultScore;
       return val ?? '-'
     },
     //学生端查询单科-我的成绩
     QueryOneSubjectData() {
-      this.subjectLoading = true
       this.$api.reportStudent[getApiName()].queryOneSubjectData(this.reportParam).then(res => {
           if (res.code == 200 && res.data) {
             const levelList = res.data?.academicLevelData?.examAcademicLevelList || [];
@@ -315,11 +316,14 @@ export default {
               this.academicLevelData.examAcademicLevelStr +=`${level}${index==0?'[':'('}${startScore}%-${endScore}%]${index==lens -1?'。':';'}`;
             })
             this.academicLevelData.scoreType = scoreType;
-            this.showHistory = res?.data?.showHistory || 0;
+            this.showHistory = res?.data?.showHistory ?? 0;
+            this.gradeNameStatus = res?.data?.gradeNameStatus ?? 1;
           } else {
             this.academicLevelData.examAcademicLevelStr = '';
             this.academicLevelData.scoreType = '';
             this.academicLevelData.num = '';
+            this.gradeNameStatus = 1;
+            this.showHistory = 0;
           }
         })
     },

+ 488 - 0
src/views/analysisReport/studentPage/studentCase/mainPage.vue

@@ -0,0 +1,488 @@
+<template>
+    <div class="page_report">
+        <div class="report_module">
+            <div class="introduction">
+                <img src="@/assets/studentCase/banner.webp" />
+            </div>
+            <div class="module_common">
+                <div class="module_common_title question_1">1.请填写你对各科的目标分数和目标排名。</div>
+                <div class="module_common_content module_table">
+                    <el-table :data="tableData" border stripe align="center">
+                        <el-table-column align="center" prop="target" label="目标" width="148" v-if="tableData.length > 0"></el-table-column>
+                        <!-- 各科列 -->
+                        <el-table-column align="center" v-for="subject in subjects" :key="subject" :label="subject">
+                            <template slot-scope="scope">
+                                <template v-if="!isEdit">
+                                    {{ scope.row[subject] || '-' }}
+                                </template>
+                                <el-input v-else v-model="scope.row[subject]" style="width: 100%;" @input="ValidateNumber(scope.row,subject)" />
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </div>
+            </div>
+            <div class="module_common">
+                <div class="module_common_title question_2">2.请选择你认为的第1个薄弱学科,并填写你对该学科的计划和困惑。</div>
+                <div class="module_common_content text_padding">
+                    <div class="common_content_title">薄弱学科1</div>
+                    <div class="common_content_select">
+                        <el-select v-model="firstWeakSubject.subjectName" placeholder="请选择科目" @change="ChangeFirstWeakSubject">
+                            <el-option v-for="(item,index) in subjects" :key="`subject_1_${index}`" :label="item" :value="item"></el-option>
+                        </el-select>
+                    </div>
+                    <div class="common_content_subtitle">计划和困惑</div>
+                    <div class="common_content_text">
+                        <el-input v-if="isEdit" type="textarea" placeholder="请输入你对该学科的计划和困惑。" v-model="firstWeakSubject.textValue" :rows="9" maxlength="1000" show-word-limit></el-input>
+                        <template v-else>
+                            <template v-if="firstWeakSubject?.textValueWrap?.length">
+                                <p v-for="text in firstWeakSubject.textValueWrap">{{ text }}</p>
+                            </template>
+                            <template v-else>暂无</template>
+                        </template>
+                    </div>
+                </div>
+            </div>
+            <div class="module_common">
+                <div class="module_common_title question_3">3.请选择你认为的第2个薄弱学科,并填写你对该学科的计划和困惑。</div>
+                <div class="module_common_content text_padding">
+                    <div class="common_content_title">薄弱学科2</div>
+                    <div class="common_content_select">
+                        <el-select v-model="secondWeakSubject.subjectName" placeholder="请选择科目" @change="ChangeSecondWeakSubject">
+                            <el-option v-for="(item,index) in subjects" :key="`subject_2_${index}`" :label="item" :value="item"></el-option>
+                        </el-select>
+                    </div>
+                    <div class="common_content_subtitle">计划和困惑</div>
+                    <div class="common_content_text">
+                        <el-input v-if="isEdit" type="textarea" placeholder="请输入你对该学科的计划和困惑。" v-model="secondWeakSubject.textValue" :rows="9" maxlength="1000" show-word-limit></el-input>
+                        <template v-else>
+                            <template v-if="secondWeakSubject?.textValueWrap?.length">
+                                <p v-for="text in secondWeakSubject.textValueWrap">{{ text }}</p>
+                            </template>
+                            <template v-else>暂无</template>
+                        </template>
+                    </div>
+                </div>
+            </div>
+            <div class="module_common">
+                <div class="module_common_title question_4">4.针对学习上的问题,你最想与哪位老师沟通?并想让老师提供什么帮助?</div>
+                <div class="module_common_content text_padding">
+                    <div class="common_content_text">
+                        <el-input v-if="isEdit" type="textarea" placeholder="请输入你对该学科的计划和困惑。" v-model="needTeacherValue" :rows="9" maxlength="1000" show-word-limit></el-input>
+                        <template v-else>
+                            <template v-if="needTeacherValueWrap?.length">
+                                <p v-for="text in needTeacherValueWrap">{{ text }}</p>
+                            </template>
+                            <template v-else>暂无</template>
+                        </template>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <GotoTop></GotoTop>
+    </div>
+</template>
+<script>
+import GotoTop from '@/views/analysisReport/components/GotoTop' //分析报告页面底部回到顶部组件
+export default {
+    name: 'oneStudOneCase',
+    components: {
+        GotoTop
+    },
+    data() {
+        return {
+            subjects:[],//科目
+            subjectsCode:[],//科目code
+            tableData: [],//表格数据
+            id: '',
+            examId: '',
+            schoolId: '',
+            studentName: '',
+            studentNo: '',
+            studentCode: '',
+            firstWeakSubject:{
+                subjectCode: 10,
+                subjectName: '',
+                textValue: '',
+                textValueWrap: [],
+            },//第一个薄弱学科
+            secondWeakSubject:{
+                subjectCode: 10,
+                subjectName: '',
+                textValue: '',
+                textValueWrap: []
+            },//第二个薄弱学科
+            needTeacherValue:'',//
+            needTeacherValueWrap:'',//
+            isEdit:false,//是否可编辑
+        }
+    },
+    computed: {
+        reportParam() {
+            return {
+                examId: this.$store.state.report.filterObject.examId, //考试id
+            }
+        },
+    },
+    mounted() {
+        this.GetOneStudentOneCase();
+    },
+    methods: {
+        ValidateNumber(row,subject) {
+            // 只允许数字输入
+            row[subject] = row[subject].replace(/[^0-9.]/g, '');
+        },
+        // 查询学生一生一案数据
+        GetOneStudentOneCase() {
+            this.$api.reportStudent.jointSchoolReport.getOneStudentOneCase(this.reportParam).then(res => {
+                if (res.code == 200 && res.data) {
+                    const {id,schoolId,studentName,studentNo,studentCode,subjectVoList,studentGoalVoList,firstWeakSubject,secondWeakSubject,needTeacherValue} = res.data;
+                    if(studentGoalVoList){
+                        const studentGoalList = studentGoalVoList || [];
+                        this.subjects = [];
+                        this.subjectsCode = [];
+                        this.tableData = [];
+                        let obj1 = {target: '目标分数'},obj2 = {target: '目标排名'};
+                        studentGoalList.forEach(item => {
+                            this.subjects.push(item.subjectName);
+                            this.subjectsCode.push(item.subjectCode);
+                            obj1[item.subjectName] = item?.goalScore || '';
+                            obj2[item.subjectName] = item?.goalRank || '';
+                        });
+                        this.tableData.push(obj1,obj2)
+                    }else{
+                        const subjectList = subjectVoList || [];
+                        this.subjects = [];
+                        this.subjectsCode = [];
+                        this.tableData = [];
+                        let obj1 = {target: '目标分数'},obj2 = {target: '目标排名'};
+                        subjectList.forEach(item => {
+                            this.subjects.push(item.subjectName);
+                            this.subjectsCode.push(item.subjectCode);
+                            obj1[item.subjectName] = '';
+                            obj2[item.subjectName] = '';
+                        });
+                        this.tableData.push(obj1,obj2)
+                    }
+                    this.id = id;
+                    this.examId = this.reportParam.examId;
+                    this.schoolId = schoolId;
+                    this.studentName = studentName;
+                    this.studentNo = studentNo;
+                    this.studentCode = studentCode;
+                    const firstTextValue = firstWeakSubject?.textValue || '';
+                    const secondTextValue = secondWeakSubject?.textValue || '';
+                    const needTextValue = needTeacherValue || '';
+                    const firstTextValueWrap = firstTextValue.split(/\r?\n/).filter(item => item !== '');
+                    const secondTextValueWrap = secondTextValue.split(/\r?\n/).filter(item => item !== '');
+                    this.firstWeakSubject = {
+                        subjectCode: firstWeakSubject?.subjectCode || '',
+                        subjectName: firstWeakSubject?.subjectName || '',
+                        textValue: firstTextValue,
+                        textValueWrap: firstTextValueWrap
+                    };//第一个薄弱学科
+                    this.secondWeakSubject = {
+                        subjectCode: secondWeakSubject?.subjectCode || '',
+                        subjectName: secondWeakSubject?.subjectName || '',
+                        textValue: secondTextValue,
+                        textValueWrap: secondTextValueWrap
+                    };//第二个薄弱学科
+                    this.needTeacherValue = needTextValue;
+                    const needTeacherValueWrap = needTextValue.split(/\r?\n/).filter(item => item !== '');
+                    this.needTeacherValueWrap = needTeacherValueWrap;
+                } else {
+                    this.subjects = [];//科目
+                    this.subjectsCode = [];//科目code
+                    this.tableData = [];//表格数据
+                    this.id = '';
+                    this.examId = '';
+                    this.schoolId = '';
+                    this.studentName = '';
+                    this.studentNo = '';
+                    this.studentCode = '';
+                    this.firstWeakSubject = {
+                        subjectCode: '',
+                        subjectName: '',
+                        textValue:'',
+                        textValueWrap: []
+                    };//第一个薄弱学科
+                    this.secondWeakSubject = {
+                        subjectCode: '',
+                        subjectName: '',
+                        textValue:'',
+                        textValueWrap: []
+                    };//第二个薄弱学科
+                    this.needTeacherValue = '';
+                    this.needTeacherValueWrap = [''];
+                }
+                //判断是否是已编辑
+                this.$emit('StudentCaseIsEdited',this.id);
+            })
+        },
+        //编辑
+        EditOneStudOneCase(isEdit){
+            this.isEdit = isEdit;
+            if(!isEdit){
+                const studentGoalVoList = this.subjects.map((item,index)=>{
+                    return {
+                        subjectCode: this.subjectsCode[index],
+                        subjectName: item,
+                        goalScore: this.tableData[0][item],
+                        goalRank: this.tableData[1][item]
+                    }
+                });
+                const firstWeakSubject = {
+                    subjectCode: this.firstWeakSubject.subjectCode,
+                    subjectName: this.firstWeakSubject.subjectName,
+                    textValue:this.firstWeakSubject.textValue
+                }
+                const secondWeakSubject = {
+                    subjectCode: this.secondWeakSubject.subjectCode,
+                    subjectName: this.secondWeakSubject.subjectName,
+                    textValue:this.secondWeakSubject.textValue
+                }
+                this.$api.reportStudent.jointSchoolReport.changeOneStudentOneCase({
+                    id:this.id,
+                    examId:this.examId,
+                    schoolId:this.schoolId,
+                    studentName:this.studentName,
+                    studentNo:this.studentNo,
+                    studentCode:this.studentCode,
+                    studentGoalVoList:studentGoalVoList,
+                    firstWeakSubject:firstWeakSubject,
+                    secondWeakSubject:secondWeakSubject,
+                    needTeacherValue:this.needTeacherValue
+                }).then(res => {
+                    if(res.code == 200){
+                        this.$message.success(res.msg);
+                        this.GetOneStudentOneCase();
+                    }else{
+                        this.$message.error(res.msg);
+                    }
+                })
+            }
+        },
+        ChangeFirstWeakSubject(val){
+            const index = this.subjects.indexOf(val);
+            this.firstWeakSubject.subjectCode = this.subjectsCode?.[index] || '';
+        },
+        ChangeSecondWeakSubject(val){
+            const index = this.subjects.indexOf(val);
+            this.secondWeakSubject.subjectCode = this.subjectsCode?.[index] || '';
+        }
+    }
+}
+</script>
+<style lang="scss" scoped>
+.report_module {
+    padding: 20px;
+    box-sizing: border-box;
+
+    .introduction {
+        width: 100%;
+        // height: 200px;
+        // background: url('@/assets/studentCase/banner.webp') left top no-repeat;
+        background-size: cover;
+        // background: linear-gradient( 281deg, #007EFF 0%, #1F8FFE 10.96%, #89C8FB 34.86%, #CFE8FE 73.44%, #E5ECFC 100%);
+        border-radius: 20px 20px;
+        overflow: hidden;
+
+        img {
+            width: 100%;
+        }
+    }
+
+    .module_common {
+        margin-top: 40px;
+        width: 100%;
+        padding-top: 40px;
+        position: relative;
+
+        .module_common_title {
+            position: absolute;
+            z-index: 2;
+            left: 0;
+            top: 0;
+            font-weight: 600;
+            font-size: 16px;
+            line-height: 40px;
+            color: #FFFFFF;
+            padding-left: 24px;
+
+            &.question_1 {
+                width: 367px;
+                height: 50px;
+                background: url('@/assets/studentCase/question_1.webp') left top no-repeat;
+                background-size: 100% 100%;
+            }
+
+            &.question_2 {
+                width: 556px;
+                height: 50px;
+                background: url('@/assets/studentCase/question_2.webp') left top no-repeat;
+                background-size: 100% 100%;
+            }
+
+            &.question_3 {
+                width: 556px;
+                height: 50px;
+                background: url('@/assets/studentCase/question_3.webp') left top no-repeat;
+                background-size: 100% 100%;
+            }
+
+            &.question_4 {
+                width: 596px;
+                height: 50px;
+                background: url('@/assets/studentCase/question_4.webp') left top no-repeat;
+                background-size: 100% 100%;
+            }
+        }
+
+        .module_common_content {
+            width: 100%;
+            background: #F4F9FF;
+            border-radius: 0px 10px 10px 10px;
+
+            &.module_table {
+                padding: 40px;
+                box-sizing: border-box;
+
+                :deep(.el-table) {
+                    border-radius: 6px;
+                    border: 1px solid #5AAFFF;
+                    border-right: 0px solid #5AAFFF;
+                    border-bottom: 0px solid #5AAFFF;
+                    .el-table__header th {
+                        background-color: #EEF7FF;
+                        color: #303133;
+                        font-weight: 500;
+                        font-size: 16px;
+                        .cell {
+                            color: #303133;
+                            font-weight: 500;
+                            font-size: 16px;
+                        }
+                    }
+                    .el-table__body-wrapper {
+                        table {
+                            tr {
+                                td {
+                                    border-right: 1px solid #5AAFFF;
+                                    border-bottom: 1px solid #5AAFFF;
+                                    &:first-child{
+                                        background-color: #EEF7FF;
+                                    }
+                                    .el-input__inner{
+                                        height: 36px;
+                                        line-height: 36px;
+                                        font-weight: 400;
+                                        font-size: 16px;
+                                        color: #333333;
+                                        text-align: center;
+                                    }
+                                }
+
+                                td:nth-last-child(2) {
+                                    border-right: 1px solid #5AAFFF;
+                                }
+
+                                td:last-child,
+                                th:last-child {
+                                    border-right: 0px solid #5AAFFF;
+                                }
+                            }
+                        }
+                    }
+
+                    thead {
+                        color: #333333 !important;
+                        font-weight: 500;
+                        font-size: 16px;
+                        height: 50px;
+                    }
+
+                    .cell {
+                        font-size: 16px;
+                        color: #333333;
+                        line-height: normal !important;
+                    }
+
+                    .el-table__cell {
+                        padding: 0;
+                        height: 50px;
+                    }
+                }
+            }
+            .el-table::before, .el-table--group::after, .el-table--border::after{
+                background-color:#5AAFFF;
+            }
+            :deep(.el-table__fixed)::before, :deep(.el-table__fixed-right)::before{
+                background-color:#5AAFFF;
+            }
+            :deep(.el-table--border) .el-table__cell{
+                border-right: 1px solid #5AAFFF;
+            }
+            :deep(.el-table) th.el-table__cell.is-leaf, :deep(.el-table) td.el-table__cell{
+                border-bottom: 1px solid #5AAFFF;
+            }
+            &.text_padding{
+                min-height: 240px;
+                padding: 30px 20px 20px;
+                box-sizing: border-box;
+                .common_content_title{
+                    display: flex;
+                    width: 100%;
+                    font-weight: 600;
+                    font-size: 16px;
+                    color: #333333;
+                    line-height: 22px;
+                }
+                .common_content_select{
+                    display: flex;
+                    width: 100%;
+                    margin-top: 10px;
+                    :deep(.el-input__inner){
+                        height: 36px;
+                        line-height: 36px;
+                        padding:0 12px;
+                        background-color: #F4F9FF;
+                        border-radius: 4px;
+                        border: 1px solid #DCDFE6;
+                    }
+                    :deep(.el-input__icon){
+                        line-height: 36px;
+                    }
+                }
+                .common_content_subtitle{
+                    display: flex;
+                    width: 100%;
+                    margin: 30px 0 10px;
+                    font-weight: 600;
+                    font-size: 16px;
+                    color: #333333;
+                    line-height: 22px;
+                }
+                .common_content_text{
+                    display: flex;
+                    width: 100%;
+                    flex-direction: column;
+                    :deep(.el-textarea__inner){
+                        border-radius: 10px;
+                        border: 1px solid #DCDFE6;
+                        font-weight: 400;
+                        font-size: 14px;
+                        color: #333333;
+                        line-height: 24px;
+                    }
+                    p{
+                        width: 100%;
+                        font-weight: 400;
+                        font-size: 14px;
+                        color: #333333;
+                        line-height: 24px;
+                    }
+                }
+            }
+        }
+    }
+}
+</style>

+ 8 - 2
src/views/analysisReport/wrongQuestion/index.vue

@@ -131,7 +131,7 @@
                                     </template>
                                 </div>
 
-                                <div class="flex">
+                                <div class="flex" style="line-height: 1.6 !important;">
                                     <div class="flex_left">【答&nbsp;&nbsp;&nbsp;&nbsp;案】</div>
                                     <div class="flex_right">
                                         <template
@@ -153,7 +153,7 @@
                                     </div>
                                 </div>
 
-                                <div class="flex">
+                                <div class="flex" style="line-height: 1.6 !important;">
                                     <div class="flex_left">【解&nbsp;&nbsp;&nbsp;&nbsp;析】</div>
                                     <div class="flex_right">
                                         <template v-if="question.questionData?.analysis || question.parseImg">
@@ -324,6 +324,7 @@ export default {
     watch: {
         subjectCode(newVal, oldVal) {
             if (newVal !== oldVal) {
+                this.pageParam.pageNum = 1;
                 this._queryStudentErrorQuestion();
             }
         },
@@ -337,6 +338,7 @@ export default {
     },
 
     created() {
+        this.$store.commit('report/SetShowTotalScore', false)
         const ExamInfoItem = JSON.parse(localStorage.getItem("ExamInfoItem") || "{}");
         const reportExamCourseItem = JSON.parse(localStorage.getItem("reportExamCourseItem") || "{}");
         this.examId = reportExamCourseItem.examId;
@@ -348,6 +350,10 @@ export default {
         await this._queryStudentErrorQuestion();
     },
 
+    beforeDestroy() {
+        this.$store.commit('report/SetShowTotalScore', true)
+    },
+
     methods: {
         countGlobalIndex(index) {
             return (this.pageParam.pageNum - 1) * this.pageParam.pageSize + index;

+ 1 - 1
src/views/layout/components/Header.vue

@@ -301,7 +301,7 @@ export default {
       }
     },
     logout() {
-      // this.$store.dispatch("user/logout");//暂不调用退出接口,直接跳转到登录页面
+      this.$store.dispatch("user/logout");//暂不调用退出接口,直接跳转到登录页面
       // window.location.href = `${process.env.VUE_APP_BASE}`;
       // 跳出 iframe,跳转到顶级窗口
       // window.top.location.href = `${process.env.VUE_APP_BASE}`;

+ 529 - 0
src/views/login/components/SlideCode.vue

@@ -0,0 +1,529 @@
+<template>
+  <transition name="zoom-center" appear>
+    <div v-if="visible" class="slide_code_modal" @click.self="HandleConfirm">
+        <div  class="slide_code_content">
+          <!-- <div class="modal_header">
+            <h3>{{ title }}</h3>
+            <i class="el-icon-close close_btn" @click="HandleConfirm"></i>
+          </div> -->
+          
+          <div class="modal_body">
+            <slot>
+              <div class="slider_container">
+                <div class="slider_text">
+                  请拖动滑块完成拼图
+                </div>
+                <div class="container_bg" :style="{ backgroundImage:  sliderBgUrl ? 'url(' + sliderBgUrl + ')' : '' }">
+                  <div class="slider_bg"  :style="{ left: sliderPositionX + 'px',top:sliderPositionY+'px', backgroundImage:  sliderImgUrl ? 'url(' + sliderImgUrl + ')' : '' }">
+
+                  </div>
+                  <!-- 验证成功的样式 -->
+                  <transition name="slide-up">
+                    <div class="slider_sucess" v-if="isSuccess">
+                      {{sliderTime }}秒的速度超过{{sliderPrent}}%的用户
+                    </div>
+                    <div class="slider_failed" v-if="isFailed">
+                      验证失败,请重新拖动滑块尝试
+                    </div>
+                  </transition>
+                </div>
+                <div class="slider_track">
+                  
+                  <div  class="slider_button"  :style="{ left: sliderPositionX + 'px' }"  @mousedown="startSlide"  @touchstart="startSlide">
+                    <i class="el-icon-d-arrow-right"></i>
+                  </div>
+                 
+                </div>
+              </div>
+            </slot>
+          </div>
+        </div>
+    </div>
+  </transition>
+</template>
+
+<script>
+import user from "@/http/api/user";
+
+export default {
+  name: 'SlideCode',
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    },//关闭打开
+
+    sliderUUID:{
+      type:String,
+      default:''
+    },//图片唯一凭证
+
+    sliderBgUrl:{
+      type:String,
+      default:''
+    },//背景图片地址
+
+    sliderImgUrl:{
+      type:String,
+      default:''
+    },//滑块图片地址
+
+    sliderPositionY:{
+      type:[Number,String],
+      default:0
+    },//滑块y轴位置
+
+    title: {
+      type: String,
+      default: '安全验证'
+    },
+
+  },
+  data() {
+    return {
+      visible: false,
+
+
+      sliderPositionX: 0,//滑块的x轴位置
+      sliderWidth: 0,
+      sliderTime: 0,//滑动耗时(秒)
+      sliderPrent:'98',//百分比
+      startTime: 0,//开始滑动的时间戳
+      sliderVerified:0,//滑动验证码验证状态 0 未开始  1 验证成功  -1 验证失败
+      isSliding: false,//是否开始滑动
+      isSuccess:false,//是否验证成功
+      isFailed:false,//是否验证失败
+      startX: 0,//这里将用于存储鼠标相对于轨道的初始偏移
+
+      sliderText: '按住滑块拖动',
+
+    };
+  },
+  watch: {
+    value: {
+      handler(newVal) {
+         this.visible = newVal;
+         this.ResetSlider();//重置滑块位置
+      },
+      immediate: true
+    },
+  },
+  methods: {
+
+
+    //验证滑动验证码位置是否正确
+    VerifySliderCaptcha()
+    {
+      let param={
+        sliderUUID: this.sliderUUID,//图片唯一凭证
+        x: Math.round(this.sliderPositionX),//滑块位置
+      };
+     
+      user.checkSlideCaptcha(param).then(res =>
+      {
+        console.log("验证滑动验证码返回结果",res);
+       
+        if(res.code==200)
+        {
+          //验证成功
+          this.isSuccess = true;
+          this.sliderVerified = 1;
+          this.sliderPhone=res.data;
+          setTimeout(() => {
+            this.HandleConfirm();//关闭弹窗
+          }, 1000);
+          
+          // 验证成功后,发送请求获取验证码
+          // this.sliderPhone=res.data;
+          
+          // const timer = setInterval(() => {
+          //   this.count--;
+          //   this.codeText = `${this.count} 秒`;
+          //   if (this.count <= 0) {
+          //     clearInterval(timer);
+          //     this.isCount = false;
+          //     this.count = 300;
+          //     this.codeText = '重新获取'
+          //   }
+          // }, 1000);
+
+        }
+        else if(res.code==601)
+        {
+          //验证成功
+          this.sliderVerified = 1;
+          this.isSuccess = true;
+          // 验证成功后,发送请求获取验证码
+          this.sliderPhone=res.data;
+          this.$message.warning("滑块验证通过,您上次的验证码还在有效期,请输入上次发送的验证码进行登录!");
+          setTimeout(() => {
+            this.HandleConfirm();//关闭弹窗
+          }, 1000);
+        }
+        else if(res.code==602)
+        {
+          //滑动验证码过期 重新获取验证码
+          this.$message.error("滑块验证码已过期,已为您重新获取!");
+          // this.ObtainSliderImg();
+          this.$emit('refresh');//重新获取验证码
+        }
+        else if(res.code==400)
+        {
+          this.isFailed=true;
+          //滑动验证码过期 重新获取验证码
+          // this.$message.error(res.msg);
+          this.sliderVerified = -1;
+          this.sliderPositionX=0;
+        }
+        else{
+          this.isFailed=true;
+          //验证失败
+          this.sliderVerified = -1;
+          //重置滑块位置
+          this.sliderPositionX=0;
+        }
+
+        //延迟2秒关闭失败层
+        setTimeout(() => {
+          this.isFailed = false;
+
+        }, 2000);
+      })
+
+    },
+
+    
+
+
+    //关闭弹窗
+    HandleConfirm() {
+      this.visible = false;
+
+      this.$emit('close',this.sliderVerified,this.sliderPhone);
+    },
+
+  
+    //重置滑块位置
+    ResetSlider() {
+      this.sliderPositionX = 0;
+      this.sliderVerified = 0;
+      this.sliderWidth = 0;
+      this.sliderText = '按住滑块拖动';
+      this.isSuccess = false;
+      this.isFailed=false;
+    },
+
+    //开始滑动事件
+    startSlide(event) {
+      event.preventDefault();
+      event.stopPropagation();
+      this.isSliding = true;//是否滑动
+
+      // 记录开始时间
+      this.startTime = Date.now();
+      // 获取轨道元素
+      const track = this.$el.querySelector('.slider_track');
+      const rect = track.getBoundingClientRect();
+      
+      // 计算鼠标点击位置距离轨道左边缘的距离
+      const clientX = event.type.includes('mouse') ? event.clientX : event.touches[0].clientX;
+      this.startX = clientX - rect.left - this.sliderPositionX;
+      
+      document.addEventListener('mousemove', this.onSlide);
+      document.addEventListener('mouseup', this.stopSlide);
+      document.addEventListener('touchmove', this.onSlide, { passive: false });
+      document.addEventListener('touchend', this.stopSlide);
+    },
+    
+    onSlide(event) 
+    {
+      if (!this.isSliding || this.isSuccess) return;
+      
+      const track = this.$el.querySelector('.slider_track');
+      const rect = track.getBoundingClientRect();
+      
+      // 计算当前鼠标距离轨道左边缘的距离
+      const currentClientX = event.type.includes('mouse') ? event.clientX : event.touches[0].clientX;
+      const currentRelativeX = currentClientX - rect.left;
+      
+      // 新的滑块位置 = 当前鼠标相对位置 - 初始点击时的相对偏移
+      // 这样能保证滑块跟随鼠标移动,且不会发生跳变
+      let newPixelPosition = currentRelativeX - this.startX;
+
+      // 计算最大可移动距离
+      const maxPosition = 300 - 50;
+      
+      // 限制范围                              
+      newPixelPosition = Math.max(0, Math.min(maxPosition, newPixelPosition));
+      
+      this.sliderPositionX = newPixelPosition;
+      
+
+      
+     
+    },
+    
+    stopSlide() {
+      if (!this.isSliding) return;
+      // 计算滑动耗时
+      const endTime = Date.now();
+      this.sliderTime = parseFloat(((endTime - this.startTime) / 1000).toFixed(2));
+      
+      console.log('滑动耗时:', this.sliderTime, '秒');
+       // 根据滑动时间计算百分比
+      if (this.sliderTime <= 1) {
+        this.sliderPrent = 99;
+      } else if (this.sliderTime <= 2) {
+        this.sliderPrent = 98;
+      } else if (this.sliderTime <= 3) {
+        this.sliderPrent = 97;
+      } else {
+        this.sliderPrent = Math.max(0, 96 - Math.floor(this.sliderTime - 3));
+      }
+      this.isSliding = false;
+      document.removeEventListener('mousemove', this.onSlide);
+      document.removeEventListener('mouseup', this.stopSlide);
+      document.removeEventListener('touchmove', this.onSlide);
+      document.removeEventListener('touchend', this.stopSlide);
+      // 松开鼠标后,发送请求验证滑块位置
+      this.VerifySliderCaptcha();
+      
+    },
+    
+
+    
+
+  },
+  
+  beforeDestroy() {
+    document.removeEventListener('mousemove', this.onSlide);
+    document.removeEventListener('mouseup', this.stopSlide);
+    document.removeEventListener('touchmove', this.onSlide);
+    document.removeEventListener('touchend', this.stopSlide);
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.slide_code_modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  // background-color: rgba(0, 0, 0, 0.5);//不要遮罩层
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 9999;
+}
+
+.slide_code_content {
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  min-width: 300px;
+  max-width: 600px;
+}
+
+.modal_header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  border-bottom: 1px solid #e4e7ed;
+  
+  h3 {
+    margin: 0;
+    font-size: 16px;
+    color: #303133;
+  }
+  
+  .close_btn {
+    cursor: pointer;
+    font-size: 18px;
+    color: #909399;
+    transition: color 0.3s;
+    
+    &:hover {
+      color: #f56c6c;
+    }
+  }
+}
+
+.modal_body {
+  padding: 20px;
+  min-height: 100px;
+}
+
+.modal_footer {
+  padding: 12px 20px;
+  border-top: 1px solid #e4e7ed;
+  text-align: right;
+  
+  .el-button {
+    margin-left: 10px;
+  }
+}
+
+.slider_container {
+
+  .slider_text
+  {
+    text-align: center;
+    height: 36px;
+    font-size: 16px;
+  }
+  //背景的图片
+  .container_bg
+  {
+    width: 300px;
+    height: 180px;
+    background-size: 100% 100%;
+    background-position: 0 0;
+    background-repeat: no-repeat;
+    position: relative;
+    overflow: hidden;
+    .slider_bg
+    {
+      width: 50px;
+      height: 50px;
+      background-position: 0 0;
+      background-repeat: no-repeat;
+      background-size: 100% 100%;
+      position: absolute;
+      left: 0;
+      top: 35px;
+      border-radius: 5px;
+      border:1px solid red;
+    }
+
+    .slider_sucess
+    {
+      width: 100%;
+      height: 40px;
+      background-color: #2BC644;
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      z-index: 88;
+      line-height: 40px;
+      color:#fff;
+      font-size: 12px;
+      text-align: center;
+    }
+
+    .slider_failed
+    {
+      width: 100%;
+      height: 40px;
+      background-color: #ff5d39;
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      z-index: 88;
+      line-height: 40px;
+      color:#fff;
+      font-size: 12px;
+      text-align: center;
+    }
+  }
+}
+
+.slider_track {
+  position: relative;
+  height: 40px;
+  background-color: #e4e7ed;
+  border-radius: 20px;
+  overflow: hidden;
+  user-select: none;
+  cursor: pointer;
+  margin-top: 20px;
+}
+
+.slider_button {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 50px;
+  height: 36px;
+  background: #fff;
+  border: 2px solid #409eff;
+  border-radius: 25px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: grab;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+  
+  i {
+    color: #409eff;
+    font-size: 16px;
+  }
+  
+  &:active {
+    cursor: grabbing;
+  }
+}
+
+// 从底部升起的动画
+.slide-up-enter-active {
+  animation: slideUpIn 0.5s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.slide-up-leave-active {
+  animation: slideUpOut 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+@keyframes slideUpIn {
+  from {
+    transform: translateY(100%);
+    opacity: 0;
+  }
+  to {
+    transform: translateY(0);
+    opacity: 1;
+  }
+}
+
+@keyframes slideUpOut {
+  from {
+    transform: translateY(0);
+    opacity: 1;
+  }
+  to {
+    transform: translateY(100%);
+    opacity: 0;
+  }
+}
+
+.zoom-center-enter-active {
+  transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.zoom-center-leave-active {
+  transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
+}
+.zoom-center-enter,
+.zoom-center-leave-to {
+  opacity: 0;
+  transform: scale(0.5);
+}
+
+.zoom-center-enter-to {
+  opacity: 1;
+  transform: scale(1);
+}
+
+.zoom-center-leave {
+  opacity: 1;
+  transform: scale(1);
+}
+
+.zoom-center-leave-to {
+  opacity: 0;
+  transform: scale(0.5);
+}
+                                   
+</style>

+ 10 - 9
src/views/login/login.vue

@@ -70,18 +70,19 @@ export default {
         if (event.origin !== window.location.origin) {
           return
         }
-        if(getToken() && schoolType){
-          const currentPath = sessionStorage.getItem('student_currentPath');//获取当前进入的路由
-          const directUrl = currentPath?currentPath:'/jointStudentAnalysisReport/list';
-          this.$router.push({ path: directUrl })
-          return
-        }
         if (event.data.type === 'STU_TOKEN') {
-          const userInfo = event.data.userInfo
-          // console.log('打印用户信息', userInfo)
+          const userInfo = event.data.userInfo;
+          const tokenValue = userInfo.tokenValue;
+          const oldToken = getToken(); 
+          if(oldToken && oldToken === tokenValue && schoolType){
+            const currentPath = sessionStorage.getItem('student_currentPath');//获取当前进入的路由
+            const directUrl = currentPath?currentPath:'/jointStudentAnalysisReport/list';
+            this.$router.push({ path: directUrl })
+            return
+          }
           this.$store.dispatch('user/CLEAR_LOCAL_STORAGE') //清空本地存储
           setToken(userInfo.tokenValue)
-
+          // setToken(tokenValue)
           sessionStorage.setItem('schoolType', schoolType || 2) //1:单校 2:联校// 暂时写死单校   线上还是单校的
           this.$store.dispatch('user/SET_TOKEN', userInfo.tokenValue)
           this.$store.dispatch('user/SET_SCHOOL_LOGO', userInfo.schoolLogoUrl) //设置学校logo

+ 124 - 21
src/views/userInfo/personInfo.vue

@@ -66,7 +66,7 @@
             <div class="bingding_input">
               <el-input v-model="bindingPhoneData.code" placeholder="请输入验证码" prefix-icon="iconfont icon_yanzhengma">
                 <template slot="suffix">
-                  <span class="get_code" @click="GetCode">{{ codeText }}</span>
+                  <span :class="isCount?'code_gray':'get_code'" @click="GetCode">{{ codeText }}</span>
                 </template>
               </el-input>
             </div>
@@ -85,6 +85,7 @@
           
         </div>
       </el-dialog>
+      <SlideCode  v-model="showSlideCode"  title="安全验证" :sliderUUID="sliderUUID" :sliderBgUrl="sliderBgUrl" :sliderPositionY="sliderPositionY" :sliderImgUrl="sliderImgUrl" @close="CloseSlideCode" @refresh="ObtainSliderImg" />
     </div>
 
   </div>
@@ -93,13 +94,14 @@
 import { mapGetters } from "vuex";
 
 import heada_Image from "../../assets/user_img.png";
+import SlideCode from "@/views/login/components/SlideCode.vue";//滑动验证码组件
 
 export default {
   computed: {
     ...mapGetters(["userInfo"]),
   },
   components: {
-
+    SlideCode
   },
   data() {
     return {
@@ -126,11 +128,18 @@ export default {
       },
       bindPhoneLoading: false, // 绑定手机号loading
       codeText: '获取验证码', // 获取验证码按钮文字 
-      count: 60, // 倒数秒数
+      count: 300, // 倒数秒数
       isCount: false, // 是否倒计时
       wechatUrl: '', // 获取微信二维码返回地址
       qrLoading: false, // 二维码加载loading
       wechatCode: '', // 扫码获取code
+
+      timer:null,//定时器
+      showSlideCode:false,//是否显示滑动验证码
+      sliderUUID:'',//图片唯一凭证
+      sliderBgUrl:'',//画框背景图片
+      sliderImgUrl:'',//滑块图片
+      sliderPositionY:0,//滑块y轴位置
     };
   },
   mounted() {
@@ -158,6 +167,22 @@ export default {
   },
   beforeDestroy() {
     window.removeEventListener('message', this.WechatBind);
+    if (this.timer) {
+      clearInterval(this.timer);
+      this.timer = null;
+    }
+
+    // 【新增】清理微信二维码定时器
+    if (this.resizeTimer) {
+      clearInterval(this.resizeTimer);
+      this.resizeTimer = null;
+    }
+    
+    // 清理微信 DOM
+    const container = document.getElementById("wechat-qr-container");
+    if (container) {
+      container.innerHTML = '';
+    }
   },
   methods: {
 
@@ -169,6 +194,14 @@ export default {
     //打开绑定手机号弹窗
     OpenPhoneDialog() 
     {
+      // 先清除已存在的定时器
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
+        this.codeText = '获取验证码'
+        this.isCount = false
+        this.count = 300;
+      }
       this.bindingPhoneData.showDialog = true;
     },
 
@@ -178,11 +211,11 @@ export default {
 
       console.log("绑定手机号",this.bindingPhoneData);
       if(!this.bindingPhoneData.phone) {
-        this.$message.warning('手机号不能为空!')
+        this.$message.warning('请输入手机号!')
         return
       }
       if(!this.bindingPhoneData.code){
-        this.$message.warning('验证码不能为空!')
+        this.$message.warning('请输入验证码!')
         return
       }
       try {
@@ -199,7 +232,7 @@ export default {
             this.bindingPhoneData.code = ''
             this.codeText = '获取验证码'
             this.isCount = false
-            this.count = 60;
+            this.count = 300;
             if(this.userInfo.wxName)
             {
 
@@ -219,14 +252,86 @@ export default {
         this.bindPhoneLoading = false
       }
     },
+
+    //关闭滑块验证码弹窗
+    CloseSlideCode(sliderVerified,phoneNumber)
+    {
+      console.log("关闭滑动验证码弹窗",sliderVerified);
+      this.sliderVerified=sliderVerified;
+      
+      if(sliderVerified==1)
+      {
+        //绑定手机验证
+        this.StartCountdown();//启动倒计时
+      }
+      this.showSlideCode=false;
+    },
+
+    //h获取滑动验证码返回
+    ObtainSliderImg()
+    {
+      let param={
+        sliderUUID:this.sliderUUID,//图片唯一凭证
+      };
+      this.$api.user.obtainSliderImg(param).then(res => {
+        console.log("打印重新获取滑动验证码结果", res);
+        if(res.code == 200) 
+        {
+          this.sliderUUID = res.data.sliderUUID
+          this.sliderBgUrl = res.data.backgroundBase64;
+          this.sliderImgUrl = res.data.blockBase64;
+          this.sliderPositionY = res.data.y;//验证码的y坐标
+        }
+        else
+        {
+          this.$message.error(res.msg);
+        }
+      })
+    },
+
+    //启动全局倒计时定时器
+    StartCountdown() 
+    {
+      // 先清除已存在的定时器
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
+      }
+      
+      this.isCount = true;
+      this.count = 300;
+      this.codeText = '300 秒';
+      this.timer = setInterval(() => {
+        this.count--;
+        this.codeText = `${this.count} 秒`;
+        if (this.count <= 0) {
+          this.StopCountdown();
+        }
+      }, 1000);
+    },
+    //停止全局倒计时定时器
+    StopCountdown() 
+    {
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
+      }
+      this.isCount = false;
+      this.count = 300;
+      this.codeText = '重新获取';
+    },
     // 获取手机验证码
     GetCode() 
     {
+      // 如果已经有定时器在运行,先清除它,防止叠加
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
+      }
       if(!this.bindingPhoneData.phone) {
         this.$message.warning('请输入手机号码');
         return
       }
-
       // 验证手机号格式和位数
       const phoneRegex = /^1[3-9]\d{9}$/;
       if (!phoneRegex.test(this.bindingPhoneData.phone)) {
@@ -234,27 +339,25 @@ export default {
         return;
       }
       if(this.isCount) return
-      this.$api.user.getBindPhoneValidCode({
+      this.$api.user.getBindPhoneSlider({
         phoneNumber: this.bindingPhoneData.phone
       }).then(res => {
-        if(res.code == 200) {
-          this.isCount = true
-          this.codeText = `${this.count} 秒`
-          const timer = setInterval(() => {
-            this.count--;
-            this.codeText = `${this.count} 秒`;
-            if (this.count <= 0) {
-              clearInterval(timer);
-              this.isCount = false;
-              this.count = 60;
-              this.codeText = '重新获取'
-            }
-          }, 1000);
+        if(res.code == 200) 
+        {
+          this.sliderUUID = res.data.sliderUUID;
+          this.sliderBgUrl = res.data.backgroundBase64;
+          this.sliderImgUrl = res.data.blockBase64;
+          this.sliderPositionY=res.data.y;
+          this.showSlideCode=true;
+          
         }else {
           this.$message.error(res.msg)
         }
       })
     },
+
+
+
     //打开绑定微信弹窗
     OpenWeChartDialog()
     {

+ 1 - 1
version.json

@@ -1,5 +1,5 @@
 {
-  "version": "0.1.3_2026_4_2_0",
+  "version": "0.1.3_2026_5_29_0",
   "content": [
     {
       "time": "2024-11-1",