Przeglądaj źródła

单校-学生端

liurongli 8 miesięcy temu
rodzic
commit
f74bfd8c8f

+ 3 - 0
src/assets/icon/icon_all_right.svg

@@ -0,0 +1,3 @@
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M37.4047 3.81226C36.9214 3.81228 35.9155 3.88671 35.4495 4.11778C34.9834 4.34885 34.6775 4.54104 33.8318 5.06107C24.5211 11.8972 18.5767 20.1449 11.7319 28.8675C10.3183 25.7889 8.86065 22.7343 7.23875 19.6673C6.78197 18.8181 6.30913 17.9602 5.74265 17.0726C5.36611 16.4817 4.77346 16.0719 4.08542 15.9398C3.39782 15.8073 2.67091 15.9627 2.07468 16.3665C1.47845 16.7704 1.06412 17.3878 0.932105 18.0755C0.799646 18.7635 0.960584 19.4657 1.3696 20.0345C1.83236 20.6737 2.32047 21.448 2.78464 22.2083C5.05546 25.9781 7.18938 30.0043 9.25242 33.9828C10.0142 35.6836 11.8675 35.8046 12.9975 34.3226C16.3226 29.6028 21.1801 22.8025 25.7924 17.5118C30.8303 11.7331 35.5054 7.64659 35.8223 7.33436C36.4293 6.73628 38.7192 4.74838 38.7192 4.74838C38.8567 4.49745 38.8275 4.34749 38.7192 4.1967C38.6109 4.04591 37.888 3.81223 37.4047 3.81226Z" fill="#D81E06"/>
+</svg>

+ 4 - 0
src/assets/icon/icon_all_wrong.svg

@@ -0,0 +1,4 @@
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.78624 2.85627C6.21927 2.51249 5.53988 2.40887 4.89661 2.56973C4.25339 2.7305 3.69899 3.14256 3.35627 3.71376C3.01355 4.28496 2.91086 4.96805 3.0717 5.61125C3.23248 6.25454 3.64362 6.80524 4.21376 7.14373C4.76706 7.472 5.33732 7.82303 5.8906 8.17389C10.9137 11.377 15.6506 15.1138 20.085 19.161C22.4012 21.2804 24.9755 23.1585 27.3157 25.3142C29.4393 27.2702 31.5122 29.3052 33.4806 31.4523C33.9413 31.9553 34.3927 32.4596 34.8411 32.9751C34.9204 33.066 35.0386 33.1234 35.164 33.1386C35.2898 33.1536 35.4124 33.1252 35.5107 33.0555C35.609 32.9859 35.6766 32.8797 35.7043 32.7561C35.7316 32.6328 35.7167 32.5022 35.6573 32.3972C35.3153 31.7965 34.9699 31.2035 34.6139 30.609C33.0948 28.0696 31.4457 25.5888 29.6634 23.2024C27.7042 20.579 25.7523 17.8822 23.3573 15.6323C18.7759 11.3416 13.8873 7.43009 8.57692 3.9782C7.99007 3.59897 7.38201 3.21731 6.78624 2.85627Z" fill="#D81E06"/>
+<path d="M37.4331 9.9987C38.2484 9.5664 38.8353 8.83633 39.0464 7.93931C39.2586 7.04402 39.0776 6.05518 38.5616 5.22013C38.0456 4.38508 37.2422 3.7808 36.3466 3.57003C35.4499 3.35753 34.5344 3.55588 33.783 4.09164C33.1508 4.54044 32.4985 5.01423 31.8757 5.47836C20.731 13.9798 10.3948 23.4607 2.40484 34.8328C1.97298 35.4645 1.54433 36.1133 1.13752 36.7574C1.07111 36.8628 1.04949 36.9944 1.07358 37.1205C1.09789 37.2468 1.16593 37.3572 1.26657 37.4304C1.36721 37.5035 1.49329 37.5341 1.6209 37.5182C1.7483 37.5022 1.86678 37.441 1.94646 37.3453C2.42593 36.7697 2.92517 36.1918 3.42609 35.6327C12.6099 25.6138 23.9025 17.7131 35.499 11.0505C36.139 10.6934 36.8015 10.3307 37.4331 9.9987Z" fill="#D81E06"/>
+</svg>

+ 4 - 0
src/assets/icon/icon_half_right.svg

@@ -0,0 +1,4 @@
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M37.4047 3.81226C36.9214 3.81228 35.9155 3.88671 35.4495 4.11778C34.9834 4.34885 34.6775 4.54104 33.8318 5.06107C24.5211 11.8972 18.5767 20.1449 11.7319 28.8675C10.3183 25.7889 8.86065 22.7343 7.23875 19.6673C6.78197 18.8181 6.30913 17.9602 5.74265 17.0726C5.36611 16.4817 4.77346 16.0719 4.08542 15.9398C3.39782 15.8073 2.67091 15.9627 2.07468 16.3665C1.47845 16.7704 1.06412 17.3878 0.932105 18.0755C0.799646 18.7635 0.960584 19.4657 1.3696 20.0345C1.83236 20.6737 2.32047 21.448 2.78464 22.2083C5.05546 25.9781 7.18938 30.0043 9.25242 33.9828C10.0142 35.6836 11.8675 35.8046 12.9975 34.3226C16.3226 29.6028 21.1801 22.8025 25.7924 17.5118C30.8303 11.7331 35.5054 7.64659 35.8223 7.33436C36.4293 6.73628 38.7192 4.74838 38.7192 4.74838C38.8567 4.49745 38.8275 4.34749 38.7192 4.1967C38.6109 4.04591 37.888 3.81223 37.4047 3.81226Z" fill="#D81E06"/>
+<path d="M15.5987 6.12501C15.2306 5.93799 14.8033 5.90485 14.4108 6.03289C14.0182 6.16093 13.6927 6.43966 13.5056 6.80776C13.3186 7.17586 13.2855 7.60318 13.4135 7.99571C13.5415 8.38825 13.8203 8.71384 14.1884 8.90087C14.532 9.07544 14.8766 9.26318 15.2164 9.45977C18.2752 11.2418 21.0308 13.7013 23.5618 16.3516C24.8875 17.7447 26.4769 18.9247 27.8602 20.3232C29.1143 21.5909 30.3226 22.912 31.5038 24.2647C31.781 24.5826 32.0515 24.8948 32.3269 25.2171C32.376 25.2745 32.4495 25.3109 32.5278 25.3208C32.6062 25.3305 32.6831 25.3129 32.7447 25.2694C32.8063 25.226 32.8487 25.1595 32.8659 25.0824C32.8829 25.0053 32.8733 24.9239 32.8358 24.8583C32.6233 24.4889 32.4146 24.1292 32.1982 23.7634C31.2775 22.2051 30.3149 20.6686 29.3123 19.1479C28.2095 17.4756 27.2155 15.6747 25.8171 14.205C23.1513 11.4193 20.2368 8.78673 16.7757 6.76478C16.3903 6.54175 15.996 6.32684 15.5987 6.12501Z" fill="#D81E06"/>
+</svg>

+ 1141 - 0
src/components/PaperImage.vue

@@ -0,0 +1,1141 @@
+<template>
+
+  <div class="paper_container"  ref="paperContainer"  @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp"  @wheel="onWheel">
+      <canvas id="paperCanvas" ref="paperCanvas" class="paper_canvas" @mouseleave="onCanvasLeave"></canvas>
+      <div id="imgContainer" class="img_container">
+        <img v-if="paperImgUrl" :src="paperImgUrl" >
+      </div>
+      <div class="no_paper_url" v-if="paperImgUrl==''">
+        暂无数据
+        <!-- 请先制作模版 -->
+      </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('fitScreen')">适合屏幕</div>
+        <div class="menu_item" @click="handleMenuAction('zoomIn')">放大(向上滚轮)</div>
+        <div class="menu_item" @click="handleMenuAction('zoomOut')">缩小(向下滚轮)</div>
+      </div>
+  </div>
+</template>
+<script>
+  import { throttle } from 'lodash';
+  import { mmToPx } from '@/utils/common.js'
+
+export default {
+  components: {
+
+  },
+  props:{
+    drawData:{
+      type:Array,
+      default:()=>[]
+    },//画的边框的数据
+
+    paperInfo:{
+      type:Object,
+      default:()=>null
+    },//纸张大小
+
+    paperImgUrl:{
+      type:String,
+      default:''
+    },//试卷地址相关信息
+
+    currentId:{
+      type:String,
+      default:''
+    },//当前选中的id
+
+    isDrag:{
+      type:Boolean,
+      default:true
+    },//是否可以拖动 默认可以拖动
+
+    isAbnormal:{
+      type:Boolean,
+      default:false
+    },//是否异常处使用 区别 异常处理使用的地方 正常答案显示蓝色
+
+    downLoadName:{
+      type:String,
+      default:''
+    },//下载图片的名称
+  },
+  computed:{
+
+  },
+  data() {
+    return {
+
+
+      position: { 
+          x: 0,
+          y: 0 
+      },//初始canvas图片位置
+
+
+      startX: 0,//鼠标按下时的初始位置x坐标
+      startY: 0,//鼠标按下时的初始位置y坐标
+ 
+      scale: 1,//画布缩放倍数
+      isDragging:false,//是否拖动画布
+      isDrawing:false,//是否正在画线
+      
+      drawType:0,//1画线 0  拖拽模式
+
+      image: null,
+    
+      paperImgInfo:{
+        width:0,
+        height:0
+      },
+      canvasInfo:{
+        width:0,
+        height:0
+      },//画布的大小
+    
+      canvas:null,//画板canvas
+      ctx:null,//画板上下文
+
+      zoomRate:0,//图片的缩放比例
+      dpr:window.devicePixelRatio || 1,
+      minScale:0.7,//最小缩放值
+      maxScale:4,//最大缩放值
+
+      rectPoint:{
+        startX:0,
+        startY:0,
+        endX:0,
+        endY:0
+      },//矩形起始坐标点
+
+      // 客观题区域
+      showObjectArea:false,//是否显示对象区域
+      addObjectAreaOption:{
+        derection:1,//排列方向 1 横线 2竖向
+        questionType:1,//题类型 1单选 2多选 3 判断
+        startNumber:16,//起始题号
+        endNumber:20,//结束题号
+        interval:1,//题号间隔 默认1
+        answerNumber:4,//选项个数  答案个数
+        answerWidth:0,//选项宽度
+        answerHeight:0,//选项高度
+      },//添加选择题设置信息
+      currenPoint:{
+        x:0,
+        y:0,
+        w:0,
+        h:0,
+      },
+
+      // isSelectBox:false,//是否选择框选答案区域框
+      containerWidth:0,//容器宽度
+      containerHeight:0,//容器高度
+      isInit:true, //是否是初始加载
+
+      showContextMenu: false,//是否显示右键菜单
+      contextMenuX: 0, // 右键菜单X坐标
+      contextMenuY: 0, // 右键菜单Y坐标
+
+      CacheAllWrong:new Image(),//缓存的全错图片
+      CacheAllRight:new Image(),//缓存的全对图片
+      CacheHalfRight:new Image(),//缓存的半对图片
+      CacheTypicalError:new Image(),//缓存的典型错误图片
+      CacheExcellentAnswer:new Image(),//缓存的优秀答案图片
+
+      
+    }
+  },
+  watch:{
+    paperImgUrl:
+    {
+      handler(newVal, oldVal)
+      {
+         console.log("地址变化了",this.paperImgUrl);
+        this.InitData();//初始化数据
+      },
+      deep: true ,// 如果需要深度监听数组内部对象的变化
+      listener: true//立即执行一次
+      // console.log("地址变化了",this.paperImgUrl);
+      // this.initData();//初始化数据
+    },
+
+    drawData:{
+      handler(newVal, oldVal) {
+        
+        this.drawImage();//更新边框数据并重新绘制
+      },
+      deep: true // 如果需要深度监听数组内部对象的变化
+    },//边框数据
+
+    currentId()
+    {
+      // console.log("当前选中项的id变化了",this.currentId);
+      this.drawImage();//更新边框数据并重新绘制
+    },
+
+
+  },
+  created(){  
+
+    this.CacheAllWrong.src=require('../assets/icon/icon_all_wrong.svg');
+    this.CacheAllRight.src=require('../assets/icon/icon_all_right.svg');
+    this.CacheHalfRight.src=require('../assets/icon/icon_half_right.svg');
+    this.CacheTypicalError.src=require('../assets/tool/model_2.png');
+    this.CacheExcellentAnswer.src=require('../assets/tool/model_1.png');
+    this.CacheAllWrong.onload=()=>{
+      console.log("缓存全错图片加载完成");
+    };
+    this.CacheAllRight.onload=()=>{
+      console.log("缓存全对图片加载完成");
+    };
+    this.CacheHalfRight.onload=()=>{
+      console.log("缓存半对图片加载完成");
+    };
+    this.CacheTypicalError.onload=()=>{
+      console.log("缓存典型错误图片加载完成");
+    };
+    this.CacheExcellentAnswer.onload=()=>{
+      console.log("缓存优秀答案图片加载完成");
+    };
+    window.addEventListener('resize', this.handleResize);
+     // 添加全局鼠标释放事件监听器,处理异常情况
+    window.addEventListener('mouseup', this.onGlobalMouseUp);
+    // 添加全局点击事件监听器,用于隐藏右键菜单
+    document.addEventListener('click', this.handleGlobalEvent);
+
+    // 添加全局右键点击事件监听器,用于隐藏右键菜单
+    document.addEventListener('contextmenu', this.handleGlobalEvent);
+  },
+  beforeDestroy() {
+
+    
+    // 移除监听 防止内存泄漏
+    window.removeEventListener('resize', this.handleResize);
+    window.removeEventListener('mouseup', this.onGlobalMouseUp);
+    document.removeEventListener('click', this.handleGlobalEvent);
+    // 移除全局右键点击事件监听器
+    document.removeEventListener('contextmenu', this.handleGlobalEvent);
+    // 移除 paper_container 上的事件监听器
+    if (this.$refs.paperContainer) 
+    {
+      this.$refs.paperContainer.removeEventListener('contextmenu', this.handleRightClick);
+      // this.$refs.paperContainer.removeEventListener('click', this.hideContextMenu);
+    }
+  },
+  mounted() {
+    this.InitData();//初始数据处理加载 如定位  居中 等 第一次居中
+    // 确保 DOM 已经渲染后再添加事件监听器
+    this.$nextTick(() => {
+      if (this.$refs.paperContainer) {
+        this.$refs.paperContainer.addEventListener('contextmenu', this.handleRightClick);
+        // this.$refs.paperContainer.addEventListener('click', this.hideContextMenu);
+        console.log('事件监听器已添加');
+      } else {
+        console.error('paperContainer 未找到');
+      }
+    });
+  },
+  methods: {
+      // 处理右键点击事件
+      handleRightClick(event) 
+      {
+        event.preventDefault();
+        event.stopPropagation(); // 阻止事件继续向上冒泡
+        // 设置菜单位置(相对于 paper_container 的位置)
+        const containerRect = this.$refs.paperContainer.getBoundingClientRect();
+        this.contextMenuX = event.clientX - containerRect.left;
+        this.contextMenuY = event.clientY - containerRect.top;
+        // 显示自定义菜单
+        this.showContextMenu = true;
+      },
+
+      // 处理全局事件(包括左键点击和右键点击),用于隐藏菜单
+      handleGlobalEvent(event) {
+        // 检查点击的元素是否在菜单内部
+        const menuElement = document.querySelector('.custom_context_menu');
+        
+        // 如果菜单是显示的,并且点击的元素不在菜单内部,则隐藏菜单
+        if (this.showContextMenu && menuElement && !menuElement.contains(event.target)) {
+          this.hideContextMenu();
+        }
+      },
+  
+      // 隐藏右键菜单
+      hideContextMenu() 
+      {
+        this.showContextMenu = false;
+      },
+
+    // 处理菜单项点击
+    handleMenuAction(action) 
+    {
+        console.log('执行操作:', action);
+        
+        
+        
+        switch(action) {
+          case 'zoomIn':
+            // 放大
+            this.scale = Math.min(this.maxScale, this.scale + 0.1);
+            this.ImageInfoChange();
+            this.updateCanvasSize();
+            this.drawImage();
+            break;
+          case 'zoomOut':
+            // 缩小
+            // 缩小
+            this.scale = Math.max(this.minScale, this.scale - 0.1);
+            this.ImageInfoChange();
+            this.updateCanvasSize();
+            this.drawImage();
+            break;
+          case 'fitScreen':
+            //适合屏幕
+            this.fitScreen()
+            break;
+          case 'download':
+            // 下载图片
+            this.downloadImage();
+            break;
+        }
+        // 隐藏菜单
+        this.hideContextMenu();
+    },
+    //下载图片功能
+    downloadImage() 
+    {
+        
+        this.DownloadDrawImage();
+        //将canvas 转换为data Url
+        const imgUrl =this.canvas.toDataURL('image/png');
+        //创建一个隐藏的a标签
+        let link = document.createElement('a');
+        link.href = imgUrl;
+        link.download = this.downLoadName+'.png';
+
+        // link.setAttribute('id', 'downloadImg');
+        // link.setAttribute('_blank', 'target');
+        //触发点击事件
+        document.body.appendChild(link);
+        link.click()
+        document.body.removeChild(link);
+     
+    },
+
+    //初始数据加载
+    InitData()
+    {
+      //获取容器的宽高
+      const { width, height } = this.$refs.paperContainer.getBoundingClientRect();
+      
+      this.containerHeight = Number(height);
+      this.containerWidth =Number(width);
+      console.log("容器宽高",this.containerWidth,this.containerHeight);
+
+      //初始化获取试卷数据
+      console.log("打印paperInfo",this.paperInfo);
+      if(this.paperInfo?.width && this.paperInfo?.height)
+      {
+        //设置初始数据
+        this.paperImgInfo.width=this.paperInfo.width;//获取宽
+        this.paperImgInfo.height=this.paperInfo.height;//获取高
+        // 更新缩放率
+        this.updateZoomAndPaperInfo();
+        // 更新画布尺寸
+        this.updateCanvasSize();
+        console.log("是否初始",this.isInit);
+        //计算中心位置使图片居中  初始加载图片是居中
+        if(this.isInit)
+        {
+          this.centerCanvas();
+        }
+        
+        
+        
+        //加载图片
+        this.loadImage();
+      }
+      else
+      {
+        //没有值 默认图片的宽高
+        console.log("没有纸张试卷的宽高信息");
+        this.image = new Image();
+        this.image.crossOrigin = 'anonymous'; // 处理跨域问题
+        this.image.src = this.paperImgUrl;
+        this.image.onload = () => {
+          console.log("图片加载完成",this.image);
+          this.paperImgInfo.width=this.image.width;//获取宽
+          this.paperImgInfo.height=this.image.height;//获取高
+        
+          // 更新缩放率
+          this.updateZoomAndPaperInfo();
+          // 更新画布尺寸
+          this.updateCanvasSize();
+          //计算中心位置使图片居中
+          console.log("是否初始",this.isInit);
+          //计算中心位置使图片居中  初始加载图片是居中
+          // if(this.isInit)
+          // {
+          this.centerCanvas();
+          // }
+          //加载图片
+          this.loadImage();
+          // this.drawQuestionPosition();
+        };
+
+      }
+      
+     
+    },
+
+    //切换试卷图片
+    chanagePaperImage()
+    {
+      // 更新缩放率
+      this.updateZoomAndPaperInfo();
+      // 更新画布尺寸
+      this.updateCanvasSize();
+      // //计算中心位置使图片居中
+      // this.centerCanvas();
+      //加载图片
+      this.loadImage();
+    },
+
+    //适合屏幕
+    fitScreen() 
+    {
+
+      this.scale=1;
+      this.isInit=true;
+      this.InitData();//初始化屏幕加载
+    },
+
+    
+    // 加载图片
+    loadImage() {
+      // this.image = new Image();
+      // this.image.src = this.paperImgUrl;
+      // this.image.onload = () => {
+      //   console.log("图片加载完成",this.image);
+
+        this.drawImage();//绘制边框数据
+        
+        // this.drawQuestionPosition();
+      // };
+    },
+
+    
+
+    // 图片宽高坐标变化
+    ImageInfoChange(){
+      let {width,height} = this.paperImgInfo;
+      // console.log("打印纸张试卷的图片宽高",this.paperImgInfo);
+      let imgDom = document.getElementById('imgContainer');
+      imgDom.style.width = width * this.zoomRate*this.scale + 'px';
+      imgDom.style.height = height * this.zoomRate*this.scale + 'px';
+      imgDom.style.left = this.position.x + 'px';
+      imgDom.style.top = this.position.y + 'px';         
+    },
+
+    //图片加载完成后绘制图片
+    drawImage() {
+      // if (this.image) {
+        this.canvas=this.$refs.paperCanvas;
+        let ctx = this.canvas.getContext('2d');
+        // ctx.imageSmoothingEnabled = false;//禁用图片平滑
+        // this.paperCtx.drawImage(this.image, 0, 0,this.canvasInfo.width,this.canvasInfo.height);
+        // 根据 devicePixelRatio 缩放图片
+        // ctx.drawImage(
+        //   this.image,
+        //   0, 0,
+        //   this.image.width, this.image.height,
+        //   0, 0,
+        //   this.canvasInfo.width, this.canvasInfo.height
+        // );
+        ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height);//清除边框数据
+        
+        this.ImageInfoChange();//用image图片替代背景  是为了高清显示
+        
+        // console.log("加载边框数据",this.drawData);
+        for(var i=0;i<this.drawData.length;i++)
+        {
+          let item=this.drawData[i];
+          const point=JSON.parse(item.samplingPosition);
+          var obj={
+            x:point.x*this.zoomRate*this.scale,
+            y:point.y*this.zoomRate*this.scale,
+          }
+          //如果是批阅块的坐标 需要相加
+          if(item.pagePaintingVOS)
+          {
+            console.log("打印item",item);
+            // let pageItem = item.pagePaintingVOS.find(item => item.page == point.page);
+            const pointIndex=point.index || 0;//默认0
+            let pageItem = item.pagePaintingVOS[pointIndex];
+            // item.pagePaintingVOS.forEach((item,index) => { 
+            //   if(index==point.index && item.page == point.page)
+            //   {
+            //     pageItem=item;
+            //   }
+            // });
+            //需要同时根据索引和页面查找
+            // const pageItem = item.pagePaintingVOS.find(item => item.page == point.page && item.index == point.index);
+            console.log("打印pageItem",pageItem);
+
+            if (pageItem) {
+              obj.x = (pageItem.x + point.x) * this.zoomRate * this.scale;
+              obj.y = (pageItem.y + point.y) * this.zoomRate * this.scale;
+            }
+            console.log("打印计算后的obj",obj);
+          }
+          // 绘制文字  定位去和客观题组不用显示
+
+            // console.log("打印标题",item.questionName);
+            // ctx.fillStyle = 'red';
+             ctx.fillStyle = '#D81E06';
+              ctx.textAlign = 'center';
+              ctx.textBaseline = 'middle';
+            if(item.questionName=='总分')
+            {
+              const fontSize = Math.max(12, 50 * this.scale); // 最小字体12px,基础字体16px
+              ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
+              
+              ctx.textAlign = 'left';
+            }
+            else
+            {
+              const fontSize = Math.max(12, 24 * this.scale); // 最小字体12px,基础字体16px
+              // ctx.font = '30px Arial';
+             
+              ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
+              
+              ctx.textAlign = 'left';
+              //判断是什么类型 scoreType 分数类型1:全对 2:半对 3: 全错
+              const iconX=obj.x-20*this.zoomRate*this.scale;
+              const iconY=obj.y-20*this.zoomRate*this.scale;
+              const iconWidth = 50*this.zoomRate*this.scale; 
+              const iconHeight = 50*this.zoomRate*this.scale;
+              // console.log("打印item.score",item.score)
+              if(item.score==0)
+              {
+                
+                
+                //0 分全错
+                ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
+                
+              }
+              else if(item.score==item.fullScore)
+              {
+                //满分 全对
+                ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
+              }
+              else
+              {
+                //半对
+                ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
+              }  
+            }
+            ctx.fillText(item.score,obj.x+35*this.zoomRate*this.scale,obj.y);
+            
+            // ctx.fillStyle = 'blue';
+            // ctx.font = `24px Arial`; // 让文字大小跟随缩放
+            // ctx.textAlign = 'center';
+            // ctx.textBaseline = 'middle';
+            // ctx.fillText(item.questionName,obj.x-65*this.zoomRate*this.scale,obj.y);
+
+
+          
+          
+          
+          
+        }
+        // ctx.restore();
+      // }
+    },
+
+    //下载图片绘制
+    DownloadDrawImage()
+    {
+      this.canvas=this.$refs.paperCanvas;
+      let ctx = this.canvas.getContext('2d');
+        
+      ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height);//清除边框数据
+      ctx.drawImage(this.image, 0, 0,this.canvasInfo.width,this.canvasInfo.height);
+
+        
+        // console.log("加载边框数据",this.drawData);
+        for(var i=0;i<this.drawData.length;i++)
+        {
+          let item=this.drawData[i];
+          const point=JSON.parse(item.samplingPosition);
+          var obj={
+            x:point.x*this.zoomRate*this.scale,
+            y:point.y*this.zoomRate*this.scale,
+          }
+          //如果是批阅块的坐标 需要相加
+          if(item.pagePaintingVOS)
+          {
+            console.log("打印item",item);
+            // let pageItem = item.pagePaintingVOS.find(item => item.page == point.page);
+            const pointIndex=point.index || 0;//默认0
+            let pageItem = item.pagePaintingVOS[pointIndex];
+            // item.pagePaintingVOS.forEach((item,index) => { 
+            //   if(index==point.index && item.page == point.page)
+            //   {
+            //     pageItem=item;
+            //   }
+            // });
+            //需要同时根据索引和页面查找
+            // const pageItem = item.pagePaintingVOS.find(item => item.page == point.page && item.index == point.index);
+            console.log("打印pageItem",pageItem);
+
+            if (pageItem) {
+              obj.x = (pageItem.x + point.x) * this.zoomRate * this.scale;
+              obj.y = (pageItem.y + point.y) * this.zoomRate * this.scale;
+            }
+            console.log("打印计算后的obj",obj);
+          }
+          // 绘制文字  定位去和客观题组不用显示
+
+            // console.log("打印标题",item.questionName);
+            // ctx.fillStyle = 'red';
+             ctx.fillStyle = '#D81E06';
+              ctx.textAlign = 'center';
+              ctx.textBaseline = 'middle';
+            if(item.questionName=='总分')
+            {
+              const fontSize = Math.max(12, 50 * this.scale); // 最小字体12px,基础字体16px
+              ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
+              
+              ctx.textAlign = 'left';
+            }
+            else
+            {
+              const fontSize = Math.max(12, 24 * this.scale); // 最小字体12px,基础字体16px
+              // ctx.font = '30px Arial';
+             
+              ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
+              
+              ctx.textAlign = 'left';
+              //判断是什么类型 scoreType 分数类型1:全对 2:半对 3: 全错
+              const iconX=obj.x-20*this.zoomRate*this.scale;
+              const iconY=obj.y-20*this.zoomRate*this.scale;
+              const iconWidth = 50*this.zoomRate*this.scale; 
+              const iconHeight = 50*this.zoomRate*this.scale;
+              // console.log("打印item.score",item.score)
+              if(item.score==0)
+              {
+                
+                
+                //0 分全错
+                ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
+                
+              }
+              else if(item.score==item.fullScore)
+              {
+                //满分 全对
+                ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
+              }
+              else
+              {
+                //半对
+                ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
+              }  
+            }
+            ctx.fillText(item.score,obj.x+35*this.zoomRate*this.scale,obj.y);
+            
+            // ctx.fillStyle = 'blue';
+            // ctx.font = `24px Arial`; // 让文字大小跟随缩放
+            // ctx.textAlign = 'center';
+            // ctx.textBaseline = 'middle';
+            // ctx.fillText(item.questionName,obj.x-65*this.zoomRate*this.scale,obj.y);
+
+
+          
+          
+          
+          
+        }
+    },
+
+    //加载答案框数据
+    drawQuestionPosition()
+    {
+
+      let canvas=this.$refs.paperCanvas;
+      let ctx = canvas.getContext('2d');
+      console.log("加载答案边框数据",this.questionPositionData);
+      for(var i=0;i<this.questionPositionData.length;i++)
+      {
+        var item=this.questionPositionData[i];
+      
+        // var obj={
+        //   x:item.x*this.zoomRate*this.scale,
+        //   y:item.y*this.zoomRate*this.scale,
+        //   width:item.w*this.zoomRate*this.scale,
+        //   height:item.h*this.zoomRate*this.scale,
+        //   blockName:item.blockName,
+        //   // page:'',
+        //   // blockArea:'',
+        // }
+        // //外边框数据
+        // if(item.unit=='mm')
+        // {
+        //   obj={
+        //     x:mmToPx(item.x)*this.zoomRate*this.scale,
+        //     y:mmToPx(item.y)*this.zoomRate*this.scale,
+        //     width:mmToPx(item.w)*this.zoomRate*this.scale,
+        //     height:mmToPx(item.h)*this.zoomRate*this.scale,
+        //     blockName:item.blockName,
+        //     // page:item.page,
+        //     // blockArea:item.blockArea,
+        //   }
+        // }
+        
+        for(var j=0;j<item.questionlist.length;j++)
+        {
+          for(var k=0;k<item.questionlist[j].answerList.length;k++)
+          {
+            var obj={
+              x:mmToPx(item.questionlist[j].answerList[k].x)*this.zoomRate*this.scale,
+              y:mmToPx(item.questionlist[j].answerList[k].y)*this.zoomRate*this.scale,
+              width:mmToPx(item.questionlist[j].answerList[k].w)*this.zoomRate*this.scale,
+              height:mmToPx(item.questionlist[j].answerList[k].h)*this.zoomRate*this.scale,
+              // blockName:item.blockName,
+            }
+            ctx.strokeRect(obj.x,obj.y,obj.width,obj.height); 
+          }
+        }
+       
+        // ctx.font = '15px Arial';
+        
+        // ctx.textAlign = 'left';
+        // // ctx.textBaseline = 'middle';
+        // // 绘制文字
+        // if(obj.blockArea!=2)
+        // {
+        //   ctx.fillText(obj.blockName,obj.x, obj.y+15);
+        // }
+        
+      }
+      ctx.restore();
+    },
+
+    // 更新缩放率
+    updateZoomAndPaperInfo() {
+      // this.zoomRate = this.containerHeight / this.paperImgInfo.height;//
+      // 计算基于宽度和高度的缩放率
+      const widthZoomRate = this.containerWidth / this.paperImgInfo.width;
+      const heightZoomRate = this.containerHeight / this.paperImgInfo.height;
+      
+      // 选择较小的缩放率作为基准,确保图像完整显示在容器内
+      this.zoomRate = Math.min(widthZoomRate, heightZoomRate);
+    
+      // this.paperImgInfo = this.cardType ==1 ? { width: 1240, height: 1754 } : { width: 2480, height: 1754 };
+      // console.log("打印this.paperImgInfo",this.paperImgInfo);
+    },
+
+    // 更新画布尺寸
+    updateCanvasSize() 
+    {
+      this.canvasInfo.width =  Math.round(this.paperImgInfo.width * this.zoomRate * this.scale);
+      this.canvasInfo.height = Math.round(this.paperImgInfo.height * this.zoomRate * this.scale);
+      this.$refs.paperCanvas.width = this.canvasInfo.width;
+      this.$refs.paperCanvas.height = this.canvasInfo.height;
+    },
+
+    // 中心化画布
+    centerCanvas() 
+    {
+      console.log("开始居中画布",this.containerWidth,this.containerHeight);
+      console.log("画布尺寸",this.canvasInfo.width,this.canvasInfo.height);
+      this.position.x = (this.containerWidth - this.canvasInfo.width) / 2;
+      this.position.y = (this.containerHeight - this.canvasInfo.height) / 2;
+      this.$refs.paperCanvas.style.left = `${this.position.x}px`;
+      this.$refs.paperCanvas.style.top = `${this.position.y}px`;
+      console.log("中心化画布打印画布位置",this.position.x,this.position.y);
+      this.isInit=false;//后面不在执行居中操作
+    },
+
+    // 全局鼠标释放事件处理
+    onGlobalMouseUp() {
+      this.isDragging = false;
+    },
+    // 鼠标按下事件
+    onMouseDown(event) {
+      // 只响应左键点击(button值为0表示左键)
+      if (event.button !== 0) {
+        // 如果是右键,确保释放拖拽状态
+       
+        this.isDragging = false;
+        return;
+      }
+      console.log("鼠标按下事件",event);
+      // 确保在开始新的拖拽前,先重置拖拽状态
+      this.isDragging = false;
+      console.log("鼠标按下事件drawType",this.drawType);
+      if(this.drawType==0)
+      {
+        // 如果drawType==0,则切换到拖拽模式
+        console.log("打印是否可拖拽",this.isDrag);
+        if(this.isDrag)
+        {
+          console.log("开始拖拽");
+          console.log("打印开始拖拽的坐标",this.startX,this.startY);
+          console.log("打印鼠标位置",event.clientX,event.clientY);
+          console.log("打印画布位置",this.position.x,this.position.y);
+          //可拖动的时候才能拖动
+          this.isDragging = true;
+          this.startX = event.clientX - this.position.x;
+          this.startY = event.clientY - this.position.y;
+          console.log("打印开始拖拽的坐标",this.startX,this.startY);
+        }
+       
+       
+      }
+      if(this.drawType==1)
+      {
+        if(event.target.id!='paperCanvas')
+        {
+          this.isDragging = true;
+          this.startX = event.clientX - this.position.x;
+          this.startY = event.clientY - this.position.y;
+        }
+      }
+    },
+
+    // 鼠标移动事件
+    onMouseMove(event) 
+    {
+      if (this.isDragging) 
+      {
+        // 在拖拽模式下,移动画布
+        this.position.x = event.clientX - this.startX;
+        this.position.y = event.clientY - this.startY;
+        this.$refs.paperCanvas.style.left = `${this.position.x}px`;
+        this.$refs.paperCanvas.style.top = `${this.position.y}px`;
+        this.ImageInfoChange();
+      }
+    },
+
+    // 鼠标抬起事件
+    onMouseUp(event) {
+      // 只响应左键释放
+      if (event && event.button !== 0) {
+        return;
+      }
+      this.isDragging = false; // 停止拖拽
+      
+    },
+
+    // // 鼠标滚轮事件
+    // onWheel(event) {
+    //   event.preventDefault();
+    //   const delta = event.deltaY < 0 ? 1 : -1;
+    //   const newScale = this.scale + delta * 0.1;
+    //   this.scale = Math.max(this.minScale, Math.min(this.maxScale, newScale));
+
+      
+    //   this.ImageInfoChange();
+    //   this.updateCanvasSize();
+    //   this.drawImage();
+
+    // },
+
+    // 鼠标滚轮事件
+    onWheel(event) {
+      event.preventDefault();
+      // 计算新的缩放比例
+      const delta = event.deltaY < 0 ? 1 : -1;
+      const newScale = this.scale + delta * 0.1;
+      const clampedScale = Math.max(this.minScale, Math.min(this.maxScale, newScale));
+      
+      // 如果缩放值没有变化,则直接返回
+      if (clampedScale === this.scale) return;
+      
+      // 获取鼠标在容器中的位置
+      const containerRect = this.$refs.paperContainer.getBoundingClientRect();
+      const mouseX = event.clientX - containerRect.left;
+      const mouseY = event.clientY - containerRect.top;
+      
+      // 计算鼠标相对于当前画布的位置
+      const mouseRelativeToCanvasX = (mouseX - this.position.x) / this.scale;
+      const mouseRelativeToCanvasY = (mouseY - this.position.y) / this.scale;
+      
+      // 更新缩放比例
+      this.scale = clampedScale;
+      
+      // 更新画布尺寸
+      this.updateCanvasSize();
+      
+      // 重新计算画布位置,使缩放围绕鼠标点进行
+      this.position.x = mouseX - mouseRelativeToCanvasX * this.scale;
+      this.position.y = mouseY - mouseRelativeToCanvasY * this.scale;
+      
+      // 应用新的位置
+      this.$refs.paperCanvas.style.left = `${this.position.x}px`;
+      this.$refs.paperCanvas.style.top = `${this.position.y}px`;
+      
+      // 更新图片位置信息
+      this.ImageInfoChange();
+      
+      // 重新绘制内容
+      this.drawImage();
+
+    },
+    //鼠标复位
+    MouseReset()
+    {
+      this.drawType=0;
+      this.canvas=this.$refs.paperCanvas;
+      this.canvas.style.cursor="pointer";
+      this.canvas.onmousedown=null;
+      this.canvas.onmousemove=null;
+      this.canvas.onmouseup=null;
+    },
+
+    //框选答案区域
+    SelectionBox()
+    {
+      console.log("开始框选答案区域");
+      this.drawType=1;
+
+      this.canvas=this.$refs.paperCanvas;
+      this.canvas.style.cursor="crosshair";
+      this.canvas.onmousedown=this.onCanvasDown;
+      this.canvas.onmousemove=this.onCanvasMove;
+      this.canvas.onmouseup=this.onCanvasUp;
+    },
+    
+
+    //画圈模式
+    PaintingCircle()
+    {
+
+      console.log("进入画圈模式");
+      this.drawType=1;
+
+      this.canvas=this.$refs.paperCanvas;
+      this.canvas.style.cursor="crosshair";
+      this.canvas.onmousedown=this.onCanvasDown;
+      this.canvas.onmousemove=this.onCanvasMove;
+      this.canvas.onmouseup=this.onCanvasUp;
+
+    },
+
+    //开始画答案区域圈
+    StartPaintingCircle(obj)
+    {
+      this.addObjectAreaOption=obj;
+      this.drawType=1;
+      this.canvas=this.$refs.paperCanvas;
+      this.canvas.style.cursor="crosshair";
+      this.canvas.onmousedown=this.onCanvasDown;
+      this.canvas.onmousemove=this.onCanvasMove;
+      this.canvas.onmouseup=this.onCanvasUp;
+    },
+
+    //canvas上按下事件
+    onCanvasDown(e)
+    {
+      this.canvas.style.cursor="crosshair";
+      this.isDrawing=true;
+      this.isDragging = false; // 禁止拖拽
+      this.rectPoint={
+        startX:e.offsetX,
+        startY:e.offsetY,
+        endX:e.offsetX,
+        endY:e.offsetY
+      };
+    },
+    //canvas上移动事件
+    onCanvasMove(e)
+    {
+      // this.drawType=1;
+      // this.isDragging=true;
+
+      if(this.isDrawing)
+      {
+        // const canvas = this.$refs.paperCanvas;
+        // const ctx = canvas.getContext('2d');
+        // // 计算矩形的宽度和高度
+        // const width = e.offsetX - this.rectPoint.startX;
+        // const height = e.offsetY - this.rectPoint.startY;
+        // this.rectPoint.endX=e.offsetX;
+        // this.rectPoint.endY=e.offsetY;
+        // // 清除之前的矩形
+        // ctx.clearRect(0, 0, canvas.width, canvas.height);
+        // // 重新绘制之前的边框数据
+        // this.drawImage();
+        // // 设置矩形样式
+        // ctx.strokeStyle = 'blue';
+        // ctx.lineWidth = 1;//画框的线的粗细
+        // // 绘制矩形
+        // ctx.strokeRect(this.rectPoint.startX, this.rectPoint.startY, width, height);
+
+        const canvas = this.$refs.paperCanvas;
+        const ctx = canvas.getContext('2d');
+        // 更新当前鼠标位置
+        this.rectPoint.endX = e.offsetX;
+        this.rectPoint.endY = e.offsetY;
+        
+        // 清除之前的矩形
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+        // 重新绘制之前的边框数据
+        this.drawImage();
+        // 设置矩形样式
+        ctx.strokeStyle = 'blue';
+        ctx.lineWidth = 1;//画框的线的粗细
+        
+        // 计算矩形的起点和宽高,处理反向框选
+        const startX = this.rectPoint.startX;
+        const startY = this.rectPoint.startY;
+        const width = e.offsetX - startX;
+        const height = e.offsetY - startY;
+        
+        // 绘制矩形(可以处理负宽度和高度)
+        ctx.strokeRect(startX, startY, width, height);
+
+      }
+    },
+    //canvas抬起事件
+    onCanvasUp()
+    {
+      this.isDrawing = false;
+      // console.log("鼠标抬起事件,画圈完成,打印四角坐标",this.rectPoint);
+     
+      // let point={
+      //   x:this.$global.floatNum((this.rectPoint.startX)/this.zoomRate/this.scale),
+      //   y:this.$global.floatNum(this.rectPoint.startY/this.zoomRate/this.scale),
+      //   w:this.$global.floatNum((this.rectPoint.endX-this.rectPoint.startX)/this.zoomRate/this.scale),
+      //   h:this.$global.floatNum((this.rectPoint.endY-this.rectPoint.startY)/this.zoomRate/this.scale),
+      //   unit:'px',
+      // };//坐标还原成px单位
+      // this.currenPoint=point;
+      // console.log("打印当前坐标",point);
+      // if(point.w>0 && point.h>0)
+      // {
+      //   console.log("打印当前坐标",point);
+       
+        
+      //   this.$emit("GetRectPoint",point);
+        
+        
+      // }
+
+      // 处理反向框选,确保宽度和高度为正数
+      let startX = this.rectPoint.startX;
+      let startY = this.rectPoint.startY;
+      let endX = this.rectPoint.endX;
+      let endY = this.rectPoint.endY;
+      
+      // 确保起点坐标是左上角,终点坐标是右下角
+      let actualStartX = Math.min(startX, endX);
+      let actualStartY = Math.min(startY, endY);
+      let actualEndX = Math.max(startX, endX);
+      let actualEndY = Math.max(startY, endY);
+      
+      // 计算实际的宽度和高度(确保为正数)
+      let width = Math.abs(actualEndX - actualStartX);
+      let height = Math.abs(actualEndY - actualStartY);
+      
+      let point = {
+        x: this.$global.floatNum(actualStartX / this.zoomRate / this.scale),
+        y: this.$global.floatNum(actualStartY / this.zoomRate / this.scale),
+        w: this.$global.floatNum(width / this.zoomRate / this.scale),
+        h: this.$global.floatNum(height / this.zoomRate / this.scale),
+        unit: 'px',
+      };//坐标还原成px单位
+      
+      this.currenPoint = point;
+      console.log("打印当前坐标", point);
+      
+      // 只有当宽度和高度都大于0时才触发事件
+      if (point.w > 0 && point.h > 0) {
+        console.log("打印当前坐标", point);
+        this.$emit("GetRectPoint", point);
+      }
+
+      
+      
+    },
+
+    //鼠标离开画布事件
+    onCanvasLeave()
+    {
+      console.log("鼠标离开画布事件");
+       // 如果正在绘制,自动结束绘制
+      if (this.isDrawing) {
+        this.onCanvasUp();
+      }
+    },
+
+    // 监听窗口大小变化,重新计算表格高度 使用节流防止频繁改变窗口大小导致计算量过大而页面卡顿
+    handleResize: throttle(function() {
+              
+      // //窗口变化重新计算canvas的宽高
+      // let {width,height} = this.$refs.paperContainer.getBoundingClientRect();
+      // this.updateZoomAndPaperInfo(height);
+      // this.updateCanvasSize(width, height);
+      // this.centerCanvas();
+      // this.loadImage();
+    }, 500), // 节流 500 毫秒内最多执行一次
+  }
+}
+</script>
+<style lang="scss" scoped>
+.paper_container
+{
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  position: relative;
+  .paper_canvas
+  {
+    position: absolute;
+    cursor: pointer;
+    background-color: transparent;
+    z-index: 10;//使canvas在图片上方
+  }
+
+  .img_container 
+  {
+    position: absolute;
+    cursor: pointer;
+    z-index: 9;
+    // border:1px solid red;
+    img
+    {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  //自定义右键菜单层样式
+  .custom_context_menu
+  {
+    position: absolute;
+    background: white;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+    z-index: 9999;
+    min-width: 120px;
+    .menu_item 
+    {
+      padding: 8px 16px;
+      cursor: pointer;
+      font-size: 14px;
+      
+      &:hover {
+        background-color: #f5f5f5;
+      }
+      
+      &:not(:last-child) {
+        border-bottom: 1px solid #eee;
+      }
+    }
+  }
+}
+</style>

+ 378 - 0
src/components/StudentPaper.vue

@@ -0,0 +1,378 @@
+<template>
+  <el-dialog title="预览答题卡"  :visible.sync="showDialog" class="page_full_dialog" fullscreen height="100%">
+      <div class="page_header">
+          <div class="back_button" @click="GoBack()">
+              <i class="iconfont icon_return"></i>返回
+          </div>
+          <div class="header_title">
+            {{ pageTitle }}<span class="header_title_tip">(右键点击图片另存为可保存图片到本地)</span>
+          </div>
+      </div>
+      <div class="dialog_paper">
+        <div class="paper_content">
+          <div class="canvas_button">
+            <div :class="currentIndex==0?'disable_button_item':'button_item'"  @click="LastPaper()">
+              <i class="el-icon-arrow-left"></i>
+            </div>
+          </div>
+          <div class="canvas_image" v-loading="isLoading" element-loading-text="加载中……" element-loading-spinner="el-icon-loading" element-loading-background="#ffffff">
+            <PaperImage :paperImgUrl="currentPaperUrl"  :drawData="currentDrawData" :downLoadName="currentDownLoadName"></PaperImage>
+          </div>
+          <div class="canvas_button">
+            <div :class="currentIndex==paperImageList.length-1?'disable_button_item':'button_item'" @click="NextPaper()">
+              <i class="el-icon-arrow-right"></i>
+            </div>
+          </div>
+        </div>
+        <div class="paper_question">
+          <div class="area_table">
+            <el-table :data="questionList" border ref="questionTable" :max-height="questionTableHeight">
+                <el-table-column label="小题名称" prop="questionName" align="center">
+                  <template slot-scope="scope">
+                    {{ scope.row.questionName }}
+                  </template>
+                </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>
+                  </template>
+                </el-table-column>
+                <el-table-column label="得分/答案" align="center">
+                  <template slot-scope="scope">
+                    <div :class="scope.row.fullScore==scope.row.score?'':'question_score'">
+                    {{ scope.row.score }}
+                    <span v-if="scope.row.answer">/{{ scope.row.answer }}</span>
+                    </div>
+                  </template>
+                </el-table-column>
+            </el-table>
+          </div>
+        </div>
+      </div>
+      
+  </el-dialog>
+</template>
+<script>
+
+import PaperImage from '@/components/PaperImage.vue';//学生试卷组件
+
+export default {
+    name: 'StudentPaper',//学生试卷组件
+    components:
+    {
+      PaperImage,
+    },
+
+    props: {
+        paperInfo:
+        {
+            type: Object,
+            default: () => { }
+        },//试卷信息
+
+        pageTitle:
+        {
+            type: String,
+            default: ''
+        },//试卷标题
+
+        value: {
+            type: Boolean,
+            required: true,
+        },//是否显示弹窗
+        currentPageIndex:{//默认显示第一页答题卡
+          type: Number,
+          required: 0,
+        }
+        
+    },
+    computed: {
+
+        showDialog: {
+            get() {
+                return this.value;
+            },
+            set(val) {
+                this.$emit("input", val);
+            },
+        },
+    },
+    watch:{
+        value(newVal) 
+        {
+          if (newVal) {
+            this.currentIndex=this.currentPageIndex;
+            this.GetStudentPaperInfo();
+          }
+        }
+    },
+    data() {
+        return {
+           paperImageList: [],//学生试卷图片列表
+           currentIndex: 0,//当前学生试卷图片索引
+           currentPaperUrl: '',//当前学生试卷图片地址
+           currentDownLoadName: '',//当前学生试卷图片下载名称
+           currentDrawData:[],//当前学生试卷答题标记数据
+           questionList: [],//学生试卷题目列表
+           questionTableHeight: 0,//学生试卷题目列表高度
+
+           isLoading:false,//是否正在加载中
+           studentName: '',//学生姓名
+           
+        }
+    },
+    created() {
+       
+    },
+    mounted() {
+      // 禁用鼠标右键
+      document.addEventListener('contextmenu', this.DisableRightClick);
+      
+       
+    },
+    beforeDestroy() 
+    {
+      document.removeEventListener('contextmenu', this.DisableRightClick);
+    },
+    methods: {
+
+      // 禁用右键菜单的方法
+      DisableRightClick(event) {
+        event.preventDefault();
+        return false;
+      },
+
+      
+      // 返回
+      GoBack()
+      {
+        this.showDialog=false;
+      },
+
+      //上一个试卷
+      LastPaper()
+      {
+        console.log("上一个试卷");
+        if (this.currentIndex > 0) 
+        {
+          this.currentIndex--;
+          this.UpdateCurrentPaperData();
+          console.log("切换到上一张试卷,当前索引:", this.currentIndex);
+        } else {
+          console.log("已经是第一张试卷");
+          // 可以添加提示信息,如:this.$message.warning('已经是第一张试卷');
+        }
+      },
+
+      //下一个试卷
+      NextPaper()
+      {
+        console.log("下一个试卷");
+        if (this.currentIndex < this.paperImageList.length - 1) 
+        {
+          this.currentIndex++;
+          this.UpdateCurrentPaperData();
+          console.log("切换到下一张试卷,当前索引:", this.currentIndex);
+        } else {
+          console.log("已经是最后一张试卷");
+          // 可以添加提示信息,如:this.$message.warning('已经是最后一张试卷');
+        }
+      },
+
+
+      // 获取学生试卷详情信息
+      GetStudentPaperInfo()
+      {
+        if(this.paperInfo.examId!='' && this.paperInfo.subjectCode!=null)
+        {
+          this.isLoading=true;
+          this.$api.reportStudent.findStudentCard(this.paperInfo).then(res=>{
+            console.log("打印学生试卷详情信息",res);
+            
+            
+
+            if(res.code==200)
+            {
+              this.paperImageList=res.data.pageVOS || [];
+              // 重置索引并更新当前试卷数据
+              // this.currentIndex = 0;
+              this.UpdateCurrentPaperData();
+              console.log("打印学生试卷当前图片地址",this.currentPaperUrl);
+
+              // 合并所有试卷图片中的题目列表
+              let allQuestions = [];
+              //先添加总分数据
+              let totalScore = {
+                questionName: '总分',
+                fullScore: res.data.fullScore || 150,
+                score: res.data.totalScore,
+                questionAnswer: '',
+                answer: '',
+                samplingPosition:"{\"x\":195,\"y\":247,\"page\":1}",
+              };
+              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.questionList = allQuestions;
+              console.log("合并后的题目列表", this.questionList);
+              
+              this.CalculateTableHeight();//计算表格高度
+              this.$nextTick(() => {
+                this.isLoading=false;
+              });
+            }
+            else
+            {
+              this.$nextTick(() => {
+                this.isLoading=false;
+              });
+            }
+          })
+        }
+        
+      },
+
+      
+
+      // 计算表格高度(更精确的方式)
+      CalculateTableHeight() 
+      {
+        this.$nextTick(() => {
+          const paperQuestionElement = this.$el.querySelector('.paper_question');
+          const tableHeaderElement = this.$el.querySelector('.page_header');
+          
+          if (paperQuestionElement && tableHeaderElement) {
+            // 获取页面相关元素的高度
+            const pageHeaderHeight = tableHeaderElement.offsetHeight || 40;
+            const paperQuestionPadding = 40; // 20px * 2 padding
+            // 计算可用高度
+            const availableHeight = window.innerHeight - 65 - 80;
+            // 设置表格最大高度
+            this.questionTableHeight = availableHeight;
+            console.log('精确计算的表格高度:', this.questionTableHeight);
+
+          } 
+          else 
+          {
+            // 备用计算方式
+            const availableHeight = window.innerHeight - 65 - 40 - 40;
+            this.questionTableHeight = availableHeight;
+          }
+        });
+      },
+
+      // 更新当前试卷数据的公共方法
+      UpdateCurrentPaperData() 
+      {
+        if (this.paperImageList.length > 0 && this.currentIndex < this.paperImageList.length) {
+          this.currentPaperUrl = this.paperImageList[this.currentIndex].picUrl;
+          this.currentDrawData = this.paperImageList[this.currentIndex].questionVOS || [];
+          this.currentDownLoadName = this.pageTitle+'答题卡第'+(this.currentIndex + 1) + '页'; // 重置下载名称
+        } else {
+          // 处理边界情况
+          this.currentPaperUrl = '';
+          this.currentDrawData = [];
+          this.currentDownLoadName = '';
+        }
+      },
+
+
+
+
+
+
+
+
+      
+
+
+    }
+}
+</script>
+<style lang="scss" scoped>
+.dialog_paper
+{
+  width: 100%;
+  height: calc(100vh - 65px);
+  background: #F0F4FB;
+  overflow: hidden;
+
+  display: flex;
+  justify-content: space-between;
+  padding: 20px;
+  box-sizing: border-box;
+  .paper_content
+  {
+    width: calc(100% - 340px);
+    height: 100%;
+    display: flex;
+    justify-content: flex-start;
+    .canvas_button
+    {
+      width: 48px;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      .button_item
+      {
+        width: 48px;
+        height: 48px;
+        border-radius: 50%;
+        background: rgba(0,0,0,0.1);
+        color:#999999;
+        font-size: 24px;
+        line-height: 48px;
+        text-align: center;
+        cursor: pointer;
+      }
+      .disable_button_item
+      {
+        width: 48px;
+        height: 48px;
+        border-radius: 50%;
+        background: rgba(0,0,0,0.1);
+        color:#C0C4CC;
+        font-size: 24px;
+        line-height: 48px;
+        text-align: center;
+      }
+    }
+    
+    .canvas_image
+    {
+      width: calc(100% - 88px - 40px);
+      height: 100%;
+      margin: auto;
+      // background-color: green;
+    }
+  }
+
+  .paper_question
+  {
+    width: 320px;
+    height: 100%;
+    background: #FFFFFF;
+    border-radius: 10px;
+    border: 1px solid #EBEEF5;
+    padding: 20px;
+    box-sizing: border-box;
+
+
+   
+    .question_score
+    {
+      color:#F56C6C;
+    }
+  }
+}
+
+
+</style>

+ 33 - 19
src/http/api/reportStudent.js

@@ -73,76 +73,90 @@ const jointSchoolReport = {
             data
         );
     },
+    //查询学生答题卡带批阅痕迹的(单校)
+    findStudentCard(data) {
+        return get(
+            base.prefix + "/api/v1/studentData/findStudentCard",
+            data
+        );
+    },
 };
 //单校接口
 const schoolReport = {
     //学生端查询考试列表(单校)
     queryStudentExamDataList(data) {
         return get(
-            base.prefix + "/api/v1/studentData/queryStudentExamDataList",
+            base.prefix + "/api/v1/oneSchoolReport/examOneSchoolDataList",
             data
         );
     },
-    //多科成绩总览-科目标准分分析
+    //学生端查询总分,多科成绩总览-科目标准分分析(单校)
     queryMultiSubjectData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryMultiSubjectData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolMultiSubjectData",
             data
         );
     },
-    //学生端查询总分,多科历次信息
+    //学生端查询总分,多科历次信息(单校)
     queryHistoryExamData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryHistoryExamData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolHistoryExamData",
             data
         );
     },
-    //学生端查询总分,多科总结建议信息
+    //学生端查询总分,多科总结建议信息(单校)
     querySuggestionData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/querySuggestionData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolSuggestionData",
             data
         );
     },
-    //学生端查询单科-我的成绩
+    //学生端查询单科-我的成绩(单校)
     queryOneSubjectData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryOneSubjectData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolSubjectData",
             data
         );
     },
-    //学生端查询单科-小题分析(表格-图表)
+    //学生端查询单科-小题分析(表格-图表)(单校)
     queryOneSubjectSmallQuestionData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryOneSubjectSmallQuestionData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolSmallQuestionData",
             data
         );
     },
-    //学生端查询单科-大题分析,知识点分析,能力要素分析
+    //学生端查询单科-大题分析,知识点分析,能力要素分析(单校)
     queryOneSubjectGroupQuestionData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryOneSubjectGroupQuestionData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolGroupQuestionData",
             data
         );
     },
-    //学生端查询单科-自定义分组
+    //学生端查询单科-自定义分组(单校)
     queryOneSubjectCustomGroupQuestion(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryOneSubjectCustomGroupQuestion",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolCustomGroupQuestion",
             data
         );
     },
-    //学生端查询单科-历次查询
+    //学生端查询单科-历次查询(单校)
     queryOneSubjectHistoryExamData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryOneSubjectHistoryExamData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolCourseHistoryExamData",
             data
         );
     },
-    //学生端查询单科-总结建议
+    //学生端查询单科-总结建议(单校)
     queryOneSubjectSuggestionData(data) {
         return post(
-            base.prefix + "/api/v1/studentData/queryOneSubjectSuggestionData",
+            base.prefix + "/api/v1/oneSchoolReport/queryOneSchoolCourseSuggestionData",
+            data
+        );
+    },
+    //查询学生答题卡带批阅痕迹的(单校)
+    findStudentCard(data) {
+        return get(
+            base.prefix + "/api/v1/oneSchoolReport/findStudentCard",
             data
         );
     },

+ 1 - 0
src/store/modules/report.js

@@ -14,6 +14,7 @@ let state = {
     examId:'',//考试id	
     subjectCode: '', //科目code
     subjectGroupType: '', //是否为组合科目 1为组合科目 0为非组合科目
+    subjectGroupNames: '',//科目名称
     isTotal: '' //是否为总分科目 1为总分 0为非总分
   },//分析报告顶部筛选数据对象  需要更多数据可以追加user_menuList
   isTotalScore: localStorage.getItem('reportIsTotalScore') || false,//考试分析选择的科目是否是总分

+ 45 - 0
src/styles/common.scss

@@ -273,6 +273,50 @@ body {
     }
 
 }
+//顶部header 菜单样式
+.page_header
+{
+  width: 100%;
+  height: 65px;
+  background: #2E64FA;
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  .back_button
+  {
+    width: 68px;
+    height: 36px;
+    position: absolute;
+    left: 50px;
+    border-radius: 4px;
+    border:2px solid #fff;
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color:#fff;
+    cursor: pointer;
+  }
+  .header_title 
+  {
+    width: calc(100% - 200px);
+    margin: auto;
+    font-weight: 700;
+    font-size: 24px;
+    color:#fff;
+    text-align: center;
+
+    .header_title_tip
+    {
+      font-size: 12px;
+      font-weight: 400;
+      color:#fff;
+      margin-left: 10px;
+    }
+  }
+}
 
 
 // 全局筛选下拉列表icon颜色调整
@@ -4718,6 +4762,7 @@ body {
 //弹窗全屏
 .page_full_dialog
 {
+  z-index:999999 !important;
   //饿了么样式覆盖
   .el-dialog__header {
     display: none;

+ 17 - 16
src/views/analysisReport/components/dCharts/GaugeChart.vue

@@ -5,9 +5,6 @@
   </div>
 </template>
 <script>
-import { color } from 'echarts';
-
-
 export default {
   name: 'gaugeChart',
   props: {
@@ -17,7 +14,7 @@ export default {
     },
     height: {
       type: String,
-      default: '300'
+      default: '340'
     },
     startAngle: {
       type: String,
@@ -31,7 +28,11 @@ export default {
       type: Number,
       default: 0,
     },
-    gradeName:{
+    gradeName:{//等级名称
+      type: String,
+      default: '',
+    },
+    subjectName:{//科目名称
       type: String,
       default: '',
     },
@@ -49,10 +50,10 @@ export default {
     return {
       myChart: null,
       colors: [
-        "#5470C6",
-        "#3BA272",
-        "#FAC858",
         "#995FB3",
+        "#FAC858",
+        "#3BA272",
+        "#5470C6",
         "#EE6666",
         "#72C0DD",
         "#90CB75",
@@ -69,10 +70,10 @@ export default {
         "#848BDC",
         "#D6AF83",
         "#84313D",//一组
-        "#5470C6",
-        "#3BA272",
-        "#FAC858",
         "#995FB3",
+        "#FAC858",
+        "#3BA272",
+        "#5470C6",
         "#EE6666",
         "#72C0DD",
         "#90CB75",
@@ -89,10 +90,10 @@ export default {
         "#848BDC",
         "#D6AF83",
         "#84313D",//二组
-        "#5470C6",
-        "#3BA272",
-        "#FAC858",
         "#995FB3",
+        "#FAC858",
+        "#3BA272",
+        "#5470C6",
         "#EE6666",
         "#72C0DD",
         "#90CB75",
@@ -142,7 +143,7 @@ export default {
         series: [{
           name: "Indicator",
           type: "gauge",
-          center: ['50%', '52%'],
+          center: ['50%', '55%'],
           radius: '100%',
           min: 0,
           max: 100,
@@ -167,7 +168,7 @@ export default {
           },
           data: [{
             value: Number(this.data),
-            name: `物理(总分${this.fullScore})`
+            name: `${this.subjectName}(总分${this.fullScore})`
           }],
           endAngle: this.endAngle || 360,
           startAngle: this.startAngle || 0,

+ 1 - 0
src/views/analysisReport/studentPage/list/mainPage.vue

@@ -475,6 +475,7 @@ export default {
         examId:courseObj.examId,//考试id	
         subjectCode: courseObj.subjectCode, //科目code
         subjectGroupType: courseObj.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
+        subjectGroupNames: courseObj.subjectName,//科目名称
         isTotal: courseObj.isTotal //是否为总分科目 1为总分 0为非总分
       };
       this.$store.dispatch("report/UpdateFilterObject", filterObject);

+ 29 - 4
src/views/analysisReport/studentPage/mainPage.vue

@@ -1,7 +1,7 @@
 <template>
   <!-- 分析报告详情总页面 -->
   <div class="analysis_main">
-    <div class="main_header">
+    <div class="main_header" ref="mainHeader">
       <div class="header_left">
         <span class="back_button" @click="GoBack">
           <i class="iconfont icon_return"></i>返回
@@ -20,7 +20,7 @@
     </div>
     <div class="main_content">
       <div class="content_right" ref="rightContent" @scroll="ScrollChange">
-        <div class="content_right_scroll">
+        <div class="content_right_scroll" ref="contentRightScroll">
           <div class="page_filter" ref="filterContent">
             <FiltersItem :filtersData="filterData" @selectItem="ChangeFilters"></FiltersItem>
           </div>
@@ -56,7 +56,8 @@ export default {
           type: "subjectName",
           list: [], //选项
         }
-      ]
+      ],
+      resizeTimer:null
     };
   },
   created() {
@@ -64,9 +65,26 @@ export default {
       //有数据
       this.filterData = this.$store.state.report.filterData;
     }
-
+    // 绑定 resize 事件
+    window.addEventListener('resize', this.HandleWidthChange);
+    this.$nextTick(()=>{
+      this.SetHeadWidth();
+    })
   },
   methods: {
+    // 处理宽度变化的逻辑
+    HandleWidthChange() {
+      // 防抖:避免窗口 resize 时频繁触发(100ms 内只执行一次)
+      clearTimeout(this.resizeTimer);
+      this.resizeTimer = setTimeout(() => {
+        this.SetHeadWidth();
+      }, 100);
+    },
+    SetHeadWidth(){
+      const scrollDivWidth = this.$refs.contentRightScroll.offsetWidth;
+      const headDiv = this.$refs.mainHeader;
+      headDiv.style.width = `${scrollDivWidth}px`;
+    },
     // 筛选事件
     ChangeFilters(e) {
       this.filterData[e.index].value = e.value;
@@ -78,6 +96,7 @@ export default {
         examId: courseObj.examId,//考试id	
         subjectCode: courseObj.subjectCode, //科目code
         subjectGroupType: courseObj.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
+        subjectGroupNames: courseObj.subjectName,//科目名称
         isTotal: courseObj.isTotal //是否为总分科目 1为总分 0为非总分
       };
       //设置是否是总分
@@ -143,6 +162,12 @@ export default {
     },
 
   },
+  beforeUnmount() {
+    // 解绑事件,避免内存泄漏
+    window.removeEventListener('resize', this.HandleWidthChange);
+    // 清除计时器
+    clearTimeout(this.resizeTimer);
+  }
 };
 </script>
 

+ 149 - 17
src/views/analysisReport/studentPage/scrolReport/transcript_single.vue

@@ -4,17 +4,23 @@
       <div class="module_title">
         <div class="title_left">我的成绩</div>
       </div>
-      <div class="module_table" style="height: 265px;overflow: hidden;">
+      <div class="module_table" style="height: 250px;overflow: hidden;">
         <div class="subject_score" v-loading="subjectLoading" :element-loading-text="loadingText"
           element-loading-spinner="el-icon-loading" element-loading-background="#ffffff">
-          <div class="subject_score_chart" v-if="!subjectLoading">
+          <div class="subject_score_chart">
             <GaugeChart
+               v-if="!subjectLoading && subjectData.levelValue"
               :data="subjectData.levelValue"
               :gradeName="subjectData.gradeName"
-              :fullScore="100" :chartData="subjectData.levelData"></GaugeChart>
-            <el-button class="button_editor"
-              :disabled="!subjectData?.data?.imgUrlList || subjectData?.data?.imgUrlList == '-'">查看答题卡</el-button>
-          </div>
+              :subjectName="subjectData.subjectName"
+              :fullScore="subjectData.fullScore"
+              :chartData="subjectData.levelData"></GaugeChart>
+            <!-- <el-button class="button_editor"
+              :disabled="!subjectData?.data?.imgUrlList || subjectData?.data?.imgUrlList == '-'">查看答题卡</el-button> -->
+              <div class="module_chart no_content_data" style="height: 240px;min-height: 240px;" v-else>
+                  <span >暂无数据</span>
+              </div>
+            </div>
           <div class="subject_score_content">
             <div class="score_item">
               <span class="title">班级</span>
@@ -74,6 +80,29 @@
         <div class="page_jg_20"></div>
       </div>
     </div>
+    <!-- 答题卡 -->
+    <div class="report_module">
+      <div class="module_title">
+        <div class="title_left">{{ subjectGroupNames }}答题卡 ({{ answerCard.paperImageList.length }}页)</div>
+      </div>
+      <div class="module_table">
+        <div class="answer_sheet" v-if="answerCard.paperImageList.length">
+          <div class="item" v-for="(item,index) in answerCard.paperImageList">
+             <PaperImage :imageIndex="index" :paperImgUrl="item.picUrl" :drawData="item.questionVOS || []"></PaperImage>
+             <div class="item_hover">
+              <div class="show_view" @click="OpenStudentPaper(index)">
+                <img src="@/assets/icon/pic_show_view.png" />
+                <span>查看</span>
+              </div>
+             </div>
+          </div>
+        </div>
+        <div class="module_chart no_content_data" v-loading="answerCard.loading" :element-loading-text="loadingText" element-loading-spinner="el-icon-loading" element-loading-background="#ffffff" v-else>
+            <span >暂无答题卡</span>
+        </div>
+        <div class="page_jg_20"></div>
+      </div>
+    </div>
     <!-- 小题分析图 -->
     <template v-for="group in groupData.groupName">
       <div class="report_module">
@@ -81,11 +110,9 @@
           <div class="title_left">{{ group.name }}分析图</div>
           <div class="title_right">
             <div class="echart_type">
-              <template v-for="item in groupData.chartTypeList">
-                <span class="chart_icon_item" :key="item.value"
+              <span class="chart_icon_item" v-for="item in groupData.chartTypeList" :key="item.value"
                   :class="groupData[group.value].chartType == item.value ? `${item.value}_cur` : item.value"
                   @click="ChangeChartType(group.value, item.value)">{{ item.label }}</span>
-              </template>
             </div>
           </div>
         </div>
@@ -181,19 +208,26 @@
       </div>
     </div>
     <GotoTop></GotoTop>
+    <!-- 学生答题卡预览组件 -->
+    <StudentPaper v-model="showStudentPaperDialog" :paperInfo="{examId:reportParam.examId,subjectCode:reportParam.subjectCode}" :currentPageIndex="currentPageIndex" :pageTitle="paperTitle"></StudentPaper>
   </div>
 </template>
 <script>
+import PaperImage from '@/components/PaperImage.vue';//学生试卷组件
+import StudentPaper from '@/components/StudentPaper.vue';//学生答题卡预览组件
 import BarChart from "@/views/analysisReport/components/dCharts/barChart";//单柱状图组件
 import RadarCharts from "@/views/analysisReport/components/dCharts/radarCharts";//雷达图
 import ExpandableText from "@/views/analysisReport/components/ExpandableText"; //文本内容展开收缩组件
 import LineChart from "@/views/analysisReport/components/dCharts/lineChart"; //折线图
 import GaugeChart from "@/views/analysisReport/components/dCharts/GaugeChart";
 import GotoTop from "@/views/analysisReport/components/GotoTop"; //分析报告页面底部回到顶部组件
+import { mapGetters } from "vuex";
 export default {
   name: "subjectQuality",
   props: {},
   components: {
+    PaperImage,
+    StudentPaper,
     BarChart,
     RadarCharts,
     ExpandableText,
@@ -208,8 +242,17 @@ export default {
         levelData:[],
         gradeName:'',
         levelValue:0,
+        fullScore:'',//满分
+        subjectName:'',//科目名称
         chartData: []
       },
+      answerCard:{//答题卡
+        loading:false,
+        paperImageList:[],//答题卡图片
+      },
+      showStudentPaperDialog:false,//显示答题卡弹框
+      paperTitle:'',//答题卡弹框标题
+      currentPageIndex:0,//当前选中的答题卡第几页
       groupData: {
         groupName: [{
           name: '小题',
@@ -315,8 +358,17 @@ export default {
         const end = start + pageSize;
         return tableData.slice(start, end);
       }
-
     },
+    subjectGroupNames(){
+        return this.$store.state.report.filterObject.subjectGroupNames
+    },//科目名称
+    pageName() {
+      let examName = this.$store.state.report.examSelectItem.examName;
+      if (examName) {
+        return examName.split("_")[0];
+      }
+    },
+    ...mapGetters(["userInfo"]),
   },
   created() { },
   mounted() {
@@ -326,6 +378,7 @@ export default {
     PageInit() {
       this.initData();
       this.QueryOneSubjectData();//学生端查询单科-我的成绩
+      this.FindStudentCard();//查询学生答题卡带批阅痕迹的(单校)
       this.QueryOneSubjectSmallQuestionData();//学生端查询单科-小题分析(表格-图表)
       this.QueryOneSubjectHistoryExamData();//学生端查询单科-历次查询
       this.QueryOneSubjectSuggestionData();//学生端查询单科-总结建议
@@ -347,8 +400,7 @@ export default {
     //学生端查询单科-我的成绩
     QueryOneSubjectData() {
       this.subjectLoading = true;
-      this.$api.reportStudent
-        .queryOneSubjectData(this.reportParam)
+      this.$api.reportStudent.queryOneSubjectData(this.reportParam)
         .then((res) => {
           if (res.code == 200 && res.data) {
             this.subjectData.data = res.data;
@@ -361,17 +413,45 @@ export default {
             }))
             const gradeName = this.subjectData.levelData.find(item=>item.label==res.data.gradeName);
             this.subjectData.levelValue = gradeName?gradeName.range:0;
-            this.subjectData.gradeName = res.data.gradeName;
+            this.subjectData.gradeName = res.data.gradeName;//等级
+            this.subjectData.fullScore = res.data.fullScore;//满分
+            this.subjectData.subjectName = res.data.subjectName;//科目名称
           } else {
             this.subjectData.data = {}
             this.subjectData.levelData = [];
             this.subjectData.levelValue = 0;
+            this.subjectData.gradeName = '';
+            this.subjectData.fullScore = '';//满分
+            this.subjectData.subjectName = '';//科目名称
           }
         })
         .finally(() => {
           this.subjectLoading = false;
         });
     },
+    //查询学生答题卡带批阅痕迹的
+    FindStudentCard(){
+      this.answerCard.loading = true;
+      this.$api.reportStudent.findStudentCard({
+        examId:this.reportParam.examId,//考试ID
+        subjectCode:this.reportParam.subjectCode//科目code
+      }).then((res) => {
+          if (res.code == 200 && res.data) {
+            this.answerCard.paperImageList=res.data.pageVOS || [];
+          }else{
+            this.answerCard.paperImageList=[];
+          }
+        })
+        .finally(() => {
+          this.answerCard.loading = false;
+        });
+    },
+    //答题卡预览
+    OpenStudentPaper(index){
+      this.currentPageIndex = index;//当选选中的第几页
+      this.paperTitle = `${this.pageName}_${this.userInfo.userName}【${this.userInfo.registrationCode}】`;
+      this.showStudentPaperDialog = true;
+    },
     getMiddleNumber(arr) {
       return parseInt((arr[0] + arr[1]) / 2);
     },
@@ -412,8 +492,7 @@ export default {
       const radarChartData = [];
       tableData.forEach(item => {
         this.groupData[type].datax.push(item.questionName);
-        const scoreRateStr = item?.scoreRateStr ?? '';
-        const scoreRate = scoreRateStr.replace("%", "")
+        const scoreRate = item?.scoreRate ?? '';
         this.groupData[type].datay.push(scoreRate)
         radarChartData.push([item.questionName, scoreRate])
       })
@@ -657,13 +736,12 @@ export default {
     display: flex;
     flex-wrap: wrap;
     gap: 12px 15px;
-    margin-top: 6px;
     align-content: flex-start;
 
     /* 元素之间的间隔 */
     .score_item {
       width: calc((100% - 15px * 4) / 5);
-      height: 100px;
+      height: 98px;
       background: #F8F9FD;
       border-radius: 5px;
       border: 1px solid #EBEEF5;
@@ -698,4 +776,58 @@ export default {
     padding-top: 0 !important;
   }
 }
+.answer_sheet{
+  display: flex;
+  flex-wrap: wrap;
+  width: 100%;
+  gap:20px;
+  .item{
+    width: calc((100% - 20px * 3) / 4);
+    padding: 9px;
+    height: 200px;
+    background: #F8F9FD;
+    border-radius: 10px;
+    border: 1px solid #EBEEF5;
+    box-sizing: border-box;
+    position: relative;
+    cursor: pointer;
+    &:hover{
+      .item_hover{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        .show_view{
+          width: 80px;
+          height: 40px;
+          background: #FFFFFF;
+          border-radius: 4px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          img{
+            width: 20px;
+            height: 20px;
+            margin-right: 6px;
+          }
+          span{
+            font-weight: 400;
+            font-size: 14px;
+            color: #606266;
+          }
+        }
+      }
+    }
+    .item_hover{
+      display: none;
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+      background: rgba(0,0,0,0.2);
+      border-radius: 10px;
+      z-index: 99;
+    }
+  }
+}
 </style>

+ 10 - 1
src/views/analysisReport/studentPage/scrolReport/transcript_total.vue

@@ -161,6 +161,9 @@ export default {
         isTotal: this.$store.state.report.filterObject.isTotal, //是否为总分科目 1为总分 0为非总分
       };
     }, //分析报告公共参数变量
+    subjectGroupNames(){
+        return this.$store.state.report.filterObject.subjectGroupNames
+    },
   },
   created() { },
   mounted() {
@@ -221,8 +224,14 @@ export default {
     //学生端查询总分,多科历次信息
     QueryHistoryExamData() {
       this.historyExamLoading = true;
+      let param = {
+        ...this.reportParam
+      }
+      if(this.reportParam.subjectGroupType==1 && this.reportParam.isTotal==0){//学生端查询总分,多科历次信息,组合科目时,一定要传组合科目的名称,组合科目的历次是按照组合科目名称来查询
+        param.subjectGroupNames = this.subjectGroupNames
+      }
       this.$api.reportStudent
-        .queryHistoryExamData(this.reportParam)
+        .queryHistoryExamData(...param)
         .then((res) => {
           if (res.code == 200 && res.data) {
             const detailData = res.data?.detailData || [];

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

@@ -47,6 +47,11 @@ export default {
 
           const directUrl = "/studentAnalysisReport/list";
           this.$router.push({ path: directUrl });
+        }else{
+          this.$message({
+            message: res.msg,
+            type: 'warning'
+          });
         }
       });
     },

+ 1 - 0
vue.config.js

@@ -90,6 +90,7 @@ module.exports = {
     proxy: {
       "/api": {
         target: "https://dev3.k12100.net/student/api/",
+        // target: "http://192.168.1.15:47003/api/",
         changeOrigin: true,
         pathRewrite: {
           "^/api": "/",