1、echarts官网汇总
series-radar雷达图 - makeapie echarts社区图表可视化案例
2、echarts官方文档
makeapie - ECharts文档 - echarts社区
3、js 比较时间前后 js 时间转时间戳
3.1、比较时间前后
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重新渲染
8、elementUI表单校验
vue中element ui Form表单自定义校验_el-form自定义校验规则-优快云博客
9、修改关系图节点形状
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、禁止浏览器回退
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画坐标
canvas画矩形
Vue中使用鼠标在Canvas上绘制矩形_技术服务生活的技术博客_51CTO博客
22、若依框架跳转大屏
跳转大屏(不可以的话就先建菜单再修改成目录) 添加路由
1、router.js
2、permission.js
3、新建文件
4、login.vue重定向
23、人工智能网站
24、贝塞尔曲线官网
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交互
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自定义显示文字) - 掘金