<template>
|
<div class="matching">
|
<div
|
class="connect"
|
id="connect"
|
ref="connect"
|
@mousemove="mousemove"
|
@mouseup="(e) => touchend(e)"
|
>
|
<div class="answer">
|
<div class="answer-box">
|
<div
|
class="answer-box-item"
|
v-for="(item, index) in leftArr"
|
:key="index"
|
ref="left"
|
@mousedown="(e) => touchstart(e, item, index)"
|
:style="{ backgroundColor: primaryColor,border:'1px solid' + bordercolor }"
|
v-html="item.label.txt"
|
></div>
|
</div>
|
<div class="answer-box">
|
<div
|
class="answer-box-item tl-le"
|
v-for="(item, index) in rightArr"
|
:key="index"
|
ref="right"
|
:style="{ backgroundColor: primaryColor,border:'1px solid' + bordercolor }"
|
v-html="item.label.txt"
|
></div>
|
</div>
|
</div>
|
<canvas
|
class="connect-canvasA"
|
:width="clientWidth"
|
:height="clientHeight"
|
ref="canvasA"
|
></canvas>
|
<canvas
|
class="connect-canvasB"
|
:width="clientWidth"
|
:height="clientHeight"
|
ref="canvasB"
|
></canvas>
|
</div>
|
<!-- 按钮 -->
|
<div class="btn-bottom">
|
<el-button @click="submitData">提交</el-button>
|
<el-button @click="saveData" :style="{ borderColor: bordercolor }"
|
>保存</el-button
|
>
|
<el-button @click="redo">重做</el-button>
|
<el-button @click="handleAnswer" :style="{ borderColor: bordercolor }"
|
>查看答案</el-button
|
>
|
</div>
|
<!-- 解析 -->
|
<ul class="show-answer" v-if="isShowAnswer">
|
<li v-if="isRight !== null">
|
答案结果:<span v-if="isRight" style="color: #83e089">正确</span>
|
<span v-if="isRight == false" style="color: #d81e06">错误</span>
|
</li>
|
<li class="show-answer-box">
|
<div>答案:</div>
|
<div>
|
<img :src="question.answerImg" alt="" class="w100" />
|
</div>
|
</li>
|
</ul>
|
</div>
|
</template>
|
|
<script>
|
export default {
|
data() {
|
return {
|
isDragging: false,
|
leftArr: [],
|
rightArr: [],
|
location: [],
|
canvasA: null,
|
canvasB: null,
|
leftDom: [],
|
rightDom: [],
|
clientWidth: 0,
|
clientHeight: 0,
|
scrollTop: 0,
|
debounce: false,
|
checkItem: null,
|
checkItemIndex: null,
|
isShowAnswer: false,
|
isRight: null,
|
value: [],
|
pageNum: null,
|
};
|
},
|
props: {
|
rawData: {
|
type: Object,
|
default: () => {
|
return {
|
left: [],
|
right: [],
|
};
|
},
|
},
|
question: {
|
type: Object,
|
default: () => {
|
return [];
|
},
|
},
|
primaryColor: {
|
type: String,
|
default: "#fff",
|
},
|
bordercolor:{
|
type:String,
|
default:'#f49a4c'
|
}
|
},
|
watch: {
|
rawData: {
|
immediate: true,
|
handler(val) {
|
if (val.left && val.left.length) this.init();
|
},
|
},
|
},
|
mounted() {
|
// 获取当前页码,用于匹配本次存储题目数据
|
this.pageNum = this.handlePage();
|
// 添加滚动事件 监听 解决因为滚动引起的拖动线不对的问题
|
window.addEventListener(
|
"scroll",
|
(e) => {
|
// 加个防抖
|
if (this.debounce) clearTimeout(this.debounce);
|
this.debounce = setTimeout(() => {
|
this.debounce = false;
|
this.scrollTop = e.target.scrollTop;
|
}, 500);
|
},
|
true
|
);
|
let connect = this.$refs.connect;
|
this.clientWidth = connect.clientWidth;
|
this.clientHeight = connect.clientHeight;
|
this.canvasA = this.$refs.canvasA.getContext("2d");
|
this.canvasB = this.$refs.canvasB.getContext("2d");
|
this.$nextTick(() => {
|
this.drawing();
|
});
|
this.getAnswer();
|
},
|
methods: {
|
init() {
|
this.leftArr = this.rawData.left.map((r) => {
|
return {
|
label: r,
|
line: [],
|
value: [],
|
};
|
});
|
this.rightArr = this.rawData.right.map((r) => {
|
return {
|
label: r,
|
};
|
});
|
// 等dom 渲染完成
|
this.$nextTick(() => {
|
this.leftDom = this.$refs.left.map((r, i) => {
|
return {
|
bom: r,
|
index: i,
|
};
|
});
|
this.rightDom = this.$refs.right.map((r, i) => {
|
return {
|
bom: r,
|
index: i,
|
};
|
});
|
this.value.map((r) => {
|
this.leftArr[r.left].line = this.attachment(r.left, r.right);
|
this.leftArr[r.left].value = [r.right];
|
});
|
this.drawing();
|
});
|
},
|
// 触摸结束
|
touchend(e, index) {
|
this.isDragging = false;
|
if (this.question.showAnswer) {
|
return false;
|
}
|
// let event = e.changedTouches[0];
|
// document.elementFromPoint 重点,根据x,y坐标 取当前元素 所有能运行的逻辑 都依托于这里。
|
let dom = (this.container ? this.container : document).elementFromPoint(
|
e.pageX,
|
e.pageY
|
);
|
// 右边的dom是哪个
|
let right = this.rightDom.find((r) => r.bom === dom);
|
// 不管是哪个都清除掉 底部的线
|
this.canvasB.clearRect(0, 0, this.clientWidth, this.clientHeight);
|
// 如果不是右边的dom 直接把 线 干掉 -- 证明不是 没有拖到右边上
|
if (!right) {
|
this.checkItem.line = [];
|
return;
|
}
|
// 如果已有的不是我自己 直接替换掉上一个的
|
if (this.checkItem.value[0] !== right.index) {
|
let model = this.leftArr.find((r) => r.value[0] === right.index);
|
if (model) {
|
model.value = [];
|
model.line = [];
|
}
|
this.checkItem.value = [right.index];
|
}
|
// 重新赋值 线的 x y 轴
|
this.checkItem.line = this.attachment(this.checkItemIndex, right.index);
|
this.drawing();
|
let model = this.leftArr
|
.map((r, i) => {
|
return {
|
left: i,
|
right: r.value[0],
|
};
|
})
|
.filter((r) => r.right !== undefined);
|
this.$emit("input", model);
|
this.question.userChoise = model;
|
// console.log(JSON.stringify(model));
|
},
|
// 触摸开始
|
touchstart(e, item, index) {
|
this.isDragging = true;
|
this.checkItem = item;
|
this.checkItemIndex = index;
|
e.stopPropagation();
|
// let event = e.targetTouches[0];
|
item.line = [
|
e.pageX,
|
e.pageY - this.$refs.connect.getBoundingClientRect().y + this.scrollTop,
|
];
|
},
|
// // 触摸中
|
// touchmove(e, item) {
|
// if(!this.isDragging) return false
|
// console.log('移动',e);
|
// if (this.question.showAnswer) {
|
// return false;
|
// }
|
// // let event = e.targetTouches[0];
|
// item.line[2] = e.pageX;
|
// item.line[3] = e.pageY - this.$refs.connect.getBoundingClientRect().y + this.scrollTop;
|
// this.backstrockline(item.line);
|
// },
|
|
// 移动中
|
mousemove(e) {
|
if (!this.isDragging) return false;
|
if (this.question.showAnswer) {
|
return false;
|
}
|
this.checkItem.line[2] = e.pageX;
|
this.checkItem.line[3] =
|
e.pageY - this.$refs.connect.getBoundingClientRect().y + this.scrollTop;
|
this.backstrockline(this.checkItem.line);
|
},
|
|
// 拖动的时候画线
|
backstrockline(val) {
|
let canvasB = this.canvasB;
|
canvasB.clearRect(0, 0, this.clientWidth, this.clientHeight);
|
canvasB.save();
|
canvasB.beginPath();
|
canvasB.lineWidth = 2;
|
canvasB.moveTo(val[0], val[1]);
|
canvasB.lineTo(val[2], val[3]);
|
canvasB.strokeStyle = "#0C6";
|
canvasB.stroke();
|
canvasB.restore();
|
},
|
// 渲染出拖动之后的线
|
drawing() {
|
let canvasA = this.canvasA;
|
this.canvasA.clearRect(0, 0, this.clientWidth, this.clientHeight);
|
canvasA.beginPath();
|
canvasA.lineWidth = 2;
|
for (let i = 0; i < this.leftArr.length; i++) {
|
const line = this.leftArr[i].line;
|
// console.log(line);
|
// console.log(this.leftArr[i]);
|
if (line.length) {
|
canvasA.moveTo(line[0], line[1]);
|
canvasA.lineTo(line[2], line[3]);
|
}
|
}
|
canvasA.strokeStyle = "#0C6";
|
canvasA.stroke();
|
},
|
// 根据 左边 和右边的 index,换算出 左右的X,Y轴坐标
|
attachment(index, rightIndex) {
|
let leftEvent = this.leftDom[index].bom;
|
let rightEvent = this.rightDom[rightIndex].bom;
|
// 为了让线都在中间 x轴不用改 最简单
|
let leftX = leftEvent.clientWidth + leftEvent.offsetLeft;
|
let rightX = rightEvent.offsetLeft;
|
let leftY = leftEvent.offsetTop + leftEvent.clientHeight / 2;
|
let rightY = rightEvent.offsetTop + rightEvent.clientHeight / 2;
|
return [leftX, leftY, rightX, rightY];
|
},
|
areArraysEqual(array1, array2) {
|
// 如果两个数组长度不同,则它们不等
|
if (array1.length !== array2.length) {
|
return false;
|
}
|
|
// 遍历数组并比较每个对象的属性
|
for (let i = 0; i < array1.length; i++) {
|
const obj1 = array1[i];
|
const obj2 = array2[i];
|
|
// 如果对象的left或right属性不相等,则数组不等
|
if (obj1.left !== obj2.left || obj1.right !== obj2.right) {
|
return false;
|
}
|
}
|
|
// 如果所有对象都匹配,则数组相等
|
return true;
|
},
|
// 查看答案
|
handleAnswer() {
|
this.isShowAnswer = !this.isShowAnswer;
|
},
|
// 提交
|
submitData() {
|
const answerArr = [];
|
const values = this.question.options.values;
|
for (let index = 0; index < values.length; index++) {
|
const item = values[index];
|
const rightIndex = this.question.options.linkValues.findIndex(
|
(citem) => citem.oldId == item.oldId
|
);
|
|
answerArr.push({
|
left: index,
|
right: rightIndex,
|
});
|
}
|
console.log(this.question.userChoise,answerArr);
|
this.isRight = this.areArraysEqual(this.question.userChoise, answerArr);
|
this.isShowAnswer = true;
|
},
|
// 获取当前页码
|
handlePage() {
|
let pageNum = null;
|
const element = (
|
this.container ? this.container : document
|
).querySelector("matching");
|
if (element) {
|
pageNum = this.getParentWithClass(element, "page-box").getAttribute(
|
"page"
|
);
|
}
|
return pageNum;
|
},
|
getParentWithClass(element, className) {
|
while (element.parentElement) {
|
element = element.parentElement;
|
if (element.classList.contains(className)) {
|
return element;
|
}
|
}
|
},
|
// 获取本地存储题目答案
|
getAnswer() {
|
const data = localStorage.getItem(
|
this.config.activeBook.name + "-matching-" + this.pageNum
|
);
|
if (data) {
|
this.value = JSON.parse(data);
|
}
|
},
|
// 保存
|
saveData() {
|
if (this.question.userChoise.length)
|
localStorage.setItem(
|
this.config.activeBook.name + "-matching-" + this.pageNum,
|
JSON.stringify(this.question.userChoise)
|
);
|
// console.log('保存成功',this.config.activeBook.name,this.pageNum);
|
},
|
// 重做
|
redo() {
|
this.question.showAnswer = false;
|
localStorage.removeItem(
|
this.config.activeBook.name + "-matching-" + this.pageNum
|
);
|
this.value = [];
|
for (let index = 0; index < this.leftArr.length; index++) {
|
const item = this.leftArr[index];
|
item.value = [];
|
item.line = [];
|
}
|
this.leftArr;
|
this.drawing();
|
this.isShowAnswer = false;
|
},
|
},
|
};
|
</script>
|
|
<style lang="less" scoped>
|
.connect {
|
position: relative;
|
padding: 10px;
|
|
&-canvasA {
|
position: absolute;
|
left: 0px;
|
top: 0px;
|
z-index: 1;
|
}
|
|
&-canvasB {
|
position: absolute;
|
left: 0px;
|
top: 0px;
|
z-index: 0;
|
}
|
}
|
|
.answer {
|
width: 100%;
|
display: flex;
|
justify-content: space-between;
|
|
&-box {
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
width: 40%;
|
text-align: center;
|
|
&-item {
|
cursor: pointer;
|
z-index: 2;
|
padding: 10px;
|
border-radius: 4px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
margin-bottom: 20px;
|
line-height: 24px;
|
padding: 6px;
|
}
|
|
&-item:last-child {
|
margin-bottom: 0;
|
}
|
}
|
}
|
.tl-le {
|
text-align: left;
|
}
|
.connect {
|
-webkit-user-select: none; /* Safari */
|
-moz-user-select: none; /* Firefox */
|
-ms-user-select: none; /* IE10+/Edge */
|
user-select: none; /* 标准语法 */
|
}
|
.show-answer {
|
margin: 30px auto;
|
width: 100%;
|
height: min-content;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
li {
|
padding: 10px;
|
}
|
}
|
.btn-bottom {
|
width: 70%;
|
margin: 70px auto 0 auto;
|
display: flex;
|
justify-content: space-evenly;
|
flex-wrap: wrap;
|
.el-button {
|
margin-top: 10px;
|
}
|
}
|
.el-button {
|
height: 30px;
|
padding: 7px;
|
min-width: 78px;
|
}
|
.answer-box-item {
|
/deep/ .un1 {
|
-webkit-text-emphasis-style: dot;
|
-moz-text-emphasis-style: dot;
|
-ms-text-emphasis-style: dot;
|
text-emphasis-style: dot;
|
-webkit-text-emphasis-position: under;
|
-moz-text-emphasis-position: under;
|
-ms-text-emphasis-position: under;
|
text-emphasis-position: under;
|
}
|
}
|
</style>
|