Ver código fonte

单校、联校学生端

liurongli 8 meses atrás
pai
commit
f0be9d6cdb

+ 1145 - 0
src/components/PaperImageSmall.vue

@@ -0,0 +1,1145 @@
+<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${imageIndex}`" 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:''
+    },//下载图片的名称
+    imageIndex:{//图片索引
+      type:[String,Number],
+      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${this.imageIndex}`);
+      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, 30 * this.scale); // 最小字体12px,基础字体16px
+              ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
+              
+              ctx.textAlign = 'left';
+            }
+            else
+            {
+              const fontSize = Math.max(12, 20 * 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>

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

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

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

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

+ 11 - 8
src/views/analysisReport/studentPage/mainPage.vue

@@ -37,10 +37,11 @@ export default {
   components: { FiltersItem },
   computed: {
     pageName() {
-      let examName = this.$store.state.report.examSelectItem.examName;
-      if (examName) {
-        return examName.split("_")[0];
-      }
+      // let examName = this.$store.state.report.examSelectItem.examName;
+      // if (examName) {
+      //   return examName.split("_")[0];
+      // }
+      return this.$store.state.report.examSelectItem.examName
     },
     updateScrollTop() {
       return this.$store.state.report.updateScrollTop;//监听改变滚动条参数 
@@ -81,9 +82,11 @@ export default {
       }, 100);
     },
     SetHeadWidth(){
-      const scrollDivWidth = this.$refs.contentRightScroll.offsetWidth;
-      const headDiv = this.$refs.mainHeader;
-      headDiv.style.width = `${scrollDivWidth}px`;
+      if(this.$refs.contentRightScroll){
+        const scrollDivWidth = this.$refs.contentRightScroll.offsetWidth;
+        const headDiv = this.$refs.mainHeader;
+        headDiv.style.width = `${scrollDivWidth}px`;
+      }
     },
     // 筛选事件
     ChangeFilters(e) {
@@ -96,7 +99,7 @@ export default {
         examId: courseObj.examId,//考试id	
         subjectCode: courseObj.subjectCode, //科目code
         subjectGroupType: courseObj.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
-        subjectGroupNames: courseObj.subjectName,//科目名称
+        subjectName: courseObj.subjectName,//科目名称
         isTotal: courseObj.isTotal //是否为总分科目 1为总分 0为非总分
       };
       //设置是否是总分

+ 13 - 15
src/views/analysisReport/studentPage/scrolReport/transcript_single.vue

@@ -83,7 +83,7 @@
     <!-- 答题卡 -->
     <div class="report_module">
       <div class="module_title">
-        <div class="title_left">{{ subjectGroupNames }}答题卡 ({{ answerCard.paperImageList.length }}页)</div>
+        <div class="title_left">{{ getSubjectName }}答题卡 ({{ answerCard.paperImageList.length }}页)</div>
       </div>
       <div class="module_table">
         <div class="answer_sheet" v-if="answerCard.paperImageList.length">
@@ -213,7 +213,7 @@
   </div>
 </template>
 <script>
-import PaperImage from '@/components/PaperImage.vue';//学生试卷组件
+import PaperImage from '@/components/PaperImageSmall.vue';//学生试卷组件
 import StudentPaper from '@/components/StudentPaper.vue';//学生答题卡预览组件
 import BarChart from "@/views/analysisReport/components/dCharts/barChart";//单柱状图组件
 import RadarCharts from "@/views/analysisReport/components/dCharts/radarCharts";//雷达图
@@ -360,17 +360,14 @@ export default {
         return tableData.slice(start, end);
       }
     },
-    subjectGroupNames(){
-        return this.$store.state.report.filterObject.subjectGroupNames
+    getSubjectName(){
+        return this.$store.state.report.filterObject.subjectName
     },//科目名称
     pageName() {
-      let examName = this.$store.state.report.examSelectItem.examName;
-      if (examName) {
-        return examName.split("_")[0];
-      }
+      return this.$store.state.report.examSelectItem.examName
     },//考试名称
     schoolType(){
-      sessionStorage.getItem('schoolType');//1:单校 2:联校
+      return sessionStorage.getItem('schoolType');//1:单校 2:联校
     }
   },
   created() { },
@@ -669,6 +666,7 @@ export default {
     //学生端查询单科-总结建议
     QueryOneSubjectSuggestionData() {
       this.$api.reportStudent.queryOneSubjectSuggestionData(this.reportParam).then((res) => {
+        console.log(res.code == 200 && res.data,77777)
           if (res.code == 200 && res.data) {
             const data = res.data;
             const upSubjectData = data.upSubjectData.map(item => {
@@ -685,7 +683,7 @@ export default {
                 return `${item.subjectName}为<span style="color: #2E64FA;">${item.score}</span>`
               }
             }).join('、')
-            if (data.studentOpenness == 1 || data.studentOpenness == 2) {
+            // if (data.studentOpenness == 1 || data.studentOpenness == 2) {
               this.suggestionData = `${data.studentName}同学,本次考试`;
               if (upSubjectData) {
                 this.suggestionData += `${upSubjectData},是你的优势学科,建议通过提分练习进行强化,继续保持这类学科的优势性!`
@@ -693,12 +691,12 @@ export default {
               if (downSubjectData) {
                 this.suggestionData += `${downSubjectData},是你的劣势学科,建议先加强学习,熟练掌握薄弱知识点的基础,然后通过提分练习进行巩固和强化,争取下次考试获得更优异的成绩!`
               }
-            }
+            // }
 
-          } else {
-            this.suggestionData = null
-          }
-        });
+        } else {
+          this.suggestionData = null
+        }
+      });
     },
   },
 };

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

@@ -161,10 +161,7 @@ export default {
   computed: {
     ...mapGetters(["userInfo"]),
     pageName() {
-      let examName = this.$store.state.report.examSelectItem.examName;
-      if (examName) {
-        return examName.split("_")[0];
-      }
+      return this.$store.state.report.examSelectItem.examName
     },//考试名称
     reportParam() {
       return {
@@ -177,8 +174,8 @@ export default {
         isTotal: this.$store.state.report.filterObject.isTotal, //是否为总分科目 1为总分 0为非总分
       };
     }, //分析报告公共参数变量
-    subjectGroupNames(){
-        return this.$store.state.report.filterObject.subjectGroupNames
+    getSubjectName(){
+        return this.$store.state.report.filterObject.subjectName
     },
   },
   created() { },
@@ -244,7 +241,7 @@ export default {
         ...this.reportParam
       }
       if(this.reportParam.subjectGroupType==1 && this.reportParam.isTotal==0){//学生端查询总分,多科历次信息,组合科目时,一定要传组合科目的名称,组合科目的历次是按照组合科目名称来查询
-        param.subjectGroupNames = this.subjectGroupNames
+        param.subjectName = this.getSubjectName
       }
       this.$api.reportStudent.queryHistoryExamData({...param}).then((res) => {
           if (res.code == 200 && res.data) {