PaperImage.vue 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132
  1. <template>
  2. <div class="paper_container" ref="paperContainer" @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp" @wheel="onWheel">
  3. <canvas id="paperCanvas" ref="paperCanvas" class="paper_canvas" @mouseleave="onCanvasLeave" :style="{transform: `rotate(${rotateDeg}deg)`}"></canvas>
  4. <div ref="imgContainer" :id="`imgContainer${imageIndex}`" class="img_container" :style="{transform: `rotate(${rotateDeg}deg)`}">
  5. <img v-if="paperImgUrl" :src="paperImgUrl" >
  6. </div>
  7. <div class="no_paper_url" v-if="paperImgUrl==''">
  8. <div>暂无答题卡</div>
  9. <!-- 暂无答题卡 -->
  10. <!-- 请先制作模版 -->
  11. </div>
  12. <div v-if="showContextMenu" class="custom_context_menu" :style="{ top: contextMenuY + 'px', left: contextMenuX + 'px' }" @click="hideContextMenu">
  13. <div class="menu_item" @click="handleMenuAction('download')">图片另存为</div>
  14. <div class="menu_item" @click="handleMenuAction('fitScreen')">适合屏幕</div>
  15. <div class="menu_item" @click="handleMenuAction('zoomIn')">放大(向上滚轮)</div>
  16. <div class="menu_item" @click="handleMenuAction('zoomOut')">缩小(向下滚轮)</div>
  17. </div>
  18. </div>
  19. </template>
  20. <script>
  21. import { throttle } from 'lodash';
  22. import { mmToPx } from '@/utils/common.js'
  23. export default {
  24. components: {
  25. },
  26. props:{
  27. drawData:{
  28. type:Array,
  29. default:()=>[]
  30. },//画的边框的数据
  31. paperInfo:{
  32. type:Object,
  33. default:()=>null
  34. },//纸张大小
  35. paperImgUrl:{
  36. type:String,
  37. default:''
  38. },//试卷地址相关信息
  39. currentId:{
  40. type:String,
  41. default:''
  42. },//当前选中的id
  43. isDrag:{
  44. type:Boolean,
  45. default:true
  46. },//是否可以拖动 默认可以拖动
  47. isWheel:{
  48. type:Boolean,
  49. default:true
  50. },//是否可以滚动放大、缩小 默认可以滚动
  51. isShowContextMenu:{
  52. type:Boolean,
  53. default:true
  54. },//是否显示右键菜单
  55. rotateDeg:{
  56. type:String,
  57. default:'0'
  58. },//答题卡方向
  59. isAbnormal:{
  60. type:Boolean,
  61. default:false
  62. },//是否异常处使用 区别 异常处理使用的地方 正常答案显示蓝色
  63. usedCardType:{
  64. type:Number,
  65. default:2
  66. },//默认三方卡 1 系统卡 2 三方卡
  67. currentPage:{
  68. type:Number,
  69. default:1
  70. },//当前页码 默认第一页
  71. downLoadName:{
  72. type:String,
  73. default:''
  74. },//下载图片的名称
  75. imageIndex:{//图片索引
  76. type:[String,Number],
  77. default:''
  78. },
  79. scoreFontSize:{
  80. type:Number,
  81. default:50
  82. },
  83. rtOrWrSize:{
  84. type:Number,
  85. default:2
  86. }
  87. },
  88. computed:{
  89. },
  90. data() {
  91. return {
  92. position: {
  93. x: 0,
  94. y: 0
  95. },//初始canvas图片位置
  96. startX: 0,//鼠标按下时的初始位置x坐标
  97. startY: 0,//鼠标按下时的初始位置y坐标
  98. scale: 1,//画布缩放倍数
  99. isDragging:false,//是否拖动画布
  100. isDrawing:false,//是否正在画线
  101. drawType:0,//1画线 0 拖拽模式
  102. image: null,
  103. paperImgInfo:{
  104. width:0,
  105. height:0
  106. },
  107. canvasInfo:{
  108. width:0,
  109. height:0
  110. },//画布的大小
  111. imageInfo:{
  112. width:0,
  113. height:0
  114. },//原始图片的大小
  115. canvas:null,//画板canvas
  116. ctx:null,//画板上下文
  117. zoomRate:0,//图片的缩放比例
  118. dpr:window.devicePixelRatio || 1,
  119. minScale:0.7,//最小缩放值
  120. maxScale:4,//最大缩放值
  121. rectPoint:{
  122. startX:0,
  123. startY:0,
  124. endX:0,
  125. endY:0
  126. },//矩形起始坐标点
  127. // 客观题区域
  128. showObjectArea:false,//是否显示对象区域
  129. addObjectAreaOption:{
  130. derection:1,//排列方向 1 横线 2竖向
  131. questionType:1,//题类型 1单选 2多选 3 判断
  132. startNumber:16,//起始题号
  133. endNumber:20,//结束题号
  134. interval:1,//题号间隔 默认1
  135. answerNumber:4,//选项个数 答案个数
  136. answerWidth:0,//选项宽度
  137. answerHeight:0,//选项高度
  138. },//添加选择题设置信息
  139. currenPoint:{
  140. x:0,
  141. y:0,
  142. w:0,
  143. h:0,
  144. },
  145. // isSelectBox:false,//是否选择框选答案区域框
  146. containerWidth:0,//容器宽度
  147. containerHeight:0,//容器高度
  148. isInit:true, //是否是初始加载
  149. showContextMenu: false,//是否显示右键菜单
  150. contextMenuX: 0, // 右键菜单X坐标
  151. contextMenuY: 0, // 右键菜单Y坐标
  152. CacheAllWrong:new Image(),//缓存的全错图片
  153. CacheAllRight:new Image(),//缓存的全对图片
  154. CacheHalfRight:new Image(),//缓存的半对图片
  155. CacheTypicalError:new Image(),//缓存的典型错误图片
  156. CacheExcellentAnswer:new Image(),//缓存的优秀答案图片
  157. jHeight:0,//跨页的标注 需要减去的高度
  158. blockList:[],//块列表
  159. }
  160. },
  161. watch:{
  162. paperImgUrl:
  163. {
  164. handler(newVal, oldVal)
  165. {
  166. console.log("地址变化了",this.paperImgUrl);
  167. if(this.paperImgUrl)
  168. {
  169. this.InitData();//初始化数据
  170. }
  171. },
  172. deep: true ,// 如果需要深度监听数组内部对象的变化
  173. listener: true//立即执行一次
  174. // console.log("地址变化了",this.paperImgUrl);
  175. // this.initData();//初始化数据
  176. },
  177. drawData:{
  178. handler(newVal, oldVal) {
  179. this.drawImage();//更新边框数据并重新绘制
  180. },
  181. deep: true // 如果需要深度监听数组内部对象的变化
  182. },//边框数据
  183. // currentId()
  184. // {
  185. // // console.log("当前选中项的id变化了",this.currentId);
  186. // this.drawImage();//更新边框数据并重新绘制
  187. // },
  188. },
  189. created(){
  190. this.CacheAllWrong.src=require('../assets/icon/icon_all_wrong.svg');
  191. this.CacheAllRight.src=require('../assets/icon/icon_all_right.svg');
  192. this.CacheHalfRight.src=require('../assets/icon/icon_half_right.svg');
  193. this.CacheTypicalError.src=require('../assets/tool/model_2.png');
  194. this.CacheExcellentAnswer.src=require('../assets/tool/model_1.png');
  195. this.CacheAllWrong.onload=()=>{
  196. };
  197. this.CacheAllRight.onload=()=>{
  198. };
  199. this.CacheHalfRight.onload=()=>{
  200. };
  201. this.CacheTypicalError.onload=()=>{
  202. };
  203. this.CacheExcellentAnswer.onload=()=>{
  204. };
  205. window.addEventListener('resize', this.handleResize);
  206. // 添加全局鼠标释放事件监听器,处理异常情况
  207. window.addEventListener('mouseup', this.onGlobalMouseUp);
  208. // 添加全局点击事件监听器,用于隐藏右键菜单
  209. document.addEventListener('click', this.handleGlobalEvent);
  210. // 添加全局右键点击事件监听器,用于隐藏右键菜单
  211. document.addEventListener('contextmenu', this.handleGlobalEvent);
  212. },
  213. beforeDestroy() {
  214. // 移除监听 防止内存泄漏
  215. window.removeEventListener('resize', this.handleResize);
  216. window.removeEventListener('mouseup', this.onGlobalMouseUp);
  217. document.removeEventListener('click', this.handleGlobalEvent);
  218. // 移除全局右键点击事件监听器
  219. document.removeEventListener('contextmenu', this.handleGlobalEvent);
  220. // 移除 paper_container 上的事件监听器
  221. if (this.$refs.paperContainer)
  222. {
  223. this.$refs.paperContainer.removeEventListener('contextmenu', this.handleRightClick);
  224. // this.$refs.paperContainer.removeEventListener('click', this.hideContextMenu);
  225. }
  226. },
  227. mounted() {
  228. this.InitData();//初始数据处理加载 如定位 居中 等 第一次居中
  229. // 确保 DOM 已经渲染后再添加事件监听器
  230. this.$nextTick(() => {
  231. if (this.$refs.paperContainer) {
  232. this.$refs.paperContainer.addEventListener('contextmenu', this.handleRightClick);
  233. // this.$refs.paperContainer.addEventListener('click', this.hideContextMenu);
  234. } else {
  235. console.error('paperContainer 未找到');
  236. }
  237. });
  238. },
  239. methods: {
  240. // 处理右键点击事件
  241. handleRightClick(event)
  242. {
  243. if(!this.isShowContextMenu) return false
  244. event.preventDefault();
  245. event.stopPropagation(); // 阻止事件继续向上冒泡
  246. // 设置菜单位置(相对于 paper_container 的位置)
  247. const containerRect = this.$refs.paperContainer.getBoundingClientRect();
  248. this.contextMenuX = event.clientX - containerRect.left;
  249. this.contextMenuY = event.clientY - containerRect.top;
  250. // 显示自定义菜单
  251. this.showContextMenu = true;
  252. },
  253. // 处理全局事件(包括左键点击和右键点击),用于隐藏菜单
  254. handleGlobalEvent(event) {
  255. // 检查点击的元素是否在菜单内部
  256. const menuElement = document.querySelector('.custom_context_menu');
  257. // 如果菜单是显示的,并且点击的元素不在菜单内部,则隐藏菜单
  258. if (this.showContextMenu && menuElement && !menuElement.contains(event.target)) {
  259. this.hideContextMenu();
  260. }
  261. },
  262. // 隐藏右键菜单
  263. hideContextMenu()
  264. {
  265. this.showContextMenu = false;
  266. },
  267. // 处理菜单项点击
  268. handleMenuAction(action)
  269. {
  270. console.log('执行操作:', action);
  271. switch(action) {
  272. case 'zoomIn':
  273. // 放大
  274. this.scale = Math.min(this.maxScale, this.scale + 0.1);
  275. this.ImageInfoChange();
  276. this.updateCanvasSize();
  277. this.drawImage();
  278. break;
  279. case 'zoomOut':
  280. // 缩小
  281. // 缩小
  282. this.scale = Math.max(this.minScale, this.scale - 0.1);
  283. this.ImageInfoChange();
  284. this.updateCanvasSize();
  285. this.drawImage();
  286. break;
  287. case 'fitScreen':
  288. //适合屏幕
  289. this.fitScreen()
  290. break;
  291. case 'download':
  292. // 下载图片
  293. this.downloadImage();
  294. break;
  295. }
  296. // 隐藏菜单
  297. this.hideContextMenu();
  298. },
  299. //下载图片功能
  300. downloadImage()
  301. {
  302. // this.DownloadDrawImage();
  303. // //将canvas 转换为data Url
  304. // const imgUrl =this.canvas.toDataURL('image/png');
  305. // //创建一个隐藏的a标签
  306. // let link = document.createElement('a');
  307. // link.href = imgUrl;
  308. // link.download = this.downLoadName+'.png';
  309. // // link.setAttribute('id', 'downloadImg');
  310. // // link.setAttribute('_blank', 'target');
  311. // //触发点击事件
  312. // document.body.appendChild(link);
  313. // link.click()
  314. // document.body.removeChild(link);
  315. // 创建一个新的canvas用于导出
  316. const exportCanvas = document.createElement('canvas');
  317. const exportCtx = exportCanvas.getContext('2d');
  318. // 设置导出canvas的尺寸与当前canvas相同
  319. exportCanvas.width = this.paperImgInfo.width;
  320. exportCanvas.height = this.paperImgInfo.height;
  321. console.log("画布宽高",this.canvasInfo.width,this.canvasInfo.height);
  322. console.log("图片宽高",this.paperImgInfo.width,this.paperImgInfo.height)
  323. // 在canvas上按当前显示尺寸绘制所有试卷图片
  324. exportCtx.drawImage(this.image, 0, 0, this.paperImgInfo.width, this.paperImgInfo.height);
  325. // 重新绘制标注信息到导出canvas上(按原始图片尺寸)
  326. this.DrawDataInfoCommon(exportCtx,1,1);//导出按原试卷尺寸
  327. // // 将当前canvas内容绘制到导出canvas上(这包括所有标记和注释)
  328. // exportCtx.drawImage(this.$refs.paperCanvas, 0, 0);
  329. // 将合并后的内容转换为data URL
  330. const imgUrl = exportCanvas.toDataURL('image/png');
  331. // 创建一个隐藏的a标签
  332. let link = document.createElement('a');
  333. link.href = imgUrl;
  334. link.download = this.downLoadName + '.png';
  335. // 触发点击事件
  336. document.body.appendChild(link);
  337. link.click();
  338. document.body.removeChild(link);
  339. },
  340. //导出绘制批注信息 公共方法 抽离成一个
  341. DrawDataInfoCommon(ctx,zoomRate,scale)
  342. {
  343. for(var i=0;i<this.drawData.length;i++)
  344. {
  345. let item=this.drawData[i];
  346. const point=JSON.parse(item.samplingPosition);//原始采分点的坐标
  347. console.log("打印point",point);
  348. console.log("打印卡类型",this.usedCardType);
  349. let blockPoint='';//切块的坐标
  350. let blockList=[];//跨页的后面的页的y坐标需要减去的高度
  351. let jHeight=0;//跨页的标注 需要减去的高度
  352. let obj={
  353. x:point.x*zoomRate*scale,
  354. y:point.y*zoomRate*scale,
  355. };//采分点的坐标
  356. if(this.usedCardType==1)
  357. {
  358. // 如果是系统卡需要特殊处理 需要将坐标转成系统卡的坐标 并且需要去掉上下左右的边框
  359. console.log("打印this.paperImgInfo",this.paperImgInfo);
  360. console.log("如果是系统卡需要特殊处理",item);
  361. let templateInfo={
  362. width:794-30*2,//减去左右的边距
  363. height:1123-25*2//减去上下的边距
  364. };//模板去掉边框的尺寸
  365. //如果长大于宽 就是A3 否则就是A4
  366. if(this.paperImgInfo.width>this.paperImgInfo.height)
  367. {
  368. //A3 1588*1123
  369. templateInfo={
  370. width:1588-30*2,//减去左右的边距
  371. height:1123-25*2//减去上下的边距
  372. };//模板去掉边框的尺寸
  373. }
  374. let imagePoint={};
  375. //客观题 需要减去 30 和25 因为客观题的坐标是相当于试卷原点 其他的主观题坐标是相当于批阅块的
  376. if(item.titleType==1)
  377. {
  378. imagePoint={
  379. x:((point.x-30)/templateInfo.width)*this.paperImgInfo.width,
  380. y:((point.y-15)/templateInfo.height)*this.paperImgInfo.height,
  381. };//相对于原图试卷的坐标 这里减去30是 黑块相对于左边的边距30px 上边的边距25px 减去15 是因为选择题的采分点 使用的是第一个答案的初始坐标点 需要减去答案的高度使其上下居中
  382. }
  383. else
  384. {
  385. imagePoint={
  386. x:(point.x/templateInfo.width)*this.paperImgInfo.width ,
  387. y:(point.y/templateInfo.height)*this.paperImgInfo.height,
  388. };//相对于原图试卷的坐标
  389. }
  390. // console.log("系统卡相对于原图试卷的坐标imagePoint",imagePoint);
  391. // let offsetX=this.GetInteger(imagePoint.x*this.zoomRate*this.scale);
  392. // console.log("系统卡转换的采分点坐标offsetX",offsetX);
  393. // let offsetY=Number(imagePoint.y*this.zoomRate*this.scale);
  394. // console.log("系统卡转换的采分点坐标offsetY",offsetY);
  395. obj={
  396. x:imagePoint.x*this.zoomRate*this.scale,
  397. y:imagePoint.y*this.zoomRate*this.scale,
  398. };//采分点坐标更新
  399. }
  400. console.log("打印当前题目",item.questionName);
  401. console.log("打印转换前采分点坐标point",point);
  402. console.log("打印转换后相对批阅块的采分点坐标obj",obj);
  403. //如果是批阅块的坐标 需要相加
  404. console.log("打印item",item);
  405. if(item.pagePaintingVOS)
  406. {
  407. console.log("打印item",item);
  408. // let pageItem = item.pagePaintingVOS.find(item => item.page == point.page);
  409. const pointIndex=point.index || 0;//默认0
  410. blockPoint = item.pagePaintingVOS[pointIndex];
  411. const pointPage=point.page;//采分点所在的页码
  412. if(item.pagePaintingVOS.length>1)
  413. {
  414. //跨页的
  415. blockList=item.pagePaintingVOS || [];
  416. }
  417. // item.pagePaintingVOS.forEach((item,index) => {
  418. // if(index==point.index && item.page == point.page)
  419. // {
  420. // pageItem=item;
  421. // }
  422. // });
  423. //需要同时根据索引和页面查找
  424. // const pageItem = item.pagePaintingVOS.find(item => item.page == point.page && item.index == point.index);
  425. console.log("打印blockPoint",blockPoint);
  426. let newBlockPoint=blockPoint;
  427. //如果是系统卡需要特殊处理 需要将坐标转成系统卡的坐标 并且需要去掉上下左右的边框
  428. if(this.usedCardType==1)
  429. {
  430. let templateInfo={
  431. width:794-30*2,//减去左右的边距
  432. height:1123-25*2//减去上下的边距
  433. };//模板去掉边框的尺寸
  434. //如果长大于宽 就是A3 否则就是A4
  435. if(this.paperImgInfo.width>this.paperImgInfo.height)
  436. {
  437. //A3 1588*1123
  438. templateInfo={
  439. width:1588-30*2,//减去左右的边距
  440. height:1123-25*2//减去上下的边距
  441. };//模板去掉边框的尺寸
  442. }
  443. newBlockPoint={
  444. x:this.GetInteger(((blockPoint.x-30)/templateInfo.width)*this.paperImgInfo.width),
  445. y:this.GetInteger(((blockPoint.y-25)/templateInfo.height)*this.paperImgInfo.height),
  446. w:this.GetInteger((blockPoint.w/templateInfo.width)*this.paperImgInfo.width),
  447. h:this.GetInteger((blockPoint.h/templateInfo.height)*this.paperImgInfo.height),
  448. page:blockPoint.page,
  449. };//相对于原图试卷的坐标
  450. blockPoint=newBlockPoint;
  451. if(item.pagePaintingVOS.length>1)
  452. {
  453. blockList=[];
  454. for(var j=0;j<item.pagePaintingVOS.length;j++)
  455. {
  456. let blockObj=item.pagePaintingVOS[j];
  457. let obj={
  458. x:this.GetInteger(((blockObj.x-30)/templateInfo.width)*this.paperImgInfo.width),
  459. y:this.GetInteger(((blockObj.y-25)/templateInfo.height)*this.paperImgInfo.height),
  460. w:this.GetInteger((blockObj.w/templateInfo.width)*this.paperImgInfo.width),
  461. h:this.GetInteger((blockObj.h/templateInfo.height)*this.paperImgInfo.height),
  462. page:blockObj.page,
  463. };
  464. blockList.push(obj);
  465. }
  466. }
  467. }
  468. if(pointPage != this.pointPage)
  469. {
  470. //如果不是同一页
  471. console.log("打印不同页的坐标 打印第一页的切块坐标",newBlockPoint);
  472. jHeight=newBlockPoint.h;
  473. }
  474. if (blockPoint) {
  475. obj.x =this.GetInteger(newBlockPoint.x* zoomRate * scale+ obj.x);
  476. obj.y =this.GetInteger(newBlockPoint.y* zoomRate * scale +obj.y);
  477. }
  478. console.log("打印计算后的obj",obj);
  479. }
  480. console.log("打印转换后相对于原试卷的采分点坐标obj",obj);
  481. // 绘制文字 定位去和客观题组不用显示
  482. //console.log("打印标题",item.questionName);
  483. //ctx.fillStyle = 'red';
  484. ctx.fillStyle = '#D81E06';
  485. ctx.textAlign = 'center';
  486. ctx.textBaseline = 'middle';
  487. if(item.questionName=='总分')
  488. {
  489. const fontSize = Math.max(12, this.scoreFontSize * scale); // 最小字体12px,基础字体50px
  490. ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
  491. ctx.textAlign = 'left';
  492. // 添加下划线
  493. let text = '',textWidth = 0;
  494. if(item.displayType === 0 || item.displayType === 2){
  495. text = item.displayName.toString();
  496. textWidth = ctx.measureText(text).width;
  497. }else{
  498. textWidth = 2 * fontSize*zoomRate*scale;
  499. }
  500. const underlineY = obj.y + 50*zoomRate * scale ; // 可根据需要调整下划线位置
  501. const startX = obj.x +35*zoomRate*scale;
  502. ctx.beginPath();
  503. ctx.moveTo(startX, underlineY);
  504. ctx.lineTo(startX + textWidth, underlineY);
  505. ctx.strokeStyle = ctx.fillStyle; // 使用与文字相同的颜色
  506. ctx.lineWidth = 4;
  507. ctx.stroke();
  508. // 添加下划线2
  509. ctx.beginPath();
  510. ctx.moveTo(startX-20, underlineY+20*this.zoomRate*this.scale);
  511. ctx.lineTo(startX + textWidth + 20, underlineY+20*zoomRate*scale);
  512. ctx.strokeStyle = ctx.fillStyle; // 使用与文字相同的颜色
  513. ctx.lineWidth = 4;
  514. ctx.stroke();
  515. if(item.displayType === 0 || item.displayType === 2){//0-分数 1-对错 2-等级
  516. ctx.fillText(item.displayName,obj.x+35*zoomRate*scale,obj.y);
  517. }else{
  518. //判断是什么类型 scoreType 分数类型1:全对 2:半对 3: 全错
  519. const iconX=obj.x +35*zoomRate*scale;
  520. const iconY=obj.y-this.rtOrWrSize*fontSize*zoomRate*scale;
  521. const iconWidth = this.rtOrWrSize * fontSize*zoomRate*scale;
  522. const iconHeight = this.rtOrWrSize * fontSize*zoomRate*scale;
  523. if(item.correctType==0){
  524. //0 分全错
  525. ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
  526. }else if(item.correctType==2){
  527. //满分全对
  528. ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
  529. }else{
  530. //半对
  531. ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
  532. }
  533. }
  534. }
  535. else
  536. {
  537. const fontSize = Math.max(12, 18 * scale); // 最小字体12px,基础字体18px
  538. // ctx.font = '30px Arial';
  539. ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
  540. ctx.textAlign = 'left';
  541. //判断是什么类型 scoreType 分数类型1:全对 2:半对 3: 全错
  542. const iconX=obj.x-20*zoomRate*scale;
  543. const iconY=obj.y-20*zoomRate*scale;
  544. const iconWidth = 40*zoomRate*scale;
  545. const iconHeight = 40*zoomRate*scale;
  546. // console.log("打印item.score",item.score)
  547. // displayType:res.data.displayType,//显示类型 0-分数 1-对错 2-等级
  548. // displayName: res.data.displayName,//显示值
  549. // correctType: res.data.correctType,//显示对错的时候 0-错 1-半对 2-全对
  550. if(item.displayType === 2){
  551. ctx.fillText(item.displayName,obj.x+35*zoomRate*scale,obj.y);
  552. }else{
  553. if(item.correctType===0){
  554. //0 分全错
  555. ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
  556. }else if(item.correctType==2){
  557. //满分 全对
  558. ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
  559. }else if(item.correctType==1){
  560. //半对
  561. ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
  562. }else{
  563. const displayName = isNaN(item.displayName)?0:Number(item.displayName);
  564. if(displayName==0){
  565. //0 分全错
  566. ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
  567. }else if(displayName==item.fullScore){
  568. //满分全对
  569. ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
  570. }else{
  571. //半对
  572. ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
  573. }
  574. }
  575. if(item.displayType === 0){//0-分数 显示分数和对错
  576. ctx.fillText(item.displayName,obj.x+35*zoomRate*scale,obj.y);
  577. }
  578. }
  579. }
  580. // ctx.fillText(item.displayName,obj.x+35*this.zoomRate*this.scale,obj.y);
  581. // ctx.fillStyle = 'blue';
  582. // ctx.font = `24px Arial`; // 让文字大小跟随缩放
  583. // ctx.textAlign = 'center';
  584. // ctx.textBaseline = 'middle';
  585. // ctx.fillText(item.questionName,obj.x-65*this.zoomRate*this.scale,obj.y);
  586. //绘制标注信息
  587. if(item.drawLineData)
  588. {
  589. //有标注信息 加载标注 所有的坐标都要加上批阅块的初始坐标
  590. console.log("打印point",point);
  591. console.log("打印当前的页面",this.currentPage);
  592. console.log("打印当前的批阅块数组",blockList);
  593. const drawLineData=JSON.parse(item.drawLineData);
  594. console.log("打印drawLineData",drawLineData);
  595. if(blockList.length>0)
  596. {
  597. //跨页的
  598. let jh=0;//打印需要减去的高度 跨页的
  599. blockList.forEach((blockItem,index) => {
  600. console.log("打印blockItem",blockItem);
  601. if(blockItem.page==this.currentPage)
  602. {
  603. drawLineData.forEach((drawlineItem,drawIndex) => {
  604. let findIndex=this.FindBlockIndex(drawlineItem,blockList);
  605. console.log("打印drawIndex",drawIndex);
  606. console.log("打印findIndex",findIndex);
  607. let drawItem=drawlineItem;
  608. if(findIndex==index)
  609. {
  610. // drawItem=this.FindTargetObj(drawlineItem,blockList);
  611. blockPoint=blockList[findIndex];
  612. console.log("打印blockPoint",blockPoint);
  613. console.log("打印drawItem",drawItem);
  614. // 计算通用的缩放因子,避免重复计算
  615. const zoomScale = zoomRate * scale;
  616. const offsetX = blockPoint.x * zoomScale;
  617. const offsetY = blockPoint.y * zoomScale;
  618. // 扣分点 加分点 标记// 1:文字 (扣分留痕 显示扣分信息) 2:划线 3:波浪线 4:画笔 5:评语
  619. switch(drawItem.drawType) {
  620. case 1: {
  621. // 文字类型(扣分点/加分点标记)
  622. const fontSize = Math.max(12, 40 * zoomScale);
  623. ctx.font = `${fontSize}px Arial`;
  624. ctx.fillStyle = '#D81E06';
  625. ctx.textAlign = 'center';
  626. ctx.textBaseline = 'middle';
  627. const x = drawItem.x * zoomScale + offsetX;
  628. const y = drawItem.y * zoomScale + offsetY;
  629. if(drawItem.type==='reduce') {
  630. ctx.fillText('-'+drawItem.score, x, y);
  631. } else if(drawItem.type==='bonus') {
  632. ctx.fillText('+'+drawItem.score, x, y);
  633. }
  634. break;
  635. }
  636. case 2: {
  637. // 绘制横线
  638. console.log("打印需要减去的高度",jHeight);
  639. jHeight=0;
  640. console.log("打印blockList",blockList);
  641. const coords = {
  642. x: this.GetInteger(drawItem.x * zoomScale + offsetX),
  643. y: this.GetInteger(drawItem.y * zoomScale + offsetY - jh * zoomScale),
  644. endX: this.GetInteger(drawItem.endX * zoomScale + offsetX),
  645. endY: this.GetInteger(drawItem.endY * zoomScale + offsetY - jh * zoomScale)
  646. };
  647. this.DrawHorizontalLine(coords.x, coords.y, coords.endX, coords.endY,ctx);
  648. break;
  649. }
  650. case 3: {
  651. // 绘制波浪线
  652. const coords = {
  653. startX: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  654. startY: parseFloat(((drawItem.y-jh) * zoomScale + offsetY).toFixed(2)),
  655. endX: parseFloat((drawItem.endX * zoomScale + offsetX).toFixed(2)),
  656. endY: parseFloat(((drawItem.endY-jh) * zoomScale + offsetY).toFixed(2))
  657. };
  658. this.DrawWaveLine(coords.startX, coords.startY, coords.endX, coords.endY,ctx);
  659. break;
  660. }
  661. case 4:
  662. // 绘制画笔数据
  663. this.DrawPenLine(drawItem,blockPoint,zoomRate,scale,ctx);
  664. break;
  665. case 5: {
  666. // 绘制评语
  667. const coords = {
  668. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  669. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  670. };
  671. this.DrawText(coords.x, coords.y, drawItem.text,ctx);
  672. break;
  673. }
  674. case 6:
  675. {
  676. // 绘制对号
  677. const coords = {
  678. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  679. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  680. };
  681. this.DrawText(coords.x, coords.y, '✔',ctx);
  682. break;
  683. }
  684. case 7: {
  685. // 绘制对号
  686. const coords = {
  687. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  688. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  689. };
  690. this.DrawText(coords.x, coords.y, '✘',ctx);
  691. break;
  692. }
  693. default:
  694. console.warn('未知的绘图类型:', drawItem.drawType);
  695. }
  696. }
  697. })
  698. }
  699. jh=jh+blockItem.h;
  700. });
  701. }
  702. else
  703. {
  704. //非跨页的数据
  705. for(const [index, drawItem] of drawLineData.entries()) {
  706. console.log("打印drawItem",drawItem);
  707. // 计算通用的缩放因子,避免重复计算
  708. const zoomScale = zoomRate * scale;
  709. const offsetX = blockPoint.x * zoomScale;
  710. const offsetY = blockPoint.y * zoomScale;
  711. // 扣分点 加分点 标记// 1:文字 (扣分留痕 显示扣分信息) 2:划线 3:波浪线 4:画笔 5:评语
  712. switch(drawItem.drawType) {
  713. case 1: {
  714. // 文字类型(扣分点/加分点标记)
  715. const fontSize = Math.max(12, 40 * zoomScale);
  716. ctx.font = `${fontSize}px Arial`;
  717. ctx.fillStyle = '#D81E06';
  718. ctx.textAlign = 'center';
  719. ctx.textBaseline = 'middle';
  720. const x = drawItem.x * zoomScale + offsetX;
  721. const y = drawItem.y * zoomScale + offsetY;
  722. if(drawItem.type==='reduce') {
  723. ctx.fillText('-'+drawItem.score, x, y);
  724. } else if(drawItem.type==='bonus') {
  725. ctx.fillText('+'+drawItem.score, x, y);
  726. }
  727. break;
  728. }
  729. case 2: {
  730. // 绘制横线
  731. console.log("打印需要减去的高度",jHeight);
  732. jHeight=0;
  733. console.log("打印blockList",blockList);
  734. const coords = {
  735. x: this.GetInteger(drawItem.x * zoomScale + offsetX),
  736. y: this.GetInteger(drawItem.y * zoomScale + offsetY - jHeight * zoomScale),
  737. endX: this.GetInteger(drawItem.endX * zoomScale + offsetX),
  738. endY: this.GetInteger(drawItem.endY * zoomScale + offsetY - jHeight * zoomScale)
  739. };
  740. this.DrawHorizontalLine(coords.x, coords.y, coords.endX, coords.endY,ctx);
  741. break;
  742. }
  743. case 3: {
  744. // 绘制波浪线
  745. const coords = {
  746. startX: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  747. startY: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2)),
  748. endX: parseFloat((drawItem.endX * zoomScale + offsetX).toFixed(2)),
  749. endY: parseFloat((drawItem.endY * zoomScale + offsetY).toFixed(2))
  750. };
  751. this.DrawWaveLine(coords.startX, coords.startY, coords.endX, coords.endY,ctx);
  752. break;
  753. }
  754. case 4:
  755. // 绘制画笔数据
  756. this.DrawPenLine(drawItem,blockPoint,zoomRate,scale,ctx);
  757. break;
  758. case 5: {
  759. // 绘制评语
  760. const coords = {
  761. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  762. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  763. };
  764. this.DrawText(coords.x, coords.y, drawItem.text,ctx);
  765. break;
  766. }
  767. case 6:
  768. {
  769. // 绘制对号
  770. const coords = {
  771. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  772. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  773. };
  774. this.DrawText(coords.x, coords.y, '✔',ctx);
  775. break;
  776. }
  777. case 7: {
  778. // 绘制对号
  779. const coords = {
  780. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  781. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  782. };
  783. this.DrawText(coords.x, coords.y, '✘',ctx);
  784. break;
  785. }
  786. default:
  787. console.warn('未知的绘图类型:', drawItem.drawType);
  788. }
  789. }
  790. }
  791. }
  792. }
  793. },
  794. //寻找对象在数组中的索引
  795. FindBlockIndex(targetObj,arr)
  796. {
  797. // 累计高度,用来确定对象位于哪个区间
  798. let cumulativeHeight = 0;
  799. for (let i = 0; i < arr.length; i++) {
  800. const item = arr[i];
  801. if(targetObj.drawType==4)
  802. {
  803. //画笔判断
  804. if (targetObj.drawlineData[0].y >= cumulativeHeight && targetObj.drawlineData[0].y < cumulativeHeight + item.h) {
  805. return i; // 返回匹配的索引
  806. }
  807. }
  808. else
  809. {
  810. // 检查目标对象的y值是否在当前区间的范围内
  811. if (targetObj.y >= cumulativeHeight && targetObj.y < cumulativeHeight + item.h) {
  812. return i; // 返回匹配的索引
  813. }
  814. }
  815. // 累计高度,为下一个区间的起始位置
  816. cumulativeHeight += item.h;
  817. }
  818. return -1; // 如果没有找到匹配的区间,返回-1
  819. },
  820. //寻找对象在数组中的对象
  821. FindTargetObj(targetObj,arr)
  822. {
  823. // 累计高度,用来确定对象位于哪个区间
  824. let cumulativeHeight = 0;
  825. for (let i = 0; i < arr.length; i++) {
  826. const item = arr[i];
  827. // 判断画笔
  828. if(targetObj.drawType==4)
  829. {
  830. if (targetObj.drawlineData[0].y >= cumulativeHeight && targetObj.drawlineData[0].y < cumulativeHeight + item.h) {
  831. targetObj.drawlineData.forEach((item,index) => {
  832. item.y=item.y-cumulativeHeight;
  833. });
  834. }
  835. }
  836. else
  837. {
  838. // 检查目标对象的y值是否在当前区间的范围内
  839. if (targetObj.y >= cumulativeHeight && targetObj.y < cumulativeHeight + item.h) {
  840. targetObj.y=targetObj.y-cumulativeHeight;
  841. }
  842. }
  843. // 累计高度,为下一个区间的起始位置
  844. cumulativeHeight += item.h;
  845. }
  846. return targetObj;
  847. },
  848. //初始数据加载
  849. InitData()
  850. {
  851. //获取容器的宽高
  852. const { width, height } = this.$refs.paperContainer.getBoundingClientRect();
  853. this.containerHeight = Number(height);
  854. this.containerWidth =Number(width);
  855. console.log("容器宽高",this.containerWidth,this.containerHeight);
  856. //初始化获取试卷数据
  857. console.log("打印paperInfo",this.paperInfo);
  858. if(this.paperInfo?.width && this.paperInfo?.height)
  859. {
  860. //设置初始数据
  861. this.paperImgInfo.width=this.paperInfo.width;//获取宽
  862. this.paperImgInfo.height=this.paperInfo.height;//获取高
  863. // 更新缩放率
  864. this.updateZoomAndPaperInfo();
  865. // 更新画布尺寸
  866. this.updateCanvasSize();
  867. console.log("是否初始",this.isInit);
  868. //计算中心位置使图片居中 初始加载图片是居中
  869. if(this.isInit)
  870. {
  871. this.centerCanvas();
  872. }
  873. //加载图片
  874. this.loadImage();
  875. }
  876. else
  877. {
  878. //没有值 默认图片的宽高
  879. console.log("没有纸张试卷的宽高信息");
  880. this.image = new Image();
  881. this.image.crossOrigin = 'anonymous'; // 处理跨域问题
  882. this.image.src = this.paperImgUrl;
  883. this.image.onload = () => {
  884. console.log("图片加载完成",this.image);
  885. this.paperImgInfo.width=this.image.width;//获取宽
  886. this.paperImgInfo.height=this.image.height;//获取高
  887. // 更新缩放率
  888. this.updateZoomAndPaperInfo();
  889. // 更新画布尺寸
  890. this.updateCanvasSize();
  891. //计算中心位置使图片居中
  892. console.log("是否初始",this.isInit);
  893. //计算中心位置使图片居中 初始加载图片是居中
  894. // if(this.isInit)
  895. // {
  896. this.centerCanvas();
  897. // }
  898. //加载图片
  899. this.loadImage();
  900. // this.drawQuestionPosition();
  901. };
  902. }
  903. },
  904. //切换试卷图片
  905. chanagePaperImage()
  906. {
  907. // 更新缩放率
  908. this.updateZoomAndPaperInfo();
  909. // 更新画布尺寸
  910. this.updateCanvasSize();
  911. // //计算中心位置使图片居中
  912. // this.centerCanvas();
  913. //加载图片
  914. this.loadImage();
  915. },
  916. //适合屏幕
  917. fitScreen()
  918. {
  919. this.scale=1;
  920. this.isInit=true;
  921. this.InitData();//初始化屏幕加载
  922. },
  923. // 加载图片
  924. loadImage() {
  925. // this.image = new Image();
  926. // this.image.src = this.paperImgUrl;
  927. // this.image.onload = () => {
  928. // console.log("图片加载完成",this.image);
  929. this.drawImage();//绘制边框数据
  930. // this.drawQuestionPosition();
  931. // };
  932. },
  933. // 图片宽高坐标变化
  934. ImageInfoChange(){
  935. let {width,height} = this.paperImgInfo;
  936. // console.log("打印纸张试卷的图片宽高",this.paperImgInfo);
  937. // let imgDom = document.getElementById(`imgContainer${this.imageIndex}`);
  938. // imgDom.style.width = width * this.zoomRate*this.scale + 'px';
  939. // imgDom.style.height = height * this.zoomRate*this.scale + 'px';
  940. // imgDom.style.left = this.position.x + 'px';
  941. // imgDom.style.top = this.position.y + 'px';
  942. let imgDom = this.$refs.imgContainer;
  943. imgDom.style.width = width * this.zoomRate*this.scale + 'px';
  944. imgDom.style.height = height * this.zoomRate*this.scale + 'px';
  945. imgDom.style.left = this.position.x + 'px';
  946. imgDom.style.top = this.position.y + 'px';
  947. },
  948. //图片加载完成后绘制图片
  949. drawImage()
  950. {
  951. // if (this.image) {
  952. this.canvas=this.$refs.paperCanvas;
  953. let ctx = this.canvas.getContext('2d');
  954. ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height);//清除边框数据
  955. this.ImageInfoChange();//用image图片替代背景 是为了高清显示
  956. console.log("加载边框数据",this.drawData);
  957. // this.DrawDataInfo();//加载边框数据
  958. this.DrawDataInfoCommon(ctx,this.zoomRate,this.scale);
  959. },
  960. //绘制批注信息 此方法已弃用 改用DrawDataInfoCommon 公共
  961. DrawDataInfo()
  962. {
  963. let ctx = this.canvas.getContext('2d');
  964. for(var i=0;i<this.drawData.length;i++)
  965. {
  966. let item=this.drawData[i];
  967. const point=JSON.parse(item.samplingPosition);//原始采分点的坐标
  968. console.log("打印point",point);
  969. console.log("打印卡类型",this.usedCardType);
  970. let blockPoint='';//切块的坐标
  971. let blockList=[];//跨页的后面的页的y坐标需要减去的高度
  972. let jHeight=0;//跨页的标注 需要减去的高度
  973. let obj={
  974. x:point.x*this.zoomRate*this.scale,
  975. y:point.y*this.zoomRate*this.scale,
  976. };//采分点的坐标
  977. if(this.usedCardType==1)
  978. {
  979. // 如果是系统卡需要特殊处理 需要将坐标转成系统卡的坐标 并且需要去掉上下左右的边框
  980. console.log("打印this.paperImgInfo",this.paperImgInfo);
  981. console.log("如果是系统卡需要特殊处理",item);
  982. let templateInfo={
  983. width:794-30*2,//减去左右的边距
  984. height:1123-25*2//减去上下的边距
  985. };//模板去掉边框的尺寸
  986. //如果长大于宽 就是A3 否则就是A4
  987. if(this.paperImgInfo.width>this.paperImgInfo.height)
  988. {
  989. //A3 1588*1123
  990. templateInfo={
  991. width:1588-30*2,//减去左右的边距
  992. height:1123-25*2//减去上下的边距
  993. };//模板去掉边框的尺寸
  994. }
  995. let imagePoint={};
  996. //客观题 需要减去 30 和25 因为客观题的坐标是相当于试卷原点 其他的主观题坐标是相当于批阅块的
  997. if(item.titleType==1)
  998. {
  999. imagePoint={
  1000. x:((point.x-30)/templateInfo.width)*this.paperImgInfo.width,
  1001. y:((point.y-15)/templateInfo.height)*this.paperImgInfo.height,
  1002. };//相对于原图试卷的坐标 这里减去30是 黑块相对于左边的边距30px 上边的边距25px 减去15 是因为选择题的采分点 使用的是第一个答案的初始坐标点 需要减去答案的高度使其上下居中
  1003. }
  1004. else
  1005. {
  1006. imagePoint={
  1007. x:(point.x/templateInfo.width)*this.paperImgInfo.width ,
  1008. y:(point.y/templateInfo.height)*this.paperImgInfo.height,
  1009. };//相对于原图试卷的坐标
  1010. }
  1011. // console.log("系统卡相对于原图试卷的坐标imagePoint",imagePoint);
  1012. // let offsetX=this.GetInteger(imagePoint.x*this.zoomRate*this.scale);
  1013. // console.log("系统卡转换的采分点坐标offsetX",offsetX);
  1014. // let offsetY=Number(imagePoint.y*this.zoomRate*this.scale);
  1015. // console.log("系统卡转换的采分点坐标offsetY",offsetY);
  1016. obj={
  1017. x:imagePoint.x*this.zoomRate*this.scale,
  1018. y:imagePoint.y*this.zoomRate*this.scale,
  1019. };//采分点坐标更新
  1020. }
  1021. console.log("打印当前题目",item.questionName);
  1022. console.log("打印转换前采分点坐标point",point);
  1023. console.log("打印转换后相对批阅块的采分点坐标obj",obj);
  1024. //如果是批阅块的坐标 需要相加
  1025. console.log("打印item",item);
  1026. if(item.pagePaintingVOS)
  1027. {
  1028. console.log("打印item",item);
  1029. // let pageItem = item.pagePaintingVOS.find(item => item.page == point.page);
  1030. const pointIndex=point.index || 0;//默认0
  1031. blockPoint = item.pagePaintingVOS[pointIndex];
  1032. const pointPage=point.page;//采分点所在的页码
  1033. if(item.pagePaintingVOS.length>1)
  1034. {
  1035. //跨页的
  1036. blockList=item.pagePaintingVOS || [];
  1037. }
  1038. // item.pagePaintingVOS.forEach((item,index) => {
  1039. // if(index==point.index && item.page == point.page)
  1040. // {
  1041. // pageItem=item;
  1042. // }
  1043. // });
  1044. //需要同时根据索引和页面查找
  1045. // const pageItem = item.pagePaintingVOS.find(item => item.page == point.page && item.index == point.index);
  1046. console.log("打印blockPoint",blockPoint);
  1047. let newBlockPoint=blockPoint;
  1048. //如果是系统卡需要特殊处理 需要将坐标转成系统卡的坐标 并且需要去掉上下左右的边框
  1049. if(this.usedCardType==1)
  1050. {
  1051. let templateInfo={
  1052. width:794-30*2,//减去左右的边距
  1053. height:1123-25*2//减去上下的边距
  1054. };//模板去掉边框的尺寸
  1055. //如果长大于宽 就是A3 否则就是A4
  1056. if(this.paperImgInfo.width>this.paperImgInfo.height)
  1057. {
  1058. //A3 1588*1123
  1059. templateInfo={
  1060. width:1588-30*2,//减去左右的边距
  1061. height:1123-25*2//减去上下的边距
  1062. };//模板去掉边框的尺寸
  1063. }
  1064. newBlockPoint={
  1065. x:this.GetInteger(((blockPoint.x-30)/templateInfo.width)*this.paperImgInfo.width),
  1066. y:this.GetInteger(((blockPoint.y-25)/templateInfo.height)*this.paperImgInfo.height),
  1067. w:this.GetInteger((blockPoint.w/templateInfo.width)*this.paperImgInfo.width),
  1068. h:this.GetInteger((blockPoint.h/templateInfo.height)*this.paperImgInfo.height),
  1069. page:blockPoint.page,
  1070. };//相对于原图试卷的坐标
  1071. blockPoint=newBlockPoint;
  1072. if(item.pagePaintingVOS.length>1)
  1073. {
  1074. blockList=[];
  1075. for(var j=0;j<item.pagePaintingVOS.length;j++)
  1076. {
  1077. let blockObj=item.pagePaintingVOS[j];
  1078. let obj={
  1079. x:this.GetInteger(((blockObj.x-30)/templateInfo.width)*this.paperImgInfo.width),
  1080. y:this.GetInteger(((blockObj.y-25)/templateInfo.height)*this.paperImgInfo.height),
  1081. w:this.GetInteger((blockObj.w/templateInfo.width)*this.paperImgInfo.width),
  1082. h:this.GetInteger((blockObj.h/templateInfo.height)*this.paperImgInfo.height),
  1083. page:blockObj.page,
  1084. };
  1085. blockList.push(obj);
  1086. }
  1087. }
  1088. }
  1089. if(pointPage != this.pointPage)
  1090. {
  1091. //如果不是同一页
  1092. console.log("打印不同页的坐标 打印第一页的切块坐标",newBlockPoint);
  1093. jHeight=newBlockPoint.h;
  1094. }
  1095. if (blockPoint) {
  1096. obj.x =this.GetInteger(newBlockPoint.x* this.zoomRate * this.scale+ obj.x);
  1097. obj.y =this.GetInteger(newBlockPoint.y* this.zoomRate * this.scale +obj.y);
  1098. }
  1099. console.log("打印计算后的obj",obj);
  1100. }
  1101. console.log("打印转换后相对于原试卷的采分点坐标obj",obj);
  1102. // 绘制文字 定位去和客观题组不用显示
  1103. //console.log("打印标题",item.questionName);
  1104. //ctx.fillStyle = 'red';
  1105. ctx.fillStyle = '#D81E06';
  1106. ctx.textAlign = 'center';
  1107. ctx.textBaseline = 'middle';
  1108. if(item.questionName=='总分')
  1109. {
  1110. const fontSize = Math.max(12, this.scoreFontSize * this.scale); // 最小字体12px,基础字体50px
  1111. ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
  1112. ctx.textAlign = 'left';
  1113. // 添加下划线
  1114. let text = '',textWidth = 0;
  1115. if(item.displayType === 0 || item.displayType === 2){
  1116. text = item.displayName.toString();
  1117. textWidth = ctx.measureText(text).width;
  1118. }else{
  1119. textWidth = 2 * fontSize*this.zoomRate*this.scale;
  1120. }
  1121. const underlineY = obj.y + 50*this.zoomRate * this.scale ; // 可根据需要调整下划线位置
  1122. const startX = obj.x +35*this.zoomRate*this.scale;
  1123. ctx.beginPath();
  1124. ctx.moveTo(startX, underlineY);
  1125. ctx.lineTo(startX + textWidth, underlineY);
  1126. ctx.strokeStyle = ctx.fillStyle; // 使用与文字相同的颜色
  1127. ctx.lineWidth = 4;
  1128. ctx.stroke();
  1129. // 添加下划线2
  1130. ctx.beginPath();
  1131. ctx.moveTo(startX-20, underlineY+20*this.zoomRate*this.scale);
  1132. ctx.lineTo(startX + textWidth + 20, underlineY+20*this.zoomRate*this.scale);
  1133. ctx.strokeStyle = ctx.fillStyle; // 使用与文字相同的颜色
  1134. ctx.lineWidth = 4;
  1135. ctx.stroke();
  1136. if(item.displayType === 0 || item.displayType === 2){//0-分数 1-对错 2-等级
  1137. ctx.fillText(item.displayName,obj.x+35*this.zoomRate*this.scale,obj.y);
  1138. }else{
  1139. //判断是什么类型 scoreType 分数类型1:全对 2:半对 3: 全错
  1140. const iconX=obj.x +35*this.zoomRate*this.scale;
  1141. const iconY=obj.y-this.rtOrWrSize*fontSize*this.zoomRate*this.scale;
  1142. const iconWidth = this.rtOrWrSize * fontSize*this.zoomRate*this.scale;
  1143. const iconHeight = this.rtOrWrSize * fontSize*this.zoomRate*this.scale;
  1144. if(item.correctType==0){
  1145. //0 分全错
  1146. ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
  1147. }else if(item.correctType==2){
  1148. //满分全对
  1149. ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
  1150. }else{
  1151. //半对
  1152. ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
  1153. }
  1154. }
  1155. }
  1156. else
  1157. {
  1158. const fontSize = Math.max(12, 18 * this.scale); // 最小字体12px,基础字体18px
  1159. // ctx.font = '30px Arial';
  1160. ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
  1161. ctx.textAlign = 'left';
  1162. //判断是什么类型 scoreType 分数类型1:全对 2:半对 3: 全错
  1163. const iconX=obj.x-20*this.zoomRate*this.scale;
  1164. const iconY=obj.y-20*this.zoomRate*this.scale;
  1165. const iconWidth = 40*this.zoomRate*this.scale;
  1166. const iconHeight = 40*this.zoomRate*this.scale;
  1167. // console.log("打印item.score",item.score)
  1168. // displayType:res.data.displayType,//显示类型 0-分数 1-对错 2-等级
  1169. // displayName: res.data.displayName,//显示值
  1170. // correctType: res.data.correctType,//显示对错的时候 0-错 1-半对 2-全对
  1171. if(item.displayType === 2){
  1172. ctx.fillText(item.displayName,obj.x+35*this.zoomRate*this.scale,obj.y);
  1173. }else{
  1174. if(item.correctType===0){
  1175. //0 分全错
  1176. ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
  1177. }else if(item.correctType==2){
  1178. //满分 全对
  1179. ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
  1180. }else if(item.correctType==1){
  1181. //半对
  1182. ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
  1183. }else{
  1184. const displayName = isNaN(item.displayName)?0:Number(item.displayName);
  1185. if(displayName==0){
  1186. //0 分全错
  1187. ctx.drawImage(this.CacheAllWrong, iconX, iconY, iconWidth, iconHeight);
  1188. }else if(displayName==item.fullScore){
  1189. //满分全对
  1190. ctx.drawImage(this.CacheAllRight, iconX, iconY, iconWidth, iconHeight);
  1191. }else{
  1192. //半对
  1193. ctx.drawImage(this.CacheHalfRight, iconX, iconY, iconWidth, iconHeight);
  1194. }
  1195. }
  1196. if(item.displayType === 0){//0-分数 显示分数和对错
  1197. ctx.fillText(item.displayName,obj.x+35*this.zoomRate*this.scale,obj.y);
  1198. }
  1199. }
  1200. }
  1201. // ctx.fillText(item.displayName,obj.x+35*this.zoomRate*this.scale,obj.y);
  1202. // ctx.fillStyle = 'blue';
  1203. // ctx.font = `24px Arial`; // 让文字大小跟随缩放
  1204. // ctx.textAlign = 'center';
  1205. // ctx.textBaseline = 'middle';
  1206. // ctx.fillText(item.questionName,obj.x-65*this.zoomRate*this.scale,obj.y);
  1207. //绘制标注信息
  1208. if(item.drawLineData)
  1209. {
  1210. //有标注信息 加载标注 所有的坐标都要加上批阅块的初始坐标
  1211. console.log("打印point",point);
  1212. console.log("打印当前的页面",this.currentPage);
  1213. console.log("打印当前的批阅块数组",blockList);
  1214. const drawLineData=JSON.parse(item.drawLineData);
  1215. console.log("打印drawLineData",drawLineData);
  1216. // for(var j=0;j<drawLineData.length;j++)
  1217. // {
  1218. // var drawItem=drawLineData[j];
  1219. // console.log("打印drawItem",drawItem);
  1220. // // 扣分点 加分点 标记// 1:文字 (扣分留痕 显示扣分信息) 2:划线 3:波浪线 4:画笔 5:评语
  1221. // if(drawItem.drawType==1)
  1222. // {
  1223. // const fontSize = Math.max(12, 40 * this.zoomRate* this.scale); // 最小字体12px,基础字体16px
  1224. // ctx.font = `${fontSize}px Arial`; // 让文字大小跟随缩放
  1225. // // ctx.font = '30px Arial';
  1226. // ctx.fillStyle = '#D81E06';
  1227. // ctx.textAlign = 'center';
  1228. // ctx.textBaseline = 'middle';
  1229. // if(drawItem.type=='reduce')
  1230. // {
  1231. // ctx.fillText('-'+drawItem.score,(drawItem.x + blockPoint.x) * this.zoomRate * this.scale, (drawItem.y + blockPoint.y) * this.zoomRate * this.scale);
  1232. // }
  1233. // if(drawItem.type=='bonus')
  1234. // {
  1235. // ctx.fillText('+'+drawItem.score, (drawItem.x + blockPoint.x) * this.zoomRate * this.scale, (drawItem.y + blockPoint.y) * this.zoomRate * this.scale);
  1236. // }
  1237. // }
  1238. // else if(drawItem.drawType==2)
  1239. // {
  1240. // //绘制横线
  1241. // console.log("打印需要减去的高度",jHeight);
  1242. // jHeight=0;
  1243. // console.log("打印blockList",blockList);
  1244. // let drawX=this.GetInteger((drawItem.x+ blockPoint.x)* this.zoomRate * this.scale);
  1245. // let drawY=this.GetInteger((drawItem.y+blockPoint.y - jHeight)* this.zoomRate * this.scale);
  1246. // let drawEndX=this.GetInteger((drawItem.endX+ blockPoint.x)* this.zoomRate * this.scale);
  1247. // let drawEndY=this.GetInteger((drawItem.endY+blockPoint.y - jHeight)* this.zoomRate * this.scale);
  1248. // this.DrawHorizontalLine(drawX,drawY,drawEndX,drawEndY);
  1249. // // this.DrawHorizontalLine((drawItem.x+ blockPoint.x)* this.zoomRate * this.scale, (drawItem.y+blockPoint.y)* this.zoomRate * this.scale,(drawItem.endX+ blockPoint.x)* this.zoomRate * this.scale, (drawItem.endY+blockPoint.y)* this.zoomRate * this.scale);
  1250. // }
  1251. // else if(drawItem.drawType==3)
  1252. // {
  1253. // //绘制波浪线
  1254. // let startX=parseFloat(((drawItem.x + blockPoint.x)* this.zoomRate * this.scale).toFixed(2));
  1255. // let startY=parseFloat(((drawItem.y + blockPoint.y)* this.zoomRate * this.scale).toFixed(2));
  1256. // let endX=parseFloat(((drawItem.endX + blockPoint.x)* this.zoomRate * this.scale).toFixed(2));
  1257. // let endY=parseFloat(((drawItem.endY +blockPoint.y)* this.zoomRate * this.scale).toFixed(2));
  1258. // this.DrawWaveLine(startX, startY,endX, endY);
  1259. // }
  1260. // else if(drawItem.drawType==4)
  1261. // {
  1262. // //绘制画笔数据
  1263. // // console.log("打印画笔数据",drawItem);
  1264. // this.DrawPenLine(drawItem,blockPoint);
  1265. // }
  1266. // else if(drawItem.drawType==5)
  1267. // {
  1268. // //绘制评语
  1269. // let startX=parseFloat(((drawItem.x + blockPoint.x)* this.zoomRate * this.scale).toFixed(2));
  1270. // let startY=parseFloat(((drawItem.y + blockPoint.y)* this.zoomRate * this.scale).toFixed(2));
  1271. // // console.log("打印绘制评语数据",drawItem);
  1272. // this.DrawText(startX,startY,drawItem.text);
  1273. // }
  1274. // }
  1275. for(const [index, drawItem] of drawLineData.entries()) {
  1276. console.log("打印drawItem",drawItem);
  1277. // 计算通用的缩放因子,避免重复计算
  1278. const zoomScale = this.zoomRate * this.scale;
  1279. const offsetX = blockPoint.x * zoomScale;
  1280. const offsetY = blockPoint.y * zoomScale;
  1281. // 扣分点 加分点 标记// 1:文字 (扣分留痕 显示扣分信息) 2:划线 3:波浪线 4:画笔 5:评语
  1282. switch(drawItem.drawType) {
  1283. case 1: {
  1284. // 文字类型(扣分点/加分点标记)
  1285. const fontSize = Math.max(12, 40 * zoomScale);
  1286. ctx.font = `${fontSize}px Arial`;
  1287. ctx.fillStyle = '#D81E06';
  1288. ctx.textAlign = 'center';
  1289. ctx.textBaseline = 'middle';
  1290. const x = drawItem.x * zoomScale + offsetX;
  1291. const y = drawItem.y * zoomScale + offsetY;
  1292. if(drawItem.type==='reduce') {
  1293. ctx.fillText('-'+drawItem.score, x, y);
  1294. } else if(drawItem.type==='bonus') {
  1295. ctx.fillText('+'+drawItem.score, x, y);
  1296. }
  1297. break;
  1298. }
  1299. case 2: {
  1300. // 绘制横线
  1301. console.log("打印需要减去的高度",jHeight);
  1302. jHeight=0;
  1303. console.log("打印blockList",blockList);
  1304. const coords = {
  1305. x: this.GetInteger(drawItem.x * zoomScale + offsetX),
  1306. y: this.GetInteger(drawItem.y * zoomScale + offsetY - jHeight * zoomScale),
  1307. endX: this.GetInteger(drawItem.endX * zoomScale + offsetX),
  1308. endY: this.GetInteger(drawItem.endY * zoomScale + offsetY - jHeight * zoomScale)
  1309. };
  1310. this.DrawHorizontalLine(coords.x, coords.y, coords.endX, coords.endY);
  1311. break;
  1312. }
  1313. case 3: {
  1314. // 绘制波浪线
  1315. const coords = {
  1316. startX: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  1317. startY: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2)),
  1318. endX: parseFloat((drawItem.endX * zoomScale + offsetX).toFixed(2)),
  1319. endY: parseFloat((drawItem.endY * zoomScale + offsetY).toFixed(2))
  1320. };
  1321. this.DrawWaveLine(coords.startX, coords.startY, coords.endX, coords.endY);
  1322. break;
  1323. }
  1324. case 4:
  1325. // 绘制画笔数据
  1326. this.DrawPenLine(drawItem,blockPoint);
  1327. break;
  1328. case 5: {
  1329. // 绘制评语
  1330. const coords = {
  1331. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  1332. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  1333. };
  1334. this.DrawText(coords.x, coords.y, drawItem.text);
  1335. break;
  1336. }
  1337. case 6: {
  1338. // 绘制对号
  1339. const coords = {
  1340. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  1341. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  1342. };
  1343. this.DrawText(coords.x, coords.y, '✔');
  1344. break;
  1345. }
  1346. case 7: {
  1347. // 绘制错号
  1348. const coords = {
  1349. x: parseFloat((drawItem.x * zoomScale + offsetX).toFixed(2)),
  1350. y: parseFloat((drawItem.y * zoomScale + offsetY).toFixed(2))
  1351. };
  1352. this.DrawText(coords.x, coords.y, '✘');
  1353. break;
  1354. }
  1355. default:
  1356. console.warn('未知的绘图类型:', drawItem.drawType);
  1357. }
  1358. }
  1359. }
  1360. }
  1361. },
  1362. //转成整数
  1363. GetInteger(value)
  1364. {
  1365. // 如果值为空、undefined 或 null,返回 0
  1366. if (value === null || value === undefined || value === '') {
  1367. return 0;
  1368. }
  1369. // 如果已经是数字,直接处理
  1370. if (typeof value === 'number') {
  1371. // 检查是否为 NaN
  1372. if (isNaN(value)) {
  1373. return 0;
  1374. }
  1375. // 使用 Math.round 四舍五入取整
  1376. return Math.round(value);
  1377. }
  1378. // 如果是字符串,先尝试转换为数字
  1379. if (typeof value === 'string') {
  1380. // 去除首尾空格
  1381. value = value.trim();
  1382. // 如果是空字符串,返回 0
  1383. if (value === '') {
  1384. return 0;
  1385. }
  1386. // 转换为数字
  1387. const num = Number(value);
  1388. // 检查是否为有效数字
  1389. if (isNaN(num)) {
  1390. return 0;
  1391. }
  1392. // 四舍五入取整
  1393. return Math.round(num);
  1394. }
  1395. // 其他情况,尝试转换为数字后取整
  1396. const num = Number(value);
  1397. return isNaN(num) ? 0 : Math.round(num);
  1398. },
  1399. //画横线
  1400. DrawHorizontalLine(x1,y1,x2,y2,ctx = null)
  1401. {
  1402. console.log("画横线起始坐标点",x1, y1, x2, y2);
  1403. // 如果没有传入上下文,则使用当前canvas的上下文
  1404. const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
  1405. canvasContext.strokeStyle = 'red';//设置红色
  1406. canvasContext.lineWidth = 2;//线条宽度
  1407. canvasContext.beginPath();
  1408. canvasContext.moveTo(x1, y1);
  1409. canvasContext.lineTo(x2, y2);
  1410. canvasContext.stroke();
  1411. },
  1412. //画波浪线
  1413. DrawWaveLine(startX, startY, endX, endY,ctx = null)
  1414. {
  1415. // console.log("画波浪线起始坐标点",startX, startY, endX, endY);
  1416. const amplitude = 2; // 波浪的宽度
  1417. const frequency = 0.8; //振幅 越大越密集
  1418. //计算中间点
  1419. const dx=endX-startX;
  1420. const dy=endY-startY;
  1421. // 如果没有传入上下文,则使用当前canvas的上下文
  1422. const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
  1423. canvasContext.strokeStyle = 'red';//设置红色
  1424. canvasContext.lineWidth = 2;//线条宽度
  1425. // 清除之前的线条
  1426. // ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height);
  1427. canvasContext.beginPath();
  1428. canvasContext.moveTo(startX, startY);//绘制第一个点
  1429. //绘制波浪
  1430. // 绘制波浪线
  1431. for (let x = startX; x <= endX; x += 1) {
  1432. const y = startY + dy * (x - startX) / dx + amplitude * Math.sin(frequency * (x - startX));
  1433. canvasContext.lineTo(x, y);
  1434. }
  1435. // ctx.lineTo(endX, endY);//绘制最后一个点
  1436. // ctx.lineWidth = 2; // 设置线条宽度
  1437. canvasContext.stroke();
  1438. },
  1439. //绘制画笔数据
  1440. DrawPenLine(data,blockPoint,zoomRate,scale, ctx = null)
  1441. {
  1442. console.log("打印绘制画笔数据",data);
  1443. let linelist=data.drawlineData;
  1444. if(linelist.length>0)
  1445. {
  1446. // 如果没有传入上下文,则使用当前canvas的上下文
  1447. const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
  1448. canvasContext.strokeStyle = 'red';//设置红色
  1449. canvasContext.lineWidth = 2;//线条宽度
  1450. // 开始绘制
  1451. canvasContext.beginPath();
  1452. // 绘制路径
  1453. for (let i = 0; i < linelist.length; i++) {
  1454. if(blockPoint)
  1455. {
  1456. let x=parseFloat(((linelist[i].x + blockPoint.x)* zoomRate * scale).toFixed(2));
  1457. let y=parseFloat(((linelist[i].y + blockPoint.y)* zoomRate * scale).toFixed(2));
  1458. canvasContext.lineTo(x, y);
  1459. }
  1460. }
  1461. canvasContext.stroke();
  1462. canvasContext.closePath();
  1463. }
  1464. },
  1465. //绘制文字
  1466. DrawText(startX, startY, text , ctx = null)
  1467. {
  1468. // 如果没有传入上下文,则使用当前canvas的上下文
  1469. const canvasContext = ctx || this.$refs.paperCanvas.getContext('2d');
  1470. // 设置字体样式
  1471. canvasContext.font = '15px Arial';
  1472. canvasContext.fillStyle = 'red';
  1473. canvasContext.textAlign = 'left';
  1474. // ctx.textBaseline = 'middle';
  1475. // 绘制文字
  1476. canvasContext.fillText(text,startX, startY);
  1477. },
  1478. //下载图片绘制
  1479. DownloadDrawImage()
  1480. {
  1481. this.canvas=this.$refs.paperCanvas;
  1482. let ctx = this.canvas.getContext('2d');
  1483. ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height);//清除边框数据
  1484. ctx.drawImage(this.image, 0, 0,this.canvasInfo.width,this.canvasInfo.height);
  1485. this.DrawDataInfo();//加载边框数据
  1486. },
  1487. // 更新缩放率
  1488. updateZoomAndPaperInfo() {
  1489. // this.zoomRate = this.containerHeight / this.paperImgInfo.height;//
  1490. // 计算基于宽度和高度的缩放率
  1491. let widthZoomRate = 1;
  1492. let heightZoomRate = 1;
  1493. if(this.rotateDeg=='-90'){//旋转90度 学生报告册使用
  1494. widthZoomRate = this.containerWidth / this.paperImgInfo.height;
  1495. heightZoomRate = (this.containerHeight - 40) / this.paperImgInfo.width;//减掉报告册底边距40px
  1496. }else{
  1497. widthZoomRate = this.containerWidth / this.paperImgInfo.width;
  1498. heightZoomRate = this.containerHeight / this.paperImgInfo.height;
  1499. }
  1500. // 选择较小的缩放率作为基准,确保图像完整显示在容器内
  1501. this.zoomRate = Math.min(widthZoomRate, heightZoomRate);
  1502. // this.paperImgInfo = this.cardType ==1 ? { width: 1240, height: 1754 } : { width: 2480, height: 1754 };
  1503. // console.log("打印this.paperImgInfo",this.paperImgInfo);
  1504. },
  1505. // 更新画布尺寸
  1506. updateCanvasSize()
  1507. {
  1508. this.canvasInfo.width = Math.round(this.paperImgInfo.width * this.zoomRate * this.scale);
  1509. this.canvasInfo.height = Math.round(this.paperImgInfo.height * this.zoomRate * this.scale);
  1510. this.$refs.paperCanvas.width = this.canvasInfo.width;
  1511. this.$refs.paperCanvas.height = this.canvasInfo.height;
  1512. },
  1513. // 中心化画布
  1514. centerCanvas()
  1515. {
  1516. console.log("开始居中画布",this.containerWidth,this.containerHeight);
  1517. console.log("画布尺寸",this.canvasInfo.width,this.canvasInfo.height);
  1518. this.position.x = (this.containerWidth - this.canvasInfo.width) / 2;
  1519. if(this.rotateDeg=='-90'){//学生报告册使用
  1520. this.position.y = (this.containerHeight - this.canvasInfo.height) / 2 - 40;
  1521. }else{
  1522. this.position.y = (this.containerHeight - this.canvasInfo.height) / 2;
  1523. }
  1524. this.$refs.paperCanvas.style.left = `${this.position.x}px`;
  1525. this.$refs.paperCanvas.style.top = `${this.position.y}px`;
  1526. console.log("中心化画布打印画布位置",this.position.x,this.position.y);
  1527. this.isInit=false;//后面不在执行居中操作
  1528. },
  1529. // 全局鼠标释放事件处理
  1530. onGlobalMouseUp() {
  1531. this.isDragging = false;
  1532. },
  1533. // 鼠标按下事件
  1534. onMouseDown(event) {
  1535. // 只响应左键点击(button值为0表示左键)
  1536. if (event.button !== 0) {
  1537. // 如果是右键,确保释放拖拽状态
  1538. this.isDragging = false;
  1539. return;
  1540. }
  1541. console.log("鼠标按下事件",event);
  1542. // 确保在开始新的拖拽前,先重置拖拽状态
  1543. this.isDragging = false;
  1544. console.log("鼠标按下事件drawType",this.drawType);
  1545. if(this.drawType==0)
  1546. {
  1547. // 如果drawType==0,则切换到拖拽模式
  1548. console.log("打印是否可拖拽",this.isDrag);
  1549. if(this.isDrag)
  1550. {
  1551. console.log("开始拖拽");
  1552. console.log("打印开始拖拽的坐标",this.startX,this.startY);
  1553. console.log("打印鼠标位置",event.clientX,event.clientY);
  1554. console.log("打印画布位置",this.position.x,this.position.y);
  1555. //可拖动的时候才能拖动
  1556. this.isDragging = true;
  1557. this.startX = event.clientX - this.position.x;
  1558. this.startY = event.clientY - this.position.y;
  1559. console.log("打印开始拖拽的坐标",this.startX,this.startY);
  1560. }
  1561. }
  1562. if(this.drawType==1)
  1563. {
  1564. if(event.target.id!='paperCanvas')
  1565. {
  1566. this.isDragging = true;
  1567. this.startX = event.clientX - this.position.x;
  1568. this.startY = event.clientY - this.position.y;
  1569. }
  1570. }
  1571. },
  1572. // 鼠标移动事件
  1573. onMouseMove(event)
  1574. {
  1575. if (this.isDragging)
  1576. {
  1577. // 在拖拽模式下,移动画布
  1578. this.position.x = event.clientX - this.startX;
  1579. this.position.y = event.clientY - this.startY;
  1580. this.$refs.paperCanvas.style.left = `${this.position.x}px`;
  1581. this.$refs.paperCanvas.style.top = `${this.position.y}px`;
  1582. this.ImageInfoChange();
  1583. }
  1584. },
  1585. // 鼠标抬起事件
  1586. onMouseUp(event) {
  1587. // 只响应左键释放
  1588. if (event && event.button !== 0) {
  1589. return;
  1590. }
  1591. this.isDragging = false; // 停止拖拽
  1592. },
  1593. // // 鼠标滚轮事件
  1594. // onWheel(event) {
  1595. // event.preventDefault();
  1596. // const delta = event.deltaY < 0 ? 1 : -1;
  1597. // const newScale = this.scale + delta * 0.1;
  1598. // this.scale = Math.max(this.minScale, Math.min(this.maxScale, newScale));
  1599. // this.ImageInfoChange();
  1600. // this.updateCanvasSize();
  1601. // this.drawImage();
  1602. // },
  1603. // 鼠标滚轮事件
  1604. onWheel(event) {
  1605. if(!this.isWheel) return false
  1606. event.preventDefault();
  1607. // 计算新的缩放比例
  1608. const delta = event.deltaY < 0 ? 1 : -1;
  1609. const newScale = this.scale + delta * 0.1;
  1610. const clampedScale = Math.max(this.minScale, Math.min(this.maxScale, newScale));
  1611. // 如果缩放值没有变化,则直接返回
  1612. if (clampedScale === this.scale) return;
  1613. // 获取鼠标在容器中的位置
  1614. const containerRect = this.$refs.paperContainer.getBoundingClientRect();
  1615. const mouseX = event.clientX - containerRect.left;
  1616. const mouseY = event.clientY - containerRect.top;
  1617. // 计算鼠标相对于当前画布的位置
  1618. const mouseRelativeToCanvasX = (mouseX - this.position.x) / this.scale;
  1619. const mouseRelativeToCanvasY = (mouseY - this.position.y) / this.scale;
  1620. // 更新缩放比例
  1621. this.scale = clampedScale;
  1622. // 更新画布尺寸
  1623. this.updateCanvasSize();
  1624. // 重新计算画布位置,使缩放围绕鼠标点进行
  1625. this.position.x = mouseX - mouseRelativeToCanvasX * this.scale;
  1626. this.position.y = mouseY - mouseRelativeToCanvasY * this.scale;
  1627. // 应用新的位置
  1628. this.$refs.paperCanvas.style.left = `${this.position.x}px`;
  1629. this.$refs.paperCanvas.style.top = `${this.position.y}px`;
  1630. // 更新图片位置信息
  1631. this.ImageInfoChange();
  1632. // 重新绘制内容
  1633. this.drawImage();
  1634. },
  1635. //鼠标复位
  1636. MouseReset()
  1637. {
  1638. this.drawType=0;
  1639. this.canvas=this.$refs.paperCanvas;
  1640. this.canvas.style.cursor="pointer";
  1641. this.canvas.onmousedown=null;
  1642. this.canvas.onmousemove=null;
  1643. this.canvas.onmouseup=null;
  1644. },
  1645. //框选答案区域
  1646. SelectionBox()
  1647. {
  1648. console.log("开始框选答案区域");
  1649. this.drawType=1;
  1650. this.canvas=this.$refs.paperCanvas;
  1651. this.canvas.style.cursor="crosshair";
  1652. this.canvas.onmousedown=this.onCanvasDown;
  1653. this.canvas.onmousemove=this.onCanvasMove;
  1654. this.canvas.onmouseup=this.onCanvasUp;
  1655. },
  1656. //画圈模式
  1657. PaintingCircle()
  1658. {
  1659. console.log("进入画圈模式");
  1660. this.drawType=1;
  1661. this.canvas=this.$refs.paperCanvas;
  1662. this.canvas.style.cursor="crosshair";
  1663. this.canvas.onmousedown=this.onCanvasDown;
  1664. this.canvas.onmousemove=this.onCanvasMove;
  1665. this.canvas.onmouseup=this.onCanvasUp;
  1666. },
  1667. //开始画答案区域圈
  1668. StartPaintingCircle(obj)
  1669. {
  1670. this.addObjectAreaOption=obj;
  1671. this.drawType=1;
  1672. this.canvas=this.$refs.paperCanvas;
  1673. this.canvas.style.cursor="crosshair";
  1674. this.canvas.onmousedown=this.onCanvasDown;
  1675. this.canvas.onmousemove=this.onCanvasMove;
  1676. this.canvas.onmouseup=this.onCanvasUp;
  1677. },
  1678. //canvas上按下事件
  1679. onCanvasDown(e)
  1680. {
  1681. this.canvas.style.cursor="crosshair";
  1682. this.isDrawing=true;
  1683. this.isDragging = false; // 禁止拖拽
  1684. this.rectPoint={
  1685. startX:e.offsetX,
  1686. startY:e.offsetY,
  1687. endX:e.offsetX,
  1688. endY:e.offsetY
  1689. };
  1690. },
  1691. //canvas上移动事件
  1692. onCanvasMove(e)
  1693. {
  1694. // this.drawType=1;
  1695. // this.isDragging=true;
  1696. if(this.isDrawing)
  1697. {
  1698. // const canvas = this.$refs.paperCanvas;
  1699. // const ctx = canvas.getContext('2d');
  1700. // // 计算矩形的宽度和高度
  1701. // const width = e.offsetX - this.rectPoint.startX;
  1702. // const height = e.offsetY - this.rectPoint.startY;
  1703. // this.rectPoint.endX=e.offsetX;
  1704. // this.rectPoint.endY=e.offsetY;
  1705. // // 清除之前的矩形
  1706. // ctx.clearRect(0, 0, canvas.width, canvas.height);
  1707. // // 重新绘制之前的边框数据
  1708. // this.drawImage();
  1709. // // 设置矩形样式
  1710. // ctx.strokeStyle = 'blue';
  1711. // ctx.lineWidth = 1;//画框的线的粗细
  1712. // // 绘制矩形
  1713. // ctx.strokeRect(this.rectPoint.startX, this.rectPoint.startY, width, height);
  1714. const canvas = this.$refs.paperCanvas;
  1715. const ctx = canvas.getContext('2d');
  1716. // 更新当前鼠标位置
  1717. this.rectPoint.endX = e.offsetX;
  1718. this.rectPoint.endY = e.offsetY;
  1719. // 清除之前的矩形
  1720. ctx.clearRect(0, 0, canvas.width, canvas.height);
  1721. // 重新绘制之前的边框数据
  1722. this.drawImage();
  1723. // 设置矩形样式
  1724. ctx.strokeStyle = 'blue';
  1725. ctx.lineWidth = 1;//画框的线的粗细
  1726. // 计算矩形的起点和宽高,处理反向框选
  1727. const startX = this.rectPoint.startX;
  1728. const startY = this.rectPoint.startY;
  1729. const width = e.offsetX - startX;
  1730. const height = e.offsetY - startY;
  1731. // 绘制矩形(可以处理负宽度和高度)
  1732. ctx.strokeRect(startX, startY, width, height);
  1733. }
  1734. },
  1735. //canvas抬起事件
  1736. onCanvasUp()
  1737. {
  1738. this.isDrawing = false;
  1739. // console.log("鼠标抬起事件,画圈完成,打印四角坐标",this.rectPoint);
  1740. // let point={
  1741. // x:this.$global.floatNum((this.rectPoint.startX)/this.zoomRate/this.scale),
  1742. // y:this.$global.floatNum(this.rectPoint.startY/this.zoomRate/this.scale),
  1743. // w:this.$global.floatNum((this.rectPoint.endX-this.rectPoint.startX)/this.zoomRate/this.scale),
  1744. // h:this.$global.floatNum((this.rectPoint.endY-this.rectPoint.startY)/this.zoomRate/this.scale),
  1745. // unit:'px',
  1746. // };//坐标还原成px单位
  1747. // this.currenPoint=point;
  1748. // console.log("打印当前坐标",point);
  1749. // if(point.w>0 && point.h>0)
  1750. // {
  1751. // console.log("打印当前坐标",point);
  1752. // this.$emit("GetRectPoint",point);
  1753. // }
  1754. // 处理反向框选,确保宽度和高度为正数
  1755. let startX = this.rectPoint.startX;
  1756. let startY = this.rectPoint.startY;
  1757. let endX = this.rectPoint.endX;
  1758. let endY = this.rectPoint.endY;
  1759. // 确保起点坐标是左上角,终点坐标是右下角
  1760. let actualStartX = Math.min(startX, endX);
  1761. let actualStartY = Math.min(startY, endY);
  1762. let actualEndX = Math.max(startX, endX);
  1763. let actualEndY = Math.max(startY, endY);
  1764. // 计算实际的宽度和高度(确保为正数)
  1765. let width = Math.abs(actualEndX - actualStartX);
  1766. let height = Math.abs(actualEndY - actualStartY);
  1767. let point = {
  1768. x: this.$global.floatNum(actualStartX / this.zoomRate / this.scale),
  1769. y: this.$global.floatNum(actualStartY / this.zoomRate / this.scale),
  1770. w: this.$global.floatNum(width / this.zoomRate / this.scale),
  1771. h: this.$global.floatNum(height / this.zoomRate / this.scale),
  1772. unit: 'px',
  1773. };//坐标还原成px单位
  1774. this.currenPoint = point;
  1775. console.log("打印当前坐标", point);
  1776. // 只有当宽度和高度都大于0时才触发事件
  1777. if (point.w > 0 && point.h > 0) {
  1778. console.log("打印当前坐标", point);
  1779. this.$emit("GetRectPoint", point);
  1780. }
  1781. },
  1782. //鼠标离开画布事件
  1783. onCanvasLeave()
  1784. {
  1785. console.log("鼠标离开画布事件");
  1786. // 如果正在绘制,自动结束绘制
  1787. if (this.isDrawing) {
  1788. this.onCanvasUp();
  1789. }
  1790. },
  1791. // 监听窗口大小变化,重新计算表格高度 使用节流防止频繁改变窗口大小导致计算量过大而页面卡顿
  1792. handleResize: throttle(function() {
  1793. // //窗口变化重新计算canvas的宽高
  1794. // let {width,height} = this.$refs.paperContainer.getBoundingClientRect();
  1795. // this.updateZoomAndPaperInfo(height);
  1796. // this.updateCanvasSize(width, height);
  1797. // this.centerCanvas();
  1798. // this.loadImage();
  1799. }, 500), // 节流 500 毫秒内最多执行一次
  1800. }
  1801. }
  1802. </script>
  1803. <style lang="scss" scoped>
  1804. .paper_container
  1805. {
  1806. width: 100%;
  1807. height: 100%;
  1808. overflow: hidden;
  1809. position: relative;
  1810. .paper_canvas
  1811. {
  1812. position: absolute;
  1813. cursor: pointer;
  1814. background-color: transparent;
  1815. z-index: 10;//使canvas在图片上方
  1816. }
  1817. .img_container
  1818. {
  1819. position: absolute;
  1820. cursor: pointer;
  1821. z-index: 9;
  1822. display: flex;
  1823. flex-direction: column;
  1824. img
  1825. {
  1826. width: 100%;
  1827. height: 100%;
  1828. }
  1829. }
  1830. //自定义右键菜单层样式
  1831. .custom_context_menu
  1832. {
  1833. position: absolute;
  1834. background: white;
  1835. border: 1px solid #ccc;
  1836. border-radius: 4px;
  1837. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  1838. z-index: 9999;
  1839. min-width: 120px;
  1840. .menu_item
  1841. {
  1842. padding: 8px 16px;
  1843. cursor: pointer;
  1844. font-size: 14px;
  1845. &:hover {
  1846. background-color: #f5f5f5;
  1847. }
  1848. &:not(:last-child) {
  1849. border-bottom: 1px solid #eee;
  1850. }
  1851. }
  1852. }
  1853. }
  1854. .no_paper_url {
  1855. background-image: url("../assets/bg/no_content_bg.png");
  1856. background-repeat: no-repeat;
  1857. background-size: 360px auto;
  1858. background-position: 50% 30%;
  1859. color: #666666;
  1860. width: 100%;
  1861. height: 100%;
  1862. justify-content: center;
  1863. display: flex;
  1864. align-items: center;
  1865. }
  1866. </style>