学习笔记更

1、echarts官网汇总

PPChart - 让图表更简单

series-radar雷达图 - makeapie echarts社区图表可视化案例

https://madeapie.com/#/

echarts图表集

2、echarts官方文档

makeapie - ECharts文档 - echarts社区

3、js 比较时间前后 js 时间转时间戳

        3.1、比较时间前后

                js时间比较的几个常用方法_js 时间比较-优快云博客

        3.2、转时间戳

               js日期格式化yyyy-MM-dd_js日期格式化yyyymmdd-优快云博客

4、视频标签禁止下载

防止video视频被下载的几种处理办法_video 禁止下载-优快云博客

5、获取elementUI日期间隔天数

https://www.cnblogs.com/yingyigongzi/p/13718448.html

https://www.cnblogs.com/shuihanxiao/p/15386444.html

6、获取关系图中节点位置

echarts 树图 获取各节点位置_echarts获取节点位置-优快云博客

7、echarts重新渲染

vue echarts重新渲染-掘金

8、elementUI表单校验

vue中element ui Form表单自定义校验_el-form自定义校验规则-优快云博客

9、修改关系图节点形状

Echarts绘制关系图(一) - 简书

10、获取数组包含的索引

getAllIndex(str, substr) {
console.log(str);
let indices = [];
let index = str.indexOf(substr);
while (index !== -1) {
indices.push(index);
index = str.indexOf(substr, index + 1);
}return indices;
},

11、深拷贝

deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
//判断ojb子元素是否为对象,如果是,递归复制
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = this.deepClone(obj[key]);
}else {
//如果不是,简单复制
objClone[key] = obj[key];
}}}}return objClone;
},

12、关键字变色

// 截取字符串
innerForChange(str,temArr){
let lastStr = "";
console.log(temArr,"传的数组长度");
if(temArr.length>1){
for(var index=0;index<temArr.length;index++){
console.log(this.getAllIndex(str.slice(temArr[index]+this.input2.length),this.input2),"剩余字符串11111111111111");
if(this.getAllIndex(str.slice(temArr[index]+this.input2.length),this.input2).length>0){
lastStr += 
str.slice(0,temArr[index])
+"<span style='color:red;'>"
+str.slice(temArr[index],temArr[index]+this.input2.length)
+"</span>"
+this.innerForChange(str.slice(temArr[index]+this.input2.length),this.getAllIndex(str.slice(temArr[index]+this.input2.length),this.input2));
console.log(lastStr);
}else{
lastStr = 
lastStr;
}}}else if(temArr.length===1){
lastStr=str.slice(0,temArr[0])+"<span style='color:red;'>"+str.slice(temArr[0],temArr[0]+this.input2.length)+"</span>"+str.slice(temArr[0]+this.input2.length);
}else{
console.log(str,temArr,"结束了");
}console.log(lastStr);
return lastStr
},
//改变关键字颜色后拼接
changeTextColor() {
this.dataList = this.deepClone(this.dataList1);
if(this.input2.length>0){
for (var i = 0; i < this.dataList.length; i++) {
this.dataList[i].describ=this.dataList[i].describ.length>70?(this.dataList[i].describ.substring(0,70)+'...'):this.dataList[i].describ;
let tempIndexArr = this.getAllIndex(this.dataList[i].name, this.input2);
let tempIndexArr1 = this.getAllIndex((this.dataList[i].describ.length>70)?(this.dataList[i].describ.substring(0,70)+'...'):(this.dataList[i].describ), this.input2);
if(tempIndexArr.length>0){
this.dataList[i].name= this.innerForChange(this.dataList[i].name,tempIndexArr);
}if(tempIndexArr1.length>0){
this.dataList[i].describ= this.innerForChange(this.dataList[i].describ.length>10?(this.dataList[i].describ.substring(0,70)+'...'):this.dataList[i].describ,tempIndexArr1)
}}}},

13、关键字变红 严格模式

var nWord = "${guanj}"; //获取el表达式冲文本框输入的关键字
    var array = nWord.split(""); //分割
    var dsa=document.getElementsByName("dsas");//获取全部商品
    for ( var t = 0; t < dsa.length; t++) {
      for ( var i = 0; i < array.length; i++) {
        //创建表达式,匹配替换
        var reg = new RegExp("(" + array[i].replace(/,/, "|") + ")", "g");
        //替换重新写入商品名, 匹配结果中对应的分组匹配结果
        dsa[t].innerHTML =dsa[t].innerHTML.replace(reg,"<font color='red'>$1</font>");
      }    
    }

14、文本溢出显示省略号

文本溢出显示省略号
overflow: hidden;//(文字长度超出限定宽度,则隐藏超出的内容)
white-space: nowrap;//(设置文字在一行显示,不能换行)
text-overflow: ellipsis;//(规定当文本溢出时,显示省略符号来代表被修剪的文本)

多行超出
text-overflow: -o-ellipsis-lastline;
overflow: hidden; //溢出内容隐藏
text-overflow: ellipsis; //文本溢出部分用省略号表示
display: -webkit-box; //特别显示模式
-webkit-line-clamp: 2; //行数
line-clamp: 2; 
-webkit-box-orient: vertical; //盒子中内容竖直排列

15、禁止浏览器回退

前端 js 代码如何禁止浏览器的回退事件? - 知乎

16、echarts图表文字过长

随笔39-echarts文字label过长显示省略号 - 百度文库

17、elementUI表格超出缩写

.el-tooltip__popper {
text-align: left;
max-width: 660px;
line-height: 30px;
}

18、echarts进度条

echarts进度条文字

element 圆形circle进度条 内置文字换行_el-progress 文字换行-优快云博客

echarts 环形进度条渐变色

elementUI环形进度条设置渐变色和修改底色_element进度条渐变色-优快云博客

关系图添加点击事件

Echarts - 图表绑定事件(事件处理)_echarts 绑定事件-优快云博客

19、图片添加注释

img src属性变为base64 并重新渲染img 使用v-if

v3 

完整代码

第二张图缺少转base64步骤 

<template>
  <div class="contain">
    <div class="topBox">考试阅卷</div>
    <!-- <div class="topBox1">提示:若添加注释,请先保存注释!</div> -->
    <div class="contBox">
      <div class="btnImgBox">
        <div class="btnBox">
          <div class="btn" @click="seeImgBox">看原卷</div>
          <!-- <div class="btn" @click="seeAnswerBox">看答案</div> -->
        </div>
        <div class="imgBox" v-if="hasImg">
          <div class="canvas_box">
            <div class="canvas_container">
              <div class="canvas_wrapper">
                <img
                  @load="init"
                  :src="imgUrl"
                  id="img"
                  ref="img"
                  style="
                    position: absolute;
                    top: 40px;
                    left: 0;
                    opacity: 0;
                    z-index: -1;
                  "
                />
                <canvas
                  style="box-shadow: 0px 0px 5px #ccc; margin-top: -50px"
                  id="canvas"
                  :width="width"
                  :height="height"
                ></canvas>
              </div>
            </div>
          </div>
        </div>
        <!-- <div  class="answerText" v-else>
            <div class="anText">答案:{{  }}</div>
          </div> -->
      </div>
      <div class="inputBox">
        <div class="title">第1题</div>
        <div class="inputBox1">
          <el-form ref="form" label-width="90px">
            <el-form-item label="文本颜色:">
              <el-color-picker
                @change="onChangeColor"
                class="mt-15"
              ></el-color-picker>
            </el-form-item>
            <el-form-item label="删除注释:">
              <el-button
                class="mt-15"
                style="width: 100%"
                type="danger"
                :icon="CloseBold"
                @click="onDel"
              />
            </el-form-item>
            <el-form-item label="该题得分:">
              <el-input v-model="scoreNum"></el-input>
            </el-form-item>
          </el-form>
        </div>
        <div class="pageNation">
          <el-pagination
            :page-size="1"
            @current-change="handleCurrentChange"
            layout="prev, pager, next"
            :total="total"
          />
        </div>
        <div class="btnBox1">
          <div class="btn" @click="save">保存</div>
        </div>
      </div>
    </div>
  </div>
</template>
  <script setup name="testInfo">
import { CloseBold } from "@element-plus/icons-vue";
import { getCurrentInstance, onBeforeMount, onMounted } from "vue";
import { upScoreData } from "@/api/upScore/index";
import { fabric } from "fabric";
const { proxy } = getCurrentInstance();
const baseURL = import.meta.env.VITE_APP_BASE_API;
const imgUrl = ref("");
const width = ref(0);
const height = ref(0);
const hasImg = ref(false);
const props = defineProps({
  testCheckList: {
    type: Array,
  },
});
const activeIndex = ref(null);
const isSelect = ref(false);
const ctxArr = ref({});
const operation = ref("text");
const color = ref("#ff0000");
const scoreNum = ref(0);
const total = ref(0);
const count = ref(0);
const canvas = ref();
const testListId=ref("");
const seeImg = ref(true);
const seeImgBox = () => {
  seeImg.value = true;
};
const seeAnswerBox = () => {
  seeImg.value = false;
};
const handleCurrentChange = (val) => {
  hasImg.value = false;
  proxy.$nextTick(() => {
    if (!hasImg.value) {
      hasImg.value = true;
      imgUrl.value = baseURL + props.testCheckList[val - 1].path;
      testListId.value=props.testCheckList[val - 1].id;
      getBase64(imgUrl.value);
    }
  });
};
const init = () => {
  width.value = proxy.$refs.img.offsetWidth; // 宽
  height.value = proxy.$refs.img.offsetHeight; // 高
  proxy.$nextTick(() => {
    canvas.value = new fabric.Canvas("canvas");
    canvas.value.width = width.value;
    canvas.value.height = height.value;
    const imgInstance = addOriginImage1(imgUrl.value, canvas.value);
    imgInstance.set("selectable", false);
    onMouseDown(canvas.value);
  });
};
// 添加背景图v2
const addOriginImage = (canvas) => {
  const imgInstance = new fabric.Image(proxy.$refs.img, {
    left: 0,
    top: 0,
    width: 300,
    height: 300,
    crossOrigin: "Anonymous", // 跨域
  });
  proxy.$refs.img.setAttribute("crossOrigin", "Anonymous");
  canvas.add(imgInstance);
  return imgInstance;
};
const onMouseDown = (canvas) => {
  canvas.on("mouse:down", (opt) => {
    const pos = opt.absolutePointer;
    const isText = () => {
      if (activeIndex.value === null) {
        addText(canvas, color.value, pos);
      } else {
        const o = canvas.getActiveObject();
        if (!o) {
          activeIndex.value = null;
        }
      }
    };
    switch (operation.value) {
      case "text":
        isText();
        break;
      default:
        isText();
    }
  });
};
const addOriginImage1 = (url, canvas) => {
  var canvasImage;
  var imageUrl = new Image();
  canvasImage = new fabric.Image(imageUrl);
  canvasImage.left = 0;
  canvasImage.top = 0;
  canvasImage.width = width.value;
  canvasImage.height = height.value;
  canvasImage.crossOrigin = "Anonymous";
  canvas.add(canvasImage);
  imageUrl.src = url;
  return canvasImage;
};
// 添加文字
const addText = (canvas, color, pos) => {
  console.log(111);
  let text = new fabric.IText("", {
    borderColor: "#ff0000",
    editingBorderColor: "#ff0000",
    left: pos.x,
    top: pos.y - 10,
    transparentCorners: false,
    fontSize: 14,
    fill: color || "#ff0000",
    padding: 5,
    cornerSize: 5,
    cornerColor: "#ff0000",
    rotatingPointOffset: 20,
    lockScalingFlip: true,
    lockUniScaling: false,
  });
  text.id = count.value;
  text.on("selected", () => {
    activeIndex.value = text.id;
    isSelect.value = true;
  });
  canvas.add(text).setActiveObject(text);
  text.enterEditing();
  activeIndex.value = text.id;
  ctxArr.value[count.value] = text;
  count.value++;
};
const onDel = () => {
  delText(canvas.value, ctxArr.value[activeIndex.value]);
  delete ctxArr.value[activeIndex.value];
};
const delText = (canvas, ctx) => {
  canvas.remove(ctx);
};
const onChangeColor = (item) => {
  const o = canvas.value.getActiveObject();
  if (o) {
    o.set("fill", item);
    canvas.value.renderAll();
  }
};
const save = () => {
  const url = canvas.value.toDataURL();
  var blob = dataURLtoBlob(url);
  var file = blobToFile(blob, "截图.jpg");
  console.log(url, "保存的数据");
  let obj={
    id:testListId.value,
    score:scoreNum.value,
    annotationsPath:url
  }
  console.log(obj,"提交的数据");
  upScoreData(obj).then(res=>{
    console.log(res);
  })
};
const dataURLtoBlob = (dataurl) => {
  var arr = dataurl.split(",");
  var mime = arr[0].match(/:(.*?);/)[1];
  var bstr = atob(arr[1]);
  var n = bstr.length;
  var u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
};
const blobToFile = (theBlob, fileName) => {
  theBlob.lastModifiedDate = new Date();
  theBlob.name = fileName;
  return theBlob;
};
// http地址出问题
// 使用base64
function getBase64(url) {
  let dataURL = "";
  let img = new Image();
  img.setAttribute("crossOrigin", "anonymous");
  img.src = url;
  console.log(img);
  img.onload = () => {
    // 要先确保图片完整获取到,这是个异步处理
    let canvas = document.createElement("canvas"); // 创建canvas元素
    let [width, height] = [img.width, img.height]; // 确保canvas的尺寸和图片一样
    canvas.width = width;
    canvas.height = height;
    canvas.getContext("2d").drawImage(img, 0, 0, width, height); // 将图片绘制到canvas中
    dataURL = canvas.toDataURL("image/pdf"); // 转换图片为dataURL
    imgUrl.value = dataURL;
  };
}

onMounted(() => {
  hasImg.value = true;
  total.value = props.testCheckList.length;
  testListId.value=props.testCheckList[0].id;
  imgUrl.value = baseURL + props.testCheckList[0].path;
  getBase64(imgUrl.value);
});
</script>
  <style lang="scss" scoped>
.canvas_box {
  width: 100%;
  height: 100%;
  .canvas_tool {
    width: 80px;
    height: 100%;
    box-sizing: border-box;
    padding: 20px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    box-shadow: 0 0 10px #ccc;
    position: fixed;
    right: 0;
    top: 0;
    background: white;
  }
  .canvas_container {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    margin-top: 50px;
    position: relative;
    .canvas_wrapper {
      width: 100%;
      height: 100%;
      position: relative;
      display: flex;
      flex-direction: row;
      justify-content: center;
      align-items: center;
      overflow: hidden;
      top: 0;
      left: 0;
    }
  }
  .el-button + .el-button {
    margin-left: 0 !important;
  }
  .mt-15 {
    margin-top: 15px;
    width: 100%;
  }
}
.contain {
  // height: 100vh;
  width: 1200px;
  margin: auto;
}
.topBox {
  width: 100%;
  height: 50px;
  background-color: rgb(255, 255, 255);
  text-align: center;
  line-height: 50px;
  font-size: 24px;
  letter-spacing: 5px;
  margin: auto;
  font-weight: bold;
}
.topBox1 {
  width: 55%;
  height: 30px;
  text-align: center;
  line-height: 30px;
  font-size: 14px;
  margin: auto;
  color: rgb(247, 1, 1);
}
.contBox {
  position: relative;
  width: 100%;
  margin: auto;
  height: auto;
  color: black;
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
  // align-items: center;
  .btnImgBox {
    width: 800px;
    background-color: rgb(255, 255, 255);
    position: relative;
    .answerText {
      width: 100%;
      margin-top: 60px;
      text-align: center;
    }
    .anText {
      margin-top: 50px;
      text-align: left;
      margin-left: 20px;
      font-weight: bold;
    }

    .btnBox {
      display: flex;
      width: 220px;
      justify-content: space-between;
      text-align: center;
      align-items: center;
      position: absolute;
      top: 10px;
      left: 10px;
      z-index: 10;
      // background-color: #fff;
      .btn {
        width: 100px;
        height: 30px;
        background-color: rgb(26, 179, 250);
        line-height: 30px;
        color: #fff;
        font-size: 16px;
        position: relative;
        cursor: pointer;
      }
    }
    .imgBox {
      width: 100%;
      height: 100%;
      margin-top: 20px;
    }
  }
  .inputBox {
    background-color: #fff;
    width: 30%;
    height: 550px;
    display: flex;
    flex-direction: column;
    .title {
      font-size: 20px;
      width: 100%;
      text-align: center;
      margin-top: 20px;
    }
    .singleScore {
      width: 100%;
      height: 50px;
    }
    .tipText {
      width: 100%;
      height: 50px;
    }
    .saveBtn {
      width: 100%;
      height: 50px;
    }
  }
  .inputBox1 {
    width: 95%;
    margin: auto;
    margin-top: 50px;
    padding-right: 10px;
    // height: 50px;
  }
  .btnBox1 {
    width: 89%;
    margin: auto;
    margin-top: 0px;
    .btn {
      width: 100%;
      height: 40px;
      background-color: rgb(26, 179, 250);
      margin-bottom: 60px;
      text-align: center;
      line-height: 40px;
      color: white;
      border-radius: 10px;
      cursor: pointer;
      position: relative;
      .spant {
        font-size: 12px;
        color: rgb(240, 237, 237);
        position: absolute;
        width: 15px;
        height: 20px;
      }
    }
    .btn1 {
      background-color: rgb(89, 166, 125);
    }
  }
  .pageNation {
    width: 350px;
    margin: auto;
    height: 50px;
    margin-left: 5px;
    text-align: center;
    display: flex;
    justify-content: space-around;
    margin-top: -20px;
  }
}
</style>

<template>
  <div class="contain">
    <div class="topBox">考试阅卷</div>
    <!-- <div class="topBox1">提示:若添加注释,请先保存注释!</div> -->
    <div class="contBox">
      <div class="btnImgBox">
        <div class="btnBox">
          <div class="btn" @click="seeImgBox">看原卷</div>
          <!-- <div class="btn" @click="seeAnswerBox">看答案</div> -->
        </div>
        <div class="imgBox" v-if="seeImg">
          <div class="canvas_box">
            <div class="canvas_container">
              <div class="canvas_wrapper">
                <canvas
                  style="box-shadow: 0px 0px 5px #ccc; margin-top: -50px"
                  id="canvas"
                  :width="width"
                  :height="height"
                ></canvas>
                <img
                  :src="imgUrl"
                  id="img"
                  @load="init"
                  ref="img"
                  style="
                    position: absolute;
                    top: 40px;
                    left: 0;
                    opacity: 0;
                    z-index: -1;
                  "
                />
              </div>
            </div>
          </div>
        </div>
        <!-- <div  class="answerText" v-else>
            <div class="anText">答案:{{  }}</div>
          </div> -->
      </div>
      <div class="inputBox">
        <div class="title">第1题</div>
        <div class="inputBox1">
          <el-form ref="form" label-width="90px">
            <el-form-item label="文本颜色:">
              <el-color-picker  @change="onChangeColor" class="mt-15"></el-color-picker>
            </el-form-item>
            <el-form-item label="删除注释:">
              <el-button
                class="mt-15"
                style="width: 100%"
                type="danger"
                :icon="CloseBold"
                @click="onDel"
              />
            </el-form-item>
            <el-form-item label="该题得分:">
              <el-input v-model="scoreNum"></el-input>
            </el-form-item>
          </el-form>
        </div>
        <div class="pageNation">
          <el-pagination :page-size="1"  @current-change="handleCurrentChange"   layout="prev, pager, next" :total="total" />
        </div>
        <div class="btnBox1">
          <div class="btn" @click="save">保存</div>
        </div>
      </div>
    </div>
  </div>
</template>
  <script setup name="testInfo">
  import {
    CloseBold
} from '@element-plus/icons-vue'
import { getCurrentInstance, onMounted } from "vue";
import { fabric } from "fabric";
const { proxy } = getCurrentInstance();
const baseURL = import.meta.env.VITE_APP_BASE_API;
// 背景图
const imgUrl = ref("");
// canvas宽高
const width = ref(0);
const height = ref(0);
const props = defineProps({
  testCheckList: {
    type: Array
  },
});
// 判断点击索引
const activeIndex=ref(null);
// 是否选中
const isSelect=ref(false);
// 画布已添加的文字
const  ctxArr=ref({});
// 初始化文字
const  operation=ref("text");
// 文字颜色
const color=ref("#ff0000");
// 批改卷面分
const scoreNum=ref(0);
const total=ref(0);
const  count=ref(0);
const canvas = ref();
const seeImg = ref(true);
const seeImgBox = () => {
  seeImg.value = true;
};
const seeAnswerBox = () => {
  seeImg.value = false;
};
const handleCurrentChange=(val)=>{
  console.log(val);
  console.log(props.testCheckList[val-1].path);
  imgUrl.value=baseURL+props.testCheckList[val-1].path;
  console.log(imgUrl.value);
}
// 图片加载完初始化画布
const init = () => {
  // 获取画布宽高
  width.value = proxy.$refs.img.offsetWidth; // 宽
  height.value = proxy.$refs.img.offsetHeight; // 高
  // 等待dom元素加载完成后再初始化
  proxy.$nextTick(() => {
    // console.log(this.width, this.height, this.$refs.img.src);
    canvas.value = new fabric.Canvas("canvas"); // 声明画布
    canvas.value.width = width.value; // 设置画布宽
    canvas.value.height = height.value; // 设置画布高
    const imgInstance = addOriginImage1(imgUrl.value, canvas.value); // 添加背景图
    imgInstance.set("selectable", false); // 背景图不可选择
    onMouseDown(canvas.value); // 绑定点击新增文字事件
  });
};
// 添加背景图v2
const addOriginImage = (canvas) => {
 
  const imgInstance = new fabric.Image(proxy.$refs.img, {
    // 设置图片位置和样子
    left: 0,
    top: 0,
    width: 300,
    height: 300,
    crossOrigin: "Anonymous", // 跨域
  });
  proxy.$refs.img.setAttribute("crossOrigin",'Anonymous')
  canvas.add(imgInstance); // 将图片加入到canvas中
  return imgInstance;
};
/**
     * 点击事件:
     * 1.画布上无选中元素,点击空白处添加文字
     * 2.画布上有选中元素,点击空白处,选中元素失去焦点
     * 3.画布上有选中元素,点击选中元素,进行文字编辑
     */
    const onMouseDown=(canvas)=> {
      canvas.on("mouse:down", (opt) => {
        console.log("activeIndex", activeIndex.value);
        const pos = opt.absolutePointer;
        // 执行文字操作
        const isText = () => {
          if (activeIndex.value === null) {
            // 如果当前没有选中元素,点击空白处添加文字
            addText(canvas, color.value, pos);
          } else {
            // 获取当前激活对象
            const o = canvas.getActiveObject();
            if (!o) {
              activeIndex.value = null;
            }
          }
        };
        switch (operation.value) {
          case "text":
            isText();
            break;
          default:
            isText();
        }
      });
    }
     // 添加背景图v3
    const addOriginImage1=(url, canvas)=> {
      var canvasImage;
      var imageUrl = new Image();
      canvasImage = new fabric.Image(imageUrl);
      canvasImage.left = 0;
      canvasImage.top = 0;
      canvasImage.width = width.value;
      canvasImage.height = height.value;
      canvasImage.crossOrigin = "Anonymous"; //这里是主要添加的属性
      canvas.add(canvasImage);
      imageUrl.crossOrigin = "Anonymous";
      imageUrl.src = url;
      return canvasImage;
    }
    // 添加文字
   const addText=(canvas, color, pos)=> {
      console.log(111);
      let text = new fabric.IText("", {
        borderColor: "#ff0000", // 激活状态时的边框颜色
        editingBorderColor: "#ff0000", // 文本对象的边框颜色,当它处于编辑模式时
        left: pos.x,
        top: pos.y - 10,
        transparentCorners: false,
        fontSize: 14,
        fill: color || "#ff0000",
        padding: 5,
        cornerSize: 5, // Size of object's controlling corners
        cornerColor: "#ff0000",
        rotatingPointOffset: 20, // Offset for object's controlling rotating point
        lockScalingFlip: true, // 不能通过缩放为负值来翻转对象
        lockUniScaling: false, // 对象非均匀缩放被锁定
      });
      text.id = count.value;
      // 绑定选中事件
      text.on("selected", () => {
        activeIndex.value = text.id;
        isSelect.value = true;
      });
      canvas.add(text).setActiveObject(text); // 添加文字到画布上,并将文字置为激活状态
      text.enterEditing(); // 将文字置为编辑状态
      activeIndex.value = text.id;
      ctxArr.value[count.value] = text;
      count.value++;
    }
    // 删除注释
   const onDel=()=> {
      delText(canvas.value,ctxArr.value[activeIndex.value]);
      delete ctxArr.value[activeIndex.value];
    }
   const delText=(canvas, ctx)=> {
      canvas.remove(ctx);
    }
    // 改变字体颜色
   const onChangeColor=(item)=> {
      // 获取当前激活对象
      const o = canvas.value.getActiveObject();
      if (o) {
        o.set("fill", item);
        canvas.value.renderAll();
      }
    }
    // 保存canvas
   const save=()=> {
      const url = canvas.value.toDataURL();
      var blob = dataURLtoBlob(url);
      var file = blobToFile(blob, "截图.jpg");
      console.log(url,"保存的数据");
    }
    // 将base64转换为blob
  const dataURLtoBlob= (dataurl)=> {
      var arr = dataurl.split(",");
      var mime = arr[0].match(/:(.*?);/)[1];
      var bstr = atob(arr[1]);
      var n = bstr.length;
      var u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    }
    // 将blob转换为file
   const blobToFile=(theBlob, fileName)=> {
      theBlob.lastModifiedDate = new Date();
      theBlob.name = fileName;
      return theBlob;
    }
onMounted(() => {
  console.log(props.testCheckList);
  total.value=props.testCheckList.length;
  imgUrl.value = baseURL + props.testCheckList[0].path;
  console.log(imgUrl.value);
});
</script>
  <style lang="scss" scoped>
.canvas_box {
  width: 100%;
  height: 100%;
  .canvas_tool {
    width: 80px;
    height: 100%;
    box-sizing: border-box;
    padding: 20px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    box-shadow: 0 0 10px #ccc;
    position: fixed;
    right: 0;
    top: 0;
    background: white;
  }
  .canvas_container {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    margin-top: 50px;
    position: relative;
    .canvas_wrapper {
      width: 100%;
      height: 100%;
      position: relative;
      display: flex;
      flex-direction: row;
      justify-content: center;
      align-items: center;
      overflow: hidden;
      top: 0;
      left: 0;
    }
  }
  .el-button + .el-button {
    margin-left: 0 !important;
  }
  .mt-15 {
    margin-top: 15px;
    width: 100%;
  }
}
.contain {
  // height: 100vh;
  width: 1200px;
  margin: auto;
}
.topBox {
  width: 100%;
  height: 50px;
  background-color: rgb(255, 255, 255);
  text-align: center;
  line-height: 50px;
  font-size: 24px;
  letter-spacing: 5px;
  margin: auto;
  font-weight: bold;
}
.topBox1 {
  width: 55%;
  height: 30px;
  text-align: center;
  line-height: 30px;
  font-size: 14px;
  margin: auto;
  color: rgb(247, 1, 1);
}
.contBox {
  position: relative;
  width: 100%;
  margin: auto;
  height: auto;
  color: black;
  margin-top: 20px;
  display: flex;
  justify-content: space-between;
  // align-items: center;
  .btnImgBox {
    width: 800px;
    background-color: rgb(255, 255, 255);
    position: relative;
    .answerText {
      width: 100%;
      margin-top: 60px;
      text-align: center;
    }
    .anText {
      margin-top: 50px;
      text-align: left;
      margin-left: 20px;
      font-weight: bold;
    }

    .btnBox {
      display: flex;
      width: 220px;
      justify-content: space-between;
      text-align: center;
      align-items: center;
      position: absolute;
      top: 10px;
      left: 10px;
      z-index: 10;
      // background-color: #fff;
      .btn {
        width: 100px;
        height: 30px;
        background-color: rgb(26, 179, 250);
        line-height: 30px;
        color: #fff;
        font-size: 16px;
        position: relative;
        cursor: pointer;
      }
    }
    .imgBox {
      width: 100%;
      height: 100%;
      margin-top: 20px;
    }
  }
  .inputBox {
    background-color: #fff;
    width: 30%;
    height: 550px;
    display: flex;
    flex-direction: column;
    .title {
      font-size: 20px;
      width: 100%;
      text-align: center;
      margin-top: 20px;
    }
    .singleScore {
      width: 100%;
      height: 50px;
    }
    .tipText {
      width: 100%;
      height: 50px;
    }
    .saveBtn {
      width: 100%;
      height: 50px;
    }
  }
  .inputBox1 {
    width: 95%;
    margin: auto;
    margin-top: 50px;
    padding-right: 10px;
    // height: 50px;
  }
  .btnBox1 {
    width: 89%;
    margin: auto;
    margin-top: 0px;
    .btn {
      width: 100%;
      height: 40px;
      background-color: rgb(26, 179, 250);
      margin-bottom: 60px;
      text-align: center;
      line-height: 40px;
      color: white;
      border-radius: 10px;
      cursor: pointer;
      position: relative;
      .spant {
        font-size: 12px;
        color: rgb(240, 237, 237);
        position: absolute;
        width: 15px;
        height: 20px;
      }
    }
    .btn1 {
      background-color: rgb(89, 166, 125);
    }
  }
  .pageNation {
    width: 350px;
    margin: auto;
    height: 50px;
    margin-left: 5px;
    text-align: center;
    display: flex;
    justify-content: space-around;
    margin-top: -20px;
  }
}
</style>

https://www.cnblogs.com/yang-shun/p/12206515.html

v2 

<template>
  <div class="canvas_box">
    <div class="canvas_container">
      <div class="canvas_wrapper">
        <canvas style="box-shadow:0px 0px 5px #ccc;" v-if="width!==''" id='canvas' :width='width' :height='height'></canvas>
        <img :src='url' id='img' ref='img' @load='init' style="position:absolute;top:0;left:0;opacity:0;z-index:-1;"/>
      </div>
    </div>
    <div class="canvas_tool">
      <el-color-picker class="mt-15" v-model="color" circle @change="onChangeColor"></el-color-picker>
      <el-button class="mt-15" type="danger" icon="el-icon-close" circle @click="onDel"></el-button>
      <el-button class="mt-15" type="success" icon="el-icon-check" circle @click="save"></el-button>
    </div>
  </div>
</template>

<script>
import { fabric } from 'fabric'

export default {
  name: 'Fabric',
  props: {
    url: {
      type: String
    }
  },
  data () {
    return {
      canvas: '',
      width: '',
      height: '',
      inputText: '',
      color: '#ff0000',
      fontSize: 18,
      ctxArr: {},
      count: 0,
      activeIndex: null,
      isSelect: false,
      fileStearm: '',
      operation: 'text'
    }
  },
  mounted () {
  },
  methods: {
    // 图片加载完初始化画布
    init () {
       
      // 获取画布宽高
      this.width = this.$refs.img.offsetWidth // 宽
      this.height = this.$refs.img.offsetHeight // 高
      this.$nextTick(() => {
        console.log(this.width, this.height, this.$refs.img.src);
    //     fabric.Image=fabric.util.createClass(fabric.Object,{
    //     crossOrigin:"Anonymous"
    //   })
        this.canvas = new fabric.Canvas('canvas') // 声明画布
        this.canvas.width = this.width // 设置画布宽
        this.canvas.height = this.height // 设置画布高
        const imgInstance = this.addOriginImage(this.url,this.canvas) // 添加背景图
        imgInstance.set('selectable', false) // 背景图不可选择
        this.onMouseDown(this.canvas) // 绑定点击新增文字事件
      })
    },
    addOriginImage (url,  canvas) {
    //     let url = this.url
    // let img = new Image()
    // img.src = url
    // img.crossOrigin = 'anonymous'// 跨域
    // img.onload = () => {
    //    let { width, height } = img
    // }
    //   const imgInstance = new fabric.Image(this.$refs.img, {// 设置图片位置和样子
    //     left: 0,
    //     top: 0,
    //     width: this.width,
    //     height: this.height,
    //     // angle: 0, // 设置图形顺时针旋转角度
    //     crossOrigin : "anonymous",// 跨域
    //   })
    //   canvas.add(imgInstance) // 加入到canvas中
    // //   this.canvas.renderAll();
     
    //   return imgInstance
    var canvasImage;
    var imageUrl = new Image();
    // imageUrl.onload=function(){
      canvasImage=new fabric.Image(imageUrl);
      canvasImage.left =0;
      canvasImage.top =0;
      canvasImage.width =this.width;
      canvasImage.height =this.height;
      canvasImage.crossOrigin = "Anonymous";   //这里是主要添加的属性
      canvas.add(canvasImage)
    // };
    imageUrl.crossOrigin = "Anonymous";
    imageUrl.src = url;
    return canvasImage
    },
    // 添加文字
    addText (canvas, color, pos) {
        console.log(111);
      let text = new fabric.IText('', {
        borderColor: '#ff0000', // 激活状态时的边框颜色
        editingBorderColor: '#ff0000', // 文本对象的边框颜色,当它处于编辑模式时
        left: pos.x,
        top: pos.y - 10,
        transparentCorners: true,
        fontSize: 14,
        fill: color || '#ff0000',
        padding: 5,
        cornerSize: 5, // Size of object's controlling corners
        cornerColor: '#ff0000',
        rotatingPointOffset: 20, // Offset for object's controlling rotating point
        lockScalingFlip: true, // 不能通过缩放为负值来翻转对象
        lockUniScaling: true // 对象非均匀缩放被锁定
      })
      text.id = this.count
      // 绑定选中事件
      text.on('selected', () => {
        this.activeIndex = text.id
        this.isSelect = true
      })
      canvas.add(text).setActiveObject(text) // 添加文字到画布上,并将文字置为激活状态
      text.enterEditing() // 将文字置为编辑状态
      this.activeIndex = text.id
      this.ctxArr[this.count] = text
      this.count++
    },
    delText (canvas, ctx) {
      canvas.remove(ctx)
    },
    // 添加箭头
    // addArrow (canvas) {
    //   const sp = {
    //     x: 0,
    //     y: 30
    //   }
    //   const ep = {
    //     x: 200,
    //     y: 30
    //   }

    //   const p = `M ${sp.x} ${sp.y} `
    //   let path = new fabric.Path('M 0 20 L 30 0 L 27 10 L 200 20 L 27 30 L 30 40 z')
    //   path.set({ left: 0, top: 0 })
    //   path.id = this.count
    //   // 绑定选中事件
    //   path.on('selected', () => {
    //     this.activeIndex = path.id
    //     this.isSelect = true
    //   })
    //   canvas.add(path).setActiveObject(path) // 添加文字到画布上,并将文字置为激活状态
    //   this.activeIndex = path.id
    //   this.ctxArr[this.count] = path
    //   this.count++
    // },
    /**
     * 点击事件:
     * 1.画布上无选中元素,点击空白处添加文字
     * 2.画布上有选中元素,点击空白处,选中元素失去焦点
     * 3.画布上有选中元素,点击选中元素,进行文字编辑
     */
    onMouseDown (canvas) {
      canvas.on('mouse:down', (opt) => {
        console.log('this.activeIndex', this.activeIndex)
        const pos = opt.absolutePointer
        // 执行文字操作
        const isText = () => {
          if (this.activeIndex === null) {
            // 如果当前没有选中元素,点击空白处添加文字
            this.addText(canvas, this.color, pos)
          } else {
            // 获取当前激活对象
            const o = canvas.getActiveObject()
            if (!o) {
              this.activeIndex = null
            };
          }
        }
        switch (this.operation) {
          case 'text': isText(); break
          default: isText()
        };
      })
    },
    onDel () {
      this.delText(this.canvas, this.ctxArr[this.activeIndex])
      delete this.ctxArr[this.activeIndex]
    },
    onChangeColor () {
      // 获取当前激活对象
      const o = this.canvas.getActiveObject()
      if (o) {
        console.log('this.color', this.color)
        o.set('fill', this.color)
        this.canvas.renderAll()
      };
    },
    save () {
      // const url = this.canvas.toDataURL({
      //   format: 'jpeg',
      //   quality: 1
      // })
      const url = this.canvas.toDataURL();
      var blob = this.dataURLtoBlob(url);
      var file = this.blobToFile(blob, '截图.jpg')
    //   console.log(url);
      console.log(file);
      this.fileStearm = file;
      // 组装a标签
      let elink = document.createElement('a');
     
      // 设置下载文件名
      elink.download = '截图.png';
      elink.style.display = 'none';
      elink.href = URL.createObjectURL(blob);
      document.body.appendChild(elink);
      elink.click();
      document.body.removeChild(elink);
    //   console.log(elink.href);
    },
    // 将base64转换为blob
    dataURLtoBlob: function (dataurl) {
      var arr = dataurl.split(',')
      var mime = arr[0].match(/:(.*?);/)[1]
      var bstr = atob(arr[1])
      var n = bstr.length
      var u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new Blob([u8arr], { type: mime })
    },
    // 将blob转换为file
    blobToFile: function (theBlob, fileName) {
      theBlob.lastModifiedDate = new Date()
      theBlob.name = fileName
      return theBlob
    }
  }
}
</script>

<style scoped lang="scss">
.canvas_box{
  width:100%;
  height:100%;
  .canvas_tool{
    width:80px;
    height:100%;
    box-sizing:border-box;
    padding:20px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    box-shadow: 0 0 10px #ccc;
    position:fixed;
    right:0;
    top:0;
    background:white;
  }
  .canvas_container{
      width:100%;
      height:100%;
      box-sizing: border-box;
      padding: 0px 80px 0px 0px;
    .canvas_wrapper{
      width:100%;
      height:100%;
      position:relative;
      display:flex;
      flex-direction:row;
      justify-content:center;
      align-items:center;
      overflow: scroll;
    }
  }
  .el-button+.el-button{
    margin-left: 0 !important;
  }
  .mt-15{
    margin-top:15px;
  }
}
</style>

v3 last

<template>
  <div class="contain">
    <div class="topBox">考试阅卷</div>
    <div class="contBox">
      <div class="btnImgBox">
        <div class="btnBox">
          <el-pagination
            small
            layout="prev, pager, next"
            :total="nowTopTotal"
            :page-size="1"
            v-model:current-page="currentPageNow"
            @current-change="topCurChange"
          />
        </div>
        <div class="imgBox" v-if="hasImg">
          <div class="canvas_box">
            <div class="canvas_container">
              <div class="canvas_wrapper">
                <canvas
                  style="box-shadow: 0px 0px 5px #ccc; margin-top: -50px"
                  id="canvas"
                  :width="width"
                  :height="height"
                ></canvas>
                <img
                  :src="imgUrlNew"
                  id="img"
                  ref="img"
                  style="
                    position: absolute;
                    top: 40px;
                    left: 0;
                    opacity: 0;
                    z-index: -1;
                  "
                />
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="inputBox">
        <div class="title">第1题</div>
        <div class="inputBox1">
          <el-form ref="form" label-width="90px">
            <el-form-item label="文本颜色:">
              <el-color-picker
                @change="onChangeColor"
                class="mt-15"
              ></el-color-picker>
            </el-form-item>
            <el-form-item label="删除注释:">
              <el-button
                class="mt-15"
                style="width: 100%"
                type="danger"
                :icon="CloseBold"
                @click="onDel"
              />
            </el-form-item>
            <el-form-item label="该题得分:">
              <el-input v-model="subData.score"></el-input>
            </el-form-item>
          </el-form>
        </div>
        <div class="pageNation">
          <el-pagination
            :page-size="1"
            background
            @current-change="handleCurrentChange"
            layout="prev, pager, next"
            :total="total"
          />
        </div>
        <div class="btnBox1">
          <div class="btn" @click="save">保存</div>
        </div>
      </div>
    </div>
  </div>
</template>
  <script setup name="testInfo">
import $ from "jquery";
import { CloseBold } from "@element-plus/icons-vue";
import { getCurrentInstance, onBeforeMount, onMounted } from "vue";
import { upScoreData, getScoreInfo, upScoreInfo } from "@/api/upScore/index";
import { fabric } from "fabric";
const { proxy } = getCurrentInstance();
const baseURL = import.meta.env.VITE_APP_BASE_API;
const imgUrlNew = ref("");
const width = ref(0);
const height = ref(0);
const hasImg = ref(false);
const props = defineProps({
  testCheckList: {
    type: Array,
  },
});
const currentPageNow = ref(1);
const nowTopTotal = ref(0);
const activeIndex = ref(null);
const isSelect = ref(false);
const ctxArr = ref({});
const operation = ref("text");
const color = ref("#ff0000");
const scoreNum = ref(0);
const total = ref(0);
const count = ref(0);
const canvas = ref();
const testListId = ref("");
const nowSeeImgs = ref([]);
const subUrlArr = ref([]);
const hasSeeImg = ref([]);
const oldPage = ref(0);
// 提交的数据
const subData = ref({
  id: "",
  score: 0,
  base64Lists: [],
});

const handleCurrentChange = (val) => {
  hasImg.value = false;
  currentPageNow.value = 1;
  proxy.$nextTick(() => {
    hasImg.value = true;
    subData.value.score = 0;
    hasSeeImg.value = [];
    getScoreListInf(props.testCheckList[val - 1].id);
    testListId.value = props.testCheckList[val - 1].id;
    subData.value.id = testListId.value;
    subData.value.base64Lists = [];
  });
};
const addTextEvent = (text, color, x, y) => {
  let canvas = document.createElement("canvas");
  canvas.style.cssText = "position:absolute;top:0;left:0";
  let myImage = new Image();
  myImage.src = imgUrlNew.value;
  canvas.width = width.value;
  canvas.height = height.value;
  myImage.crossOrigin = "Anonymous";
  let context = canvas.getContext("2d");
  myImage.onload = function () {
    context.drawImage(myImage, 0, 0, canvas.width, canvas.height);
    context.font = "14px Courier New";
    context.fillStyle = color;
    context.textAlign = "end"; //文字右对齐
    context.fillText(text, x + 280, y + 15); // 文字的内容和位置
    let base64 = canvas.toDataURL("image/jpeg"); // "image/png" 这里注意一下
    console.log(base64);
    imgUrlNew.value = base64;
  };
};
// 将base64转换为blob
const dataURLtoBlob = (dataurl) => {
  var arr = dataurl.split(",");
  var mime = arr[0].match(/:(.*?);/)[1];
  var bstr = atob(arr[1]);
  var n = bstr.length;
  var u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
};
// 将blob转换为file
const blobToFile = (theBlob, fileName) => {
  theBlob.lastModifiedDate = new Date();
  theBlob.name = fileName;
  return theBlob;
};
const topCurChange = (val) => {
  hasImg.value = false;
  window.pageYoffset = 0;
  document.documentElement.scrollTop = 0;
  document.body.scrollTop = 0;
  let flag = false;
  hasSeeImg.value.push(val - 1);
  let canvas = document.querySelector("canvas");
  proxy.$nextTick(() => {
    hasImg.value = true;
    const url = canvas.toDataURL("image/jpeg", 0.5);
    // 保存上一个注释
    if (oldPage.value > val) {
      flag = true;
      oldPage.value = val;
      subData.value.base64Lists[val] = url;
    } else {
      flag = false;
      oldPage.value = val;
      subData.value.base64Lists[val - 2] = url;
    }
    if (subData.value.base64Lists[val - 1]) {
      getBase64(subData.value.base64Lists[val - 1]);
    } else {
      getBase64(baseURL + nowSeeImgs.value[val - 1]);
    }
  });
};
const save = () => {
  let canvas = document.querySelector("canvas");
  const url = canvas.toDataURL("image/jpeg", 0.5);
  subData.value.base64Lists[currentPageNow.value - 1] = url;
  console.log(nowTopTotal.value, "当前总页");
  console.log(hasSeeImg.value, "已查看图片");
  console.log(nowSeeImgs.value, "总图片");
  console.log(Array.from(new Set(hasSeeImg.value)));
  let calcutLength = Array.from(new Set(hasSeeImg.value));
  if (calcutLength.length < nowTopTotal.value) {
    let tempArr = [];
    for (let index = 0; index < nowTopTotal.value; index++) {
      tempArr.push(index);
    }
    let lastNoArr = findMissingValues(tempArr, calcutLength);
    lastNoArr.map((item, index) => {
      console.log(nowSeeImgs.value[item]);
      subData.value.base64Lists[item] = nowSeeImgs.value[item];
    });
    console.log(subData.value);
  }
  upScoreInfo(subData.value).then((res) => {
    proxy.$modal.msgSuccess("保存成功");
  });
};
const findMissingValues = (arr1, arr2) => {
  var missing = []; // 保存结果的数组
  for (var i = 0; i < arr1.length; i++) {
    if (!arr2.includes(arr1[i])) {
      missing.push(arr1[i]);
    }
  }
  return missing;
};
const init = () => {
  width.value = proxy.$refs.img.scrollWidth;
  height.value = proxy.$refs.img.scrollHeight;
  hasImg.value = true;
  proxy.$nextTick(() => {
    canvas.value = new fabric.Canvas("canvas");
    canvas.value.width = width.value;
    canvas.value.height = height.value;
    const imgInstance = addOriginImage1(imgUrlNew.value, canvas.value);
    imgInstance.set("selectable", false);
    imgInstance.set("fill", "silver");
    imgInstance.set("crossOrigin", "Anonymous");
    onMouseDown(canvas.value);
  });
};

const onMouseDown = (canvas) => {
  canvas.on("mouse:down", (opt) => {
    console.log(ctxArr.value);
    let t = Object.values(ctxArr.value);
    if (t.length > 0) {
      t.map((item) => {
        console.log(item.canvas._offset);
        addTextEvent(item.text, "red", item.left, item.top);
      });
    }
    const pos = opt.absolutePointer;
    const isText = () => {
      if (activeIndex.value === null) {
        addText(canvas, color.value, pos);
      } else {
        const o = canvas.getActiveObject();
        if (!o) {
          activeIndex.value = null;
        }
      }
    };
    switch (operation.value) {
      case "text":
        isText();
        break;
      default:
        isText();
    }
  });
};
const addOriginImage1 = (url, canvas) => {
  var canvasImage;
  var imageUrl = new Image();
  imageUrl.src = url;
  canvasImage = new fabric.Image(imageUrl, {
    clipPath: new fabric.Rect({
      top: 0,
      left: 0,
      width: 5000,
      height: 6000,
      fill: "silver",
      stroke: "silver",
      strokeDashArray: [5, 5],
      absolutePositioned: true,
      lockMovementX: true, // 禁止水平移动
      lockMovementY: true, // 禁止垂直移动
      hasRotatingPoint: false, // 无旋转点
      hasControls: true, // 编辑框
      selectable: false, // 不可选中
    }),
  });
  canvasImage.crossOrigin = "anonymous";
  canvas.add(canvasImage);
  return canvasImage;
};
const addOriginImage = (canvas) => {
  const imgInstance = new fabric.Image(proxy.$refs.img, {
    // 设置图片位置和样子
    left: 0,
    top: 0,
    width: width.value,
    height: 1500,
    angle: 0, // 设置图形顺时针旋转角度
  });
  canvas.add(imgInstance); // 加入到canvas中
  return imgInstance;
};
const addText = (canvas, color, pos) => {
  let text = new fabric.IText("", {
    borderColor: "#ff0000",
    editingBorderColor: "#ff0000",
    left: pos.x,
    top: pos.y - 10,
    transparentCorners: false,
    fontSize: 14,
    fill: color || "#ff0000",
    padding: 5,
    cornerSize: 5,
    cornerColor: "#ff0000",
    rotatingPointOffset: 20,
    lockScalingFlip: true,
    lockUniScaling: false,
  });
  text.id = count.value;
  text.on("selected", () => {
    activeIndex.value = text.id;
    isSelect.value = true;
  });
  canvas.add(text).setActiveObject(text);
  text.enterEditing();
  activeIndex.value = text.id;
  ctxArr.value[count.value] = text;
  count.value++;
};
const onDel = () => {
  delText(canvas.value, ctxArr.value[activeIndex.value]);
  delete ctxArr.value[activeIndex.value];
};
const delText = (canvas, ctx) => {
  canvas.remove(ctx);
};
const onChangeColor = (item) => {
  const o = canvas.value.getActiveObject();
  if (o) {
    o.set("fill", item);
    canvas.value.renderAll();
  }
};
// // 放大
// const sacleBase = (url) => {
//   // Base64字符串表示的图像数据

//   // 创建Image对象并加载图像
//   var img = new Image();
//   img.src = url;

//   // 等待图像加载完成后再进行操作
//   img.onload = function () {
//     var canvas = document.createElement("canvas");

//     // 设置画布的宽度和高度为原始图像的两倍(或其他任意比例)
//     canvas.width = img.width * 2;
//     canvas.height = img.height * 2;

//     var ctx = canvas.getContext("2d");

//     // 清空画布内容
//     ctx.clearRect(0, 0, canvas.width, canvas.height);

//     // 将原始图像绘制到新的画布上,通过指定位置、尺寸参数控制放大效果
//     ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

//     // 获取放大后的图像数据URL
//     var enlargedImgUrl = canvas.toDataURL();

//     console.log(enlargedImgUrl); // 输出放大后的图像数据URL
//   };
// };

function imageToBase64(image) {
  const canvas = document.createElement("canvas");
  canvas.width = image.width;
  canvas.height = image.height;

  const context = canvas.getContext("2d");
  context.drawImage(image, 0, 0);

  return canvas.toDataURL("image/jpeg"); // 替换为相应的MIME类型
}
function getBase64(url) {
  let dataURL = "";
  let img = new Image();
  img.crossOrigin = "Anonymous";
  img.src = url;
  img.onload = () => {
    let canvas = document.createElement("canvas");
    let [widths, heights] = [img.width, img.height];
    canvas.width = widths;
    canvas.height = heights;
    width.value = widths;
    height.value = heights;
    canvas.getContext("2d").drawImage(img, 0, 0, widths, heights);
    dataURL = canvas.toDataURL("image/jpeg");
    imgUrlNew.value = dataURL;
    subData.value.base64Lists[currentPageNow.value - 1] = dataURL;
    proxy.$nextTick(() => {
      init();
    });
  };
}

const onlyGetBase = (url) => {
  let dataURL = "";
  let img = new Image();
  img.crossOrigin = "Anonymous";
  img.src = url;
  img.onload = () => {
    let canvas = document.createElement("canvas");
    let [widths, heights] = [img.width, img.height];
    canvas.width = widths;
    canvas.height = heights;
    width.value = widths;
    height.value = heights;
    canvas.getContext("2d").drawImage(img, 0, 0, widths, heights);
    dataURL = canvas.toDataURL("image/jpeg");
    subData.value.base64Lists[index] = dataURL;
  };
};
const getScoreListInf = (id) => {
  getScoreInfo(id).then((res) => {
    scoreNum.value = res.data.score;
    subData.value.score = res.data.score;
    nowSeeImgs.value = res.data.annotationsPath.split(",");
    nowTopTotal.value = nowSeeImgs.value.length;
    hasSeeImg.value.push(0);
    proxy.$nextTick(() => {
      getBase64(baseURL + nowSeeImgs.value[0]);
    });
  });
};
onMounted(() => {
  total.value = props.testCheckList.length;
  if (props.testCheckList.length > 0) {
    testListId.value = props.testCheckList[0].id;
    getScoreListInf(props.testCheckList[0].id);
    handleCurrentChange(1);
  } else {
    proxy.$modal.msgWarning("未查询到阅卷信息");
  }
});
</script>
  <style lang="scss" scoped>
.canvas_box {
  width: 100%;
  height: 100%;
  .canvas_tool {
    // width: 80px;
    height: 100%;
    box-sizing: border-box;
    padding: 20px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    box-shadow: 0 0 10px #ccc;
    position: fixed;
    right: 0;
    top: 0;
    background: white;
  }
  .canvas_container {
    width: 100%;
    height: 100%;
    position: relative;
    margin-top: 50px;
    display: flex;
    justify-content: center;
    align-items: center;
    .canvas_wrapper {
      width: 100%;
      height: 100%;
      position: relative;
      padding-top: 50px;
      overflow: auto;
      top: 0;
      left: 0;
      // display: flex;
      // justify-content: center;
      // align-items: center;
    }
    .canvas_wrapper::-webkit-scrollbar-thumb {
      background-color: rgb(70, 156, 236);
    }
    .canvas_wrapper::-webkit-scrollbar-thumb:hover {
      background-color: rgb(228, 10, 10);
    }
  }
  .el-button + .el-button {
    margin-left: 0 !important;
  }
  .mt-15 {
    margin-top: 15px;
    width: 100%;
  }
}
.contain {
  // height: 100vh;
  width: 100%;
  margin: auto;
  padding: 10px;
  height: 100%;
}
.topBox {
  width: 100%;
  height: 50px;
  background-color: rgb(255, 255, 255);
  text-align: center;
  line-height: 50px;
  font-size: 24px;
  letter-spacing: 5px;
  margin: auto;
  font-weight: bold;
}
.topBox1 {
  width: 55%;
  height: 30px;
  text-align: center;
  line-height: 30px;
  font-size: 14px;
  margin: auto;
  color: rgb(247, 1, 1);
}
.contBox {
  position: relative;
  width: 100%;
  margin: auto;
  height: 100%;
  color: black;
  margin-top: 20px;
  display: flex;
  justify-content: space-around;
  // align-items: center;
  .btnImgBox {
    // width: 800px;
    // height: 1500px;
    background-color: rgb(255, 255, 255);
    position: relative;
    .answerText {
      width: 100%;
      margin-top: 60px;
      text-align: center;
    }
    .anText {
      margin-top: 50px;
      text-align: left;
      margin-left: 20px;
      font-weight: bold;
    }

    .btnBox {
      display: flex;
      width: 220px;
      justify-content: space-between;
      text-align: center;
      align-items: center;
      position: absolute;
      top: 10px;
      left: 10px;
      z-index: 10;
      // background-color: #fff;
      .btn {
        width: 100px;
        height: 30px;
        background-color: rgb(26, 179, 250);
        line-height: 30px;
        color: #fff;
        font-size: 16px;
        position: relative;
        cursor: pointer;
      }
    }
    .imgBox {
      width: 100%;
      height: 100%;
      margin-top: 20px;
    }
  }
  .inputBox {
    background-color: #fff;
    width: 30%;
    height: 550px;
    display: flex;
    flex-direction: column;
    .title {
      font-size: 20px;
      width: 100%;
      text-align: center;
      margin-top: 20px;
    }
    .singleScore {
      width: 100%;
      height: 50px;
    }
    .tipText {
      width: 100%;
      height: 50px;
    }
    .saveBtn {
      width: 100%;
      height: 50px;
    }
  }
  .inputBox1 {
    width: 95%;
    margin: auto;
    margin-top: 50px;
    padding-right: 10px;
    // height: 50px;
  }
  .btnBox1 {
    width: 89%;
    margin: auto;
    margin-top: 0px;
    .btn {
      width: 100%;
      height: 40px;
      background-color: rgb(26, 179, 250);
      margin-bottom: 60px;
      text-align: center;
      line-height: 40px;
      color: white;
      border-radius: 10px;
      cursor: pointer;
      position: relative;
      .spant {
        font-size: 12px;
        color: rgb(240, 237, 237);
        position: absolute;
        width: 15px;
        height: 20px;
      }
    }
    .btn1 {
      background-color: rgb(89, 166, 125);
    }
  }
  .pageNation {
    width: 350px;
    margin: auto;
    height: 50px;
    margin-left: 5px;
    text-align: center;
    display: flex;
    justify-content: space-around;
    margin-top: -20px;
  }
}
</style>

20、base64转字符串

21、canvas画图

canvas画坐标

JavaScript使用canvas绘制坐标和线

canvas画矩形

Vue中使用鼠标在Canvas上绘制矩形_技术服务生活的技术博客_51CTO博客

22、若依框架跳转大屏

跳转大屏(不可以的话就先建菜单再修改成目录) 添加路由

1、router.js

2、permission.js

3、新建文件

4、login.vue重定向

23、人工智能网站

前言

24、贝塞尔曲线官网

cubic-bezier.com

25、切换淘宝镜像

error An unexpected error occurred: https://registry.npmmirror.com-优快云博客

26、day.js常用语法

import dayjs from 'dayjs'
// 获取本周
export const getCurrtentWeek = () => {
    let day1 = dayjs().startOf('week').format('YYYY-MM-DD HH:mm:ss')
    let day2 = dayjs().endOf('week').format('YYYY-MM-DD HH:mm:ss')
    const value = [day1, day2]
    return value
}
// 获取上周
export const getLastWeek = () => {
    let lastWeek1 = dayjs().add(-1, 'week').startOf('week').add(1, 'day').format('YYYY-MM-DD HH:mm:ss')
    let lastWeek2 = dayjs().add(-1, 'week').endOf('week').add(1, 'day').format('YYYY-MM-DD HH:mm:ss')
    const value = [lastWeek1, lastWeek2]
    return value
}
// 获取今天
export const getDay = () => {
    let today1 = dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss')
    let today2 = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
    const value = [today1, today2]
    return value
}
// 获取本月
export const getMonth = () => {
    let month1 = dayjs().startOf('month').format('YYYY-MM-DD HH:mm:ss')
    let month2 = dayjs().endOf('month').format('YYYY-MM-DD HH:mm:ss')
    const value = [month1, month2]
    return value
}
// 获取上月
export const getLastMonth = () => {
    let lastMonth1 = dayjs().add(-1, 'month').startOf('month').format('YYYY-MM-DD HH:mm:ss')
    let lastMonth2 = dayjs().add(-1, 'month').endOf('month').format('YYYY-MM-DD HH:mm:ss')
    const value = [lastMonth1, lastMonth2]
    return value
}
// 获取昨天
export const getLastDay = () => {
    let lastDay1 = dayjs().add(-1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
    let lastDay2 = dayjs().add(-1, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss')
    const value = [lastDay1, lastDay2]
    return value
}

27、图像框选

js实现框选图像多个区域并获取区域内图像信息_cropper.js 多个选取-优快云博客

28、获取图片的宽和高

calculateImageSize(res.url).then(function({width, height}) {
                // 通过ES6的结构赋值来得到图片的宽和高
                console.log(width,height);
                localStorage.setItem('imgWidth',width)
            });


const calculateImageSize = function(url) {
        return new Promise(function(resolve, reject) {
            const image = document.createElement("img");
            image.addEventListener("load", function(e) {
                resolve({
                    width: e.target.width,
                    height: e.target.height,
                });
            });

            image.addEventListener("error", function() {
                reject();
            });

            // 将图片的url地址添加到图片地址中
            image.src = url;
        });
    }

29、综合设置元素样式

 setStyle(oDiv, {width: '200px', background: 'red'}); //第一种 
 oDiv.style.cssText="width: 200px; height:300px; background:yellow;"; //第二种 使用cssText 

30、canvas画虚线

JavaScript全解析——canvas 绘制形状(下) - 知乎

31、图片多重裁剪

js实现框选图像多个区域并获取区域内图像信息_cropper.js 多个选取-优快云博客

32、canvas简易裁剪框

<template>
    <div class="rectangle">
      <div class="imgBox">
        <img :src="imgUrl" alt="" />
      </div>
      <div class="rectBox"></div>
      <div class="canvasBox">
        <canvas
          ref="canvas"
          :height="imgWidth"
          :width="imgHeight"
          @mousedown="mouseDown"
          @mousemove="mouseMove"
          @mouseup="mouseUp"
        ></canvas>
      </div>
      <div class="canvasBox canvasBoxx">
        <canvas
          ref="canvasBox"
          :height="imgWidth"
          :width="imgHeight"
          @mousedown="mouseDown"
          @mousemove="mouseMove"
          @mouseup="mouseUp"
        ></canvas>
      </div>
      <!-- 删除 -->
      <div class="delIcon" @click="delData">
        <el-icon>
          <Close />
        </el-icon>
      </div>
      <div class="textInner">2</div>
    </div>
  </template>
   
  <script setup name='cutImg'>
  import $ from "jquery";
  // 初始化画布
  const canvas = ref();
  // 初始换记录布
  const canvasBox = ref();
  const imgUrl = ref(localStorage.getItem("stuNoImg"));
  // 初始化canvas
  let ctx = ref();
  let ctxx = ref();
  // 图片变化值
  const changeScale = ref(0);
  // 框选初始值
  const xVal = ref(0);
  const yVal = ref(0);
  // 框选结束值
  const endValX = ref(0);
  const endValY = ref(0);
  // 当前对象
  const { proxy } = getCurrentInstance();
  // 点击flag
  const flag = ref(false);
  // 图片宽高
  const imgWidth = ref(localStorage.getItem("imgWidth"));
  const imgHeight = ref(localStorage.getItem("imgHeight"));
  // const tempTemp=ref();
  // 选择的数组
  const rectData = ref([]);
  // 需要删除的rect
  const delRect = ref();
  // 删除元素的索引
  const nowDelDataIndex=ref();
  const mouseDown = (e) => {
    // 记录初始值
    flag.value = true;
    xVal.value = e.offsetX;
    yVal.value = e.offsetY;
    ctxx.clearRect(0, 0, canvasBox.value.width, canvasBox.value.height);
    isClickRect();
  };
  // 判断是否点击区域内
  const isClickRect = () => {
    rectData.value.map((item, index) => {
      let temp = getRectData(...item);
      let clickData = [];
      if (
        yVal.value >= temp[1] &&
        yVal.value <= temp[1] + temp[3] &&
        xVal.value <= temp[0] + temp[2] &&
        xVal.value >= temp[0]
      ) {
        let prop = [temp];
        clickData.push(prop);
        document.querySelector(".delIcon").style.cssText = `display:block;top:${
          temp[1] + "px"
        };left:${temp[0] + temp[2] + "px"}`;
        delRect.value = item;
        // delData(item)
        drawStrokeRectSel(...item,index);
        changeTextColor(index,'yellow')
      }else{
        drawStrokeRectS(...item,index,false);
        changeTextColor(index,'red')
      }
    });
  };
  // 改字体颜色
  const changeTextColor=(index,color)=>{
    let tempEle=document.querySelectorAll(".textInner")[index+1];
    tempEle.style.color=color
  }
  // 删除画布
  const delData = (item) => {
    let indext = rectData.value.indexOf(delRect.value);
    let parent = document.querySelector(".rectangle");
    let child = document.querySelectorAll(".textInner");
    nowDelDataIndex.value=indext;
    // 遍历所有元素并移除它们
    for (var i = 1; i < child.length; i++) {
        var childTemp = child[i];
        childTemp.parentNode.removeChild(childTemp); // 从父节点中移除该元素
    }
    rectData.value.splice(indext, 1);
    // 清除所有画布
    ctxx.clearRect(0, 0, canvasBox.value.width, canvasBox.value.height);
    rectData.value.map((itemt,indexs) => {
      drawStrokeRectS(...itemt,indexs,true);
    });
    document.querySelector(".delIcon").style.cssText = `display:none;`;
  };
  // 鼠标移动划线
  const mouseMove = (e) => {
    drawStrokeRect(
      xVal.value,
      yVal.value,
      e.offsetX - xVal.value,
      e.offsetY - yVal.value
    );
  };
  // 鼠标抬起
  const mouseUp = (e) => {
    flag.value = false;
    ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
    endValX.value = e.offsetX;
    endValY.value = e.offsetY;
    let x = xVal.value;
    let y = yVal.value;
    let width = endValX.value - xVal.value;
    let height = endValY.value - yVal.value;
    if (Math.abs(width) > 0 && Math.abs(height) > 0) {
      rectData.value.push([x, y, width, height]);
      let child = document.querySelectorAll(".textInner");
    // 遍历所有元素并移除它们
    for (var i = 1; i < child.length; i++) {
        var childTemp = child[i];
        childTemp.parentNode.removeChild(childTemp); // 从父节点中移除该元素
    }
      rectData.value.map((item, index) => {
        drawStrokeRectS(...item, index,true);
      });
    }
  };
  // 选中框选
  const drawStrokeRectSel = (x, y, w, h,index) => {
    ctxx.beginPath();
    ctxx.strokeStyle = "yellow";
    ctxx.strokeRect(x, y, w, h);
  };
  // 获取正确的数值
  const getRectData = (x, y, w, h) => {
    let minx = x;
    let miny = y;
    let maxx = x + w;
    let maxy = y + h;
    if (w < 0 && h > 0) {
      let tem = Math.abs(w);
      minx = x - tem;
      maxx = x;
      console.log(xVal.value, yVal.value, minx, miny, maxx, maxy);
    } else if (w > 0 && h < 0) {
      let tem = Math.abs(h);
      miny = y - tem;
      maxy = y;
    } else if (w < 0 && h < 0) {
      let temx = Math.abs(w);
      minx = x - temx;
      let temy = Math.abs(h);
      miny = y - temy;
      maxx = x;
      maxy = y;
    }
    return [minx, miny, maxx - minx, maxy - miny];
  };
  // 未选中数组划线
  const drawStrokeRectS = (x, y, w, h, index,isInsert) => {
    console.log(x,y,w,h,index);
    ctxx.beginPath();
    ctxx.setLineDash([ 5, 10 ])
    ctxx.strokeStyle = "red";
    ctxx.lineWidth = 10;
    ctxx.strokeRect(x, y, w, h);
    // 获取准确的数值
    let rectData = getRectData(x, y, w, h);
    // 填入数字顺序
   if(isInsert){
    var temp = document.querySelectorAll(".textInner")[0].cloneNode(true);
    temp.innerText = index;
    temp.style.cssText = `display:block;position:absolute;top:${
      rectData[1] + rectData[3] / 2 + "px"
    };left:${rectData[0] + rectData[2] / 2 + "px"};z-index:999999999;`;
    document.querySelector(".rectangle").appendChild(temp);
   }
  };
  // 划线方法
  const drawStrokeRect = (x, y, w, h) => {
    if (flag.value) {
      ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
      ctx.beginPath();
      ctx.setLineDash([ 5, 10 ]);
      ctx.lineWidth = 10
      ctx.strokeStyle = "red";
      ctx.strokeRect(x, y, w, h);
    }
  };
  // 初始化画布
  const initContext = () => {
    ctx = canvas.value.getContext("2d");
    ctxx = canvasBox.value.getContext("2d");
    if (imgWidth.value > 1500) {
      changeScale.value = 0.5;
      document.querySelector(".imgBox img").style.width =
        imgWidth.value / 2 + "px";
    }
  };
  onMounted(() => {
    initContext();
  });
  </script>
   
  <style scoped lang="scss">
  .imgBox {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0px;
    left: 0;
    object-fit: contain;
    img {
      width: 100%;
    }
  }
  .canvasBox {
    position: absolute;
    top: 0px;
    left: 0;
    z-index: 9999;
  }
  .canvasBoxx {
    z-index: 99999;
  }
  .rectangle {
    position: relative;
    top: 0;
    left: 0;
    height: 100%;
    background-color: #fff;
    color: red;
    font-size: 20px;
    font-weight: 700;
  }
  .rectBox {
    position: absolute;
    top: 0;
    left: 0;
  }
  .delIcon {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 9999999999999;
    color: red;
    display: none;
    width: 20px;
    height: 20px;
    font-weight: bold;
  }
  .textInner {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    text-align: center;
    line-height: 0;
    color: red;
    font-size: 30px;
    font-weight: 700;
    display: none;
    z-index: 999999999999999;
  }
  </style>

33、websocket交互

WebSocket前后端交互-优快云博客

34、正则表达式

正则表达式大全 - 简书

35、vscode自动补全

VScode 设置与取消回车enter自动补全快捷键_vscode回车自动补全-优快云博客

36、elementUI表单双层校验 

element 的el-form双层循环嵌套动态校验vue3_el-from动态循环2层编辑表单校验-优快云博客

37、vacode回车后自动补全失效问题 

vscode智能提示失效解决方法_vscode code suggestions are disabled-优快云博客

38、图片添加注释

 VUE项目开发,使用canvas实现图片签名编辑手写板功能_vue3 canvas实现图片贴文字功能-优快云博客

<template>
  <div class="modal-body">
    <div class="container">
      <div class="titleTop">考试阅卷</div>
      <div class="drawContBox"></div>
      <div class="contBox">
        <div class="btnImgBox">
          <div class="btnBox">
            <el-pagination
              small
              layout="prev, pager, next"
              :total="nowTopTotal"
              :page-size="1"
              v-model:current-page="currentPageNow"
              @current-change="topCurChange"
            />
          </div>
          <div class="imgBox" v-if="hasImg">
            <div class="canvas_box">
              <div class="canvas_container">
                <div class="canvas_wrapper">
                  <canvas
                    style="box-shadow: 0px 0px 5px #ccc; margin-top: -50px"
                    id="canvas"
                    ref="canvas"
                    width="600"
                    height="900"
                  ></canvas>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="inputBox">
          <div class="titleName">
            批改试卷区 <el-icon><WarningFilled /></el-icon>
          </div>
          <div class="inputBox1">
            <el-form ref="form" label-width="90px">
              <!-- <el-form-item label="文本颜色:">
                <el-color-picker
                  v-model="color"
                  class="mt-15"
                ></el-color-picker>
              </el-form-item> -->
              <el-form-item label="温馨提示:">
                <el-button
                  class="mt-15"
                  style="width: 100%; color: white"
                  type="info"
                  :icon="EditPen"
                  >注意批改后保存批改结果!</el-button
                >
              </el-form-item>
              <el-form-item label="功能选择:">
                <div class="tool-container">
                  <div
                    class="icon-div icon"
                    @click="isShowDrawPane = !isShowDrawPane"
                  >
                    <el-icon color="white"><PieChart /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="filterObject('erase')">
                    <el-icon color="white"><Hide /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="filterObject('undefined')">
                    <el-icon color="white"><Cloudy /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="filterObject('line')">
                    <el-icon color="white"><Minus /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="filterObject('arrows')">
                    <el-icon color="white"><TopRight /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="filterObject('rect')">
                    <el-icon color="white"><Crop /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="filterObject('circle')">
                    <el-icon color="white"><Compass /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="filterObject('text')">
                    <el-icon color="white"><Edit /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="clearCanvas()">
                    <el-icon color="white"><Delete /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="redo()">
                    <!-- <svg-icon :icon-class="historyImageData.length > 0 ? 'redo' : 'grey-redo' " scale="4"></svg-icon> -->
                    <el-icon color="white"><Back /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="cancelRedo()">
                    <!-- <svg-icon :icon-class="newHistoryImageData.length > 0 ? 'cancelRedo' : 'grey-cancelRedo' " scale="4"></svg-icon> -->
                    <el-icon color="white"><Right /></el-icon>
                  </div>
                  <div class="icon-div icon" @click="downLoad()">
                    <el-icon color="white"><Download /></el-icon>
                  </div>
                  <div class="drawPane" v-show="isShowDrawPane">
                    <div @click="isShowDrawPane = false" class="closeIcon">
                      <el-icon color="red"><Close /></el-icon>
                    </div>
                    <div class="colorClass">画笔大小</div>
                    <input
                      type="range"
                      id="lwRange"
                      min="1"
                      max="10"
                      value="1"
                      @change="LwRangeBtn"
                    />
                    <div class="colorClass">画笔颜色</div>
                    <input
                      type="color"
                      id="lcolor"
                      value="#FF1493"
                      @change="LcolorBtn"
                    />
                  </div>
                </div>
              </el-form-item>
              <!-- @click="onDel" -->
              <el-form-item label="该题得分:">
                <el-input v-model="subData.score"></el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="pageNation">
            <el-pagination
              :page-size="1"
              background
              @current-change="handleCurrentChange"
              layout="prev, pager, next"
              :total="total"
            />
          </div>
          <div class="btnBox1">
            <div class="btn" @click="saveSubData">保存</div>
          </div>
        </div>
      </div>
      <textarea
        id="textarea"
        name="textBox"
        cols="9"
        rows="1"
        class="text-style"
        v-show="isShowText"
      ></textarea>
    </div>
  </div>
</template>

<script setup>
import { upScoreData, getScoreInfo, upScoreInfo } from "@/api/upScore/index";
const props = defineProps({
  testCheckList: {
    type: Array,
  },
});
const form = ref({});
const isShowDrawPane = ref(false);
const canvas = ref(null);
const context = ref(null);
const lwidth = ref(2);
const lcolor = ref("#FF1493");
const textColor = ref("#FF1493");
const color = ref("");
const oldPage = ref(0);
const nowTopTotal = ref(0);
const paintTypeArr = ref({
  painting: false,
  erase: false,
  line: false,
  arrows: false,
  rect: false,
  circle: false,
  text: false,
});
const topCurChange = (val) => {
  historyImageData.value=[];
  window.pageYoffset = 0;
  document.documentElement.scrollTop = 0;
  document.body.scrollTop = 0;
  let flag = false;
  hasSeeImg.value.push(val - 1);
  let canvas = document.querySelector("canvas");
  proxy.$nextTick(() => {
    const url = canvas.toDataURL("image/jpeg", 0.5);
    // 保存上一个注释
    if (oldPage.value > val) {
      flag = true;
      oldPage.value = val;
      subData.value.base64Lists[val] = url;
    } else {
      flag = false;
      oldPage.value = val;
      subData.value.base64Lists[val - 2] = url;
    }
    if (subData.value.base64Lists[val - 1]) {
      // listen()
      init(subData.value.base64Lists[val - 1]);
    } else {
      getBase64(baseURL + nowSeeImgs.value[val - 1]);
    }
  });
};
const hasImg = ref(true);
//最近一次的canvas图片的数据
const imageData = ref(null);

//是否显示文字编写框
const isShowText = ref(false);
//保存画布图片历史的数据
const historyImageData = ref([]);
//保存已被撤销的历史画布图片数据
const newHistoryImageData = ref([]);
const socket = ref(null);
const img = ref(null);
const filterType = ref(undefined);
const loading = ref(false);
const imgNewUrl = ref("");
const nowSeeImgs = ref([]);
const baseURL = import.meta.env.VITE_APP_BASE_API;
const { proxy } = getCurrentInstance();
const total = ref(0);
const currentPageNow = ref(1);
const hasSeeImg = ref([]);
const testListId = ref("");
const scoreNum = ref(0);
const nowMouseEventX = ref(0);
const nowMouseEventY = ref(0);
const lastHasImgs=ref([]);
// 提交的数据
const subData = ref({
  id: "",
  score: 0,
  base64Lists: [],
});
watch(
  () => color,
  (value) => (context.value.strokeStyle = value)
);
const saveSubData = () => {
  let canvas = document.querySelector("canvas");
  const url = canvas.toDataURL("image/jpeg", 0.5);
  subData.value.base64Lists[currentPageNow.value - 1] = url;
  console.log(subData.value.base64Lists);
  lastHasImgs.value=subData.value.base64Lists;
  console.log(nowTopTotal.value, "当前总页");
  console.log(hasSeeImg.value, "已查看图片");
  console.log(nowSeeImgs.value, "总图片");
  console.log(Array.from(new Set(hasSeeImg.value)));
  let calcutLength = Array.from(new Set(hasSeeImg.value));
  let lastNoArr=[];
  if (calcutLength.length < nowTopTotal.value) {
    let tempArr = [];
    for (let index = 0; index < nowTopTotal.value; index++) {
      tempArr.push(index);
    }
    lastNoArr = findMissingValues(tempArr, calcutLength);
    lastNoArr.map((item, index) => {
      console.log(nowSeeImgs.value[item]);
      subData.value.base64Lists[item] = nowSeeImgs.value[item];
    });
   
  }
  setTimeout(()=>{
    console.log(subData.value);
    subData.value.base64Lists.map(item=>{
      console.log(item);
    })
  },2000)
  upScoreInfo(subData.value).then((res) => {
    proxy.$modal.msgSuccess("保存成功");
    lastNoArr.map((item, index) => {
      subData.value.base64Lists.splice(item,1);
    });
  });
};
const findMissingValues = (arr1, arr2) => {
  var missing = []; // 保存结果的数组
  for (var i = 0; i < arr1.length; i++) {
    if (!arr2.includes(arr1[i])) {
      missing.push(arr1[i]);
    }
  }
  return missing;
};
const LwRangeBtn = () => {
  lwidth.value = parseInt(document.getElementById("lwRange").value);
};
const LcolorBtn = () => {
  context.value.fillStyle = document.getElementById("lcolor").value;
  context.value.strokeStyle = document.getElementById("lcolor").value;
  textColor.value = document.getElementById("lcolor").value;
};
const handleCurrentChange = (val) => {
  historyImageData.value=[];
  currentPageNow.value = 1;
  hasImg.value = true;
  subData.value.score = 0;
  hasSeeImg.value = [];
  getScoreListInf(props.testCheckList[val - 1].id);
  testListId.value = props.testCheckList[val - 1].id;
  subData.value.id = testListId.value;
  subData.value.base64Lists = [];
};
//撤销
const redo = () => {
  let historyImageData1 = historyImageData.value;
  let newHistoryImageData1 = newHistoryImageData.value;
  if (historyImageData1.length > 0) {
    let hisImg = historyImageData1.pop();
    newHistoryImageData1.push(hisImg);
    if (historyImageData1.length === 0) {
      imageData.value = null;
      clearCanvas();
      init();
    } else {
      context.value.putImageData(
        historyImageData1[historyImageData1.length - 1],
        0,
        0
      );
    }
  }
};
//反撤销
const cancelRedo = () => {
  if (newHistoryImageData.value.length > 0) {
    const newHisImg = newHistoryImageData.value.pop();
    imageData.value = newHisImg;
    context.value.putImageData(newHisImg, 0, 0);
    historyImageData.value.push(newHisImg);
  }
};
//保存图片
const downLoad = () => {
  const imgUrl = canvas.value.toDataURL("image/png");
  const a = document.createElement("a");
  console.log(imgUrl);
  a.href = imgUrl;
  a.download = "绘图保存记录" + new Date().getTime();
  a.target = "_blank";
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  console.log(imageData.value);
};
//监听鼠标,用于画笔任意绘制和橡皮擦
const listen = () => {
  proxy.$nextTick(() => {
    let lastPoint = { x: undefined, y: undefined };
    let rect = canvas.value.getBoundingClientRect();
    console.log(rect, "rect");
    var scaleX = canvas.value.width / rect.width;
    var scaleY = canvas.value.height / rect.height;
    console.log(scaleX, "scaleX");
    console.log(scaleY, "scaleY");
    let textPoint = { x: undefined, y: undefined };

    canvas.value.onmousedown = function (e) {
      paintTypeArr.value["painting"] = true;

      let x1 = e.clientX - 25;
      let y1 = e.clientY;
      // x1 -= rect.left;
      y1 -= rect.top;
      lastPoint = { x: x1 * scaleX, y: y1 * scaleY };
      console.log(paintTypeArr.value["text"]);
      if (paintTypeArr.value["text"]) {
        let textarea = document.getElementById("textarea");
        if (isShowText.value) {
          let textContent = textarea.value;
          isShowText.value = false;
          textarea.value = "";
          console.log(textPoint.x, textPoint.y, "textPoint.x, textPoint.y,");
          drawText(textPoint.x, textPoint.y, textContent);
        } else if (!isShowText.value) {
          isShowText.value = true;
          textarea.style.left = lastPoint.x + "px";
          textarea.style.top = lastPoint.y + 160 + "px";
          textarea.style.color = textColor.value;
          textPoint = { x: lastPoint.x, y: lastPoint.y };
        }
      }

      if (paintTypeArr.value["erase"]) {
        let ctx = context.value;
        ctx.save();
        ctx.globalCompositeOperation = "destination-out";
        ctx.beginPath();
        let radius = lwidth.value / 2 > 5 ? lwidth.value / 2 : 5;
        ctx.arc(lastPoint.x, lastPoint.y, radius, 0, 2 * Math.PI);
        ctx.clip();
        ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
        ctx.restore();
      }

      var thee = e ? e : window.event;
      stopBubble(thee);
    };
    canvas.value.onmousemove = function (e) {
      nowMouseEventX.value = e.offsetX;
      nowMouseEventY.value = e.offsetY;
      let x2 = e.clientX;
      let y2 = e.clientY;
      x2 -= rect.left;
      y2 -= rect.top;
      let newPoint = { x: x2 * scaleX + 160, y: y2 * scaleY };

      if (isPainting()) {
        drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
        lastPoint = newPoint;
      } else if (paintTypeArr.value["erase"]) {
        if (!lastPoint.x && !lastPoint.y) {
          return;
        }
        handleErase(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
        lastPoint = newPoint;
      } else if (paintTypeArr.value["line"]) {
        // self.clearCanvas()
        imageData.value && context.value.putImageData(imageData.value, 0, 0);
        drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
      } else if (paintTypeArr.value["arrows"]) {
        // self.clearCanvas()
        imageData.value && context.value.putImageData(imageData.value, 0, 0);
        drawArrow(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
      } else if (paintTypeArr.value["rect"]) {
        // self.clearCanvas()
        imageData.value && context.value.putImageData(imageData.value, 0, 0);
        drawRect(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
      } else if (paintTypeArr.value["circle"]) {
        // self.clearCanvas()

        imageData.value && context.value.putImageData(imageData.value, 0, 0);
        console.log(imageData.value);
        drawCircle(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
      }

      var thee = e ? e : window.event;
      stopBubble(thee);
    };
    canvas.value.onmouseup = function () {
      lastPoint = { x: undefined, y: undefined };
      canvasDraw();
      console.log(123);
      filterObject(filterType.value);
    };
  });
};
const keyDowm = () => {
  document.onkeydown = function (e) {
    var keyNum = window.event ? e.keyCode : e.which; 
    if (keyNum == 68) {
      console.log(nowMouseEventX.value, nowMouseEventY.value);
      context.value.font = "24px Courier New";
      context.value.fillStyle = color;
      context.value.textAlign = "end"; //文字右对齐
      context.value.fillText("√", nowMouseEventX.value, nowMouseEventY.value); 
    }
    if (keyNum == 88) {
      context.value.font = "44px Courier New";
      context.value.fillStyle = color;
      context.value.textAlign = "end"; 
      context.value.fillText("×", nowMouseEventX.value, nowMouseEventY.value);
    }
  };
};
//更新绘画类型数组paintTypeArr的状态
const filterObject = (type) => {
  console.log(type);
  filterType.value = type;
  if (!type) {
    for (const key in paintTypeArr.value) {
      paintTypeArr.value[key] = false;
    }
  } else {
    for (const key in paintTypeArr.value) {
      key === type
        ? (paintTypeArr.value[key] = true)
        : (paintTypeArr.value[key] = false);
      console.log(paintTypeArr.value[key]);
    }
  }
};
//定格画布图片
const canvasDraw = () => {
  imageData.value = context.value.getImageData(
    0,
    0,
    canvas.value.width,
    canvas.value.height
  );
  historyImageData.value.push(imageData.value);
  console.log(historyImageData.value);
  console.log(imageData.value);
};
//清屏
const clearCanvas = () => {
  console.log(context.value);
  context.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
  init(imgNewUrl.value);
  console.log(imageData.value);
};
//画圆
const drawCircle = (circleX, circleY, endX, endY) => {
  console.log(circleX, circleY, endX, endY);
  let ctx = context.value;
  let radius = Math.sqrt(
    (circleX - endX) * (circleX - endX) + (circleY - endY) * (circleY - endY)
  );
  ctx.beginPath();
  ctx.arc(circleX, circleY, radius, 0, Math.PI * 2, true);
  ctx.stroke();
};
//绘制矩形
const drawRect = (topLeftX, topLeftY, botRightX, botRightY) => {
  let ctx = context.value;
  ctx.strokeRect(
    topLeftX,
    topLeftY,
    Math.abs(botRightX - topLeftX),
    Math.abs(botRightY - topLeftY)
  );
};
//画箭头
const drawArrow = (fromX, fromY, toX, toY) => {
  let ctx = context.value;
  var headlen = 10; //自定义箭头线的长度
  var theta = 45; //自定义箭头线与直线的夹角,个人觉得45°刚刚好
  var arrowX, arrowY; //箭头线终点坐标
  // 计算各角度和对应的箭头终点坐标
  var angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI;
  var angle1 = ((angle + theta) * Math.PI) / 180;
  var angle2 = ((angle - theta) * Math.PI) / 180;
  var topX = headlen * Math.cos(angle1);
  var topY = headlen * Math.sin(angle1);
  var botX = headlen * Math.cos(angle2);
  var botY = headlen * Math.sin(angle2);
  ctx.beginPath();
  //画直线
  ctx.moveTo(fromX, fromY);
  ctx.lineTo(toX, toY);

  arrowX = toX + topX;
  arrowY = toY + topY;
  //画上边箭头线
  ctx.moveTo(arrowX, arrowY);
  ctx.lineTo(toX, toY);

  arrowX = toX + botX;
  arrowY = toY + botY;
  //画下边箭头线
  ctx.lineTo(arrowX, arrowY);

  ctx.stroke();
  ctx.closePath();
};
//橡皮擦
const handleErase = (x1, y1, x2, y2) => {
  let ctx = context.value;
  //获取两个点之间的剪辑区域四个端点
  var asin = radius * Math.sin(Math.atan((y2 - y1) / (x2 - x1)));
  var acos = radius * Math.cos(Math.atan((y2 - y1) / (x2 - x1)));
  var x3 = x1 + asin;
  var y3 = y1 - acos;
  var x4 = x1 - asin;
  var y4 = y1 + acos;
  var x5 = x2 + asin;
  var y5 = y2 - acos;
  var x6 = x2 - asin;
  var y6 = y2 + acos; //保证线条的连贯,所以在矩形一端画圆

  ctx.save();
  ctx.beginPath();
  ctx.globalCompositeOperation = "destination-out";
  let radius = lwidth.value / 2 > 5 ? lwidth.value / 2 : 5;
  ctx.arc(x2, y2, radius, 0, 2 * Math.PI);
  ctx.clip();
  ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
  ctx.restore(); //清除矩形剪辑区域里的像素

  ctx.save();
  ctx.beginPath();
  ctx.globalCompositeOperation = "destination-out";
  ctx.moveTo(x3, y3);
  ctx.lineTo(x5, y5);
  ctx.lineTo(x6, y6);
  ctx.lineTo(x4, y4);
  ctx.closePath();
  ctx.clip();
  ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  ctx.restore();
};
//判断是否是自由绘画模式
const isPainting = () => {
  for (let key in paintTypeArr.value) {
    if (key !== "painting" && paintTypeArr.value[key]) {
      return false;
    }
  }
  if (paintTypeArr.value["painting"]) {
    return true;
  }
  return false;
};
//画线
const drawLine = (fromX, fromY, toX, toY) => {
  let ctx = context.value;
  ctx.beginPath();
  ctx.lineWidth = lwidth.value;
  ctx.lineCap = "round";
  ctx.lineJoin = "round";
  ctx.moveTo(fromX, fromY);
  ctx.lineTo(toX, toY);
  ctx.stroke();
  ctx.closePath();
};
//阻止事件冒泡
const stopBubble = (evt) => {
  if (evt.stopPropagation) {
    evt.stopPropagation();
  } else {
    //ie
    evt.cancelBubble = true;
  }
};
//画文字
const drawText = (startX, startY, content) => {
  let ctx = context.value;
  ctx.save();
  ctx.beginPath();
  ctx.font = "25px orbitron";
  ctx.textBaseline = "top";
  ctx.fillText(content, parseInt(startX), parseInt(startY));
  ctx.restore();
  ctx.closePath();
};
const init = (urls) => {
  proxy.$nextTick(() => {
    canvas.value = document.getElementById("canvas");
    context.value = canvas.value.getContext("2d");
    imageData.value && context.value.putImageData(imageData.value, 0, 0);
    let img = new Image();
    img.setAttribute("crossOrigin", "anonymous");
    let url = urls; //重点之重,这是要编辑的图片base64,如图一
    img.src = url;
    img.onload = () => {
      if (img.complete) {
        canvas.value.setAttribute("width", img.width);
        canvas.value.setAttribute("height", img.height);
        context.value.drawImage(img, 0, 0, img.width, img.height);
        img = img;
        textColor.value = "#FF1493";
        context.value.fillStyle = "#FF1493";
        context.value.strokeStyle = "#FF1493";
      }
    };
  });
};
function getBase64(url) {
  let dataURL = "";
  let img = new Image();
  img.crossOrigin = "Anonymous";
  img.src = url;
  img.onload = () => {
    let canvas = document.createElement("canvas");
    let [widths, heights] = [img.width, img.height];
    canvas.width = widths;
    canvas.height = heights;
    canvas.getContext("2d").drawImage(img, 0, 0, widths, heights);
    dataURL = canvas.toDataURL("image/jpeg");
    imgNewUrl.value = dataURL;
    proxy.$nextTick(() => {
      init(dataURL);
    });
  };
}
const getScoreListInf = (id) => {
  getScoreInfo(id).then((res) => {
    scoreNum.value = res.data.score;
    subData.value.score = res.data.score;
    hasSeeImg.value.push(0);
    nowTopTotal.value = nowSeeImgs.value.length;
    nowSeeImgs.value = res.data.annotationsPath.split(",");
    proxy.$nextTick(() => {
      getBase64(baseURL + nowSeeImgs.value[0]);
    });
  });
};
onMounted(() => {
  init();
  listen();
  keyDowm();
  total.value = props.testCheckList.length;
  if (props.testCheckList.length > 0) {
    getScoreListInf(props.testCheckList[0].id);
    handleCurrentChange(1);
  } else {
    proxy.$modal.msgWarning("未查询到阅卷信息");
  }
});
</script>

<style lang="scss" scoped>
.container {
  .titleTop {
    height: 50px;
    background-color: #fff;
    text-align: center;
    line-height: 50px;
    font-size: 18px;
    font-weight: bold;
  }
  .drawContBox {
    display: flex;
  }
}
.tool-container {
  width: 460px;
  border: 2px solid rgb(245, 108, 108);
  border-radius: 5px;
  display: flex;
  justify-content: center;
  position: relative;
  // margin-top: 20px;
  // margin-bottom: 20px;
  align-items: center;
  height: 30px;
  background-color: rgb(245, 108, 108);
  padding-top: 5px;
}
.drawPane {
  padding: 10px 10px;
  height: 150px;
  position: absolute;
  top: 20px;
  left: 0px;
  border-radius: 5px;
  border: 2px solid orangered;
  background-color: #fff;
  z-index: 99999;
  .closeIcon {
    position: absolute;
    top: 4px;
    right: 4px;
  }
}
.close-draw-pane {
  position: absolute;
  right: 5px;
  top: 5px;
}
.icon-div {
  margin: 4px 12px;
}
.icon :hover {
  cursor: pointer;
}
input[type="range"] {
  -webkit-appearance: none;
  width: 180px;
  height: 24px;
  outline: none;
  margin-bottom: 3px;
}
input[type="range"]::-webkit-slider-runnable-track {
  background-color: orangered;
  height: 4px;
  border-radius: 5px;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: orange;
  cursor: pointer;
  margin-top: -4px;
}
.text-style {
  // float: left;
  position: absolute;
  font: 14px orbitron;
  word-break: break-all;
  background-color: #fff;
  position: absolute;
  top: 160px;
}
.colorClass {
  color: orange;
}
.svg-icon {
  font-size: 24px;
}
.contBox {
  position: relative;
  width: 100%;
  margin: auto;
  height: 100%;
  color: black;
  margin-top: 20px;
  display: flex;
  justify-content: space-around;
  // align-items: center;
  .btnImgBox {
    // width: 800px;
    // height: 1500px;
    background-color: rgb(255, 255, 255);
    position: relative;
    .answerText {
      width: 100%;
      margin-top: 60px;
      text-align: center;
    }
    .anText {
      margin-top: 50px;
      text-align: left;
      margin-left: 20px;
      font-weight: bold;
    }

    .btnBox {
      display: flex;
      width: 220px;
      justify-content: space-between;
      text-align: center;
      align-items: center;
      position: absolute;
      top: 10px;
      left: 10px;
      z-index: 10;
      // background-color: #fff;
      .btn {
        width: 100px;
        height: 30px;
        background-color: rgb(26, 179, 250);
        line-height: 30px;
        color: #fff;
        font-size: 16px;
        position: relative;
        cursor: pointer;
      }
    }
    .imgBox {
      width: 100%;
      height: 100%;
      margin-top: 20px;
    }
  }
  .inputBox {
    background-color: #fff;
    width: 30%;
    height: 550px;
    display: flex;
    flex-direction: column;
    .titleName {
      width: 100%;
      height: 80px;
      background-color: #fff;
      line-height: 100px;
      text-align: center;
      font-size: 20px;
      font-weight: bold;
      color: rgb(245, 108, 108);
      display: flex;
      align-items: center;
      justify-content: center;
    }
    // .title {
    //   font-size: 20px;
    //   width: 100%;
    //   text-align: center;
    //   margin-top: 20px;
    // }
    .singleScore {
      width: 100%;
      height: 50px;
    }
    .tipText {
      width: 100%;
      height: 50px;
    }
    .saveBtn {
      width: 100%;
      height: 50px;
    }
  }
  .inputBox1 {
    width: 95%;
    margin: auto;
    margin-top: 20px;
    padding-right: 10px;
    // height: 50px;
  }
  .btnBox1 {
    width: 89%;
    margin: auto;
    margin-top: 0px;
    .btn {
      width: 100%;
      height: 40px;
      background-color: rgb(26, 179, 250);
      margin-bottom: 60px;
      text-align: center;
      line-height: 40px;
      color: white;
      border-radius: 10px;
      cursor: pointer;
      position: relative;
      .spant {
        font-size: 12px;
        color: rgb(240, 237, 237);
        position: absolute;
        width: 15px;
        height: 20px;
      }
    }
    .btn1 {
      background-color: rgb(89, 166, 125);
    }
  }
  .pageNation {
    width: 350px;
    margin: auto;
    height: 50px;
    margin-left: 5px;
    text-align: center;
    display: flex;
    justify-content: space-around;
    margin-top: -20px;
  }
}
.canvas_box {
  width: 100%;
  height: 100%;
  .canvas_tool {
    // width: 80px;
    height: 100%;
    box-sizing: border-box;
    padding: 20px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    box-shadow: 0 0 10px #ccc;
    position: fixed;
    right: 0;
    top: 0;
    background: white;
  }
  .canvas_container {
    width: 100%;
    height: 100%;
    position: relative;
    margin-top: 50px;
    display: flex;
    justify-content: center;
    align-items: center;
    .canvas_wrapper {
      width: 100%;
      height: 100%;
      position: relative;
      padding-top: 50px;
      overflow: auto;
      top: 0;
      left: 0;
      // display: flex;
      // justify-content: center;
      // align-items: center;
    }
    .canvas_wrapper::-webkit-scrollbar-thumb {
      background-color: rgb(70, 156, 236);
    }
    .canvas_wrapper::-webkit-scrollbar-thumb:hover {
      background-color: rgb(228, 10, 10);
    }
  }
  .el-button + .el-button {
    margin-left: 0 !important;
  }
  .mt-15 {
    margin-top: 15px;
    width: 100%;
  }
}
</style>
<template>
      <div class="modal-body">
        <div class="container">
          <canvas height="570"
              id="canvas"
              ref="canvas"
              width="940"></canvas>
          <div class="tool-container">
            <div class="icon-div icon" @click="isShowDrawPane = !isShowDrawPane">
                <el-icon><PieChart /></el-icon>
            </div>
            <div class="icon-div icon" @click="filterObject('erase')">
                <el-icon><Hide /></el-icon>
            </div>
            <div class="icon-div icon" @click="filterObject('undefined')">
                <el-icon><Cloudy /></el-icon>
            </div>
            <div class="icon-div icon" @click="filterObject('line')">
                <el-icon><Minus /></el-icon>
            </div>
            <div class="icon-div icon" @click="filterObject('arrows')">
                <el-icon><TopRight /></el-icon>
            </div>
            <div class="icon-div icon" @click="filterObject('rect')">
                <el-icon><Crop /></el-icon>
            </div>
            <div class="icon-div icon" @click="filterObject('circle')">
                <el-icon><Compass /></el-icon>
            </div>
            <div class="icon-div icon" @click="filterObject('text')">
                <el-icon><Edit /></el-icon>
            </div>
            <div class="icon-div icon" @click="clearCanvas()">
                <el-icon><Delete /></el-icon>
            </div>
            <div class="icon-div icon" @click="redo()">
              <!-- <svg-icon :icon-class="historyImageData.length > 0 ? 'redo' : 'grey-redo' " scale="4"></svg-icon> -->
              <el-icon><Back /></el-icon>
            </div>
            <div class="icon-div icon" @click="cancelRedo()">
              <!-- <svg-icon :icon-class="newHistoryImageData.length > 0 ? 'cancelRedo' : 'grey-cancelRedo' " scale="4"></svg-icon> -->
              <el-icon><Right /></el-icon>
            </div>
            <div class="icon-div icon" @click="downLoad()">
              <el-icon><Download /></el-icon>
            </div>
            <div class="drawPane" v-show="isShowDrawPane">
              <div @click="isShowDrawPane = false" class="closeIcon">
                <el-icon color="red"><Close /></el-icon>
              </div>
              <div class="colorClass">画笔大小</div>
              <input type="range" id="lwRange" min="1" max="10" value="1" @change="LwRangeBtn"/>
              <div class="colorClass">画笔颜色</div>
              <input type="color" id="lcolor" value="#FF1493" @change="LcolorBtn"/>
            </div>
          </div>
          <textarea
            id="textarea"
            name="textBox"
            cols="9"
            rows="1"
            class="text-style"
            v-show="isShowText"
          ></textarea>
        </div>
        </div>
      <div slot="footer" class="dialog-footer">
        <el-button plain @click="closeDialog">取 消</el-button>
        <el-button type="primary" @click="submitBtn" class="g-background00BCD4" :disable="loading" :loading="loading">保 存</el-button>
      </div>
  </template>
   
  <script >
  //画笔颜色选择引入
  import pickerColor from './pickerColor'
  export default {
    props: {
      otherParameter: Object,//我这里传了对象是因为我的业务需求,可直接传baseUrl:String
    },
    components:{pickerColor},
    data() {
      return {
        form: {},
   
        isShowDrawPane: false,
        canvas: null,
        context: null,
        //线宽
        lwidth: 2,
        //画笔颜色
        lcolor: "#FF1493",
        textColor:"#FF1493",
        //维护绘画状态的数组
        paintTypeArr: {
          painting: false,
          erase: false,
          line: false,
          arrows: false,
          rect: false,
          circle: false,
          text: false,
        },
        //最近一次的canvas图片的数据
        imageData: null,
        //是否显示文字编写框
        isShowText: false,
        //保存画布图片历史的数据
        historyImageData:[],
        //保存已被撤销的历史画布图片数据
        newHistoryImageData:[],
        socket:null,
        img: null,
        filterType: undefined,
        loading: false
      };
    },
    watch: {
      color () {
        this.context.strokeStyle = this.color;
        // this.pickerVisible = false//颜色改变后消失
      }
    },
    mounted() {
      let self = this;
      self.init()  
      window.onresize = function () {
        self.init()  
      }  
      this.listen() 
    },
    methods: {
      LwRangeBtn() {
        this.lwidth = parseInt(document.getElementById("lwRange").value)  
      },
      LcolorBtn() {
        this.context.fillStyle = document.getElementById("lcolor").value  
        this.context.strokeStyle = document.getElementById("lcolor").value  
        this.textColor = document.getElementById("lcolor").value 
      },
   
      closeDialog() {
        this.$emit("onClose");
      },
      dataURLtoFile(dataURI, type) {
        let binary = atob(dataURI.split(',')[1]);
        let array = [];
        for(let i = 0; i < binary.length; i++) {
                  array.push(binary.charCodeAt(i));
        }
        return new Blob([new Uint8Array(array)], {type:type });
      },
       //初始化画布
      init() {
        this.$nextTick(()=>{
          this.canvas = document.getElementById("canvas")  
          this.context = this.canvas.getContext('2d')
          this.imageData && this.context.putImageData(this.imageData, 0, 0)  
          let img = new Image()
          img.setAttribute('crossOrigin', 'anonymous');
          let url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAUZJREFUOE+lkztOw0AYhOf3KyZSCqghriLHQTSUVHQ0XIKWE4BEAaLiBNRcgpZbhCRVcEBKBxLKg3W8P9ooa60fERJ259XON//O7BJqflRTjwwwDLxPZm6EcbJHwLIKPAr8e2Z5DcJ3+CZ21Z4M8Np2ZwRqgpGmLbF/2MfUhCixZHmzWRPdWDRyAPUzaHsrADYAORfi6HiKvlo3xcxYRRPhangpAw0hZun5yUki/HPtzICINs5bAeYkBGYGrU0IvAjjpFnMZmsLo7aXSsBSAgYvozjZqQq2ElAIbM2g1L0IP2ZPf05gionwxYyWCpYBOJZz2RnPH01IboKiWHdttAMp7ave++KhFOI2sd5oQmzLuuuMl7e5ezAIPAGGq8bWzsXzZhUDaRgLJwcYHjSemeRpt6IqE6SMpKSX3uTnrHQT//Owar/GX3nJnRFl79BcAAAAAElFTkSuQmCC";//重点之重,这是要编辑的图片base64,如图一
          img.src = url
          img.onload = () => {
            if (img.complete) {
              this.canvas.setAttribute('width', img.width)
              this.canvas.setAttribute('height', img.height)
              this.context .drawImage(img, 0, 0, img.width, img.height)
              this.img = img
              this.textColor = "#FF1493";
              this.context.fillStyle =  "#FF1493";
              this.context.strokeStyle =  "#FF1493";
            }
          }
        })
      },
      //监听鼠标,用于画笔任意绘制和橡皮擦
      listen() {
        this.$nextTick(()=>{
          let self = this  
          let lastPoint = { x: undefined, y: undefined }  
          let rect = self.canvas.getBoundingClientRect()  
          console.log(rect,"rect")
          var scaleX = self.canvas.width / rect.width  
          var scaleY = self.canvas.height / rect.height  
          console.log(scaleX,"scaleX")
          console.log(scaleY,"scaleY")
          let textPoint = { x: undefined, y: undefined }  
   
          self.canvas.onmousedown = function (e) {
            self.paintTypeArr["painting"] = true  
   
            let x1 = e.clientX  
            let y1 = e.clientY  
            x1 -= rect.left  
            y1 -= rect.top  
            lastPoint = { x: x1 * scaleX, y: y1 * scaleY }  
            console.log((self.paintTypeArr["text"]))
            if (self.paintTypeArr["text"]) {
              let textarea = document.getElementById("textarea")  
              if (self.isShowText) {
                let textContent = textarea.value  
                self.isShowText = false   
                textarea.value = ""  
                console.log(textPoint.x, textPoint.y,"textPoint.x, textPoint.y,")
                self.drawText(textPoint.x, textPoint.y, textContent) 
              } else if (!self.isShowText) {
                self.isShowText = true  
                textarea.style.left = lastPoint.x + "px"  
                textarea.style.top = lastPoint.y + 160 + "px"  
                textarea.style.color = self.textColor
                textPoint = { x: lastPoint.x, y: lastPoint.y }  
              }
            }
   
            if (self.paintTypeArr["erase"]) {
              let ctx = self.context  
              ctx.save()  
              ctx.globalCompositeOperation = "destination-out"  
              ctx.beginPath()  ;
              console.log(self);
              let radius = self.lWidth / 2 > 5 ? self.lWidth / 2 : 5  
              ctx.arc(lastPoint.x, lastPoint.y, radius, 0, 2 * Math.PI)  
              ctx.clip()  
              ctx.clearRect(0, 0, self.canvas.width, self.canvas.height)  
              ctx.restore()  
            }
   
            var thee = e ? e : window.event  
            self.stopBubble(thee)  
          }  
          self.canvas.onmousemove = function (e) {
            let x2 = e.clientX  
            let y2 = e.clientY  
            x2 -= rect.left  
            y2 -= rect.top  
            let newPoint = { x: x2 * scaleX, y: y2 * scaleY }  
   
            if (self.isPainting()) {
              self.drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y)  
              lastPoint = newPoint  
            } else if (self.paintTypeArr["erase"]) {
              if(!lastPoint.x && !lastPoint.y){return}
              self.handleErase(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y)  
              lastPoint = newPoint  
            } else if (self.paintTypeArr["line"]) {
              // self.clearCanvas()  
              self.imageData && self.context.putImageData(self.imageData, 0, 0)  
              self.drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y)  
            } else if (self.paintTypeArr["arrows"]) {
              // self.clearCanvas()  
              self.imageData && self.context.putImageData(self.imageData, 0, 0)  
              self.drawArrow(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y)  
            } else if (self.paintTypeArr["rect"]) {
              // self.clearCanvas()  
              self.imageData && self.context.putImageData(self.imageData, 0, 0)  
              self.drawRect(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y)  
            } else if (self.paintTypeArr["circle"]) {
              // self.clearCanvas()  
              
              self.imageData && self.context.putImageData(self.imageData, 0, 0) 
              console.log(self.imageData) 
              self.drawCircle(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y)  
            }
   
            var thee = e ? e : window.event  
            self.stopBubble(thee)  
          }  
          self.canvas.onmouseup = function () {
            lastPoint = { x: undefined, y: undefined }  
            self.canvasDraw()  
            console.log(123)
            self.filterObject(self.filterType)  
          }  
        })
      },
      //更新绘画类型数组paintTypeArr的状态
      filterObject(type) {
        this.filterType = type
        if (!type) {
          for (const key in this.paintTypeArr) {
            this.paintTypeArr[key] = false  
          }
        } else {
          for (const key in this.paintTypeArr) {
            key === type
              ? (this.paintTypeArr[key] = true)
              : (this.paintTypeArr[key] = false)  
          }
        }
      },
      //阻止事件冒泡
      stopBubble(evt) {
        if (evt.stopPropagation) {
          evt.stopPropagation()  
        } else {
          //ie
          evt.cancelBubble = true  
        }
      },
      //判断是否是自由绘画模式
      isPainting() {
        for (let key in this.paintTypeArr) {
          if (key !== "painting" && this.paintTypeArr[key]) {
            return false  
          }
        }
        if (this.paintTypeArr["painting"]) {
          return true  
        }
        return false  
      },
      //橡皮擦
      handleErase(x1, y1, x2, y2) {
        let ctx = this.context  
        //获取两个点之间的剪辑区域四个端点
        var asin = radius * Math.sin(Math.atan((y2 - y1) / (x2 - x1)))  
        var acos = radius * Math.cos(Math.atan((y2 - y1) / (x2 - x1)))  
        var x3 = x1 + asin  
        var y3 = y1 - acos  
        var x4 = x1 - asin  
        var y4 = y1 + acos  
        var x5 = x2 + asin  
        var y5 = y2 - acos  
        var x6 = x2 - asin  
        var y6 = y2 + acos   //保证线条的连贯,所以在矩形一端画圆
   
        ctx.save()  
        ctx.beginPath()  
        ctx.globalCompositeOperation = "destination-out"  
        let radius = this.lWidth / 2 > 5 ? this.lWidth / 2 : 5  
        ctx.arc(x2, y2, radius, 0, 2 * Math.PI)  
        ctx.clip()  
        ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)  
        ctx.restore()   //清除矩形剪辑区域里的像素
   
        ctx.save()  
        ctx.beginPath()  
        ctx.globalCompositeOperation = "destination-out"  
        ctx.moveTo(x3, y3)  
        ctx.lineTo(x5, y5)  
        ctx.lineTo(x6, y6)  
        ctx.lineTo(x4, y4)  
        ctx.closePath()  
        ctx.clip()  
        ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)  
        ctx.restore()  
      },
      //画线
      drawLine(fromX, fromY, toX, toY) {
        
        let ctx = this.context  
        ctx.beginPath()  
        ctx.lineWidth = this.lwidth  
        ctx.lineCap = "round"  
        ctx.lineJoin = "round"  
        ctx.moveTo(fromX, fromY)  
        ctx.lineTo(toX, toY)  
        ctx.stroke()  
        ctx.closePath()  
      },
      //画箭头
      drawArrow(fromX, fromY, toX, toY) {
        let ctx = this.context  
        var headlen = 10   //自定义箭头线的长度
        var theta = 45   //自定义箭头线与直线的夹角,个人觉得45°刚刚好
        var arrowX, arrowY   //箭头线终点坐标
        // 计算各角度和对应的箭头终点坐标
        var angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI  
        var angle1 = ((angle + theta) * Math.PI) / 180  
        var angle2 = ((angle - theta) * Math.PI) / 180  
        var topX = headlen * Math.cos(angle1)  
        var topY = headlen * Math.sin(angle1)  
        var botX = headlen * Math.cos(angle2)  
        var botY = headlen * Math.sin(angle2)  
        ctx.beginPath()  
        //画直线
        ctx.moveTo(fromX, fromY)  
        ctx.lineTo(toX, toY)  
   
        arrowX = toX + topX  
        arrowY = toY + topY  
        //画上边箭头线
        ctx.moveTo(arrowX, arrowY)  
        ctx.lineTo(toX, toY)  
   
        arrowX = toX + botX  
        arrowY = toY + botY  
        //画下边箭头线
        ctx.lineTo(arrowX, arrowY)  
   
        ctx.stroke()  
        ctx.closePath()  
      },
      //绘制矩形
      drawRect(topLeftX, topLeftY, botRightX, botRightY) {
        let ctx = this.context  
        ctx.strokeRect(
          topLeftX,
          topLeftY,
          Math.abs(botRightX - topLeftX),
          Math.abs(botRightY - topLeftY)
        )  
      },
      //画圆
      drawCircle(circleX, circleY, endX, endY) {
        console.log(circleX, circleY, endX, endY)
        let ctx = this.context  
        let radius = Math.sqrt(
          (circleX - endX) * (circleX - endX) +
            (circleY - endY) * (circleY - endY)
        )  
        ctx.beginPath()  
        ctx.arc(circleX, circleY, radius, 0, Math.PI * 2, true)  
        ctx.stroke()  
      },
      //画文字
      drawText(startX, startY, content) {
        let ctx = this.context  
        ctx.save()  
        ctx.beginPath()  
        ctx.font = "25px orbitron"  
        ctx.textBaseline = "top"  
        ctx.fillText(content, parseInt(startX), parseInt(startY))  
        ctx.restore()  
        ctx.closePath()  
      },
      //清屏
      clearCanvas() {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)  
        this.init()
        console.log(this.imageData) 
      },
      //定格画布图片
      canvasDraw() {
        this.imageData = this.context.getImageData(0,0,this.canvas.width,this.canvas.height)  
        this.historyImageData.push(this.imageData)
        console.log(this.historyImageData)
        console.log(this.imageData)
      },
      //撤销
      redo(){
        let historyImageData = this.historyImageData
        let newHistoryImageData = this.newHistoryImageData
        if(historyImageData.length > 0){
          let hisImg = historyImageData.pop()
          newHistoryImageData.push(hisImg)
          if(historyImageData.length === 0){
            this.imageData = null
            this.clearCanvas()
            this.init()
          }else{
            this.context.putImageData(historyImageData[historyImageData.length - 1],0,0)
          }
        }
      },
      //反撤销
      cancelRedo(){
        if(this.newHistoryImageData.length > 0){
          const newHisImg = this.newHistoryImageData.pop()
          this.imageData = newHisImg
          this.context.putImageData(newHisImg,0,0)
          this.historyImageData.push(newHisImg)
        }
      },
      //保存图片
      downLoad(){
        const imgUrl = this.canvas.toDataURL('image/png')
        const a = document.createElement('a')
        a.href = imgUrl
        a.download = '绘图保存记录' + (new Date).getTime()
        a.target = '_blank'
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a);
        console.log(this.imageData) 
      },
      submitBtn() {
          //防止多次点击提交
        this.loading = true;
        setTimeout(()=>{
            this.loading = false;
        },3000)
   
        let fileObj = {
          relativeType: 3,
          name:"编辑图片"
        }
        let canvas = document.getElementById('canvas')
        var file = canvas.toDataURL("image/png");
        var formData = new FormData();
        let blob= this.dataURLtoFile(file, 'image/jpg')
        let fileOfBlob = new File([blob], new Date()+'.jpg')
        formData.append('file', fileOfBlob);
        formData.append('relativeType', 3);
        formData.append('name', "编辑图片");
        //上传图片后提交保存,根据实际开发需求编写
       console.log(file.toString());
      },
    },
  };
  </script>
   
  <style lang="scss" scoped>
  .container {
    // width: 100%;
    // height: 100%;
    // margin: 10px auto;
    // overflow: hidden;
  }
  .tool-container {
    width: 500px;
    border: 2px solid orange;
    border-radius: 10px;
    display: flex;
    justify-content: center;
    position: relative;
    margin-top: 20px;
    margin-bottom: 20px;
    align-items: center;
    height: 40px;
  }
  .drawPane {
    padding: 10px 10px;
    height: 130px;
    position: absolute;
    top: 20px;
    left: 0px;
    border-radius: 5px;
    border: 2px solid orangered;
    background-color: #fff;
    .closeIcon{
        position: absolute;
        top: 4px;
        right: 4px;
    }
  }
  .close-draw-pane {
    position: absolute;
    right: 5px;
    top: 5px;
  }
  .icon-div {
    margin: 4px 12px;
  }
  .icon :hover {
    cursor: pointer;
  }
  input[type="range"] {
    -webkit-appearance: none;
    width: 180px;
    height: 24px;
    outline: none;
    margin-bottom: 3px;
  }
  input[type="range"]::-webkit-slider-runnable-track {
    background-color: orangered;
    height: 4px;
    border-radius: 5px;
  }
  input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: orange;
    cursor: pointer;
    margin-top: -4px;
  }
  .text-style {
    // float: left;
    position: absolute;
    font: 14px orbitron;
    word-break: break-all;
    background-color: #fff;
    position: absolute;
    top: 160px;
  }
  .colorClass {
    color: orange;
  }
  .svg-icon {
    font-size: 24px;
  }
  </style>

快捷打钩

<template>
  <div class="outBox">
    <div class="blockInfo">
      <div class="firInfo">
        <div class="toptitle toptitleBox">
          <div>题块名称</div>
          <div>阅卷进度</div>
        </div>
        <div
          class="blockList"
          :class="clickIndex == index ? 'blockListClick' : ''"
          v-for="(item, index) in blockList"
          :key="index"
          @click="changeBlock(item, index)"
        >
          <el-tooltip
            class="item"
            effect="dark"
            :content="item.locationName"
            placement="right"
          >
            <el-button class="blockBtn">{{ item.locationName }}</el-button>
          </el-tooltip>
          <!-- </div> -->
          <div class="itemProgress">{{ item.progress }}%</div>
        </div>
      </div>
    </div>
    <div class="blockInfo checkTest">
      <el-pagination
        small
        layout="prev, pager, next"
        @current-change="changeNum"
        :page-size="1"
        :current-page="topNowSelPage"
        :total="topTotal"
      >
      </el-pagination>
      <canvas height="570" id="canvas" ref="canvas" width="540"></canvas>
    </div>
    <div class="blockInfo paginationg">
      <div class="toptitle">
        功能选择
        <div style="width: 100%; margin: auto" class="pageStyle">
          <el-pagination
            layout="prev, pager, next"
            :page-size="1"
            :current-page="nowSelPage"
            @current-change="handleCurrentChange"
            :total="total"
          >
          </el-pagination>
        </div>
      </div>
      <div style="display: flex; align-items: center; padding-right: 5px">
        <span
          style="
            display: inline-block;
            width: 130px;
            text-align: center;
            color: #1296db;
          "
          >该题得分</span
        >
        <el-input v-model="subData.score" placeholder="输入分数"></el-input>
      </div>
      <div class="tool-container" style="margin-top: 35px">
        <div class="icon-div icon" @click="isShowDrawPane = !isShowDrawPane">
          <div class="iconfont icon-huaban" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="filterObject('erase')">
          <div class="iconfont icon-xiangpica" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="filterObject('undefined')">
          <div class="iconfont icon-quxian" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="filterObject('line')">
          <div class="iconfont icon--_zhixian" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="filterObject('arrows')">
          <div class="iconfont icon-jiantou" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="filterObject('rect')">
          <div class="iconfont icon-juxing" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="filterObject('checkDui')">
          <i class="el-icon-folder-checked" style="color: #1296db"></i>
        </div>
        <div class="icon-div icon" @click="filterObject('text')">
          <div class="iconfont icon-wenben" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="clearCanvas()">
          <div class="iconfont icon-shanchu" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="redo()">
          <!-- <svg-icon :icon-class="historyImageData.length > 0 ? 'redo' : 'grey-redo' " scale="4"></svg-icon> -->
          <div class="iconfont icon-chehui" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="cancelRedo()">
          <!-- <svg-icon :icon-class="newHistoryImageData.length > 0 ? 'cancelRedo' : 'grey-cancelRedo' " scale="4"></svg-icon> -->
          <div class="iconfont icon-fanchehui" style="color: #1296db"></div>
        </div>
        <div class="icon-div icon" @click="downLoad()">
          <i class="el-icon-s-claim" style="color: #1296db"></i>
        </div>
        <div class="drawPane" v-show="isShowDrawPane">
          <div @click="isShowDrawPane = false" class="closeIcon">
            <i class="el-icon-circle-close" style="color: #1296db"></i>
          </div>
          <div class="colorClass">画笔大小</div>
          <input
            type="range"
            id="lwRange"
            min="1"
            max="10"
            value="1"
            @change="LwRangeBtn"
          />
          <div class="colorClass">画笔颜色</div>
          <input type="color" id="lcolor" value="#FF1493" @change="LcolorBtn" />
        </div>
      </div>

      <el-button
        type="primary"
        style="width: 100%; margin-top: 15px"
        @click="submitBtn"
        >点击保存</el-button
      >
      <textarea
        id="textarea"
        name="textBox"
        cols="9"
        rows="1"
        class="text-style"
        v-show="isShowText"
      ></textarea>
    </div>
  </div>
</template>
   <script>
import {
  getBlockList,
  getBlockListInfo,
  postBlockListInfo,
} from "@/api/checkBlock/index";
import pickerColor from "./pickerColor.vue";
export default {
  data() {
    return {
      topTotal: 0,
      total: 0,
      blockList: [],
      exId: "",
      clickIndex: 0,
      isShowDrawPane: false,
      canvas: null,
      context: null,
      //线宽
      lwidth: 2,
      //画笔颜色
      lcolor: "#FF1493",
      textColor: "#FF1493",
      // 根路径
      baseURL: process.env.VUE_APP_BASE_API,
      //维护绘画状态的数组
      paintTypeArr: {
        painting: false,
        erase: false,
        line: false,
        arrows: false,
        rect: false,
        circle: false,
        text: false,
        checkDui: false,
      },
      //最近一次的canvas图片的数据
      imageData: null,
      //是否显示文字编写框
      isShowText: false,
      //保存画布图片历史的数据
      historyImageData: [],
      //保存已被撤销的历史画布图片数据
      newHistoryImageData: [],
      socket: null,
      img: null,
      filterType: undefined,
      loading: false,
      queryParams: {
        pageSize: 1,
        pageNum: 1,
      },
      nowBlockPageInfo: [],
      nowCanvasImg: "",
      subData: [],
      nowSelPage: 1,
      nowMouseEventX: 0,
      nowMouseEventY: 0,
      nowBlockName: "",
      topNowSelPage: 0,
    };
  },
  components: { pickerColor },
  watch: {
    color() {
      this.context.strokeStyle = this.color;
      // this.pickerVisible = false//颜色改变后消失
    },
  },
  methods: {
    changeNum(val) {
      this.topNowSelPage = val;
      this.init();
      if (this.nowBlockPageInfo[0].paths[val - 1].length > 70) {
        this.nowCanvasImg = this.nowBlockPageInfo[0].paths[val - 1];
      } else {
        this.nowCanvasImg =
          this.baseURL + this.nowBlockPageInfo[0].paths[val - 1];
      }
    },
    keyDowm() {
      let that = this;
      that.canvasDraw();
      document.onkeydown = function (e) {
        var keyNum = window.event ? e.keyCode : e.which;
        if (that.filterType == "checkDui") {
          if (keyNum == 68) {
            that.drawText(that.nowMouseEventX-7, that.nowMouseEventY-13, "√");
            setTimeout(() => {
              that.canvasDraw();
            }, 20);
          }
          if (keyNum == 67) {
            that.drawText(that.nowMouseEventX-7, that.nowMouseEventY-13, "x");
            setTimeout(() => {
              that.canvasDraw();
            }, 20);
          }
        }
      };
    },
    handleCurrentChange(val) {
      this.nowSelPage = val;
      this.queryParams.pageNum = val;
      this.topTotal = this.nowBlockPageInfo[0].paths.length;
      this.init();
      this.getBlockInfo(this.exId, this.nowBlockName, this.queryParams);
    },
    LwRangeBtn() {
      this.lwidth = parseInt(document.getElementById("lwRange").value);
    },
    LcolorBtn() {
      this.context.fillStyle = document.getElementById("lcolor").value;
      this.context.strokeStyle = document.getElementById("lcolor").value;
      this.textColor = document.getElementById("lcolor").value;
    },

    closeDialog() {
      this.$emit("onClose");
    },
    dataURLtoFile(dataURI, type) {
      let binary = atob(dataURI.split(",")[1]);
      let array = [];
      for (let i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
      }
      return new Blob([new Uint8Array(array)], { type: type });
    },
    //初始化画布
    init() {
      let that = this;
      that.$nextTick(() => {
        that.canvas = document.getElementById("canvas");
        that.context = that.canvas.getContext("2d");
        that.imageData && that.context.putImageData(that.imageData, 0, 0);
        let img = new Image();
        img.setAttribute("crossOrigin", "anonymous");
        let url = that.nowCanvasImg; //重点之重,这是要编辑的图片base64,如图一
        img.src = url;
        img.onload = () => {
          if (img.complete) {
            that.canvas.setAttribute("width", img.width);
            that.canvas.setAttribute("height", img.height);
            that.context.drawImage(img, 0, 0, img.width, img.height);
            that.img = img;
            that.textColor = "#FF1493";
            that.context.fillStyle = "#FF1493";
            that.context.strokeStyle = "#FF1493";
          }
          this.listen();
        };
        this.listen();
      });
    },
    //监听鼠标,用于画笔任意绘制和橡皮擦
    listen() {
      this.$nextTick(() => {
        let self = this;
        let lastPoint = { x: undefined, y: undefined };
        let rect = self.canvas.getBoundingClientRect();
        var scaleX = self.canvas.width / rect.width;
        var scaleY = self.canvas.height / rect.height;
        let textPoint = { x: undefined, y: undefined };
        self.canvas.onmousedown = function (e) {
          self.paintTypeArr["painting"] = true;
          let x1 = e.offsetX + 355;
          let y1 = e.offsetY + 92;
          x1 -= rect.left;
          y1 -= rect.top;
          lastPoint = { x: x1 * scaleX, y: y1 * scaleY };
          if (self.paintTypeArr["text"]) {
            let textarea = document.getElementById("textarea");
            if (self.isShowText) {
              let textContent = textarea.value;
              self.isShowText = false;
              textarea.value = "";
              self.drawText(textPoint.x - 3, textPoint.y - 5, textContent);
            } else if (!self.isShowText) {
              self.isShowText = true;
              textarea.style.left = lastPoint.x + 350 + "px";
              textarea.style.top = lastPoint.y + 79 + "px";
              textarea.style.color = self.textColor;
              textPoint = { x: lastPoint.x, y: lastPoint.y };
            }
          }
          if (self.paintTypeArr["erase"]) {
            let ctx = self.context;
            ctx.save();
            ctx.globalCompositeOperation = "destination-out";
            ctx.beginPath();
            let radius = self.lwidth / 2 > 5 ? self.lwidth / 2 : 5;
            ctx.arc(lastPoint.x, lastPoint.y, radius, 0, 2 * Math.PI);
            ctx.clip();
            ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
            ctx.restore();
          }
          self.canvasDraw();
          var thee = e ? e : window.event;
          self.stopBubble(thee);
        };
        self.canvas.onmousemove = function (e) {
          let x2 = e.offsetX + 355;
          let y2 = e.offsetY + 92;
          x2 -= rect.left;
          y2 -= rect.top;
          let newPoint = { x: x2 * scaleX, y: y2 * scaleY };
          self.nowMouseEventX = e.offsetX;
          self.nowMouseEventY = e.offsetY;
          if (self.isPainting()) {
            self.drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
            lastPoint = newPoint;
          }
          if (self.paintTypeArr["erase"]) {
            if (!lastPoint.x && !lastPoint.y) {
              return;
            }
            self.handleErase(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
            lastPoint = newPoint;
          }
          if (self.paintTypeArr["line"]) {
            // self.clearCanvas()
            self.imageData && self.context.putImageData(self.imageData, 0, 0);
            self.drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
          }
          if (self.paintTypeArr["arrows"]) {
            // self.clearCanvas()
            self.imageData && self.context.putImageData(self.imageData, 0, 0);
            self.drawArrow(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
          }
          if (self.paintTypeArr["rect"]) {
            // self.clearCanvas()

            self.imageData && self.context.putImageData(self.imageData, 0, 0);
            self.drawRect(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
          }
          if (self.paintTypeArr["circle"]) {
            self.imageData && self.context.putImageData(self.imageData, 0, 0);
            self.drawCircle(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
          }
          if (self.paintTypeArr["checkDui"]) {
            // self.drawText(self.nowMouseEventX, self.nowMouseEventY, "√");
            self.keyDowm();
            self.canvasDraw();
            // //       document.onkeydown = function (e) {
            // //   var keyNum = window.event ? e.keyCode : e.which;
            // //   if (that.filterType == "checkDC") {
            // //     if (keyNum == 68) {
            // //       self.drawText(that.nowMouseEventX, that.nowMouseEventY, '√');
            //       self.canvasDraw();
            // //     }
            // //     if (keyNum == 67) {
            // //       self.drawText(that.nowMouseEventX,that.nowMouseEventY, 'x');
            // //       self.canvasDraw();
            // //     }
            // //   }
            // // };
          }

          var thee = e ? e : window.event;
          self.stopBubble(thee);
        };
        self.canvas.onmouseup = function () {
          lastPoint = { x: undefined, y: undefined };
          self.canvasDraw();
          self.filterObject(self.filterType);
        };
      });
    },
    //更新绘画类型数组paintTypeArr的状态
    filterObject(type) {
      let that = this;
      that.filterType = type;
      // if (info&&info=='check') {
      //   document.addEventListener("keydown", function (e) {
      //     var keyNum = window.event ? e.keyCode : e.which;
      //     if (keyNum == 68) {
      //       that.drawText(that.nowMouseEventX, that.nowMouseEventY + 3, "√");
      //     }
      //     if (keyNum == 67) {
      //       that.drawText(that.nowMouseEventX, that.nowMouseEventY + 3, "×");
      //     }
      //   });
      // }
      if (!type) {
        for (const key in that.paintTypeArr) {
          that.paintTypeArr[key] = false;
        }
      } else {
        for (const key in that.paintTypeArr) {
          key === type
            ? (that.paintTypeArr[key] = true)
            : (that.paintTypeArr[key] = false);
        }
      }
      // console.log(that.paintTypeArr,info, "all");
      // if (type == "checkDC") {
      //   that.keyDowm();
      //   that.paintTypeArr["painting"] = true;
      // }
    },
    //阻止事件冒泡
    stopBubble(evt) {
      if (evt.stopPropagation) {
        evt.stopPropagation();
      } else {
        //ie
        evt.cancelBubble = true;
      }
    },
    //判断是否是自由绘画模式
    isPainting() {
      for (let key in this.paintTypeArr) {
        if (key !== "painting" && this.paintTypeArr[key]) {
          return false;
        }
      }
      if (this.paintTypeArr["painting"]) {
        return true;
      }
      return false;
    },
    //橡皮擦
    handleErase(x1, y1, x2, y2) {
      let ctx = this.context;
      var asin = radius * Math.sin(Math.atan((y2 - y1) / (x2 - x1)));
      var acos = radius * Math.cos(Math.atan((y2 - y1) / (x2 - x1)));
      var x3 = x1 + asin;
      var y3 = y1 - acos;
      var x4 = x1 - asin;
      var y4 = y1 + acos;
      var x5 = x2 + asin;
      var y5 = y2 - acos;
      var x6 = x2 - asin;
      var y6 = y2 + acos;

      ctx.save();
      ctx.beginPath();
      ctx.globalCompositeOperation = "destination-out";
      let radius = this.lwidth / 2 > 5 ? this.lwidth / 2 : 5;
      ctx.arc(x2, y2, radius, 0, 2 * Math.PI);
      ctx.clip();
      ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      ctx.restore();

      ctx.save();
      ctx.beginPath();
      ctx.globalCompositeOperation = "destination-out";
      ctx.moveTo(x3, y3);
      ctx.lineTo(x5, y5);
      ctx.lineTo(x6, y6);
      ctx.lineTo(x4, y4);
      ctx.closePath();
      ctx.clip();
      ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      ctx.restore();
    },
    drawLine(fromX, fromY, toX, toY) {
      let ctx = this.context;
      ctx.beginPath();
      ctx.lineWidth = this.lwidth;
      ctx.lineCap = "round";
      ctx.lineJoin = "round";
      ctx.moveTo(fromX, fromY);
      ctx.lineTo(toX, toY);
      ctx.stroke();
      ctx.closePath();
    },
    drawArrow(fromX, fromY, toX, toY) {
      let ctx = this.context;
      var headlen = 10;
      var theta = 45;
      var arrowX, arrowY;
      var angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI;
      var angle1 = ((angle + theta) * Math.PI) / 180;
      var angle2 = ((angle - theta) * Math.PI) / 180;
      var topX = headlen * Math.cos(angle1);
      var topY = headlen * Math.sin(angle1);
      var botX = headlen * Math.cos(angle2);
      var botY = headlen * Math.sin(angle2);
      ctx.beginPath();
      ctx.moveTo(fromX, fromY);
      ctx.lineTo(toX, toY);
      arrowX = toX + topX;
      arrowY = toY + topY;
      ctx.moveTo(arrowX, arrowY);
      ctx.lineTo(toX, toY);

      arrowX = toX + botX;
      arrowY = toY + botY;
      ctx.lineTo(arrowX, arrowY);

      ctx.stroke();
      ctx.closePath();
    },
    drawRect(topLeftX, topLeftY, botRightX, botRightY) {
      let ctx = this.context;
      ctx.beginPath();
      ctx.strokeRect(
        topLeftX,
        topLeftY,
        Math.abs(botRightX - topLeftX),
        Math.abs(botRightY - topLeftY)
      );
      ctx.closePath();
    },
    drawCircle(circleX, circleY, endX, endY) {
      let ctx = this.context;
      let radius = Math.sqrt(
        (circleX - endX) * (circleX - endX) +
          (circleY - endY) * (circleY - endY)
      );
      ctx.beginPath();
      ctx.arc(circleX, circleY, radius, 0, Math.PI * 2, true);
      ctx.stroke();
    },
    drawText(startX, startY, content) {
      let ctx = this.context;
      ctx.save();
      ctx.beginPath();
      ctx.font = "18px orbitron";
      ctx.textBaseline = "top";
      ctx.fillText(content, parseInt(startX), parseInt(startY));
      ctx.restore();
      ctx.closePath();
    },
    clearCanvas() {
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.init();
    },
    canvasDraw() {
      this.imageData = this.context.getImageData(
        0,
        0,
        this.canvas.width,
        this.canvas.height
      );
      this.historyImageData.push(this.imageData);
    },
    redo() {
      let historyImageData = this.historyImageData;
      let newHistoryImageData = this.newHistoryImageData;
      if (historyImageData.length > 0) {
        let hisImg = historyImageData.pop();
        newHistoryImageData.push(hisImg);
        if (historyImageData.length === 0) {
          this.imageData = null;
          this.clearCanvas();
          this.init();
        } else {
          this.context.putImageData(
            historyImageData[historyImageData.length - 1],
            0,
            0
          );
        }
      }
    },
    //反撤销
    cancelRedo() {
      if (this.newHistoryImageData.length > 0) {
        const newHisImg = this.newHistoryImageData.pop();
        this.imageData = newHisImg;
        this.context.putImageData(newHisImg, 0, 0);
        this.historyImageData.push(newHisImg);
      }
    },
    //保存图片
    downLoad() {
      const imgUrl = this.canvas.toDataURL("image/png");
      const a = document.createElement("a");
      a.href = imgUrl;
      this.subData.paths[this.topNowSelPage - 1] = imgUrl;
      // a.download = '绘图保存记录' + (new Date).getTime()
      // a.target = '_blank'
      // document.body.appendChild(a)
      // a.click()
      // document.body.removeChild(a);
    },
    submitBtn() {
      //防止多次点击提交
      this.loading = true;
      setTimeout(() => {
        this.loading = false;
      }, 3000);

      // let fileObj = {
      //   relativeType: 3,
      //   name: "编辑图片",
      // };
      let canvas = document.getElementById("canvas");
      var file = canvas.toDataURL("image/png");
      // var formData = new FormData();
      // let blob = this.dataURLtoFile(file, "image/jpg");
      // let fileOfBlob = new File([blob], new Date() + ".jpg");
      // formData.append("file", fileOfBlob);
      // formData.append("relativeType", 3);
      // formData.append("name", "编辑图片");
      //上传图片后提交保存,根据实际开发需求编写
      // console.log(file.toString());
      if (this.subData.score.length > 0) {
        this.subData.name = this.subData.blockName;
        this.subData.base64Lists = this.subData.paths;
        postBlockListInfo(this.subData).then((res) => {
          this.$modal.msgSuccess("保存成功!");
        });
      } else {
        this.$modal.msgError("未填写分数");
      }
    },
    changeBlock(item, index) {
      this.clickIndex = index;
      this.queryParams.pageNum = 1;
      this.nowBlockName = item.locationName;
      this.getBlockInfo(this.exId, item.locationName, this.queryParams);
      this.subData = item;
    },
    getList() {
      let that = this;
      getBlockList(that.exId).then((res) => {
        that.blockList = res.data;
        if (res.data && res.data.length > 0) {
          that.nowBlockName = res.data[0].locationName;
          that.getBlockInfo(
            that.exId,
            res.data[0].locationName,
            that.queryParams
          );
        }
      });
    },

    getBlockInfo(id, name, params) {
      let that = this;
      getBlockListInfo(id, name, params).then((res) => {
        that.nowBlockPageInfo = res.data.records;
        that.subData = res.data.records[0];
        that.topTotal = res.data.records[0].paths.length;
        that.total = res.data.total;
        if (res.data.records[0].paths[0].length > 70) {
          that.nowCanvasImg = this.nowBlockPageInfo[0].paths[0];
        } else {
          that.nowCanvasImg = this.baseURL + this.nowBlockPageInfo[0].paths[0];
        }
        that.init();
        window.onresize = function () {
          that.init();
        };
      });
    },
  },
  mounted() {
    let self = this;
    self.exId = self.$route.query.id;
    self.getList();
    self.init();
  },
  beforeMount() {},
};
</script>
   <style lang="scss" scoped>
.outBox {
  background-color: #f4f4f4;
  margin: 0;
  padding: 0;
  width: 100%;
  height: 95%;
  display: flex;
  justify-content: space-between;
  .blockInfo {
    width: 15%;
    background-color: #fff;
    height: 100%;
    overflow: auto;
    .toptitle,.toptitleBox {
      font-weight: bold;
      line-height: 30px;
      padding: 20px 50px;
      font-size: 20px;
      text-align: center;
    }
    .toptitleBox{
      display: flex;
      align-items: center;
      justify-content: space-between;
      width: 100%;
      padding: 10px 20px;
      font-weight: normal;
      font-size: 16px;
    }
    .firInfo {
      max-height: 500px;
      overflow: auto;
    }
    .blockList {
      width: 91%;
      margin: auto;
      padding: 10px 10px;
      font-size: 20px;
      color: #747070;
      display: flex;
      justify-content: space-between;
      cursor: pointer;
      align-items: center;
      .blockBtn {
        border: none;
        background-color: transparent;
        font-size: 20px;
        color: #747070 !important;
        overflow: hidden;
        width: 40%;
        white-space: nowrap;
        text-overflow: ellipsis;
        text-align: left;
      }
    }
    .blockList:hover {
      background-color: #f5f7fa;
      .blockBtn {
        border: none;
        background-color: transparent;
        font-size: 20px;
        overflow: hidden;
        width: 40%;
        white-space: nowrap;
        text-overflow: ellipsis;
        text-align: left;
      }
    }
    .blockListClick {
      background-color: #f5f7fa;
      color: #62affc;
      .blockBtn {
        border: none;
        background-color: transparent;
        font-size: 20px;
        color: #62affc !important;
        text-align: left;
      }
    }
  }
  .checkTest {
    width: 65%;
    margin: auto;
    height: 100%;
    overflow: auto;
    padding: 20px;
    // display: flex;
    // justify-content: center;
  }
}
</style>
<style lang="scss" scoped>
.container {
  // width: 100%;
  // height: 100%;
  // margin: 10px auto;
  // overflow: hidden;
}
.tool-container {
  width: 100%;
  border: 1px solid #62affc;
  border-radius: 10px;
  display: flex;
  justify-content: space-around;
  position: relative;
  margin: auto;
  margin-top: 20px;
  margin-bottom: 20px;
  align-items: center;
  height: 40px;
}
.drawPane {
  padding: 10px 10px;
  height: 130px;
  position: absolute;
  top: 20px;
  left: 0px;
  border-radius: 5px;
  border: 2px solid #f4f4f4;
  background-color: #f4f4f4;
  z-index: 999;
  .closeIcon {
    position: absolute;
    top: 4px;
    right: 4px;
  }
}
.close-draw-pane {
  position: absolute;
  right: 5px;
  top: 5px;
}
// .icon-div {
//   margin: 4px 12px;
// }
.icon :hover {
  cursor: pointer;
}
input {
  -webkit-appearance: none;
  width: 180px;
  height: 24px;
  outline: none;
  margin-bottom: 3px;
}
input::-webkit-slider-runnable-track {
  background-color: orangered;
  height: 4px;
  border-radius: 5px;
}
input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: orange;
  cursor: pointer;
  margin-top: -4px;
}
.text-style {
  // float: left;
  position: absolute;
  font: 14px orbitron;
  word-break: break-all;
  background-color: #fff;
  position: absolute;
  top: 160px;
}
.colorClass {
  color: orange;
}
.svg-icon {
  font-size: 24px;
}
.pageStyle .el-pagination {
  white-space: wrap;
}
</style>

 

 39、进度条自定义文字

【Vue + ElementUI】el-progress 各类常用场景(自动计算percentage,format自定义显示文字) - 掘金

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值