2026/5/23 22:45:05
网站建设
项目流程
吉林省建设监理检测网站,网站建设 可以吗,北京市建设工程信息网中标公告,本地好的app开发公司直接将以下代码保存为.html文件#xff0c;双击即可在浏览器运行#xff0c;无需依赖 Vue 工程、打包工具#xff0c;内置 CDN 引入 Vue3#xff0c;保留所有核心功能#xff1a;标准圆展示、手绘捕捉、完整度评分、跨端兼容、重置功能。
!DOCTYPE html
html …直接将以下代码保存为.html文件双击即可在浏览器运行无需依赖 Vue 工程、打包工具内置 CDN 引入 Vue3保留所有核心功能标准圆展示、手绘捕捉、完整度评分、跨端兼容、重置功能。!DOCTYPEhtmlhtml langzh-CNheadmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, initial-scale1.0title画圈完整度检测/title!--引入Vue3CDN--script srchttps://unpkg.com/vue3/dist/vue.global.prod.js/scriptstyle*{margin:0;padding:0;box-sizing:border-box;font-family:Microsoft YaHei,sans-serif;}.circle-check-container{width:100%;max-width:400px;margin:40px auto;text-align:center;}.circle-check-container h3{color:#333;margin-bottom:15px;}.tip{color:#666;font-size:14px;margin:10px0;line-height:1.5;}.score{font-size:16px;font-weight:600;color:#333;margin:15px0;}.score span{color:#f53f3f;font-size:18px;margin-left:4px;}.draw-canvas{border:1px solid #ddd;border-radius:4px;cursor:crosshair;margin:10px0;}.reset-btn{padding:8px 24px;background:#165dff;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;transition:background0.2s;}.reset-btn:hover{background:#0e4bdb;}/* 移动端适配 */media(max-width:420px){.circle-check-container{padding:015px;}.draw-canvas{width:100%;}}/style/headbodydiv idappdivclasscircle-check-containerh3画圈完整度检测/h3pclasstip请在画布内沿标准圆手绘圆圈松开鼠标/手指自动检测/pdivclassscorev-ifscore -1画圈完整度span{{score}}/span分/divcanvas refcanvasRefclassdraw-canvaswidth400height400mousedownstartDrawmousemovedrawingmouseupendDrawmouseleaveendDrawtouchstarthandleTouchStarttouchmovehandleTouchMovetouchendhandleTouchEnd/canvasbuttonclassreset-btnclickresetDraw重新绘制/button/div/divscriptconst{createApp,ref,onMounted,onUnmounted}Vue;createApp({setup(){// 画布引用constcanvasRefref(null);letctxnull;constcanvasWidth400;constcanvasHeight400;// 标准圆参数conststandardCircle{x:canvasWidth/2,y:canvasHeight/2,r:150,color:#e5e7eb,lineWidth:2};// 手绘轨迹数据constdrawData{isDrawing:false,points:[],color:#165dff,lineWidth:3};// 评分相关constscoreref(-1);constscoreWeights{shapeFit:0.5,closeDegree:0.3,coverDegree:0.2};// 初始化画布onMounted((){if(!canvasRef.value)return;ctxcanvasRef.value.getContext(2d);drawStandardCircle();});// 销毁清空onUnmounted((){drawData.points[];score.value-1;});// 绘制标准圆functiondrawStandardCircle(){ctx.clearRect(0,0,canvasWidth,canvasHeight);ctx.beginPath();ctx.arc(standardCircle.x,standardCircle.y,standardCircle.r,0,2*Math.PI);ctx.strokeStylestandardCircle.color;ctx.lineWidthstandardCircle.lineWidth;ctx.stroke();ctx.closePath();}// PC端开始绘制functionstartDraw(e){drawData.isDrawingtrue;drawData.points[];score.value-1;const{x,y}getCanvasXY(e);drawData.points.push({x,y});}// PC端绘制中functiondrawing(e){if(!drawData.isDrawing)return;const{x,y}getCanvasXY(e);drawData.points.push({x,y});drawTrack();}// PC端结束绘制functionendDraw(){if(!drawData.isDrawing||drawData.points.length10){drawData.isDrawingfalse;drawStandardCircle();return;}drawData.isDrawingfalse;calculateCompleteScore();}// 移动端触摸适配functionhandleTouchStart(e){e.preventDefault();consttouche.touches[0];startDraw(touch);}functionhandleTouchMove(e){e.preventDefault();consttouche.touches[0];drawing(touch);}functionhandleTouchEnd(e){e.preventDefault();endDraw();}// 获取Canvas内真实坐标functiongetCanvasXY(e){constrectcanvasRef.value.getBoundingClientRect();return{x:e.clientX-rect.left,y:e.clientY-rect.top};}// 绘制手绘轨迹functiondrawTrack(){drawStandardCircle();if(drawData.points.length2)return;ctx.beginPath();ctx.moveTo(drawData.points[0].x,drawData.points[0].y);drawData.points.forEach((point,index){if(index0)ctx.lineTo(point.x,point.y);});ctx.strokeStyledrawData.color;ctx.lineWidthdrawData.lineWidth;ctx.lineCapround;ctx.lineJoinround;ctx.stroke();ctx.closePath();}// 计算完整度评分functioncalculateCompleteScore(){const{points}drawData;const{x:cx,y:cy,r:cr}standardCircle;// 1. 形状贴合度letdistanceSum0;points.forEach(({x,y}){constdisMath.sqrt(Math.pow(x-cx,2)Math.pow(y-cy,2));distanceSum1-Math.abs(dis-cr)/cr;});constshapeFitMath.max(0,distanceSum/points.length);// 2. 闭合度conststartpoints[0];constendpoints[points.length-1];constcloseDisMath.sqrt(Math.pow(start.x-end.x,2)Math.pow(start.y-end.y,2));constcloseThreshold2*Math.PI*cr*0.05;constcloseDegreecloseDiscloseThreshold?0:1-closeDis/closeThreshold;// 3. 轨迹覆盖度constangles[];points.forEach(({x,y}){letangleMath.atan2(y-cy,x-cx)*(180/Math.PI);if(angle0)angle360;angles.push(angle);});angles.sort((a,b)a-b);letmaxGap0;constangleCountangles.length;for(leti1;iangleCount;i){constgapangles[i]-angles[i-1];maxGapMath.max(maxGap,gap);}constlastGap(360angles[0])-angles[angleCount-1];maxGapMath.max(maxGap,lastGap);constcoverDegree1-maxGap/360;// 加权计算最终评分consttotalScore(shapeFit*scoreWeights.shapeFitcloseDegree*scoreWeights.closeDegreecoverDegree*scoreWeights.coverDegree)*100;score.valueMath.round(Math.max(0,Math.min(100,totalScore)));}// 重置绘制functionresetDraw(){drawData.isDrawingfalse;drawData.points[];score.value-1;drawStandardCircle();}return{canvasRef,score,startDraw,drawing,endDraw,handleTouchStart,handleTouchMove,handleTouchEnd,resetDraw};}}).mount(#app);/script/body/html核心使用说明运行方式将代码保存为circle-check.html直接用浏览器打开即可无需任何额外配置操作流程鼠标 / 手指按下画布开始画圈 → 松开后自动计算完整度0-100 分→ 点击「重新绘制」可清空轨迹重新检测跨端支持完美兼容 PC 端鼠标操作和移动端触摸操作移动端会阻止默认滚动保证绘制体验无效绘制判定手绘轨迹点少于 10 个时会判定为误触自动清空轨迹不进行评分。可快速调整的参数直接在代码中修改标准圆修改standardCircle中的r半径、color颜色可调整标准圆大小和样式评分权重修改scoreWeights中的数值可调整「形状贴合度、闭合度、覆盖度」的评分占比总和建议为 1手绘样式修改drawData中的color轨迹颜色、lineWidth线宽可调整手绘轨迹的视觉效果画布大小直接修改 canvas 标签的width/height标准圆会自动居中适配。关键核心原理分模块详解下面拆解最核心的4 个模块这是整个功能的底层逻辑也是可按需调整的关键部分模块 1Canvas 坐标捕捉与轨迹绘制这是整个功能的基础核心是获取鼠标 / 触摸点在 Canvas 内的真实坐标并将连续的坐标点连接成轨迹。坐标修正鼠标 / 触摸的clientX/clientY是相对于浏览器视口的坐标而 Canvas 有自己的独立坐标系因此需要通过getBoundingClientRect()获取 Canvas 的视口位置用视口坐标 - Canvas视口偏移量得到Canvas 内的真实坐标避免轨迹绘制偏移轨迹绘制连续的坐标点形成数组后通过 Canvas 的 2D 上下文 API 绘制moveTo()定位起点 → lineTo()依次连接后续点 → stroke()描边形成轨迹视觉优化设置lineCap: round线条端点圆润、lineJoin: round线条拐角圆润让手绘轨迹更符合实际画画的视觉效果实时绘制时先重绘标准圆避免轨迹重叠导致的模糊。模块 2形状贴合度计算核心评价「圆不圆」目的是判断用户画的轨迹每个点到标准圆圆心的距离是否接近标准圆的半径越接近则「越圆」分值越高0-1。计算单一点的偏差对每个手绘坐标点用欧几里得距离公式计算点到标准圆圆心的实际距离x,y 是手绘点坐标cx,cy 是标准圆圆心坐标计算单一点的贴合值用1 - |实际距离 - 标准半径| / 标准半径得到该点的贴合值偏差越小值越接近 1计算平均贴合度将所有点的贴合值求和后除以点的总数得到整体形状贴合度同时做边界处理Math.max(0, …)避免出现负数比如点离圆心过远时。模块 3闭合度计算核心评价「封没封口」目的是判断用户画的轨迹起点和终点是否重合 / 接近越近则闭合度越高分值越高0-1。计算起点终点距离同样用欧几里得距离公式计算轨迹第一个点和最后一个点的直线距离设置闭合阈值以 ** 标准圆周长的 5%** 为阈值可调整如果起点终点距离超过这个阈值判定为「未封口」闭合度直接为 0计算有效闭合度如果距离小于阈值用1 - 起点终点距离 / 阈值计算闭合度距离越近值越接近 1。模块 4轨迹覆盖度计算核心评价「画没画全一圈」目的是判断用户画的轨迹是否覆盖了标准圆的 360°避免只画半圈、1/4 圈却被判定为高完整度覆盖越全面分值越高0-1。这是三个维度中稍复杂的一个核心是将直角坐标转换为极角分析角度的分布间隔直角坐标转极角对每个手绘点用Math.atan2(y - cy, x - cx)计算该点相对于标准圆圆心的极角弧度再转换为0-360° 的角度负数角度加 360°比如 - 10° 转为 350°角度排序将所有点的角度按从小到大排序方便计算相邻角度的间隔计算最大角度间隔遍历排序后的角度计算每两个相邻角度的差值间隔同时处理 360° 闭环比如最后一个角度 350° 和第一个角度 10°间隔是 20°而非 - 340°找到所有间隔中最大的那个计算覆盖度用1 - 最大角度间隔 / 360°得到覆盖度最大间隔越小角度分布越均匀覆盖度越接近 1比如 360° 全覆盖时最大间隔趋近于 0覆盖度≈1只画半圈时最大间隔≈180°覆盖度 0.5。三、最终评分的加权计算原理三个维度的计算结果都是0-1 的量化值最终需要转换为 0-100 分的完整度核心是加权求和设置维度权重根据业务需求给三个维度分配不同的权重比如形状贴合度 50%、闭合度 30%、覆盖度 20%权重总和建议为 1方便计算加权求和(形状贴合度×形状权重) (闭合度×闭合权重) (覆盖度×覆盖权重)得到 0-1 的总量化值分数转换与边界限制总量化值 ×100转换为 0-100 分同时用Math.max(0, Math.min(100, …))限制分数范围避免出现负分或超过 100 的分数最后四舍五入为整数得到最终完整度评分。